muc_messages.js 52 KB

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