muc_messages.js 55 KB

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