chatbox.js 99 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680
  1. (function (root, factory) {
  2. define([
  3. "utils",
  4. "converse-api",
  5. "mock",
  6. "test_utils"
  7. ], factory);
  8. } (this, function (utils, converse, mock, test_utils) {
  9. "use strict";
  10. var _ = converse.env._;
  11. var $ = converse.env.jQuery;
  12. var $msg = converse.env.$msg;
  13. var Strophe = converse.env.Strophe;
  14. var moment = converse.env.moment;
  15. return describe("Chatboxes", function() {
  16. describe("A Chatbox", function () {
  17. it("is created when you click on a roster item", mock.initConverse(function (_converse) {
  18. test_utils.createContacts(_converse, 'current');
  19. test_utils.openControlBox();
  20. test_utils.openContactsPanel(_converse);
  21. var i, $el, jid, chatboxview;
  22. // openControlBox was called earlier, so the controlbox is
  23. // visible, but no other chat boxes have been created.
  24. expect(_converse.chatboxes.length).toEqual(1);
  25. spyOn(_converse.chatboxviews, 'trimChats');
  26. expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open
  27. var online_contacts = _converse.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact').find('a.open-chat');
  28. for (i=0; i<online_contacts.length; i++) {
  29. $el = $(online_contacts[i]);
  30. jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
  31. $el.click();
  32. chatboxview = _converse.chatboxviews.get(jid);
  33. expect(_converse.chatboxes.length).toEqual(i+2);
  34. expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
  35. // Check that new chat boxes are created to the left of the
  36. // controlbox (but to the right of all existing chat boxes)
  37. expect($("#conversejs .chatbox").length).toBe(i+2);
  38. expect($("#conversejs .chatbox")[1].id).toBe(chatboxview.model.get('box_id'));
  39. }
  40. }));
  41. it("can be trimmed to conserve space", mock.initConverse(function (_converse) {
  42. test_utils.createContacts(_converse, 'current');
  43. test_utils.openControlBox();
  44. test_utils.openContactsPanel(_converse);
  45. var i, $el, jid, chatbox, chatboxview, trimmedview;
  46. // openControlBox was called earlier, so the controlbox is
  47. // visible, but no other chat boxes have been created.
  48. var trimmed_chatboxes = _converse.minimized_chats;
  49. expect(_converse.chatboxes.length).toEqual(1);
  50. spyOn(_converse.chatboxviews, 'trimChats');
  51. spyOn(trimmed_chatboxes, 'addChat').andCallThrough();
  52. spyOn(trimmed_chatboxes, 'removeChat').andCallThrough();
  53. expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open
  54. // Test that they can be trimmed
  55. runs(function () {
  56. _converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced.
  57. });
  58. waits(50);
  59. runs(function () {
  60. // Test that they can be maximized again
  61. var online_contacts = _converse.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact').find('a.open-chat');
  62. for (i=0; i<online_contacts.length; i++) {
  63. $el = $(online_contacts[i]);
  64. jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
  65. $el.click();
  66. expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
  67. chatboxview = _converse.chatboxviews.get(jid);
  68. spyOn(chatboxview, 'minimize').andCallThrough();
  69. chatboxview.model.set({'minimized': true});
  70. expect(trimmed_chatboxes.addChat).toHaveBeenCalled();
  71. expect(chatboxview.minimize).toHaveBeenCalled();
  72. trimmedview = trimmed_chatboxes.get(jid);
  73. }
  74. var key = _converse.chatboxviews.keys()[1];
  75. trimmedview = trimmed_chatboxes.get(key);
  76. chatbox = trimmedview.model;
  77. spyOn(chatbox, 'maximize').andCallThrough();
  78. spyOn(trimmedview, 'restore').andCallThrough();
  79. trimmedview.delegateEvents();
  80. trimmedview.$("a.restore-chat").click();
  81. });
  82. waits(250);
  83. runs(function () {
  84. expect(trimmedview.restore).toHaveBeenCalled();
  85. expect(chatbox.maximize).toHaveBeenCalled();
  86. expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
  87. });
  88. }));
  89. it("is focused if its already open and you click on its corresponding roster item", mock.initConverse(function (_converse) {
  90. test_utils.createContacts(_converse, 'current');
  91. test_utils.openControlBox();
  92. test_utils.openContactsPanel(_converse);
  93. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  94. var $el, jid, chatboxview, chatbox;
  95. // openControlBox was called earlier, so the controlbox is
  96. // visible, but no other chat boxes have been created.
  97. expect(_converse.chatboxes.length).toEqual(1);
  98. chatbox = test_utils.openChatBoxFor(_converse, contact_jid);
  99. chatboxview = _converse.chatboxviews.get(contact_jid);
  100. spyOn(chatboxview, 'focus');
  101. // Test that they can be trimmed
  102. runs(function () {
  103. _converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced.
  104. });
  105. waits(300); // ChatBox.show() is debounced for 250ms
  106. runs(function () {
  107. $el = _converse.rosterview.$el.find('a.open-chat:contains("'+chatbox.get('fullname')+'")');
  108. jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
  109. $el.click();
  110. expect(_converse.chatboxes.length).toEqual(2);
  111. expect(chatboxview.focus).toHaveBeenCalled();
  112. });
  113. }));
  114. it("can be saved to, and retrieved from, browserStorage", mock.initConverse(function (_converse) {
  115. test_utils.createContacts(_converse, 'current');
  116. test_utils.openControlBox();
  117. test_utils.openContactsPanel(_converse);
  118. spyOn(_converse, 'emit');
  119. spyOn(_converse.chatboxviews, 'trimChats');
  120. runs(function () {
  121. test_utils.openControlBox();
  122. });
  123. waits(250);
  124. runs(function () {
  125. test_utils.openChatBoxes(_converse, 6);
  126. expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
  127. // We instantiate a new ChatBoxes collection, which by default
  128. // will be empty.
  129. var newchatboxes = new _converse.ChatBoxes();
  130. expect(newchatboxes.length).toEqual(0);
  131. // The chatboxes will then be fetched from browserStorage inside the
  132. // onConnected method
  133. newchatboxes.onConnected();
  134. expect(newchatboxes.length).toEqual(7);
  135. // Check that the chatboxes items retrieved from browserStorage
  136. // have the same attributes values as the original ones.
  137. var attrs = ['id', 'box_id', 'visible'];
  138. var new_attrs, old_attrs;
  139. for (var i=0; i<attrs.length; i++) {
  140. new_attrs = _.map(_.map(newchatboxes.models, 'attributes'), attrs[i]);
  141. old_attrs = _.map(_.map(_converse.chatboxes.models, 'attributes'), attrs[i]);
  142. expect(_.isEqual(new_attrs, old_attrs)).toEqual(true);
  143. }
  144. _converse.rosterview.render();
  145. });
  146. }));
  147. it("can be closed by clicking a DOM element with class 'close-chatbox-button'", mock.initConverse(function (_converse) {
  148. test_utils.createContacts(_converse, 'current');
  149. test_utils.openControlBox();
  150. test_utils.openContactsPanel(_converse);
  151. var chatbox = test_utils.openChatBoxes(_converse, 1)[0],
  152. controlview = _converse.chatboxviews.get('controlbox'), // The controlbox is currently open
  153. chatview = _converse.chatboxviews.get(chatbox.get('jid'));
  154. spyOn(chatview, 'close').andCallThrough();
  155. spyOn(controlview, 'close').andCallThrough();
  156. spyOn(_converse, 'emit');
  157. // We need to rebind all events otherwise our spy won't be called
  158. controlview.delegateEvents();
  159. chatview.delegateEvents();
  160. runs(function () {
  161. controlview.$el.find('.close-chatbox-button').click();
  162. });
  163. waits(250);
  164. runs(function () {
  165. expect(controlview.close).toHaveBeenCalled();
  166. expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  167. expect(_converse.emit.callCount, 1);
  168. chatview.$el.find('.close-chatbox-button').click();
  169. });
  170. waits(250);
  171. runs(function () {
  172. expect(chatview.close).toHaveBeenCalled();
  173. expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  174. expect(_converse.emit.callCount, 2);
  175. });
  176. }));
  177. it("can be minimized by clicking a DOM element with class 'toggle-chatbox-button'", mock.initConverse(function (_converse) {
  178. test_utils.createContacts(_converse, 'current');
  179. test_utils.openControlBox();
  180. test_utils.openContactsPanel(_converse);
  181. var chatbox = test_utils.openChatBoxes(_converse, 1)[0],
  182. chatview = _converse.chatboxviews.get(chatbox.get('jid')),
  183. trimmed_chatboxes = _converse.minimized_chats,
  184. trimmedview;
  185. spyOn(chatview, 'minimize').andCallThrough();
  186. spyOn(_converse, 'emit');
  187. // We need to rebind all events otherwise our spy won't be called
  188. chatview.delegateEvents();
  189. runs(function () {
  190. chatview.$el.find('.toggle-chatbox-button').click();
  191. });
  192. waits(250);
  193. runs(function () {
  194. expect(chatview.minimize).toHaveBeenCalled();
  195. expect(_converse.emit).toHaveBeenCalledWith('chatBoxMinimized', jasmine.any(Object));
  196. expect(_converse.emit.callCount, 2);
  197. expect(chatview.$el.is(':visible')).toBeFalsy();
  198. expect(chatview.model.get('minimized')).toBeTruthy();
  199. chatview.$el.find('.toggle-chatbox-button').click();
  200. trimmedview = trimmed_chatboxes.get(chatview.model.get('id'));
  201. spyOn(trimmedview, 'restore').andCallThrough();
  202. trimmedview.delegateEvents();
  203. trimmedview.$("a.restore-chat").click();
  204. });
  205. waits(250);
  206. runs(function () {
  207. expect(trimmedview.restore).toHaveBeenCalled();
  208. expect(_converse.emit).toHaveBeenCalledWith('chatBoxMaximized', jasmine.any(Object));
  209. expect(chatview.$el.find('.chat-body').is(':visible')).toBeTruthy();
  210. expect(chatview.$el.find('.toggle-chatbox-button').hasClass('icon-minus')).toBeTruthy();
  211. expect(chatview.$el.find('.toggle-chatbox-button').hasClass('icon-plus')).toBeFalsy();
  212. expect(chatview.model.get('minimized')).toBeFalsy();
  213. });
  214. }));
  215. it("will be removed from browserStorage when closed", mock.initConverse(function (_converse) {
  216. test_utils.createContacts(_converse, 'current');
  217. test_utils.openControlBox();
  218. test_utils.openContactsPanel(_converse);
  219. spyOn(_converse, 'emit');
  220. spyOn(_converse.chatboxviews, 'trimChats');
  221. _converse.chatboxes.browserStorage._clear();
  222. runs(function () {
  223. test_utils.closeControlBox();
  224. });
  225. waits(50);
  226. runs(function () {
  227. expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  228. expect(_converse.chatboxes.length).toEqual(1);
  229. expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
  230. test_utils.openChatBoxes(_converse, 6);
  231. expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
  232. expect(_converse.chatboxes.length).toEqual(7);
  233. expect(_converse.emit).toHaveBeenCalledWith('chatBoxOpened', jasmine.any(Object));
  234. test_utils.closeAllChatBoxes(_converse);
  235. });
  236. waits(50);
  237. runs(function () {
  238. expect(_converse.chatboxes.length).toEqual(1);
  239. expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
  240. expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
  241. var newchatboxes = new _converse.ChatBoxes();
  242. expect(newchatboxes.length).toEqual(0);
  243. expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
  244. // onConnected will fetch chatboxes in browserStorage, but
  245. // because there aren't any open chatboxes, there won't be any
  246. // in browserStorage either. XXX except for the controlbox
  247. newchatboxes.onConnected();
  248. expect(newchatboxes.length).toEqual(1);
  249. expect(newchatboxes.models[0].id).toBe("controlbox");
  250. });
  251. }));
  252. describe("A chat toolbar", function () {
  253. it("can be found on each chat box", mock.initConverse(function (_converse) {
  254. test_utils.createContacts(_converse, 'current');
  255. test_utils.openControlBox();
  256. test_utils.openContactsPanel(_converse);
  257. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  258. test_utils.openChatBoxFor(_converse, contact_jid);
  259. var chatbox = _converse.chatboxes.get(contact_jid);
  260. var view = _converse.chatboxviews.get(contact_jid);
  261. expect(chatbox).toBeDefined();
  262. expect(view).toBeDefined();
  263. var $toolbar = view.$el.find('ul.chat-toolbar');
  264. expect($toolbar.length).toBe(1);
  265. expect($toolbar.children('li').length).toBe(3);
  266. }));
  267. it("contains a button for inserting emoticons", mock.initConverse(function (_converse) {
  268. test_utils.createContacts(_converse, 'current');
  269. test_utils.openControlBox();
  270. test_utils.openContactsPanel(_converse);
  271. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost',
  272. view, $toolbar, $textarea;
  273. waits(300); // ChatBox.show() is debounced for 250ms
  274. runs(function () {
  275. test_utils.openChatBoxFor(_converse, contact_jid);
  276. view = _converse.chatboxviews.get(contact_jid);
  277. $toolbar = view.$el.find('ul.chat-toolbar');
  278. $textarea = view.$el.find('textarea.chat-textarea');
  279. expect($toolbar.children('li.toggle-smiley').length).toBe(1);
  280. // Register spies
  281. spyOn(view, 'toggleEmoticonMenu').andCallThrough();
  282. spyOn(view, 'insertEmoticon').andCallThrough();
  283. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  284. $toolbar.children('li.toggle-smiley').click();
  285. });
  286. waits(250);
  287. runs(function () {
  288. expect(view.toggleEmoticonMenu).toHaveBeenCalled();
  289. var $menu = view.$el.find('.toggle-smiley ul');
  290. var $items = $menu.children('li');
  291. expect($menu.is(':visible')).toBeTruthy();
  292. expect($items.length).toBe(13);
  293. expect($($items[0]).children('a').data('emoticon')).toBe(':)');
  294. expect($($items[1]).children('a').data('emoticon')).toBe(';)');
  295. expect($($items[2]).children('a').data('emoticon')).toBe(':D');
  296. expect($($items[3]).children('a').data('emoticon')).toBe(':P');
  297. expect($($items[4]).children('a').data('emoticon')).toBe('8)');
  298. expect($($items[5]).children('a').data('emoticon')).toBe('>:)');
  299. expect($($items[6]).children('a').data('emoticon')).toBe(':S');
  300. expect($($items[7]).children('a').data('emoticon')).toBe(':\\');
  301. expect($($items[8]).children('a').data('emoticon')).toBe('>:(');
  302. expect($($items[9]).children('a').data('emoticon')).toBe(':(');
  303. expect($($items[10]).children('a').data('emoticon')).toBe(':O');
  304. expect($($items[11]).children('a').data('emoticon')).toBe('(^.^)b');
  305. expect($($items[12]).children('a').data('emoticon')).toBe('<3');
  306. $items.first().click();
  307. });
  308. waits(250);
  309. runs(function () {
  310. expect(view.insertEmoticon).toHaveBeenCalled();
  311. expect($textarea.val()).toBe(':) ');
  312. expect(view.$el.find('.toggle-smiley ul').is(':visible')).toBeFalsy();
  313. $toolbar.children('li.toggle-smiley').click();
  314. });
  315. waits(250);
  316. runs(function () {
  317. expect(view.toggleEmoticonMenu).toHaveBeenCalled();
  318. expect(view.$el.find('.toggle-smiley ul').is(':visible')).toBeTruthy();
  319. view.$el.find('.toggle-smiley ul').children('li').last().click();
  320. });
  321. waits(250);
  322. runs(function () {
  323. expect(view.insertEmoticon).toHaveBeenCalled();
  324. expect(view.$el.find('.toggle-smiley ul').is(':visible')).toBeFalsy();
  325. expect($textarea.val()).toBe(':) <3 ');
  326. });
  327. }));
  328. it("contains a button for starting an encrypted chat session", mock.initConverse(function (_converse) {
  329. test_utils.createContacts(_converse, 'current');
  330. test_utils.openControlBox();
  331. test_utils.openContactsPanel(_converse);
  332. // TODO: More tests can be added here...
  333. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  334. test_utils.openChatBoxFor(_converse, contact_jid);
  335. var view = _converse.chatboxviews.get(contact_jid);
  336. var $toolbar = view.$el.find('ul.chat-toolbar');
  337. expect($toolbar.children('li.toggle-otr').length).toBe(1);
  338. // Register spies
  339. spyOn(view, 'toggleOTRMenu').andCallThrough();
  340. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  341. runs(function () {
  342. $toolbar.children('li.toggle-otr').click();
  343. });
  344. waits(250);
  345. runs(function () {
  346. expect(view.toggleOTRMenu).toHaveBeenCalled();
  347. var $menu = view.$el.find('.toggle-otr ul');
  348. expect($menu.is(':visible')).toBeTruthy();
  349. expect($menu.children('li').length).toBe(2);
  350. });
  351. }));
  352. it("can contain a button for starting a call", mock.initConverse(function (_converse) {
  353. test_utils.createContacts(_converse, 'current');
  354. test_utils.openControlBox();
  355. test_utils.openContactsPanel(_converse);
  356. var view, callButton, $toolbar;
  357. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  358. spyOn(_converse, 'emit');
  359. // First check that the button doesn't show if it's not enabled
  360. // via "visible_toolbar_buttons"
  361. _converse.visible_toolbar_buttons.call = false;
  362. test_utils.openChatBoxFor(_converse, contact_jid);
  363. view = _converse.chatboxviews.get(contact_jid);
  364. $toolbar = view.$el.find('ul.chat-toolbar');
  365. callButton = $toolbar.find('.toggle-call');
  366. expect(callButton.length).toBe(0);
  367. view.close();
  368. // Now check that it's shown if enabled and that it emits
  369. // callButtonClicked
  370. _converse.visible_toolbar_buttons.call = true; // enable the button
  371. test_utils.openChatBoxFor(_converse, contact_jid);
  372. view = _converse.chatboxviews.get(contact_jid);
  373. $toolbar = view.$el.find('ul.chat-toolbar');
  374. callButton = $toolbar.find('.toggle-call');
  375. expect(callButton.length).toBe(1);
  376. callButton.click();
  377. expect(_converse.emit).toHaveBeenCalledWith('callButtonClicked', jasmine.any(Object));
  378. }));
  379. it("can contain a button for clearing messages", mock.initConverse(function (_converse) {
  380. test_utils.createContacts(_converse, 'current');
  381. test_utils.openControlBox();
  382. test_utils.openContactsPanel(_converse);
  383. var view, clearButton, $toolbar;
  384. var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  385. // First check that the button doesn't show if it's not enabled
  386. // via "visible_toolbar_buttons"
  387. _converse.visible_toolbar_buttons.clear = false;
  388. test_utils.openChatBoxFor(_converse, contact_jid);
  389. view = _converse.chatboxviews.get(contact_jid);
  390. view = _converse.chatboxviews.get(contact_jid);
  391. $toolbar = view.$el.find('ul.chat-toolbar');
  392. clearButton = $toolbar.find('.toggle-clear');
  393. expect(clearButton.length).toBe(0);
  394. view.close();
  395. // Now check that it's shown if enabled and that it calls
  396. // clearMessages
  397. _converse.visible_toolbar_buttons.clear = true; // enable the button
  398. test_utils.openChatBoxFor(_converse, contact_jid);
  399. view = _converse.chatboxviews.get(contact_jid);
  400. $toolbar = view.$el.find('ul.chat-toolbar');
  401. clearButton = $toolbar.find('.toggle-clear');
  402. expect(clearButton.length).toBe(1);
  403. spyOn(view, 'clearMessages');
  404. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  405. clearButton.click();
  406. expect(view.clearMessages).toHaveBeenCalled();
  407. }));
  408. });
  409. describe("A Chat Message", function () {
  410. describe("when received from someone else", function () {
  411. it("can be received which will open a chatbox and be displayed inside it", mock.initConverse(function (_converse) {
  412. test_utils.createContacts(_converse, 'current');
  413. test_utils.openControlBox();
  414. test_utils.openContactsPanel(_converse);
  415. spyOn(_converse, 'emit');
  416. var message = 'This is a received message';
  417. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  418. var msg = $msg({
  419. from: sender_jid,
  420. to: _converse.connection.jid,
  421. type: 'chat',
  422. id: (new Date()).getTime()
  423. }).c('body').t(message).up()
  424. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  425. // We don't already have an open chatbox for this user
  426. expect(_converse.chatboxes.get(sender_jid)).not.toBeDefined();
  427. runs(function () {
  428. // onMessage is a handler for received XMPP messages
  429. _converse.chatboxes.onMessage(msg);
  430. expect(_converse.emit).toHaveBeenCalledWith('message', msg);
  431. });
  432. waits(50);
  433. runs(function () {
  434. // Check that the chatbox and its view now exist
  435. var chatbox = _converse.chatboxes.get(sender_jid);
  436. var chatboxview = _converse.chatboxviews.get(sender_jid);
  437. expect(chatbox).toBeDefined();
  438. expect(chatboxview).toBeDefined();
  439. // Check that the message was received and check the message parameters
  440. expect(chatbox.messages.length).toEqual(1);
  441. var msg_obj = chatbox.messages.models[0];
  442. expect(msg_obj.get('message')).toEqual(message);
  443. expect(msg_obj.get('fullname')).toEqual(mock.cur_names[0]);
  444. expect(msg_obj.get('sender')).toEqual('them');
  445. expect(msg_obj.get('delayed')).toEqual(false);
  446. // Now check that the message appears inside the chatbox in the DOM
  447. var $chat_content = chatboxview.$el.find('.chat-content');
  448. var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
  449. expect(msg_txt).toEqual(message);
  450. var sender_txt = $chat_content.find('span.chat-msg-them').text();
  451. expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
  452. });
  453. }));
  454. describe("who is not on the roster", function () {
  455. it("will open a chatbox and be displayed inside it if allow_non_roster_messaging is true", mock.initConverse(function (converse) {
  456. converse.allow_non_roster_messaging = false;
  457. spyOn(converse, 'emit');
  458. var message = 'This is a received message from someone not on the roster';
  459. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  460. var msg = $msg({
  461. from: sender_jid,
  462. to: converse.connection.jid,
  463. type: 'chat',
  464. id: (new Date()).getTime()
  465. }).c('body').t(message).up()
  466. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  467. // We don't already have an open chatbox for this user
  468. expect(converse.chatboxes.get(sender_jid)).not.toBeDefined();
  469. runs(function () {
  470. // onMessage is a handler for received XMPP messages
  471. converse.chatboxes.onMessage(msg);
  472. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  473. });
  474. waits(50);
  475. runs(function () {
  476. var chatbox = converse.chatboxes.get(sender_jid);
  477. expect(chatbox).not.toBeDefined();
  478. // onMessage is a handler for received XMPP messages
  479. converse.allow_non_roster_messaging =true;
  480. converse.chatboxes.onMessage(msg);
  481. expect(converse.emit).toHaveBeenCalledWith('message', msg);
  482. });
  483. waits(50);
  484. runs(function () {
  485. // Check that the chatbox and its view now exist
  486. var chatbox = converse.chatboxes.get(sender_jid);
  487. var chatboxview = converse.chatboxviews.get(sender_jid);
  488. expect(chatbox).toBeDefined();
  489. expect(chatboxview).toBeDefined();
  490. // Check that the message was received and check the message parameters
  491. expect(chatbox.messages.length).toEqual(1);
  492. var msg_obj = chatbox.messages.models[0];
  493. expect(msg_obj.get('message')).toEqual(message);
  494. expect(msg_obj.get('fullname')).toEqual(sender_jid);
  495. expect(msg_obj.get('sender')).toEqual('them');
  496. expect(msg_obj.get('delayed')).toEqual(false);
  497. // Now check that the message appears inside the chatbox in the DOM
  498. var $chat_content = chatboxview.$el.find('.chat-content');
  499. var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
  500. expect(msg_txt).toEqual(message);
  501. var sender_txt = $chat_content.find('span.chat-msg-them').text();
  502. expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
  503. });
  504. }));
  505. });
  506. describe("and for which then an error message is received from the server", function () {
  507. it("will have the error message displayed after itself", mock.initConverse(function (_converse) {
  508. test_utils.createContacts(_converse, 'current');
  509. test_utils.openControlBox();
  510. test_utils.openContactsPanel(_converse);
  511. // TODO: what could still be done for error
  512. // messages... if the <error> element has type
  513. // "cancel", then we know the messages wasn't sent,
  514. // and can give the user a nicer indication of
  515. // that.
  516. /* <message from="scotty@enterprise.com/_converse.js-84843526"
  517. * to="kirk@enterprise.com.com"
  518. * type="chat"
  519. * id="82bc02ce-9651-4336-baf0-fa04762ed8d2"
  520. * xmlns="jabber:client">
  521. * <body>yo</body>
  522. * <active xmlns="http://jabber.org/protocol/chatstates"/>
  523. * </message>
  524. */
  525. var sender_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
  526. var fullname = _converse.xmppstatus.get('fullname');
  527. fullname = _.isEmpty(fullname)? _converse.bare_jid: fullname;
  528. _converse.api.chats.open(sender_jid);
  529. var msg_text = 'This message will not be sent, due to an error';
  530. var view = _converse.chatboxviews.get(sender_jid);
  531. var message = view.model.messages.create({
  532. 'msgid': '82bc02ce-9651-4336-baf0-fa04762ed8d2',
  533. 'fullname': fullname,
  534. 'sender': 'me',
  535. 'time': moment().format(),
  536. 'message': msg_text
  537. });
  538. view.sendMessage(message);
  539. var $chat_content = view.$el.find('.chat-content');
  540. var msg_txt = $chat_content.find('.chat-message:last').find('.chat-msg-content').text();
  541. expect(msg_txt).toEqual(msg_text);
  542. // We send another message, for which an error will
  543. // not be received, to test that errors appear
  544. // after the relevant message.
  545. msg_text = 'This message will be sent, and not receive an error';
  546. message = view.model.messages.create({
  547. 'msgid': '6fcdeee3-000f-4ce8-a17e-9ce28f0ae104',
  548. 'fullname': fullname,
  549. 'sender': 'me',
  550. 'time': moment().format(),
  551. 'message': msg_text
  552. });
  553. view.sendMessage(message);
  554. msg_txt = $chat_content.find('.chat-message:last').find('.chat-msg-content').text();
  555. expect(msg_txt).toEqual(msg_text);
  556. /* <message xmlns="jabber:client"
  557. * to="scotty@enterprise.com/_converse.js-84843526"
  558. * type="error"
  559. * id="82bc02ce-9651-4336-baf0-fa04762ed8d2"
  560. * from="kirk@enterprise.com.com">
  561. * <error type="cancel">
  562. * <remote-server-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
  563. * <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Server-to-server connection failed: Connecting failed: connection timeout</text>
  564. * </error>
  565. * </message>
  566. */
  567. var error_txt = 'Server-to-server connection failed: Connecting failed: connection timeout';
  568. var stanza = $msg({
  569. 'to': _converse.connection.jid,
  570. 'type':'error',
  571. 'id':'82bc02ce-9651-4336-baf0-fa04762ed8d2',
  572. 'from': sender_jid
  573. })
  574. .c('error', {'type': 'cancel'})
  575. .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up()
  576. .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" })
  577. .t('Server-to-server connection failed: Connecting failed: connection timeout');
  578. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  579. expect($chat_content.find('.chat-error').text()).toEqual(error_txt);
  580. /* Incoming error messages that are not tied to a
  581. * certain show message (via the msgid attribute),
  582. * are not shown at all. The reason for this is
  583. * that we may get error messages for chat state
  584. * notifications as well.
  585. */
  586. stanza = $msg({
  587. 'to': _converse.connection.jid,
  588. 'type':'error',
  589. 'id':'some-other-unused-id',
  590. 'from': sender_jid
  591. })
  592. .c('error', {'type': 'cancel'})
  593. .c('remote-server-not-found', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }).up()
  594. .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" })
  595. .t('Server-to-server connection failed: Connecting failed: connection timeout');
  596. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  597. expect($chat_content.find('.chat-error').length).toEqual(1);
  598. }));
  599. });
  600. it("will cause the chat area to be scrolled down only if it was at the bottom already", mock.initConverse(function (_converse) {
  601. test_utils.createContacts(_converse, 'current');
  602. test_utils.openControlBox();
  603. test_utils.openContactsPanel(_converse);
  604. var message = 'This message is received while the chat area is scrolled up';
  605. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  606. test_utils.openChatBoxFor(_converse, sender_jid);
  607. var chatboxview = _converse.chatboxviews.get(sender_jid);
  608. spyOn(chatboxview, 'scrollDown').andCallThrough();
  609. runs(function () {
  610. /* Create enough messages so that there's a
  611. * scrollbar.
  612. */
  613. for (var i=0; i<20; i++) {
  614. _converse.chatboxes.onMessage($msg({
  615. from: sender_jid,
  616. to: _converse.connection.jid,
  617. type: 'chat',
  618. id: (new Date()).getTime()
  619. }).c('body').t('Message: '+i).up()
  620. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
  621. }
  622. });
  623. waits(50);
  624. runs(function () {
  625. chatboxview.$content.scrollTop(0);
  626. });
  627. waits(250);
  628. runs(function () {
  629. _converse.chatboxes.onMessage($msg({
  630. from: sender_jid,
  631. to: _converse.connection.jid,
  632. type: 'chat',
  633. id: (new Date()).getTime()
  634. }).c('body').t(message).up()
  635. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
  636. });
  637. waits(150);
  638. runs(function () {
  639. // Now check that the message appears inside the chatbox in the DOM
  640. var $chat_content = chatboxview.$el.find('.chat-content');
  641. var msg_txt = $chat_content.find('.chat-message:last').find('.chat-msg-content').text();
  642. expect(msg_txt).toEqual(message);
  643. expect(chatboxview.model.get('scrolled')).toBeTruthy();
  644. expect(chatboxview.$content.scrollTop()).toBe(0);
  645. expect(chatboxview.$('.new-msgs-indicator').is(':visible')).toBeTruthy();
  646. // Scroll down again
  647. chatboxview.$content.scrollTop(chatboxview.$content[0].scrollHeight);
  648. });
  649. waits(250);
  650. runs(function () {
  651. expect(chatboxview.$('.new-msgs-indicator').is(':visible')).toBeFalsy();
  652. });
  653. }));
  654. it("is ignored if it's intended for a different resource and filter_by_resource is set to true", mock.initConverse(function (_converse) {
  655. test_utils.createContacts(_converse, 'current');
  656. test_utils.openControlBox();
  657. test_utils.openContactsPanel(_converse);
  658. // Send a message from a different resource
  659. var message, sender_jid, msg;
  660. spyOn(_converse, 'log');
  661. spyOn(_converse.chatboxes, 'getChatBox').andCallThrough();
  662. runs(function () {
  663. _converse.filter_by_resource = true;
  664. sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  665. msg = $msg({
  666. from: sender_jid,
  667. to: _converse.bare_jid+"/some-other-resource",
  668. type: 'chat',
  669. id: (new Date()).getTime()
  670. }).c('body').t("This message will not be shown").up()
  671. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  672. _converse.chatboxes.onMessage(msg);
  673. });
  674. waits(50);
  675. runs(function () {
  676. expect(_converse.log).toHaveBeenCalledWith(
  677. "onMessage: Ignoring incoming message intended for a different resource: dummy@localhost/some-other-resource", "info");
  678. expect(_converse.chatboxes.getChatBox).not.toHaveBeenCalled();
  679. _converse.filter_by_resource = false;
  680. });
  681. waits(50);
  682. runs(function () {
  683. message = "This message sent to a different resource will be shown";
  684. msg = $msg({
  685. from: sender_jid,
  686. to: _converse.bare_jid+"/some-other-resource",
  687. type: 'chat',
  688. id: '134234623462346'
  689. }).c('body').t(message).up()
  690. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  691. _converse.chatboxes.onMessage(msg);
  692. });
  693. waits(50);
  694. runs(function () {
  695. expect(_converse.chatboxes.getChatBox).toHaveBeenCalled();
  696. var chatboxview = _converse.chatboxviews.get(sender_jid);
  697. var $chat_content = chatboxview.$el.find('.chat-content:last');
  698. var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
  699. expect(msg_txt).toEqual(message);
  700. });
  701. }));
  702. });
  703. describe("when sent by the current user", function () {
  704. it("will always cause the chat area to be scrolled down", mock.initConverse(function (_converse) {
  705. // TODO
  706. }));
  707. });
  708. it("is ignored if it's a malformed headline message", mock.initConverse(function (_converse) {
  709. test_utils.createContacts(_converse, 'current');
  710. test_utils.openControlBox();
  711. test_utils.openContactsPanel(_converse);
  712. /* Ideally we wouldn't have to filter out headline
  713. * messages, but Prosody gives them the wrong 'type' :(
  714. */
  715. sinon.spy(_converse, 'log');
  716. sinon.spy(_converse.chatboxes, 'getChatBox');
  717. sinon.spy(utils, 'isHeadlineMessage');
  718. var msg = $msg({
  719. from: 'localhost',
  720. to: _converse.bare_jid,
  721. type: 'chat',
  722. id: (new Date()).getTime()
  723. }).c('body').t("This headline message will not be shown").tree();
  724. _converse.chatboxes.onMessage(msg);
  725. expect(_converse.log.calledWith(
  726. "onMessage: Ignoring incoming headline message sent with type 'chat' from JID: localhost",
  727. "info"
  728. )).toBeTruthy();
  729. expect(utils.isHeadlineMessage.called).toBeTruthy();
  730. expect(utils.isHeadlineMessage.returned(true)).toBeTruthy();
  731. expect(_converse.chatboxes.getChatBox.called).toBeFalsy();
  732. // Remove sinon spies
  733. _converse.log.restore();
  734. _converse.chatboxes.getChatBox.restore();
  735. utils.isHeadlineMessage.restore();
  736. }));
  737. it("can be a carbon message, as defined in XEP-0280", mock.initConverse(function (_converse) {
  738. test_utils.createContacts(_converse, 'current');
  739. test_utils.openControlBox();
  740. test_utils.openContactsPanel(_converse);
  741. // Send a message from a different resource
  742. spyOn(_converse, 'log');
  743. var msgtext = 'This is a carbon message';
  744. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  745. var msg = $msg({
  746. 'from': sender_jid,
  747. 'id': (new Date()).getTime(),
  748. 'to': _converse.connection.jid,
  749. 'type': 'chat',
  750. 'xmlns': 'jabber:client'
  751. }).c('received', {'xmlns': 'urn:xmpp:carbons:2'})
  752. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  753. .c('message', {
  754. 'xmlns': 'jabber:client',
  755. 'from': sender_jid,
  756. 'to': _converse.bare_jid+'/another-resource',
  757. 'type': 'chat'
  758. }).c('body').t(msgtext).tree();
  759. _converse.chatboxes.onMessage(msg);
  760. // Check that the chatbox and its view now exist
  761. var chatbox = _converse.chatboxes.get(sender_jid);
  762. var chatboxview = _converse.chatboxviews.get(sender_jid);
  763. expect(chatbox).toBeDefined();
  764. expect(chatboxview).toBeDefined();
  765. // Check that the message was received and check the message parameters
  766. expect(chatbox.messages.length).toEqual(1);
  767. var msg_obj = chatbox.messages.models[0];
  768. expect(msg_obj.get('message')).toEqual(msgtext);
  769. expect(msg_obj.get('fullname')).toEqual(mock.cur_names[1]);
  770. expect(msg_obj.get('sender')).toEqual('them');
  771. expect(msg_obj.get('delayed')).toEqual(false);
  772. // Now check that the message appears inside the chatbox in the DOM
  773. var $chat_content = chatboxview.$el.find('.chat-content');
  774. var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
  775. expect(msg_txt).toEqual(msgtext);
  776. var sender_txt = $chat_content.find('span.chat-msg-them').text();
  777. expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
  778. }));
  779. it("can be a carbon message that this user sent from a different client, as defined in XEP-0280", mock.initConverse(function (_converse) {
  780. test_utils.createContacts(_converse, 'current');
  781. test_utils.openControlBox();
  782. test_utils.openContactsPanel(_converse);
  783. // Send a message from a different resource
  784. spyOn(_converse, 'log');
  785. var msgtext = 'This is a sent carbon message';
  786. var recipient_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
  787. var msg = $msg({
  788. 'from': _converse.bare_jid,
  789. 'id': (new Date()).getTime(),
  790. 'to': _converse.connection.jid,
  791. 'type': 'chat',
  792. 'xmlns': 'jabber:client'
  793. }).c('sent', {'xmlns': 'urn:xmpp:carbons:2'})
  794. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  795. .c('message', {
  796. 'xmlns': 'jabber:client',
  797. 'from': _converse.bare_jid+'/another-resource',
  798. 'to': recipient_jid,
  799. 'type': 'chat'
  800. }).c('body').t(msgtext).tree();
  801. _converse.chatboxes.onMessage(msg);
  802. // Check that the chatbox and its view now exist
  803. var chatbox = _converse.chatboxes.get(recipient_jid);
  804. var chatboxview = _converse.chatboxviews.get(recipient_jid);
  805. expect(chatbox).toBeDefined();
  806. expect(chatboxview).toBeDefined();
  807. // Check that the message was received and check the message parameters
  808. expect(chatbox.messages.length).toEqual(1);
  809. var msg_obj = chatbox.messages.models[0];
  810. expect(msg_obj.get('message')).toEqual(msgtext);
  811. expect(msg_obj.get('fullname')).toEqual(mock.cur_names[5]);
  812. expect(msg_obj.get('sender')).toEqual('me');
  813. expect(msg_obj.get('delayed')).toEqual(false);
  814. // Now check that the message appears inside the chatbox in the DOM
  815. var $chat_content = chatboxview.$el.find('.chat-content');
  816. var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
  817. expect(msg_txt).toEqual(msgtext);
  818. }));
  819. it("will be discarded if it's a malicious message meant to look like a carbon copy", mock.initConverse(function (converse) {
  820. test_utils.createContacts(converse, 'current');
  821. test_utils.openControlBox();
  822. test_utils.openContactsPanel(converse);
  823. /* <message from="mallory@evil.example" to="b@xmpp.example">
  824. * <received xmlns='urn:xmpp:carbons:2'>
  825. * <forwarded xmlns='urn:xmpp:forward:0'>
  826. * <message from="alice@xmpp.example" to="bob@xmpp.example/client1">
  827. * <body>Please come to Creepy Valley tonight, alone!</body>
  828. * </message>
  829. * </forwarded>
  830. * </received>
  831. * </message>
  832. */
  833. spyOn(converse, 'log');
  834. var msgtext = 'Please come to Creepy Valley tonight, alone!';
  835. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  836. var impersonated_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  837. var msg = $msg({
  838. 'from': sender_jid,
  839. 'id': (new Date()).getTime(),
  840. 'to': converse.connection.jid,
  841. 'type': 'chat',
  842. 'xmlns': 'jabber:client'
  843. }).c('received', {'xmlns': 'urn:xmpp:carbons:2'})
  844. .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
  845. .c('message', {
  846. 'xmlns': 'jabber:client',
  847. 'from': impersonated_jid,
  848. 'to': converse.connection.jid,
  849. 'type': 'chat'
  850. }).c('body').t(msgtext).tree();
  851. converse.chatboxes.onMessage(msg);
  852. // Check that chatbox for impersonated user is not created.
  853. var chatbox = converse.chatboxes.get(impersonated_jid);
  854. expect(chatbox).not.toBeDefined();
  855. // Check that the chatbox for the malicous user is not created
  856. chatbox = converse.chatboxes.get(sender_jid);
  857. expect(chatbox).not.toBeDefined();
  858. }));
  859. it("received for a minimized chat box will increment a counter on its header", mock.initConverse(function (_converse) {
  860. test_utils.createContacts(_converse, 'current');
  861. test_utils.openControlBox();
  862. test_utils.openContactsPanel(_converse);
  863. var contact_name = mock.cur_names[0];
  864. var contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost';
  865. runs(function () {
  866. spyOn(_converse, 'emit').andCallThrough();
  867. test_utils.openChatBoxFor(_converse, contact_jid);
  868. var chatview = _converse.chatboxviews.get(contact_jid);
  869. expect(chatview.$el.is(':visible')).toBeTruthy();
  870. expect(chatview.model.get('minimized')).toBeFalsy();
  871. chatview.$el.find('.toggle-chatbox-button').click();
  872. expect(chatview.model.get('minimized')).toBeTruthy();
  873. var message = 'This message is sent to a minimized chatbox';
  874. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  875. var msg = $msg({
  876. from: sender_jid,
  877. to: _converse.connection.jid,
  878. type: 'chat',
  879. id: (new Date()).getTime()
  880. }).c('body').t(message).up()
  881. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  882. _converse.chatboxes.onMessage(msg);
  883. expect(_converse.emit).toHaveBeenCalledWith('message', msg);
  884. var trimmed_chatboxes = _converse.minimized_chats;
  885. var trimmedview = trimmed_chatboxes.get(contact_jid);
  886. var $count = trimmedview.$el.find('.chat-head-message-count');
  887. expect(chatview.$el.is(':visible')).toBeFalsy();
  888. expect(trimmedview.model.get('minimized')).toBeTruthy();
  889. expect($count.is(':visible')).toBeTruthy();
  890. expect($count.html()).toBe('1');
  891. _converse.chatboxes.onMessage(
  892. $msg({
  893. from: mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
  894. to: _converse.connection.jid,
  895. type: 'chat',
  896. id: (new Date()).getTime()
  897. }).c('body').t('This message is also sent to a minimized chatbox').up()
  898. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
  899. );
  900. expect(chatview.$el.is(':visible')).toBeFalsy();
  901. expect(trimmedview.model.get('minimized')).toBeTruthy();
  902. $count = trimmedview.$el.find('.chat-head-message-count');
  903. expect($count.is(':visible')).toBeTruthy();
  904. expect($count.html()).toBe('2');
  905. trimmedview.$el.find('.restore-chat').click();
  906. expect(trimmed_chatboxes.keys().length).toBe(0);
  907. });
  908. }));
  909. it("will indicate when it has a time difference of more than a day between it and its predecessor", mock.initConverse(function (_converse) {
  910. test_utils.createContacts(_converse, 'current');
  911. test_utils.openControlBox();
  912. test_utils.openContactsPanel(_converse);
  913. spyOn(_converse, 'emit');
  914. var contact_name = mock.cur_names[1];
  915. var contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@localhost';
  916. test_utils.openChatBoxFor(_converse, contact_jid);
  917. test_utils.clearChatBoxMessages(_converse, contact_jid);
  918. var one_day_ago = moment();
  919. one_day_ago.subtract('days', 1);
  920. var message = 'This is a day old message';
  921. var chatbox = _converse.chatboxes.get(contact_jid);
  922. var chatboxview = _converse.chatboxviews.get(contact_jid);
  923. var $chat_content = chatboxview.$el.find('.chat-content');
  924. var msg_obj;
  925. var msg_txt;
  926. var sender_txt;
  927. var msg = $msg({
  928. from: contact_jid,
  929. to: _converse.connection.jid,
  930. type: 'chat',
  931. id: one_day_ago.unix()
  932. }).c('body').t(message).up()
  933. .c('delay', { xmlns:'urn:xmpp:delay', from: 'localhost', stamp: one_day_ago.format() })
  934. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  935. _converse.chatboxes.onMessage(msg);
  936. expect(_converse.emit).toHaveBeenCalledWith('message', msg);
  937. expect(chatbox.messages.length).toEqual(1);
  938. msg_obj = chatbox.messages.models[0];
  939. expect(msg_obj.get('message')).toEqual(message);
  940. expect(msg_obj.get('fullname')).toEqual(contact_name);
  941. expect(msg_obj.get('sender')).toEqual('them');
  942. expect(msg_obj.get('delayed')).toEqual(true);
  943. msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
  944. expect(msg_txt).toEqual(message);
  945. sender_txt = $chat_content.find('span.chat-msg-them').text();
  946. expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
  947. var $time = $chat_content.find('time');
  948. expect($time.length).toEqual(1);
  949. expect($time.attr('class')).toEqual('chat-info chat-date');
  950. expect($time.data('isodate')).toEqual(moment(one_day_ago.startOf('day')).format());
  951. expect($time.text()).toEqual(moment(one_day_ago.startOf('day')).format("dddd MMM Do YYYY"));
  952. message = 'This is a current message';
  953. msg = $msg({
  954. from: contact_jid,
  955. to: _converse.connection.jid,
  956. type: 'chat',
  957. id: new Date().getTime()
  958. }).c('body').t(message).up()
  959. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  960. _converse.chatboxes.onMessage(msg);
  961. expect(_converse.emit).toHaveBeenCalledWith('message', msg);
  962. // Check that there is a <time> element, with the required
  963. // props.
  964. $time = $chat_content.find('time');
  965. expect($time.length).toEqual(2); // There are now two time elements
  966. $time = $chat_content.find('time:last'); // We check the last one
  967. var message_date = new Date();
  968. expect($time.attr('class')).toEqual('chat-info chat-date');
  969. expect($time.data('isodate')).toEqual(moment(message_date).startOf('day').format());
  970. expect($time.text()).toEqual(moment(message_date).startOf('day').format("dddd MMM Do YYYY"));
  971. // Normal checks for the 2nd message
  972. expect(chatbox.messages.length).toEqual(2);
  973. msg_obj = chatbox.messages.models[1];
  974. expect(msg_obj.get('message')).toEqual(message);
  975. expect(msg_obj.get('fullname')).toEqual(contact_name);
  976. expect(msg_obj.get('sender')).toEqual('them');
  977. expect(msg_obj.get('delayed')).toEqual(false);
  978. msg_txt = $chat_content.find('.chat-message').last().find('.chat-msg-content').text();
  979. expect(msg_txt).toEqual(message);
  980. sender_txt = $chat_content.find('span.chat-msg-them').last().text();
  981. expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
  982. }));
  983. it("can be sent from a chatbox, and will appear inside it", mock.initConverse(function (_converse) {
  984. test_utils.createContacts(_converse, 'current');
  985. test_utils.openControlBox();
  986. test_utils.openContactsPanel(_converse);
  987. spyOn(_converse, 'emit');
  988. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  989. test_utils.openChatBoxFor(_converse, contact_jid);
  990. expect(_converse.emit).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object));
  991. var view = _converse.chatboxviews.get(contact_jid);
  992. var message = 'This message is sent from this chatbox';
  993. spyOn(view, 'sendMessage').andCallThrough();
  994. test_utils.sendMessage(view, message);
  995. expect(view.sendMessage).toHaveBeenCalled();
  996. expect(view.model.messages.length, 2);
  997. expect(_converse.emit.mostRecentCall.args, ['messageSend', message]);
  998. expect(view.$el.find('.chat-content').find('.chat-message').last().find('.chat-msg-content').text()).toEqual(message);
  999. }));
  1000. it("is sanitized to prevent Javascript injection attacks", mock.initConverse(function (_converse) {
  1001. test_utils.createContacts(_converse, 'current');
  1002. test_utils.openControlBox();
  1003. test_utils.openContactsPanel(_converse);
  1004. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1005. test_utils.openChatBoxFor(_converse, contact_jid);
  1006. var view = _converse.chatboxviews.get(contact_jid);
  1007. var message = '<p>This message contains <em>some</em> <b>markup</b></p>';
  1008. spyOn(view, 'sendMessage').andCallThrough();
  1009. test_utils.sendMessage(view, message);
  1010. expect(view.sendMessage).toHaveBeenCalled();
  1011. var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-msg-content');
  1012. expect(msg.text()).toEqual(message);
  1013. 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;');
  1014. }));
  1015. it("should display emoticons correctly", mock.initConverse(function (_converse) {
  1016. test_utils.createContacts(_converse, 'current');
  1017. test_utils.openControlBox();
  1018. test_utils.openContactsPanel(_converse);
  1019. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1020. test_utils.openChatBoxFor(_converse, contact_jid);
  1021. var view = _converse.chatboxviews.get(contact_jid);
  1022. var messages = [':)', ';)', ':D', ':P', '8)', '>:)', ':S', ':\\', '>:(', ':(', ':O', '(^.^)b', '<3'];
  1023. var emoticons = [
  1024. '<span class="emoticon icon-smiley"></span>', '<span class="emoticon icon-wink"></span>',
  1025. '<span class="emoticon icon-grin"></span>', '<span class="emoticon icon-tongue"></span>',
  1026. '<span class="emoticon icon-cool"></span>', '<span class="emoticon icon-evil"></span>',
  1027. '<span class="emoticon icon-confused"></span>', '<span class="emoticon icon-wondering"></span>',
  1028. '<span class="emoticon icon-angry"></span>', '<span class="emoticon icon-sad"></span>',
  1029. '<span class="emoticon icon-shocked"></span>', '<span class="emoticon icon-thumbs-up"></span>',
  1030. '<span class="emoticon icon-heart"></span>'
  1031. ];
  1032. spyOn(view, 'sendMessage').andCallThrough();
  1033. for (var i = 0; i < messages.length; i++) {
  1034. var message = messages[i];
  1035. test_utils.sendMessage(view, message);
  1036. expect(view.sendMessage).toHaveBeenCalled();
  1037. var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-msg-content');
  1038. expect(msg.html()).toEqual(emoticons[i]);
  1039. }
  1040. }));
  1041. it("can contain hyperlinks, which will be clickable", mock.initConverse(function (_converse) {
  1042. test_utils.createContacts(_converse, 'current');
  1043. test_utils.openControlBox();
  1044. test_utils.openContactsPanel(_converse);
  1045. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1046. test_utils.openChatBoxFor(_converse, contact_jid);
  1047. var view = _converse.chatboxviews.get(contact_jid);
  1048. var message = 'This message contains a hyperlink: www.opkode.com';
  1049. spyOn(view, 'sendMessage').andCallThrough();
  1050. runs(function () {
  1051. test_utils.sendMessage(view, message);
  1052. });
  1053. waits(500);
  1054. runs(function () {
  1055. expect(view.sendMessage).toHaveBeenCalled();
  1056. var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-msg-content');
  1057. expect(msg.text()).toEqual(message);
  1058. expect(msg.html()).toEqual('This message contains a hyperlink: <a target="_blank" rel="noopener" href="http://www.opkode.com">www.opkode.com</a>');
  1059. });
  1060. }));
  1061. it("will have properly escaped URLs", mock.initConverse(function (_converse) {
  1062. test_utils.createContacts(_converse, 'current');
  1063. test_utils.openControlBox();
  1064. test_utils.openContactsPanel(_converse);
  1065. var message, msg;
  1066. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1067. test_utils.openChatBoxFor(_converse, contact_jid);
  1068. var view = _converse.chatboxviews.get(contact_jid);
  1069. spyOn(view, 'sendMessage').andCallThrough();
  1070. runs(function () {
  1071. message = "http://www.opkode.com/'onmouseover='alert(1)'whatever";
  1072. test_utils.sendMessage(view, message);
  1073. });
  1074. waits(50);
  1075. runs(function () {
  1076. expect(view.sendMessage).toHaveBeenCalled();
  1077. msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-msg-content');
  1078. expect(msg.text()).toEqual(message);
  1079. expect(msg.html()).toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%27onmouseover=%27alert%281%29%27whatever">http://www.opkode.com/\'onmouseover=\'alert(1)\'whatever</a>');
  1080. message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever';
  1081. test_utils.sendMessage(view, message);
  1082. });
  1083. waits(50);
  1084. runs(function () {
  1085. expect(view.sendMessage).toHaveBeenCalled();
  1086. msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-msg-content');
  1087. expect(msg.text()).toEqual(message);
  1088. expect(msg.html()).toEqual('<a target="_blank" rel="noopener" href="http://www.opkode.com/%22onmouseover=%22alert%281%29%22whatever">http://www.opkode.com/"onmouseover="alert(1)"whatever</a>');
  1089. message = "https://en.wikipedia.org/wiki/Ender's_Game";
  1090. test_utils.sendMessage(view, message);
  1091. });
  1092. waits(50);
  1093. runs(function () {
  1094. expect(view.sendMessage).toHaveBeenCalled();
  1095. msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-msg-content');
  1096. expect(msg.text()).toEqual(message);
  1097. expect(msg.html()).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">https://en.wikipedia.org/wiki/Ender\'s_Game</a>');
  1098. message = "https://en.wikipedia.org/wiki/Ender%27s_Game";
  1099. test_utils.sendMessage(view, message);
  1100. });
  1101. waits(50);
  1102. runs(function () {
  1103. expect(view.sendMessage).toHaveBeenCalled();
  1104. msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-msg-content');
  1105. expect(msg.text()).toEqual(message);
  1106. expect(msg.html()).toEqual('<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Ender%27s_Game">https://en.wikipedia.org/wiki/Ender%27s_Game</a>');
  1107. });
  1108. }));
  1109. it("will render images from their URLs", mock.initConverse(function (_converse) {
  1110. test_utils.createContacts(_converse, 'current');
  1111. test_utils.openControlBox();
  1112. test_utils.openContactsPanel(_converse);
  1113. if (/PhantomJS/.test(window.navigator.userAgent)) {
  1114. // Doesn't work when running tests in PhantomJS, since
  1115. // the page is loaded via file:///
  1116. return;
  1117. }
  1118. var message = document.URL.split(window.location.pathname)[0] + "/logo/conversejs.svg";
  1119. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1120. test_utils.openChatBoxFor(_converse, contact_jid);
  1121. var view = _converse.chatboxviews.get(contact_jid);
  1122. spyOn(view, 'sendMessage').andCallThrough();
  1123. runs(function () {
  1124. test_utils.sendMessage(view, message);
  1125. });
  1126. waits(500);
  1127. runs(function () {
  1128. expect(view.sendMessage).toHaveBeenCalled();
  1129. var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-msg-content');
  1130. expect(msg.html()).toEqual('<img src="'+message+'" class="chat-image">');
  1131. });
  1132. }));
  1133. });
  1134. describe("A Chat Status Notification", function () {
  1135. it("does not open automatically if a chat state notification is received", mock.initConverse(function (_converse) {
  1136. test_utils.createContacts(_converse, 'current');
  1137. test_utils.openControlBox();
  1138. test_utils.openContactsPanel(_converse);
  1139. spyOn(_converse, 'emit');
  1140. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  1141. // <composing> state
  1142. var msg = $msg({
  1143. from: sender_jid,
  1144. to: _converse.connection.jid,
  1145. type: 'chat',
  1146. id: (new Date()).getTime()
  1147. }).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  1148. _converse.chatboxes.onMessage(msg);
  1149. expect(_converse.emit).toHaveBeenCalledWith('message', msg);
  1150. }));
  1151. describe("An active notification", function () {
  1152. it("is sent when the user opens a chat box", mock.initConverse(function (_converse) {
  1153. test_utils.createContacts(_converse, 'current');
  1154. test_utils.openControlBox();
  1155. test_utils.openContactsPanel(_converse);
  1156. waits(300); // ChatBox.show() is debounced for 250ms
  1157. runs(function () {
  1158. spyOn(_converse.connection, 'send');
  1159. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1160. test_utils.openChatBoxFor(_converse, contact_jid);
  1161. var view = _converse.chatboxviews.get(contact_jid);
  1162. expect(view.model.get('chat_state')).toBe('active');
  1163. expect(_converse.connection.send).toHaveBeenCalled();
  1164. var $stanza = $(_converse.connection.send.argsForCall[0][0].tree());
  1165. expect($stanza.attr('to')).toBe(contact_jid);
  1166. expect($stanza.children().length).toBe(3);
  1167. expect($stanza.children().get(0).tagName).toBe('active');
  1168. expect($stanza.children().get(1).tagName).toBe('no-store');
  1169. expect($stanza.children().get(2).tagName).toBe('no-permanent-store');
  1170. });
  1171. }));
  1172. it("is sent when the user maximizes a minimized a chat box", mock.initConverse(function (_converse) {
  1173. test_utils.createContacts(_converse, 'current');
  1174. test_utils.openControlBox();
  1175. test_utils.openContactsPanel(_converse);
  1176. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1177. runs(function () {
  1178. test_utils.openChatBoxFor(_converse, contact_jid);
  1179. });
  1180. waits(300); // ChatBox.show() is debounced for 250ms
  1181. runs(function () {
  1182. var view = _converse.chatboxviews.get(contact_jid);
  1183. view.model.minimize();
  1184. expect(view.model.get('chat_state')).toBe('inactive');
  1185. spyOn(_converse.connection, 'send');
  1186. view.model.maximize();
  1187. expect(view.model.get('chat_state')).toBe('active');
  1188. expect(_converse.connection.send).toHaveBeenCalled();
  1189. var $stanza = $(_converse.connection.send.argsForCall[0][0].tree());
  1190. expect($stanza.attr('to')).toBe(contact_jid);
  1191. expect($stanza.children().length).toBe(3);
  1192. expect($stanza.children().get(0).tagName).toBe('active');
  1193. expect($stanza.children().get(1).tagName).toBe('no-store');
  1194. expect($stanza.children().get(2).tagName).toBe('no-permanent-store');
  1195. });
  1196. }));
  1197. });
  1198. describe("A composing notification", function () {
  1199. it("is sent as soon as the user starts typing a message which is not a command", mock.initConverse(function (_converse) {
  1200. test_utils.createContacts(_converse, 'current');
  1201. test_utils.openControlBox();
  1202. test_utils.openContactsPanel(_converse);
  1203. waits(300); // ChatBox.show() is debounced for 250ms
  1204. runs(function () {
  1205. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1206. test_utils.openChatBoxFor(_converse, contact_jid);
  1207. var view = _converse.chatboxviews.get(contact_jid);
  1208. expect(view.model.get('chat_state')).toBe('active');
  1209. spyOn(_converse.connection, 'send');
  1210. view.keyPressed({
  1211. target: view.$el.find('textarea.chat-textarea'),
  1212. keyCode: 1
  1213. });
  1214. expect(view.model.get('chat_state')).toBe('composing');
  1215. expect(_converse.connection.send).toHaveBeenCalled();
  1216. var $stanza = $(_converse.connection.send.argsForCall[0][0].tree());
  1217. expect($stanza.attr('to')).toBe(contact_jid);
  1218. expect($stanza.children().get(0).tagName).toBe('composing');
  1219. expect($stanza.children().get(1).tagName).toBe('no-store');
  1220. expect($stanza.children().get(2).tagName).toBe('no-permanent-store');
  1221. // The notification is not sent again
  1222. view.keyPressed({
  1223. target: view.$el.find('textarea.chat-textarea'),
  1224. keyCode: 1
  1225. });
  1226. expect(view.model.get('chat_state')).toBe('composing');
  1227. expect(_converse.emit.callCount, 1);
  1228. });
  1229. }));
  1230. it("will be shown if received", mock.initConverse(function (_converse) {
  1231. test_utils.createContacts(_converse, 'current');
  1232. test_utils.openControlBox();
  1233. test_utils.openContactsPanel(_converse);
  1234. // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
  1235. spyOn(_converse, 'emit');
  1236. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  1237. // <composing> state
  1238. var msg = $msg({
  1239. from: sender_jid,
  1240. to: _converse.connection.jid,
  1241. type: 'chat',
  1242. id: (new Date()).getTime()
  1243. }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  1244. _converse.chatboxes.onMessage(msg);
  1245. expect(_converse.emit).toHaveBeenCalledWith('message', msg);
  1246. var chatboxview = _converse.chatboxviews.get(sender_jid);
  1247. expect(chatboxview).toBeDefined();
  1248. // Check that the notification appears inside the chatbox in the DOM
  1249. var $events = chatboxview.$el.find('.chat-event');
  1250. expect($events.text()).toEqual(mock.cur_names[1] + ' is typing');
  1251. }));
  1252. });
  1253. describe("A paused notification", function () {
  1254. it("is sent if the user has stopped typing since 30 seconds", mock.initConverse(function (_converse) {
  1255. test_utils.createContacts(_converse, 'current');
  1256. test_utils.openControlBox();
  1257. test_utils.openContactsPanel(_converse);
  1258. var view, contact_jid;
  1259. _converse.TIMEOUTS.PAUSED = 200; // Make the timeout shorter so that we can test
  1260. waits(300); // ChatBox.show() is debounced for 250ms
  1261. runs(function () {
  1262. contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1263. test_utils.openChatBoxFor(_converse, contact_jid);
  1264. view = _converse.chatboxviews.get(contact_jid);
  1265. spyOn(_converse.connection, 'send');
  1266. spyOn(view, 'setChatState').andCallThrough();
  1267. expect(view.model.get('chat_state')).toBe('active');
  1268. view.keyPressed({
  1269. target: view.$el.find('textarea.chat-textarea'),
  1270. keyCode: 1
  1271. });
  1272. expect(view.model.get('chat_state')).toBe('composing');
  1273. expect(_converse.connection.send).toHaveBeenCalled();
  1274. var $stanza = $(_converse.connection.send.argsForCall[0][0].tree());
  1275. expect($stanza.children().get(0).tagName).toBe('composing');
  1276. });
  1277. waits(250);
  1278. runs(function () {
  1279. expect(view.model.get('chat_state')).toBe('paused');
  1280. expect(_converse.connection.send).toHaveBeenCalled();
  1281. var $stanza = $(_converse.connection.send.argsForCall[1][0].tree());
  1282. expect($stanza.attr('to')).toBe(contact_jid);
  1283. expect($stanza.children().length).toBe(3);
  1284. expect($stanza.children().get(0).tagName).toBe('paused');
  1285. expect($stanza.children().get(1).tagName).toBe('no-store');
  1286. expect($stanza.children().get(2).tagName).toBe('no-permanent-store');
  1287. // Test #359. A paused notification should not be sent
  1288. // out if the user simply types longer than the
  1289. // timeout.
  1290. view.keyPressed({
  1291. target: view.$el.find('textarea.chat-textarea'),
  1292. keyCode: 1
  1293. });
  1294. expect(view.setChatState).toHaveBeenCalled();
  1295. expect(view.model.get('chat_state')).toBe('composing');
  1296. });
  1297. waits(100);
  1298. runs(function () {
  1299. view.keyPressed({
  1300. target: view.$el.find('textarea.chat-textarea'),
  1301. keyCode: 1
  1302. });
  1303. expect(view.model.get('chat_state')).toBe('composing');
  1304. });
  1305. waits(150);
  1306. runs(function () {
  1307. expect(view.model.get('chat_state')).toBe('composing');
  1308. });
  1309. }));
  1310. it("will be shown if received", mock.initConverse(function (_converse) {
  1311. test_utils.createContacts(_converse, 'current');
  1312. test_utils.openControlBox();
  1313. test_utils.openContactsPanel(_converse);
  1314. // TODO: only show paused state if the previous state was composing
  1315. // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
  1316. spyOn(_converse, 'emit');
  1317. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  1318. // <paused> state
  1319. var msg = $msg({
  1320. from: sender_jid,
  1321. to: _converse.connection.jid,
  1322. type: 'chat',
  1323. id: (new Date()).getTime()
  1324. }).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  1325. _converse.chatboxes.onMessage(msg);
  1326. expect(_converse.emit).toHaveBeenCalledWith('message', msg);
  1327. var chatboxview = _converse.chatboxviews.get(sender_jid);
  1328. var $events = chatboxview.$el.find('.chat-event');
  1329. expect($events.text()).toEqual(mock.cur_names[1] + ' has stopped typing');
  1330. }));
  1331. });
  1332. describe("An inactive notifciation", function () {
  1333. it("is sent if the user has stopped typing since 2 minutes", mock.initConverse(function (_converse) {
  1334. test_utils.createContacts(_converse, 'current');
  1335. test_utils.openControlBox();
  1336. test_utils.openContactsPanel(_converse);
  1337. // Make the timeouts shorter so that we can test
  1338. _converse.TIMEOUTS.PAUSED = 200;
  1339. _converse.TIMEOUTS.INACTIVE = 200;
  1340. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1341. test_utils.openChatBoxFor(_converse, contact_jid);
  1342. var view = _converse.chatboxviews.get(contact_jid);
  1343. runs(function () {
  1344. expect(view.model.get('chat_state')).toBe('active');
  1345. view.keyPressed({
  1346. target: view.$el.find('textarea.chat-textarea'),
  1347. keyCode: 1
  1348. });
  1349. expect(view.model.get('chat_state')).toBe('composing');
  1350. });
  1351. waits(250);
  1352. runs(function () {
  1353. expect(view.model.get('chat_state')).toBe('paused');
  1354. spyOn(_converse.connection, 'send');
  1355. });
  1356. waits(250);
  1357. runs(function () {
  1358. expect(view.model.get('chat_state')).toBe('inactive');
  1359. expect(_converse.connection.send).toHaveBeenCalled();
  1360. var $stanza = $(_converse.connection.send.argsForCall[0][0].tree());
  1361. expect($stanza.attr('to')).toBe(contact_jid);
  1362. expect($stanza.children().length).toBe(3);
  1363. expect($stanza.children().get(0).tagName).toBe('inactive');
  1364. expect($stanza.children().get(1).tagName).toBe('no-store');
  1365. expect($stanza.children().get(2).tagName).toBe('no-permanent-store');
  1366. });
  1367. }));
  1368. it("is sent when the user a minimizes a chat box", mock.initConverse(function (_converse) {
  1369. test_utils.createContacts(_converse, 'current');
  1370. test_utils.openControlBox();
  1371. test_utils.openContactsPanel(_converse);
  1372. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1373. test_utils.openChatBoxFor(_converse, contact_jid);
  1374. var view = _converse.chatboxviews.get(contact_jid);
  1375. spyOn(_converse.connection, 'send');
  1376. view.minimize();
  1377. expect(view.model.get('chat_state')).toBe('inactive');
  1378. expect(_converse.connection.send).toHaveBeenCalled();
  1379. var $stanza = $(_converse.connection.send.argsForCall[0][0].tree());
  1380. expect($stanza.attr('to')).toBe(contact_jid);
  1381. expect($stanza.children().get(0).tagName).toBe('inactive');
  1382. }));
  1383. it("is sent if the user closes a chat box", mock.initConverse(function (_converse) {
  1384. test_utils.createContacts(_converse, 'current');
  1385. test_utils.openControlBox();
  1386. test_utils.openContactsPanel(_converse);
  1387. waits(300); // ChatBox.show() is debounced for 250ms
  1388. runs(function () {
  1389. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1390. test_utils.openChatBoxFor(_converse, contact_jid);
  1391. var view = _converse.chatboxviews.get(contact_jid);
  1392. expect(view.model.get('chat_state')).toBe('active');
  1393. spyOn(_converse.connection, 'send');
  1394. view.close();
  1395. expect(view.model.get('chat_state')).toBe('inactive');
  1396. expect(_converse.connection.send).toHaveBeenCalled();
  1397. var $stanza = $(_converse.connection.send.argsForCall[0][0].tree());
  1398. expect($stanza.attr('to')).toBe(contact_jid);
  1399. expect($stanza.children().length).toBe(3);
  1400. expect($stanza.children().get(0).tagName).toBe('inactive');
  1401. expect($stanza.children().get(1).tagName).toBe('no-store');
  1402. expect($stanza.children().get(2).tagName).toBe('no-permanent-store');
  1403. });
  1404. }));
  1405. it("will clear any other chat status notifications if its received", mock.initConverse(function (_converse) {
  1406. test_utils.createContacts(_converse, 'current');
  1407. test_utils.openControlBox();
  1408. test_utils.openContactsPanel(_converse);
  1409. // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
  1410. spyOn(_converse, 'emit');
  1411. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  1412. test_utils.openChatBoxFor(_converse, sender_jid);
  1413. var view = _converse.chatboxviews.get(sender_jid);
  1414. expect(view.$el.find('.chat-event').length).toBe(0);
  1415. view.showStatusNotification(sender_jid+' is typing');
  1416. expect(view.$el.find('.chat-event').length).toBe(1);
  1417. var msg = $msg({
  1418. from: sender_jid,
  1419. to: _converse.connection.jid,
  1420. type: 'chat',
  1421. id: (new Date()).getTime()
  1422. }).c('body').c('inactive', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  1423. _converse.chatboxes.onMessage(msg);
  1424. expect(_converse.emit).toHaveBeenCalledWith('message', msg);
  1425. expect(view.$el.find('.chat-event').length).toBe(0);
  1426. }));
  1427. });
  1428. describe("A gone notifciation", function () {
  1429. it("will be shown if received", mock.initConverse(function (_converse) {
  1430. test_utils.createContacts(_converse, 'current');
  1431. test_utils.openControlBox();
  1432. test_utils.openContactsPanel(_converse);
  1433. spyOn(_converse, 'emit');
  1434. var sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
  1435. // <paused> state
  1436. var msg = $msg({
  1437. from: sender_jid,
  1438. to: _converse.connection.jid,
  1439. type: 'chat',
  1440. id: (new Date()).getTime()
  1441. }).c('body').c('gone', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  1442. _converse.chatboxes.onMessage(msg);
  1443. expect(_converse.emit).toHaveBeenCalledWith('message', msg);
  1444. var chatboxview = _converse.chatboxviews.get(sender_jid);
  1445. var $events = chatboxview.$el.find('.chat-event');
  1446. expect($events.text()).toEqual(mock.cur_names[1] + ' has gone away');
  1447. }));
  1448. });
  1449. });
  1450. });
  1451. describe("Special Messages", function () {
  1452. it("'/clear' can be used to clear messages in a conversation", mock.initConverse(function (_converse) {
  1453. test_utils.createContacts(_converse, 'current');
  1454. test_utils.openControlBox();
  1455. test_utils.openContactsPanel(_converse);
  1456. spyOn(_converse, 'emit');
  1457. var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1458. test_utils.openChatBoxFor(_converse, contact_jid);
  1459. var view = _converse.chatboxviews.get(contact_jid);
  1460. var message = 'This message is another sent from this chatbox';
  1461. // Lets make sure there is at least one message already
  1462. // (e.g for when this test is run on its own).
  1463. test_utils.sendMessage(view, message);
  1464. expect(view.model.messages.length > 0).toBeTruthy();
  1465. expect(view.model.messages.browserStorage.records.length > 0).toBeTruthy();
  1466. expect(_converse.emit).toHaveBeenCalledWith('messageSend', message);
  1467. message = '/clear';
  1468. spyOn(view, 'onMessageSubmitted').andCallThrough();
  1469. spyOn(view, 'clearMessages').andCallThrough();
  1470. spyOn(window, 'confirm').andCallFake(function () {
  1471. return true;
  1472. });
  1473. test_utils.sendMessage(view, message);
  1474. expect(view.onMessageSubmitted).toHaveBeenCalled();
  1475. expect(view.clearMessages).toHaveBeenCalled();
  1476. expect(window.confirm).toHaveBeenCalled();
  1477. expect(view.model.messages.length, 0); // The messages must be removed from the chatbox
  1478. expect(view.model.messages.browserStorage.records.length, 0); // And also from browserStorage
  1479. expect(_converse.emit.callCount, 1);
  1480. expect(_converse.emit.mostRecentCall.args, ['messageSend', message]);
  1481. }));
  1482. });
  1483. describe("A Message Counter", function () {
  1484. it("is incremented when the message is received and the window is not focused", mock.initConverse(function (_converse) {
  1485. test_utils.createContacts(_converse, 'current');
  1486. test_utils.openControlBox();
  1487. test_utils.openContactsPanel(_converse);
  1488. spyOn(_converse, 'emit');
  1489. expect(_converse.msg_counter).toBe(0);
  1490. spyOn(_converse, 'incrementMsgCounter').andCallThrough();
  1491. var previous_state = _converse.windowState;
  1492. var message = 'This message will increment the message counter';
  1493. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
  1494. msg = $msg({
  1495. from: sender_jid,
  1496. to: _converse.connection.jid,
  1497. type: 'chat',
  1498. id: (new Date()).getTime()
  1499. }).c('body').t(message).up()
  1500. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  1501. _converse.windowState = 'hidden';
  1502. _converse.chatboxes.onMessage(msg);
  1503. expect(_converse.incrementMsgCounter).toHaveBeenCalled();
  1504. expect(_converse.msg_counter).toBe(1);
  1505. expect(_converse.emit).toHaveBeenCalledWith('message', msg);
  1506. _converse.windowSate = previous_state;
  1507. }));
  1508. it("is cleared when the window is focused", mock.initConverse(function (_converse) {
  1509. test_utils.createContacts(_converse, 'current');
  1510. test_utils.openControlBox();
  1511. test_utils.openContactsPanel(_converse);
  1512. _converse.windowState = 'hidden';
  1513. spyOn(_converse, 'clearMsgCounter').andCallThrough();
  1514. runs(function () {
  1515. _converse.saveWindowState(null, 'focus');
  1516. _converse.saveWindowState(null, 'blur');
  1517. });
  1518. waits(50);
  1519. runs(function () {
  1520. expect(_converse.clearMsgCounter).toHaveBeenCalled();
  1521. });
  1522. }));
  1523. it("is not incremented when the message is received and the window is focused", mock.initConverse(function (_converse) {
  1524. test_utils.createContacts(_converse, 'current');
  1525. test_utils.openControlBox();
  1526. test_utils.openContactsPanel(_converse);
  1527. expect(_converse.msg_counter).toBe(0);
  1528. spyOn(_converse, 'incrementMsgCounter').andCallThrough();
  1529. _converse.saveWindowState(null, 'focus');
  1530. var message = 'This message will not increment the message counter';
  1531. var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
  1532. msg = $msg({
  1533. from: sender_jid,
  1534. to: _converse.connection.jid,
  1535. type: 'chat',
  1536. id: (new Date()).getTime()
  1537. }).c('body').t(message).up()
  1538. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  1539. _converse.chatboxes.onMessage(msg);
  1540. expect(_converse.incrementMsgCounter).not.toHaveBeenCalled();
  1541. expect(_converse.msg_counter).toBe(0);
  1542. }));
  1543. });
  1544. });
  1545. }));