chatbox.js 93 KB

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