chatbox.js 109 KB

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