muc_messages.js 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990
  1. (function (root, factory) {
  2. define([
  3. "jasmine",
  4. "mock",
  5. "test-utils"
  6. ], factory);
  7. } (this, function (jasmine, mock, test_utils) {
  8. "use strict";
  9. const { Promise, Strophe, $msg, $pres, sizzle, stanza_utils } = converse.env;
  10. const u = converse.env.utils;
  11. describe("A Groupchat Message", function () {
  12. 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.env.log, 'warn');
  36. _converse.connection._dataRecv(test_utils.createRequest(received_stanza));
  37. expect(converse.env.log.warn).toHaveBeenCalledWith(
  38. 'onMessage: Ignoring unencapsulated forwarded groupchat message'
  39. );
  40. expect(view.el.querySelectorAll('.chat-msg').length).toBe(0);
  41. expect(view.model.messages.length).toBe(0);
  42. done();
  43. }));
  44. it("is specially marked when you are mentioned in it",
  45. mock.initConverse(
  46. ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  47. async function (done, _converse) {
  48. const muc_jid = 'lounge@montague.lit';
  49. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  50. const view = _converse.api.chatviews.get(muc_jid);
  51. if (!view.el.querySelectorAll('.chat-area').length) { view.renderChatArea(); }
  52. const message = 'romeo: Your attention is required';
  53. const nick = mock.chatroom_names[0],
  54. msg = $msg({
  55. from: 'lounge@montague.lit/'+nick,
  56. id: (new Date()).getTime(),
  57. to: 'romeo@montague.lit',
  58. type: 'groupchat'
  59. }).c('body').t(message).tree();
  60. await view.model.onMessage(msg);
  61. expect(u.hasClass('mentioned', view.el.querySelector('.chat-msg'))).toBeTruthy();
  62. done();
  63. }));
  64. it("can not be expected to have a unique id attribute",
  65. mock.initConverse(
  66. ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  67. async function (done, _converse) {
  68. const muc_jid = 'lounge@montague.lit';
  69. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  70. const view = _converse.api.chatviews.get(muc_jid);
  71. if (!view.el.querySelectorAll('.chat-area').length) { view.renderChatArea(); }
  72. const id = u.getUniqueId();
  73. let msg = $msg({
  74. from: 'lounge@montague.lit/some1',
  75. id: id,
  76. to: 'romeo@montague.lit',
  77. type: 'groupchat'
  78. }).c('body').t('First message').tree();
  79. await view.model.onMessage(msg);
  80. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
  81. msg = $msg({
  82. from: 'lounge@montague.lit/some2',
  83. id: id,
  84. to: 'romeo@montague.lit',
  85. type: 'groupchat'
  86. }).c('body').t('Another message').tree();
  87. await view.model.onMessage(msg);
  88. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 2);
  89. expect(view.model.messages.length).toBe(2);
  90. done();
  91. }));
  92. it("is ignored if it has the same stanza-id of an already received on",
  93. mock.initConverse(
  94. ['rosterGroupsFetched'], {},
  95. async function (done, _converse) {
  96. const muc_jid = 'room@muc.example.com';
  97. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  98. const view = _converse.api.chatviews.get(muc_jid);
  99. spyOn(view.model, 'findDuplicateFromStanzaID').and.callThrough();
  100. let stanza = u.toStanza(`
  101. <message xmlns="jabber:client"
  102. from="room@muc.example.com/some1"
  103. to="${_converse.connection.jid}"
  104. type="groupchat">
  105. <body>Typical body text</body>
  106. <stanza-id xmlns="urn:xmpp:sid:0"
  107. id="5f3dbc5e-e1d3-4077-a492-693f3769c7ad"
  108. by="room@muc.example.com"/>
  109. </message>`);
  110. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  111. await u.waitUntil(() => view.model.messages.length === 1);
  112. await u.waitUntil(() => view.model.findDuplicateFromStanzaID.calls.count() === 1);
  113. let result = await view.model.findDuplicateFromStanzaID.calls.all()[0].returnValue;
  114. expect(result).toBe(undefined);
  115. stanza = u.toStanza(`
  116. <message xmlns="jabber:client"
  117. from="room@muc.example.com/some1"
  118. to="${_converse.connection.jid}"
  119. type="groupchat">
  120. <body>Typical body text</body>
  121. <stanza-id xmlns="urn:xmpp:sid:0"
  122. id="5f3dbc5e-e1d3-4077-a492-693f3769c7ad"
  123. by="room@muc.example.com"/>
  124. </message>`);
  125. spyOn(view.model, 'updateMessage');
  126. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  127. await u.waitUntil(() => view.model.findDuplicateFromStanzaID.calls.count() === 2);
  128. result = await view.model.findDuplicateFromStanzaID.calls.all()[1].returnValue;
  129. expect(result instanceof _converse.Message).toBe(true);
  130. expect(view.model.messages.length).toBe(1);
  131. await u.waitUntil(() => view.model.updateMessage.calls.count());
  132. done();
  133. }));
  134. it("will be discarded if it's a malicious message meant to look like a carbon copy",
  135. mock.initConverse(
  136. ['rosterGroupsFetched'], {},
  137. async function (done, _converse) {
  138. await test_utils.waitForRoster(_converse, 'current');
  139. await test_utils.openControlBox(_converse);
  140. const muc_jid = 'xsf@muc.xmpp.org';
  141. const sender_jid = `${muc_jid}/romeo`;
  142. const impersonated_jid = `${muc_jid}/i_am_groot`
  143. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  144. const stanza = $pres({
  145. to: 'romeo@montague.lit/_converse.js-29092160',
  146. from: sender_jid
  147. })
  148. .c('x', {xmlns: Strophe.NS.MUC_USER})
  149. .c('item', {
  150. 'affiliation': 'none',
  151. 'jid': 'newguy@montague.lit/_converse.js-290929789',
  152. 'role': 'participant'
  153. }).tree();
  154. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  155. /*
  156. * <message to="romeo@montague.im/poezio" id="718d40df-3948-4798-a99b-35cc9f03cc4f-641" type="groupchat" from="xsf@muc.xmpp.org/romeo">
  157. * <received xmlns="urn:xmpp:carbons:2">
  158. * <forwarded xmlns="urn:xmpp:forward:0">
  159. * <message xmlns="jabber:client" to="xsf@muc.xmpp.org" type="groupchat" from="xsf@muc.xmpp.org/i_am_groot">
  160. * <body>I am groot.</body>
  161. * </message>
  162. * </forwarded>
  163. * </received>
  164. * </message>
  165. */
  166. const msg = $msg({
  167. 'from': sender_jid,
  168. 'id': _converse.connection.getUniqueId(),
  169. 'to': _converse.connection.jid,
  170. 'type': 'groupchat',
  171. 'xmlns': 'jabber:client'
  172. }).c('received', {'xmlns': 'urn:xmpp:carbons:2'})
  173. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  174. .c('message', {
  175. 'xmlns': 'jabber:client',
  176. 'from': impersonated_jid,
  177. 'to': muc_jid,
  178. 'type': 'groupchat'
  179. }).c('body').t('I am groot').tree();
  180. const view = _converse.api.chatviews.get(muc_jid);
  181. spyOn(converse.env.log, 'warn');
  182. await view.model.onMessage(msg);
  183. expect(converse.env.log.warn).toHaveBeenCalledWith(
  184. 'onMessage: Ignoring XEP-0280 "groupchat" message carbon, '+
  185. 'according to the XEP groupchat messages SHOULD NOT be carbon copied'
  186. );
  187. expect(view.el.querySelectorAll('.chat-msg').length).toBe(0);
  188. expect(view.model.messages.length).toBe(0);
  189. done();
  190. }));
  191. it("keeps track of the sender's role and affiliation",
  192. mock.initConverse(
  193. ['rosterGroupsFetched'], {},
  194. async function (done, _converse) {
  195. const muc_jid = 'lounge@montague.lit';
  196. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  197. const view = _converse.api.chatviews.get(muc_jid);
  198. let msg = $msg({
  199. from: 'lounge@montague.lit/romeo',
  200. id: (new Date()).getTime(),
  201. to: 'romeo@montague.lit',
  202. type: 'groupchat'
  203. }).c('body').t('I wrote this message!').tree();
  204. await view.model.onMessage(msg);
  205. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length);
  206. expect(view.model.messages.last().occupant.get('affiliation')).toBe('owner');
  207. expect(view.model.messages.last().occupant.get('role')).toBe('moderator');
  208. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  209. expect(sizzle('.chat-msg', view.el).pop().classList.value.trim()).toBe('message chat-msg groupchat moderator owner');
  210. let presence = $pres({
  211. to:'romeo@montague.lit/orchard',
  212. from:'lounge@montague.lit/romeo',
  213. id: u.getUniqueId()
  214. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  215. .c('item').attrs({
  216. affiliation: 'member',
  217. jid: 'romeo@montague.lit/orchard',
  218. role: 'participant'
  219. }).up()
  220. .c('status').attrs({code:'110'}).up()
  221. .c('status').attrs({code:'210'}).nodeTree;
  222. _converse.connection._dataRecv(test_utils.createRequest(presence));
  223. msg = $msg({
  224. from: 'lounge@montague.lit/romeo',
  225. id: (new Date()).getTime(),
  226. to: 'romeo@montague.lit',
  227. type: 'groupchat'
  228. }).c('body').t('Another message!').tree();
  229. await view.model.onMessage(msg);
  230. expect(view.model.messages.last().occupant.get('affiliation')).toBe('member');
  231. expect(view.model.messages.last().occupant.get('role')).toBe('participant');
  232. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  233. expect(sizzle('.chat-msg', view.el).pop().classList.value.trim()).toBe('message chat-msg groupchat participant member');
  234. presence = $pres({
  235. to:'romeo@montague.lit/orchard',
  236. from:'lounge@montague.lit/romeo',
  237. id: u.getUniqueId()
  238. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  239. .c('item').attrs({
  240. affiliation: 'owner',
  241. jid: 'romeo@montague.lit/orchard',
  242. role: 'moderator'
  243. }).up()
  244. .c('status').attrs({code:'110'}).up()
  245. .c('status').attrs({code:'210'}).nodeTree;
  246. _converse.connection._dataRecv(test_utils.createRequest(presence));
  247. view.model.sendMessage('hello world');
  248. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 3);
  249. expect(view.model.messages.last().occupant.get('affiliation')).toBe('owner');
  250. expect(view.model.messages.last().occupant.get('role')).toBe('moderator');
  251. expect(view.el.querySelectorAll('.chat-msg').length).toBe(3);
  252. expect(sizzle('.chat-msg', view.el).pop().classList.value.trim()).toBe('message chat-msg groupchat moderator owner');
  253. const add_events = view.model.occupants._events.add.length;
  254. msg = $msg({
  255. from: 'lounge@montague.lit/some1',
  256. id: (new Date()).getTime(),
  257. to: 'romeo@montague.lit',
  258. type: 'groupchat'
  259. }).c('body').t('Message from someone not in the MUC right now').tree();
  260. await view.model.onMessage(msg);
  261. expect(view.model.messages.last().occupant).toBeUndefined();
  262. // Check that there's a new "add" event handler, for when the occupant appears.
  263. expect(view.model.occupants._events.add.length).toBe(add_events+1);
  264. // Check that the occupant gets added/removed to the message as it
  265. // gets removed or added.
  266. presence = $pres({
  267. to:'romeo@montague.lit/orchard',
  268. from:'lounge@montague.lit/some1',
  269. id: u.getUniqueId()
  270. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  271. .c('item').attrs({jid: 'some1@montague.lit/orchard'});
  272. _converse.connection._dataRecv(test_utils.createRequest(presence));
  273. await u.waitUntil(() => view.model.messages.last().occupant);
  274. expect(view.model.messages.last().get('message')).toBe('Message from someone not in the MUC right now');
  275. expect(view.model.messages.last().occupant.get('nick')).toBe('some1');
  276. // Check that the "add" event handler was removed.
  277. expect(view.model.occupants._events.add.length).toBe(add_events);
  278. presence = $pres({
  279. to:'romeo@montague.lit/orchard',
  280. type: 'unavailable',
  281. from:'lounge@montague.lit/some1',
  282. id: u.getUniqueId()
  283. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  284. .c('item').attrs({jid: 'some1@montague.lit/orchard'});
  285. _converse.connection._dataRecv(test_utils.createRequest(presence));
  286. await u.waitUntil(() => !view.model.messages.last().occupant);
  287. expect(view.model.messages.last().get('message')).toBe('Message from someone not in the MUC right now');
  288. expect(view.model.messages.last().occupant).toBeUndefined();
  289. // Check that there's a new "add" event handler, for when the occupant appears.
  290. expect(view.model.occupants._events.add.length).toBe(add_events+1);
  291. presence = $pres({
  292. to:'romeo@montague.lit/orchard',
  293. from:'lounge@montague.lit/some1',
  294. id: u.getUniqueId()
  295. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  296. .c('item').attrs({jid: 'some1@montague.lit/orchard'});
  297. _converse.connection._dataRecv(test_utils.createRequest(presence));
  298. await u.waitUntil(() => view.model.messages.last().occupant);
  299. expect(view.model.messages.last().get('message')).toBe('Message from someone not in the MUC right now');
  300. expect(view.model.messages.last().occupant.get('nick')).toBe('some1');
  301. // Check that the "add" event handler was removed.
  302. expect(view.model.occupants._events.add.length).toBe(add_events);
  303. done();
  304. }));
  305. it("keeps track whether you are the sender or not",
  306. mock.initConverse(
  307. ['rosterGroupsFetched'], {},
  308. async function (done, _converse) {
  309. const muc_jid = 'lounge@montague.lit';
  310. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  311. const view = _converse.api.chatviews.get(muc_jid);
  312. const msg = $msg({
  313. from: 'lounge@montague.lit/romeo',
  314. id: (new Date()).getTime(),
  315. to: 'romeo@montague.lit',
  316. type: 'groupchat'
  317. }).c('body').t('I wrote this message!').tree();
  318. await view.model.onMessage(msg);
  319. expect(view.model.messages.last().get('sender')).toBe('me');
  320. done();
  321. }));
  322. it("can be replaced with a correction",
  323. mock.initConverse(
  324. ['rosterGroupsFetched'], {},
  325. async function (done, _converse) {
  326. const muc_jid = 'lounge@montague.lit';
  327. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  328. const view = _converse.api.chatviews.get(muc_jid);
  329. const stanza = $pres({
  330. to: 'romeo@montague.lit/_converse.js-29092160',
  331. from: 'coven@chat.shakespeare.lit/newguy'
  332. })
  333. .c('x', {xmlns: Strophe.NS.MUC_USER})
  334. .c('item', {
  335. 'affiliation': 'none',
  336. 'jid': 'newguy@montague.lit/_converse.js-290929789',
  337. 'role': 'participant'
  338. }).tree();
  339. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  340. const msg_id = u.getUniqueId();
  341. await view.model.onMessage($msg({
  342. 'from': 'lounge@montague.lit/newguy',
  343. 'to': _converse.connection.jid,
  344. 'type': 'groupchat',
  345. 'id': msg_id,
  346. }).c('body').t('But soft, what light through yonder airlock breaks?').tree());
  347. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  348. expect(view.el.querySelector('.chat-msg__text').textContent)
  349. .toBe('But soft, what light through yonder airlock breaks?');
  350. await view.model.onMessage($msg({
  351. 'from': 'lounge@montague.lit/newguy',
  352. 'to': _converse.connection.jid,
  353. 'type': 'groupchat',
  354. 'id': u.getUniqueId(),
  355. }).c('body').t('But soft, what light through yonder chimney breaks?').up()
  356. .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
  357. await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
  358. 'But soft, what light through yonder chimney breaks?', 500);
  359. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  360. expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
  361. await view.model.onMessage($msg({
  362. 'from': 'lounge@montague.lit/newguy',
  363. 'to': _converse.connection.jid,
  364. 'type': 'groupchat',
  365. 'id': u.getUniqueId(),
  366. }).c('body').t('But soft, what light through yonder window breaks?').up()
  367. .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
  368. await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
  369. 'But soft, what light through yonder window breaks?', 500);
  370. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  371. expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
  372. view.el.querySelector('.chat-msg__content .fa-edit').click();
  373. const modal = view.model.messages.at(0).message_versions_modal;
  374. await u.waitUntil(() => u.isVisible(modal.el), 1000);
  375. const older_msgs = modal.el.querySelectorAll('.older-msg');
  376. expect(older_msgs.length).toBe(2);
  377. expect(older_msgs[0].childNodes[1].textContent).toBe(': But soft, what light through yonder airlock breaks?');
  378. expect(older_msgs[0].childNodes[0].nodeName).toBe('TIME');
  379. expect(older_msgs[1].childNodes[0].nodeName).toBe('TIME');
  380. expect(older_msgs[1].childNodes[1].textContent).toBe(': But soft, what light through yonder chimney breaks?');
  381. done();
  382. }));
  383. it("can be sent as a correction by using the up arrow",
  384. mock.initConverse(
  385. ['rosterGroupsFetched'], {},
  386. async function (done, _converse) {
  387. const muc_jid = 'lounge@montague.lit';
  388. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  389. const view = _converse.api.chatviews.get(muc_jid);
  390. const textarea = view.el.querySelector('textarea.chat-textarea');
  391. expect(textarea.value).toBe('');
  392. view.onKeyDown({
  393. target: textarea,
  394. keyCode: 38 // Up arrow
  395. });
  396. expect(textarea.value).toBe('');
  397. textarea.value = 'But soft, what light through yonder airlock breaks?';
  398. view.onKeyDown({
  399. target: textarea,
  400. preventDefault: function preventDefault () {},
  401. keyCode: 13 // Enter
  402. });
  403. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
  404. expect(view.el.querySelector('.chat-msg__text').textContent)
  405. .toBe('But soft, what light through yonder airlock breaks?');
  406. const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
  407. expect(textarea.value).toBe('');
  408. view.onKeyDown({
  409. target: textarea,
  410. keyCode: 38 // Up arrow
  411. });
  412. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  413. expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
  414. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  415. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  416. expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true);
  417. spyOn(_converse.connection, 'send');
  418. textarea.value = 'But soft, what light through yonder window breaks?';
  419. view.onKeyDown({
  420. target: textarea,
  421. preventDefault: function preventDefault () {},
  422. keyCode: 13 // Enter
  423. });
  424. expect(_converse.connection.send).toHaveBeenCalled();
  425. await new Promise(resolve => view.model.messages.once('rendered', resolve));
  426. const msg = _converse.connection.send.calls.all()[0].args[0];
  427. expect(msg.toLocaleString())
  428. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+
  429. `to="lounge@montague.lit" type="groupchat" `+
  430. `xmlns="jabber:client">`+
  431. `<body>But soft, what light through yonder window breaks?</body>`+
  432. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  433. `<replace id="${first_msg.get("msgid")}" xmlns="urn:xmpp:message-correct:0"/>`+
  434. `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  435. `</message>`);
  436. expect(view.model.messages.models.length).toBe(1);
  437. const corrected_message = view.model.messages.at(0);
  438. expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid'));
  439. expect(corrected_message.get('correcting')).toBe(false);
  440. const older_versions = corrected_message.get('older_versions');
  441. const keys = Object.keys(older_versions);
  442. expect(keys.length).toBe(1);
  443. expect(older_versions[keys[0]]).toBe('But soft, what light through yonder airlock breaks?');
  444. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  445. expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false);
  446. // Check that messages from other users are skipped
  447. await view.model.onMessage($msg({
  448. 'from': muc_jid+'/someone-else',
  449. 'id': (new Date()).getTime(),
  450. 'to': 'romeo@montague.lit',
  451. 'type': 'groupchat'
  452. }).c('body').t('Hello world').tree());
  453. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  454. // Test that pressing the down arrow cancels message correction
  455. expect(textarea.value).toBe('');
  456. view.onKeyDown({
  457. target: textarea,
  458. keyCode: 38 // Up arrow
  459. });
  460. expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
  461. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  462. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  463. await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
  464. expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
  465. view.onKeyDown({
  466. target: textarea,
  467. keyCode: 40 // Down arrow
  468. });
  469. expect(textarea.value).toBe('');
  470. expect(view.model.messages.at(0).get('correcting')).toBe(false);
  471. expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
  472. await u.waitUntil(() => !u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
  473. done();
  474. }));
  475. it("will be shown as received upon MUC reflection",
  476. mock.initConverse(
  477. ['rosterGroupsFetched'], {},
  478. async function (done, _converse) {
  479. await test_utils.waitForRoster(_converse, 'current');
  480. const muc_jid = 'lounge@montague.lit';
  481. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  482. const view = _converse.api.chatviews.get(muc_jid);
  483. const textarea = view.el.querySelector('textarea.chat-textarea');
  484. textarea.value = 'But soft, what light through yonder airlock breaks?';
  485. view.onKeyDown({
  486. target: textarea,
  487. preventDefault: function preventDefault () {},
  488. keyCode: 13 // Enter
  489. });
  490. await new Promise(resolve => view.once('messageInserted', resolve));
  491. expect(view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length).toBe(0);
  492. const msg_obj = view.model.messages.at(0);
  493. const stanza = u.toStanza(`
  494. <message xmlns="jabber:client"
  495. from="${msg_obj.get('from')}"
  496. to="${_converse.connection.jid}"
  497. type="groupchat">
  498. <msg_body>${msg_obj.get('message')}</msg_body>
  499. <stanza-id xmlns="urn:xmpp:sid:0"
  500. id="5f3dbc5e-e1d3-4077-a492-693f3769c7ad"
  501. by="lounge@montague.lit"/>
  502. <origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
  503. </message>`);
  504. await view.model.onMessage(stanza);
  505. await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
  506. expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
  507. expect(view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length).toBe(1);
  508. expect(view.model.messages.length).toBe(1);
  509. const message = view.model.messages.at(0);
  510. expect(message.get('stanza_id lounge@montague.lit')).toBe('5f3dbc5e-e1d3-4077-a492-693f3769c7ad');
  511. expect(message.get('origin_id')).toBe(msg_obj.get('origin_id'));
  512. done();
  513. }));
  514. it("gets updated with its stanza-id upon MUC reflection",
  515. mock.initConverse(
  516. ['rosterGroupsFetched'], {},
  517. async function (done, _converse) {
  518. const muc_jid = 'room@muc.example.com';
  519. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  520. const view = _converse.api.chatviews.get(muc_jid);
  521. view.model.sendMessage('hello world');
  522. await u.waitUntil(() => view.model.messages.length === 1);
  523. const msg = view.model.messages.at(0);
  524. expect(msg.get('stanza_id')).toBeUndefined();
  525. expect(msg.get('origin_id')).toBe(msg.get('origin_id'));
  526. const stanza = u.toStanza(`
  527. <message xmlns="jabber:client"
  528. from="room@muc.example.com/romeo"
  529. to="${_converse.connection.jid}"
  530. type="groupchat">
  531. <body>Hello world</body>
  532. <stanza-id xmlns="urn:xmpp:sid:0"
  533. id="5f3dbc5e-e1d3-4077-a492-693f3769c7ad"
  534. by="room@muc.example.com"/>
  535. <origin-id xmlns="urn:xmpp:sid:0" id="${msg.get('origin_id')}"/>
  536. </message>`);
  537. spyOn(view.model, 'updateMessage').and.callThrough();
  538. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  539. await u.waitUntil(() => view.model.updateMessage.calls.count() === 1);
  540. expect(view.model.messages.length).toBe(1);
  541. expect(view.model.messages.at(0).get('stanza_id room@muc.example.com')).toBe("5f3dbc5e-e1d3-4077-a492-693f3769c7ad");
  542. expect(view.model.messages.at(0).get('origin_id')).toBe(msg.get('origin_id'));
  543. done();
  544. }));
  545. it("can cause a delivery receipt to be returned",
  546. mock.initConverse(
  547. ['rosterGroupsFetched'], {},
  548. async function (done, _converse) {
  549. await test_utils.waitForRoster(_converse, 'current');
  550. const muc_jid = 'lounge@montague.lit';
  551. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  552. const view = _converse.api.chatviews.get(muc_jid);
  553. const textarea = view.el.querySelector('textarea.chat-textarea');
  554. textarea.value = 'But soft, what light through yonder airlock breaks?';
  555. view.onKeyDown({
  556. target: textarea,
  557. preventDefault: function preventDefault () {},
  558. keyCode: 13 // Enter
  559. });
  560. await new Promise(resolve => view.once('messageInserted', resolve));
  561. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  562. const msg_obj = view.model.messages.at(0);
  563. const stanza = u.toStanza(`
  564. <message xml:lang="en" to="romeo@montague.lit/orchard"
  565. from="lounge@montague.lit/some1" type="groupchat" xmlns="jabber:client">
  566. <received xmlns="urn:xmpp:receipts" id="${msg_obj.get('msgid')}"/>
  567. <origin-id xmlns="urn:xmpp:sid:0" id="CE08D448-5ED8-4B6A-BB5B-07ED9DFE4FF0"/>
  568. </message>`);
  569. spyOn(_converse.api, "trigger").and.callThrough();
  570. spyOn(stanza_utils, "isReceipt").and.callThrough();
  571. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  572. await u.waitUntil(() => stanza_utils.isReceipt.calls.count() === 1);
  573. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  574. expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(0);
  575. expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
  576. done();
  577. }));
  578. it("can cause a chat marker to be returned",
  579. mock.initConverse(
  580. ['rosterGroupsFetched'], {},
  581. async function (done, _converse) {
  582. await test_utils.waitForRoster(_converse, 'current');
  583. const muc_jid = 'lounge@montague.lit';
  584. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  585. const view = _converse.api.chatviews.get(muc_jid);
  586. const textarea = view.el.querySelector('textarea.chat-textarea');
  587. textarea.value = 'But soft, what light through yonder airlock breaks?';
  588. view.onKeyDown({
  589. target: textarea,
  590. preventDefault: function preventDefault () {},
  591. keyCode: 13 // Enter
  592. });
  593. await new Promise(resolve => view.once('messageInserted', resolve));
  594. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  595. expect(view.el.querySelector('.chat-msg .chat-msg__body').textContent.trim())
  596. .toBe("But soft, what light through yonder airlock breaks?");
  597. const msg_obj = view.model.messages.at(0);
  598. let stanza = u.toStanza(`
  599. <message xml:lang="en" to="romeo@montague.lit/orchard"
  600. from="lounge@montague.lit/some1" type="groupchat" xmlns="jabber:client">
  601. <received xmlns="urn:xmpp:chat-markers:0" id="${msg_obj.get('msgid')}"/>
  602. </message>`);
  603. const stanza_utils = converse.env.stanza_utils;
  604. spyOn(stanza_utils, "isChatMarker").and.callThrough();
  605. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  606. await u.waitUntil(() => stanza_utils.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(() => stanza_utils.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(() => stanza_utils.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(() => stanza_utils.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. }));