chatbox.js 60 KB

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