chatbox.js 69 KB

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