2
0

chatbox.js 95 KB

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