chatbox.js 92 KB

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