chatbox.js 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783
  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. var online_contacts = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact').find('a.open-chat');
  57. for (i=0; i<online_contacts.length; i++) {
  58. $el = $(online_contacts[i]);
  59. jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
  60. $el.click();
  61. expect(this.chatboxviews.trimChats).toHaveBeenCalled();
  62. chatboxview = this.chatboxviews.get(jid);
  63. spyOn(chatboxview, 'hide').andCallThrough();
  64. chatboxview.model.set({'minimized': true});
  65. expect(trimmed_chatboxes.addChat).toHaveBeenCalled();
  66. expect(chatboxview.hide).toHaveBeenCalled();
  67. trimmedview = trimmed_chatboxes.get(jid);
  68. }
  69. // Test that they can be maximized again
  70. runs($.proxy(function () {
  71. var key = this.chatboxviews.keys()[1];
  72. trimmedview = trimmed_chatboxes.get(key);
  73. chatbox = trimmedview.model;
  74. spyOn(chatbox, 'maximize').andCallThrough();
  75. spyOn(trimmedview, 'restore').andCallThrough();
  76. trimmedview.delegateEvents();
  77. trimmedview.$("a.restore-chat").click();
  78. }, this));
  79. waits(250);
  80. runs($.proxy(function () {
  81. expect(trimmedview.restore).toHaveBeenCalled();
  82. expect(chatbox.maximize).toHaveBeenCalled();
  83. expect(this.chatboxviews.trimChats).toHaveBeenCalled();
  84. }, this));
  85. }, converse));
  86. it("is focused if its already open and you click on its corresponding roster item", $.proxy(function () {
  87. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  88. var i, $el, click, jid, chatboxview, chatbox;
  89. // openControlBox was called earlier, so the controlbox is
  90. // visible, but no other chat boxes have been created.
  91. expect(this.chatboxes.length).toEqual(1);
  92. chatbox = test_utils.openChatBoxFor(contact_jid);
  93. chatboxview = this.chatboxviews.get(contact_jid);
  94. spyOn(chatboxview, 'focus');
  95. $el = this.rosterview.$el.find('a.open-chat:contains("'+chatbox.get('fullname')+'")');
  96. jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
  97. $el.click();
  98. expect(this.chatboxes.length).toEqual(2);
  99. expect(chatboxview.focus).toHaveBeenCalled();
  100. }, converse));
  101. it("can be saved to, and retrieved from, browserStorage", $.proxy(function () {
  102. spyOn(converse, 'emit');
  103. spyOn(this.chatboxviews, 'trimChats');
  104. runs(function () {
  105. test_utils.openControlBox();
  106. });
  107. waits(250);
  108. runs(function () {
  109. test_utils.openChatBoxes(6);
  110. expect(this.chatboxviews.trimChats).toHaveBeenCalled();
  111. // We instantiate a new ChatBoxes collection, which by default
  112. // will be empty.
  113. var newchatboxes = new this.ChatBoxes();
  114. expect(newchatboxes.length).toEqual(0);
  115. // The chatboxes will then be fetched from browserStorage inside the
  116. // onConnected method
  117. newchatboxes.onConnected();
  118. expect(newchatboxes.length).toEqual(7);
  119. // Check that the chatboxes items retrieved from browserStorage
  120. // have the same attributes values as the original ones.
  121. attrs = ['id', 'box_id', 'visible'];
  122. for (i=0; i<attrs.length; i++) {
  123. new_attrs = _.pluck(_.pluck(newchatboxes.models, 'attributes'), attrs[i]);
  124. old_attrs = _.pluck(_.pluck(this.chatboxes.models, 'attributes'), attrs[i]);
  125. expect(_.isEqual(new_attrs, old_attrs)).toEqual(true);
  126. }
  127. this.rosterview.render();
  128. }.bind(converse));
  129. }, converse));
  130. it("can be closed by clicking a DOM element with class 'close-chatbox-button'", $.proxy(function () {
  131. var chatbox = test_utils.openChatBoxes(1)[0],
  132. controlview = this.chatboxviews.get('controlbox'), // The controlbox is currently open
  133. chatview = this.chatboxviews.get(chatbox.get('jid'));
  134. spyOn(chatview, 'close').andCallThrough();
  135. spyOn(controlview, 'close').andCallThrough();
  136. spyOn(converse, 'emit');
  137. // We need to rebind all events otherwise our spy won't be called
  138. controlview.delegateEvents();
  139. chatview.delegateEvents();
  140. runs(function () {
  141. controlview.$el.find('.close-chatbox-button').click();
  142. });
  143. waits(250);
  144. runs(function () {
  145. expect(controlview.close).toHaveBeenCalled();
  146. expect(converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  147. expect(converse.emit.callCount, 1);
  148. chatview.$el.find('.close-chatbox-button').click();
  149. });
  150. waits(250);
  151. runs(function () {
  152. expect(chatview.close).toHaveBeenCalled();
  153. expect(converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  154. expect(converse.emit.callCount, 2);
  155. });
  156. }, converse));
  157. it("can be minimized by clicking a DOM element with class 'toggle-chatbox-button'", function () {
  158. var chatbox = test_utils.openChatBoxes(1)[0],
  159. chatview = this.chatboxviews.get(chatbox.get('jid')),
  160. trimmed_chatboxes = this.minimized_chats,
  161. trimmedview;
  162. spyOn(chatview, 'minimize').andCallThrough();
  163. spyOn(converse, 'emit');
  164. // We need to rebind all events otherwise our spy won't be called
  165. chatview.delegateEvents();
  166. runs(function () {
  167. chatview.$el.find('.toggle-chatbox-button').click();
  168. });
  169. waits(250);
  170. runs(function () {
  171. expect(chatview.minimize).toHaveBeenCalled();
  172. expect(converse.emit).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object));
  173. expect(converse.emit.callCount, 2);
  174. expect(chatview.$el.is(':visible')).toBeFalsy();
  175. expect(chatview.model.get('minimized')).toBeTruthy();
  176. chatview.$el.find('.toggle-chatbox-button').click();
  177. trimmedview = trimmed_chatboxes.get(chatview.model.get('id'));
  178. spyOn(trimmedview, 'restore').andCallThrough();
  179. trimmedview.delegateEvents();
  180. trimmedview.$("a.restore-chat").click();
  181. });
  182. waits(250);
  183. runs(function () {
  184. expect(trimmedview.restore).toHaveBeenCalled();
  185. expect(converse.emit).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object));
  186. expect(chatview.$el.find('.chat-body').is(':visible')).toBeTruthy();
  187. expect(chatview.$el.find('.toggle-chatbox-button').hasClass('icon-minus')).toBeTruthy();
  188. expect(chatview.$el.find('.toggle-chatbox-button').hasClass('icon-plus')).toBeFalsy();
  189. expect(chatview.model.get('minimized')).toBeFalsy();
  190. });
  191. }.bind(converse));
  192. it("will be removed from browserStorage when closed", $.proxy(function () {
  193. spyOn(converse, 'emit');
  194. spyOn(converse.chatboxviews, 'trimChats');
  195. this.chatboxes.browserStorage._clear();
  196. runs(function () {
  197. test_utils.closeControlBox();
  198. });
  199. waits(50);
  200. runs(function () {
  201. expect(converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  202. expect(converse.chatboxes.length).toEqual(1);
  203. expect(converse.chatboxes.pluck('id')).toEqual(['controlbox']);
  204. test_utils.openChatBoxes(6);
  205. expect(converse.chatboxviews.trimChats).toHaveBeenCalled();
  206. expect(converse.chatboxes.length).toEqual(7);
  207. expect(converse.emit).toHaveBeenCalledWith('chatBoxOpened', jasmine.any(Object));
  208. test_utils.closeAllChatBoxes();
  209. });
  210. waits(50);
  211. runs(function () {
  212. expect(converse.chatboxes.length).toEqual(1);
  213. expect(converse.chatboxes.pluck('id')).toEqual(['controlbox']);
  214. expect(converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  215. var newchatboxes = new this.ChatBoxes();
  216. expect(newchatboxes.length).toEqual(0);
  217. expect(converse.chatboxes.pluck('id')).toEqual(['controlbox']);
  218. // onConnected will fetch chatboxes in browserStorage, but
  219. // because there aren't any open chatboxes, there won't be any
  220. // in browserStorage either. XXX except for the controlbox
  221. newchatboxes.onConnected();
  222. expect(newchatboxes.length).toEqual(1);
  223. expect(newchatboxes.models[0].id).toBe("controlbox");
  224. }.bind(converse));
  225. }, converse));
  226. describe("A chat toolbar", $.proxy(function () {
  227. it("can be found on each chat box", $.proxy(function () {
  228. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  229. test_utils.openChatBoxFor(contact_jid);
  230. var chatbox = this.chatboxes.get(contact_jid);
  231. var view = this.chatboxviews.get(contact_jid);
  232. expect(chatbox).toBeDefined();
  233. expect(view).toBeDefined();
  234. var $toolbar = view.$el.find('ul.chat-toolbar');
  235. expect($toolbar.length).toBe(1);
  236. expect($toolbar.children('li').length).toBe(3);
  237. }, converse));
  238. it("contains a button for inserting emoticons", $.proxy(function () {
  239. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  240. test_utils.openChatBoxFor(contact_jid);
  241. var view = this.chatboxviews.get(contact_jid);
  242. var $toolbar = view.$el.find('ul.chat-toolbar');
  243. var $textarea = view.$el.find('textarea.chat-textarea');
  244. expect($toolbar.children('li.toggle-smiley').length).toBe(1);
  245. // Register spies
  246. spyOn(view, 'toggleEmoticonMenu').andCallThrough();
  247. spyOn(view, 'insertEmoticon').andCallThrough();
  248. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  249. runs(function () {
  250. $toolbar.children('li.toggle-smiley').click();
  251. });
  252. waits(250);
  253. runs(function () {
  254. expect(view.toggleEmoticonMenu).toHaveBeenCalled();
  255. var $menu = view.$el.find('.toggle-smiley ul');
  256. var $items = $menu.children('li');
  257. expect($menu.is(':visible')).toBeTruthy();
  258. expect($items.length).toBe(13);
  259. expect($($items[0]).children('a').data('emoticon')).toBe(':)');
  260. expect($($items[1]).children('a').data('emoticon')).toBe(';)');
  261. expect($($items[2]).children('a').data('emoticon')).toBe(':D');
  262. expect($($items[3]).children('a').data('emoticon')).toBe(':P');
  263. expect($($items[4]).children('a').data('emoticon')).toBe('8)');
  264. expect($($items[5]).children('a').data('emoticon')).toBe('>:)');
  265. expect($($items[6]).children('a').data('emoticon')).toBe(':S');
  266. expect($($items[7]).children('a').data('emoticon')).toBe(':\\');
  267. expect($($items[8]).children('a').data('emoticon')).toBe('>:(');
  268. expect($($items[9]).children('a').data('emoticon')).toBe(':(');
  269. expect($($items[10]).children('a').data('emoticon')).toBe(':O');
  270. expect($($items[11]).children('a').data('emoticon')).toBe('(^.^)b');
  271. expect($($items[12]).children('a').data('emoticon')).toBe('<3');
  272. $items.first().click();
  273. });
  274. waits(250);
  275. runs(function () {
  276. expect(view.insertEmoticon).toHaveBeenCalled();
  277. expect($textarea.val()).toBe(':) ');
  278. expect(view.$el.find('.toggle-smiley ul').is(':visible')).toBeFalsy();
  279. $toolbar.children('li.toggle-smiley').click();
  280. });
  281. waits(250);
  282. runs(function () {
  283. expect(view.toggleEmoticonMenu).toHaveBeenCalled();
  284. expect(view.$el.find('.toggle-smiley ul').is(':visible')).toBeTruthy();
  285. view.$el.find('.toggle-smiley ul').children('li').last().click();
  286. });
  287. waits(250);
  288. runs(function () {
  289. expect(view.insertEmoticon).toHaveBeenCalled();
  290. expect(view.$el.find('.toggle-smiley ul').is(':visible')).toBeFalsy();
  291. expect($textarea.val()).toBe(':) <3 ');
  292. });
  293. }, converse));
  294. it("contains a button for starting an encrypted chat session", $.proxy(function () {
  295. // TODO: More tests can be added here...
  296. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  297. test_utils.openChatBoxFor(contact_jid);
  298. var view = this.chatboxviews.get(contact_jid);
  299. var $toolbar = view.$el.find('ul.chat-toolbar');
  300. expect($toolbar.children('li.toggle-otr').length).toBe(1);
  301. // Register spies
  302. spyOn(view, 'toggleOTRMenu').andCallThrough();
  303. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  304. runs(function () {
  305. $toolbar.children('li.toggle-otr').click();
  306. });
  307. waits(250);
  308. runs(function () {
  309. expect(view.toggleOTRMenu).toHaveBeenCalled();
  310. var $menu = view.$el.find('.toggle-otr ul');
  311. expect($menu.is(':visible')).toBeTruthy();
  312. expect($menu.children('li').length).toBe(2);
  313. });
  314. }, converse));
  315. it("can contain a button for starting a call", $.proxy(function () {
  316. var view, callButton, $toolbar;
  317. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  318. spyOn(converse, 'emit');
  319. // First check that the button doesn't show if it's not enabled
  320. // via "visible_toolbar_buttons"
  321. converse.visible_toolbar_buttons.call = false;
  322. test_utils.openChatBoxFor(contact_jid);
  323. view = this.chatboxviews.get(contact_jid);
  324. $toolbar = view.$el.find('ul.chat-toolbar');
  325. callButton = $toolbar.find('.toggle-call');
  326. expect(callButton.length).toBe(0);
  327. view.close();
  328. // Now check that it's shown if enabled and that it emits
  329. // callButtonClicked
  330. converse.visible_toolbar_buttons.call = true; // enable the button
  331. test_utils.openChatBoxFor(contact_jid);
  332. view = this.chatboxviews.get(contact_jid);
  333. $toolbar = view.$el.find('ul.chat-toolbar');
  334. callButton = $toolbar.find('.toggle-call');
  335. expect(callButton.length).toBe(1);
  336. callButton.click();
  337. expect(converse.emit).toHaveBeenCalledWith('callButtonClicked', jasmine.any(Object));
  338. }, converse));
  339. it("can contain a button for clearing messages", $.proxy(function () {
  340. var view, clearButton, $toolbar;
  341. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  342. // First check that the button doesn't show if it's not enabled
  343. // via "visible_toolbar_buttons"
  344. converse.visible_toolbar_buttons.clear = false;
  345. test_utils.openChatBoxFor(contact_jid);
  346. view = this.chatboxviews.get(contact_jid);
  347. view = this.chatboxviews.get(contact_jid);
  348. $toolbar = view.$el.find('ul.chat-toolbar');
  349. clearButton = $toolbar.find('.toggle-clear');
  350. expect(clearButton.length).toBe(0);
  351. view.close();
  352. // Now check that it's shown if enabled and that it calls
  353. // clearMessages
  354. converse.visible_toolbar_buttons.clear = true; // enable the button
  355. test_utils.openChatBoxFor(contact_jid);
  356. view = this.chatboxviews.get(contact_jid);
  357. $toolbar = view.$el.find('ul.chat-toolbar');
  358. clearButton = $toolbar.find('.toggle-clear');
  359. expect(clearButton.length).toBe(1);
  360. spyOn(view, 'clearMessages');
  361. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  362. clearButton.click();
  363. expect(view.clearMessages).toHaveBeenCalled();
  364. }, converse));
  365. }, converse));
  366. describe("A Chat Message", $.proxy(function () {
  367. beforeEach(function () {
  368. runs(function () {
  369. test_utils.closeAllChatBoxes();
  370. });
  371. waits(250);
  372. runs(function () {});
  373. });
  374. it("can be received which will open a chatbox and be displayed inside it", $.proxy(function () {
  375. spyOn(converse, 'emit');
  376. var message = 'This is a received message';
  377. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  378. msg = $msg({
  379. from: sender_jid,
  380. to: this.connection.jid,
  381. type: 'chat',
  382. id: (new Date()).getTime()
  383. }).c('body').t(message).up()
  384. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  385. // We don't already have an open chatbox for this user
  386. expect(this.chatboxes.get(sender_jid)).not.toBeDefined();
  387. runs($.proxy(function () {
  388. // onMessage is a handler for received XMPP messages
  389. this.chatboxes.onMessage(msg);
  390. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  391. }, converse));
  392. waits(250);
  393. runs($.proxy(function () {
  394. // Check that the chatbox and its view now exist
  395. var chatbox = this.chatboxes.get(sender_jid);
  396. var chatboxview = this.chatboxviews.get(sender_jid);
  397. expect(chatbox).toBeDefined();
  398. expect(chatboxview).toBeDefined();
  399. // Check that the message was received and check the
  400. // message parameters
  401. expect(chatbox.messages.length).toEqual(1);
  402. var msg_obj = chatbox.messages.models[0];
  403. expect(msg_obj.get('message')).toEqual(message);
  404. // XXX: This is stupid, fullname is actually only the
  405. // users first name
  406. expect(msg_obj.get('fullname')).toEqual(mock.cur_names[0].split(' ')[0]);
  407. expect(msg_obj.get('sender')).toEqual('them');
  408. expect(msg_obj.get('delayed')).toEqual(false);
  409. // Now check that the message appears inside the
  410. // chatbox in the DOM
  411. var $chat_content = chatboxview.$el.find('.chat-content');
  412. var msg_txt = $chat_content.find('.chat-message').find('.chat-message-content').text();
  413. expect(msg_txt).toEqual(message);
  414. var sender_txt = $chat_content.find('span.chat-message-them').text();
  415. expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
  416. }, converse));
  417. }, converse));
  418. it("received for a minimized chat box will increment a counter on its header", $.proxy(function () {
  419. var contact_name = mock.cur_names[0];
  420. var contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost';
  421. spyOn(this, 'emit');
  422. runs(function () {
  423. test_utils.openChatBoxFor(contact_jid);
  424. var chatview = converse.chatboxviews.get(contact_jid);
  425. expect(chatview.model.get('minimized')).toBeFalsy();
  426. chatview.$el.find('.toggle-chatbox-button').click();
  427. });
  428. waits(50);
  429. runs($.proxy(function () {
  430. var chatview = this.chatboxviews.get(contact_jid);
  431. expect(chatview.model.get('minimized')).toBeTruthy();
  432. var message = 'This message is sent to a minimized chatbox';
  433. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  434. msg = $msg({
  435. from: sender_jid,
  436. to: this.connection.jid,
  437. type: 'chat',
  438. id: (new Date()).getTime()
  439. }).c('body').t(message).up()
  440. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  441. this.chatboxes.onMessage(msg);
  442. expect(this.emit).toHaveBeenCalledWith('message', msg);
  443. }, converse));
  444. waits(50);
  445. runs($.proxy(function () {
  446. var trimmed_chatboxes = this.minimized_chats;
  447. var trimmedview = trimmed_chatboxes.get(contact_jid);
  448. var $count = trimmedview.$el.find('.chat-head-message-count');
  449. expect(trimmedview.model.get('minimized')).toBeTruthy();
  450. expect($count.is(':visible')).toBeTruthy();
  451. expect($count.html()).toBe('1');
  452. this.chatboxes.onMessage(
  453. $msg({
  454. from: mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
  455. to: this.connection.jid,
  456. type: 'chat',
  457. id: (new Date()).getTime()
  458. }).c('body').t('This message is also sent to a minimized chatbox').up()
  459. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
  460. );
  461. }, converse));
  462. waits(50);
  463. runs($.proxy(function () {
  464. var trimmed_chatboxes = this.minimized_chats;
  465. var trimmedview = trimmed_chatboxes.get(contact_jid);
  466. var $count = trimmedview.$el.find('.chat-head-message-count');
  467. expect(trimmedview.model.get('minimized')).toBeTruthy();
  468. expect($count.is(':visible')).toBeTruthy();
  469. expect($count.html()).toBe('2');
  470. trimmedview.$el.find('.restore-chat').click();
  471. }, converse));
  472. waits(250);
  473. runs($.proxy(function () {
  474. var trimmed_chatboxes = this.minimized_chats;
  475. expect(trimmed_chatboxes.keys().length).toBe(0);
  476. }, converse));
  477. }, converse));
  478. it("will indicate when it has a time difference of more than a day between it and its predecessor", $.proxy(function () {
  479. spyOn(converse, 'emit');
  480. var contact_name = mock.cur_names[1];
  481. var contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost';
  482. test_utils.openChatBoxFor(contact_jid);
  483. test_utils.clearChatBoxMessages(contact_jid);
  484. var one_day_ago = moment();
  485. one_day_ago.subtract('days', 1);
  486. var message = 'This is a day old message';
  487. var chatbox = this.chatboxes.get(contact_jid);
  488. var chatboxview = this.chatboxviews.get(contact_jid);
  489. var $chat_content = chatboxview.$el.find('.chat-content');
  490. var msg_obj;
  491. var msg_txt;
  492. var sender_txt;
  493. var msg = $msg({
  494. from: contact_jid,
  495. to: this.connection.jid,
  496. type: 'chat',
  497. id: one_day_ago.unix()
  498. }).c('body').t(message).up()
  499. .c('delay', { xmlns:'urn:xmpp:delay', from: 'localhost', stamp: one_day_ago.format() })
  500. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  501. this.chatboxes.onMessage(msg);
  502. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  503. expect(chatbox.messages.length).toEqual(1);
  504. msg_obj = chatbox.messages.models[0];
  505. expect(msg_obj.get('message')).toEqual(message);
  506. expect(msg_obj.get('fullname')).toEqual(contact_name.split(' ')[0]);
  507. expect(msg_obj.get('sender')).toEqual('them');
  508. expect(msg_obj.get('delayed')).toEqual(true);
  509. msg_txt = $chat_content.find('.chat-message').find('.chat-message-content').text();
  510. expect(msg_txt).toEqual(message);
  511. sender_txt = $chat_content.find('span.chat-message-them').text();
  512. expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
  513. message = 'This is a current message';
  514. msg = $msg({
  515. from: contact_jid,
  516. to: this.connection.jid,
  517. type: 'chat',
  518. id: new Date().getTime()
  519. }).c('body').t(message).up()
  520. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  521. this.chatboxes.onMessage(msg);
  522. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  523. // Check that there is a <time> element, with the required
  524. // props.
  525. var $time = $chat_content.find('time');
  526. var message_date = new Date();
  527. expect($time.length).toEqual(1);
  528. expect($time.attr('class')).toEqual('chat-date');
  529. expect($time.attr('datetime')).toEqual(moment(message_date).format("YYYY-MM-DD"));
  530. expect($time.text()).toEqual(moment(message_date).format("dddd MMM Do YYYY"));
  531. // Normal checks for the 2nd message
  532. expect(chatbox.messages.length).toEqual(2);
  533. msg_obj = chatbox.messages.models[1];
  534. expect(msg_obj.get('message')).toEqual(message);
  535. expect(msg_obj.get('fullname')).toEqual(contact_name.split(' ')[0]);
  536. expect(msg_obj.get('sender')).toEqual('them');
  537. expect(msg_obj.get('delayed')).toEqual(false);
  538. msg_txt = $chat_content.find('.chat-message').last().find('.chat-message-content').text();
  539. expect(msg_txt).toEqual(message);
  540. sender_txt = $chat_content.find('span.chat-message-them').last().text();
  541. expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
  542. }, converse));
  543. it("can be sent from a chatbox, and will appear inside it", $.proxy(function () {
  544. spyOn(converse, 'emit');
  545. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  546. runs(function () {
  547. test_utils.openChatBoxFor(contact_jid);
  548. });
  549. waits(250);
  550. runs(function () {
  551. expect(converse.emit).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object));
  552. var view = this.chatboxviews.get(contact_jid);
  553. var message = 'This message is sent from this chatbox';
  554. spyOn(view, 'sendMessage').andCallThrough();
  555. test_utils.sendMessage(view, message);
  556. expect(view.sendMessage).toHaveBeenCalled();
  557. expect(view.model.messages.length, 2);
  558. expect(converse.emit.mostRecentCall.args, ['messageSend', message]);
  559. expect(view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content').text()).toEqual(message);
  560. }.bind(converse));
  561. }, converse));
  562. it("is sanitized to prevent Javascript injection attacks", $.proxy(function () {
  563. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  564. test_utils.openChatBoxFor(contact_jid);
  565. var view = this.chatboxviews.get(contact_jid);
  566. var message = '<p>This message contains <em>some</em> <b>markup</b></p>';
  567. spyOn(view, 'sendMessage').andCallThrough();
  568. test_utils.sendMessage(view, message);
  569. expect(view.sendMessage).toHaveBeenCalled();
  570. var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
  571. expect(msg.text()).toEqual(message);
  572. 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;');
  573. }, converse));
  574. it("can contain hyperlinks, which will be clickable", $.proxy(function () {
  575. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  576. test_utils.openChatBoxFor(contact_jid);
  577. var view = this.chatboxviews.get(contact_jid);
  578. var message = 'This message contains a hyperlink: www.opkode.com';
  579. spyOn(view, 'sendMessage').andCallThrough();
  580. test_utils.sendMessage(view, message);
  581. expect(view.sendMessage).toHaveBeenCalled();
  582. var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
  583. expect(msg.text()).toEqual(message);
  584. expect(msg.html()).toEqual('This message contains a hyperlink: <a target="_blank" href="http://www.opkode.com">www.opkode.com</a>');
  585. }, converse));
  586. it("should display emoticons correctly", $.proxy(function () {
  587. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  588. test_utils.openChatBoxFor(contact_jid);
  589. var view = this.chatboxviews.get(contact_jid);
  590. var messages = [':)', ';)', ':D', ':P', '8)', '>:)', ':S', ':\\', '>:(', ':(', ':O', '(^.^)b', '<3'];
  591. var emoticons = [
  592. '<span class="emoticon icon-smiley"></span>', '<span class="emoticon icon-wink"></span>',
  593. '<span class="emoticon icon-grin"></span>', '<span class="emoticon icon-tongue"></span>',
  594. '<span class="emoticon icon-cool"></span>', '<span class="emoticon icon-evil"></span>',
  595. '<span class="emoticon icon-confused"></span>', '<span class="emoticon icon-wondering"></span>',
  596. '<span class="emoticon icon-angry"></span>', '<span class="emoticon icon-sad"></span>',
  597. '<span class="emoticon icon-shocked"></span>', '<span class="emoticon icon-thumbs-up"></span>',
  598. '<span class="emoticon icon-heart"></span>'
  599. ];
  600. spyOn(view, 'sendMessage').andCallThrough();
  601. for (var i = 0; i < messages.length; i++) {
  602. var message = messages[i];
  603. test_utils.sendMessage(view, message);
  604. expect(view.sendMessage).toHaveBeenCalled();
  605. var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
  606. expect(msg.html()).toEqual(emoticons[i]);
  607. }
  608. }, converse));
  609. it("will have properly escaped URLs", $.proxy(function () {
  610. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  611. test_utils.openChatBoxFor(contact_jid);
  612. var view = this.chatboxviews.get(contact_jid);
  613. spyOn(view, 'sendMessage').andCallThrough();
  614. var message = "http://www.opkode.com/'onmouseover='alert(1)'whatever";
  615. test_utils.sendMessage(view, message);
  616. expect(view.sendMessage).toHaveBeenCalled();
  617. var 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/%27onmouseover=%27alert%281%29%27whatever">http://www.opkode.com/\'onmouseover=\'alert(1)\'whatever</a>');
  620. message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever';
  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="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>');
  626. message = "https://en.wikipedia.org/wiki/Ender's_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\'s_Game</a>');
  632. message = "https://en.wikipedia.org/wiki/Ender%27s_Game";
  633. test_utils.sendMessage(view, message);
  634. expect(view.sendMessage).toHaveBeenCalled();
  635. msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content');
  636. expect(msg.text()).toEqual(message);
  637. 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>');
  638. }, converse));
  639. }, converse));
  640. }, converse));
  641. describe("Special Messages", $.proxy(function () {
  642. beforeEach(function () {
  643. test_utils.closeAllChatBoxes();
  644. test_utils.removeControlBox();
  645. converse.roster.browserStorage._clear();
  646. test_utils.initConverse();
  647. test_utils.createContacts('current');
  648. test_utils.openControlBox();
  649. test_utils.openContactsPanel();
  650. });
  651. it("'/clear' can be used to clear messages in a conversation", $.proxy(function () {
  652. spyOn(converse, 'emit');
  653. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  654. test_utils.openChatBoxFor(contact_jid);
  655. var view = this.chatboxviews.get(contact_jid);
  656. var message = 'This message is another sent from this chatbox';
  657. // Lets make sure there is at least one message already
  658. // (e.g for when this test is run on its own).
  659. test_utils.sendMessage(view, message);
  660. expect(view.model.messages.length > 0).toBeTruthy();
  661. expect(view.model.messages.browserStorage.records.length > 0).toBeTruthy();
  662. expect(converse.emit).toHaveBeenCalledWith('messageSend', message);
  663. message = '/clear';
  664. var old_length = view.model.messages.length;
  665. spyOn(view, 'sendMessage').andCallThrough();
  666. spyOn(view, 'clearMessages').andCallThrough();
  667. spyOn(window, 'confirm').andCallFake(function () {
  668. return true;
  669. });
  670. test_utils.sendMessage(view, message);
  671. expect(view.sendMessage).toHaveBeenCalled();
  672. expect(view.clearMessages).toHaveBeenCalled();
  673. expect(window.confirm).toHaveBeenCalled();
  674. expect(view.model.messages.length, 0); // The messages must be removed from the chatbox
  675. expect(view.model.messages.browserStorage.records.length, 0); // And also from browserStorage
  676. expect(converse.emit.callCount, 1);
  677. expect(converse.emit.mostRecentCall.args, ['messageSend', message]);
  678. }, converse));
  679. }, converse));
  680. describe("A Message Counter", $.proxy(function () {
  681. beforeEach($.proxy(function () {
  682. converse.clearMsgCounter();
  683. }, converse));
  684. it("is incremented when the message is received and the window is not focused", $.proxy(function () {
  685. spyOn(converse, 'emit');
  686. expect(this.msg_counter).toBe(0);
  687. spyOn(converse, 'incrementMsgCounter').andCallThrough();
  688. $(window).trigger('blur');
  689. var message = 'This message will increment the message counter';
  690. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  691. msg = $msg({
  692. from: sender_jid,
  693. to: this.connection.jid,
  694. type: 'chat',
  695. id: (new Date()).getTime()
  696. }).c('body').t(message).up()
  697. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  698. this.chatboxes.onMessage(msg);
  699. expect(converse.incrementMsgCounter).toHaveBeenCalled();
  700. expect(this.msg_counter).toBe(1);
  701. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  702. }, converse));
  703. it("is cleared when the window is focused", $.proxy(function () {
  704. spyOn(converse, 'clearMsgCounter').andCallThrough();
  705. runs(function () {
  706. $(window).triggerHandler('blur');
  707. $(window).triggerHandler('focus');
  708. });
  709. waits(50);
  710. runs(function () {
  711. expect(converse.clearMsgCounter).toHaveBeenCalled();
  712. });
  713. }, converse));
  714. it("is not incremented when the message is received and the window is focused", $.proxy(function () {
  715. expect(this.msg_counter).toBe(0);
  716. spyOn(converse, 'incrementMsgCounter').andCallThrough();
  717. $(window).trigger('focus');
  718. var message = 'This message will not increment the message counter';
  719. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  720. msg = $msg({
  721. from: sender_jid,
  722. to: this.connection.jid,
  723. type: 'chat',
  724. id: (new Date()).getTime()
  725. }).c('body').t(message).up()
  726. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  727. this.chatboxes.onMessage(msg);
  728. expect(converse.incrementMsgCounter).not.toHaveBeenCalled();
  729. expect(this.msg_counter).toBe(0);
  730. }, converse));
  731. }, converse));
  732. }, converse, mock, test_utils));
  733. }));