chatbox.js 88 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554
  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 _ = converse.env._;
  10. const $iq = converse.env.$iq;
  11. const $msg = converse.env.$msg;
  12. const Strophe = converse.env.Strophe;
  13. const u = converse.env.utils;
  14. const sizzle = converse.env.sizzle;
  15. return describe("Chatboxes", function () {
  16. describe("A Chatbox", function () {
  17. it("has a /help command to show the available commands",
  18. mock.initConverse(
  19. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  20. async function (done, _converse) {
  21. await test_utils.waitForRoster(_converse, 'current', 1);
  22. test_utils.openControlBox();
  23. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  24. await test_utils.openChatBoxFor(_converse, contact_jid);
  25. const view = _converse.chatboxviews.get(contact_jid);
  26. test_utils.sendMessage(view, '/help');
  27. const info_messages = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info:not(.chat-date)'), 0);
  28. expect(info_messages.length).toBe(3);
  29. expect(info_messages.pop().textContent).toBe('/help: Show this menu');
  30. expect(info_messages.pop().textContent).toBe('/me: Write in the third person');
  31. expect(info_messages.pop().textContent).toBe('/clear: Remove messages');
  32. const msg = $msg({
  33. from: contact_jid,
  34. to: _converse.connection.jid,
  35. type: 'chat',
  36. id: (new Date()).getTime()
  37. }).c('body').t('hello world').tree();
  38. await _converse.chatboxes.onMessage(msg);
  39. await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
  40. expect(view.content.lastElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1);
  41. done();
  42. }));
  43. it("supports the /me command",
  44. mock.initConverse(
  45. null, ['rosterGroupsFetched'], {},
  46. async function (done, _converse) {
  47. await test_utils.waitForRoster(_converse, 'current');
  48. await test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']);
  49. await test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
  50. await test_utils.openControlBox();
  51. expect(_converse.chatboxes.length).toEqual(1);
  52. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  53. let message = '/me is tired';
  54. const msg = $msg({
  55. from: sender_jid,
  56. to: _converse.connection.jid,
  57. type: 'chat',
  58. id: (new Date()).getTime()
  59. }).c('body').t(message).up()
  60. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  61. await _converse.chatboxes.onMessage(msg);
  62. const view = _converse.chatboxviews.get(sender_jid);
  63. await new Promise((resolve, reject) => view.once('messageInserted', resolve));
  64. expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(1);
  65. expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, '**Max Frankfurter')).toBeTruthy();
  66. expect(view.el.querySelector('.chat-msg__text').textContent).toBe('is tired');
  67. message = '/me is as well';
  68. await test_utils.sendMessage(view, message);
  69. expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(2);
  70. await test_utils.waitUntil(() => sizzle('.chat-msg__author:last', view.el).pop().textContent.trim() === '**Max Mustermann');
  71. const last_el = sizzle('.chat-msg__text:last', view.el).pop();
  72. expect(last_el.textContent).toBe('is as well');
  73. expect(u.hasClass('chat-msg--followup', last_el)).toBe(false);
  74. // Check that /me messages after a normal message don't
  75. // get the 'chat-msg--followup' class.
  76. message = 'This a normal message';
  77. await test_utils.sendMessage(view, message);
  78. let message_el = view.el.querySelector('.message:last-child');
  79. expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy();
  80. message = '/me wrote a 3rd person message';
  81. await test_utils.sendMessage(view, message);
  82. message_el = view.el.querySelector('.message:last-child');
  83. expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(3);
  84. expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe('wrote a 3rd person message');
  85. expect(u.isVisible(sizzle('.chat-msg__author:last', view.el).pop())).toBeTruthy();
  86. expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy();
  87. done();
  88. }));
  89. it("is created when you click on a roster item", mock.initConverse(
  90. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  91. async function (done, _converse) {
  92. await test_utils.waitForRoster(_converse, 'current');
  93. test_utils.openControlBox();
  94. // openControlBox was called earlier, so the controlbox is
  95. // visible, but no other chat boxes have been created.
  96. expect(_converse.chatboxes.length).toEqual(1);
  97. spyOn(_converse.chatboxviews, 'trimChats');
  98. expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(1); // Controlbox is open
  99. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
  100. const online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat');
  101. expect(online_contacts.length).toBe(15);
  102. let el = online_contacts[0];
  103. const jid = el.textContent.trim().replace(/ /g,'.').toLowerCase() + '@localhost';
  104. el.click();
  105. await test_utils.waitUntil(() => _converse.chatboxes.length == 2);
  106. const chatboxview = _converse.chatboxviews.get(jid);
  107. expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
  108. // Check that new chat boxes are created to the left of the
  109. // controlbox (but to the right of all existing chat boxes)
  110. expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(2);
  111. expect(document.querySelectorAll("#conversejs .chatbox")[1].id).toBe(chatboxview.model.get('box_id'));
  112. online_contacts[1].click();
  113. await test_utils.waitUntil(() => _converse.chatboxes.length == 3);
  114. el = online_contacts[1];
  115. const new_jid = el.textContent.trim().replace(/ /g,'.').toLowerCase() + '@localhost';
  116. const new_chatboxview = _converse.chatboxviews.get(new_jid);
  117. expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
  118. // Check that new chat boxes are created to the left of the
  119. // controlbox (but to the right of all existing chat boxes)
  120. expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(3);
  121. expect(document.querySelectorAll("#conversejs .chatbox")[2].id).toBe(chatboxview.model.get('box_id'));
  122. expect(document.querySelectorAll("#conversejs .chatbox")[1].id).toBe(new_chatboxview.model.get('box_id'));
  123. done();
  124. }));
  125. it("opens when a new message is received", mock.initConverse(
  126. null, ['rosterGroupsFetched'], {'allow_non_roster_messaging': true},
  127. async function (done, _converse) {
  128. _converse.api.trigger('rosterContactsFetched');
  129. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  130. const stanza = u.toStanza(`
  131. <message from="${sender_jid}"
  132. type="chat"
  133. to="dummy@localhost/resource">
  134. <body>Hey\nHave you heard the news?</body>
  135. </message>`);
  136. const message_promise = new Promise(resolve => _converse.api.listen.on('message', resolve));
  137. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  138. await test_utils.waitUntil(() => _converse.api.chats.get().length === 2);
  139. await test_utils.waitUntil(() => message_promise);
  140. expect(_converse.chatboxviews.keys().length).toBe(2);
  141. done();
  142. }));
  143. it("doesn't open when a message without body is received", mock.initConverse(
  144. null, ['rosterGroupsFetched'], {},
  145. async function (done, _converse) {
  146. await test_utils.waitForRoster(_converse, 'current', 1);
  147. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  148. const stanza = u.toStanza(`
  149. <message from="${sender_jid}"
  150. type="chat"
  151. to="dummy@localhost/resource">
  152. <composing xmlns="http://jabber.org/protocol/chatstates"/>
  153. </message>`);
  154. const message_promise = new Promise(resolve => _converse.api.listen.on('message', resolve))
  155. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  156. await test_utils.waitUntil(() => message_promise);
  157. expect(_converse.chatboxviews.keys().length).toBe(1);
  158. done();
  159. }));
  160. it("can be trimmed to conserve space",
  161. mock.initConverse(null, ['rosterGroupsFetched'], {},
  162. async function (done, _converse) {
  163. spyOn(_converse.chatboxviews, 'trimChats');
  164. const trimmed_chatboxes = _converse.minimized_chats;
  165. spyOn(trimmed_chatboxes, 'addChat').and.callThrough();
  166. spyOn(trimmed_chatboxes, 'removeChat').and.callThrough();
  167. await test_utils.waitForRoster(_converse, 'current');
  168. test_utils.openControlBox();
  169. let jid, chatboxview;
  170. // openControlBox was called earlier, so the controlbox is
  171. // visible, but no other chat boxes have been created.
  172. expect(_converse.chatboxes.length).toEqual(1);
  173. expect(document.querySelectorAll("#conversejs .chatbox").length).toBe(1); // Controlbox is open
  174. _converse.rosterview.update(); // XXX: Hack to make sure $roster element is attached.
  175. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length);
  176. // Test that they can be maximized again
  177. const online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat');
  178. expect(online_contacts.length).toBe(15);
  179. let i;
  180. for (i=0; i<online_contacts.length; i++) {
  181. const el = online_contacts[i];
  182. el.click();
  183. }
  184. await test_utils.waitUntil(() => _converse.chatboxes.length == 16);
  185. expect(_converse.chatboxviews.trimChats.calls.count()).toBe(16);
  186. for (i=0; i<online_contacts.length; i++) {
  187. const el = online_contacts[i];
  188. jid = _.trim(el.textContent.trim()).replace(/ /g,'.').toLowerCase() + '@localhost';
  189. chatboxview = _converse.chatboxviews.get(jid);
  190. spyOn(chatboxview, 'minimize').and.callThrough();
  191. chatboxview.model.set({'minimized': true});
  192. expect(trimmed_chatboxes.addChat).toHaveBeenCalled();
  193. expect(chatboxview.minimize).toHaveBeenCalled();
  194. }
  195. await test_utils.waitUntil(() => _converse.chatboxviews.keys().length);
  196. var key = _converse.chatboxviews.keys()[1];
  197. const trimmedview = trimmed_chatboxes.get(key);
  198. const chatbox = trimmedview.model;
  199. spyOn(chatbox, 'maximize').and.callThrough();
  200. spyOn(trimmedview, 'restore').and.callThrough();
  201. trimmedview.delegateEvents();
  202. trimmedview.el.querySelector("a.restore-chat").click();
  203. expect(trimmedview.restore).toHaveBeenCalled();
  204. expect(chatbox.maximize).toHaveBeenCalled();
  205. expect(_converse.chatboxviews.trimChats.calls.count()).toBe(17);
  206. done();
  207. }));
  208. it("can be opened in minimized mode initially",
  209. mock.initConverse(
  210. null, ['rosterGroupsFetched'], {},
  211. async function (done, _converse) {
  212. await test_utils.waitForRoster(_converse, 'current');
  213. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  214. const chat = _converse.api.chats.create(sender_jid, {
  215. minimized: true
  216. });
  217. const chatBoxView = _converse.chatboxviews.get(sender_jid);
  218. expect(u.isVisible(chatBoxView.el)).toBeFalsy();
  219. const minimized_chat = _converse.minimized_chats.get(sender_jid);
  220. expect(minimized_chat).toBeTruthy();
  221. expect(u.isVisible(minimized_chat.el)).toBeTruthy();
  222. done();
  223. }));
  224. it("is focused if its already open and you click on its corresponding roster item",
  225. mock.initConverse(null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  226. async function (done, _converse) {
  227. await test_utils.waitForRoster(_converse, 'current');
  228. test_utils.openControlBox();
  229. const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  230. // openControlBox was called earlier, so the controlbox is
  231. // visible, but no other chat boxes have been created.
  232. expect(_converse.chatboxes.length).toEqual(1);
  233. const view = await test_utils.openChatBoxFor(_converse, contact_jid);
  234. const el = sizzle('a.open-chat:contains("'+view.model.getDisplayName()+'")', _converse.rosterview.el).pop();
  235. const jid = el.textContent.replace(/ /g,'.').toLowerCase() + '@localhost';
  236. spyOn(_converse.api, "trigger");
  237. el.click();
  238. await test_utils.waitUntil(() => _converse.api.trigger.calls.count(), 500);
  239. expect(_converse.chatboxes.length).toEqual(2);
  240. expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object));
  241. done();
  242. }));
  243. it("can be saved to, and retrieved from, browserStorage",
  244. mock.initConverse(
  245. null, ['rosterGroupsFetched', 'chatBoxesFetched',], {},
  246. async function (done, _converse) {
  247. await test_utils.waitForRoster(_converse, 'current');
  248. test_utils.openControlBox();
  249. spyOn(_converse.api, "trigger");
  250. spyOn(_converse.chatboxviews, 'trimChats');
  251. test_utils.openControlBox();
  252. test_utils.openChatBoxes(_converse, 6);
  253. await test_utils.waitUntil(() => _converse.chatboxes.length == 7);
  254. expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
  255. // We instantiate a new ChatBoxes collection, which by default
  256. // will be empty.
  257. const newchatboxes = new _converse.ChatBoxes();
  258. expect(newchatboxes.length).toEqual(0);
  259. // The chatboxes will then be fetched from browserStorage inside the
  260. // onConnected method
  261. newchatboxes.onConnected();
  262. expect(newchatboxes.length).toEqual(7);
  263. // Check that the chatboxes items retrieved from browserStorage
  264. // have the same attributes values as the original ones.
  265. const attrs = ['id', 'box_id', 'visible'];
  266. let new_attrs, old_attrs;
  267. for (var i=0; i<attrs.length; i++) {
  268. new_attrs = _.map(_.map(newchatboxes.models, 'attributes'), attrs[i]);
  269. old_attrs = _.map(_.map(_converse.chatboxes.models, 'attributes'), attrs[i]);
  270. expect(_.isEqual(new_attrs, old_attrs)).toEqual(true);
  271. }
  272. _converse.rosterview.render();
  273. done();
  274. }));
  275. it("can be closed by clicking a DOM element with class 'close-chatbox-button'",
  276. mock.initConverse(
  277. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  278. async function (done, _converse) {
  279. await test_utils.waitForRoster(_converse, 'current');
  280. test_utils.openControlBox();
  281. const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@localhost';
  282. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
  283. await test_utils.openChatBoxFor(_converse, contact_jid);
  284. const controlview = _converse.chatboxviews.get('controlbox'), // The controlbox is currently open
  285. chatview = _converse.chatboxviews.get(contact_jid);
  286. spyOn(chatview, 'close').and.callThrough();
  287. spyOn(controlview, 'close').and.callThrough();
  288. spyOn(_converse.api, "trigger");
  289. // We need to rebind all events otherwise our spy won't be called
  290. controlview.delegateEvents();
  291. chatview.delegateEvents();
  292. controlview.el.querySelector('.close-chatbox-button').click();
  293. expect(controlview.close).toHaveBeenCalled();
  294. expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  295. expect(_converse.api.trigger.calls.count(), 1);
  296. chatview.el.querySelector('.close-chatbox-button').click();
  297. expect(chatview.close).toHaveBeenCalled();
  298. expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  299. expect(_converse.api.trigger.calls.count(), 2);
  300. done();
  301. }));
  302. it("can be minimized by clicking a DOM element with class 'toggle-chatbox-button'",
  303. mock.initConverse(
  304. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  305. async function (done, _converse) {
  306. await test_utils.waitForRoster(_converse, 'current');
  307. test_utils.openControlBox();
  308. const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@localhost';
  309. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
  310. await test_utils.openChatBoxFor(_converse, contact_jid);
  311. const trimmed_chatboxes = _converse.minimized_chats;
  312. const chatview = _converse.chatboxviews.get(contact_jid);
  313. spyOn(chatview, 'minimize').and.callThrough();
  314. spyOn(_converse.api, "trigger");
  315. // We need to rebind all events otherwise our spy won't be called
  316. chatview.delegateEvents();
  317. chatview.el.querySelector('.toggle-chatbox-button').click();
  318. expect(chatview.minimize).toHaveBeenCalled();
  319. expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object));
  320. expect(_converse.api.trigger.calls.count(), 2);
  321. expect(u.isVisible(chatview.el)).toBeFalsy();
  322. expect(chatview.model.get('minimized')).toBeTruthy();
  323. chatview.el.querySelector('.toggle-chatbox-button').click();
  324. const trimmedview = trimmed_chatboxes.get(chatview.model.get('id'));
  325. spyOn(trimmedview, 'restore').and.callThrough();
  326. trimmedview.delegateEvents();
  327. trimmedview.el.querySelector("a.restore-chat").click();
  328. expect(trimmedview.restore).toHaveBeenCalled();
  329. expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object));
  330. await test_utils.waitUntil(() => u.isVisible(chatview.el.querySelector('.chat-body')), 500);
  331. const toggle_el = sizzle('.toggle-chatbox-button', chatview.el).pop();
  332. expect(u.hasClass('fa-minus', toggle_el)).toBeTruthy();
  333. expect(u.hasClass('fa-plus', toggle_el)).toBeFalsy();
  334. expect(chatview.model.get('minimized')).toBeFalsy();
  335. done();
  336. }));
  337. it("will be removed from browserStorage when closed",
  338. mock.initConverse(
  339. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  340. async function (done, _converse) {
  341. await test_utils.waitForRoster(_converse, 'current');
  342. test_utils.openControlBox();
  343. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
  344. spyOn(_converse.api, "trigger");
  345. spyOn(_converse.chatboxviews, 'trimChats');
  346. _converse.chatboxes.browserStorage._clear();
  347. test_utils.closeControlBox();
  348. expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  349. expect(_converse.chatboxes.length).toEqual(1);
  350. expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
  351. test_utils.openChatBoxes(_converse, 6);
  352. await test_utils.waitUntil(() => _converse.chatboxes.length == 7)
  353. expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
  354. expect(_converse.chatboxes.length).toEqual(7);
  355. expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxOpened', jasmine.any(Object));
  356. test_utils.closeAllChatBoxes(_converse);
  357. expect(_converse.chatboxes.length).toEqual(1);
  358. expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
  359. expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  360. var newchatboxes = new _converse.ChatBoxes();
  361. expect(newchatboxes.length).toEqual(0);
  362. expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
  363. // onConnected will fetch chatboxes in browserStorage, but
  364. // because there aren't any open chatboxes, there won't be any
  365. // in browserStorage either. XXX except for the controlbox
  366. newchatboxes.onConnected();
  367. expect(newchatboxes.length).toEqual(1);
  368. expect(newchatboxes.models[0].id).toBe("controlbox");
  369. done();
  370. }));
  371. describe("A chat toolbar", function () {
  372. it("can be found on each chat box",
  373. mock.initConverse(
  374. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  375. async function (done, _converse) {
  376. await test_utils.waitForRoster(_converse, 'current', 3);
  377. test_utils.openControlBox();
  378. const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  379. await test_utils.openChatBoxFor(_converse, contact_jid);
  380. const chatbox = _converse.chatboxes.get(contact_jid);
  381. const view = _converse.chatboxviews.get(contact_jid);
  382. expect(chatbox).toBeDefined();
  383. expect(view).toBeDefined();
  384. const toolbar = view.el.querySelector('ul.chat-toolbar');
  385. expect(_.isElement(toolbar)).toBe(true);
  386. expect(toolbar.querySelectorAll(':scope > li').length).toBe(2);
  387. done();
  388. }));
  389. it("contains a button for inserting emojis",
  390. mock.initConverse(
  391. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  392. async function (done, _converse) {
  393. await test_utils.waitForRoster(_converse, 'current');
  394. test_utils.openControlBox();
  395. const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  396. await test_utils.openChatBoxFor(_converse, contact_jid);
  397. const view = _converse.chatboxviews.get(contact_jid);
  398. const toolbar = view.el.querySelector('ul.chat-toolbar');
  399. expect(toolbar.querySelectorAll('li.toggle-smiley').length).toBe(1);
  400. // Register spies
  401. spyOn(view, 'toggleEmojiMenu').and.callThrough();
  402. spyOn(view, 'insertEmoji').and.callThrough();
  403. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  404. toolbar.querySelector('li.toggle-smiley').click();
  405. await test_utils.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker-container')));
  406. var picker = view.el.querySelector('.toggle-smiley .emoji-picker-container');
  407. var items = picker.querySelectorAll('.emoji-picker li');
  408. items[0].click()
  409. expect(view.insertEmoji).toHaveBeenCalled();
  410. expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':grinning: ');
  411. toolbar.querySelector('li.toggle-smiley').click(); // Close the panel again
  412. done();
  413. }));
  414. it("can contain a button for starting a call",
  415. mock.initConverse(
  416. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  417. async function (done, _converse) {
  418. await test_utils.waitForRoster(_converse, 'current');
  419. test_utils.openControlBox();
  420. let toolbar, call_button;
  421. const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  422. spyOn(_converse.api, "trigger");
  423. // First check that the button doesn't show if it's not enabled
  424. // via "visible_toolbar_buttons"
  425. _converse.visible_toolbar_buttons.call = false;
  426. await test_utils.openChatBoxFor(_converse, contact_jid);
  427. let view = _converse.chatboxviews.get(contact_jid);
  428. toolbar = view.el.querySelector('ul.chat-toolbar');
  429. call_button = toolbar.querySelector('.toggle-call');
  430. expect(_.isNull(call_button)).toBeTruthy();
  431. view.close();
  432. // Now check that it's shown if enabled and that it emits
  433. // callButtonClicked
  434. _converse.visible_toolbar_buttons.call = true; // enable the button
  435. await test_utils.openChatBoxFor(_converse, contact_jid);
  436. view = _converse.chatboxviews.get(contact_jid);
  437. toolbar = view.el.querySelector('ul.chat-toolbar');
  438. call_button = toolbar.querySelector('.toggle-call');
  439. call_button.click();
  440. expect(_converse.api.trigger).toHaveBeenCalledWith('callButtonClicked', jasmine.any(Object));
  441. done();
  442. }));
  443. });
  444. describe("A Chat Status Notification", function () {
  445. it("is ignored when it's a carbon copy of one of my own",
  446. mock.initConverse(
  447. null, ['rosterGroupsFetched'], {},
  448. async function (done, _converse) {
  449. await test_utils.waitForRoster(_converse, 'current');
  450. test_utils.openControlBox();
  451. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  452. await test_utils.openChatBoxFor(_converse, sender_jid);
  453. let stanza = u.toStanza(
  454. `<message from="${sender_jid}"
  455. type="chat"
  456. to="dummy@localhost/resource">
  457. <composing xmlns="http://jabber.org/protocol/chatstates"/>
  458. <no-store xmlns="urn:xmpp:hints"/>
  459. <no-permanent-store xmlns="urn:xmpp:hints"/>
  460. </message>`);
  461. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  462. stanza = u.toStanza(
  463. `<message from="${sender_jid}"
  464. type="chat"
  465. to="dummy@localhost/resource">
  466. <paused xmlns="http://jabber.org/protocol/chatstates"/>
  467. <no-store xmlns="urn:xmpp:hints"/>
  468. <no-permanent-store xmlns="urn:xmpp:hints"/>
  469. </message>`);
  470. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  471. done();
  472. }));
  473. it("does not open a new chatbox",
  474. mock.initConverse(
  475. null, ['rosterGroupsFetched'], {},
  476. async function (done, _converse) {
  477. await test_utils.waitForRoster(_converse, 'current');
  478. test_utils.openControlBox();
  479. spyOn(_converse.api, "trigger");
  480. const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  481. // <composing> state
  482. const msg = $msg({
  483. 'from': sender_jid,
  484. 'to': _converse.connection.jid,
  485. 'type': 'chat',
  486. 'id': (new Date()).getTime()
  487. }).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  488. await _converse.chatboxes.onMessage(msg);
  489. expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
  490. expect(_converse.api.chats.get().length).toBe(1);
  491. done();
  492. }));
  493. describe("An active notification", function () {
  494. it("is sent when the user opens a chat box",
  495. mock.initConverse(
  496. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  497. async function (done, _converse) {
  498. await test_utils.waitForRoster(_converse, 'current');
  499. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  500. test_utils.openControlBox();
  501. test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
  502. spyOn(_converse.connection, 'send');
  503. await test_utils.openChatBoxFor(_converse, contact_jid);
  504. const view = _converse.chatboxviews.get(contact_jid);
  505. expect(view.model.get('chat_state')).toBe('active');
  506. expect(_converse.connection.send).toHaveBeenCalled();
  507. const stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
  508. expect(stanza.getAttribute('to')).toBe(contact_jid);
  509. expect(stanza.childNodes.length).toBe(3);
  510. expect(stanza.childNodes[0].tagName).toBe('active');
  511. expect(stanza.childNodes[1].tagName).toBe('no-store');
  512. expect(stanza.childNodes[2].tagName).toBe('no-permanent-store');
  513. done();
  514. }));
  515. it("is sent when the user maximizes a minimized a chat box", mock.initConverse(
  516. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  517. async function (done, _converse) {
  518. await test_utils.waitForRoster(_converse, 'current', 1);
  519. test_utils.openControlBox();
  520. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  521. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
  522. await test_utils.openChatBoxFor(_converse, contact_jid);
  523. const view = _converse.chatboxviews.get(contact_jid);
  524. view.model.minimize();
  525. expect(view.model.get('chat_state')).toBe('inactive');
  526. spyOn(_converse.connection, 'send');
  527. view.model.maximize();
  528. await test_utils.waitUntil(() => view.model.get('chat_state') === 'active', 1000);
  529. expect(_converse.connection.send).toHaveBeenCalled();
  530. const calls = _.filter(_converse.connection.send.calls.all(), function (call) {
  531. return call.args[0] instanceof Strophe.Builder;
  532. });
  533. expect(calls.length).toBe(1);
  534. const stanza = calls[0].args[0].tree();
  535. expect(stanza.getAttribute('to')).toBe(contact_jid);
  536. expect(stanza.childNodes.length).toBe(3);
  537. expect(stanza.childNodes[0].tagName).toBe('active');
  538. expect(stanza.childNodes[1].tagName).toBe('no-store');
  539. expect(stanza.childNodes[2].tagName).toBe('no-permanent-store');
  540. done();
  541. }));
  542. });
  543. describe("A composing notification", function () {
  544. it("is sent as soon as the user starts typing a message which is not a command",
  545. mock.initConverse(
  546. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  547. async function (done, _converse) {
  548. await test_utils.waitForRoster(_converse, 'current');
  549. test_utils.openControlBox();
  550. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  551. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
  552. await test_utils.openChatBoxFor(_converse, contact_jid);
  553. var view = _converse.chatboxviews.get(contact_jid);
  554. expect(view.model.get('chat_state')).toBe('active');
  555. spyOn(_converse.connection, 'send');
  556. spyOn(_converse.api, "trigger");
  557. view.keyPressed({
  558. target: view.el.querySelector('textarea.chat-textarea'),
  559. keyCode: 1
  560. });
  561. expect(view.model.get('chat_state')).toBe('composing');
  562. expect(_converse.connection.send).toHaveBeenCalled();
  563. const stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
  564. expect(stanza.getAttribute('to')).toBe(contact_jid);
  565. expect(stanza.childNodes.length).toBe(3);
  566. expect(stanza.childNodes[0].tagName).toBe('composing');
  567. expect(stanza.childNodes[1].tagName).toBe('no-store');
  568. expect(stanza.childNodes[2].tagName).toBe('no-permanent-store');
  569. // The notification is not sent again
  570. view.keyPressed({
  571. target: view.el.querySelector('textarea.chat-textarea'),
  572. keyCode: 1
  573. });
  574. expect(view.model.get('chat_state')).toBe('composing');
  575. expect(_converse.api.trigger.calls.count(), 1);
  576. done();
  577. }));
  578. it("will be shown if received",
  579. mock.initConverse(
  580. null, ['rosterGroupsFetched'], {},
  581. async function (done, _converse) {
  582. await test_utils.waitForRoster(_converse, 'current');
  583. test_utils.openControlBox();
  584. // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
  585. spyOn(_converse.api, "trigger");
  586. const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  587. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
  588. await test_utils.openChatBoxFor(_converse, sender_jid);
  589. // <composing> state
  590. let msg = $msg({
  591. from: sender_jid,
  592. to: _converse.connection.jid,
  593. type: 'chat',
  594. id: (new Date()).getTime()
  595. }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  596. await _converse.chatboxes.onMessage(msg);
  597. expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
  598. var view = _converse.chatboxviews.get(sender_jid);
  599. expect(view).toBeDefined();
  600. await test_utils.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1])
  601. // Check that the notification appears inside the chatbox in the DOM
  602. let events = view.el.querySelectorAll('.chat-state-notification');
  603. expect(events[0].textContent).toEqual(mock.cur_names[1] + ' is typing');
  604. // Check that it doesn't appear twice
  605. msg = $msg({
  606. from: sender_jid,
  607. to: _converse.connection.jid,
  608. type: 'chat',
  609. id: (new Date()).getTime()
  610. }).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  611. await _converse.chatboxes.onMessage(msg);
  612. events = view.el.querySelectorAll('.chat-state-notification');
  613. expect(events.length).toBe(1);
  614. expect(events[0].textContent).toEqual(mock.cur_names[1] + ' is typing');
  615. done();
  616. }));
  617. it("can be a composing carbon message that this user sent from a different client",
  618. mock.initConverse(
  619. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  620. async function (done, _converse) {
  621. let contact, sent_stanza, IQ_id, stanza;
  622. await test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']);
  623. await test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
  624. await test_utils.waitForRoster(_converse, 'current');
  625. // Send a message from a different resource
  626. spyOn(_converse, 'log');
  627. const recipient_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
  628. const view = await test_utils.openChatBoxFor(_converse, recipient_jid);
  629. const msg = $msg({
  630. 'from': _converse.bare_jid,
  631. 'id': (new Date()).getTime(),
  632. 'to': _converse.connection.jid,
  633. 'type': 'chat',
  634. 'xmlns': 'jabber:client'
  635. }).c('sent', {'xmlns': 'urn:xmpp:carbons:2'})
  636. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  637. .c('message', {
  638. 'xmlns': 'jabber:client',
  639. 'from': _converse.bare_jid+'/another-resource',
  640. 'to': recipient_jid,
  641. 'type': 'chat'
  642. }).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  643. await _converse.chatboxes.onMessage(msg);
  644. await test_utils.waitUntil(() => view.model.messages.length);
  645. // Check that the chatbox and its view now exist
  646. const chatbox = _converse.chatboxes.get(recipient_jid);
  647. const chatboxview = _converse.chatboxviews.get(recipient_jid);
  648. // Check that the message was received and check the message parameters
  649. expect(chatbox.messages.length).toEqual(1);
  650. const msg_obj = chatbox.messages.models[0];
  651. expect(msg_obj.get('sender')).toEqual('me');
  652. expect(msg_obj.get('is_delayed')).toEqual(false);
  653. const chat_content = chatboxview.el.querySelector('.chat-content');
  654. const status_text = chat_content.querySelector('.chat-info.chat-state-notification').textContent;
  655. expect(status_text).toBe('Typing from another device');
  656. done();
  657. }));
  658. });
  659. describe("A paused notification", function () {
  660. it("is sent if the user has stopped typing since 30 seconds",
  661. mock.initConverse(
  662. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  663. async function (done, _converse) {
  664. await test_utils.waitForRoster(_converse, 'current');
  665. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  666. test_utils.openControlBox();
  667. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
  668. _converse.TIMEOUTS.PAUSED = 200; // Make the timeout shorter so that we can test
  669. await test_utils.openChatBoxFor(_converse, contact_jid);
  670. const view = _converse.chatboxviews.get(contact_jid);
  671. spyOn(_converse.connection, 'send');
  672. spyOn(view, 'setChatState').and.callThrough();
  673. expect(view.model.get('chat_state')).toBe('active');
  674. view.keyPressed({
  675. target: view.el.querySelector('textarea.chat-textarea'),
  676. keyCode: 1
  677. });
  678. expect(view.model.get('chat_state')).toBe('composing');
  679. expect(_converse.connection.send).toHaveBeenCalled();
  680. let stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
  681. expect(stanza.childNodes[0].tagName).toBe('composing');
  682. await test_utils.waitUntil(() => view.model.get('chat_state') === 'paused', 500);
  683. expect(_converse.connection.send).toHaveBeenCalled();
  684. var calls = _.filter(_converse.connection.send.calls.all(), function (call) {
  685. return call.args[0] instanceof Strophe.Builder;
  686. });
  687. expect(calls.length).toBe(2);
  688. stanza = calls[1].args[0].tree();
  689. expect(stanza.getAttribute('to')).toBe(contact_jid);
  690. expect(stanza.childNodes.length).toBe(3);
  691. expect(stanza.childNodes[0].tagName).toBe('paused');
  692. expect(stanza.childNodes[1].tagName).toBe('no-store');
  693. expect(stanza.childNodes[2].tagName).toBe('no-permanent-store');
  694. // Test #359. A paused notification should not be sent
  695. // out if the user simply types longer than the
  696. // timeout.
  697. view.keyPressed({
  698. target: view.el.querySelector('textarea.chat-textarea'),
  699. keyCode: 1
  700. });
  701. expect(view.setChatState).toHaveBeenCalled();
  702. expect(view.model.get('chat_state')).toBe('composing');
  703. view.keyPressed({
  704. target: view.el.querySelector('textarea.chat-textarea'),
  705. keyCode: 1
  706. });
  707. expect(view.model.get('chat_state')).toBe('composing');
  708. done();
  709. }));
  710. it("will be shown if received",
  711. mock.initConverse(
  712. null, ['rosterGroupsFetched'], {},
  713. async function (done, _converse) {
  714. await test_utils.waitForRoster(_converse, 'current');
  715. test_utils.openControlBox();
  716. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
  717. // TODO: only show paused state if the previous state was composing
  718. // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
  719. spyOn(_converse.api, "trigger").and.callThrough();
  720. const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  721. const view = await test_utils.openChatBoxFor(_converse, sender_jid);
  722. // <paused> state
  723. const msg = $msg({
  724. from: sender_jid,
  725. to: _converse.connection.jid,
  726. type: 'chat',
  727. id: (new Date()).getTime()
  728. }).c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  729. await _converse.chatboxes.onMessage(msg);
  730. expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
  731. await test_utils.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1])
  732. var event = view.el.querySelector('.chat-info.chat-state-notification');
  733. expect(event.textContent).toEqual(mock.cur_names[1] + ' has stopped typing');
  734. done();
  735. }));
  736. it("can be a paused carbon message that this user sent from a different client",
  737. mock.initConverse(
  738. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  739. async function (done, _converse) {
  740. let contact, sent_stanza, IQ_id, stanza;
  741. await test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']);
  742. await test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
  743. await test_utils.waitForRoster(_converse, 'current');
  744. // Send a message from a different resource
  745. spyOn(_converse, 'log');
  746. const recipient_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
  747. const view = await test_utils.openChatBoxFor(_converse, recipient_jid);
  748. const msg = $msg({
  749. 'from': _converse.bare_jid,
  750. 'id': (new Date()).getTime(),
  751. 'to': _converse.connection.jid,
  752. 'type': 'chat',
  753. 'xmlns': 'jabber:client'
  754. }).c('sent', {'xmlns': 'urn:xmpp:carbons:2'})
  755. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  756. .c('message', {
  757. 'xmlns': 'jabber:client',
  758. 'from': _converse.bare_jid+'/another-resource',
  759. 'to': recipient_jid,
  760. 'type': 'chat'
  761. }).c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  762. await _converse.chatboxes.onMessage(msg);
  763. await test_utils.waitUntil(() => view.model.messages.length);
  764. // Check that the chatbox and its view now exist
  765. const chatbox = _converse.chatboxes.get(recipient_jid);
  766. const chatboxview = _converse.chatboxviews.get(recipient_jid);
  767. // Check that the message was received and check the message parameters
  768. expect(chatbox.messages.length).toEqual(1);
  769. const msg_obj = chatbox.messages.models[0];
  770. expect(msg_obj.get('sender')).toEqual('me');
  771. expect(msg_obj.get('is_delayed')).toEqual(false);
  772. const chat_content = chatboxview.el.querySelector('.chat-content');
  773. const status_text = chat_content.querySelector('.chat-info.chat-state-notification').textContent;
  774. expect(status_text).toBe('Stopped typing on the other device');
  775. done();
  776. }));
  777. });
  778. describe("An inactive notifciation", function () {
  779. it("is sent if the user has stopped typing since 2 minutes",
  780. mock.initConverse(
  781. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  782. async function (done, _converse) {
  783. // Make the timeouts shorter so that we can test
  784. _converse.TIMEOUTS.PAUSED = 200;
  785. _converse.TIMEOUTS.INACTIVE = 200;
  786. await test_utils.waitForRoster(_converse, 'current');
  787. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  788. test_utils.openControlBox();
  789. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 1000);
  790. await test_utils.openChatBoxFor(_converse, contact_jid);
  791. const view = _converse.chatboxviews.get(contact_jid);
  792. await test_utils.waitUntil(() => view.model.get('chat_state') === 'active', 1000);
  793. console.log('chat_state set to active');
  794. expect(view.model.get('chat_state')).toBe('active');
  795. view.keyPressed({
  796. target: view.el.querySelector('textarea.chat-textarea'),
  797. keyCode: 1
  798. });
  799. await test_utils.waitUntil(() => view.model.get('chat_state') === 'composing', 500);
  800. console.log('chat_state set to composing');
  801. expect(view.model.get('chat_state')).toBe('composing');
  802. spyOn(_converse.connection, 'send');
  803. await test_utils.waitUntil(() => view.model.get('chat_state') === 'paused', 1000);
  804. await test_utils.waitUntil(() => view.model.get('chat_state') === 'inactive', 1000);
  805. console.log('chat_state set to inactive');
  806. expect(_converse.connection.send).toHaveBeenCalled();
  807. var calls = _.filter(_converse.connection.send.calls.all(), function (call) {
  808. return call.args[0] instanceof Strophe.Builder;
  809. });
  810. expect(calls.length).toBe(2);
  811. var stanza = calls[0].args[0].tree();
  812. expect(stanza.getAttribute('to')).toBe(contact_jid);
  813. expect(stanza.children.length).toBe(3);
  814. expect(stanza.children[0].tagName).toBe('paused');
  815. expect(stanza.children[1].tagName).toBe('no-store');
  816. expect(stanza.children[2].tagName).toBe('no-permanent-store');
  817. stanza = _converse.connection.send.calls.mostRecent().args[0].tree();
  818. expect(stanza.getAttribute('to')).toBe(contact_jid);
  819. expect(stanza.children.length).toBe(3);
  820. expect(stanza.children[0].tagName).toBe('inactive');
  821. expect(stanza.children[1].tagName).toBe('no-store');
  822. expect(stanza.children[2].tagName).toBe('no-permanent-store');
  823. done();
  824. }));
  825. it("is sent when the user a minimizes a chat box",
  826. mock.initConverse(
  827. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  828. async function (done, _converse) {
  829. await test_utils.waitForRoster(_converse, 'current');
  830. test_utils.openControlBox();
  831. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  832. await test_utils.openChatBoxFor(_converse, contact_jid);
  833. const view = _converse.chatboxviews.get(contact_jid);
  834. spyOn(_converse.connection, 'send');
  835. view.minimize();
  836. expect(view.model.get('chat_state')).toBe('inactive');
  837. expect(_converse.connection.send).toHaveBeenCalled();
  838. var stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
  839. expect(stanza.getAttribute('to')).toBe(contact_jid);
  840. expect(stanza.childNodes[0].tagName).toBe('inactive');
  841. done();
  842. }));
  843. it("is sent if the user closes a chat box",
  844. mock.initConverse(
  845. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  846. async function (done, _converse) {
  847. await test_utils.waitForRoster(_converse, 'current');
  848. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  849. test_utils.openControlBox();
  850. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
  851. const view = await test_utils.openChatBoxFor(_converse, contact_jid);
  852. expect(view.model.get('chat_state')).toBe('active');
  853. spyOn(_converse.connection, 'send');
  854. view.close();
  855. expect(view.model.get('chat_state')).toBe('inactive');
  856. expect(_converse.connection.send).toHaveBeenCalled();
  857. var $stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
  858. const stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
  859. expect(stanza.getAttribute('to')).toBe(contact_jid);
  860. expect(stanza.childNodes.length).toBe(3);
  861. expect(stanza.childNodes[0].tagName).toBe('inactive');
  862. expect(stanza.childNodes[1].tagName).toBe('no-store');
  863. expect(stanza.childNodes[2].tagName).toBe('no-permanent-store');
  864. done();
  865. }));
  866. it("will clear any other chat status notifications",
  867. mock.initConverse(
  868. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  869. async function (done, _converse) {
  870. await test_utils.waitForRoster(_converse, 'current');
  871. test_utils.openControlBox();
  872. const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  873. // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
  874. spyOn(_converse.api, "trigger");
  875. await test_utils.openChatBoxFor(_converse, sender_jid);
  876. const view = _converse.chatboxviews.get(sender_jid);
  877. expect(view.el.querySelectorAll('.chat-event').length).toBe(0);
  878. // Insert <composing> message, to also check that
  879. // text messages are inserted correctly with
  880. // temporary chat events in the chat contents.
  881. let msg = $msg({
  882. 'to': _converse.bare_jid,
  883. 'xmlns': 'jabber:client',
  884. 'from': sender_jid,
  885. 'type': 'chat'})
  886. .c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
  887. .tree();
  888. await _converse.chatboxes.onMessage(msg);
  889. await test_utils.waitUntil(() => view.model.messages.length);
  890. expect(view.el.querySelectorAll('.chat-state-notification').length).toBe(1);
  891. msg = $msg({
  892. from: sender_jid,
  893. to: _converse.connection.jid,
  894. type: 'chat',
  895. id: (new Date()).getTime()
  896. }).c('body').c('inactive', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  897. await _converse.chatboxes.onMessage(msg);
  898. await test_utils.waitUntil(() => (view.model.messages.length > 1));
  899. expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
  900. expect(view.el.querySelectorAll('.chat-state-notification').length).toBe(0);
  901. done();
  902. }));
  903. });
  904. describe("A gone notifciation", function () {
  905. it("will be shown if received",
  906. mock.initConverse(
  907. null, ['rosterGroupsFetched'], {},
  908. async function (done, _converse) {
  909. await test_utils.waitForRoster(_converse, 'current', 3);
  910. test_utils.openControlBox();
  911. spyOn(_converse.api, "trigger");
  912. const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  913. // <paused> state
  914. const msg = $msg({
  915. from: sender_jid,
  916. to: _converse.connection.jid,
  917. type: 'chat',
  918. id: (new Date()).getTime()
  919. }).c('body').c('gone', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  920. await _converse.chatboxes.onMessage(msg);
  921. expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
  922. const view = _converse.chatboxviews.get(sender_jid);
  923. await test_utils.waitUntil(() => view.model.vcard.get('fullname') === mock.cur_names[1]);
  924. const event = view.el.querySelector('.chat-state-notification');
  925. expect(event.textContent).toEqual(mock.cur_names[1] + ' has gone away');
  926. done();
  927. }));
  928. });
  929. });
  930. });
  931. describe("Special Messages", function () {
  932. it("'/clear' can be used to clear messages in a conversation",
  933. mock.initConverse(
  934. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  935. async function (done, _converse) {
  936. await test_utils.waitForRoster(_converse, 'current');
  937. test_utils.openControlBox();
  938. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  939. spyOn(_converse.api, "trigger");
  940. await test_utils.openChatBoxFor(_converse, contact_jid);
  941. const view = _converse.chatboxviews.get(contact_jid);
  942. let message = 'This message is another sent from this chatbox';
  943. await test_utils.sendMessage(view, message);
  944. expect(view.model.messages.length > 0).toBeTruthy();
  945. expect(view.model.messages.browserStorage.records.length > 0).toBeTruthy();
  946. await test_utils.waitUntil(() => view.el.querySelector('.chat-msg'));
  947. message = '/clear';
  948. spyOn(view, 'clearMessages').and.callThrough();
  949. spyOn(window, 'confirm').and.callFake(function () {
  950. return true;
  951. });
  952. view.el.querySelector('.chat-textarea').value = message;
  953. view.keyPressed({
  954. target: view.el.querySelector('textarea.chat-textarea'),
  955. preventDefault: _.noop,
  956. keyCode: 13
  957. });
  958. expect(view.clearMessages).toHaveBeenCalled();
  959. expect(window.confirm).toHaveBeenCalled();
  960. expect(view.model.messages.length, 0); // The messages must be removed from the chatbox
  961. expect(view.model.messages.browserStorage.records.length, 0); // And also from browserStorage
  962. expect(_converse.api.trigger.calls.count(), 1);
  963. expect(_converse.api.trigger.calls.mostRecent().args, ['messageSend', message]);
  964. done();
  965. }));
  966. });
  967. describe("A Message Counter", function () {
  968. it("is incremented when the message is received and the window is not focused",
  969. mock.initConverse(
  970. null, ['rosterGroupsFetched'], {},
  971. async function (done, _converse) {
  972. await test_utils.waitForRoster(_converse, 'current');
  973. test_utils.openControlBox();
  974. expect(_converse.msg_counter).toBe(0);
  975. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  976. const view = await test_utils.openChatBoxFor(_converse, sender_jid)
  977. const previous_state = _converse.windowState;
  978. const message = 'This message will increment the message counter';
  979. const msg = $msg({
  980. from: sender_jid,
  981. to: _converse.connection.jid,
  982. type: 'chat',
  983. id: (new Date()).getTime()
  984. }).c('body').t(message).up()
  985. .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  986. _converse.windowState = 'hidden';
  987. spyOn(_converse.api, "trigger").and.callThrough();
  988. spyOn(_converse, 'incrementMsgCounter').and.callThrough();
  989. spyOn(_converse, 'clearMsgCounter').and.callThrough();
  990. await _converse.chatboxes.onMessage(msg);
  991. await new Promise((resolve, reject) => view.once('messageInserted', resolve));
  992. expect(_converse.incrementMsgCounter).toHaveBeenCalled();
  993. expect(_converse.clearMsgCounter).not.toHaveBeenCalled();
  994. expect(_converse.msg_counter).toBe(1);
  995. expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
  996. _converse.windowSate = previous_state;
  997. done();
  998. }));
  999. it("is cleared when the window is focused",
  1000. mock.initConverse(
  1001. null, ['rosterGroupsFetched'], {},
  1002. async function (done, _converse) {
  1003. await test_utils.waitForRoster(_converse, 'current');
  1004. test_utils.openControlBox();
  1005. _converse.windowState = 'hidden';
  1006. spyOn(_converse, 'clearMsgCounter').and.callThrough();
  1007. _converse.saveWindowState(null, 'focus');
  1008. _converse.saveWindowState(null, 'blur');
  1009. expect(_converse.clearMsgCounter).toHaveBeenCalled();
  1010. done();
  1011. }));
  1012. it("is not incremented when the message is received and the window is focused",
  1013. mock.initConverse(
  1014. null, ['rosterGroupsFetched'], {},
  1015. async function (done, _converse) {
  1016. await test_utils.waitForRoster(_converse, 'current');
  1017. test_utils.openControlBox();
  1018. expect(_converse.msg_counter).toBe(0);
  1019. spyOn(_converse, 'incrementMsgCounter').and.callThrough();
  1020. _converse.saveWindowState(null, 'focus');
  1021. const message = 'This message will not increment the message counter';
  1022. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
  1023. msg = $msg({
  1024. from: sender_jid,
  1025. to: _converse.connection.jid,
  1026. type: 'chat',
  1027. id: (new Date()).getTime()
  1028. }).c('body').t(message).up()
  1029. .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  1030. await _converse.chatboxes.onMessage(msg);
  1031. expect(_converse.incrementMsgCounter).not.toHaveBeenCalled();
  1032. expect(_converse.msg_counter).toBe(0);
  1033. done();
  1034. }));
  1035. it("is incremented from zero when chatbox was closed after viewing previously received messages and the window is not focused now",
  1036. mock.initConverse(
  1037. null, ['rosterGroupsFetched'], {},
  1038. async function (done, _converse) {
  1039. await test_utils.waitForRoster(_converse, 'current');
  1040. // initial state
  1041. expect(_converse.msg_counter).toBe(0);
  1042. const message = 'This message will always increment the message counter from zero',
  1043. sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
  1044. msgFactory = function () {
  1045. return $msg({
  1046. from: sender_jid,
  1047. to: _converse.connection.jid,
  1048. type: 'chat',
  1049. id: (new Date()).getTime()
  1050. })
  1051. .c('body').t(message).up()
  1052. .c('active', {'xmlns': Strophe.NS.CHATSTATES})
  1053. .tree();
  1054. };
  1055. // leave converse-chat page
  1056. _converse.windowState = 'hidden';
  1057. _converse.chatboxes.onMessage(msgFactory());
  1058. await test_utils.waitUntil(() => _converse.api.chats.get().length === 2)
  1059. let view = _converse.chatboxviews.get(sender_jid);
  1060. expect(_converse.msg_counter).toBe(1);
  1061. // come back to converse-chat page
  1062. _converse.saveWindowState(null, 'focus');
  1063. expect(u.isVisible(view.el)).toBeTruthy();
  1064. expect(_converse.msg_counter).toBe(0);
  1065. // close chatbox and leave converse-chat page again
  1066. view.close();
  1067. _converse.windowState = 'hidden';
  1068. // check that msg_counter is incremented from zero again
  1069. _converse.chatboxes.onMessage(msgFactory());
  1070. await test_utils.waitUntil(() => _converse.api.chats.get().length === 2)
  1071. view = _converse.chatboxviews.get(sender_jid);
  1072. expect(u.isVisible(view.el)).toBeTruthy();
  1073. expect(_converse.msg_counter).toBe(1);
  1074. done();
  1075. }));
  1076. });
  1077. describe("A ChatBox's Unread Message Count", function () {
  1078. it("is incremented when the message is received and ChatBoxView is scrolled up",
  1079. mock.initConverse(
  1080. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1081. async function (done, _converse) {
  1082. await test_utils.waitForRoster(_converse, 'current');
  1083. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
  1084. msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
  1085. const view = await test_utils.openChatBoxFor(_converse, sender_jid)
  1086. view.model.save('scrolled', true);
  1087. await _converse.chatboxes.onMessage(msg);
  1088. await test_utils.waitUntil(() => view.model.messages.length);
  1089. expect(view.model.get('num_unread')).toBe(1);
  1090. done();
  1091. }));
  1092. it("is not incremented when the message is received and ChatBoxView is scrolled down",
  1093. mock.initConverse(
  1094. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1095. async function (done, _converse) {
  1096. await test_utils.waitForRoster(_converse, 'current');
  1097. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
  1098. msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be read');
  1099. await test_utils.openChatBoxFor(_converse, sender_jid);
  1100. const chatbox = _converse.chatboxes.get(sender_jid);
  1101. await _converse.chatboxes.onMessage(msg);
  1102. expect(chatbox.get('num_unread')).toBe(0);
  1103. done();
  1104. }));
  1105. it("is incremeted when message is received, chatbox is scrolled down and the window is not focused",
  1106. mock.initConverse(null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1107. async function (done, _converse) {
  1108. await test_utils.waitForRoster(_converse, 'current');
  1109. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1110. const msgFactory = function () {
  1111. return test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
  1112. };
  1113. await test_utils.openChatBoxFor(_converse, sender_jid);
  1114. const chatbox = _converse.chatboxes.get(sender_jid);
  1115. _converse.windowState = 'hidden';
  1116. _converse.chatboxes.onMessage(msgFactory());
  1117. await test_utils.waitUntil(() => chatbox.messages.length);
  1118. expect(chatbox.get('num_unread')).toBe(1);
  1119. done();
  1120. }));
  1121. it("is incremeted when message is received, chatbox is scrolled up and the window is not focused",
  1122. mock.initConverse(
  1123. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1124. async function (done, _converse) {
  1125. await test_utils.waitForRoster(_converse, 'current', 1);
  1126. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1127. const msgFactory = () => test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
  1128. await test_utils.openChatBoxFor(_converse, sender_jid);
  1129. const chatbox = _converse.chatboxes.get(sender_jid);
  1130. chatbox.save('scrolled', true);
  1131. _converse.windowState = 'hidden';
  1132. _converse.chatboxes.onMessage(msgFactory());
  1133. await test_utils.waitUntil(() => chatbox.messages.length);
  1134. expect(chatbox.get('num_unread')).toBe(1);
  1135. done();
  1136. }));
  1137. it("is cleared when ChatBoxView was scrolled down and the window become focused",
  1138. mock.initConverse(
  1139. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1140. async function (done, _converse) {
  1141. await test_utils.waitForRoster(_converse, 'current', 1);
  1142. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1143. const msgFactory = () => test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
  1144. await test_utils.openChatBoxFor(_converse, sender_jid);
  1145. const chatbox = _converse.chatboxes.get(sender_jid);
  1146. _converse.windowState = 'hidden';
  1147. _converse.chatboxes.onMessage(msgFactory());
  1148. await test_utils.waitUntil(() => chatbox.messages.length);
  1149. expect(chatbox.get('num_unread')).toBe(1);
  1150. _converse.saveWindowState(null, 'focus');
  1151. expect(chatbox.get('num_unread')).toBe(0);
  1152. done();
  1153. }));
  1154. it("is not cleared when ChatBoxView was scrolled up and the windows become focused",
  1155. mock.initConverse(
  1156. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1157. async function (done, _converse) {
  1158. await test_utils.waitForRoster(_converse, 'current', 1);
  1159. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1160. const msgFactory = () => test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
  1161. await test_utils.openChatBoxFor(_converse, sender_jid);
  1162. const chatbox = _converse.chatboxes.get(sender_jid);
  1163. chatbox.save('scrolled', true);
  1164. _converse.windowState = 'hidden';
  1165. _converse.chatboxes.onMessage(msgFactory());
  1166. await test_utils.waitUntil(() => chatbox.messages.length);
  1167. expect(chatbox.get('num_unread')).toBe(1);
  1168. _converse.saveWindowState(null, 'focus');
  1169. expect(chatbox.get('num_unread')).toBe(1);
  1170. done();
  1171. }));
  1172. });
  1173. describe("A RosterView's Unread Message Count", function () {
  1174. it("is updated when message is received and chatbox is scrolled up",
  1175. mock.initConverse(
  1176. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1177. async function (done, _converse) {
  1178. await test_utils.waitForRoster(_converse, 'current', 1);
  1179. let msg, indicator_el;
  1180. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1181. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
  1182. await test_utils.openChatBoxFor(_converse, sender_jid);
  1183. const chatbox = _converse.chatboxes.get(sender_jid);
  1184. chatbox.save('scrolled', true);
  1185. msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
  1186. await _converse.chatboxes.onMessage(msg);
  1187. await test_utils.waitUntil(() => chatbox.messages.length);
  1188. const selector = 'a.open-chat:contains("' + chatbox.get('nickname') + '") .msgs-indicator';
  1189. indicator_el = sizzle(selector, _converse.rosterview.el).pop();
  1190. expect(indicator_el.textContent).toBe('1');
  1191. msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread too');
  1192. await _converse.chatboxes.onMessage(msg);
  1193. await test_utils.waitUntil(() => chatbox.messages.length > 1);
  1194. indicator_el = sizzle(selector, _converse.rosterview.el).pop();
  1195. expect(indicator_el.textContent).toBe('2');
  1196. done();
  1197. }));
  1198. it("is updated when message is received and chatbox is minimized",
  1199. mock.initConverse(
  1200. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1201. async function (done, _converse) {
  1202. await test_utils.waitForRoster(_converse, 'current', 1);
  1203. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1204. let indicator_el, msg;
  1205. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
  1206. await test_utils.openChatBoxFor(_converse, sender_jid);
  1207. const chatbox = _converse.chatboxes.get(sender_jid);
  1208. var chatboxview = _converse.chatboxviews.get(sender_jid);
  1209. chatboxview.minimize();
  1210. msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
  1211. await _converse.chatboxes.onMessage(msg);
  1212. await test_utils.waitUntil(() => chatbox.messages.length);
  1213. const selector = 'a.open-chat:contains("' + chatbox.get('nickname') + '") .msgs-indicator';
  1214. indicator_el = sizzle(selector, _converse.rosterview.el).pop();
  1215. expect(indicator_el.textContent).toBe('1');
  1216. msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread too');
  1217. await _converse.chatboxes.onMessage(msg);
  1218. await test_utils.waitUntil(() => chatbox.messages.length > 1);
  1219. indicator_el = sizzle(selector, _converse.rosterview.el).pop();
  1220. expect(indicator_el.textContent).toBe('2');
  1221. done();
  1222. }));
  1223. it("is cleared when chatbox is maximzied after receiving messages in minimized mode",
  1224. mock.initConverse(
  1225. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1226. async function (done, _converse) {
  1227. await test_utils.waitForRoster(_converse, 'current', 1);
  1228. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1229. const msgFactory = () => test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read');
  1230. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
  1231. await test_utils.openChatBoxFor(_converse, sender_jid);
  1232. const chatbox = _converse.chatboxes.get(sender_jid);
  1233. const view = _converse.chatboxviews.get(sender_jid);
  1234. const selector = 'a.open-chat:contains("' + chatbox.get('nickname') + '") .msgs-indicator';
  1235. const select_msgs_indicator = () => sizzle(selector, _converse.rosterview.el).pop();
  1236. view.minimize();
  1237. _converse.chatboxes.onMessage(msgFactory());
  1238. await test_utils.waitUntil(() => chatbox.messages.length);
  1239. expect(select_msgs_indicator().textContent).toBe('1');
  1240. _converse.chatboxes.onMessage(msgFactory());
  1241. await test_utils.waitUntil(() => chatbox.messages.length > 1);
  1242. expect(select_msgs_indicator().textContent).toBe('2');
  1243. view.maximize();
  1244. expect(select_msgs_indicator()).toBeUndefined();
  1245. done();
  1246. }));
  1247. it("is cleared when unread messages are viewed which were received in scrolled-up chatbox",
  1248. mock.initConverse(
  1249. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1250. async function (done, _converse) {
  1251. await test_utils.waitForRoster(_converse, 'current', 1);
  1252. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1253. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
  1254. await test_utils.openChatBoxFor(_converse, sender_jid);
  1255. const chatbox = _converse.chatboxes.get(sender_jid);
  1256. const view = _converse.chatboxviews.get(sender_jid);
  1257. const msgFactory = () => test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read');
  1258. const selector = 'a.open-chat:contains("' + chatbox.get('nickname') + '") .msgs-indicator';
  1259. const select_msgs_indicator = () => sizzle(selector, _converse.rosterview.el).pop();
  1260. chatbox.save('scrolled', true);
  1261. _converse.chatboxes.onMessage(msgFactory());
  1262. await test_utils.waitUntil(() => view.model.messages.length);
  1263. expect(select_msgs_indicator().textContent).toBe('1');
  1264. view.viewUnreadMessages();
  1265. _converse.rosterview.render();
  1266. expect(select_msgs_indicator()).toBeUndefined();
  1267. done();
  1268. }));
  1269. it("is not cleared after user clicks on roster view when chatbox is already opened and scrolled up",
  1270. mock.initConverse(
  1271. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1272. async function (done, _converse) {
  1273. await test_utils.waitForRoster(_converse, 'current', 1);
  1274. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1275. await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);
  1276. await test_utils.openChatBoxFor(_converse, sender_jid);
  1277. const chatbox = _converse.chatboxes.get(sender_jid);
  1278. const view = _converse.chatboxviews.get(sender_jid);
  1279. const msg = 'This message will be received as unread, but eventually will be read';
  1280. const msgFactory = () => test_utils.createChatMessage(_converse, sender_jid, msg);
  1281. const selector = 'a.open-chat:contains("' + chatbox.get('nickname') + '") .msgs-indicator';
  1282. const select_msgs_indicator = () => sizzle(selector, _converse.rosterview.el).pop();
  1283. chatbox.save('scrolled', true);
  1284. _converse.chatboxes.onMessage(msgFactory());
  1285. await test_utils.waitUntil(() => view.model.messages.length);
  1286. expect(select_msgs_indicator().textContent).toBe('1');
  1287. await test_utils.openChatBoxFor(_converse, sender_jid);
  1288. expect(select_msgs_indicator().textContent).toBe('1');
  1289. done();
  1290. }));
  1291. });
  1292. describe("A Minimized ChatBoxView's Unread Message Count", function () {
  1293. it("is displayed when scrolled up chatbox is minimized after receiving unread messages",
  1294. mock.initConverse(
  1295. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1296. async function (done, _converse) {
  1297. await test_utils.waitForRoster(_converse, 'current', 1);
  1298. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1299. await test_utils.openChatBoxFor(_converse, sender_jid);
  1300. const msgFactory = function () {
  1301. return test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read');
  1302. };
  1303. const selectUnreadMsgCount = function () {
  1304. const minimizedChatBoxView = _converse.minimized_chats.get(sender_jid);
  1305. return minimizedChatBoxView.el.querySelector('.message-count');
  1306. };
  1307. const chatbox = _converse.chatboxes.get(sender_jid);
  1308. chatbox.save('scrolled', true);
  1309. _converse.chatboxes.onMessage(msgFactory());
  1310. await test_utils.waitUntil(() => chatbox.messages.length);
  1311. const chatboxview = _converse.chatboxviews.get(sender_jid);
  1312. chatboxview.minimize();
  1313. const unread_count = selectUnreadMsgCount();
  1314. expect(u.isVisible(unread_count)).toBeTruthy();
  1315. expect(unread_count.innerHTML).toBe('1');
  1316. done();
  1317. }));
  1318. it("is incremented when message is received and windows is not focused",
  1319. mock.initConverse(
  1320. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1321. async function (done, _converse) {
  1322. await test_utils.waitForRoster(_converse, 'current', 1);
  1323. const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1324. const view = await test_utils.openChatBoxFor(_converse, sender_jid)
  1325. const msgFactory = function () {
  1326. return test_utils.createChatMessage(_converse, sender_jid,
  1327. 'This message will be received as unread, but eventually will be read');
  1328. };
  1329. const selectUnreadMsgCount = function () {
  1330. const minimizedChatBoxView = _converse.minimized_chats.get(sender_jid);
  1331. return minimizedChatBoxView.el.querySelector('.message-count');
  1332. };
  1333. view.minimize();
  1334. _converse.chatboxes.onMessage(msgFactory());
  1335. await test_utils.waitUntil(() => view.model.messages.length);
  1336. const unread_count = selectUnreadMsgCount();
  1337. expect(u.isVisible(unread_count)).toBeTruthy();
  1338. expect(unread_count.innerHTML).toBe('1');
  1339. done();
  1340. }));
  1341. it("will render Openstreetmap-URL from geo-URI",
  1342. mock.initConverse(
  1343. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1344. async function (done, _converse) {
  1345. await test_utils.waitForRoster(_converse, 'current', 1);
  1346. const base_url = document.URL.split(window.location.pathname)[0],
  1347. message = "geo:37.786971,-122.399677",
  1348. contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1349. await test_utils.openChatBoxFor(_converse, contact_jid);
  1350. const view = _converse.chatboxviews.get(contact_jid);
  1351. spyOn(view.model, 'sendMessage').and.callThrough();
  1352. test_utils.sendMessage(view, message);
  1353. await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-msg').length, 1000);
  1354. expect(view.model.sendMessage).toHaveBeenCalled();
  1355. const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop();
  1356. expect(msg.innerHTML).toEqual(
  1357. '<a target="_blank" rel="noopener" href="https://www.openstreetmap.org/?mlat=37.786971&amp;'+
  1358. 'mlon=-122.399677#map=18/37.786971/-122.399677">https://www.openstreetmap.org/?mlat=37.7869'+
  1359. '71&amp;mlon=-122.399677#map=18/37.786971/-122.399677</a>');
  1360. done();
  1361. }));
  1362. });
  1363. });
  1364. }));