mentions.js 24 KB

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