2
0

chatbox.js 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054
  1. (function (root, factory) {
  2. define([
  3. "jquery",
  4. "mock",
  5. "test_utils"
  6. ], function ($, mock, test_utils) {
  7. return factory($, mock, test_utils);
  8. }
  9. );
  10. } (this, function ($, mock, test_utils) {
  11. var $msg = converse_api.env.$msg;
  12. var Strophe = converse_api.env.Strophe;
  13. var moment = converse_api.env.moment;
  14. return describe("Chatboxes", $.proxy(function(mock, test_utils) {
  15. describe("A Chatbox", $.proxy(function () {
  16. beforeEach(function () {
  17. runs(function () {
  18. test_utils.closeAllChatBoxes();
  19. test_utils.removeControlBox();
  20. test_utils.clearBrowserStorage();
  21. test_utils.initConverse();
  22. test_utils.createContacts('current');
  23. test_utils.openControlBox();
  24. test_utils.openContactsPanel();
  25. });
  26. });
  27. it("is created when you click on a roster item", $.proxy(function () {
  28. var i, $el, click, jid, chatboxview;
  29. // openControlBox was called earlier, so the controlbox is
  30. // visible, but no other chat boxes have been created.
  31. expect(this.chatboxes.length).toEqual(1);
  32. spyOn(this.chatboxviews, 'trimChats');
  33. expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open
  34. var online_contacts = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact').find('a.open-chat');
  35. for (i=0; i<online_contacts.length; i++) {
  36. $el = $(online_contacts[i]);
  37. jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
  38. $el.click();
  39. chatboxview = this.chatboxviews.get(jid);
  40. expect(this.chatboxes.length).toEqual(i+2);
  41. expect(this.chatboxviews.trimChats).toHaveBeenCalled();
  42. // Check that new chat boxes are created to the left of the
  43. // controlbox (but to the right of all existing chat boxes)
  44. expect($("#conversejs .chatbox").length).toBe(i+2);
  45. expect($("#conversejs .chatbox")[1].id).toBe(chatboxview.model.get('box_id'));
  46. }
  47. }, converse));
  48. it("can be trimmed to conserve space", $.proxy(function () {
  49. var i, $el, click, jid, key, chatbox, chatboxview;
  50. // openControlBox was called earlier, so the controlbox is
  51. // visible, but no other chat boxes have been created.
  52. var trimmed_chatboxes = converse.minimized_chats;
  53. expect(this.chatboxes.length).toEqual(1);
  54. spyOn(this.chatboxviews, 'trimChats');
  55. spyOn(trimmed_chatboxes, 'addChat').andCallThrough();
  56. spyOn(trimmed_chatboxes, 'removeChat').andCallThrough();
  57. expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open
  58. // Test that they can be trimmed
  59. runs($.proxy(function () {
  60. converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced.
  61. }, this));
  62. waits(50);
  63. runs($.proxy(function () {
  64. // Test that they can be maximized again
  65. var online_contacts = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact').find('a.open-chat');
  66. for (i=0; i<online_contacts.length; i++) {
  67. $el = $(online_contacts[i]);
  68. jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
  69. $el.click();
  70. expect(this.chatboxviews.trimChats).toHaveBeenCalled();
  71. chatboxview = this.chatboxviews.get(jid);
  72. spyOn(chatboxview, 'hide').andCallThrough();
  73. chatboxview.model.set({'minimized': true});
  74. expect(trimmed_chatboxes.addChat).toHaveBeenCalled();
  75. expect(chatboxview.hide).toHaveBeenCalled();
  76. trimmedview = trimmed_chatboxes.get(jid);
  77. }
  78. var key = this.chatboxviews.keys()[1];
  79. trimmedview = trimmed_chatboxes.get(key);
  80. chatbox = trimmedview.model;
  81. spyOn(chatbox, 'maximize').andCallThrough();
  82. spyOn(trimmedview, 'restore').andCallThrough();
  83. trimmedview.delegateEvents();
  84. trimmedview.$("a.restore-chat").click();
  85. }, this));
  86. waits(250);
  87. runs($.proxy(function () {
  88. expect(trimmedview.restore).toHaveBeenCalled();
  89. expect(chatbox.maximize).toHaveBeenCalled();
  90. expect(this.chatboxviews.trimChats).toHaveBeenCalled();
  91. }, this));
  92. }, converse));
  93. it("is focused if its already open and you click on its corresponding roster item", $.proxy(function () {
  94. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  95. var i, $el, click, jid, chatboxview, chatbox;
  96. // openControlBox was called earlier, so the controlbox is
  97. // visible, but no other chat boxes have been created.
  98. expect(this.chatboxes.length).toEqual(1);
  99. chatbox = test_utils.openChatBoxFor(contact_jid);
  100. chatboxview = this.chatboxviews.get(contact_jid);
  101. spyOn(chatboxview, 'focus');
  102. // Test that they can be trimmed
  103. runs($.proxy(function () {
  104. converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced.
  105. }, this));
  106. waits(50);
  107. runs($.proxy(function () {
  108. $el = this.rosterview.$el.find('a.open-chat:contains("'+chatbox.get('fullname')+'")');
  109. jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
  110. $el.click();
  111. expect(this.chatboxes.length).toEqual(2);
  112. expect(chatboxview.focus).toHaveBeenCalled();
  113. }, this));
  114. }, converse));
  115. it("can be saved to, and retrieved from, browserStorage", $.proxy(function () {
  116. spyOn(converse, 'emit');
  117. spyOn(this.chatboxviews, 'trimChats');
  118. runs(function () {
  119. test_utils.openControlBox();
  120. });
  121. waits(250);
  122. runs(function () {
  123. test_utils.openChatBoxes(6);
  124. expect(this.chatboxviews.trimChats).toHaveBeenCalled();
  125. // We instantiate a new ChatBoxes collection, which by default
  126. // will be empty.
  127. var newchatboxes = new this.ChatBoxes();
  128. expect(newchatboxes.length).toEqual(0);
  129. // The chatboxes will then be fetched from browserStorage inside the
  130. // onConnected method
  131. newchatboxes.onConnected();
  132. expect(newchatboxes.length).toEqual(7);
  133. // Check that the chatboxes items retrieved from browserStorage
  134. // have the same attributes values as the original ones.
  135. attrs = ['id', 'box_id', 'visible'];
  136. for (i=0; i<attrs.length; i++) {
  137. new_attrs = _.pluck(_.pluck(newchatboxes.models, 'attributes'), attrs[i]);
  138. old_attrs = _.pluck(_.pluck(this.chatboxes.models, 'attributes'), attrs[i]);
  139. expect(_.isEqual(new_attrs, old_attrs)).toEqual(true);
  140. }
  141. this.rosterview.render();
  142. }.bind(converse));
  143. }, converse));
  144. it("can be closed by clicking a DOM element with class 'close-chatbox-button'", $.proxy(function () {
  145. var chatbox = test_utils.openChatBoxes(1)[0],
  146. controlview = this.chatboxviews.get('controlbox'), // The controlbox is currently open
  147. chatview = this.chatboxviews.get(chatbox.get('jid'));
  148. spyOn(chatview, 'close').andCallThrough();
  149. spyOn(controlview, 'close').andCallThrough();
  150. spyOn(converse, 'emit');
  151. // We need to rebind all events otherwise our spy won't be called
  152. controlview.delegateEvents();
  153. chatview.delegateEvents();
  154. runs(function () {
  155. controlview.$el.find('.close-chatbox-button').click();
  156. });
  157. waits(250);
  158. runs(function () {
  159. expect(controlview.close).toHaveBeenCalled();
  160. expect(converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  161. expect(converse.emit.callCount, 1);
  162. chatview.$el.find('.close-chatbox-button').click();
  163. });
  164. waits(250);
  165. runs(function () {
  166. expect(chatview.close).toHaveBeenCalled();
  167. expect(converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  168. expect(converse.emit.callCount, 2);
  169. });
  170. }, converse));
  171. it("can be minimized by clicking a DOM element with class 'toggle-chatbox-button'", function () {
  172. var chatbox = test_utils.openChatBoxes(1)[0],
  173. chatview = this.chatboxviews.get(chatbox.get('jid')),
  174. trimmed_chatboxes = this.minimized_chats,
  175. trimmedview;
  176. spyOn(chatview, 'minimize').andCallThrough();
  177. spyOn(converse, 'emit');
  178. // We need to rebind all events otherwise our spy won't be called
  179. chatview.delegateEvents();
  180. runs(function () {
  181. chatview.$el.find('.toggle-chatbox-button').click();
  182. });
  183. waits(250);
  184. runs(function () {
  185. expect(chatview.minimize).toHaveBeenCalled();
  186. expect(converse.emit).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object));
  187. expect(converse.emit.callCount, 2);
  188. expect(chatview.$el.is(':visible')).toBeFalsy();
  189. expect(chatview.model.get('minimized')).toBeTruthy();
  190. chatview.$el.find('.toggle-chatbox-button').click();
  191. trimmedview = trimmed_chatboxes.get(chatview.model.get('id'));
  192. spyOn(trimmedview, 'restore').andCallThrough();
  193. trimmedview.delegateEvents();
  194. trimmedview.$("a.restore-chat").click();
  195. });
  196. waits(250);
  197. runs(function () {
  198. expect(trimmedview.restore).toHaveBeenCalled();
  199. expect(converse.emit).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object));
  200. expect(chatview.$el.find('.chat-body').is(':visible')).toBeTruthy();
  201. expect(chatview.$el.find('.toggle-chatbox-button').hasClass('icon-minus')).toBeTruthy();
  202. expect(chatview.$el.find('.toggle-chatbox-button').hasClass('icon-plus')).toBeFalsy();
  203. expect(chatview.model.get('minimized')).toBeFalsy();
  204. });
  205. }.bind(converse));
  206. it("will be removed from browserStorage when closed", $.proxy(function () {
  207. spyOn(converse, 'emit');
  208. spyOn(converse.chatboxviews, 'trimChats');
  209. this.chatboxes.browserStorage._clear();
  210. runs(function () {
  211. test_utils.closeControlBox();
  212. });
  213. waits(50);
  214. runs(function () {
  215. expect(converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  216. expect(converse.chatboxes.length).toEqual(1);
  217. expect(converse.chatboxes.pluck('id')).toEqual(['controlbox']);
  218. test_utils.openChatBoxes(6);
  219. expect(converse.chatboxviews.trimChats).toHaveBeenCalled();
  220. expect(converse.chatboxes.length).toEqual(7);
  221. expect(converse.emit).toHaveBeenCalledWith('chatBoxOpened', jasmine.any(Object));
  222. test_utils.closeAllChatBoxes();
  223. });
  224. waits(50);
  225. runs(function () {
  226. expect(converse.chatboxes.length).toEqual(1);
  227. expect(converse.chatboxes.pluck('id')).toEqual(['controlbox']);
  228. expect(converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  229. var newchatboxes = new this.ChatBoxes();
  230. expect(newchatboxes.length).toEqual(0);
  231. expect(converse.chatboxes.pluck('id')).toEqual(['controlbox']);
  232. // onConnected will fetch chatboxes in browserStorage, but
  233. // because there aren't any open chatboxes, there won't be any
  234. // in browserStorage either. XXX except for the controlbox
  235. newchatboxes.onConnected();
  236. expect(newchatboxes.length).toEqual(1);
  237. expect(newchatboxes.models[0].id).toBe("controlbox");
  238. }.bind(converse));
  239. }, converse));
  240. describe("A chat toolbar", $.proxy(function () {
  241. it("can be found on each chat box", $.proxy(function () {
  242. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  243. test_utils.openChatBoxFor(contact_jid);
  244. var chatbox = this.chatboxes.get(contact_jid);
  245. var view = this.chatboxviews.get(contact_jid);
  246. expect(chatbox).toBeDefined();
  247. expect(view).toBeDefined();
  248. var $toolbar = view.$el.find('ul.chat-toolbar');
  249. expect($toolbar.length).toBe(1);
  250. expect($toolbar.children('li').length).toBe(3);
  251. }, converse));
  252. it("contains a button for inserting emoticons", $.proxy(function () {
  253. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  254. test_utils.openChatBoxFor(contact_jid);
  255. var view = this.chatboxviews.get(contact_jid);
  256. var $toolbar = view.$el.find('ul.chat-toolbar');
  257. var $textarea = view.$el.find('textarea.chat-textarea');
  258. expect($toolbar.children('li.toggle-smiley').length).toBe(1);
  259. // Register spies
  260. spyOn(view, 'toggleEmoticonMenu').andCallThrough();
  261. spyOn(view, 'insertEmoticon').andCallThrough();
  262. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  263. runs(function () {
  264. $toolbar.children('li.toggle-smiley').click();
  265. });
  266. waits(250);
  267. runs(function () {
  268. expect(view.toggleEmoticonMenu).toHaveBeenCalled();
  269. var $menu = view.$el.find('.toggle-smiley ul');
  270. var $items = $menu.children('li');
  271. expect($menu.is(':visible')).toBeTruthy();
  272. expect($items.length).toBe(13);
  273. expect($($items[0]).children('a').data('emoticon')).toBe(':)');
  274. expect($($items[1]).children('a').data('emoticon')).toBe(';)');
  275. expect($($items[2]).children('a').data('emoticon')).toBe(':D');
  276. expect($($items[3]).children('a').data('emoticon')).toBe(':P');
  277. expect($($items[4]).children('a').data('emoticon')).toBe('8)');
  278. expect($($items[5]).children('a').data('emoticon')).toBe('>:)');
  279. expect($($items[6]).children('a').data('emoticon')).toBe(':S');
  280. expect($($items[7]).children('a').data('emoticon')).toBe(':\\');
  281. expect($($items[8]).children('a').data('emoticon')).toBe('>:(');
  282. expect($($items[9]).children('a').data('emoticon')).toBe(':(');
  283. expect($($items[10]).children('a').data('emoticon')).toBe(':O');
  284. expect($($items[11]).children('a').data('emoticon')).toBe('(^.^)b');
  285. expect($($items[12]).children('a').data('emoticon')).toBe('<3');
  286. $items.first().click();
  287. });
  288. waits(250);
  289. runs(function () {
  290. expect(view.insertEmoticon).toHaveBeenCalled();
  291. expect($textarea.val()).toBe(':) ');
  292. expect(view.$el.find('.toggle-smiley ul').is(':visible')).toBeFalsy();
  293. $toolbar.children('li.toggle-smiley').click();
  294. });
  295. waits(250);
  296. runs(function () {
  297. expect(view.toggleEmoticonMenu).toHaveBeenCalled();
  298. expect(view.$el.find('.toggle-smiley ul').is(':visible')).toBeTruthy();
  299. view.$el.find('.toggle-smiley ul').children('li').last().click();
  300. });
  301. waits(250);
  302. runs(function () {
  303. expect(view.insertEmoticon).toHaveBeenCalled();
  304. expect(view.$el.find('.toggle-smiley ul').is(':visible')).toBeFalsy();
  305. expect($textarea.val()).toBe(':) <3 ');
  306. });
  307. }, converse));
  308. it("contains a button for starting an encrypted chat session", $.proxy(function () {
  309. // TODO: More tests can be added here...
  310. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  311. test_utils.openChatBoxFor(contact_jid);
  312. var view = this.chatboxviews.get(contact_jid);
  313. var $toolbar = view.$el.find('ul.chat-toolbar');
  314. expect($toolbar.children('li.toggle-otr').length).toBe(1);
  315. // Register spies
  316. spyOn(view, 'toggleOTRMenu').andCallThrough();
  317. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  318. runs(function () {
  319. $toolbar.children('li.toggle-otr').click();
  320. });
  321. waits(250);
  322. runs(function () {
  323. expect(view.toggleOTRMenu).toHaveBeenCalled();
  324. var $menu = view.$el.find('.toggle-otr ul');
  325. expect($menu.is(':visible')).toBeTruthy();
  326. expect($menu.children('li').length).toBe(2);
  327. });
  328. }, converse));
  329. it("can contain a button for starting a call", $.proxy(function () {
  330. var view, callButton, $toolbar;
  331. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  332. spyOn(converse, 'emit');
  333. // First check that the button doesn't show if it's not enabled
  334. // via "visible_toolbar_buttons"
  335. converse.visible_toolbar_buttons.call = false;
  336. test_utils.openChatBoxFor(contact_jid);
  337. view = this.chatboxviews.get(contact_jid);
  338. $toolbar = view.$el.find('ul.chat-toolbar');
  339. callButton = $toolbar.find('.toggle-call');
  340. expect(callButton.length).toBe(0);
  341. view.close();
  342. // Now check that it's shown if enabled and that it emits
  343. // callButtonClicked
  344. converse.visible_toolbar_buttons.call = true; // enable the button
  345. test_utils.openChatBoxFor(contact_jid);
  346. view = this.chatboxviews.get(contact_jid);
  347. $toolbar = view.$el.find('ul.chat-toolbar');
  348. callButton = $toolbar.find('.toggle-call');
  349. expect(callButton.length).toBe(1);
  350. callButton.click();
  351. expect(converse.emit).toHaveBeenCalledWith('callButtonClicked', jasmine.any(Object));
  352. }, converse));
  353. it("can contain a button for clearing messages", $.proxy(function () {
  354. var view, clearButton, $toolbar;
  355. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  356. // First check that the button doesn't show if it's not enabled
  357. // via "visible_toolbar_buttons"
  358. converse.visible_toolbar_buttons.clear = false;
  359. test_utils.openChatBoxFor(contact_jid);
  360. view = this.chatboxviews.get(contact_jid);
  361. view = this.chatboxviews.get(contact_jid);
  362. $toolbar = view.$el.find('ul.chat-toolbar');
  363. clearButton = $toolbar.find('.toggle-clear');
  364. expect(clearButton.length).toBe(0);
  365. view.close();
  366. // Now check that it's shown if enabled and that it calls
  367. // clearMessages
  368. converse.visible_toolbar_buttons.clear = true; // enable the button
  369. test_utils.openChatBoxFor(contact_jid);
  370. view = this.chatboxviews.get(contact_jid);
  371. $toolbar = view.$el.find('ul.chat-toolbar');
  372. clearButton = $toolbar.find('.toggle-clear');
  373. expect(clearButton.length).toBe(1);
  374. spyOn(view, 'clearMessages');
  375. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  376. clearButton.click();
  377. expect(view.clearMessages).toHaveBeenCalled();
  378. }, converse));
  379. }, converse));
  380. describe("A Chat Message", $.proxy(function () {
  381. beforeEach(function () {
  382. runs(function () {
  383. test_utils.closeAllChatBoxes();
  384. });
  385. waits(250);
  386. runs(function () {});
  387. });
  388. it("can be received which will open a chatbox and be displayed inside it", $.proxy(function () {
  389. spyOn(converse, 'emit');
  390. var message = 'This is a received message';
  391. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  392. var msg = $msg({
  393. from: sender_jid,
  394. to: this.connection.jid,
  395. type: 'chat',
  396. id: (new Date()).getTime()
  397. }).c('body').t(message).up()
  398. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  399. // We don't already have an open chatbox for this user
  400. expect(this.chatboxes.get(sender_jid)).not.toBeDefined();
  401. runs($.proxy(function () {
  402. // onMessage is a handler for received XMPP messages
  403. this.chatboxes.onMessage(msg);
  404. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  405. }, converse));
  406. waits(50);
  407. runs($.proxy(function () {
  408. // Check that the chatbox and its view now exist
  409. var chatbox = this.chatboxes.get(sender_jid);
  410. var chatboxview = this.chatboxviews.get(sender_jid);
  411. expect(chatbox).toBeDefined();
  412. expect(chatboxview).toBeDefined();
  413. // Check that the message was received and check the
  414. // message parameters
  415. expect(chatbox.messages.length).toEqual(1);
  416. var msg_obj = chatbox.messages.models[0];
  417. expect(msg_obj.get('message')).toEqual(message);
  418. // XXX: This is stupid, fullname is actually only the
  419. // users first name
  420. expect(msg_obj.get('fullname')).toEqual(mock.cur_names[0].split(' ')[0]);
  421. expect(msg_obj.get('sender')).toEqual('them');
  422. expect(msg_obj.get('delayed')).toEqual(false);
  423. // Now check that the message appears inside the
  424. // chatbox in the DOM
  425. var $chat_content = chatboxview.$el.find('.chat-content');
  426. var msg_txt = $chat_content.find('.chat-message').find('.chat-message-content').text();
  427. expect(msg_txt).toEqual(message);
  428. var sender_txt = $chat_content.find('span.chat-message-them').text();
  429. expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
  430. }, converse));
  431. }, converse));
  432. it("received for a minimized chat box will increment a counter on its header", $.proxy(function () {
  433. var contact_name = mock.cur_names[0];
  434. var contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost';
  435. spyOn(this, 'emit');
  436. test_utils.openChatBoxFor(contact_jid);
  437. var chatview = this.chatboxviews.get(contact_jid);
  438. expect(chatview.$el.is(':visible')).toBeTruthy();
  439. expect(chatview.model.get('minimized')).toBeFalsy();
  440. chatview.$el.find('.toggle-chatbox-button').click();
  441. expect(chatview.model.get('minimized')).toBeTruthy();
  442. var message = 'This message is sent to a minimized chatbox';
  443. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  444. msg = $msg({
  445. from: sender_jid,
  446. to: this.connection.jid,
  447. type: 'chat',
  448. id: (new Date()).getTime()
  449. }).c('body').t(message).up()
  450. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  451. this.chatboxes.onMessage(msg);
  452. expect(this.emit).toHaveBeenCalledWith('message', msg);
  453. var trimmed_chatboxes = this.minimized_chats;
  454. var trimmedview = trimmed_chatboxes.get(contact_jid);
  455. var $count = trimmedview.$el.find('.chat-head-message-count');
  456. expect(chatview.$el.is(':visible')).toBeFalsy();
  457. expect(trimmedview.model.get('minimized')).toBeTruthy();
  458. expect($count.is(':visible')).toBeTruthy();
  459. expect($count.html()).toBe('1');
  460. this.chatboxes.onMessage(
  461. $msg({
  462. from: mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
  463. to: this.connection.jid,
  464. type: 'chat',
  465. id: (new Date()).getTime()
  466. }).c('body').t('This message is also sent to a minimized chatbox').up()
  467. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
  468. );
  469. expect(chatview.$el.is(':visible')).toBeFalsy();
  470. expect(trimmedview.model.get('minimized')).toBeTruthy();
  471. $count = trimmedview.$el.find('.chat-head-message-count');
  472. expect($count.is(':visible')).toBeTruthy();
  473. expect($count.html()).toBe('2');
  474. trimmedview.$el.find('.restore-chat').click();
  475. expect(trimmed_chatboxes.keys().length).toBe(0);
  476. }, converse));
  477. it("will indicate when it has a time difference of more than a day between it and its predecessor", $.proxy(function () {
  478. spyOn(converse, 'emit');
  479. var contact_name = mock.cur_names[1];
  480. var contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost';
  481. test_utils.openChatBoxFor(contact_jid);
  482. test_utils.clearChatBoxMessages(contact_jid);
  483. var one_day_ago = moment();
  484. one_day_ago.subtract('days', 1);
  485. var message = 'This is a day old message';
  486. var chatbox = this.chatboxes.get(contact_jid);
  487. var chatboxview = this.chatboxviews.get(contact_jid);
  488. var $chat_content = chatboxview.$el.find('.chat-content');
  489. var msg_obj;
  490. var msg_txt;
  491. var sender_txt;
  492. var msg = $msg({
  493. from: contact_jid,
  494. to: this.connection.jid,
  495. type: 'chat',
  496. id: one_day_ago.unix()
  497. }).c('body').t(message).up()
  498. .c('delay', { xmlns:'urn:xmpp:delay', from: 'localhost', stamp: one_day_ago.format() })
  499. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  500. this.chatboxes.onMessage(msg);
  501. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  502. expect(chatbox.messages.length).toEqual(1);
  503. msg_obj = chatbox.messages.models[0];
  504. expect(msg_obj.get('message')).toEqual(message);
  505. expect(msg_obj.get('fullname')).toEqual(contact_name.split(' ')[0]);
  506. expect(msg_obj.get('sender')).toEqual('them');
  507. expect(msg_obj.get('delayed')).toEqual(true);
  508. msg_txt = $chat_content.find('.chat-message').find('.chat-message-content').text();
  509. expect(msg_txt).toEqual(message);
  510. sender_txt = $chat_content.find('span.chat-message-them').text();
  511. expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
  512. message = 'This is a current message';
  513. msg = $msg({
  514. from: contact_jid,
  515. to: this.connection.jid,
  516. type: 'chat',
  517. id: new Date().getTime()
  518. }).c('body').t(message).up()
  519. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  520. this.chatboxes.onMessage(msg);
  521. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  522. // Check that there is a <time> element, with the required
  523. // props.
  524. var $time = $chat_content.find('time');
  525. var message_date = new Date();
  526. expect($time.length).toEqual(1);
  527. expect($time.attr('class')).toEqual('chat-date');
  528. expect($time.attr('datetime')).toEqual(moment(message_date).format("YYYY-MM-DD"));
  529. expect($time.text()).toEqual(moment(message_date).format("dddd MMM Do YYYY"));
  530. // Normal checks for the 2nd message
  531. expect(chatbox.messages.length).toEqual(2);
  532. msg_obj = chatbox.messages.models[1];
  533. expect(msg_obj.get('message')).toEqual(message);
  534. expect(msg_obj.get('fullname')).toEqual(contact_name.split(' ')[0]);
  535. expect(msg_obj.get('sender')).toEqual('them');
  536. expect(msg_obj.get('delayed')).toEqual(false);
  537. msg_txt = $chat_content.find('.chat-message').last().find('.chat-message-content').text();
  538. expect(msg_txt).toEqual(message);
  539. sender_txt = $chat_content.find('span.chat-message-them').last().text();
  540. expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
  541. }, converse));
  542. it("can be sent from a chatbox, and will appear inside it", $.proxy(function () {
  543. spyOn(converse, 'emit');
  544. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  545. test_utils.openChatBoxFor(contact_jid);
  546. expect(converse.emit).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object));
  547. var view = this.chatboxviews.get(contact_jid);
  548. var message = 'This message is sent from this chatbox';
  549. spyOn(view, 'sendMessage').andCallThrough();
  550. test_utils.sendMessage(view, message);
  551. expect(view.sendMessage).toHaveBeenCalled();
  552. expect(view.model.messages.length, 2);
  553. expect(converse.emit.mostRecentCall.args, ['messageSend', message]);
  554. expect(view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content').text()).toEqual(message);
  555. }, converse));
  556. it("is sanitized to prevent Javascript injection attacks", $.proxy(function () {
  557. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  558. test_utils.openChatBoxFor(contact_jid);
  559. var view = this.chatboxviews.get(contact_jid);
  560. var message = '<p>This message contains <em>some</em> <b>markup</b></p>';
  561. spyOn(view, 'sendMessage').andCallThrough();
  562. test_utils.sendMessage(view, message);
  563. expect(view.sendMessage).toHaveBeenCalled();
  564. var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
  565. expect(msg.text()).toEqual(message);
  566. expect(msg.html()).toEqual('&lt;p&gt;This message contains &lt;em&gt;some&lt;/em&gt; &lt;b&gt;markup&lt;/b&gt;&lt;/p&gt;');
  567. }, converse));
  568. it("can contain hyperlinks, which will be clickable", $.proxy(function () {
  569. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  570. test_utils.openChatBoxFor(contact_jid);
  571. var view = this.chatboxviews.get(contact_jid);
  572. var message = 'This message contains a hyperlink: www.opkode.com';
  573. spyOn(view, 'sendMessage').andCallThrough();
  574. test_utils.sendMessage(view, message);
  575. expect(view.sendMessage).toHaveBeenCalled();
  576. var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
  577. expect(msg.text()).toEqual(message);
  578. expect(msg.html()).toEqual('This message contains a hyperlink: <a target="_blank" href="http://www.opkode.com">www.opkode.com</a>');
  579. }, converse));
  580. it("should display emoticons correctly", $.proxy(function () {
  581. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  582. test_utils.openChatBoxFor(contact_jid);
  583. var view = this.chatboxviews.get(contact_jid);
  584. var messages = [':)', ';)', ':D', ':P', '8)', '>:)', ':S', ':\\', '>:(', ':(', ':O', '(^.^)b', '<3'];
  585. var emoticons = [
  586. '<span class="emoticon icon-smiley"></span>', '<span class="emoticon icon-wink"></span>',
  587. '<span class="emoticon icon-grin"></span>', '<span class="emoticon icon-tongue"></span>',
  588. '<span class="emoticon icon-cool"></span>', '<span class="emoticon icon-evil"></span>',
  589. '<span class="emoticon icon-confused"></span>', '<span class="emoticon icon-wondering"></span>',
  590. '<span class="emoticon icon-angry"></span>', '<span class="emoticon icon-sad"></span>',
  591. '<span class="emoticon icon-shocked"></span>', '<span class="emoticon icon-thumbs-up"></span>',
  592. '<span class="emoticon icon-heart"></span>'
  593. ];
  594. spyOn(view, 'sendMessage').andCallThrough();
  595. for (var i = 0; i < messages.length; i++) {
  596. var message = messages[i];
  597. test_utils.sendMessage(view, message);
  598. expect(view.sendMessage).toHaveBeenCalled();
  599. var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
  600. expect(msg.html()).toEqual(emoticons[i]);
  601. }
  602. }, converse));
  603. it("will have properly escaped URLs", $.proxy(function () {
  604. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  605. test_utils.openChatBoxFor(contact_jid);
  606. var view = this.chatboxviews.get(contact_jid);
  607. spyOn(view, 'sendMessage').andCallThrough();
  608. var message = "http://www.opkode.com/'onmouseover='alert(1)'whatever";
  609. test_utils.sendMessage(view, message);
  610. expect(view.sendMessage).toHaveBeenCalled();
  611. var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
  612. expect(msg.text()).toEqual(message);
  613. expect(msg.html()).toEqual('<a target="_blank" href="http://www.opkode.com/%27onmouseover=%27alert%281%29%27whatever">http://www.opkode.com/\'onmouseover=\'alert(1)\'whatever</a>');
  614. message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever';
  615. test_utils.sendMessage(view, message);
  616. expect(view.sendMessage).toHaveBeenCalled();
  617. msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
  618. expect(msg.text()).toEqual(message);
  619. expect(msg.html()).toEqual('<a target="_blank" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>');
  620. message = "https://en.wikipedia.org/wiki/Ender's_Game";
  621. test_utils.sendMessage(view, message);
  622. expect(view.sendMessage).toHaveBeenCalled();
  623. msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
  624. expect(msg.text()).toEqual(message);
  625. expect(msg.html()).toEqual('<a target="_blank" href="https://en.wikipedia.org/wiki/Ender%27s_Game">https://en.wikipedia.org/wiki/Ender\'s_Game</a>');
  626. message = "https://en.wikipedia.org/wiki/Ender%27s_Game";
  627. test_utils.sendMessage(view, message);
  628. expect(view.sendMessage).toHaveBeenCalled();
  629. msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
  630. expect(msg.text()).toEqual(message);
  631. expect(msg.html()).toEqual('<a target="_blank" href="https://en.wikipedia.org/wiki/Ender%27s_Game">https://en.wikipedia.org/wiki/Ender%27s_Game</a>');
  632. }, converse));
  633. }, converse));
  634. describe("A Chat Status Notification", $.proxy(function () {
  635. it("does not open automatically if a chat state notification is received", $.proxy(function () {
  636. spyOn(converse, 'emit');
  637. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  638. // <composing> state
  639. var msg = $msg({
  640. from: sender_jid,
  641. to: this.connection.jid,
  642. type: 'chat',
  643. id: (new Date()).getTime()
  644. }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  645. this.chatboxes.onMessage(msg);
  646. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  647. var chatboxview = this.chatboxviews.get(sender_jid);
  648. expect(chatboxview).toBeDefined();
  649. expect(chatboxview.$el.is(':visible')).toBeFalsy(); // The chat box is not visible
  650. }, converse));
  651. describe("An active notification", $.proxy(function () {
  652. it("is sent when the user opens a chat box", $.proxy(function () {
  653. spyOn(converse.connection, 'send');
  654. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  655. test_utils.openChatBoxFor(contact_jid);
  656. var view = this.chatboxviews.get(contact_jid);
  657. expect(view.model.get('chat_state')).toBe('active');
  658. expect(converse.connection.send).toHaveBeenCalled();
  659. var $stanza = $(converse.connection.send.argsForCall[0][0].tree());
  660. expect($stanza.attr('to')).toBe(contact_jid);
  661. expect($stanza.children().length).toBe(1);
  662. expect($stanza.children().prop('tagName')).toBe('active');
  663. }, converse));
  664. it("is sent when the user maximizes a minimized a chat box", $.proxy(function () {
  665. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  666. test_utils.openChatBoxFor(contact_jid);
  667. var view = this.chatboxviews.get(contact_jid);
  668. view.minimize();
  669. expect(view.model.get('chat_state')).toBe('inactive');
  670. spyOn(converse.connection, 'send');
  671. view.maximize();
  672. expect(view.model.get('chat_state')).toBe('active');
  673. expect(converse.connection.send).toHaveBeenCalled();
  674. var $stanza = $(converse.connection.send.argsForCall[0][0].tree());
  675. expect($stanza.attr('to')).toBe(contact_jid);
  676. expect($stanza.children().length).toBe(1);
  677. expect($stanza.children().prop('tagName')).toBe('active');
  678. }, converse));
  679. }, converse));
  680. describe("A composing notification", $.proxy(function () {
  681. it("is sent as soon as the user starts typing a message which is not a command", $.proxy(function () {
  682. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  683. test_utils.openChatBoxFor(contact_jid);
  684. var view = this.chatboxviews.get(contact_jid);
  685. expect(view.model.get('chat_state')).toBe('active');
  686. spyOn(this.connection, 'send');
  687. view.keyPressed({
  688. target: view.$el.find('textarea.chat-textarea'),
  689. keyCode: 1
  690. });
  691. expect(view.model.get('chat_state')).toBe('composing');
  692. expect(this.connection.send).toHaveBeenCalled();
  693. var $stanza = $(this.connection.send.argsForCall[0][0].tree());
  694. expect($stanza.attr('to')).toBe(contact_jid);
  695. expect($stanza.children().length).toBe(1);
  696. expect($stanza.children().prop('tagName')).toBe('composing');
  697. // The notification is not sent again
  698. view.keyPressed({
  699. target: view.$el.find('textarea.chat-textarea'),
  700. keyCode: 1
  701. });
  702. expect(view.model.get('chat_state')).toBe('composing');
  703. expect(converse.emit.callCount, 1);
  704. }, converse));
  705. it("will be shown if received", $.proxy(function () {
  706. // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
  707. spyOn(converse, 'emit');
  708. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  709. // <composing> state
  710. var msg = $msg({
  711. from: sender_jid,
  712. to: this.connection.jid,
  713. type: 'chat',
  714. id: (new Date()).getTime()
  715. }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  716. this.chatboxes.onMessage(msg);
  717. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  718. var chatboxview = this.chatboxviews.get(sender_jid);
  719. expect(chatboxview).toBeDefined();
  720. // Check that the notification appears inside the chatbox in the DOM
  721. var $events = chatboxview.$el.find('.chat-event');
  722. expect($events.length).toBe(1);
  723. expect($events.text()).toEqual(mock.cur_names[1].split(' ')[0] + ' is typing');
  724. }, converse));
  725. }, converse));
  726. describe("A paused notification", $.proxy(function () {
  727. it("is sent if the user has stopped typing since 30 seconds", $.proxy(function () {
  728. this.TIMEOUTS.PAUSED = 200; // Make the timeout shorter so that we can test
  729. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  730. test_utils.openChatBoxFor(contact_jid);
  731. var view = this.chatboxviews.get(contact_jid);
  732. spyOn(converse.connection, 'send');
  733. spyOn(view, 'setChatState').andCallThrough();
  734. runs(function () {
  735. expect(view.model.get('chat_state')).toBe('active');
  736. view.keyPressed({
  737. target: view.$el.find('textarea.chat-textarea'),
  738. keyCode: 1
  739. });
  740. expect(view.model.get('chat_state')).toBe('composing');
  741. expect(converse.connection.send).toHaveBeenCalled();
  742. var $stanza = $(converse.connection.send.argsForCall[0][0].tree());
  743. expect($stanza.children().prop('tagName')).toBe('composing');
  744. });
  745. waits(250);
  746. runs(function () {
  747. expect(view.model.get('chat_state')).toBe('paused');
  748. expect(converse.connection.send).toHaveBeenCalled();
  749. var $stanza = $(converse.connection.send.argsForCall[1][0].tree());
  750. expect($stanza.attr('to')).toBe(contact_jid);
  751. expect($stanza.children().length).toBe(1);
  752. expect($stanza.children().prop('tagName')).toBe('paused');
  753. // Test #359. A paused notification should not be sent
  754. // out if the user simply types longer than the
  755. // timeout.
  756. view.keyPressed({
  757. target: view.$el.find('textarea.chat-textarea'),
  758. keyCode: 1
  759. });
  760. expect(view.setChatState).toHaveBeenCalled();
  761. expect(view.model.get('chat_state')).toBe('composing');
  762. });
  763. waits(100);
  764. runs(function () {
  765. view.keyPressed({
  766. target: view.$el.find('textarea.chat-textarea'),
  767. keyCode: 1
  768. });
  769. expect(view.model.get('chat_state')).toBe('composing');
  770. });
  771. waits(150);
  772. runs(function () {
  773. expect(view.model.get('chat_state')).toBe('composing');
  774. });
  775. }, converse));
  776. it("will be shown if received", $.proxy(function () {
  777. // TODO: only show paused state if the previous state was composing
  778. // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
  779. spyOn(converse, 'emit');
  780. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  781. // <paused> state
  782. msg = $msg({
  783. from: sender_jid,
  784. to: this.connection.jid,
  785. type: 'chat',
  786. id: (new Date()).getTime()
  787. }).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  788. this.chatboxes.onMessage(msg);
  789. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  790. var chatboxview = this.chatboxviews.get(sender_jid);
  791. $events = chatboxview.$el.find('.chat-event');
  792. expect($events.length).toBe(1);
  793. expect($events.text()).toEqual(mock.cur_names[1].split(' ')[0] + ' has stopped typing');
  794. }, converse));
  795. }, converse));
  796. describe("An inactive notifciation", $.proxy(function () {
  797. it("is sent if the user has stopped typing since 2 minutes", $.proxy(function () {
  798. // Make the timeouts shorter so that we can test
  799. this.TIMEOUTS.PAUSED = 200;
  800. this.TIMEOUTS.INACTIVE = 200;
  801. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  802. test_utils.openChatBoxFor(contact_jid);
  803. var view = this.chatboxviews.get(contact_jid);
  804. runs(function () {
  805. expect(view.model.get('chat_state')).toBe('active');
  806. view.keyPressed({
  807. target: view.$el.find('textarea.chat-textarea'),
  808. keyCode: 1
  809. });
  810. expect(view.model.get('chat_state')).toBe('composing');
  811. });
  812. waits(250);
  813. runs(function () {
  814. expect(view.model.get('chat_state')).toBe('paused');
  815. spyOn(converse.connection, 'send');
  816. });
  817. waits(250);
  818. runs(function () {
  819. expect(view.model.get('chat_state')).toBe('inactive');
  820. expect(converse.connection.send).toHaveBeenCalled();
  821. var $stanza = $(converse.connection.send.argsForCall[0][0].tree());
  822. expect($stanza.attr('to')).toBe(contact_jid);
  823. expect($stanza.children().length).toBe(1);
  824. expect($stanza.children().prop('tagName')).toBe('inactive');
  825. });
  826. }, converse));
  827. it("is sent when the user a minimizes a chat box", $.proxy(function () {
  828. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  829. test_utils.openChatBoxFor(contact_jid);
  830. var view = this.chatboxviews.get(contact_jid);
  831. spyOn(converse.connection, 'send');
  832. view.minimize();
  833. expect(view.model.get('chat_state')).toBe('inactive');
  834. expect(converse.connection.send).toHaveBeenCalled();
  835. var $stanza = $(converse.connection.send.argsForCall[0][0].tree());
  836. expect($stanza.attr('to')).toBe(contact_jid);
  837. expect($stanza.children().length).toBe(1);
  838. expect($stanza.children().prop('tagName')).toBe('inactive');
  839. }, converse));
  840. it("is sent if the user closes a chat box", $.proxy(function () {
  841. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  842. test_utils.openChatBoxFor(contact_jid);
  843. var view = this.chatboxviews.get(contact_jid);
  844. expect(view.model.get('chat_state')).toBe('active');
  845. spyOn(converse.connection, 'send');
  846. view.close();
  847. expect(view.model.get('chat_state')).toBe('inactive');
  848. expect(converse.connection.send).toHaveBeenCalled();
  849. var $stanza = $(converse.connection.send.argsForCall[0][0].tree());
  850. expect($stanza.attr('to')).toBe(contact_jid);
  851. expect($stanza.children().length).toBe(1);
  852. expect($stanza.children().prop('tagName')).toBe('inactive');
  853. }, converse));
  854. it("will clear any other chat status notifications if its received", $.proxy(function () {
  855. // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
  856. spyOn(converse, 'emit');
  857. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  858. test_utils.openChatBoxFor(sender_jid);
  859. var view = this.chatboxviews.get(sender_jid);
  860. expect(view.$el.find('.chat-event').length).toBe(0);
  861. view.showStatusNotification(sender_jid+' '+'is typing');
  862. expect(view.$el.find('.chat-event').length).toBe(1);
  863. msg = $msg({
  864. from: sender_jid,
  865. to: this.connection.jid,
  866. type: 'chat',
  867. id: (new Date()).getTime()
  868. }).c('body').c('inactive', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  869. this.chatboxes.onMessage(msg);
  870. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  871. expect(view.$el.find('.chat-event').length).toBe(0);
  872. }, converse));
  873. }, converse));
  874. describe("A gone notifciation", $.proxy(function () {
  875. it("will be shown if received", $.proxy(function () {
  876. spyOn(converse, 'emit');
  877. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  878. // <paused> state
  879. msg = $msg({
  880. from: sender_jid,
  881. to: this.connection.jid,
  882. type: 'chat',
  883. id: (new Date()).getTime()
  884. }).c('body').c('gone', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  885. this.chatboxes.onMessage(msg);
  886. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  887. var chatboxview = this.chatboxviews.get(sender_jid);
  888. $events = chatboxview.$el.find('.chat-event');
  889. expect($events.length).toBe(1);
  890. expect($events.text()).toEqual(mock.cur_names[1].split(' ')[0] + ' has gone away');
  891. }, converse));
  892. }, converse));
  893. }, converse));
  894. }, converse));
  895. describe("Special Messages", $.proxy(function () {
  896. beforeEach(function () {
  897. test_utils.closeAllChatBoxes();
  898. test_utils.removeControlBox();
  899. converse.roster.browserStorage._clear();
  900. test_utils.initConverse();
  901. test_utils.createContacts('current');
  902. test_utils.openControlBox();
  903. test_utils.openContactsPanel();
  904. });
  905. it("'/clear' can be used to clear messages in a conversation", $.proxy(function () {
  906. spyOn(converse, 'emit');
  907. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  908. test_utils.openChatBoxFor(contact_jid);
  909. var view = this.chatboxviews.get(contact_jid);
  910. var message = 'This message is another sent from this chatbox';
  911. // Lets make sure there is at least one message already
  912. // (e.g for when this test is run on its own).
  913. test_utils.sendMessage(view, message);
  914. expect(view.model.messages.length > 0).toBeTruthy();
  915. expect(view.model.messages.browserStorage.records.length > 0).toBeTruthy();
  916. expect(converse.emit).toHaveBeenCalledWith('messageSend', message);
  917. message = '/clear';
  918. var old_length = view.model.messages.length;
  919. spyOn(view, 'sendMessage').andCallThrough();
  920. spyOn(view, 'clearMessages').andCallThrough();
  921. spyOn(window, 'confirm').andCallFake(function () {
  922. return true;
  923. });
  924. test_utils.sendMessage(view, message);
  925. expect(view.sendMessage).toHaveBeenCalled();
  926. expect(view.clearMessages).toHaveBeenCalled();
  927. expect(window.confirm).toHaveBeenCalled();
  928. expect(view.model.messages.length, 0); // The messages must be removed from the chatbox
  929. expect(view.model.messages.browserStorage.records.length, 0); // And also from browserStorage
  930. expect(converse.emit.callCount, 1);
  931. expect(converse.emit.mostRecentCall.args, ['messageSend', message]);
  932. }, converse));
  933. }, converse));
  934. describe("A Message Counter", $.proxy(function () {
  935. beforeEach($.proxy(function () {
  936. converse.clearMsgCounter();
  937. }, converse));
  938. it("is incremented when the message is received and the window is not focused", $.proxy(function () {
  939. spyOn(converse, 'emit');
  940. expect(this.msg_counter).toBe(0);
  941. spyOn(converse, 'incrementMsgCounter').andCallThrough();
  942. $(window).trigger('blur');
  943. var message = 'This message will increment the message counter';
  944. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  945. msg = $msg({
  946. from: sender_jid,
  947. to: this.connection.jid,
  948. type: 'chat',
  949. id: (new Date()).getTime()
  950. }).c('body').t(message).up()
  951. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  952. this.chatboxes.onMessage(msg);
  953. expect(converse.incrementMsgCounter).toHaveBeenCalled();
  954. expect(this.msg_counter).toBe(1);
  955. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  956. }, converse));
  957. it("is cleared when the window is focused", $.proxy(function () {
  958. spyOn(converse, 'clearMsgCounter').andCallThrough();
  959. runs(function () {
  960. $(window).triggerHandler('blur');
  961. $(window).triggerHandler('focus');
  962. });
  963. waits(50);
  964. runs(function () {
  965. expect(converse.clearMsgCounter).toHaveBeenCalled();
  966. });
  967. }, converse));
  968. it("is not incremented when the message is received and the window is focused", $.proxy(function () {
  969. expect(this.msg_counter).toBe(0);
  970. spyOn(converse, 'incrementMsgCounter').andCallThrough();
  971. $(window).trigger('focus');
  972. var message = 'This message will not increment the message counter';
  973. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  974. msg = $msg({
  975. from: sender_jid,
  976. to: this.connection.jid,
  977. type: 'chat',
  978. id: (new Date()).getTime()
  979. }).c('body').t(message).up()
  980. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  981. this.chatboxes.onMessage(msg);
  982. expect(converse.incrementMsgCounter).not.toHaveBeenCalled();
  983. expect(this.msg_counter).toBe(0);
  984. }, converse));
  985. }, converse));
  986. }, converse, mock, test_utils));
  987. }));