2
0

muc_messages.js 63 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184
  1. (function (root, factory) {
  2. define([
  3. "jasmine",
  4. "mock",
  5. "test-utils"
  6. ], factory);
  7. } (this, function (jasmine, mock, test_utils) {
  8. "use strict";
  9. const { Promise, Strophe, $msg, $pres, sizzle, stanza_utils } = converse.env;
  10. const u = converse.env.utils;
  11. describe("A Groupchat Message", function () {
  12. describe("an info message", function () {
  13. it("is not rendered as a followup message",
  14. mock.initConverse(
  15. ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  16. async function (done, _converse) {
  17. const muc_jid = 'lounge@montague.lit';
  18. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  19. const view = _converse.api.chatviews.get(muc_jid);
  20. await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length);
  21. const presence = u.toStanza(`
  22. <presence xmlns="jabber:client" to="${_converse.jid}" from="${muc_jid}/romeo">
  23. <x xmlns="http://jabber.org/protocol/muc#user">
  24. <status code="201"/>
  25. <item role="moderator" affiliation="owner" jid="${_converse.jid}"/>
  26. <status code="110"/>
  27. </x>
  28. </presence>
  29. `);
  30. _converse.connection._dataRecv(test_utils.createRequest(presence));
  31. await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
  32. const messages = view.el.querySelectorAll('.chat-info');
  33. expect(u.hasClass('chat-msg--followup', messages[0])).toBe(false);
  34. expect(u.hasClass('chat-msg--followup', messages[1])).toBe(false);
  35. done();
  36. }));
  37. it("is not shown if its a duplicate",
  38. mock.initConverse(
  39. ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  40. async function (done, _converse) {
  41. const muc_jid = 'lounge@montague.lit';
  42. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  43. const view = _converse.api.chatviews.get(muc_jid);
  44. await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length);
  45. expect(view.el.querySelectorAll('.chat-info').length).toBe(1);
  46. const presence = u.toStanza(`
  47. <presence xmlns="jabber:client" to="${_converse.jid}" from="${muc_jid}/romeo">
  48. <x xmlns="http://jabber.org/protocol/muc#user">
  49. <status code="201"/>
  50. <item role="moderator" affiliation="owner" jid="${_converse.jid}"/>
  51. <status code="110"/>
  52. </x>
  53. </presence>
  54. `);
  55. // XXX: We wait for createInfoMessages to complete, if we don't
  56. // we still get two info messages due to messages
  57. // created from presences not being queued and run
  58. // sequentially (i.e. by waiting for promises to resolve)
  59. // like we do with message stanzas.
  60. spyOn(view.model, 'createInfoMessages').and.callThrough();
  61. _converse.connection._dataRecv(test_utils.createRequest(presence));
  62. await u.waitUntil(() => view.model.createInfoMessages.calls.count());
  63. await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
  64. _converse.connection._dataRecv(test_utils.createRequest(presence));
  65. await u.waitUntil(() => view.model.createInfoMessages.calls.count() === 2);
  66. expect(view.el.querySelectorAll('.chat-info').length).toBe(2);
  67. done();
  68. }));
  69. });
  70. it("is rejected if it's an unencapsulated forwarded message",
  71. mock.initConverse(
  72. ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  73. async function (done, _converse) {
  74. const muc_jid = 'lounge@montague.lit';
  75. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  76. const impersonated_jid = `${muc_jid}/alice`;
  77. const received_stanza = u.toStanza(`
  78. <message to='${_converse.jid}' from='${muc_jid}/mallory' type='groupchat' id='${_converse.connection.getUniqueId()}'>
  79. <forwarded xmlns='urn:xmpp:forward:0'>
  80. <delay xmlns='urn:xmpp:delay' stamp='2019-07-10T23:08:25Z'/>
  81. <message from='${impersonated_jid}'
  82. id='0202197'
  83. to='${_converse.bare_jid}'
  84. type='groupchat'
  85. xmlns='jabber:client'>
  86. <body>Yet I should kill thee with much cherishing.</body>
  87. </message>
  88. </forwarded>
  89. </message>
  90. `);
  91. const view = _converse.api.chatviews.get(muc_jid);
  92. spyOn(view.model, 'onMessage').and.callThrough();
  93. await view.model.queueMessage(received_stanza);
  94. spyOn(converse.env.log, 'warn');
  95. _converse.connection._dataRecv(test_utils.createRequest(received_stanza));
  96. await u.waitUntil(() => view.model.onMessage.calls.count());
  97. expect(converse.env.log.warn).toHaveBeenCalledWith(
  98. 'onMessage: Ignoring unencapsulated forwarded groupchat message'
  99. );
  100. expect(view.el.querySelectorAll('.chat-msg').length).toBe(0);
  101. expect(view.model.messages.length).toBe(0);
  102. done();
  103. }));
  104. it("is specially marked when you are mentioned in it",
  105. mock.initConverse(
  106. ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  107. async function (done, _converse) {
  108. const muc_jid = 'lounge@montague.lit';
  109. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  110. const view = _converse.api.chatviews.get(muc_jid);
  111. if (!view.el.querySelectorAll('.chat-area').length) { view.renderChatArea(); }
  112. const message = 'romeo: Your attention is required';
  113. const nick = mock.chatroom_names[0],
  114. msg = $msg({
  115. from: 'lounge@montague.lit/'+nick,
  116. id: u.getUniqueId(),
  117. to: 'romeo@montague.lit',
  118. type: 'groupchat'
  119. }).c('body').t(message).tree();
  120. await view.model.queueMessage(msg);
  121. await new Promise(resolve => view.once('messageInserted', resolve));
  122. expect(u.hasClass('mentioned', view.el.querySelector('.chat-msg'))).toBeTruthy();
  123. done();
  124. }));
  125. it("can not be expected to have a unique id attribute",
  126. mock.initConverse(
  127. ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  128. async function (done, _converse) {
  129. const muc_jid = 'lounge@montague.lit';
  130. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  131. const view = _converse.api.chatviews.get(muc_jid);
  132. if (!view.el.querySelectorAll('.chat-area').length) { view.renderChatArea(); }
  133. const id = u.getUniqueId();
  134. let msg = $msg({
  135. from: 'lounge@montague.lit/some1',
  136. id: id,
  137. to: 'romeo@montague.lit',
  138. type: 'groupchat'
  139. }).c('body').t('First message').tree();
  140. await view.model.queueMessage(msg);
  141. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
  142. msg = $msg({
  143. from: 'lounge@montague.lit/some2',
  144. id: id,
  145. to: 'romeo@montague.lit',
  146. type: 'groupchat'
  147. }).c('body').t('Another message').tree();
  148. await view.model.queueMessage(msg);
  149. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 2);
  150. expect(view.model.messages.length).toBe(2);
  151. done();
  152. }));
  153. it("is ignored if it has the same archive-id of an already received one",
  154. mock.initConverse(
  155. ['rosterGroupsFetched'], {},
  156. async function (done, _converse) {
  157. const muc_jid = 'room@muc.example.com';
  158. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  159. const view = _converse.api.chatviews.get(muc_jid);
  160. spyOn(view.model, 'getDuplicateMessage').and.callThrough();
  161. let stanza = u.toStanza(`
  162. <message xmlns="jabber:client"
  163. from="room@muc.example.com/some1"
  164. to="${_converse.connection.jid}"
  165. type="groupchat">
  166. <body>Typical body text</body>
  167. <stanza-id xmlns="urn:xmpp:sid:0"
  168. id="5f3dbc5e-e1d3-4077-a492-693f3769c7ad"
  169. by="room@muc.example.com"/>
  170. </message>`);
  171. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  172. await u.waitUntil(() => view.model.messages.length === 1);
  173. await u.waitUntil(() => view.model.getDuplicateMessage.calls.count() === 1);
  174. let result = await view.model.getDuplicateMessage.calls.all()[0].returnValue;
  175. expect(result).toBe(undefined);
  176. stanza = u.toStanza(`
  177. <message xmlns="jabber:client"
  178. to="${_converse.connection.jid}"
  179. from="room@muc.example.com">
  180. <result xmlns="urn:xmpp:mam:2" queryid="82d9db27-6cf8-4787-8c2c-5a560263d823" id="5f3dbc5e-e1d3-4077-a492-693f3769c7ad">
  181. <forwarded xmlns="urn:xmpp:forward:0">
  182. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:17:23Z"/>
  183. <message from="room@muc.example.com/some1" type="groupchat">
  184. <body>Typical body text</body>
  185. </message>
  186. </forwarded>
  187. </result>
  188. </message>`);
  189. spyOn(view.model, 'updateMessage');
  190. await view.model.queueMessage(stanza);
  191. await u.waitUntil(() => view.model.getDuplicateMessage.calls.count() === 2);
  192. result = await view.model.getDuplicateMessage.calls.all()[1].returnValue;
  193. expect(result instanceof _converse.Message).toBe(true);
  194. expect(view.model.messages.length).toBe(1);
  195. await u.waitUntil(() => view.model.updateMessage.calls.count());
  196. done();
  197. }));
  198. it("is ignored if it has the same stanza-id of an already received one",
  199. mock.initConverse(
  200. ['rosterGroupsFetched'], {},
  201. async function (done, _converse) {
  202. const muc_jid = 'room@muc.example.com';
  203. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  204. const view = _converse.api.chatviews.get(muc_jid);
  205. spyOn(view.model, 'getStanzaIdQueryAttrs').and.callThrough();
  206. let stanza = u.toStanza(`
  207. <message xmlns="jabber:client"
  208. from="room@muc.example.com/some1"
  209. to="${_converse.connection.jid}"
  210. type="groupchat">
  211. <body>Typical body text</body>
  212. <stanza-id xmlns="urn:xmpp:sid:0"
  213. id="5f3dbc5e-e1d3-4077-a492-693f3769c7ad"
  214. by="room@muc.example.com"/>
  215. </message>`);
  216. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  217. await u.waitUntil(() => view.model.messages.length === 1);
  218. await u.waitUntil(() => view.model.getStanzaIdQueryAttrs.calls.count() === 1);
  219. let result = await view.model.getStanzaIdQueryAttrs.calls.all()[0].returnValue;
  220. expect(result instanceof Array).toBe(true);
  221. expect(result[0] instanceof Object).toBe(true);
  222. expect(result[0]['stanza_id room@muc.example.com']).toBe("5f3dbc5e-e1d3-4077-a492-693f3769c7ad");
  223. stanza = u.toStanza(`
  224. <message xmlns="jabber:client"
  225. from="room@muc.example.com/some1"
  226. to="${_converse.connection.jid}"
  227. type="groupchat">
  228. <body>Typical body text</body>
  229. <stanza-id xmlns="urn:xmpp:sid:0"
  230. id="5f3dbc5e-e1d3-4077-a492-693f3769c7ad"
  231. by="room@muc.example.com"/>
  232. </message>`);
  233. spyOn(view.model, 'updateMessage');
  234. spyOn(view.model, 'getDuplicateMessage').and.callThrough();
  235. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  236. await u.waitUntil(() => view.model.getDuplicateMessage.calls.count());
  237. result = await view.model.getDuplicateMessage.calls.all()[0].returnValue;
  238. expect(result instanceof _converse.Message).toBe(true);
  239. expect(view.model.messages.length).toBe(1);
  240. await u.waitUntil(() => view.model.updateMessage.calls.count());
  241. done();
  242. }));
  243. it("will be discarded if it's a malicious message meant to look like a carbon copy",
  244. mock.initConverse(
  245. ['rosterGroupsFetched'], {},
  246. async function (done, _converse) {
  247. await test_utils.waitForRoster(_converse, 'current');
  248. await test_utils.openControlBox(_converse);
  249. const muc_jid = 'xsf@muc.xmpp.org';
  250. const sender_jid = `${muc_jid}/romeo`;
  251. const impersonated_jid = `${muc_jid}/i_am_groot`
  252. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  253. const stanza = $pres({
  254. to: 'romeo@montague.lit/_converse.js-29092160',
  255. from: sender_jid
  256. })
  257. .c('x', {xmlns: Strophe.NS.MUC_USER})
  258. .c('item', {
  259. 'affiliation': 'none',
  260. 'jid': 'newguy@montague.lit/_converse.js-290929789',
  261. 'role': 'participant'
  262. }).tree();
  263. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  264. /*
  265. * <message to="romeo@montague.im/poezio" id="718d40df-3948-4798-a99b-35cc9f03cc4f-641" type="groupchat" from="xsf@muc.xmpp.org/romeo">
  266. * <received xmlns="urn:xmpp:carbons:2">
  267. * <forwarded xmlns="urn:xmpp:forward:0">
  268. * <message xmlns="jabber:client" to="xsf@muc.xmpp.org" type="groupchat" from="xsf@muc.xmpp.org/i_am_groot">
  269. * <body>I am groot.</body>
  270. * </message>
  271. * </forwarded>
  272. * </received>
  273. * </message>
  274. */
  275. const msg = $msg({
  276. 'from': sender_jid,
  277. 'id': _converse.connection.getUniqueId(),
  278. 'to': _converse.connection.jid,
  279. 'type': 'groupchat',
  280. 'xmlns': 'jabber:client'
  281. }).c('received', {'xmlns': 'urn:xmpp:carbons:2'})
  282. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  283. .c('message', {
  284. 'xmlns': 'jabber:client',
  285. 'from': impersonated_jid,
  286. 'to': muc_jid,
  287. 'type': 'groupchat'
  288. }).c('body').t('I am groot').tree();
  289. const view = _converse.api.chatviews.get(muc_jid);
  290. spyOn(converse.env.log, 'warn');
  291. await view.model.queueMessage(msg);
  292. expect(converse.env.log.warn).toHaveBeenCalledWith(
  293. 'onMessage: Ignoring XEP-0280 "groupchat" message carbon, '+
  294. 'according to the XEP groupchat messages SHOULD NOT be carbon copied'
  295. );
  296. expect(view.el.querySelectorAll('.chat-msg').length).toBe(0);
  297. expect(view.model.messages.length).toBe(0);
  298. done();
  299. }));
  300. it("keeps track of the sender's role and affiliation",
  301. mock.initConverse(
  302. ['rosterGroupsFetched'], {},
  303. async function (done, _converse) {
  304. const muc_jid = 'lounge@montague.lit';
  305. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  306. const view = _converse.api.chatviews.get(muc_jid);
  307. let msg = $msg({
  308. from: 'lounge@montague.lit/romeo',
  309. id: u.getUniqueId(),
  310. to: 'romeo@montague.lit',
  311. type: 'groupchat'
  312. }).c('body').t('I wrote this message!').tree();
  313. await view.model.queueMessage(msg);
  314. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length);
  315. expect(view.model.messages.last().occupant.get('affiliation')).toBe('owner');
  316. expect(view.model.messages.last().occupant.get('role')).toBe('moderator');
  317. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  318. expect(sizzle('.chat-msg', view.el).pop().classList.value.trim()).toBe('message chat-msg groupchat moderator owner');
  319. let presence = $pres({
  320. to:'romeo@montague.lit/orchard',
  321. from:'lounge@montague.lit/romeo',
  322. id: u.getUniqueId()
  323. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  324. .c('item').attrs({
  325. affiliation: 'member',
  326. jid: 'romeo@montague.lit/orchard',
  327. role: 'participant'
  328. }).up()
  329. .c('status').attrs({code:'110'}).up()
  330. .c('status').attrs({code:'210'}).nodeTree;
  331. _converse.connection._dataRecv(test_utils.createRequest(presence));
  332. msg = $msg({
  333. from: 'lounge@montague.lit/romeo',
  334. id: u.getUniqueId(),
  335. to: 'romeo@montague.lit',
  336. type: 'groupchat'
  337. }).c('body').t('Another message!').tree();
  338. await view.model.queueMessage(msg);
  339. await new Promise(resolve => view.once('messageInserted', resolve));
  340. expect(view.model.messages.last().occupant.get('affiliation')).toBe('member');
  341. expect(view.model.messages.last().occupant.get('role')).toBe('participant');
  342. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  343. expect(sizzle('.chat-msg', view.el).pop().classList.value.trim()).toBe('message chat-msg groupchat participant member');
  344. presence = $pres({
  345. to:'romeo@montague.lit/orchard',
  346. from:'lounge@montague.lit/romeo',
  347. id: u.getUniqueId()
  348. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  349. .c('item').attrs({
  350. affiliation: 'owner',
  351. jid: 'romeo@montague.lit/orchard',
  352. role: 'moderator'
  353. }).up()
  354. .c('status').attrs({code:'110'}).up()
  355. .c('status').attrs({code:'210'}).nodeTree;
  356. _converse.connection._dataRecv(test_utils.createRequest(presence));
  357. view.model.sendMessage('hello world');
  358. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 3);
  359. const occupant = await u.waitUntil(() => view.model.messages.filter(m => m.get('type') === 'groupchat')[2].occupant);
  360. expect(occupant.get('affiliation')).toBe('owner');
  361. expect(occupant.get('role')).toBe('moderator');
  362. expect(view.el.querySelectorAll('.chat-msg').length).toBe(3);
  363. await u.waitUntil(() => sizzle('.chat-msg', view.el).pop().classList.value.trim() === 'message chat-msg groupchat moderator owner');
  364. const add_events = view.model.occupants._events.add.length;
  365. msg = $msg({
  366. from: 'lounge@montague.lit/some1',
  367. id: u.getUniqueId(),
  368. to: 'romeo@montague.lit',
  369. type: 'groupchat'
  370. }).c('body').t('Message from someone not in the MUC right now').tree();
  371. await view.model.queueMessage(msg);
  372. await new Promise(resolve => view.once('messageInserted', resolve));
  373. expect(view.model.messages.last().occupant).toBeUndefined();
  374. // Check that there's a new "add" event handler, for when the occupant appears.
  375. expect(view.model.occupants._events.add.length).toBe(add_events+1);
  376. // Check that the occupant gets added/removed to the message as it
  377. // gets removed or added.
  378. presence = $pres({
  379. to:'romeo@montague.lit/orchard',
  380. from:'lounge@montague.lit/some1',
  381. id: u.getUniqueId()
  382. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  383. .c('item').attrs({jid: 'some1@montague.lit/orchard'});
  384. _converse.connection._dataRecv(test_utils.createRequest(presence));
  385. await u.waitUntil(() => view.model.messages.last().occupant);
  386. expect(view.model.messages.last().get('message')).toBe('Message from someone not in the MUC right now');
  387. expect(view.model.messages.last().occupant.get('nick')).toBe('some1');
  388. // Check that the "add" event handler was removed.
  389. expect(view.model.occupants._events.add.length).toBe(add_events);
  390. presence = $pres({
  391. to:'romeo@montague.lit/orchard',
  392. type: 'unavailable',
  393. from:'lounge@montague.lit/some1',
  394. id: u.getUniqueId()
  395. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  396. .c('item').attrs({jid: 'some1@montague.lit/orchard'});
  397. _converse.connection._dataRecv(test_utils.createRequest(presence));
  398. await u.waitUntil(() => !view.model.messages.last().occupant);
  399. expect(view.model.messages.last().get('message')).toBe('Message from someone not in the MUC right now');
  400. expect(view.model.messages.last().occupant).toBeUndefined();
  401. // Check that there's a new "add" event handler, for when the occupant appears.
  402. expect(view.model.occupants._events.add.length).toBe(add_events+1);
  403. presence = $pres({
  404. to:'romeo@montague.lit/orchard',
  405. from:'lounge@montague.lit/some1',
  406. id: u.getUniqueId()
  407. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  408. .c('item').attrs({jid: 'some1@montague.lit/orchard'});
  409. _converse.connection._dataRecv(test_utils.createRequest(presence));
  410. await u.waitUntil(() => view.model.messages.last().occupant);
  411. expect(view.model.messages.last().get('message')).toBe('Message from someone not in the MUC right now');
  412. expect(view.model.messages.last().occupant.get('nick')).toBe('some1');
  413. // Check that the "add" event handler was removed.
  414. expect(view.model.occupants._events.add.length).toBe(add_events);
  415. done();
  416. }));
  417. it("keeps track whether you are the sender or not",
  418. mock.initConverse(
  419. ['rosterGroupsFetched'], {},
  420. async function (done, _converse) {
  421. const muc_jid = 'lounge@montague.lit';
  422. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  423. const view = _converse.api.chatviews.get(muc_jid);
  424. const msg = $msg({
  425. from: 'lounge@montague.lit/romeo',
  426. id: u.getUniqueId(),
  427. to: 'romeo@montague.lit',
  428. type: 'groupchat'
  429. }).c('body').t('I wrote this message!').tree();
  430. await view.model.queueMessage(msg);
  431. expect(view.model.messages.last().get('sender')).toBe('me');
  432. done();
  433. }));
  434. it("can be replaced with a correction",
  435. mock.initConverse(
  436. ['rosterGroupsFetched'], {},
  437. async function (done, _converse) {
  438. const muc_jid = 'lounge@montague.lit';
  439. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  440. const view = _converse.api.chatviews.get(muc_jid);
  441. const stanza = $pres({
  442. to: 'romeo@montague.lit/_converse.js-29092160',
  443. from: 'coven@chat.shakespeare.lit/newguy'
  444. })
  445. .c('x', {xmlns: Strophe.NS.MUC_USER})
  446. .c('item', {
  447. 'affiliation': 'none',
  448. 'jid': 'newguy@montague.lit/_converse.js-290929789',
  449. 'role': 'participant'
  450. }).tree();
  451. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  452. const msg_id = u.getUniqueId();
  453. await view.model.queueMessage($msg({
  454. 'from': 'lounge@montague.lit/newguy',
  455. 'to': _converse.connection.jid,
  456. 'type': 'groupchat',
  457. 'id': msg_id,
  458. }).c('body').t('But soft, what light through yonder airlock breaks?').tree());
  459. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length);
  460. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  461. expect(view.el.querySelector('.chat-msg__text').textContent)
  462. .toBe('But soft, what light through yonder airlock breaks?');
  463. await view.model.queueMessage($msg({
  464. 'from': 'lounge@montague.lit/newguy',
  465. 'to': _converse.connection.jid,
  466. 'type': 'groupchat',
  467. 'id': u.getUniqueId(),
  468. }).c('body').t('But soft, what light through yonder chimney breaks?').up()
  469. .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
  470. await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
  471. 'But soft, what light through yonder chimney breaks?', 500);
  472. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  473. expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
  474. await view.model.queueMessage($msg({
  475. 'from': 'lounge@montague.lit/newguy',
  476. 'to': _converse.connection.jid,
  477. 'type': 'groupchat',
  478. 'id': u.getUniqueId(),
  479. }).c('body').t('But soft, what light through yonder window breaks?').up()
  480. .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
  481. await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
  482. 'But soft, what light through yonder window breaks?', 500);
  483. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  484. expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
  485. view.el.querySelector('.chat-msg__content .fa-edit').click();
  486. const modal = view.model.messages.at(0).message_versions_modal;
  487. await u.waitUntil(() => u.isVisible(modal.el), 1000);
  488. const older_msgs = modal.el.querySelectorAll('.older-msg');
  489. expect(older_msgs.length).toBe(2);
  490. expect(older_msgs[0].childNodes[2].textContent).toBe('But soft, what light through yonder airlock breaks?');
  491. expect(older_msgs[0].childNodes[0].nodeName).toBe('TIME');
  492. expect(older_msgs[1].childNodes[0].nodeName).toBe('TIME');
  493. expect(older_msgs[1].childNodes[2].textContent).toBe('But soft, what light through yonder chimney breaks?');
  494. done();
  495. }));
  496. it("can be sent as a correction by using the up arrow",
  497. mock.initConverse(
  498. ['rosterGroupsFetched'], {},
  499. async function (done, _converse) {
  500. const muc_jid = 'lounge@montague.lit';
  501. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  502. const view = _converse.api.chatviews.get(muc_jid);
  503. const textarea = view.el.querySelector('textarea.chat-textarea');
  504. expect(textarea.value).toBe('');
  505. view.onKeyDown({
  506. target: textarea,
  507. keyCode: 38 // Up arrow
  508. });
  509. expect(textarea.value).toBe('');
  510. textarea.value = 'But soft, what light through yonder airlock breaks?';
  511. view.onKeyDown({
  512. target: textarea,
  513. preventDefault: function preventDefault () {},
  514. keyCode: 13 // Enter
  515. });
  516. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
  517. expect(view.el.querySelector('.chat-msg__text').textContent)
  518. .toBe('But soft, what light through yonder airlock breaks?');
  519. const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
  520. expect(textarea.value).toBe('');
  521. view.onKeyDown({
  522. target: textarea,
  523. keyCode: 38 // Up arrow
  524. });
  525. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  526. expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
  527. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  528. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  529. expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true);
  530. spyOn(_converse.connection, 'send');
  531. textarea.value = 'But soft, what light through yonder window breaks?';
  532. view.onKeyDown({
  533. target: textarea,
  534. preventDefault: function preventDefault () {},
  535. keyCode: 13 // Enter
  536. });
  537. expect(_converse.connection.send).toHaveBeenCalled();
  538. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  539. const msg = _converse.connection.send.calls.all()[0].args[0];
  540. expect(msg.toLocaleString())
  541. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+
  542. `to="lounge@montague.lit" type="groupchat" `+
  543. `xmlns="jabber:client">`+
  544. `<body>But soft, what light through yonder window breaks?</body>`+
  545. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  546. `<replace id="${first_msg.get("msgid")}" xmlns="urn:xmpp:message-correct:0"/>`+
  547. `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  548. `</message>`);
  549. expect(view.model.messages.models.length).toBe(1);
  550. const corrected_message = view.model.messages.at(0);
  551. expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid'));
  552. expect(corrected_message.get('correcting')).toBe(false);
  553. const older_versions = corrected_message.get('older_versions');
  554. const keys = Object.keys(older_versions);
  555. expect(keys.length).toBe(1);
  556. expect(older_versions[keys[0]]).toBe('But soft, what light through yonder airlock breaks?');
  557. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  558. expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false);
  559. // Check that messages from other users are skipped
  560. await view.model.queueMessage($msg({
  561. 'from': muc_jid+'/someone-else',
  562. 'id': u.getUniqueId(),
  563. 'to': 'romeo@montague.lit',
  564. 'type': 'groupchat'
  565. }).c('body').t('Hello world').tree());
  566. await new Promise(resolve => view.once('messageInserted', resolve));
  567. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  568. // Test that pressing the down arrow cancels message correction
  569. expect(textarea.value).toBe('');
  570. view.onKeyDown({
  571. target: textarea,
  572. keyCode: 38 // Up arrow
  573. });
  574. expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
  575. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  576. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  577. await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
  578. expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
  579. view.onKeyDown({
  580. target: textarea,
  581. keyCode: 40 // Down arrow
  582. });
  583. expect(textarea.value).toBe('');
  584. expect(view.model.messages.at(0).get('correcting')).toBe(false);
  585. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  586. await u.waitUntil(() => !u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
  587. done();
  588. }));
  589. it("will be shown as received upon MUC reflection",
  590. mock.initConverse(
  591. ['rosterGroupsFetched'], {},
  592. async function (done, _converse) {
  593. await test_utils.waitForRoster(_converse, 'current');
  594. const muc_jid = 'lounge@montague.lit';
  595. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  596. const view = _converse.api.chatviews.get(muc_jid);
  597. const textarea = view.el.querySelector('textarea.chat-textarea');
  598. textarea.value = 'But soft, what light through yonder airlock breaks?';
  599. view.onKeyDown({
  600. target: textarea,
  601. preventDefault: function preventDefault () {},
  602. keyCode: 13 // Enter
  603. });
  604. await new Promise(resolve => view.once('messageInserted', resolve));
  605. expect(view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length).toBe(0);
  606. const msg_obj = view.model.messages.at(0);
  607. const stanza = u.toStanza(`
  608. <message xmlns="jabber:client"
  609. from="${msg_obj.get('from')}"
  610. to="${_converse.connection.jid}"
  611. type="groupchat">
  612. <body>${msg_obj.get('message')}</body>
  613. <stanza-id xmlns="urn:xmpp:sid:0"
  614. id="5f3dbc5e-e1d3-4077-a492-693f3769c7ad"
  615. by="lounge@montague.lit"/>
  616. <origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
  617. </message>`);
  618. await view.model.queueMessage(stanza);
  619. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
  620. expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
  621. expect(view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length).toBe(1);
  622. expect(view.model.messages.length).toBe(1);
  623. const message = view.model.messages.at(0);
  624. expect(message.get('stanza_id lounge@montague.lit')).toBe('5f3dbc5e-e1d3-4077-a492-693f3769c7ad');
  625. expect(message.get('origin_id')).toBe(msg_obj.get('origin_id'));
  626. done();
  627. }));
  628. it("gets updated with its stanza-id upon MUC reflection",
  629. mock.initConverse(
  630. ['rosterGroupsFetched'], {},
  631. async function (done, _converse) {
  632. const muc_jid = 'room@muc.example.com';
  633. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  634. const view = _converse.api.chatviews.get(muc_jid);
  635. view.model.sendMessage('hello world');
  636. await u.waitUntil(() => view.model.messages.length === 1);
  637. const msg = view.model.messages.at(0);
  638. expect(msg.get('stanza_id')).toBeUndefined();
  639. expect(msg.get('origin_id')).toBe(msg.get('origin_id'));
  640. const stanza = u.toStanza(`
  641. <message xmlns="jabber:client"
  642. from="room@muc.example.com/romeo"
  643. to="${_converse.connection.jid}"
  644. type="groupchat">
  645. <body>Hello world</body>
  646. <stanza-id xmlns="urn:xmpp:sid:0"
  647. id="5f3dbc5e-e1d3-4077-a492-693f3769c7ad"
  648. by="room@muc.example.com"/>
  649. <origin-id xmlns="urn:xmpp:sid:0" id="${msg.get('origin_id')}"/>
  650. </message>`);
  651. spyOn(view.model, 'updateMessage').and.callThrough();
  652. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  653. await u.waitUntil(() => view.model.updateMessage.calls.count() === 1);
  654. expect(view.model.messages.length).toBe(1);
  655. expect(view.model.messages.at(0).get('stanza_id room@muc.example.com')).toBe("5f3dbc5e-e1d3-4077-a492-693f3769c7ad");
  656. expect(view.model.messages.at(0).get('origin_id')).toBe(msg.get('origin_id'));
  657. done();
  658. }));
  659. it("can cause a delivery receipt to be returned",
  660. mock.initConverse(
  661. ['rosterGroupsFetched'], {},
  662. async function (done, _converse) {
  663. await test_utils.waitForRoster(_converse, 'current');
  664. const muc_jid = 'lounge@montague.lit';
  665. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  666. const view = _converse.api.chatviews.get(muc_jid);
  667. const textarea = view.el.querySelector('textarea.chat-textarea');
  668. textarea.value = 'But soft, what light through yonder airlock breaks?';
  669. view.onKeyDown({
  670. target: textarea,
  671. preventDefault: function preventDefault () {},
  672. keyCode: 13 // Enter
  673. });
  674. await new Promise(resolve => view.once('messageInserted', resolve));
  675. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  676. const msg_obj = view.model.messages.at(0);
  677. const stanza = u.toStanza(`
  678. <message xml:lang="en" to="romeo@montague.lit/orchard"
  679. from="lounge@montague.lit/some1" type="groupchat" xmlns="jabber:client">
  680. <received xmlns="urn:xmpp:receipts" id="${msg_obj.get('msgid')}"/>
  681. <origin-id xmlns="urn:xmpp:sid:0" id="CE08D448-5ED8-4B6A-BB5B-07ED9DFE4FF0"/>
  682. </message>`);
  683. spyOn(_converse.api, "trigger").and.callThrough();
  684. spyOn(stanza_utils, "isReceipt").and.callThrough();
  685. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  686. await u.waitUntil(() => stanza_utils.isReceipt.calls.count() === 1);
  687. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  688. expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
  689. expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
  690. done();
  691. }));
  692. it("can cause a chat marker to be returned",
  693. mock.initConverse(
  694. ['rosterGroupsFetched'], {},
  695. async function (done, _converse) {
  696. await test_utils.waitForRoster(_converse, 'current');
  697. const muc_jid = 'lounge@montague.lit';
  698. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  699. const view = _converse.api.chatviews.get(muc_jid);
  700. const textarea = view.el.querySelector('textarea.chat-textarea');
  701. textarea.value = 'But soft, what light through yonder airlock breaks?';
  702. view.onKeyDown({
  703. target: textarea,
  704. preventDefault: function preventDefault () {},
  705. keyCode: 13 // Enter
  706. });
  707. await new Promise(resolve => view.once('messageInserted', resolve));
  708. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  709. expect(view.el.querySelector('.chat-msg .chat-msg__body').textContent.trim())
  710. .toBe("But soft, what light through yonder airlock breaks?");
  711. const msg_obj = view.model.messages.at(0);
  712. let stanza = u.toStanza(`
  713. <message xml:lang="en" to="romeo@montague.lit/orchard"
  714. from="lounge@montague.lit/some1" type="groupchat" xmlns="jabber:client">
  715. <received xmlns="urn:xmpp:chat-markers:0" id="${msg_obj.get('msgid')}"/>
  716. </message>`);
  717. const stanza_utils = converse.env.stanza_utils;
  718. spyOn(stanza_utils, "isChatMarker").and.callThrough();
  719. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  720. await u.waitUntil(() => stanza_utils.isChatMarker.calls.count() === 1);
  721. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  722. expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
  723. stanza = u.toStanza(`
  724. <message xml:lang="en" to="romeo@montague.lit/orchard"
  725. from="lounge@montague.lit/some1" type="groupchat" xmlns="jabber:client">
  726. <displayed xmlns="urn:xmpp:chat-markers:0" id="${msg_obj.get('msgid')}"/>
  727. </message>`);
  728. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  729. await u.waitUntil(() => stanza_utils.isChatMarker.calls.count() === 2);
  730. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  731. expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
  732. stanza = u.toStanza(`
  733. <message xml:lang="en" to="romeo@montague.lit/orchard"
  734. from="lounge@montague.lit/some1" type="groupchat" xmlns="jabber:client">
  735. <acknowledged xmlns="urn:xmpp:chat-markers:0" id="${msg_obj.get('msgid')}"/>
  736. </message>`);
  737. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  738. await u.waitUntil(() => stanza_utils.isChatMarker.calls.count() === 3);
  739. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  740. expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
  741. stanza = u.toStanza(`
  742. <message xml:lang="en" to="romeo@montague.lit/orchard"
  743. from="lounge@montague.lit/some1" type="groupchat" xmlns="jabber:client">
  744. <body>'tis I!</body>
  745. <markable xmlns="urn:xmpp:chat-markers:0"/>
  746. </message>`);
  747. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  748. await u.waitUntil(() => stanza_utils.isChatMarker.calls.count() === 4);
  749. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  750. expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
  751. done();
  752. }));
  753. describe("when received", function () {
  754. it("highlights all users mentioned via XEP-0372 references",
  755. mock.initConverse(
  756. ['rosterGroupsFetched'], {},
  757. async function (done, _converse) {
  758. const muc_jid = 'lounge@montague.lit';
  759. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'tom');
  760. const view = _converse.api.chatviews.get(muc_jid);
  761. ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => {
  762. _converse.connection._dataRecv(test_utils.createRequest(
  763. $pres({
  764. 'to': 'tom@montague.lit/resource',
  765. 'from': `lounge@montague.lit/${nick}`
  766. })
  767. .c('x', {xmlns: Strophe.NS.MUC_USER})
  768. .c('item', {
  769. 'affiliation': 'none',
  770. 'jid': `${nick}@montague.lit/resource`,
  771. 'role': 'participant'
  772. }))
  773. );
  774. });
  775. const msg = $msg({
  776. from: 'lounge@montague.lit/gibson',
  777. id: u.getUniqueId(),
  778. to: 'romeo@montague.lit',
  779. type: 'groupchat'
  780. }).c('body').t('hello z3r0 tom mr.robot, how are you?').up()
  781. .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'6', 'end':'10', 'type':'mention', 'uri':'xmpp:z3r0@montague.lit'}).up()
  782. .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'11', 'end':'14', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).up()
  783. .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'15', 'end':'23', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree;
  784. await view.model.queueMessage(msg);
  785. const message = await u.waitUntil(() => view.el.querySelector('.chat-msg__text'));
  786. expect(message.classList.length).toEqual(1);
  787. expect(message.innerHTML).toBe(
  788. 'hello <span class="mention">z3r0</span> '+
  789. '<span class="mention mention--self badge badge-info">tom</span> '+
  790. '<span class="mention">mr.robot</span>, how are you?');
  791. done();
  792. }));
  793. });
  794. describe("in which someone is mentioned", function () {
  795. it("gets parsed for mentions which get turned into references",
  796. mock.initConverse(
  797. ['rosterGroupsFetched'], {},
  798. async function (done, _converse) {
  799. const muc_jid = 'lounge@montague.lit';
  800. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'tom');
  801. const view = _converse.api.chatviews.get(muc_jid);
  802. ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh', 'Link Mauve'].forEach((nick) => {
  803. _converse.connection._dataRecv(test_utils.createRequest(
  804. $pres({
  805. 'to': 'tom@montague.lit/resource',
  806. 'from': `lounge@montague.lit/${nick}`
  807. })
  808. .c('x', {xmlns: Strophe.NS.MUC_USER})
  809. .c('item', {
  810. 'affiliation': 'none',
  811. 'jid': `${nick.replace(/\s/g, '-')}@montague.lit/resource`,
  812. 'role': 'participant'
  813. })));
  814. });
  815. // Also check that nicks from received messages, (but for which
  816. // we don't have occupant objects) can be mentioned.
  817. const stanza = u.toStanza(`
  818. <message xmlns="jabber:client"
  819. from="${muc_jid}/gh0st"
  820. to="${_converse.connection.bare_jid}"
  821. type="groupchat">
  822. <body>Boo!</body>
  823. </message>`);
  824. await view.model.queueMessage(stanza);
  825. // Run a few unit tests for the parseTextForReferences method
  826. let [text, references] = view.model.parseTextForReferences('hello z3r0')
  827. expect(references.length).toBe(0);
  828. expect(text).toBe('hello z3r0');
  829. [text, references] = view.model.parseTextForReferences('hello @z3r0')
  830. expect(references.length).toBe(1);
  831. expect(text).toBe('hello z3r0');
  832. expect(JSON.stringify(references))
  833. .toBe('[{"begin":6,"end":10,"value":"z3r0","type":"mention","uri":"xmpp:z3r0@montague.lit"}]');
  834. [text, references] = view.model.parseTextForReferences('hello @some1 @z3r0 @gibson @mr.robot, how are you?')
  835. expect(text).toBe('hello @some1 z3r0 gibson mr.robot, how are you?');
  836. expect(JSON.stringify(references))
  837. .toBe('[{"begin":13,"end":17,"value":"z3r0","type":"mention","uri":"xmpp:z3r0@montague.lit"},'+
  838. '{"begin":18,"end":24,"value":"gibson","type":"mention","uri":"xmpp:gibson@montague.lit"},'+
  839. '{"begin":25,"end":33,"value":"mr.robot","type":"mention","uri":"xmpp:mr.robot@montague.lit"}]');
  840. [text, references] = view.model.parseTextForReferences('yo @gib')
  841. expect(text).toBe('yo @gib');
  842. expect(references.length).toBe(0);
  843. [text, references] = view.model.parseTextForReferences('yo @gibsonian')
  844. expect(text).toBe('yo @gibsonian');
  845. expect(references.length).toBe(0);
  846. [text, references] = view.model.parseTextForReferences('@gibson')
  847. expect(text).toBe('gibson');
  848. expect(references.length).toBe(1);
  849. expect(JSON.stringify(references))
  850. .toBe('[{"begin":0,"end":6,"value":"gibson","type":"mention","uri":"xmpp:gibson@montague.lit"}]');
  851. [text, references] = view.model.parseTextForReferences('hi @Link Mauve how are you?')
  852. expect(text).toBe('hi Link Mauve how are you?');
  853. expect(references.length).toBe(1);
  854. expect(JSON.stringify(references))
  855. .toBe('[{"begin":3,"end":13,"value":"Link Mauve","type":"mention","uri":"xmpp:Link-Mauve@montague.lit"}]');
  856. [text, references] = view.model.parseTextForReferences('https://example.org/@gibson')
  857. expect(text).toBe('https://example.org/@gibson');
  858. expect(references.length).toBe(0);
  859. expect(JSON.stringify(references))
  860. .toBe('[]');
  861. [text, references] = view.model.parseTextForReferences('mail@gibson.com')
  862. expect(text).toBe('mail@gibson.com');
  863. expect(references.length).toBe(0);
  864. expect(JSON.stringify(references))
  865. .toBe('[]');
  866. [text, references] = view.model.parseTextForReferences(
  867. 'https://linkmauve.fr@Link Mauve/ https://linkmauve.fr/@github/is_back gibson@gibson.com gibson@Link Mauve.fr')
  868. expect(text).toBe(
  869. 'https://linkmauve.fr@Link Mauve/ https://linkmauve.fr/@github/is_back gibson@gibson.com gibson@Link Mauve.fr');
  870. expect(references.length).toBe(0);
  871. expect(JSON.stringify(references))
  872. .toBe('[]');
  873. [text, references] = view.model.parseTextForReferences('@gh0st where are you?')
  874. expect(text).toBe('gh0st where are you?');
  875. expect(references.length).toBe(1);
  876. expect(JSON.stringify(references))
  877. .toBe('[{"begin":0,"end":5,"value":"gh0st","type":"mention","uri":"xmpp:lounge@montague.lit/gh0st"}]');
  878. done();
  879. }));
  880. it("parses for mentions as indicated with an @ preceded by a space or at the start of the text",
  881. mock.initConverse(
  882. ['rosterGroupsFetched'], {},
  883. async function (done, _converse) {
  884. const muc_jid = 'lounge@montague.lit';
  885. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'tom');
  886. const view = _converse.api.chatviews.get(muc_jid);
  887. ['NotAnAdress', 'darnuria'].forEach((nick) => {
  888. _converse.connection._dataRecv(test_utils.createRequest(
  889. $pres({
  890. 'to': 'tom@montague.lit/resource',
  891. 'from': `lounge@montague.lit/${nick}`
  892. })
  893. .c('x', {xmlns: Strophe.NS.MUC_USER})
  894. .c('item', {
  895. 'affiliation': 'none',
  896. 'jid': `${nick.replace(/\s/g, '-')}@montague.lit/resource`,
  897. 'role': 'participant'
  898. })));
  899. });
  900. // Test that we don't match @nick in email adresses.
  901. let [text, references] = view.model.parseTextForReferences('contact contact@NotAnAdress.eu');
  902. expect(references.length).toBe(0);
  903. expect(text).toBe('contact contact@NotAnAdress.eu');
  904. // Test that we don't match @nick in url
  905. [text, references] = view.model.parseTextForReferences('nice website https://darnuria.eu/@darnuria');
  906. expect(references.length).toBe(0);
  907. expect(text).toBe('nice website https://darnuria.eu/@darnuria');
  908. done();
  909. }));
  910. it("properly encodes the URIs in sent out references",
  911. mock.initConverse(
  912. ['rosterGroupsFetched'], {},
  913. async function (done, _converse) {
  914. const muc_jid = 'lounge@montague.lit';
  915. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'tom');
  916. const view = _converse.api.roomviews.get(muc_jid);
  917. _converse.connection._dataRecv(test_utils.createRequest(
  918. $pres({
  919. 'to': 'tom@montague.lit/resource',
  920. 'from': `lounge@montague.lit/Link Mauve`
  921. })
  922. .c('x', {xmlns: Strophe.NS.MUC_USER})
  923. .c('item', {
  924. 'affiliation': 'none',
  925. 'role': 'participant'
  926. })));
  927. await u.waitUntil(() => view.model.occupants.length === 2);
  928. const textarea = view.el.querySelector('textarea.chat-textarea');
  929. textarea.value = 'hello @Link Mauve'
  930. const enter_event = {
  931. 'target': textarea,
  932. 'preventDefault': function preventDefault () {},
  933. 'stopPropagation': function stopPropagation () {},
  934. 'keyCode': 13 // Enter
  935. }
  936. spyOn(_converse.connection, 'send');
  937. view.onKeyDown(enter_event);
  938. await new Promise(resolve => view.once('messageInserted', resolve));
  939. const msg = _converse.connection.send.calls.all()[0].args[0];
  940. expect(msg.toLocaleString())
  941. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+
  942. `to="lounge@montague.lit" type="groupchat" `+
  943. `xmlns="jabber:client">`+
  944. `<body>hello Link Mauve</body>`+
  945. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  946. `<reference begin="6" end="16" type="mention" uri="xmpp:lounge@montague.lit/Link%20Mauve" xmlns="urn:xmpp:reference:0"/>`+
  947. `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  948. `</message>`);
  949. done();
  950. }));
  951. it("can get corrected and given new references",
  952. mock.initConverse(
  953. ['rosterGroupsFetched'], {},
  954. async function (done, _converse) {
  955. const muc_jid = 'lounge@montague.lit';
  956. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'tom');
  957. const view = _converse.api.chatviews.get(muc_jid);
  958. ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => {
  959. _converse.connection._dataRecv(test_utils.createRequest(
  960. $pres({
  961. 'to': 'tom@montague.lit/resource',
  962. 'from': `lounge@montague.lit/${nick}`
  963. })
  964. .c('x', {xmlns: Strophe.NS.MUC_USER})
  965. .c('item', {
  966. 'affiliation': 'none',
  967. 'jid': `${nick}@montague.lit/resource`,
  968. 'role': 'participant'
  969. })));
  970. });
  971. await u.waitUntil(() => view.model.occupants.length === 5);
  972. const textarea = view.el.querySelector('textarea.chat-textarea');
  973. textarea.value = 'hello @z3r0 @gibson @mr.robot, how are you?'
  974. const enter_event = {
  975. 'target': textarea,
  976. 'preventDefault': function preventDefault () {},
  977. 'stopPropagation': function stopPropagation () {},
  978. 'keyCode': 13 // Enter
  979. }
  980. spyOn(_converse.connection, 'send');
  981. view.onKeyDown(enter_event);
  982. await new Promise(resolve => view.once('messageInserted', resolve));
  983. const msg = _converse.connection.send.calls.all()[0].args[0];
  984. expect(msg.toLocaleString())
  985. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+
  986. `to="lounge@montague.lit" type="groupchat" `+
  987. `xmlns="jabber:client">`+
  988. `<body>hello z3r0 gibson mr.robot, how are you?</body>`+
  989. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  990. `<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  991. `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  992. `<reference begin="18" end="26" type="mention" uri="xmpp:mr.robot@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  993. `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  994. `</message>`);
  995. const action = view.el.querySelector('.chat-msg .chat-msg__action');
  996. action.style.opacity = 1;
  997. action.click();
  998. expect(textarea.value).toBe('hello @z3r0 @gibson @mr.robot, how are you?');
  999. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  1000. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  1001. await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
  1002. await u.waitUntil(() => _converse.connection.send.calls.count() === 2);
  1003. textarea.value = 'hello @z3r0 @gibson @sw0rdf1sh, how are you?';
  1004. view.onKeyDown(enter_event);
  1005. await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
  1006. 'hello z3r0 gibson sw0rdf1sh, how are you?', 500);
  1007. const correction = _converse.connection.send.calls.all()[2].args[0];
  1008. expect(correction.toLocaleString())
  1009. .toBe(`<message from="romeo@montague.lit/orchard" id="${correction.nodeTree.getAttribute("id")}" `+
  1010. `to="lounge@montague.lit" type="groupchat" `+
  1011. `xmlns="jabber:client">`+
  1012. `<body>hello z3r0 gibson sw0rdf1sh, how are you?</body>`+
  1013. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  1014. `<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  1015. `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  1016. `<reference begin="18" end="27" type="mention" uri="xmpp:sw0rdf1sh@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  1017. `<replace id="${msg.nodeTree.getAttribute("id")}" xmlns="urn:xmpp:message-correct:0"/>`+
  1018. `<origin-id id="${correction.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  1019. `</message>`);
  1020. done();
  1021. }));
  1022. it("includes XEP-0372 references to that person",
  1023. mock.initConverse(
  1024. ['rosterGroupsFetched'], {},
  1025. async function (done, _converse) {
  1026. const muc_jid = 'lounge@montague.lit';
  1027. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  1028. const view = _converse.api.chatviews.get(muc_jid);
  1029. ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => {
  1030. _converse.connection._dataRecv(test_utils.createRequest(
  1031. $pres({
  1032. 'to': 'tom@montague.lit/resource',
  1033. 'from': `lounge@montague.lit/${nick}`
  1034. })
  1035. .c('x', {xmlns: Strophe.NS.MUC_USER})
  1036. .c('item', {
  1037. 'affiliation': 'none',
  1038. 'jid': `${nick}@montague.lit/resource`,
  1039. 'role': 'participant'
  1040. })));
  1041. });
  1042. await u.waitUntil(() => view.model.occupants.length === 5);
  1043. spyOn(_converse.connection, 'send');
  1044. const textarea = view.el.querySelector('textarea.chat-textarea');
  1045. textarea.value = 'hello @z3r0 @gibson @mr.robot, how are you?'
  1046. const enter_event = {
  1047. 'target': textarea,
  1048. 'preventDefault': function preventDefault () {},
  1049. 'stopPropagation': function stopPropagation () {},
  1050. 'keyCode': 13 // Enter
  1051. }
  1052. view.onKeyDown(enter_event);
  1053. await new Promise(resolve => view.once('messageInserted', resolve));
  1054. const msg = _converse.connection.send.calls.all()[0].args[0];
  1055. expect(msg.toLocaleString())
  1056. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+
  1057. `to="lounge@montague.lit" type="groupchat" `+
  1058. `xmlns="jabber:client">`+
  1059. `<body>hello z3r0 gibson mr.robot, how are you?</body>`+
  1060. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  1061. `<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  1062. `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  1063. `<reference begin="18" end="26" type="mention" uri="xmpp:mr.robot@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  1064. `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  1065. `</message>`);
  1066. done();
  1067. }));
  1068. });
  1069. });
  1070. }));