muc_messages.js 31 KB

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