MainSpec.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. (function (root, factory) {
  2. define([
  3. "converse"
  4. ], function (converse) {
  5. return factory(converse);
  6. }
  7. );
  8. } (this, function (converse) {
  9. return describe("Converse.js", $.proxy(function() {
  10. // Names from http://www.fakenamegenerator.com/
  11. var req_names = [
  12. 'Louw Spekman', 'Mohamad Stet', 'Dominik Beyer'
  13. ];
  14. var pend_names = [
  15. 'Suleyman van Beusichem', 'Nicole Diederich', 'Nanja van Yperen'
  16. ];
  17. var cur_names = [
  18. 'Max Frankfurter', 'Candice van der Knijff', 'Irini Vlastuin', 'Rinse Sommer', 'Annegreet Gomez',
  19. 'Robin Schook', 'Marcel Eberhardt', 'Simone Brauer', 'Asmaa Haakman', 'Felix Amsel',
  20. 'Lena Grunewald', 'Laura Grunewald', 'Mandy Seiler', 'Sven Bosch', 'Nuriye Cuypers'
  21. ];
  22. var chatroom_names = [
  23. 'Dyon van de Wege', 'Thomas Kalb', 'Dirk Theissen', 'Felix Hofmann', 'Ka Lek', 'Anne Ebersbacher'
  24. ];
  25. var num_contacts = req_names.length + pend_names.length + cur_names.length;
  26. mock_connection = {
  27. 'muc': {
  28. 'listRooms': function () {},
  29. 'join': function () {},
  30. 'leave': function () {}
  31. },
  32. 'jid': 'dummy@localhost',
  33. 'addHandler': function (handler, ns, name, type, id, from, options) {
  34. return function () {};
  35. },
  36. 'send': function () {},
  37. 'roster': {
  38. 'add': function () {},
  39. 'authorize': function () {},
  40. 'unauthorize': function () {},
  41. 'get': function () {},
  42. 'subscribe': function () {},
  43. 'registerCallback': function () {}
  44. },
  45. 'vcard': {
  46. 'get': function (callback, jid) {
  47. var name = jid.split('@')[0].replace('.', ' ').split(' ');
  48. var firstname = name[0].charAt(0).toUpperCase()+name[0].slice(1);
  49. var lastname = name[1].charAt(0).toUpperCase()+name[1].slice(1);
  50. var fullname = firstname+' '+lastname;
  51. var vcard = $iq().c('vCard').c('FN').t(fullname);
  52. callback(vcard.tree());
  53. }
  54. },
  55. 'disco': {
  56. 'info': function () {},
  57. 'items': function () {}
  58. }
  59. };
  60. // Clear localStorage
  61. window.localStorage.clear();
  62. this.initialize({
  63. prebind: false,
  64. xhr_user_search: false,
  65. auto_subscribe: false,
  66. animate: false
  67. });
  68. this.onConnected(mock_connection);
  69. // Variable declarations for specs
  70. var open_controlbox;
  71. describe("The Control Box", $.proxy(function () {
  72. it("is not shown by default", $.proxy(function () {
  73. expect(this.rosterview.$el.is(':visible')).toEqual(false);
  74. }, converse));
  75. open_controlbox = $.proxy(function () {
  76. // This spec will only pass if the controlbox is not currently
  77. // open yet.
  78. expect($("div#controlbox").is(':visible')).toBe(false);
  79. spyOn(this, 'toggleControlBox').andCallThrough();
  80. $('.toggle-online-users').click();
  81. expect(this.toggleControlBox).toHaveBeenCalled();
  82. expect($("div#controlbox").is(':visible')).toBe(true);
  83. }, converse);
  84. it("can be opened by clicking a DOM element with class 'toggle-online-users'", open_controlbox);
  85. describe("The Status Widget", $.proxy(function () {
  86. it("can be used to set the current user's chat status", $.proxy(function () {
  87. var view = this.xmppstatusview;
  88. spyOn(view, 'toggleOptions').andCallThrough();
  89. spyOn(view, 'setStatus').andCallThrough();
  90. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  91. runs(function () {
  92. view.$el.find('a.choose-xmpp-status').click();
  93. expect(view.toggleOptions).toHaveBeenCalled();
  94. expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(false);
  95. });
  96. waits(250);
  97. runs(function () {
  98. spyOn(view, 'updateStatusUI').andCallThrough();
  99. view.initialize(); // Rebind events for spy
  100. view.$el.find('.dropdown dd ul li a').first().click();
  101. expect(view.setStatus).toHaveBeenCalled();
  102. });
  103. waits(250);
  104. runs($.proxy(function () {
  105. expect(view.updateStatusUI).toHaveBeenCalled();
  106. expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(true);
  107. expect(view.$el.find('a.choose-xmpp-status span.value').text()).toBe('I am online');
  108. }, converse));
  109. }, converse));
  110. it("can be used to set a custom status message", $.proxy(function () {
  111. var view = this.xmppstatusview;
  112. spyOn(view, 'setStatusMessage').andCallThrough();
  113. spyOn(view, 'renderStatusChangeForm').andCallThrough();
  114. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  115. view.$el.find('a.change-xmpp-status-message').click();
  116. expect(view.renderStatusChangeForm).toHaveBeenCalled();
  117. // The async testing here is used only to provide time for
  118. // visual feedback
  119. var msg = 'I am happy';
  120. runs (function () {
  121. view.$el.find('form input.custom-xmpp-status').val(msg);
  122. });
  123. waits(250);
  124. runs (function () {
  125. view.$el.find('form#set-custom-xmpp-status').submit();
  126. expect(view.setStatusMessage).toHaveBeenCalled();
  127. expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(true);
  128. expect(view.$el.find('a.choose-xmpp-status span.value').text()).toBe(msg);
  129. });
  130. }, converse));
  131. }, converse));
  132. }, converse));
  133. describe("The Contacts Roster", $.proxy(function () {
  134. describe("Pending Contacts", $.proxy(function () {
  135. it("do not have a heading if there aren't any", $.proxy(function () {
  136. expect(this.rosterview.$el.find('dt#pending-xmpp-contacts').css('display')).toEqual('none');
  137. }, converse));
  138. it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
  139. var i, t, is_last;
  140. spyOn(this.rosterview, 'render').andCallThrough();
  141. for (i=0; i<pend_names.length; i++) {
  142. is_last = i==(pend_names.length-1);
  143. this.roster.create({
  144. jid: pend_names[i].replace(' ','.').toLowerCase() + '@localhost',
  145. subscription: 'none',
  146. ask: 'subscribe',
  147. fullname: pend_names[i],
  148. is_last: is_last
  149. });
  150. // For performance reasons, the roster should only be shown once
  151. // the last contact has been added.
  152. if (is_last) {
  153. expect(this.rosterview.$el.is(':visible')).toEqual(true);
  154. } else {
  155. expect(this.rosterview.$el.is(':visible')).toEqual(false);
  156. }
  157. expect(this.rosterview.render).toHaveBeenCalled();
  158. // Check that they are sorted alphabetically
  159. t = this.rosterview.$el.find('dt#pending-xmpp-contacts').siblings('dd.pending-xmpp-contact').text();
  160. expect(t).toEqual(pend_names.slice(0,i+1).sort().join(''));
  161. }
  162. }, converse));
  163. it("will have their own heading once they have been added", $.proxy(function () {
  164. expect(this.rosterview.$el.find('dt#pending-xmpp-contacts').css('display')).toEqual('block');
  165. }, converse));
  166. }, converse));
  167. describe("Existing Contacts", $.proxy(function () {
  168. it("do not have a heading if there aren't any", $.proxy(function () {
  169. expect(this.rosterview.$el.find('dt#xmpp-contacts').css('display')).toEqual('none');
  170. }, converse));
  171. it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
  172. var i, t;
  173. spyOn(this.rosterview, 'render').andCallThrough();
  174. for (i=0; i<cur_names.length; i++) {
  175. this.roster.create({
  176. jid: cur_names[i].replace(' ','.').toLowerCase() + '@localhost',
  177. subscription: 'both',
  178. ask: null,
  179. fullname: cur_names[i],
  180. is_last: i==(cur_names.length-1)
  181. });
  182. expect(this.rosterview.render).toHaveBeenCalled();
  183. // Check that they are sorted alphabetically
  184. t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.offline').find('a.open-chat').text();
  185. expect(t).toEqual(cur_names.slice(0,i+1).sort().join(''));
  186. }
  187. }, converse));
  188. it("will have their own heading once they have been added", $.proxy(function () {
  189. expect(this.rosterview.$el.find('dt#xmpp-contacts').css('display')).toEqual('block');
  190. }, converse));
  191. it("can change their status to online and be sorted alphabetically", $.proxy(function () {
  192. var item, view, jid, t;
  193. spyOn(this.rosterview, 'render').andCallThrough();
  194. for (i=0; i<3; i++) {
  195. jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
  196. view = this.rosterview.rosteritemviews[jid];
  197. spyOn(view, 'render').andCallThrough();
  198. item = view.model;
  199. item.set('chat_status', 'online');
  200. expect(view.render).toHaveBeenCalled();
  201. expect(this.rosterview.render).toHaveBeenCalled();
  202. // Check that they are sorted alphabetically
  203. t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.online').find('a.open-chat').text();
  204. expect(t).toEqual(cur_names.slice(0,i+1).sort().join(''));
  205. }
  206. }, converse));
  207. it("can change their status to busy and be sorted alphabetically", $.proxy(function () {
  208. var item, view, jid, t;
  209. spyOn(this.rosterview, 'render').andCallThrough();
  210. for (i=3; i<6; i++) {
  211. jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
  212. view = this.rosterview.rosteritemviews[jid];
  213. spyOn(view, 'render').andCallThrough();
  214. item = view.model;
  215. item.set('chat_status', 'dnd');
  216. expect(view.render).toHaveBeenCalled();
  217. expect(this.rosterview.render).toHaveBeenCalled();
  218. // Check that they are sorted alphabetically
  219. t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.dnd').find('a.open-chat').text();
  220. expect(t).toEqual(cur_names.slice(3,i+1).sort().join(''));
  221. }
  222. }, converse));
  223. it("can change their status to away and be sorted alphabetically", $.proxy(function () {
  224. var item, view, jid, t;
  225. spyOn(this.rosterview, 'render').andCallThrough();
  226. for (i=6; i<9; i++) {
  227. jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
  228. view = this.rosterview.rosteritemviews[jid];
  229. spyOn(view, 'render').andCallThrough();
  230. item = view.model;
  231. item.set('chat_status', 'away');
  232. expect(view.render).toHaveBeenCalled();
  233. expect(this.rosterview.render).toHaveBeenCalled();
  234. // Check that they are sorted alphabetically
  235. t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.away').find('a.open-chat').text();
  236. expect(t).toEqual(cur_names.slice(6,i+1).sort().join(''));
  237. }
  238. }, converse));
  239. it("can change their status to unavailable and be sorted alphabetically", $.proxy(function () {
  240. var item, view, jid, t;
  241. spyOn(this.rosterview, 'render').andCallThrough();
  242. for (i=9; i<12; i++) {
  243. jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
  244. view = this.rosterview.rosteritemviews[jid];
  245. spyOn(view, 'render').andCallThrough();
  246. item = view.model;
  247. item.set('chat_status', 'unavailable');
  248. expect(view.render).toHaveBeenCalled();
  249. expect(this.rosterview.render).toHaveBeenCalled();
  250. // Check that they are sorted alphabetically
  251. t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.unavailable').find('a.open-chat').text();
  252. expect(t).toEqual(cur_names.slice(9, i+1).sort().join(''));
  253. }
  254. }, converse));
  255. it("are ordered according to status: online, busy, away, unavailable, offline", $.proxy(function () {
  256. var contacts = this.rosterview.$el.find('dd.current-xmpp-contact');
  257. var i;
  258. // The first five contacts are online.
  259. for (i=0; i<3; i++) {
  260. expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('online');
  261. }
  262. // The next five are busy
  263. for (i=3; i<6; i++) {
  264. expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('dnd');
  265. }
  266. // The next five are away
  267. for (i=6; i<9; i++) {
  268. expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('away');
  269. }
  270. // The next five are unavailable
  271. for (i=9; i<12; i++) {
  272. expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('unavailable');
  273. }
  274. // The next 20 are offline
  275. for (i=12; i<cur_names.length; i++) {
  276. expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('offline');
  277. }
  278. }, converse));
  279. }, converse));
  280. describe("Requesting Contacts", $.proxy(function () {
  281. // by default the dts are hidden from css class and only later they will be hidden
  282. // by jQuery therefore for the first check we will see if visible instead of none
  283. it("do not have a heading if there aren't any", $.proxy(function () {
  284. expect(this.rosterview.$el.find('dt#xmpp-contact-requests').is(':visible')).toEqual(false);
  285. }, converse));
  286. it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
  287. var i, t;
  288. spyOn(this.rosterview, 'render').andCallThrough();
  289. spyOn(this, 'showControlBox').andCallThrough();
  290. for (i=0; i<req_names.length; i++) {
  291. this.roster.create({
  292. jid: req_names[i].replace(' ','.').toLowerCase() + '@localhost',
  293. subscription: 'none',
  294. ask: 'request',
  295. fullname: req_names[i],
  296. is_last: i==(req_names.length-1)
  297. });
  298. expect(this.rosterview.render).toHaveBeenCalled();
  299. // Check that they are sorted alphabetically
  300. t = this.rosterview.$el.find('dt#xmpp-contact-requests').siblings('dd.requesting-xmpp-contact').text().replace(/AcceptDecline/g, '');
  301. expect(t).toEqual(req_names.slice(0,i+1).sort().join(''));
  302. // When a requesting contact is added, the controlbox must
  303. // be opened.
  304. expect(this.showControlBox).toHaveBeenCalled();
  305. }
  306. }, converse));
  307. it("will have their own heading once they have been added", $.proxy(function () {
  308. expect(this.rosterview.$el.find('dt#xmpp-contact-requests').css('display')).toEqual('block');
  309. }, converse));
  310. it("can have their requests accepted by the user", $.proxy(function () {
  311. // TODO: Testing can be more thorough here, the user is
  312. // actually not accepted/authorized because of
  313. // mock_connection.
  314. var jid = req_names.sort()[0].replace(' ','.').toLowerCase() + '@localhost';
  315. var view = this.rosterview.rosteritemviews[jid];
  316. spyOn(this.connection.roster, 'authorize');
  317. spyOn(view, 'acceptRequest').andCallThrough();
  318. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  319. var accept_button = view.$el.find('.accept-xmpp-request');
  320. accept_button.click();
  321. expect(view.acceptRequest).toHaveBeenCalled();
  322. expect(this.connection.roster.authorize).toHaveBeenCalled();
  323. }, converse));
  324. it("can have their requests denied by the user", $.proxy(function () {
  325. var jid = req_names.sort()[1].replace(' ','.').toLowerCase() + '@localhost';
  326. var view = this.rosterview.rosteritemviews[jid];
  327. spyOn(this.connection.roster, 'unauthorize');
  328. spyOn(this.rosterview, 'removeRosterItem').andCallThrough();
  329. spyOn(view, 'declineRequest').andCallThrough();
  330. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  331. var accept_button = view.$el.find('.decline-xmpp-request');
  332. accept_button.click();
  333. expect(view.declineRequest).toHaveBeenCalled();
  334. expect(this.rosterview.removeRosterItem).toHaveBeenCalled();
  335. expect(this.connection.roster.unauthorize).toHaveBeenCalled();
  336. // There should now be one less contact
  337. expect(this.roster.length).toEqual(num_contacts-1);
  338. }, converse));
  339. }, converse));
  340. describe("All Contacts", $.proxy(function () {
  341. it("are saved to, and can be retrieved from, localStorage", $.proxy(function () {
  342. var new_attrs, old_attrs, attrs, old_roster;
  343. // One contact was declined, so we have 1 less contact then originally
  344. expect(this.roster.length).toEqual(num_contacts-1);
  345. new_roster = new this.RosterItems();
  346. // Roster items are yet to be fetched from localStorage
  347. expect(new_roster.length).toEqual(0);
  348. new_roster.localStorage = new Backbone.LocalStorage(
  349. hex_sha1('converse.rosteritems-dummy@localhost'));
  350. new_roster.fetch();
  351. expect(this.roster.length).toEqual(num_contacts-1);
  352. // Check that the roster items retrieved from localStorage
  353. // have the same attributes values as the original ones.
  354. attrs = ['jid', 'fullname', 'subscription', 'ask'];
  355. for (i=0; i<attrs.length; i++) {
  356. new_attrs = _.pluck(_.pluck(new_roster.models, 'attributes'), attrs[i]);
  357. old_attrs = _.pluck(_.pluck(this.roster.models, 'attributes'), attrs[i]);
  358. // Roster items in storage are not necessarily sorted,
  359. // so we have to sort them here to do a proper
  360. // comparison
  361. expect(_.isEqual(new_attrs.sort(), old_attrs.sort())).toEqual(true);
  362. }
  363. this.rosterview.render();
  364. }, converse));
  365. afterEach($.proxy(function () {
  366. // Contacts retrieved from localStorage have chat_status of
  367. // "offline".
  368. // In the next test suite, we need some online contacts, so
  369. // we make some online now
  370. for (i=0; i<5; i++) {
  371. jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
  372. view = this.rosterview.rosteritemviews[jid];
  373. view.model.set('chat_status', 'online');
  374. }
  375. }, converse));
  376. }, converse));
  377. }, converse));
  378. describe("The 'Add Contact' widget", $.proxy(function () {
  379. it("opens up an add form when you click on it", $.proxy(function () {
  380. var panel = this.chatboxesview.views.controlbox.contactspanel;
  381. spyOn(panel, 'toggleContactForm').andCallThrough();
  382. panel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  383. panel.$el.find('a.toggle-xmpp-contact-form').click();
  384. expect(panel.toggleContactForm).toHaveBeenCalled();
  385. // XXX: Awaiting more tests, close it again for now...
  386. panel.$el.find('a.toggle-xmpp-contact-form').click();
  387. }, converse));
  388. }, converse));
  389. describe("A Chatbox", $.proxy(function () {
  390. it("is created when you click on a roster item", $.proxy(function () {
  391. var i, $el, click, jid, view;
  392. // showControlBox was called earlier, so the controlbox is
  393. // visible, but no other chat boxes have been created.
  394. expect(this.chatboxes.length).toEqual(1);
  395. var online_contacts = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.online').find('a.open-chat');
  396. for (i=0; i<online_contacts.length; i++) {
  397. $el = $(online_contacts[i]);
  398. jid = $el.text().replace(' ','.').toLowerCase() + '@localhost';
  399. view = this.rosterview.rosteritemviews[jid];
  400. spyOn(view, 'openChat').andCallThrough();
  401. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  402. $el.click();
  403. expect(view.openChat).toHaveBeenCalled();
  404. expect(this.chatboxes.length).toEqual(i+2);
  405. }
  406. }, converse));
  407. it("can be saved to, and retrieved from, localStorage", $.proxy(function () {
  408. // We instantiate a new ChatBoxes collection, which by default
  409. // will be empty.
  410. var newchatboxes = new this.ChatBoxes();
  411. expect(newchatboxes.length).toEqual(0);
  412. // The chatboxes will then be fetched from localStorage inside the
  413. // onConnected method
  414. newchatboxes.onConnected();
  415. expect(newchatboxes.length).toEqual(6);
  416. // Check that the chatboxes items retrieved from localStorage
  417. // have the same attributes values as the original ones.
  418. attrs = ['id', 'box_id', 'visible'];
  419. for (i=0; i<attrs.length; i++) {
  420. new_attrs = _.pluck(_.pluck(newchatboxes.models, 'attributes'), attrs[i]);
  421. old_attrs = _.pluck(_.pluck(this.chatboxes.models, 'attributes'), attrs[i]);
  422. expect(_.isEqual(new_attrs, old_attrs)).toEqual(true);
  423. }
  424. this.rosterview.render();
  425. }, converse));
  426. it("can be closed again by clicking a DOM element with class 'close-chatbox-button'", $.proxy(function () {
  427. var chatbox, view, $el,
  428. num_open_chats = this.chatboxes.length;
  429. for (i=0; i<num_open_chats; i++) {
  430. chatbox = this.chatboxes.models[0];
  431. view = this.chatboxesview.views[chatbox.get('id')];
  432. spyOn(view, 'closeChat').andCallThrough();
  433. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  434. view.$el.find('.close-chatbox-button').click();
  435. expect(view.closeChat).toHaveBeenCalled();
  436. }
  437. }, converse));
  438. it("will be removed from localStorage when closed", $.proxy(function () {
  439. var newchatboxes = new this.ChatBoxes();
  440. expect(newchatboxes.length).toEqual(0);
  441. // onConnected will fetch chatboxes in localStorage, but
  442. // because there aren't any open chatboxes, there won't be any
  443. // in localStorage either.
  444. newchatboxes.onConnected();
  445. expect(newchatboxes.length).toEqual(0);
  446. // Lets open the controlbox again, purely for visual feedback
  447. open_controlbox();
  448. }, converse));
  449. describe("A Chat Message", $.proxy(function () {
  450. it("can be received which will open a chatbox and be displayed inside it", $.proxy(function () {
  451. var message = 'This is a received message';
  452. var sender_jid = cur_names[0].replace(' ','.').toLowerCase() + '@localhost';
  453. msg = $msg({
  454. from: sender_jid,
  455. to: this.connection.jid,
  456. type: 'chat',
  457. id: (new Date()).getTime()
  458. }).c('body').t(message).up()
  459. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  460. spyOn(this, 'getVCard').andCallThrough();
  461. // We don't already have an open chatbox for this user
  462. expect(this.chatboxes.get(sender_jid)).not.toBeDefined();
  463. runs($.proxy(function () {
  464. // messageReceived is a handler for received XMPP
  465. // messages
  466. this.chatboxes.messageReceived(msg);
  467. }, converse));
  468. waits(500);
  469. runs($.proxy(function () {
  470. // Since we didn't already have an open chatbox, one
  471. // will asynchronously created inside a callback to
  472. // getVCard
  473. expect(this.getVCard).toHaveBeenCalled();
  474. // Check that the chatbox and its view now exist
  475. var chatbox = this.chatboxes.get(sender_jid);
  476. var chatboxview = this.chatboxesview.views[sender_jid];
  477. expect(chatbox).toBeDefined();
  478. expect(chatboxview).toBeDefined();
  479. // Check that the message was received and check the
  480. // message parameters
  481. expect(chatbox.messages.length).toEqual(1);
  482. var msg_obj = chatbox.messages.models[0];
  483. expect(msg_obj.get('message')).toEqual(message);
  484. // XXX: This is stupid, fullname is actually only the
  485. // users first name
  486. expect(msg_obj.get('fullname')).toEqual(cur_names[0].split(' ')[0]);
  487. expect(msg_obj.get('sender')).toEqual('them');
  488. expect(msg_obj.get('delayed')).toEqual(false);
  489. // Now check that the message appears inside the
  490. // chatbox in the DOM
  491. var txt = chatboxview.$el.find('.chat-content').find('.chat-message').find('.chat-message-content').text();
  492. expect(txt).toEqual(message);
  493. }, converse));
  494. }, converse));
  495. it("can be sent from a chatbox, and will appear inside it", $.proxy(function () {
  496. var contact_jid = cur_names[0].replace(' ','.').toLowerCase() + '@localhost';
  497. var view = this.chatboxesview.views[contact_jid];
  498. var message = 'This message is sent from this chatbox';
  499. spyOn(view, 'sendMessage').andCallThrough();
  500. view.$el.find('.chat-textarea').text(message);
  501. view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13}));
  502. expect(view.sendMessage).toHaveBeenCalled();
  503. expect(view.model.messages.length, 2);
  504. var txt = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content').text();
  505. expect(txt).toEqual(message);
  506. }, converse));
  507. }, converse));
  508. }, converse));
  509. describe("A Message Counter", $.proxy(function () {
  510. beforeEach($.proxy(function () {
  511. converse.clearMsgCounter();
  512. }, converse));
  513. it("is incremented when the message is received and the window is not focused", $.proxy(function () {
  514. expect(this.msg_counter).toBe(0);
  515. spyOn(converse, 'incrementMsgCounter').andCallThrough();
  516. $(window).trigger('blur');
  517. var message = 'This message will increment the message counter';
  518. var sender_jid = cur_names[0].replace(' ','.').toLowerCase() + '@localhost';
  519. msg = $msg({
  520. from: sender_jid,
  521. to: this.connection.jid,
  522. type: 'chat',
  523. id: (new Date()).getTime()
  524. }).c('body').t(message).up()
  525. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  526. this.chatboxes.messageReceived(msg);
  527. expect(converse.incrementMsgCounter).toHaveBeenCalled();
  528. expect(this.msg_counter).toBe(1);
  529. }, converse));
  530. it("is cleared when the window is focused", $.proxy(function () {
  531. spyOn(converse, 'clearMsgCounter').andCallThrough();
  532. $(window).trigger('focus');
  533. expect(converse.clearMsgCounter).toHaveBeenCalled();
  534. }, converse));
  535. it("is not incremented when the message is received and the window is focused", $.proxy(function () {
  536. expect(this.msg_counter).toBe(0);
  537. spyOn(converse, 'incrementMsgCounter').andCallThrough();
  538. $(window).trigger('focus');
  539. var message = 'This message will not increment the message counter';
  540. var sender_jid = cur_names[0].replace(' ','.').toLowerCase() + '@localhost';
  541. msg = $msg({
  542. from: sender_jid,
  543. to: this.connection.jid,
  544. type: 'chat',
  545. id: (new Date()).getTime()
  546. }).c('body').t(message).up()
  547. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  548. this.chatboxes.messageReceived(msg);
  549. expect(converse.incrementMsgCounter).not.toHaveBeenCalled();
  550. expect(this.msg_counter).toBe(0);
  551. }, converse));
  552. }, converse));
  553. describe("The Controlbox Tabs", $.proxy(function () {
  554. beforeEach($.proxy(function () {
  555. // Close any remaining open chatboxes
  556. var i, chatbox, num_chatboxes = this.chatboxes.models.length;
  557. for (i=0; i<num_chatboxes; i++) {
  558. chatbox = this.chatboxes.models[i];
  559. if (chatbox.get('id') != 'controlbox') {
  560. this.chatboxesview.views[chatbox.get('id')].closeChat();
  561. }
  562. }
  563. }, converse));
  564. it("contains two tabs, 'Contacts' and 'ChatRooms'", $.proxy(function () {
  565. var cbview = this.chatboxesview.views.controlbox;
  566. var $panels = cbview.$el.find('#controlbox-panes');
  567. expect($panels.children().length).toBe(2);
  568. expect($panels.children().first().attr('id')).toBe('users');
  569. expect($panels.children().first().is(':visible')).toBe(true);
  570. expect($panels.children().last().attr('id')).toBe('chatrooms');
  571. expect($panels.children().last().is(':visible')).toBe(false);
  572. }, converse));
  573. describe("The Chatrooms Panel", $.proxy(function () {
  574. it("is opened by clicking the 'Chatrooms' tab", $.proxy(function () {
  575. var cbview = this.chatboxesview.views.controlbox;
  576. var $tabs = cbview.$el.find('#controlbox-tabs');
  577. var $panels = cbview.$el.find('#controlbox-panes');
  578. var $contacts = $panels.children().first();
  579. var $chatrooms = $panels.children().last();
  580. spyOn(cbview, 'switchTab').andCallThrough();
  581. cbview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  582. runs(function () {
  583. $tabs.find('li').last().find('a').click(); // Clicks the chatrooms tab
  584. });
  585. waits(250);
  586. runs(function () {
  587. expect($contacts.is(':visible')).toBe(false);
  588. expect($chatrooms.is(':visible')).toBe(true);
  589. expect(cbview.switchTab).toHaveBeenCalled();
  590. });
  591. }, converse));
  592. it("contains a form through which a new chatroom can be created", $.proxy(function () {
  593. var roomspanel = this.chatboxesview.views.controlbox.roomspanel;
  594. var $input = roomspanel.$el.find('input.new-chatroom-name');
  595. var $server = roomspanel.$el.find('input.new-chatroom-server');
  596. expect($input.length).toBe(1);
  597. expect($server.length).toBe(1);
  598. expect($('.chatroom').length).toBe(0); // There shouldn't be any chatrooms open currently
  599. spyOn(roomspanel, 'createChatRoom').andCallThrough();
  600. roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  601. runs(function () {
  602. $input.val('Lounge');
  603. $server.val('muc.localhost');
  604. });
  605. waits('250');
  606. runs(function () {
  607. roomspanel.$el.find('form').submit();
  608. expect(roomspanel.createChatRoom).toHaveBeenCalled();
  609. });
  610. waits('250');
  611. runs($.proxy(function () {
  612. expect($('.chatroom').length).toBe(1); // There should now be an open chatroom
  613. }, converse));
  614. }, converse));
  615. }, converse));
  616. }, converse));
  617. describe("A Chat Room", $.proxy(function () {
  618. it("shows users currently present in the room", $.proxy(function () {
  619. var chatroomview = this.chatboxesview.views['lounge@muc.localhost'];
  620. var $participant_list = chatroomview.$el.find('.participant-list');
  621. var roster = {}, room = {}, i;
  622. for (i=0; i<chatroom_names.length; i++) {
  623. roster[chatroom_names[i]] = {};
  624. chatroomview.onChatRoomRoster(roster, room);
  625. expect($participant_list.find('li').length).toBe(1+i);
  626. expect($($participant_list.find('li')[i]).text()).toBe(chatroom_names[i]);
  627. }
  628. roster[converse.bare_jid] = {};
  629. chatroomview.onChatRoomRoster(roster, room);
  630. }, converse));
  631. it("can be saved to, and retrieved from, localStorage", $.proxy(function () {
  632. // We instantiate a new ChatBoxes collection, which by default
  633. // will be empty.
  634. var newchatboxes = new this.ChatBoxes();
  635. expect(newchatboxes.length).toEqual(0);
  636. // The chatboxes will then be fetched from localStorage inside the
  637. // onConnected method
  638. newchatboxes.onConnected();
  639. expect(newchatboxes.length).toEqual(2); // controlbox is also included
  640. // Check that the chatrooms retrieved from localStorage
  641. // have the same attributes values as the original ones.
  642. attrs = ['id', 'box_id', 'visible'];
  643. for (i=0; i<attrs.length; i++) {
  644. new_attrs = _.pluck(_.pluck(newchatboxes.models, 'attributes'), attrs[i]);
  645. old_attrs = _.pluck(_.pluck(this.chatboxes.models, 'attributes'), attrs[i]);
  646. expect(_.isEqual(new_attrs, old_attrs)).toEqual(true);
  647. }
  648. this.rosterview.render();
  649. }, converse));
  650. it("can be closed again by clicking a DOM element with class 'close-chatbox-button'", $.proxy(function () {
  651. var view = this.chatboxesview.views['lounge@muc.localhost'], chatroom = view.model, $el;
  652. spyOn(view, 'closeChat').andCallThrough();
  653. spyOn(converse.connection.muc, 'leave');
  654. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  655. view.$el.find('.close-chatbox-button').click();
  656. expect(view.closeChat).toHaveBeenCalled();
  657. expect(converse.connection.muc.leave).toHaveBeenCalled();
  658. }, converse));
  659. }, converse));
  660. }, converse));
  661. }));