MainSpec.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. (function (root, factory) {
  2. define([
  3. "converse"
  4. ], function (xmppchat) {
  5. return factory(xmppchat);
  6. }
  7. );
  8. } (this, function (xmppchat) {
  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 num_contacts = req_names.length + pend_names.length + cur_names.length;
  23. this.bare_jid = 'dummy@localhost';
  24. mock_connection = {
  25. 'muc': {
  26. 'listRooms': function () {}
  27. },
  28. 'jid': this.bare_jid,
  29. 'addHandler': function (handler, ns, name, type, id, from, options) {
  30. return function () {};
  31. },
  32. 'send': function () {},
  33. 'roster': {
  34. 'add': function () {},
  35. 'authorize': function () {},
  36. 'unauthorize': function () {},
  37. 'get': function () {},
  38. 'subscribe': function () {},
  39. 'registerCallback': function () {}
  40. },
  41. 'vcard': {
  42. 'get': function (callback, jid) {
  43. var name = jid.split('@')[0].replace('.', ' ').split(' ');
  44. var firstname = name[0].charAt(0).toUpperCase()+name[0].slice(1);
  45. var lastname = name[1].charAt(0).toUpperCase()+name[1].slice(1);
  46. var fullname = firstname+' '+lastname;
  47. var vcard = $iq().c('vCard').c('FN').t(fullname);
  48. callback(vcard.tree());
  49. }
  50. }
  51. };
  52. // Clear localStorage
  53. window.localStorage.removeItem(
  54. hex_sha1('converse.rosteritems-'+this.bare_jid));
  55. window.localStorage.removeItem(
  56. hex_sha1('converse.chatboxes-'+this.bare_jid));
  57. window.localStorage.removeItem(
  58. hex_sha1('converse.xmppstatus-'+this.bare_jid));
  59. window.localStorage.removeItem(
  60. hex_sha1('converse.messages'+cur_names[0].replace(' ','.').toLowerCase() + '@localhost'));
  61. this.prebind = true;
  62. this.onConnected(mock_connection);
  63. this.animate = false; // don't use animations
  64. describe("The Contacts Roster", $.proxy(function () {
  65. it("is not shown by default", $.proxy(function () {
  66. expect(this.rosterview.$el.is(':visible')).toEqual(false);
  67. }, xmppchat));
  68. it("can be opened by clicking a DOM element with id 'toggle-online-users'", $.proxy(function () {
  69. spyOn(this, 'toggleControlBox').andCallThrough();
  70. $('#toggle-online-users').click();
  71. expect(this.toggleControlBox).toHaveBeenCalled();
  72. }, xmppchat));
  73. describe("Pending Contacts", $.proxy(function () {
  74. it("do not have a heading if there aren't any", $.proxy(function () {
  75. expect(this.rosterview.$el.find('dt#pending-xmpp-contacts').css('display')).toEqual('none');
  76. }, xmppchat));
  77. it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
  78. var i, t, is_last;
  79. spyOn(this.rosterview, 'render').andCallThrough();
  80. for (i=0; i<pend_names.length; i++) {
  81. is_last = i==(pend_names.length-1);
  82. this.roster.create({
  83. jid: pend_names[i].replace(' ','.').toLowerCase() + '@localhost',
  84. subscription: 'none',
  85. ask: 'subscribe',
  86. fullname: pend_names[i],
  87. is_last: is_last
  88. });
  89. // For performance reasons, the roster should only be shown once
  90. // the last contact has been added.
  91. if (is_last) {
  92. expect(this.rosterview.$el.is(':visible')).toEqual(true);
  93. } else {
  94. expect(this.rosterview.$el.is(':visible')).toEqual(false);
  95. }
  96. expect(this.rosterview.render).toHaveBeenCalled();
  97. // Check that they are sorted alphabetically
  98. t = this.rosterview.$el.find('dt#pending-xmpp-contacts').siblings('dd.pending-xmpp-contact').text();
  99. expect(t).toEqual(pend_names.slice(0,i+1).sort().join(''));
  100. }
  101. }, xmppchat));
  102. it("will have their own heading once they have been added", $.proxy(function () {
  103. expect(this.rosterview.$el.find('dt#pending-xmpp-contacts').css('display')).toEqual('block');
  104. }, xmppchat));
  105. }, xmppchat));
  106. describe("Existing Contacts", $.proxy(function () {
  107. it("do not have a heading if there aren't any", $.proxy(function () {
  108. expect(this.rosterview.$el.find('dt#xmpp-contacts').css('display')).toEqual('none');
  109. }, xmppchat));
  110. it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
  111. var i, t;
  112. spyOn(this.rosterview, 'render').andCallThrough();
  113. for (i=0; i<cur_names.length; i++) {
  114. this.roster.create({
  115. jid: cur_names[i].replace(' ','.').toLowerCase() + '@localhost',
  116. subscription: 'both',
  117. ask: null,
  118. fullname: cur_names[i],
  119. is_last: i==(cur_names.length-1)
  120. });
  121. expect(this.rosterview.render).toHaveBeenCalled();
  122. // Check that they are sorted alphabetically
  123. t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.offline').find('a.open-chat').text();
  124. expect(t).toEqual(cur_names.slice(0,i+1).sort().join(''));
  125. }
  126. }, xmppchat));
  127. it("will have their own heading once they have been added", $.proxy(function () {
  128. expect(this.rosterview.$el.find('dt#xmpp-contacts').css('display')).toEqual('block');
  129. }, xmppchat));
  130. it("can change their status to online and be sorted alphabetically", $.proxy(function () {
  131. var item, view, jid, t;
  132. spyOn(this.rosterview, 'render').andCallThrough();
  133. for (i=0; i<3; i++) {
  134. jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
  135. view = this.rosterview.rosteritemviews[jid];
  136. spyOn(view, 'render').andCallThrough();
  137. item = view.model;
  138. item.set('chat_status', 'online');
  139. expect(view.render).toHaveBeenCalled();
  140. expect(this.rosterview.render).toHaveBeenCalled();
  141. // Check that they are sorted alphabetically
  142. t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.online').find('a.open-chat').text();
  143. expect(t).toEqual(cur_names.slice(0,i+1).sort().join(''));
  144. }
  145. }, xmppchat));
  146. it("can change their status to busy and be sorted alphabetically", $.proxy(function () {
  147. var item, view, jid, t;
  148. spyOn(this.rosterview, 'render').andCallThrough();
  149. for (i=3; i<6; i++) {
  150. jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
  151. view = this.rosterview.rosteritemviews[jid];
  152. spyOn(view, 'render').andCallThrough();
  153. item = view.model;
  154. item.set('chat_status', 'dnd');
  155. expect(view.render).toHaveBeenCalled();
  156. expect(this.rosterview.render).toHaveBeenCalled();
  157. // Check that they are sorted alphabetically
  158. t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.dnd').find('a.open-chat').text();
  159. expect(t).toEqual(cur_names.slice(3,i+1).sort().join(''));
  160. }
  161. }, xmppchat));
  162. it("can change their status to away and be sorted alphabetically", $.proxy(function () {
  163. var item, view, jid, t;
  164. spyOn(this.rosterview, 'render').andCallThrough();
  165. for (i=6; i<9; i++) {
  166. jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
  167. view = this.rosterview.rosteritemviews[jid];
  168. spyOn(view, 'render').andCallThrough();
  169. item = view.model;
  170. item.set('chat_status', 'away');
  171. expect(view.render).toHaveBeenCalled();
  172. expect(this.rosterview.render).toHaveBeenCalled();
  173. // Check that they are sorted alphabetically
  174. t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.away').find('a.open-chat').text();
  175. expect(t).toEqual(cur_names.slice(6,i+1).sort().join(''));
  176. }
  177. }, xmppchat));
  178. it("can change their status to unavailable and be sorted alphabetically", $.proxy(function () {
  179. var item, view, jid, t;
  180. spyOn(this.rosterview, 'render').andCallThrough();
  181. for (i=9; i<12; i++) {
  182. jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
  183. view = this.rosterview.rosteritemviews[jid];
  184. spyOn(view, 'render').andCallThrough();
  185. item = view.model;
  186. item.set('chat_status', 'unavailable');
  187. expect(view.render).toHaveBeenCalled();
  188. expect(this.rosterview.render).toHaveBeenCalled();
  189. // Check that they are sorted alphabetically
  190. t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.unavailable').find('a.open-chat').text();
  191. expect(t).toEqual(cur_names.slice(9, i+1).sort().join(''));
  192. }
  193. }, xmppchat));
  194. it("are ordered according to status: online, busy, away, unavailable, offline", $.proxy(function () {
  195. var contacts = this.rosterview.$el.find('dd.current-xmpp-contact');
  196. var i;
  197. // The first five contacts are online.
  198. for (i=0; i<3; i++) {
  199. expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('online');
  200. }
  201. // The next five are busy
  202. for (i=3; i<6; i++) {
  203. expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('dnd');
  204. }
  205. // The next five are away
  206. for (i=6; i<9; i++) {
  207. expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('away');
  208. }
  209. // The next five are unavailable
  210. for (i=9; i<12; i++) {
  211. expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('unavailable');
  212. }
  213. // The next 20 are offline
  214. for (i=12; i<cur_names.length; i++) {
  215. expect($(contacts[i]).attr('class').split(' ',1)[0]).toEqual('offline');
  216. }
  217. }, xmppchat));
  218. }, xmppchat));
  219. describe("Requesting Contacts", $.proxy(function () {
  220. // by default the dts are hidden from css class and only later they will be hidden
  221. // by jQuery therefore for the first check we will see if visible instead of none
  222. it("do not have a heading if there aren't any", $.proxy(function () {
  223. expect(this.rosterview.$el.find('dt#xmpp-contact-requests').is(':visible')).toEqual(false);
  224. }, xmppchat));
  225. it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
  226. var i, t;
  227. spyOn(this.rosterview, 'render').andCallThrough();
  228. spyOn(this, 'showControlBox').andCallThrough();
  229. for (i=0; i<req_names.length; i++) {
  230. this.roster.create({
  231. jid: req_names[i].replace(' ','.').toLowerCase() + '@localhost',
  232. subscription: 'none',
  233. ask: 'request',
  234. fullname: req_names[i],
  235. is_last: i==(req_names.length-1)
  236. });
  237. expect(this.rosterview.render).toHaveBeenCalled();
  238. // Check that they are sorted alphabetically
  239. t = this.rosterview.$el.find('dt#xmpp-contact-requests').siblings('dd.requesting-xmpp-contact').text().replace(/AcceptDecline/g, '');
  240. expect(t).toEqual(req_names.slice(0,i+1).sort().join(''));
  241. // When a requesting contact is added, the controlbox must
  242. // be opened.
  243. expect(this.showControlBox).toHaveBeenCalled();
  244. }
  245. }, xmppchat));
  246. it("will have their own heading once they have been added", $.proxy(function () {
  247. expect(this.rosterview.$el.find('dt#xmpp-contact-requests').css('display')).toEqual('block');
  248. }, xmppchat));
  249. it("can have their requests accepted by the user", $.proxy(function () {
  250. // TODO: Testing can be more thorough here, the user is
  251. // actually not accepted/authorized because of
  252. // mock_connection.
  253. var jid = req_names.sort()[0].replace(' ','.').toLowerCase() + '@localhost';
  254. var view = this.rosterview.rosteritemviews[jid];
  255. spyOn(this.connection.roster, 'authorize');
  256. spyOn(view, 'acceptRequest').andCallThrough();
  257. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  258. var accept_button = view.$el.find('.accept-xmpp-request');
  259. accept_button.click();
  260. expect(view.acceptRequest).toHaveBeenCalled();
  261. expect(this.connection.roster.authorize).toHaveBeenCalled();
  262. }, xmppchat));
  263. it("can have their requests denied by the user", $.proxy(function () {
  264. var jid = req_names.sort()[1].replace(' ','.').toLowerCase() + '@localhost';
  265. var view = this.rosterview.rosteritemviews[jid];
  266. spyOn(this.connection.roster, 'unauthorize');
  267. spyOn(this.rosterview, 'removeRosterItem').andCallThrough();
  268. spyOn(view, 'declineRequest').andCallThrough();
  269. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  270. var accept_button = view.$el.find('.decline-xmpp-request');
  271. accept_button.click();
  272. expect(view.declineRequest).toHaveBeenCalled();
  273. expect(this.rosterview.removeRosterItem).toHaveBeenCalled();
  274. expect(this.connection.roster.unauthorize).toHaveBeenCalled();
  275. // There should now be one less contact
  276. expect(this.roster.length).toEqual(num_contacts-1);
  277. }, xmppchat));
  278. }, xmppchat));
  279. describe("All Contacts", $.proxy(function () {
  280. it("are saved to, and can be retrieved from, localStorage", $.proxy(function () {
  281. var new_attrs, old_attrs, attrs, old_roster;
  282. // One contact was declined, so we have 1 less contact then originally
  283. expect(this.roster.length).toEqual(num_contacts-1);
  284. old_roster = this.roster;
  285. this.roster = new this.RosterItems();
  286. expect(this.roster.length).toEqual(0);
  287. this.roster.localStorage = new Backbone.LocalStorage(
  288. hex_sha1('converse.rosteritems-dummy@localhost'));
  289. this.chatboxes.onConnected();
  290. spyOn(this.roster, 'fetch').andCallThrough();
  291. this.rosterview = new this.RosterView({'model':this.roster});
  292. expect(this.roster.fetch).toHaveBeenCalled();
  293. expect(this.roster.length).toEqual(num_contacts-1);
  294. // Check that the roster items retrieved from localStorage
  295. // have the same attributes values as the original ones.
  296. attrs = ['jid', 'fullname', 'subscription', 'ask'];
  297. for (i=0; i<attrs.length; i++) {
  298. new_attrs = _.pluck(_.pluck(this.roster.models, 'attributes'), attrs[i]);
  299. old_attrs = _.pluck(_.pluck(old_roster.models, 'attributes'), attrs[i]);
  300. // Roster items in storage are not necessarily sorted,
  301. // so we have to sort them here to do a proper
  302. // comparison
  303. expect(_.isEqual(new_attrs.sort(), old_attrs.sort())).toEqual(true);
  304. }
  305. this.rosterview.render();
  306. }, xmppchat));
  307. afterEach($.proxy(function () {
  308. // Contacts retrieved from localStorage have chat_status of
  309. // "offline".
  310. // In the next test suite, we need some online contacts, so
  311. // we make some online now
  312. for (i=0; i<5; i++) {
  313. jid = cur_names[i].replace(' ','.').toLowerCase() + '@localhost';
  314. view = this.rosterview.rosteritemviews[jid];
  315. view.model.set('chat_status', 'online');
  316. }
  317. }, xmppchat));
  318. }, xmppchat));
  319. }, xmppchat));
  320. describe("A Chatbox", $.proxy(function () {
  321. it("is created when you click on a roster item", $.proxy(function () {
  322. var i, $el, click, jid, view;
  323. // showControlBox was called earlier, so the controlbox is
  324. // visible, but no other chat boxes have been created.
  325. expect(this.chatboxes.length).toEqual(1);
  326. var online_contacts = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.online').find('a.open-chat');
  327. for (i=0; i<online_contacts.length; i++) {
  328. $el = $(online_contacts[i]);
  329. jid = $el.text().replace(' ','.').toLowerCase() + '@localhost';
  330. view = this.rosterview.rosteritemviews[jid];
  331. spyOn(view, 'openChat').andCallThrough();
  332. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  333. $el.click();
  334. expect(view.openChat).toHaveBeenCalled();
  335. expect(this.chatboxes.length).toEqual(i+2);
  336. }
  337. }, xmppchat));
  338. it("can be saved to, and retrieved from, localStorage", $.proxy(function () {
  339. // We instantiate a new ChatBoxes collection, which by default
  340. // will be empty.
  341. this.newchatboxes = new this.ChatBoxes();
  342. expect(this.newchatboxes.length).toEqual(0);
  343. // The chatboxes will then be fetched from localStorage inside the
  344. // onConnected method
  345. this.newchatboxes.onConnected();
  346. expect(this.newchatboxes.length).toEqual(6);
  347. // Check that the roster items retrieved from localStorage
  348. // have the same attributes values as the original ones.
  349. attrs = ['id', 'box_id', 'visible'];
  350. for (i=0; i<attrs.length; i++) {
  351. new_attrs = _.pluck(_.pluck(this.newchatboxes.models, 'attributes'), attrs[i]);
  352. old_attrs = _.pluck(_.pluck(this.chatboxes.models, 'attributes'), attrs[i]);
  353. expect(_.isEqual(new_attrs, old_attrs)).toEqual(true);
  354. }
  355. this.rosterview.render();
  356. }, xmppchat));
  357. it("can be closed again by clicking a DOM element with class 'close-chatbox-button'", $.proxy(function () {
  358. var chatbox, view, $el,
  359. num_open_chats = this.chatboxes.length;
  360. for (i=0; i<num_open_chats; i++) {
  361. chatbox = this.chatboxes.models[0];
  362. view = this.chatboxesview.views[chatbox.get('id')];
  363. spyOn(view, 'closeChat').andCallThrough();
  364. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  365. view.$el.find('.close-chatbox-button').click();
  366. expect(view.closeChat).toHaveBeenCalled();
  367. }
  368. }, xmppchat));
  369. it("will be removed from localStorage when closed", $.proxy(function () {
  370. this.newchatboxes = new this.ChatBoxes();
  371. expect(this.newchatboxes.length).toEqual(0);
  372. // onConnected will fetch chatboxes in localStorage, but
  373. // because there aren't any open chatboxes, there won't be any
  374. // in localStorage either.
  375. this.chatboxes.onConnected();
  376. expect(this.chatboxes.length).toEqual(0);
  377. }, xmppchat));
  378. describe("A Chat Message", $.proxy(function () {
  379. it("can be received which will open a chatbox and be displayed inside it", $.proxy(function () {
  380. var message = 'This is a received message';
  381. var sender_jid = cur_names[0].replace(' ','.').toLowerCase() + '@localhost';
  382. msg = $msg({
  383. from: sender_jid,
  384. to: this.bare_jid,
  385. type: 'chat',
  386. id: (new Date()).getTime()
  387. }).c('body').t(message).up()
  388. .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
  389. spyOn(this, 'getVCard').andCallThrough();
  390. // We don't already have an open chatbox for this user
  391. expect(this.chatboxes.get(sender_jid)).not.toBeDefined();
  392. runs($.proxy(function () {
  393. // messageReceived is a handler for received XMPP
  394. // messages
  395. this.chatboxes.messageReceived(msg);
  396. }, xmppchat));
  397. waits(500);
  398. runs($.proxy(function () {
  399. // Since we didn't already have an open chatbox, one
  400. // will asynchronously created inside a callback to
  401. // getVCard
  402. expect(this.getVCard).toHaveBeenCalled();
  403. // Check that the chatbox and its view now exist
  404. var chatbox = this.chatboxes.get(sender_jid);
  405. var chatboxview = this.chatboxesview.views[sender_jid];
  406. expect(chatbox).toBeDefined();
  407. expect(chatboxview).toBeDefined();
  408. // Check that the message was received and check the
  409. // message parameters
  410. expect(chatbox.messages.length).toEqual(1);
  411. var msg_obj = chatbox.messages.models[0];
  412. expect(msg_obj.get('message')).toEqual(message);
  413. // XXX: This is stupid, fullname is actually only the
  414. // users first name
  415. expect(msg_obj.get('fullname')).toEqual(cur_names[0].split(' ')[0]);
  416. expect(msg_obj.get('sender')).toEqual('them');
  417. expect(msg_obj.get('delayed')).toEqual(false);
  418. // Now check that the message appears inside the
  419. // chatbox in the DOM
  420. var txt = chatboxview.$el.find('.chat-content').find('.chat-message').find('.chat-message-content').text();
  421. expect(txt).toEqual(message);
  422. }, xmppchat));
  423. }, xmppchat));
  424. it("can be sent from a chatbox, and will appear inside it", $.proxy(function () {
  425. var contact_jid = cur_names[0].replace(' ','.').toLowerCase() + '@localhost';
  426. var view = this.chatboxesview.views[contact_jid];
  427. var message = 'This is a message sent from the chatbox';
  428. spyOn(view, 'sendMessage').andCallThrough();
  429. view.$el.find('.chat-textarea').text(message);
  430. view.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13}));
  431. expect(view.sendMessage).toHaveBeenCalled();
  432. expect(view.model.messages.length, 2);
  433. var txt = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-message-content').text();
  434. expect(txt).toEqual(message);
  435. }, xmppchat));
  436. }, xmppchat));
  437. }, xmppchat));
  438. }, xmppchat));
  439. }));