mam.js 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  1. (function (root, factory) {
  2. define(["jasmine", "mock", "test-utils"], factory);
  3. } (this, function (jasmine, mock, test_utils) {
  4. "use strict";
  5. const _ = converse.env._;
  6. const Backbone = converse.env.Backbone;
  7. const Strophe = converse.env.Strophe;
  8. const $iq = converse.env.$iq;
  9. const $msg = converse.env.$msg;
  10. const dayjs = converse.env.dayjs;
  11. const u = converse.env.utils;
  12. const sizzle = converse.env.sizzle;
  13. // See: https://xmpp.org/rfcs/rfc3921.html
  14. // Implements the protocol defined in https://xmpp.org/extensions/xep-0313.html#config
  15. describe("Message Archive Management", function () {
  16. describe("The XEP-0313 Archive", function () {
  17. it("is queried when the user enters a new MUC",
  18. mock.initConverse(
  19. null, ['discoInitialized'], {'archived_messages_page_size': 2},
  20. async function (done, _converse) {
  21. spyOn(_converse.ChatBox.prototype, 'fetchArchivedMessages').and.callThrough();
  22. const sent_IQs = _converse.connection.IQ_stanzas;
  23. const muc_jid = 'orchard@chat.shakespeare.lit';
  24. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  25. let view = _converse.chatboxviews.get(muc_jid);
  26. let iq_get = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq query[xmlns="${Strophe.NS.MAM}"]`)).pop());
  27. expect(Strophe.serialize(iq_get)).toBe(
  28. `<iq id="${iq_get.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client">`+
  29. `<query queryid="${iq_get.querySelector('query').getAttribute('queryid')}" xmlns="${Strophe.NS.MAM}">`+
  30. `<x type="submit" xmlns="jabber:x:data">`+
  31. `<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
  32. `</x>`+
  33. `<set xmlns="http://jabber.org/protocol/rsm"><max>2</max><before></before></set>`+
  34. `</query>`+
  35. `</iq>`);
  36. let first_msg_id = _converse.connection.getUniqueId();
  37. let last_msg_id = _converse.connection.getUniqueId();
  38. let message = u.toStanza(
  39. `<message xmlns="jabber:client"
  40. to="romeo@montague.lit/orchard"
  41. from="${muc_jid}">
  42. <result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${first_msg_id}">
  43. <forwarded xmlns="urn:xmpp:forward:0">
  44. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:15:23Z"/>
  45. <message from="${muc_jid}/some1" type="groupchat">
  46. <body>2nd Message</body>
  47. </message>
  48. </forwarded>
  49. </result>
  50. </message>`);
  51. _converse.connection._dataRecv(test_utils.createRequest(message));
  52. message = u.toStanza(
  53. `<message xmlns="jabber:client"
  54. to="romeo@montague.lit/orchard"
  55. from="${muc_jid}">
  56. <result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${last_msg_id}">
  57. <forwarded xmlns="urn:xmpp:forward:0">
  58. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:16:23Z"/>
  59. <message from="${muc_jid}/some1" type="groupchat">
  60. <body>3rd Message</body>
  61. </message>
  62. </forwarded>
  63. </result>
  64. </message>`);
  65. _converse.connection._dataRecv(test_utils.createRequest(message));
  66. // Clear so that we don't match the older query
  67. while (sent_IQs.length) { sent_IQs.pop(); }
  68. // XXX: Even though the count is 3, when fetching messages for
  69. // the first time, we don't paginate, so that message
  70. // is not fetched. The user needs to manually load older
  71. // messages for it to be fetched.
  72. // TODO: we need to add a clickable link to load older messages
  73. let result = u.toStanza(
  74. `<iq type='result' id='${iq_get.getAttribute('id')}'>
  75. <fin xmlns='urn:xmpp:mam:2'>
  76. <set xmlns='http://jabber.org/protocol/rsm'>
  77. <first index='0'>${first_msg_id}</first>
  78. <last>${last_msg_id}</last>
  79. <count>3</count>
  80. </set>
  81. </fin>
  82. </iq>`);
  83. _converse.connection._dataRecv(test_utils.createRequest(result));
  84. await u.waitUntil(() => view.model.messages.length === 2);
  85. view.close();
  86. // Clear so that we don't match the older query
  87. while (sent_IQs.length) { sent_IQs.pop(); }
  88. await u.waitUntil(() => _converse.chatboxes.length === 1);
  89. await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
  90. view = _converse.chatboxviews.get(muc_jid);
  91. await u.waitUntil(() => view.model.messages.length);
  92. iq_get = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq query[xmlns="${Strophe.NS.MAM}"]`)).pop());
  93. expect(Strophe.serialize(iq_get)).toBe(
  94. `<iq id="${iq_get.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client">`+
  95. `<query queryid="${iq_get.querySelector('query').getAttribute('queryid')}" xmlns="${Strophe.NS.MAM}">`+
  96. `<x type="submit" xmlns="jabber:x:data">`+
  97. `<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
  98. `</x>`+
  99. `<set xmlns="http://jabber.org/protocol/rsm"><max>2</max><after>${message.querySelector('result').getAttribute('id')}</after></set>`+
  100. `</query>`+
  101. `</iq>`);
  102. first_msg_id = _converse.connection.getUniqueId();
  103. last_msg_id = _converse.connection.getUniqueId();
  104. message = u.toStanza(
  105. `<message xmlns="jabber:client"
  106. to="romeo@montague.lit/orchard"
  107. from="${muc_jid}">
  108. <result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${first_msg_id}">
  109. <forwarded xmlns="urn:xmpp:forward:0">
  110. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:17:23Z"/>
  111. <message from="${muc_jid}/some1" type="groupchat">
  112. <body>4th Message</body>
  113. </message>
  114. </forwarded>
  115. </result>
  116. </message>`);
  117. _converse.connection._dataRecv(test_utils.createRequest(message));
  118. message = u.toStanza(
  119. `<message xmlns="jabber:client"
  120. to="romeo@montague.lit/orchard"
  121. from="${muc_jid}">
  122. <result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${last_msg_id}">
  123. <forwarded xmlns="urn:xmpp:forward:0">
  124. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:18:23Z"/>
  125. <message from="${muc_jid}/some1" type="groupchat">
  126. <body>5th Message</body>
  127. </message>
  128. </forwarded>
  129. </result>
  130. </message>`);
  131. _converse.connection._dataRecv(test_utils.createRequest(message));
  132. // Clear so that we don't match the older query
  133. while (sent_IQs.length) { sent_IQs.pop(); }
  134. result = u.toStanza(
  135. `<iq type='result' id='${iq_get.getAttribute('id')}'>
  136. <fin xmlns='urn:xmpp:mam:2'>
  137. <set xmlns='http://jabber.org/protocol/rsm'>
  138. <first index='0'>${first_msg_id}</first>
  139. <last>${last_msg_id}</last>
  140. <count>5</count>
  141. </set>
  142. </fin>
  143. </iq>`);
  144. _converse.connection._dataRecv(test_utils.createRequest(result));
  145. await u.waitUntil(() => view.model.messages.length === 4);
  146. iq_get = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq query[xmlns="${Strophe.NS.MAM}"]`)).pop());
  147. expect(Strophe.serialize(iq_get)).toBe(
  148. `<iq id="${iq_get.getAttribute('id')}" to="orchard@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
  149. `<query queryid="${iq_get.querySelector('query').getAttribute('queryid')}" xmlns="urn:xmpp:mam:2">`+
  150. `<x type="submit" xmlns="jabber:x:data">`+
  151. `<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
  152. `</x>`+
  153. `<set xmlns="http://jabber.org/protocol/rsm">`+
  154. `<max>2</max><after>${last_msg_id}</after>`+
  155. `</set>`+
  156. `</query>`+
  157. `</iq>`);
  158. const msg_id = _converse.connection.getUniqueId();
  159. message = u.toStanza(
  160. `<message xmlns="jabber:client"
  161. to="romeo@montague.lit/orchard"
  162. from="${muc_jid}">
  163. <result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${msg_id}">
  164. <forwarded xmlns="urn:xmpp:forward:0">
  165. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:19:23Z"/>
  166. <message from="${muc_jid}/some1" type="groupchat">
  167. <body>6th Message</body>
  168. </message>
  169. </forwarded>
  170. </result>
  171. </message>`);
  172. _converse.connection._dataRecv(test_utils.createRequest(message));
  173. result = u.toStanza(
  174. `<iq type='result' id='${iq_get.getAttribute('id')}'>
  175. <fin xmlns="urn:xmpp:mam:2" complete="true">
  176. <set xmlns="http://jabber.org/protocol/rsm">
  177. <first index="0">${msg_id}</first>
  178. <last>${msg_id}</last>
  179. <count>6</count>
  180. </set>
  181. </fin>
  182. </iq>`);
  183. _converse.connection._dataRecv(test_utils.createRequest(result));
  184. await u.waitUntil(() => view.model.messages.length === 5);
  185. expect(view.model.fetchArchivedMessages.calls.count()).toBe(3);
  186. const msg_els = view.content.querySelectorAll('.chat-msg__text');
  187. expect(Array.from(msg_els).map(e => e.textContent).join(' ')).toBe("2nd Message 3rd Message 4th Message 5th Message 6th Message");
  188. done();
  189. }));
  190. });
  191. describe("An archived message", function () {
  192. describe("when recieved", function () {
  193. it("updates the is_archived value of an already cached version",
  194. mock.initConverse(
  195. null, ['discoInitialized'], {},
  196. async function (done, _converse) {
  197. await test_utils.openAndEnterChatRoom(_converse, 'trek-radio@conference.lightwitch.org', 'romeo');
  198. const view = _converse.chatboxviews.get('trek-radio@conference.lightwitch.org');
  199. let stanza = u.toStanza(
  200. `<message xmlns="jabber:client" to="romeo@montague.lit/orchard" type="groupchat" from="trek-radio@conference.lightwitch.org/some1">
  201. <body>Hello</body>
  202. <stanza-id xmlns="urn:xmpp:sid:0" id="45fbbf2a-1059-479d-9283-c8effaf05621" by="trek-radio@conference.lightwitch.org"/>
  203. </message>`);
  204. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  205. await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
  206. expect(view.model.messages.length).toBe(1);
  207. expect(view.model.messages.at(0).get('is_archived')).toBe(false);
  208. expect(view.model.messages.at(0).get('stanza_id trek-radio@conference.lightwitch.org')).toBe('45fbbf2a-1059-479d-9283-c8effaf05621');
  209. stanza = u.toStanza(
  210. `<message xmlns="jabber:client"
  211. to="romeo@montague.lit/orchard"
  212. from="trek-radio@conference.lightwitch.org">
  213. <result xmlns="urn:xmpp:mam:2" queryid="82d9db27-6cf8-4787-8c2c-5a560263d823" id="45fbbf2a-1059-479d-9283-c8effaf05621">
  214. <forwarded xmlns="urn:xmpp:forward:0">
  215. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:17:23Z"/>
  216. <message from="trek-radio@conference.lightwitch.org/some1" type="groupchat">
  217. <body>Hello</body>
  218. </message>
  219. </forwarded>
  220. </result>
  221. </message>`);
  222. spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough();
  223. spyOn(view.model, 'updateMessage').and.callThrough();
  224. view.model.onMessage(stanza);
  225. await u.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count());
  226. expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1);
  227. const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue
  228. expect(result instanceof _converse.Message).toBe(true);
  229. expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
  230. await u.waitUntil(() => view.model.updateMessage.calls.count());
  231. expect(view.model.messages.length).toBe(1);
  232. expect(view.model.messages.at(0).get('is_archived')).toBe(true);
  233. expect(view.model.messages.at(0).get('stanza_id trek-radio@conference.lightwitch.org')).toBe('45fbbf2a-1059-479d-9283-c8effaf05621');
  234. done();
  235. }));
  236. it("isn't shown as duplicate by comparing its stanza id or archive id",
  237. mock.initConverse(
  238. null, ['discoInitialized'], {},
  239. async function (done, _converse) {
  240. await test_utils.openAndEnterChatRoom(_converse, 'trek-radio@conference.lightwitch.org', 'jcbrand');
  241. const view = _converse.chatboxviews.get('trek-radio@conference.lightwitch.org');
  242. let stanza = u.toStanza(
  243. `<message xmlns="jabber:client" to="jcbrand@lightwitch.org/converse.js-73057452" type="groupchat" from="trek-radio@conference.lightwitch.org/comndrdukath#0805 (STO)">
  244. <body>negan</body>
  245. <stanza-id xmlns="urn:xmpp:sid:0" id="45fbbf2a-1059-479d-9283-c8effaf05621" by="trek-radio@conference.lightwitch.org"/>
  246. </message>`);
  247. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  248. await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
  249. // Not sure whether such a race-condition might pose a problem
  250. // in "real-world" situations.
  251. stanza = u.toStanza(
  252. `<message xmlns="jabber:client"
  253. to="jcbrand@lightwitch.org/converse.js-73057452"
  254. from="trek-radio@conference.lightwitch.org">
  255. <result xmlns="urn:xmpp:mam:2" queryid="82d9db27-6cf8-4787-8c2c-5a560263d823" id="45fbbf2a-1059-479d-9283-c8effaf05621">
  256. <forwarded xmlns="urn:xmpp:forward:0">
  257. <delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:17:23Z"/>
  258. <message from="trek-radio@conference.lightwitch.org/comndrdukath#0805 (STO)" type="groupchat">
  259. <body>negan</body>
  260. </message>
  261. </forwarded>
  262. </result>
  263. </message>`);
  264. spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough();
  265. view.model.onMessage(stanza);
  266. await u.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count());
  267. expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1);
  268. const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue
  269. expect(result instanceof _converse.Message).toBe(true);
  270. expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
  271. done();
  272. }));
  273. it("isn't shown as duplicate by comparing only the archive id",
  274. mock.initConverse(
  275. null, ['discoInitialized'], {},
  276. async function (done, _converse) {
  277. await test_utils.openAndEnterChatRoom(_converse, 'discuss@conference.conversejs.org', 'romeo');
  278. const view = _converse.chatboxviews.get('discuss@conference.conversejs.org');
  279. let stanza = u.toStanza(
  280. `<message xmlns="jabber:client" to="romeo@montague.lit/orchard" from="discuss@conference.conversejs.org">
  281. <result xmlns="urn:xmpp:mam:2" queryid="06fea9ca-97c9-48c4-8583-009ff54ea2e8" id="7a9fde91-4387-4bf8-b5d3-978dab8f6bf3">
  282. <forwarded xmlns="urn:xmpp:forward:0">
  283. <delay xmlns="urn:xmpp:delay" stamp="2018-12-05T04:53:12Z"/>
  284. <message xmlns="jabber:client" to="discuss@conference.conversejs.org" type="groupchat" xml:lang="en" from="discuss@conference.conversejs.org/prezel">
  285. <body>looks like omemo fails completely with "bundle is undefined" when there is a device in the devicelist that has no keys published</body>
  286. <x xmlns="http://jabber.org/protocol/muc#user">
  287. <item affiliation="none" jid="prezel@blubber.im" role="participant"/>
  288. </x>
  289. </message>
  290. </forwarded>
  291. </result>
  292. </message>`);
  293. view.model.onMessage(stanza);
  294. await u.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
  295. expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
  296. stanza = u.toStanza(
  297. `<message xmlns="jabber:client" to="romeo@montague.lit/orchard" from="discuss@conference.conversejs.org">
  298. <result xmlns="urn:xmpp:mam:2" queryid="06fea9ca-97c9-48c4-8583-009ff54ea2e8" id="7a9fde91-4387-4bf8-b5d3-978dab8f6bf3">
  299. <forwarded xmlns="urn:xmpp:forward:0">
  300. <delay xmlns="urn:xmpp:delay" stamp="2018-12-05T04:53:12Z"/>
  301. <message xmlns="jabber:client" to="discuss@conference.conversejs.org" type="groupchat" xml:lang="en" from="discuss@conference.conversejs.org/prezel">
  302. <body>looks like omemo fails completely with "bundle is undefined" when there is a device in the devicelist that has no keys published</body>
  303. <x xmlns="http://jabber.org/protocol/muc#user">
  304. <item affiliation="none" jid="prezel@blubber.im" role="participant"/>
  305. </x>
  306. </message>
  307. </forwarded>
  308. </result>
  309. </message>`);
  310. spyOn(view.model, 'findDuplicateFromArchiveID').and.callThrough();
  311. view.model.onMessage(stanza);
  312. await u.waitUntil(() => view.model.findDuplicateFromArchiveID.calls.count());
  313. expect(view.model.findDuplicateFromArchiveID.calls.count()).toBe(1);
  314. const result = await view.model.findDuplicateFromArchiveID.calls.all()[0].returnValue
  315. expect(result instanceof _converse.Message).toBe(true);
  316. expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
  317. done();
  318. }))
  319. });
  320. });
  321. describe("The archive.query API", function () {
  322. it("can be used to query for all archived messages",
  323. mock.initConverse(
  324. null, ['discoInitialized'], {},
  325. async function (done, _converse) {
  326. const sendIQ = _converse.connection.sendIQ;
  327. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  328. let sent_stanza, IQ_id;
  329. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  330. sent_stanza = iq;
  331. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  332. });
  333. _converse.api.archive.query();
  334. await u.waitUntil(() => sent_stanza);
  335. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  336. expect(sent_stanza.toString()).toBe(
  337. `<iq id="${IQ_id}" type="set" xmlns="jabber:client"><query queryid="${queryid}" xmlns="urn:xmpp:mam:2"/></iq>`);
  338. done();
  339. }));
  340. it("can be used to query for all messages to/from a particular JID",
  341. mock.initConverse(
  342. null, [], {},
  343. async function (done, _converse) {
  344. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  345. let sent_stanza, IQ_id;
  346. const sendIQ = _converse.connection.sendIQ;
  347. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  348. sent_stanza = iq;
  349. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  350. });
  351. _converse.api.archive.query({'with':'juliet@capulet.lit'});
  352. await u.waitUntil(() => sent_stanza);
  353. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  354. expect(sent_stanza.toString()).toBe(
  355. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  356. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  357. `<x type="submit" xmlns="jabber:x:data">`+
  358. `<field type="hidden" var="FORM_TYPE">`+
  359. `<value>urn:xmpp:mam:2</value>`+
  360. `</field>`+
  361. `<field var="with">`+
  362. `<value>juliet@capulet.lit</value>`+
  363. `</field>`+
  364. `</x>`+
  365. `</query>`+
  366. `</iq>`);
  367. done();
  368. }));
  369. it("can be used to query for archived messages from a chat room",
  370. mock.initConverse(
  371. null, [], {},
  372. async function (done, _converse) {
  373. const room_jid = 'coven@chat.shakespeare.lit';
  374. _converse.api.archive.query({'with': room_jid, 'groupchat': true});
  375. await test_utils.waitUntilDiscoConfirmed(_converse, room_jid, null, [Strophe.NS.MAM]);
  376. const sent_stanzas = _converse.connection.sent_stanzas;
  377. const stanza = await u.waitUntil(
  378. () => sent_stanzas.filter(s => sizzle(`[xmlns="${Strophe.NS.MAM}"]`, s).length).pop());
  379. const queryid = stanza.querySelector('query').getAttribute('queryid');
  380. expect(Strophe.serialize(stanza)).toBe(
  381. `<iq id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
  382. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  383. `<x type="submit" xmlns="jabber:x:data">`+
  384. `<field type="hidden" var="FORM_TYPE">`+
  385. `<value>urn:xmpp:mam:2</value>`+
  386. `</field>`+
  387. `</x>`+
  388. `</query>`+
  389. `</iq>`);
  390. done();
  391. }));
  392. it("checks whether returned MAM messages from a MUC room are from the right JID",
  393. mock.initConverse(
  394. null, [], {},
  395. async function (done, _converse) {
  396. const room_jid = 'coven@chat.shakespeare.lit';
  397. const promise = _converse.api.archive.query({'with': room_jid, 'groupchat': true, 'max':'10'});
  398. await test_utils.waitUntilDiscoConfirmed(_converse, room_jid, null, [Strophe.NS.MAM]);
  399. const sent_stanzas = _converse.connection.sent_stanzas;
  400. const sent_stanza = await u.waitUntil(
  401. () => sent_stanzas.filter(s => sizzle(`[xmlns="${Strophe.NS.MAM}"]`, s).length).pop());
  402. const queryid = sent_stanza.querySelector('query').getAttribute('queryid');
  403. /* <message id='iasd207' from='coven@chat.shakespeare.lit' to='hag66@shakespeare.lit/pda'>
  404. * <result xmlns='urn:xmpp:mam:2' queryid='g27' id='34482-21985-73620'>
  405. * <forwarded xmlns='urn:xmpp:forward:0'>
  406. * <delay xmlns='urn:xmpp:delay' stamp='2002-10-13T23:58:37Z'/>
  407. * <message xmlns="jabber:client"
  408. * from='coven@chat.shakespeare.lit/firstwitch'
  409. * id='162BEBB1-F6DB-4D9A-9BD8-CFDCC801A0B2'
  410. * type='groupchat'>
  411. * <body>Thrice the brinded cat hath mew'd.</body>
  412. * <x xmlns='http://jabber.org/protocol/muc#user'>
  413. * <item affiliation='none'
  414. * jid='witch1@shakespeare.lit'
  415. * role='participant' />
  416. * </x>
  417. * </message>
  418. * </forwarded>
  419. * </result>
  420. * </message>
  421. */
  422. const msg1 = $msg({'id':'iasd207', 'from': 'other@chat.shakespear.lit', 'to': 'romeo@montague.lit'})
  423. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid':queryid, 'id':'34482-21985-73620'})
  424. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  425. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:08:25Z'}).up()
  426. .c('message', {
  427. 'xmlns':'jabber:client',
  428. 'to':'romeo@montague.lit',
  429. 'id':'162BEBB1-F6DB-4D9A-9BD8-CFDCC801A0B2',
  430. 'from':'coven@chat.shakespeare.lit/firstwitch',
  431. 'type':'groupchat' })
  432. .c('body').t("Thrice the brinded cat hath mew'd.");
  433. _converse.connection._dataRecv(test_utils.createRequest(msg1));
  434. /* Send an <iq> stanza to indicate the end of the result set.
  435. *
  436. * <iq type='result' id='juliet1'>
  437. * <fin xmlns='urn:xmpp:mam:2'>
  438. * <set xmlns='http://jabber.org/protocol/rsm'>
  439. * <first index='0'>28482-98726-73623</first>
  440. * <last>09af3-cc343-b409f</last>
  441. * <count>20</count>
  442. * </set>
  443. * </iq>
  444. */
  445. const stanza = $iq({'type': 'result', 'id': sent_stanza.getAttribute('id')})
  446. .c('fin', {'xmlns': 'urn:xmpp:mam:2'})
  447. .c('set', {'xmlns': 'http://jabber.org/protocol/rsm'})
  448. .c('first', {'index': '0'}).t('23452-4534-1').up()
  449. .c('last').t('09af3-cc343-b409f').up()
  450. .c('count').t('16');
  451. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  452. const result = await promise;
  453. expect(result.messages.length).toBe(0);
  454. done();
  455. }));
  456. it("can be used to query for all messages in a certain timespan",
  457. mock.initConverse(
  458. null, [], {},
  459. async function (done, _converse) {
  460. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  461. let sent_stanza, IQ_id;
  462. const sendIQ = _converse.connection.sendIQ;
  463. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  464. sent_stanza = iq;
  465. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  466. });
  467. const start = '2010-06-07T00:00:00Z';
  468. const end = '2010-07-07T13:23:54Z';
  469. _converse.api.archive.query({
  470. 'start': start,
  471. 'end': end
  472. });
  473. await u.waitUntil(() => sent_stanza);
  474. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  475. expect(sent_stanza.toString()).toBe(
  476. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  477. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  478. `<x type="submit" xmlns="jabber:x:data">`+
  479. `<field type="hidden" var="FORM_TYPE">`+
  480. `<value>urn:xmpp:mam:2</value>`+
  481. `</field>`+
  482. `<field var="start">`+
  483. `<value>${dayjs(start).toISOString()}</value>`+
  484. `</field>`+
  485. `<field var="end">`+
  486. `<value>${dayjs(end).toISOString()}</value>`+
  487. `</field>`+
  488. `</x>`+
  489. `</query>`+
  490. `</iq>`
  491. );
  492. done();
  493. }));
  494. it("throws a TypeError if an invalid date is provided",
  495. mock.initConverse(
  496. null, [], {},
  497. async function (done, _converse) {
  498. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  499. try {
  500. await _converse.api.archive.query({'start': 'not a real date'});
  501. } catch (e) {
  502. expect(() => {throw e}).toThrow(new TypeError('archive.query: invalid date provided for: start'));
  503. }
  504. done();
  505. }));
  506. it("can be used to query for all messages after a certain time",
  507. mock.initConverse(
  508. null, [], {},
  509. async function (done, _converse) {
  510. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  511. let sent_stanza, IQ_id;
  512. const sendIQ = _converse.connection.sendIQ;
  513. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  514. sent_stanza = iq;
  515. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  516. });
  517. if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
  518. _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
  519. }
  520. const start = '2010-06-07T00:00:00Z';
  521. _converse.api.archive.query({'start': start});
  522. await u.waitUntil(() => sent_stanza);
  523. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  524. expect(sent_stanza.toString()).toBe(
  525. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  526. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  527. `<x type="submit" xmlns="jabber:x:data">`+
  528. `<field type="hidden" var="FORM_TYPE">`+
  529. `<value>urn:xmpp:mam:2</value>`+
  530. `</field>`+
  531. `<field var="start">`+
  532. `<value>${dayjs(start).toISOString()}</value>`+
  533. `</field>`+
  534. `</x>`+
  535. `</query>`+
  536. `</iq>`
  537. );
  538. done();
  539. }));
  540. it("can be used to query for a limited set of results",
  541. mock.initConverse(
  542. null, [], {},
  543. async function (done, _converse) {
  544. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  545. let sent_stanza, IQ_id;
  546. const sendIQ = _converse.connection.sendIQ;
  547. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  548. sent_stanza = iq;
  549. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  550. });
  551. const start = '2010-06-07T00:00:00Z';
  552. _converse.api.archive.query({'start': start, 'max':10});
  553. await u.waitUntil(() => sent_stanza);
  554. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  555. expect(sent_stanza.toString()).toBe(
  556. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  557. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  558. `<x type="submit" xmlns="jabber:x:data">`+
  559. `<field type="hidden" var="FORM_TYPE">`+
  560. `<value>urn:xmpp:mam:2</value>`+
  561. `</field>`+
  562. `<field var="start">`+
  563. `<value>${dayjs(start).toISOString()}</value>`+
  564. `</field>`+
  565. `</x>`+
  566. `<set xmlns="http://jabber.org/protocol/rsm">`+
  567. `<max>10</max>`+
  568. `</set>`+
  569. `</query>`+
  570. `</iq>`
  571. );
  572. done();
  573. }));
  574. it("can be used to page through results",
  575. mock.initConverse(
  576. null, [], {},
  577. async function (done, _converse) {
  578. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  579. let sent_stanza, IQ_id;
  580. const sendIQ = _converse.connection.sendIQ;
  581. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  582. sent_stanza = iq;
  583. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  584. });
  585. const start = '2010-06-07T00:00:00Z';
  586. _converse.api.archive.query({
  587. 'start': start,
  588. 'after': '09af3-cc343-b409f',
  589. 'max':10
  590. });
  591. await u.waitUntil(() => sent_stanza);
  592. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  593. expect(sent_stanza.toString()).toBe(
  594. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  595. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  596. `<x type="submit" xmlns="jabber:x:data">`+
  597. `<field type="hidden" var="FORM_TYPE">`+
  598. `<value>urn:xmpp:mam:2</value>`+
  599. `</field>`+
  600. `<field var="start">`+
  601. `<value>${dayjs(start).toISOString()}</value>`+
  602. `</field>`+
  603. `</x>`+
  604. `<set xmlns="http://jabber.org/protocol/rsm">`+
  605. `<max>10</max>`+
  606. `<after>09af3-cc343-b409f</after>`+
  607. `</set>`+
  608. `</query>`+
  609. `</iq>`);
  610. done();
  611. }));
  612. it("accepts \"before\" with an empty string as value to reverse the order",
  613. mock.initConverse(
  614. null, [], {},
  615. async function (done, _converse) {
  616. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  617. let sent_stanza, IQ_id;
  618. const sendIQ = _converse.connection.sendIQ;
  619. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  620. sent_stanza = iq;
  621. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  622. });
  623. _converse.api.archive.query({'before': '', 'max':10});
  624. await u.waitUntil(() => sent_stanza);
  625. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  626. expect(sent_stanza.toString()).toBe(
  627. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  628. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  629. `<x type="submit" xmlns="jabber:x:data">`+
  630. `<field type="hidden" var="FORM_TYPE">`+
  631. `<value>urn:xmpp:mam:2</value>`+
  632. `</field>`+
  633. `</x>`+
  634. `<set xmlns="http://jabber.org/protocol/rsm">`+
  635. `<max>10</max>`+
  636. `<before></before>`+
  637. `</set>`+
  638. `</query>`+
  639. `</iq>`);
  640. done();
  641. }));
  642. it("accepts a _converse.RSM object for the query options",
  643. mock.initConverse(
  644. null, [], {},
  645. async function (done, _converse) {
  646. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  647. let sent_stanza, IQ_id;
  648. const sendIQ = _converse.connection.sendIQ;
  649. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  650. sent_stanza = iq;
  651. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  652. });
  653. // Normally the user wouldn't manually make a _converse.RSM object
  654. // and pass it in. However, in the callback method an RSM object is
  655. // returned which can be reused for easy paging. This test is
  656. // more for that usecase.
  657. const rsm = new _converse.RSM({'max': '10'});
  658. rsm['with'] = 'romeo@montague.lit'; // eslint-disable-line dot-notation
  659. rsm.start = '2010-06-07T00:00:00Z';
  660. _converse.api.archive.query(rsm);
  661. await u.waitUntil(() => sent_stanza);
  662. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  663. expect(sent_stanza.toString()).toBe(
  664. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  665. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  666. `<x type="submit" xmlns="jabber:x:data">`+
  667. `<field type="hidden" var="FORM_TYPE">`+
  668. `<value>urn:xmpp:mam:2</value>`+
  669. `</field>`+
  670. `<field var="with">`+
  671. `<value>romeo@montague.lit</value>`+
  672. `</field>`+
  673. `<field var="start">`+
  674. `<value>${dayjs(rsm.start).toISOString()}</value>`+
  675. `</field>`+
  676. `</x>`+
  677. `<set xmlns="http://jabber.org/protocol/rsm">`+
  678. `<max>10</max>`+
  679. `</set>`+
  680. `</query>`+
  681. `</iq>`);
  682. done();
  683. }));
  684. it("returns an object which includes the messages and a _converse.RSM object",
  685. mock.initConverse(
  686. null, [], {},
  687. async function (done, _converse) {
  688. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  689. let sent_stanza, IQ_id;
  690. const sendIQ = _converse.connection.sendIQ;
  691. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  692. sent_stanza = iq;
  693. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  694. });
  695. const promise = _converse.api.archive.query({'with': 'romeo@capulet.lit', 'max':'10'});
  696. await u.waitUntil(() => sent_stanza);
  697. const queryid = sent_stanza.nodeTree.querySelector('query').getAttribute('queryid');
  698. /* <message id='aeb213' to='juliet@capulet.lit/chamber'>
  699. * <result xmlns='urn:xmpp:mam:2' queryid='f27' id='28482-98726-73623'>
  700. * <forwarded xmlns='urn:xmpp:forward:0'>
  701. * <delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/>
  702. * <message xmlns='jabber:client'
  703. * to='juliet@capulet.lit/balcony'
  704. * from='romeo@montague.lit/orchard'
  705. * type='chat'>
  706. * <body>Call me but love, and I'll be new baptized; Henceforth I never will be Romeo.</body>
  707. * </message>
  708. * </forwarded>
  709. * </result>
  710. * </message>
  711. */
  712. const msg1 = $msg({'id':'aeb213', 'to':'juliet@capulet.lit/chamber'})
  713. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid':queryid, 'id':'28482-98726-73623'})
  714. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  715. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:08:25Z'}).up()
  716. .c('message', {
  717. 'xmlns':'jabber:client',
  718. 'to':'juliet@capulet.lit/balcony',
  719. 'from':'romeo@montague.lit/orchard',
  720. 'type':'chat' })
  721. .c('body').t("Call me but love, and I'll be new baptized;");
  722. _converse.connection._dataRecv(test_utils.createRequest(msg1));
  723. const msg2 = $msg({'id':'aeb213', 'to':'juliet@capulet.lit/chamber'})
  724. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid':queryid, 'id':'28482-98726-73624'})
  725. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  726. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:08:25Z'}).up()
  727. .c('message', {
  728. 'xmlns':'jabber:client',
  729. 'to':'juliet@capulet.lit/balcony',
  730. 'from':'romeo@montague.lit/orchard',
  731. 'type':'chat' })
  732. .c('body').t("Henceforth I never will be Romeo.");
  733. _converse.connection._dataRecv(test_utils.createRequest(msg2));
  734. /* Send an <iq> stanza to indicate the end of the result set.
  735. *
  736. * <iq type='result' id='juliet1'>
  737. * <fin xmlns='urn:xmpp:mam:2'>
  738. * <set xmlns='http://jabber.org/protocol/rsm'>
  739. * <first index='0'>28482-98726-73623</first>
  740. * <last>09af3-cc343-b409f</last>
  741. * <count>20</count>
  742. * </set>
  743. * </iq>
  744. */
  745. const stanza = $iq({'type': 'result', 'id': IQ_id})
  746. .c('fin', {'xmlns': 'urn:xmpp:mam:2'})
  747. .c('set', {'xmlns': 'http://jabber.org/protocol/rsm'})
  748. .c('first', {'index': '0'}).t('23452-4534-1').up()
  749. .c('last').t('09af3-cc343-b409f').up()
  750. .c('count').t('16');
  751. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  752. const result = await promise;
  753. expect(result.messages.length).toBe(2);
  754. expect(result.messages[0].outerHTML).toBe(msg1.nodeTree.outerHTML);
  755. expect(result.messages[1].outerHTML).toBe(msg2.nodeTree.outerHTML);
  756. expect(result.rsm['with']).toBe('romeo@capulet.lit'); // eslint-disable-line dot-notation
  757. expect(result.rsm.max).toBe('10');
  758. expect(result.rsm.count).toBe('16');
  759. expect(result.rsm.first).toBe('23452-4534-1');
  760. expect(result.rsm.last).toBe('09af3-cc343-b409f');
  761. done()
  762. }));
  763. });
  764. describe("The default preference", function () {
  765. it("is set once server support for MAM has been confirmed",
  766. mock.initConverse(
  767. null, [], {},
  768. async function (done, _converse) {
  769. const entity = await _converse.api.disco.entities.get(_converse.domain);
  770. let sent_stanza, IQ_id;
  771. const sendIQ = _converse.connection.sendIQ;
  772. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  773. sent_stanza = iq;
  774. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  775. });
  776. spyOn(_converse, 'onMAMPreferences').and.callThrough();
  777. _converse.message_archiving = 'never';
  778. const feature = new Backbone.Model({
  779. 'var': Strophe.NS.MAM
  780. });
  781. spyOn(feature, 'save').and.callFake(feature.set); // Save will complain about a url not being set
  782. entity.onFeatureAdded(feature);
  783. expect(_converse.connection.sendIQ).toHaveBeenCalled();
  784. expect(sent_stanza.toLocaleString()).toBe(
  785. `<iq id="${IQ_id}" type="get" xmlns="jabber:client">`+
  786. `<prefs xmlns="urn:xmpp:mam:2"/>`+
  787. `</iq>`);
  788. /* Example 20. Server responds with current preferences
  789. *
  790. * <iq type='result' id='juliet2'>
  791. * <prefs xmlns='urn:xmpp:mam:0' default='roster'>
  792. * <always/>
  793. * <never/>
  794. * </prefs>
  795. * </iq>
  796. */
  797. let stanza = $iq({'type': 'result', 'id': IQ_id})
  798. .c('prefs', {'xmlns': Strophe.NS.MAM, 'default':'roster'})
  799. .c('always').c('jid').t('romeo@montague.lit').up().up()
  800. .c('never').c('jid').t('montague@montague.lit');
  801. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  802. await u.waitUntil(() => _converse.onMAMPreferences.calls.count());
  803. expect(_converse.onMAMPreferences).toHaveBeenCalled();
  804. expect(sent_stanza.toString()).toBe(
  805. `<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
  806. `<prefs default="never" xmlns="urn:xmpp:mam:2">`+
  807. `<always><jid>romeo@montague.lit</jid></always>`+
  808. `<never><jid>montague@montague.lit</jid></never>`+
  809. `</prefs>`+
  810. `</iq>`
  811. );
  812. expect(feature.get('preference')).toBe(undefined);
  813. /* <iq type='result' id='juliet3'>
  814. * <prefs xmlns='urn:xmpp:mam:0' default='always'>
  815. * <always>
  816. * <jid>romeo@montague.lit</jid>
  817. * </always>
  818. * <never>
  819. * <jid>montague@montague.lit</jid>
  820. * </never>
  821. * </prefs>
  822. * </iq>
  823. */
  824. stanza = $iq({'type': 'result', 'id': IQ_id})
  825. .c('prefs', {'xmlns': Strophe.NS.MAM, 'default':'always'})
  826. .c('always').up()
  827. .c('never');
  828. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  829. await u.waitUntil(() => feature.save.calls.count());
  830. expect(feature.save).toHaveBeenCalled();
  831. expect(feature.get('preferences')['default']).toBe('never'); // eslint-disable-line dot-notation
  832. done();
  833. }));
  834. });
  835. });
  836. describe("Chatboxes", function () {
  837. describe("A Chatbox", function () {
  838. it("will fetch archived messages once it's opened",
  839. mock.initConverse(
  840. null, ['discoInitialized'], {},
  841. async function (done, _converse) {
  842. await test_utils.waitForRoster(_converse, 'current', 1);
  843. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  844. await test_utils.openChatBoxFor(_converse, contact_jid);
  845. await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, null, [Strophe.NS.MAM]);
  846. let sent_stanza, IQ_id;
  847. const sendIQ = _converse.connection.sendIQ;
  848. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  849. sent_stanza = iq;
  850. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  851. });
  852. await u.waitUntil(() => sent_stanza);
  853. const stanza_el = sent_stanza.root().nodeTree;
  854. const queryid = stanza_el.querySelector('query').getAttribute('queryid');
  855. expect(sent_stanza.toString()).toBe(
  856. `<iq id="${stanza_el.getAttribute('id')}" type="set" xmlns="jabber:client">`+
  857. `<query queryid="${queryid}" xmlns="urn:xmpp:mam:2">`+
  858. `<x type="submit" xmlns="jabber:x:data">`+
  859. `<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
  860. `<field var="with"><value>mercutio@montague.lit</value></field>`+
  861. `</x>`+
  862. `<set xmlns="http://jabber.org/protocol/rsm"><max>50</max><before></before></set>`+
  863. `</query>`+
  864. `</iq>`
  865. );
  866. const msg1 = $msg({'id':'aeb213', 'to': contact_jid})
  867. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid':queryid, 'id':'28482-98726-73623'})
  868. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  869. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:08:25Z'}).up()
  870. .c('message', {
  871. 'xmlns':'jabber:client',
  872. 'to': contact_jid,
  873. 'from': _converse.bare_jid,
  874. 'type':'chat' })
  875. .c('body').t("Call me but love, and I'll be new baptized;");
  876. _converse.connection._dataRecv(test_utils.createRequest(msg1));
  877. const msg2 = $msg({'id':'aeb213', 'to': contact_jid})
  878. .c('result', {'xmlns': 'urn:xmpp:mam:2', 'queryid':queryid, 'id':'28482-98726-73624'})
  879. .c('forwarded', {'xmlns':'urn:xmpp:forward:0'})
  880. .c('delay', {'xmlns':'urn:xmpp:delay', 'stamp':'2010-07-10T23:08:25Z'}).up()
  881. .c('message', {
  882. 'xmlns':'jabber:client',
  883. 'to': contact_jid,
  884. 'from': _converse.bare_jid,
  885. 'type':'chat' })
  886. .c('body').t("Henceforth I never will be Romeo.");
  887. _converse.connection._dataRecv(test_utils.createRequest(msg2));
  888. const stanza = $iq({'type': 'result', 'id': IQ_id})
  889. .c('fin', {'xmlns': 'urn:xmpp:mam:2'})
  890. .c('set', {'xmlns': 'http://jabber.org/protocol/rsm'})
  891. .c('first', {'index': '0'}).t('23452-4534-1').up()
  892. .c('last').t('09af3-cc343-b409f').up()
  893. .c('count').t('16');
  894. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  895. done();
  896. }));
  897. });
  898. });
  899. }));