2
0

muc_messages.js 52 KB

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