2
0

muc_messages.js 60 KB

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