chatbox.js 129 KB

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