chatbox.js 62 KB

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