muc_messages.js 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090
  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("properly encodes the URIs in sent out 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.roomviews.get(muc_jid);
  837. _converse.connection._dataRecv(test_utils.createRequest(
  838. $pres({
  839. 'to': 'tom@montague.lit/resource',
  840. 'from': `lounge@montague.lit/Link Mauve`
  841. })
  842. .c('x', {xmlns: Strophe.NS.MUC_USER})
  843. .c('item', {
  844. 'affiliation': 'none',
  845. 'role': 'participant'
  846. })));
  847. const textarea = view.el.querySelector('textarea.chat-textarea');
  848. textarea.value = 'hello @Link Mauve'
  849. const enter_event = {
  850. 'target': textarea,
  851. 'preventDefault': function preventDefault () {},
  852. 'stopPropagation': function stopPropagation () {},
  853. 'keyCode': 13 // Enter
  854. }
  855. spyOn(_converse.connection, 'send');
  856. view.onKeyDown(enter_event);
  857. await new Promise(resolve => view.once('messageInserted', resolve));
  858. const msg = _converse.connection.send.calls.all()[0].args[0];
  859. expect(msg.toLocaleString())
  860. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+
  861. `to="lounge@montague.lit" type="groupchat" `+
  862. `xmlns="jabber:client">`+
  863. `<body>hello Link Mauve</body>`+
  864. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  865. `<reference begin="6" end="16" type="mention" uri="xmpp:lounge@montague.lit/Link%20Mauve" xmlns="urn:xmpp:reference:0"/>`+
  866. `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  867. `</message>`);
  868. done();
  869. }));
  870. it("can get corrected and given new references",
  871. mock.initConverse(
  872. ['rosterGroupsFetched'], {},
  873. async function (done, _converse) {
  874. const muc_jid = 'lounge@montague.lit';
  875. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'tom');
  876. const view = _converse.api.chatviews.get(muc_jid);
  877. ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => {
  878. _converse.connection._dataRecv(test_utils.createRequest(
  879. $pres({
  880. 'to': 'tom@montague.lit/resource',
  881. 'from': `lounge@montague.lit/${nick}`
  882. })
  883. .c('x', {xmlns: Strophe.NS.MUC_USER})
  884. .c('item', {
  885. 'affiliation': 'none',
  886. 'jid': `${nick}@montague.lit/resource`,
  887. 'role': 'participant'
  888. })));
  889. });
  890. const textarea = view.el.querySelector('textarea.chat-textarea');
  891. textarea.value = 'hello @z3r0 @gibson @mr.robot, how are you?'
  892. const enter_event = {
  893. 'target': textarea,
  894. 'preventDefault': function preventDefault () {},
  895. 'stopPropagation': function stopPropagation () {},
  896. 'keyCode': 13 // Enter
  897. }
  898. spyOn(_converse.connection, 'send');
  899. view.onKeyDown(enter_event);
  900. await new Promise(resolve => view.once('messageInserted', resolve));
  901. const msg = _converse.connection.send.calls.all()[0].args[0];
  902. expect(msg.toLocaleString())
  903. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+
  904. `to="lounge@montague.lit" type="groupchat" `+
  905. `xmlns="jabber:client">`+
  906. `<body>hello z3r0 gibson mr.robot, how are you?</body>`+
  907. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  908. `<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  909. `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  910. `<reference begin="18" end="26" type="mention" uri="xmpp:mr.robot@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  911. `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  912. `</message>`);
  913. const action = view.el.querySelector('.chat-msg .chat-msg__action');
  914. action.style.opacity = 1;
  915. action.click();
  916. expect(textarea.value).toBe('hello @z3r0 @gibson @mr.robot, how are you?');
  917. expect(view.model.messages.at(0).get('correcting')).toBe(true);
  918. expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
  919. await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
  920. textarea.value = 'hello @z3r0 @gibson @sw0rdf1sh, how are you?';
  921. view.onKeyDown(enter_event);
  922. await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
  923. 'hello z3r0 gibson sw0rdf1sh, how are you?', 500);
  924. const correction = _converse.connection.send.calls.all()[2].args[0];
  925. expect(correction.toLocaleString())
  926. .toBe(`<message from="romeo@montague.lit/orchard" id="${correction.nodeTree.getAttribute("id")}" `+
  927. `to="lounge@montague.lit" type="groupchat" `+
  928. `xmlns="jabber:client">`+
  929. `<body>hello z3r0 gibson sw0rdf1sh, how are you?</body>`+
  930. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  931. `<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  932. `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  933. `<reference begin="18" end="27" type="mention" uri="xmpp:sw0rdf1sh@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  934. `<replace id="${msg.nodeTree.getAttribute("id")}" xmlns="urn:xmpp:message-correct:0"/>`+
  935. `<origin-id id="${correction.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  936. `</message>`);
  937. done();
  938. }));
  939. it("includes XEP-0372 references to that person",
  940. mock.initConverse(
  941. ['rosterGroupsFetched'], {},
  942. async function (done, _converse) {
  943. const muc_jid = 'lounge@montague.lit';
  944. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  945. const view = _converse.api.chatviews.get(muc_jid);
  946. ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => {
  947. _converse.connection._dataRecv(test_utils.createRequest(
  948. $pres({
  949. 'to': 'tom@montague.lit/resource',
  950. 'from': `lounge@montague.lit/${nick}`
  951. })
  952. .c('x', {xmlns: Strophe.NS.MUC_USER})
  953. .c('item', {
  954. 'affiliation': 'none',
  955. 'jid': `${nick}@montague.lit/resource`,
  956. 'role': 'participant'
  957. })));
  958. });
  959. spyOn(_converse.connection, 'send');
  960. const textarea = view.el.querySelector('textarea.chat-textarea');
  961. textarea.value = 'hello @z3r0 @gibson @mr.robot, how are you?'
  962. const enter_event = {
  963. 'target': textarea,
  964. 'preventDefault': function preventDefault () {},
  965. 'stopPropagation': function stopPropagation () {},
  966. 'keyCode': 13 // Enter
  967. }
  968. view.onKeyDown(enter_event);
  969. const msg = _converse.connection.send.calls.all()[0].args[0];
  970. expect(msg.toLocaleString())
  971. .toBe(`<message from="romeo@montague.lit/orchard" id="${msg.nodeTree.getAttribute("id")}" `+
  972. `to="lounge@montague.lit" type="groupchat" `+
  973. `xmlns="jabber:client">`+
  974. `<body>hello z3r0 gibson mr.robot, how are you?</body>`+
  975. `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
  976. `<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  977. `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  978. `<reference begin="18" end="26" type="mention" uri="xmpp:mr.robot@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
  979. `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
  980. `</message>`);
  981. done();
  982. }));
  983. });
  984. });
  985. }));