chatbox.js 91 KB

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