mentions.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. /*global mock, converse */
  2. const { Strophe, $msg, $pres } = converse.env;
  3. const u = converse.env.utils;
  4. describe("An incoming groupchat message", function () {
  5. it("is specially marked when you are mentioned in it",
  6. mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
  7. const muc_jid = 'lounge@montague.lit';
  8. await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  9. const view = _converse.api.chatviews.get(muc_jid);
  10. if (!view.querySelectorAll('.chat-area').length) { view.renderChatArea(); }
  11. const message = 'romeo: Your attention is required';
  12. const nick = mock.chatroom_names[0],
  13. msg = $msg({
  14. from: 'lounge@montague.lit/'+nick,
  15. id: u.getUniqueId(),
  16. to: 'romeo@montague.lit',
  17. type: 'groupchat'
  18. }).c('body').t(message).tree();
  19. await view.model.handleMessageStanza(msg);
  20. await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
  21. expect(u.hasClass('mentioned', view.querySelector('.chat-msg'))).toBeTruthy();
  22. done();
  23. }));
  24. it("highlights all users mentioned via XEP-0372 references",
  25. mock.initConverse([], {}, async function (done, _converse) {
  26. const muc_jid = 'lounge@montague.lit';
  27. await mock.openAndEnterChatRoom(_converse, muc_jid, 'tom');
  28. const view = _converse.api.chatviews.get(muc_jid);
  29. ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => {
  30. _converse.connection._dataRecv(mock.createRequest(
  31. $pres({
  32. 'to': 'tom@montague.lit/resource',
  33. 'from': `lounge@montague.lit/${nick}`
  34. })
  35. .c('x', {xmlns: Strophe.NS.MUC_USER})
  36. .c('item', {
  37. 'affiliation': 'none',
  38. 'jid': `${nick}@montague.lit/resource`,
  39. 'role': 'participant'
  40. }))
  41. );
  42. });
  43. let msg = $msg({
  44. from: 'lounge@montague.lit/gibson',
  45. id: u.getUniqueId(),
  46. to: 'romeo@montague.lit',
  47. type: 'groupchat'
  48. }).c('body').t('hello z3r0 tom mr.robot, how are you?').up()
  49. .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'6', 'end':'10', 'type':'mention', 'uri':'xmpp:z3r0@montague.lit'}).up()
  50. .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'11', 'end':'14', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).up()
  51. .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'15', 'end':'23', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree;
  52. await view.model.handleMessageStanza(msg);
  53. await u.waitUntil(() => view.querySelector('.chat-msg__text')?.innerHTML.replace(/<!---->/g, '') ===
  54. 'hello <span class="mention">z3r0</span> '+
  55. '<span class="mention mention--self badge badge-info">tom</span> '+
  56. '<span class="mention">mr.robot</span>, how are you?');
  57. let message = view.querySelector('.chat-msg__text')
  58. expect(message.classList.length).toEqual(1);
  59. msg = $msg({
  60. from: 'lounge@montague.lit/sw0rdf1sh',
  61. id: u.getUniqueId(),
  62. to: 'romeo@montague.lit',
  63. type: 'groupchat'
  64. }).c('body').t('https://conversejs.org/@gibson').up()
  65. .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'23', 'end':'29', 'type':'mention', 'uri':'xmpp:gibson@montague.lit'}).nodeTree;
  66. await view.model.handleMessageStanza(msg);
  67. message = await u.waitUntil(() => view.querySelector('.chat-msg__text'));
  68. expect(message.classList.length).toEqual(1);
  69. expect(message.innerHTML.replace(/<!---->/g, '')).toBe(
  70. 'hello <span class="mention">z3r0</span> '+
  71. '<span class="mention mention--self badge badge-info">tom</span> '+
  72. '<span class="mention">mr.robot</span>, how are you?');
  73. done();
  74. }));
  75. it("highlights all users mentioned via XEP-0372 references in a quoted message",
  76. mock.initConverse([], {}, async function (done, _converse) {
  77. const muc_jid = 'lounge@montague.lit';
  78. await mock.openAndEnterChatRoom(_converse, muc_jid, 'tom');
  79. const view = _converse.api.chatviews.get(muc_jid);
  80. ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => {
  81. _converse.connection._dataRecv(mock.createRequest(
  82. $pres({
  83. 'to': 'tom@montague.lit/resource',
  84. 'from': `lounge@montague.lit/${nick}`
  85. })
  86. .c('x', {xmlns: Strophe.NS.MUC_USER})
  87. .c('item', {
  88. 'affiliation': 'none',
  89. 'jid': `${nick}@montague.lit/resource`,
  90. 'role': 'participant'
  91. }))
  92. );
  93. });
  94. const msg = $msg({
  95. from: 'lounge@montague.lit/gibson',
  96. id: u.getUniqueId(),
  97. to: 'romeo@montague.lit',
  98. type: 'groupchat'
  99. }).c('body').t('>hello z3r0 tom mr.robot, how are you?').up()
  100. .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'7', 'end':'11', 'type':'mention', 'uri':'xmpp:z3r0@montague.lit'}).up()
  101. .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'12', 'end':'15', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).up()
  102. .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'16', 'end':'24', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree;
  103. await view.model.handleMessageStanza(msg);
  104. await u.waitUntil(() => view.querySelector('.chat-msg__text')?.innerHTML.replace(/<!---->/g, '') ===
  105. '<blockquote>hello <span class="mention">z3r0</span> <span class="mention mention--self badge badge-info">tom</span> <span class="mention">mr.robot</span>, how are you?</blockquote>');
  106. const message = view.querySelector('.chat-msg__text');
  107. expect(message.classList.length).toEqual(1);
  108. done();
  109. }));
  110. });
  111. describe("A sent groupchat message", function () {
  112. describe("in which someone is mentioned", function () {
  113. it("gets parsed for mentions which get turned into references",
  114. mock.initConverse([], {}, async function (done, _converse) {
  115. const muc_jid = 'lounge@montague.lit';
  116. // Making the MUC non-anonymous so that real JIDs are included
  117. const features = [
  118. 'http://jabber.org/protocol/muc',
  119. 'jabber:iq:register',
  120. Strophe.NS.SID,
  121. Strophe.NS.MAM,
  122. 'muc_passwordprotected',
  123. 'muc_hidden',
  124. 'muc_temporary',
  125. 'muc_open',
  126. 'muc_unmoderated',
  127. 'muc_nonanonymous'
  128. ];
  129. await mock.openAndEnterChatRoom(_converse, muc_jid, 'tom', features);
  130. const view = _converse.api.chatviews.get(muc_jid);
  131. ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh', 'Link Mauve', 'robot'].forEach((nick) => {
  132. _converse.connection._dataRecv(mock.createRequest(
  133. $pres({
  134. 'to': 'tom@montague.lit/resource',
  135. 'from': `lounge@montague.lit/${nick}`
  136. })
  137. .c('x', {xmlns: Strophe.NS.MUC_USER})
  138. .c('item', {
  139. 'affiliation': 'none',
  140. 'jid': `${nick.replace(/\s/g, '-')}@montague.lit/resource`,
  141. 'role': 'participant'
  142. })));
  143. });
  144. // Also check that nicks from received messages, (but for which we don't have occupant objects) can be mentioned.
  145. const stanza = u.toStanza(`
  146. <message xmlns="jabber:client"
  147. from="${muc_jid}/gh0st"
  148. to="${_converse.connection.bare_jid}"
  149. type="groupchat">
  150. <body>Boo!</body>
  151. </message>`);
  152. await view.model.handleMessageStanza(stanza);
  153. // Run a few unit tests for the parseTextForReferences method
  154. let [text, references] = view.model.parseTextForReferences('yo @robot')
  155. expect(text).toBe('yo robot');
  156. expect(references)
  157. .toEqual([{"begin":3,"end":8,"value":"robot","type":"mention","uri":"xmpp:robot@montague.lit"}]);
  158. [text, references] = view.model.parseTextForReferences('@@gh0st')
  159. expect(text).toBe('@gh0st');
  160. expect(references.length).toBe(1);
  161. expect(references)
  162. .toEqual([{"begin":1,"end":6,"value":"gh0st","type":"mention","uri":"xmpp:lounge@montague.lit/gh0st"}]);
  163. [text, references] = view.model.parseTextForReferences('hello z3r0')
  164. expect(references.length).toBe(0);
  165. expect(text).toBe('hello z3r0');
  166. [text, references] = view.model.parseTextForReferences('hello @z3r0')
  167. expect(references.length).toBe(1);
  168. expect(text).toBe('hello z3r0');
  169. expect(references)
  170. .toEqual([{"begin":6,"end":10,"value":"z3r0","type":"mention","uri":"xmpp:z3r0@montague.lit"}]);
  171. [text, references] = view.model.parseTextForReferences('hello @some1 @z3r0 @gibson @mr.robot, how are you?')
  172. expect(text).toBe('hello @some1 z3r0 gibson mr.robot, how are you?');
  173. expect(references)
  174. .toEqual([{"begin":13,"end":17,"value":"z3r0","type":"mention","uri":"xmpp:z3r0@montague.lit"},
  175. {"begin":18,"end":24,"value":"gibson","type":"mention","uri":"xmpp:gibson@montague.lit"},
  176. {"begin":25,"end":33,"value":"mr.robot","type":"mention","uri":"xmpp:mr.robot@montague.lit"}]);
  177. [text, references] = view.model.parseTextForReferences('yo @gib')
  178. expect(text).toBe('yo @gib');
  179. expect(references.length).toBe(0);
  180. [text, references] = view.model.parseTextForReferences('yo @gibsonian')
  181. expect(text).toBe('yo @gibsonian');
  182. expect(references.length).toBe(0);
  183. [text, references] = view.model.parseTextForReferences('yo @GiBsOn')
  184. expect(text).toBe('yo gibson');
  185. expect(references.length).toBe(1);
  186. [text, references] = view.model.parseTextForReferences('@gibson')
  187. expect(text).toBe('gibson');
  188. expect(references.length).toBe(1);
  189. expect(references)
  190. .toEqual([{"begin":0,"end":6,"value":"gibson","type":"mention","uri":"xmpp:gibson@montague.lit"}]);
  191. [text, references] = view.model.parseTextForReferences('hi @Link Mauve how are you?')
  192. expect(text).toBe('hi Link Mauve how are you?');
  193. expect(references.length).toBe(1);
  194. expect(references)
  195. .toEqual([{"begin":3,"end":13,"value":"Link Mauve","type":"mention","uri":"xmpp:Link-Mauve@montague.lit"}]);
  196. [text, references] = view.model.parseTextForReferences('https://example.org/@gibson')
  197. expect(text).toBe('https://example.org/@gibson');
  198. expect(references.length).toBe(0);
  199. expect(references).toEqual([]);
  200. [text, references] = view.model.parseTextForReferences('mail@gibson.com')
  201. expect(text).toBe('mail@gibson.com');
  202. expect(references.length).toBe(0);
  203. expect(references)
  204. .toEqual([]);
  205. [text, references] = view.model.parseTextForReferences(
  206. "Welcome @gibson 💩 We have a guide on how to do that here: https://conversejs.org/docs/html/index.html");
  207. expect(text).toBe("Welcome gibson 💩 We have a guide on how to do that here: https://conversejs.org/docs/html/index.html");
  208. expect(references.length).toBe(1);
  209. expect(references).toEqual([{"begin":8,"end":14,"value":"gibson","type":"mention","uri":"xmpp:gibson@montague.lit"}]);
  210. [text, references] = view.model.parseTextForReferences(
  211. 'https://linkmauve.fr@Link Mauve/ https://linkmauve.fr/@github/is_back gibson@gibson.com gibson@Link Mauve.fr')
  212. expect(text).toBe(
  213. 'https://linkmauve.fr@Link Mauve/ https://linkmauve.fr/@github/is_back gibson@gibson.com gibson@Link Mauve.fr');
  214. expect(references.length).toBe(0);
  215. expect(references)
  216. .toEqual([]);
  217. [text, references] = view.model.parseTextForReferences('@gh0st where are you?')
  218. expect(text).toBe('gh0st where are you?');
  219. expect(references.length).toBe(1);
  220. expect(references)
  221. .toEqual([{"begin":0,"end":5,"value":"gh0st","type":"mention","uri":"xmpp:lounge@montague.lit/gh0st"}]);
  222. done();
  223. }));
  224. it("gets parsed for mentions as indicated with an @ preceded by a space or at the start of the text",
  225. mock.initConverse([], {}, async function (done, _converse) {
  226. const muc_jid = 'lounge@montague.lit';
  227. await mock.openAndEnterChatRoom(_converse, muc_jid, 'tom');
  228. const view = _converse.api.chatviews.get(muc_jid);
  229. ['NotAnAdress', 'darnuria'].forEach((nick) => {
  230. _converse.connection._dataRecv(mock.createRequest(
  231. $pres({
  232. 'to': 'tom@montague.lit/resource',
  233. 'from': `lounge@montague.lit/${nick}`
  234. })
  235. .c('x', {xmlns: Strophe.NS.MUC_USER})
  236. .c('item', {
  237. 'affiliation': 'none',
  238. 'jid': `${nick.replace(/\s/g, '-')}@montague.lit/resource`,
  239. 'role': 'participant'
  240. })));
  241. });
  242. // Test that we don't match @nick in email adresses.
  243. let [text, references] = view.model.parseTextForReferences('contact contact@NotAnAdress.eu');
  244. expect(references.length).toBe(0);
  245. expect(text).toBe('contact contact@NotAnAdress.eu');
  246. // Test that we don't match @nick in url
  247. [text, references] = view.model.parseTextForReferences('nice website https://darnuria.eu/@darnuria');
  248. expect(references.length).toBe(0);
  249. expect(text).toBe('nice website https://darnuria.eu/@darnuria');
  250. done();
  251. }));
  252. it("properly encodes the URIs in sent out references",
  253. mock.initConverse([], {}, async function (done, _converse) {
  254. const muc_jid = 'lounge@montague.lit';
  255. await mock.openAndEnterChatRoom(_converse, muc_jid, 'tom');
  256. const view = _converse.api.roomviews.get(muc_jid);
  257. _converse.connection._dataRecv(mock.createRequest(
  258. $pres({
  259. 'to': 'tom@montague.lit/resource',
  260. 'from': `lounge@montague.lit/Link Mauve`
  261. })
  262. .c('x', {xmlns: Strophe.NS.MUC_USER})
  263. .c('item', {
  264. 'affiliation': 'none',
  265. 'role': 'participant'
  266. })));
  267. await u.waitUntil(() => view.model.occupants.length === 2);
  268. const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
  269. textarea.value = 'hello @Link Mauve'
  270. const enter_event = {
  271. 'target': textarea,
  272. 'preventDefault': function preventDefault () {},
  273. 'stopPropagation': function stopPropagation () {},
  274. 'keyCode': 13 // Enter
  275. }
  276. const bottom_panel = view.querySelector('converse-muc-bottom-panel');
  277. bottom_panel.onKeyDown(enter_event);
  278. await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
  279. const sent_stanzas = _converse.connection.sent_stanzas;
  280. const msg = await u.waitUntil(() => sent_stanzas.filter(s => s.nodeName.toLowerCase() === 'message').pop());
  281. expect(Strophe.serialize(msg))
  282. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.getAttribute("id")}" `+
  283. `to="lounge@montague.lit" type="groupchat" `+
  284. `xmlns="jabber:client">`+
  285. `<body>hello Link Mauve</body>`+
  286. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  287. `<reference begin="6" end="16" type="mention" uri="xmpp:lounge@montague.lit/Link%20Mauve" xmlns="urn:xmpp:reference:0"/>`+
  288. `<origin-id id="${msg.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  289. `</message>`);
  290. done();
  291. }));
  292. it("can get corrected and given new references",
  293. mock.initConverse([], {}, async function (done, _converse) {
  294. const muc_jid = 'lounge@montague.lit';
  295. // Making the MUC non-anonymous so that real JIDs are included
  296. const features = [
  297. 'http://jabber.org/protocol/muc',
  298. 'jabber:iq:register',
  299. Strophe.NS.SID,
  300. Strophe.NS.MAM,
  301. 'muc_passwordprotected',
  302. 'muc_hidden',
  303. 'muc_temporary',
  304. 'muc_open',
  305. 'muc_unmoderated',
  306. 'muc_nonanonymous'
  307. ];
  308. await mock.openAndEnterChatRoom(_converse, muc_jid, 'tom', features);
  309. const view = _converse.api.chatviews.get(muc_jid);
  310. ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => {
  311. _converse.connection._dataRecv(mock.createRequest(
  312. $pres({
  313. 'to': 'tom@montague.lit/resource',
  314. 'from': `lounge@montague.lit/${nick}`
  315. })
  316. .c('x', {xmlns: Strophe.NS.MUC_USER})
  317. .c('item', {
  318. 'affiliation': 'none',
  319. 'jid': `${nick}@montague.lit/resource`,
  320. 'role': 'participant'
  321. })));
  322. });
  323. await u.waitUntil(() => view.model.occupants.length === 5);
  324. const textarea = await u.waitUntil(() => view.querySelector('textarea.chat-textarea'));
  325. textarea.value = 'hello @z3r0 @gibson @mr.robot, how are you?'
  326. const enter_event = {
  327. 'target': textarea,
  328. 'preventDefault': function preventDefault () {},
  329. 'stopPropagation': function stopPropagation () {},
  330. 'keyCode': 13 // Enter
  331. }
  332. const bottom_panel = view.querySelector('converse-muc-bottom-panel');
  333. bottom_panel.onKeyDown(enter_event);
  334. await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
  335. const last_msg_sel = 'converse-chat-message:last-child .chat-msg__text';
  336. await u.waitUntil(() =>
  337. view.querySelector(last_msg_sel).innerHTML.replace(/<!---->/g, '') ===
  338. 'hello <span class="mention">z3r0</span> <span class="mention">gibson</span> <span class="mention">mr.robot</span>, how are you?'
  339. );
  340. const sent_stanzas = _converse.connection.sent_stanzas;
  341. const msg = await u.waitUntil(() => sent_stanzas.filter(s => s.nodeName.toLowerCase() === 'message').pop());
  342. expect(Strophe.serialize(msg))
  343. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.getAttribute("id")}" `+
  344. `to="lounge@montague.lit" type="groupchat" `+
  345. `xmlns="jabber:client">`+
  346. `<body>hello z3r0 gibson mr.robot, how are you?</body>`+
  347. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  348. `<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  349. `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  350. `<reference begin="18" end="26" type="mention" uri="xmpp:mr.robot@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  351. `<origin-id id="${msg.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  352. `</message>`);
  353. const action = await u.waitUntil(() => view.querySelector('.chat-msg .chat-msg__action'));
  354. action.style.opacity = 1;
  355. action.click();
  356. expect(textarea.value).toBe('hello @z3r0 @gibson @mr.robot, how are you?');
  357. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  358. expect(view.querySelectorAll('.chat-msg').length).toBe(1);
  359. await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')), 500);
  360. textarea.value = 'hello @z3r0 @gibson @sw0rdf1sh, how are you?';
  361. bottom_panel.onKeyDown(enter_event);
  362. await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent ===
  363. 'hello z3r0 gibson sw0rdf1sh, how are you?', 500);
  364. const correction = sent_stanzas.filter(s => s.nodeName.toLowerCase() === 'message').pop();
  365. expect(Strophe.serialize(correction))
  366. .toBe(`<message from="romeo@montague.lit/orchard" id="${correction.getAttribute("id")}" `+
  367. `to="lounge@montague.lit" type="groupchat" `+
  368. `xmlns="jabber:client">`+
  369. `<body>hello z3r0 gibson sw0rdf1sh, how are you?</body>`+
  370. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  371. `<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  372. `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  373. `<reference begin="18" end="27" type="mention" uri="xmpp:sw0rdf1sh@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  374. `<replace id="${msg.getAttribute("id")}" xmlns="urn:xmpp:message-correct:0"/>`+
  375. `<origin-id id="${correction.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  376. `</message>`);
  377. done();
  378. }));
  379. it("includes a XEP-0372 references to that person",
  380. mock.initConverse([], {}, async function (done, _converse) {
  381. const muc_jid = 'lounge@montague.lit';
  382. await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  383. const view = _converse.api.chatviews.get(muc_jid);
  384. ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => {
  385. _converse.connection._dataRecv(mock.createRequest(
  386. $pres({
  387. 'to': 'tom@montague.lit/resource',
  388. 'from': `lounge@montague.lit/${nick}`
  389. })
  390. .c('x', {xmlns: Strophe.NS.MUC_USER})
  391. .c('item', {
  392. 'affiliation': 'none',
  393. 'jid': `${nick}@montague.lit/resource`,
  394. 'role': 'participant'
  395. })));
  396. });
  397. await u.waitUntil(() => view.model.occupants.length === 5);
  398. spyOn(_converse.connection, 'send');
  399. const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
  400. textarea.value = 'hello @z3r0 @gibson @mr.robot, how are you?'
  401. const enter_event = {
  402. 'target': textarea,
  403. 'preventDefault': function preventDefault () {},
  404. 'stopPropagation': function stopPropagation () {},
  405. 'keyCode': 13 // Enter
  406. }
  407. const bottom_panel = view.querySelector('converse-muc-bottom-panel');
  408. bottom_panel.onKeyDown(enter_event);
  409. await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
  410. const msg = _converse.connection.send.calls.all()[1].args[0];
  411. expect(msg.toLocaleString())
  412. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+
  413. `to="lounge@montague.lit" type="groupchat" `+
  414. `xmlns="jabber:client">`+
  415. `<body>hello z3r0 gibson mr.robot, how are you?</body>`+
  416. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  417. `<reference begin="6" end="10" type="mention" uri="xmpp:${muc_jid}/z3r0" xmlns="urn:xmpp:reference:0"/>`+
  418. `<reference begin="11" end="17" type="mention" uri="xmpp:${muc_jid}/gibson" xmlns="urn:xmpp:reference:0"/>`+
  419. `<reference begin="18" end="26" type="mention" uri="xmpp:${muc_jid}/mr.robot" xmlns="urn:xmpp:reference:0"/>`+
  420. `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  421. `</message>`);
  422. done();
  423. }));
  424. });
  425. it("highlights all users mentioned via XEP-0372 references in a quoted message",
  426. mock.initConverse([], {}, async function (done, _converse) {
  427. const members = [{'jid': 'gibson@gibson.net', 'nick': 'gibson', 'affiliation': 'member'}];
  428. const muc_jid = 'lounge@montague.lit';
  429. await mock.openAndEnterChatRoom(_converse, muc_jid, 'tom', [], members);
  430. const view = _converse.api.chatviews.get(muc_jid);
  431. const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
  432. textarea.value = "Welcome @gibson 💩 We have a guide on how to do that here: https://conversejs.org/docs/html/index.html";
  433. const enter_event = {
  434. 'target': textarea,
  435. 'preventDefault': function preventDefault () {},
  436. 'stopPropagation': function stopPropagation () {},
  437. 'keyCode': 13 // Enter
  438. }
  439. const bottom_panel = view.querySelector('converse-muc-bottom-panel');
  440. bottom_panel.onKeyDown(enter_event);
  441. const message = await u.waitUntil(() => view.querySelector('.chat-msg__text'));
  442. expect(message.innerHTML.replace(/<!---->/g, '')).toEqual(
  443. `Welcome <span class="mention">gibson</span> <span title=":poop:">💩</span> `+
  444. `We have a guide on how to do that here: `+
  445. `<a target="_blank" rel="noopener" href="https://conversejs.org/docs/html/index.html">https://conversejs.org/docs/html/index.html</a>`);
  446. done();
  447. }));
  448. });