chatbox.js 95 KB

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