controlbox.js 63 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182
  1. (function (root, factory) {
  2. define(["mock", "converse-core", "test-utils"], factory);
  3. } (this, function (mock, converse, test_utils) {
  4. var _ = converse.env._;
  5. var $ = converse.env.jQuery;
  6. var $pres = converse.env.$pres;
  7. var $iq = converse.env.$iq;
  8. var checkHeaderToggling = function ($header) {
  9. var $toggle = $header.find('a.group-toggle');
  10. expect($header.css('display')).toEqual('block');
  11. expect($header.nextUntil('dt', 'dd').length === $header.nextUntil('dt', 'dd:visible').length).toBeTruthy();
  12. expect($toggle.hasClass('icon-closed')).toBeFalsy();
  13. expect($toggle.hasClass('icon-opened')).toBeTruthy();
  14. $toggle.click();
  15. expect($toggle.hasClass('icon-closed')).toBeTruthy();
  16. expect($toggle.hasClass('icon-opened')).toBeFalsy();
  17. expect($header.nextUntil('dt', 'dd').length === $header.nextUntil('dt', 'dd:hidden').length).toBeTruthy();
  18. $toggle.click();
  19. expect($toggle.hasClass('icon-closed')).toBeFalsy();
  20. expect($toggle.hasClass('icon-opened')).toBeTruthy();
  21. expect($header.nextUntil('dt', 'dd').length === $header.nextUntil('dt', 'dd:visible').length).toBeTruthy();
  22. };
  23. describe("The Control Box", function () {
  24. it("can be opened by clicking a DOM element with class 'toggle-controlbox'", mock.initConverse(function (_converse) {
  25. // This spec will only pass if the controlbox is not currently
  26. // open yet.
  27. expect($("div#controlbox").is(':visible')).toBe(false);
  28. spyOn(_converse.controlboxtoggle, 'onClick').and.callThrough();
  29. spyOn(_converse.controlboxtoggle, 'showControlBox').and.callThrough();
  30. spyOn(_converse, 'emit');
  31. // Redelegate so that the spies are now registered as the event handlers (specifically for 'onClick')
  32. _converse.controlboxtoggle.delegateEvents();
  33. $('.toggle-controlbox').click();
  34. expect(_converse.controlboxtoggle.onClick).toHaveBeenCalled();
  35. expect(_converse.controlboxtoggle.showControlBox).toHaveBeenCalled();
  36. expect(_converse.emit).toHaveBeenCalledWith('controlBoxOpened', jasmine.any(Object));
  37. expect($("div#controlbox").is(':visible')).toBe(true);
  38. }));
  39. describe("The Status Widget", function () {
  40. it("shows the user's chat status, which is online by default", mock.initConverse(function (_converse) {
  41. test_utils.openControlBox();
  42. var view = _converse.xmppstatusview;
  43. expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(true);
  44. expect(view.$el.find('a.choose-xmpp-status').attr('data-value')).toBe('I am online');
  45. }));
  46. it("can be used to set the current user's chat status", mock.initConverse(function (_converse) {
  47. test_utils.openControlBox();
  48. var view = _converse.xmppstatusview;
  49. spyOn(view, 'toggleOptions').and.callThrough();
  50. spyOn(view, 'setStatus').and.callThrough();
  51. spyOn(_converse, 'emit');
  52. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  53. view.$el.find('a.choose-xmpp-status').click();
  54. expect(view.toggleOptions).toHaveBeenCalled();
  55. spyOn(view, 'updateStatusUI').and.callThrough();
  56. view.initialize(); // Rebind events for spy
  57. $(view.$el.find('.dropdown dd ul li a')[1]).click(); // Change status to "dnd"
  58. expect(view.setStatus).toHaveBeenCalled();
  59. expect(_converse.emit).toHaveBeenCalledWith('statusChanged', 'dnd');
  60. expect(view.updateStatusUI).toHaveBeenCalled();
  61. expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(false);
  62. expect(view.$el.find('a.choose-xmpp-status').hasClass('dnd')).toBe(true);
  63. expect(view.$el.find('a.choose-xmpp-status').attr('data-value')).toBe('I am busy');
  64. }));
  65. it("can be used to set a custom status message", mock.initConverse(function (_converse) {
  66. test_utils.openControlBox();
  67. var view = _converse.xmppstatusview;
  68. _converse.xmppstatus.save({'status': 'online'});
  69. spyOn(view, 'setStatusMessage').and.callThrough();
  70. spyOn(view, 'renderStatusChangeForm').and.callThrough();
  71. spyOn(_converse, 'emit');
  72. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  73. view.$el.find('a.change-xmpp-status-message').click();
  74. expect(view.renderStatusChangeForm).toHaveBeenCalled();
  75. // The async testing here is used only to provide time for
  76. // visual feedback
  77. var msg = 'I am happy';
  78. view.$el.find('form input.custom-xmpp-status').val(msg);
  79. view.$el.find('form#set-custom-xmpp-status').submit();
  80. expect(view.setStatusMessage).toHaveBeenCalled();
  81. expect(_converse.emit).toHaveBeenCalledWith('statusMessageChanged', msg);
  82. expect(view.$el.find('a.choose-xmpp-status').hasClass('online')).toBe(true);
  83. expect(view.$el.find('a.choose-xmpp-status').attr('data-value')).toBe(msg);
  84. }));
  85. });
  86. });
  87. describe("The Contacts Roster", function () {
  88. describe("The live filter", function () {
  89. it("will only appear when roster contacts flow over the visible area", mock.initConverseWithAsync(function (done, _converse) {
  90. var $filter = _converse.rosterview.$('.roster-filter');
  91. var names = mock.cur_names;
  92. test_utils.openControlBox();
  93. _converse.rosterview.update(); // XXX: Will normally called as event handler
  94. expect($filter.length).toBe(1);
  95. test_utils.waitUntil(function () {
  96. return !$filter.is(':visible');
  97. }).then(function () {
  98. for (var i=0; i<names.length; i++) {
  99. _converse.roster.create({
  100. ask: null,
  101. fullname: names[i],
  102. jid: names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
  103. requesting: 'false',
  104. subscription: 'both'
  105. });
  106. _converse.rosterview.update(); // XXX: Will normally called as event handler
  107. }
  108. return test_utils.waitUntil(function () {
  109. if (_converse.rosterview.$roster.hasScrollBar()) {
  110. return $filter.is(':visible');
  111. } else {
  112. return !$filter.is(':visible');
  113. }
  114. }).then(function () {
  115. done();
  116. });
  117. });
  118. }));
  119. it("can be used to filter the contacts shown", mock.initConverseWithAsync(function (done, _converse) {
  120. var $filter;
  121. var $roster;
  122. _converse.roster_groups = true;
  123. test_utils.openControlBox();
  124. test_utils.createGroupedContacts(_converse);
  125. $filter = _converse.rosterview.$('.roster-filter');
  126. $roster = _converse.rosterview.$roster;
  127. _converse.rosterview.filter_view.delegateEvents();
  128. var promise = test_utils.waitUntil(function () {
  129. return $roster.find('dd:visible').length === 15;
  130. }, 500)
  131. .then(function (contacts) {
  132. expect($roster.find('dt:visible').length).toBe(5);
  133. $filter.val("candice");
  134. $filter.trigger('keydown');
  135. return test_utils.waitUntil(function () {
  136. return $roster.find('dd:visible').length === 1;
  137. }, 500);
  138. }).then(function (contacts) {
  139. expect($roster.find('dd:visible').eq(0).text().trim()).toBe('Candice van der Knijff');
  140. expect($roster.find('dt:visible').length).toBe(1);
  141. expect(_.trim($roster.find('dt:visible').eq(0).text())).toBe('colleagues');
  142. $filter = _converse.rosterview.$('.roster-filter');
  143. $filter.val("an");
  144. $filter.trigger('keydown');
  145. return test_utils.waitUntil(function () {
  146. return $roster.find('dd:visible').length === 5;
  147. }, 500)
  148. }).then(function (contacts) {
  149. expect($roster.find('dt:visible').length).toBe(4);
  150. $filter = _converse.rosterview.$('.roster-filter');
  151. $filter.val("xxx");
  152. $filter.trigger('keydown');
  153. return test_utils.waitUntil(function () {
  154. return $roster.find('dd:visible').length === 0;
  155. }, 500)
  156. }).then(function () {
  157. expect($roster.find('dt:visible').length).toBe(0);
  158. $filter = _converse.rosterview.$('.roster-filter');
  159. $filter.val(""); // Check that contacts are shown again, when the filter string is cleared.
  160. $filter.trigger('keydown');
  161. return test_utils.waitUntil(function () {
  162. return $roster.find('dd:visible').length === 15;
  163. }, 500)
  164. }).then(function () {
  165. expect($roster.find('dt:visible').length).toBe(5);
  166. _converse.roster_groups = false;
  167. done();
  168. });
  169. }));
  170. it("can be used to filter the groups shown", mock.initConverseWithAsync(function (done, _converse) {
  171. var $filter;
  172. var $roster;
  173. var $type;
  174. _converse.roster_groups = true;
  175. test_utils.openControlBox();
  176. test_utils.createGroupedContacts(_converse);
  177. _converse.rosterview.filter_view.delegateEvents();
  178. $filter = _converse.rosterview.$('.roster-filter');
  179. $roster = _converse.rosterview.$roster;
  180. $type = _converse.rosterview.$('.filter-type');
  181. $type.val('groups');
  182. var promise = test_utils.waitUntil(function () {
  183. return $roster.find('dd:visible').length === 15;
  184. }, 500);
  185. promise.then(function () {
  186. expect($roster.find('dt:visible').length).toBe(5);
  187. $filter.val("colleagues");
  188. $filter.trigger('keydown');
  189. return test_utils.waitUntil(function () {
  190. return $roster.find('dt:visible').length === 1;
  191. }, 500);
  192. }).then(function () {
  193. expect(_.trim($roster.find('dt:visible').eq(0).text())).toBe('colleagues');
  194. expect($roster.find('dd:visible').length).toBe(3);
  195. // Check that all contacts under the group are shown
  196. expect($roster.find('dt:visible').nextUntil('dt', 'dd:hidden').length).toBe(0);
  197. $filter = _converse.rosterview.$('.roster-filter');
  198. $filter.val("xxx").trigger('keydown');
  199. return test_utils.waitUntil(function () {
  200. return $roster.find('dd:visible').length === 0;
  201. }, 700);
  202. }).then(function () {
  203. expect($roster.find('dt:visible').length).toBe(0);
  204. $filter = _converse.rosterview.$('.roster-filter');
  205. $filter.val(""); // Check that groups are shown again, when the filter string is cleared.
  206. $filter.trigger('keydown');
  207. return test_utils.waitUntil(function () {
  208. return $roster.find('dd:visible').length === 15;
  209. }, 500);
  210. }).then(function () {
  211. expect($roster.find('dt:visible').length).toBe(5);
  212. done();
  213. });
  214. }));
  215. it("has a button with which its contents can be cleared", mock.initConverseWithAsync(function (done, _converse) {
  216. _converse.roster_groups = true;
  217. test_utils.openControlBox();
  218. test_utils.createGroupedContacts(_converse);
  219. var $filter = _converse.rosterview.$('.roster-filter');
  220. _converse.rosterview.filter_view.delegateEvents();
  221. $filter.val("xxx");
  222. $filter.trigger('keydown');
  223. expect($filter.hasClass("x")).toBeFalsy();
  224. $filter = _converse.rosterview.$('.roster-filter');
  225. test_utils.waitUntil(function () {
  226. return _converse.rosterview.$('.roster-filter').hasClass("x");
  227. }, 900).then(function () {
  228. var $filter = _converse.rosterview.$('.roster-filter');
  229. $filter.addClass("onX").click();
  230. return test_utils.waitUntil(function () {
  231. return !_converse.rosterview.$('.roster-filter').hasClass("x");
  232. }, 900)
  233. }).then(function () {
  234. expect(document.querySelector('.roster-filter').value).toBe("");
  235. done();
  236. });
  237. }));
  238. it("can be used to filter contacts by their chat state", mock.initConverseWithAsync(function (done, _converse) {
  239. var $filter;
  240. var $roster;
  241. _converse.roster_groups = true;
  242. test_utils.createGroupedContacts(_converse);
  243. var jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@localhost';
  244. _converse.roster.get(jid).set('chat_status', 'online');
  245. test_utils.openControlBox();
  246. _converse.rosterview.filter_view.delegateEvents();
  247. var $type = _converse.rosterview.$('.filter-type');
  248. $type.val('state').trigger('change');
  249. $filter = _converse.rosterview.$('.state-type');
  250. $roster = _converse.rosterview.$roster;
  251. test_utils.waitUntil(function () {
  252. return $roster.find('dd:visible').length === 15;
  253. }, 500)
  254. .then(function () {
  255. expect($roster.find('dt:visible').length).toBe(5);
  256. $filter.val("online");
  257. $filter.trigger('change');
  258. return test_utils.waitUntil(function () {
  259. return $roster.find('dd:visible').length === 1;
  260. }, 500)
  261. }).then(function () {
  262. expect($roster.find('dd:visible').eq(0).text().trim()).toBe('Rinse Sommer');
  263. expect($roster.find('dt:visible').length).toBe(1);
  264. var $type = _converse.rosterview.$('.filter-type');
  265. $type.val('contacts').trigger('change');
  266. done();
  267. });
  268. }));
  269. });
  270. describe("A Roster Group", function () {
  271. it("can be used to organize existing contacts", mock.initConverseWithAsync(function (done, _converse) {
  272. _converse.roster_groups = true;
  273. spyOn(_converse, 'emit');
  274. spyOn(_converse.rosterview, 'update').and.callThrough();
  275. _converse.rosterview.render();
  276. test_utils.createContacts(_converse, 'pending');
  277. test_utils.createContacts(_converse, 'requesting');
  278. test_utils.createGroupedContacts(_converse);
  279. // Check that the groups appear alphabetically and that
  280. // requesting and pending contacts are last.
  281. test_utils.waitUntil(function () {
  282. return _converse.rosterview.$el.find('dt').length;
  283. }, 500)
  284. .then(function () {
  285. var group_titles = $.map(_converse.rosterview.$el.find('dt'), function (o) { return $(o).text().trim(); });
  286. expect(group_titles).toEqual([
  287. "Contact requests",
  288. "colleagues",
  289. "Family",
  290. "friends & acquaintences",
  291. "ænemies",
  292. "Ungrouped",
  293. "Pending contacts"
  294. ]);
  295. // Check that usernames appear alphabetically per group
  296. _.each(_.keys(mock.groups), function (name) {
  297. var $contacts = _converse.rosterview.$('dt.roster-group[data-group="'+name+'"]').nextUntil('dt', 'dd');
  298. var names = $.map($contacts, function (o) { return $(o).text().trim(); });
  299. expect(names).toEqual(_.clone(names).sort());
  300. });
  301. done();
  302. });
  303. }));
  304. it("can share contacts with other roster groups", mock.initConverseWithAsync(function (done, _converse) {
  305. _converse.roster_groups = true;
  306. var groups = ['colleagues', 'friends'];
  307. spyOn(_converse, 'emit');
  308. spyOn(_converse.rosterview, 'update').and.callThrough();
  309. _converse.rosterview.render();
  310. for (var i=0; i<mock.cur_names.length; i++) {
  311. _converse.roster.create({
  312. jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
  313. subscription: 'both',
  314. ask: null,
  315. groups: groups,
  316. fullname: mock.cur_names[i]
  317. });
  318. }
  319. test_utils.waitUntil(function () {
  320. return _converse.rosterview.$el.find('dd').length;
  321. }, 500)
  322. .then(function () {
  323. // Check that usernames appear alphabetically per group
  324. _.each(groups, function (name) {
  325. var $contacts = _converse.rosterview.$('dt.roster-group[data-group="'+name+'"]').nextUntil('dt', 'dd');
  326. var names = $.map($contacts, function (o) { return $(o).text().trim(); });
  327. expect(names).toEqual(_.clone(names).sort());
  328. expect(names.length).toEqual(mock.cur_names.length);
  329. });
  330. done();
  331. });
  332. }));
  333. it("remembers whether it is closed or opened", mock.initConverse(function (_converse) {
  334. _converse.roster_groups = true;
  335. var i=0, j=0;
  336. var groups = {
  337. 'colleagues': 3,
  338. 'friends & acquaintences': 3,
  339. 'Ungrouped': 2
  340. };
  341. _.each(_.keys(groups), function (name) {
  342. j = i;
  343. for (i=j; i<j+groups[name]; i++) {
  344. _converse.roster.create({
  345. jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
  346. subscription: 'both',
  347. ask: null,
  348. groups: name === 'ungrouped'? [] : [name],
  349. fullname: mock.cur_names[i]
  350. });
  351. }
  352. });
  353. var view = _converse.rosterview.get('colleagues');
  354. var $toggle = view.$el.find('a.group-toggle');
  355. expect(view.model.get('state')).toBe('opened');
  356. $toggle.click();
  357. expect(view.model.get('state')).toBe('closed');
  358. $toggle.click();
  359. expect(view.model.get('state')).toBe('opened');
  360. }));
  361. });
  362. describe("Pending Contacts", function () {
  363. function _addContacts (_converse) {
  364. // Must be initialized, so that render is called and documentFragment set up.
  365. test_utils.createContacts(_converse, 'pending').openControlBox().openContactsPanel(_converse);
  366. }
  367. it("can be collapsed under their own header", mock.initConverseWithAsync(function (done, _converse) {
  368. _addContacts(_converse);
  369. test_utils.waitUntil(function () {
  370. return _converse.rosterview.$el.find('dd').length;
  371. }, 500)
  372. .then(function () {
  373. checkHeaderToggling.apply(_converse, [_converse.rosterview.get('Pending contacts').$el]);
  374. done();
  375. });
  376. }));
  377. it("can be added to the roster", mock.initConverse(function (_converse) {
  378. spyOn(_converse, 'emit');
  379. spyOn(_converse.rosterview, 'update').and.callThrough();
  380. test_utils.openControlBox();
  381. _converse.roster.create({
  382. jid: mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
  383. subscription: 'none',
  384. ask: 'subscribe',
  385. fullname: mock.pend_names[0]
  386. });
  387. expect(_converse.rosterview.update).toHaveBeenCalled();
  388. }));
  389. it("are shown in the roster when show_only_online_users", mock.initConverseWithAsync(function (done, _converse) {
  390. _converse.show_only_online_users = true;
  391. spyOn(_converse.rosterview, 'update').and.callThrough();
  392. _addContacts(_converse);
  393. test_utils.waitUntil(function () {
  394. return _converse.rosterview.$el.find('dd').length;
  395. }, 500)
  396. .then(function () {
  397. expect(_converse.rosterview.$el.is(':visible')).toEqual(true);
  398. expect(_converse.rosterview.update).toHaveBeenCalled();
  399. expect(_converse.rosterview.$el.find('dd:visible').length).toBe(3);
  400. expect(_converse.rosterview.$el.find('dt:visible').length).toBe(1);
  401. done();
  402. });
  403. }));
  404. it("are shown in the roster when hide_offline_users", mock.initConverseWithAsync(function (done, _converse) {
  405. _converse.hide_offline_users = true;
  406. spyOn(_converse.rosterview, 'update').and.callThrough();
  407. _addContacts(_converse);
  408. test_utils.waitUntil(function () {
  409. return _converse.rosterview.$el.find('dd:visible').length;
  410. }, 500)
  411. .then(function () {
  412. expect(_converse.rosterview.update).toHaveBeenCalled();
  413. expect(_converse.rosterview.$el.is(':visible')).toBe(true);
  414. expect(_converse.rosterview.$el.find('dd:visible').length).toBe(3);
  415. expect(_converse.rosterview.$el.find('dt:visible').length).toBe(1);
  416. done();
  417. });
  418. }));
  419. it("can be removed by the user", mock.initConverseWithAsync(function (done, _converse) {
  420. _addContacts(_converse);
  421. var name = mock.pend_names[0];
  422. var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
  423. var contact = _converse.roster.get(jid);
  424. spyOn(window, 'confirm').and.returnValue(true);
  425. spyOn(contact, 'unauthorize').and.callFake(function () { return contact; });
  426. spyOn(contact, 'removeFromRoster');
  427. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback) {
  428. if (typeof callback === "function") { return callback(); }
  429. });
  430. test_utils.waitUntil(function () {
  431. return _converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')").length;
  432. }, 500)
  433. .then(function () {
  434. _converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
  435. .parent().siblings('.remove-xmpp-contact').click();
  436. return test_utils.waitUntil(function () {
  437. return _converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')").length === 0
  438. }, 500)
  439. }).then(function () {
  440. expect(window.confirm).toHaveBeenCalled();
  441. expect(_converse.connection.sendIQ).toHaveBeenCalled();
  442. expect(contact.removeFromRoster).toHaveBeenCalled();
  443. expect(_converse.connection.sendIQ).toHaveBeenCalled();
  444. done();
  445. });
  446. }));
  447. it("do not have a header if there aren't any", mock.initConverseWithAsync(function (done, _converse) {
  448. test_utils.openControlBox();
  449. var name = mock.pend_names[0];
  450. _converse.roster.create({
  451. jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
  452. subscription: 'none',
  453. ask: 'subscribe',
  454. fullname: name
  455. });
  456. spyOn(window, 'confirm').and.returnValue(true);
  457. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback) {
  458. if (typeof callback === "function") { return callback(); }
  459. });
  460. test_utils.waitUntil(function () {
  461. return _converse.rosterview.get('Pending contacts').$el.is(':visible');
  462. }, 500)
  463. .then(function () {
  464. _converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
  465. .parent().siblings('.remove-xmpp-contact').click();
  466. expect(window.confirm).toHaveBeenCalled();
  467. expect(_converse.connection.sendIQ).toHaveBeenCalled();
  468. expect(_converse.rosterview.get('Pending contacts').$el.is(':visible')).toEqual(false);
  469. done();
  470. });
  471. }));
  472. it("will lose their own header once the last one has been removed", mock.initConverse(function (_converse) {
  473. _addContacts(_converse);
  474. var name;
  475. spyOn(window, 'confirm').and.returnValue(true);
  476. for (var i=0; i<mock.pend_names.length; i++) {
  477. name = mock.pend_names[i];
  478. _converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
  479. .parent().siblings('.remove-xmpp-contact').click();
  480. }
  481. expect(_converse.rosterview.$el.find('dt#pending-xmpp-contacts').is(':visible')).toBeFalsy();
  482. }));
  483. it("can be added to the roster and they will be sorted alphabetically", mock.initConverse(function (_converse) {
  484. var i, t;
  485. spyOn(_converse, 'emit');
  486. spyOn(_converse.rosterview, 'update').and.callThrough();
  487. for (i=0; i<mock.pend_names.length; i++) {
  488. _converse.roster.create({
  489. jid: mock.pend_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
  490. subscription: 'none',
  491. ask: 'subscribe',
  492. fullname: mock.pend_names[i]
  493. });
  494. expect(_converse.rosterview.update).toHaveBeenCalled();
  495. }
  496. // Check that they are sorted alphabetically
  497. t = _converse.rosterview.get('Pending contacts').$el.siblings('dd.pending-xmpp-contact').find('span').text();
  498. expect(t).toEqual(mock.pend_names.slice(0,i+1).sort().join(''));
  499. }));
  500. });
  501. describe("Existing Contacts", function () {
  502. var _addContacts = function (_converse) {
  503. test_utils.createContacts(_converse, 'current').openControlBox().openContactsPanel(_converse);
  504. };
  505. it("can be collapsed under their own header", mock.initConverseWithAsync(function (done, _converse) {
  506. _addContacts(_converse);
  507. test_utils.waitUntil(function () {
  508. return _converse.rosterview.$el.find('dd:visible').length;
  509. }, 500)
  510. .then(function () {
  511. checkHeaderToggling.apply(_converse, [_converse.rosterview.$el.find('dt.roster-group')]);
  512. done();
  513. });
  514. }));
  515. it("will be hidden when appearing under a collapsed group", mock.initConverseWithAsync(function (done, _converse) {
  516. _converse.roster_groups = false;
  517. _addContacts(_converse);
  518. test_utils.waitUntil(function () {
  519. return _converse.rosterview.$el.find('dd:visible').length;
  520. }, 500)
  521. .then(function () {
  522. _converse.rosterview.$el.find('dt.roster-group').find('a.group-toggle').click();
  523. var name = "Max Mustermann";
  524. var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
  525. _converse.roster.create({
  526. ask: null,
  527. fullname: name,
  528. jid: jid,
  529. requesting: false,
  530. subscription: 'both'
  531. });
  532. var view = _converse.rosterview.get('My contacts').get(jid);
  533. expect(view.$el.is(':visible')).toBe(false);
  534. done();
  535. });
  536. }));
  537. it("can be added to the roster and they will be sorted alphabetically", mock.initConverseWithAsync(function (done, _converse) {
  538. spyOn(_converse.rosterview, 'update').and.callThrough();
  539. for (var i=0; i<mock.cur_names.length; i++) {
  540. _converse.roster.create({
  541. jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
  542. subscription: 'both',
  543. ask: null,
  544. fullname: mock.cur_names[i]
  545. });
  546. expect(_converse.rosterview.update).toHaveBeenCalled();
  547. }
  548. test_utils.waitUntil(function () {
  549. return _converse.rosterview.$el.find('dd').length;
  550. }).then(function () {
  551. // Check that they are sorted alphabetically
  552. var t = _converse.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.offline').find('a.open-chat').text();
  553. expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
  554. done();
  555. });
  556. }));
  557. it("can be removed by the user", mock.initConverseWithAsync(function (done, _converse) {
  558. _addContacts(_converse);
  559. test_utils.waitUntil(function () {
  560. return _converse.rosterview.$el.find('dd').length;
  561. }, 500)
  562. .then(function () {
  563. var name = mock.cur_names[0];
  564. var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
  565. var contact = _converse.roster.get(jid);
  566. spyOn(window, 'confirm').and.returnValue(true);
  567. spyOn(contact, 'removeFromRoster');
  568. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback) {
  569. if (typeof callback === "function") { return callback(); }
  570. });
  571. _converse.rosterview.$el.find(".open-chat:contains('"+name+"')")
  572. .parent().find('.remove-xmpp-contact').click();
  573. expect(window.confirm).toHaveBeenCalled();
  574. expect(_converse.connection.sendIQ).toHaveBeenCalled();
  575. expect(contact.removeFromRoster).toHaveBeenCalled();
  576. expect(_converse.rosterview.$el.find(".open-chat:contains('"+name+"')").length).toEqual(0);
  577. done();
  578. });
  579. }));
  580. it("do not have a header if there aren't any", mock.initConverseWithAsync(function (done, _converse) {
  581. var name = mock.cur_names[0];
  582. var contact;
  583. contact = _converse.roster.create({
  584. jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
  585. subscription: 'both',
  586. ask: null,
  587. fullname: name
  588. });
  589. test_utils.waitUntil(function () {
  590. return _converse.rosterview.$el.find('dt').length;
  591. }, 500)
  592. .then(function () {
  593. spyOn(window, 'confirm').and.returnValue(true);
  594. spyOn(contact, 'removeFromRoster');
  595. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback) {
  596. if (typeof callback === "function") { return callback(); }
  597. });
  598. expect(_converse.rosterview.$el.find('dt.roster-group').css('display')).toEqual('block');
  599. _converse.rosterview.$el.find(".open-chat:contains('"+name+"')")
  600. .parent().find('.remove-xmpp-contact').click();
  601. expect(window.confirm).toHaveBeenCalled();
  602. expect(_converse.connection.sendIQ).toHaveBeenCalled();
  603. expect(contact.removeFromRoster).toHaveBeenCalled();
  604. expect(_converse.rosterview.$el.find('dt.roster-group').css('display')).toEqual('none');
  605. done();
  606. });
  607. }));
  608. it("can change their status to online and be sorted alphabetically", mock.initConverseWithAsync(function (done, _converse) {
  609. _addContacts(_converse);
  610. test_utils.waitUntil(function () {
  611. return _converse.rosterview.$el.find('dt').length;
  612. }, 500)
  613. .then(function () {
  614. var jid, t;
  615. spyOn(_converse, 'emit');
  616. spyOn(_converse.rosterview, 'update').and.callThrough();
  617. for (var i=0; i<mock.cur_names.length; i++) {
  618. jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
  619. _converse.roster.get(jid).set('chat_status', 'online');
  620. expect(_converse.rosterview.update).toHaveBeenCalled();
  621. // Check that they are sorted alphabetically
  622. t = _converse.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.online').find('a.open-chat').text();
  623. expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
  624. }
  625. done();
  626. });
  627. }));
  628. it("can change their status to busy and be sorted alphabetically", mock.initConverseWithAsync(function (done, _converse) {
  629. _addContacts(_converse);
  630. test_utils.waitUntil(function () {
  631. return _converse.rosterview.$el.find('dt').length;
  632. }, 500)
  633. .then(function () {
  634. var jid, t;
  635. spyOn(_converse, 'emit');
  636. spyOn(_converse.rosterview, 'update').and.callThrough();
  637. for (var i=0; i<mock.cur_names.length; i++) {
  638. jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
  639. _converse.roster.get(jid).set('chat_status', 'dnd');
  640. expect(_converse.rosterview.update).toHaveBeenCalled();
  641. // Check that they are sorted alphabetically
  642. t = _converse.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.dnd').find('a.open-chat').text();
  643. expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
  644. }
  645. done();
  646. });
  647. }));
  648. it("can change their status to away and be sorted alphabetically", mock.initConverseWithAsync(function (done, _converse) {
  649. _addContacts(_converse);
  650. test_utils.waitUntil(function () {
  651. return _converse.rosterview.$el.find('dt').length;
  652. }, 500)
  653. .then(function () {
  654. var jid, t;
  655. spyOn(_converse, 'emit');
  656. spyOn(_converse.rosterview, 'update').and.callThrough();
  657. for (var i=0; i<mock.cur_names.length; i++) {
  658. jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
  659. _converse.roster.get(jid).set('chat_status', 'away');
  660. expect(_converse.rosterview.update).toHaveBeenCalled();
  661. // Check that they are sorted alphabetically
  662. t = _converse.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.away').find('a.open-chat').text();
  663. expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
  664. }
  665. done();
  666. });
  667. }));
  668. it("can change their status to xa and be sorted alphabetically", mock.initConverseWithAsync(function (done, _converse) {
  669. _addContacts(_converse);
  670. test_utils.waitUntil(function () {
  671. return _converse.rosterview.$el.find('dt').length;
  672. }, 500)
  673. .then(function () {
  674. var jid, t;
  675. spyOn(_converse, 'emit');
  676. spyOn(_converse.rosterview, 'update').and.callThrough();
  677. for (var i=0; i<mock.cur_names.length; i++) {
  678. jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
  679. _converse.roster.get(jid).set('chat_status', 'xa');
  680. expect(_converse.rosterview.update).toHaveBeenCalled();
  681. // Check that they are sorted alphabetically
  682. t = _converse.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.xa').find('a.open-chat').text();
  683. expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
  684. }
  685. done();
  686. });
  687. }));
  688. it("can change their status to unavailable and be sorted alphabetically", mock.initConverseWithAsync(function (done, _converse) {
  689. _addContacts(_converse);
  690. test_utils.waitUntil(function () {
  691. return _converse.rosterview.$el.find('dt').length;
  692. }, 500)
  693. .then(function () {
  694. var jid, t;
  695. spyOn(_converse, 'emit');
  696. spyOn(_converse.rosterview, 'update').and.callThrough();
  697. for (var i=0; i<mock.cur_names.length; i++) {
  698. jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
  699. _converse.roster.get(jid).set('chat_status', 'unavailable');
  700. expect(_converse.rosterview.update).toHaveBeenCalled();
  701. // Check that they are sorted alphabetically
  702. t = _converse.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.unavailable').find('a.open-chat').text();
  703. expect(t).toEqual(mock.cur_names.slice(0, i+1).sort().join(''));
  704. }
  705. done();
  706. });
  707. }));
  708. it("are ordered according to status: online, busy, away, xa, unavailable, offline", mock.initConverseWithAsync(function (done, _converse) {
  709. _addContacts(_converse);
  710. test_utils.waitUntil(function () {
  711. return _converse.rosterview.$el.find('dt').length;
  712. }, 500)
  713. .then(function () {
  714. var i, jid;
  715. for (i=0; i<3; i++) {
  716. jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
  717. _converse.roster.get(jid).set('chat_status', 'online');
  718. }
  719. for (i=3; i<6; i++) {
  720. jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
  721. _converse.roster.get(jid).set('chat_status', 'dnd');
  722. }
  723. for (i=6; i<9; i++) {
  724. jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
  725. _converse.roster.get(jid).set('chat_status', 'away');
  726. }
  727. for (i=9; i<12; i++) {
  728. jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
  729. _converse.roster.get(jid).set('chat_status', 'xa');
  730. }
  731. for (i=12; i<15; i++) {
  732. jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
  733. _converse.roster.get(jid).set('chat_status', 'unavailable');
  734. }
  735. var contacts = _converse.rosterview.$el.find('dd.current-xmpp-contact');
  736. for (i=0; i<3; i++) {
  737. expect($(contacts[i]).hasClass('online')).toBeTruthy();
  738. expect($(contacts[i]).hasClass('both')).toBeTruthy();
  739. expect($(contacts[i]).hasClass('dnd')).toBeFalsy();
  740. expect($(contacts[i]).hasClass('away')).toBeFalsy();
  741. expect($(contacts[i]).hasClass('xa')).toBeFalsy();
  742. expect($(contacts[i]).hasClass('unavailable')).toBeFalsy();
  743. expect($(contacts[i]).hasClass('offline')).toBeFalsy();
  744. }
  745. for (i=3; i<6; i++) {
  746. expect($(contacts[i]).hasClass('dnd')).toBeTruthy();
  747. expect($(contacts[i]).hasClass('both')).toBeTruthy();
  748. expect($(contacts[i]).hasClass('online')).toBeFalsy();
  749. expect($(contacts[i]).hasClass('away')).toBeFalsy();
  750. expect($(contacts[i]).hasClass('xa')).toBeFalsy();
  751. expect($(contacts[i]).hasClass('unavailable')).toBeFalsy();
  752. expect($(contacts[i]).hasClass('offline')).toBeFalsy();
  753. }
  754. for (i=6; i<9; i++) {
  755. expect($(contacts[i]).hasClass('away')).toBeTruthy();
  756. expect($(contacts[i]).hasClass('both')).toBeTruthy();
  757. expect($(contacts[i]).hasClass('online')).toBeFalsy();
  758. expect($(contacts[i]).hasClass('dnd')).toBeFalsy();
  759. expect($(contacts[i]).hasClass('xa')).toBeFalsy();
  760. expect($(contacts[i]).hasClass('unavailable')).toBeFalsy();
  761. expect($(contacts[i]).hasClass('offline')).toBeFalsy();
  762. }
  763. for (i=9; i<12; i++) {
  764. expect($(contacts[i]).hasClass('xa')).toBeTruthy();
  765. expect($(contacts[i]).hasClass('both')).toBeTruthy();
  766. expect($(contacts[i]).hasClass('online')).toBeFalsy();
  767. expect($(contacts[i]).hasClass('dnd')).toBeFalsy();
  768. expect($(contacts[i]).hasClass('away')).toBeFalsy();
  769. expect($(contacts[i]).hasClass('unavailable')).toBeFalsy();
  770. expect($(contacts[i]).hasClass('offline')).toBeFalsy();
  771. }
  772. for (i=12; i<15; i++) {
  773. expect($(contacts[i]).hasClass('unavailable')).toBeTruthy();
  774. expect($(contacts[i]).hasClass('both')).toBeTruthy();
  775. expect($(contacts[i]).hasClass('online')).toBeFalsy();
  776. expect($(contacts[i]).hasClass('dnd')).toBeFalsy();
  777. expect($(contacts[i]).hasClass('away')).toBeFalsy();
  778. expect($(contacts[i]).hasClass('xa')).toBeFalsy();
  779. expect($(contacts[i]).hasClass('offline')).toBeFalsy();
  780. }
  781. for (i=15; i<mock.cur_names.length; i++) {
  782. expect($(contacts[i]).hasClass('offline')).toBeTruthy();
  783. expect($(contacts[i]).hasClass('both')).toBeTruthy();
  784. expect($(contacts[i]).hasClass('online')).toBeFalsy();
  785. expect($(contacts[i]).hasClass('dnd')).toBeFalsy();
  786. expect($(contacts[i]).hasClass('away')).toBeFalsy();
  787. expect($(contacts[i]).hasClass('xa')).toBeFalsy();
  788. expect($(contacts[i]).hasClass('unavailable')).toBeFalsy();
  789. }
  790. done();
  791. });
  792. }));
  793. });
  794. describe("Requesting Contacts", function () {
  795. it("can be added to the roster and they will be sorted alphabetically", mock.initConverse(function (_converse) {
  796. var i, children;
  797. var names = [];
  798. var addName = function (idx, item) {
  799. if (!$(item).hasClass('request-actions')) {
  800. names.push($(item).text().replace(/^\s+|\s+$/g, ''));
  801. }
  802. };
  803. test_utils.openContactsPanel(_converse);
  804. spyOn(_converse, 'emit');
  805. spyOn(_converse.rosterview, 'update').and.callThrough();
  806. spyOn(_converse.controlboxtoggle, 'showControlBox').and.callThrough();
  807. for (i=0; i<mock.req_names.length; i++) {
  808. _converse.roster.create({
  809. jid: mock.req_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
  810. subscription: 'none',
  811. ask: null,
  812. requesting: true,
  813. fullname: mock.req_names[i]
  814. });
  815. }
  816. expect(_converse.rosterview.update).toHaveBeenCalled();
  817. // Check that they are sorted alphabetically
  818. children = _converse.rosterview.get('Contact requests').$el.siblings('dd.requesting-xmpp-contact').find('span');
  819. names = [];
  820. children.each(addName);
  821. expect(names.join('')).toEqual(mock.req_names.slice(0,mock.req_names.length+1).sort().join(''));
  822. }));
  823. it("do not have a header if there aren't any", mock.initConverseWithAsync(function (done, _converse) {
  824. test_utils.openContactsPanel(_converse);
  825. var name = mock.req_names[0];
  826. spyOn(window, 'confirm').and.returnValue(true);
  827. _converse.roster.create({
  828. jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
  829. subscription: 'none',
  830. ask: null,
  831. requesting: true,
  832. fullname: name
  833. });
  834. test_utils.waitUntil(function () {
  835. return _converse.rosterview.$el.find('dt').length;
  836. }, 500)
  837. .then(function () {
  838. expect(_converse.rosterview.get('Contact requests').$el.is(':visible')).toEqual(true);
  839. _converse.rosterview.$el.find(".req-contact-name:contains('"+name+"')")
  840. .parent().siblings('.request-actions')
  841. .find('.decline-xmpp-request').click();
  842. expect(window.confirm).toHaveBeenCalled();
  843. expect(_converse.rosterview.get('Contact requests').$el.is(':visible')).toEqual(false);
  844. done();
  845. });
  846. }));
  847. it("can be collapsed under their own header", mock.initConverseWithAsync(function (done, _converse) {
  848. test_utils.createContacts(_converse, 'requesting').openControlBox();
  849. test_utils.waitUntil(function () {
  850. return _converse.rosterview.$el.find('dt').length;
  851. }, 500)
  852. .then(function () {
  853. checkHeaderToggling.apply(_converse, [_converse.rosterview.get('Contact requests').$el]);
  854. done();
  855. });
  856. }));
  857. it("can have their requests accepted by the user", mock.initConverseWithAsync(function (done, _converse) {
  858. test_utils.createContacts(_converse, 'requesting').openControlBox();
  859. test_utils.waitUntil(function () {
  860. return _converse.rosterview.$el.find('dt').length;
  861. }, 500)
  862. .then(function () {
  863. // TODO: Testing can be more thorough here, the user is
  864. // actually not accepted/authorized because of
  865. // mock_connection.
  866. var name = mock.req_names.sort()[0];
  867. var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
  868. var contact = _converse.roster.get(jid);
  869. spyOn(_converse.roster, 'sendContactAddIQ').and.callFake(function (jid, fullname, groups, callback) {
  870. callback();
  871. });
  872. spyOn(contact, 'authorize').and.callFake(function () { return contact; });
  873. _converse.rosterview.$el.find(".req-contact-name:contains('"+name+"')")
  874. .parent().siblings('.request-actions')
  875. .find('.accept-xmpp-request').click();
  876. expect(_converse.roster.sendContactAddIQ).toHaveBeenCalled();
  877. expect(contact.authorize).toHaveBeenCalled();
  878. done();
  879. });
  880. }));
  881. it("can have their requests denied by the user", mock.initConverseWithAsync(function (done, _converse) {
  882. test_utils.createContacts(_converse, 'requesting').openControlBox();
  883. test_utils.waitUntil(function () {
  884. return _converse.rosterview.$el.find('dt').length;
  885. }, 500)
  886. .then(function () {
  887. _converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced.
  888. var name = mock.req_names.sort()[1];
  889. var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
  890. var contact = _converse.roster.get(jid);
  891. spyOn(window, 'confirm').and.returnValue(true);
  892. spyOn(contact, 'unauthorize').and.callFake(function () { return contact; });
  893. _converse.rosterview.$el.find(".req-contact-name:contains('"+name+"')")
  894. .parent().siblings('.request-actions')
  895. .find('.decline-xmpp-request').click();
  896. expect(window.confirm).toHaveBeenCalled();
  897. expect(contact.unauthorize).toHaveBeenCalled();
  898. // There should now be one less contact
  899. expect(_converse.roster.length).toEqual(mock.req_names.length-1);
  900. done();
  901. });
  902. }));
  903. it("are persisted even if other contacts' change their presence ", mock.initConverse(function (_converse) {
  904. /* This is a regression test.
  905. * https://github.com/jcbrand/_converse.js/issues/262
  906. */
  907. expect(_converse.roster.pluck('jid').length).toBe(0);
  908. var stanza = $pres({from: 'data@enterprise/resource', type: 'subscribe'});
  909. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  910. expect(_converse.roster.pluck('jid').length).toBe(1);
  911. expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
  912. // Taken from the spec
  913. // http://xmpp.org/rfcs/rfc3921.html#rfc.section.7.3
  914. stanza = $iq({
  915. to: _converse.connection.jid,
  916. type: 'result',
  917. id: 'roster_1'
  918. }).c('query', {
  919. xmlns: 'jabber:iq:roster',
  920. }).c('item', {
  921. jid: 'romeo@example.net',
  922. name: 'Romeo',
  923. subscription:'both'
  924. }).c('group').t('Friends').up().up()
  925. .c('item', {
  926. jid: 'mercutio@example.org',
  927. name: 'Mercutio',
  928. subscription:'from'
  929. }).c('group').t('Friends').up().up()
  930. .c('item', {
  931. jid: 'benvolio@example.org',
  932. name: 'Benvolio',
  933. subscription:'both'
  934. }).c('group').t('Friends');
  935. _converse.roster.onReceivedFromServer(stanza.tree());
  936. expect(_.includes(_converse.roster.pluck('jid'), 'data@enterprise')).toBeTruthy();
  937. }));
  938. });
  939. describe("All Contacts", function () {
  940. it("are saved to, and can be retrieved from browserStorage", mock.initConverse(function (_converse) {
  941. test_utils.createContacts(_converse, 'all').openControlBox();
  942. test_utils.openContactsPanel(_converse);
  943. var new_attrs, old_attrs, attrs;
  944. var num_contacts = _converse.roster.length;
  945. var new_roster = new _converse.RosterContacts();
  946. // Roster items are yet to be fetched from browserStorage
  947. expect(new_roster.length).toEqual(0);
  948. new_roster.browserStorage = _converse.roster.browserStorage;
  949. new_roster.fetch();
  950. expect(new_roster.length).toEqual(num_contacts);
  951. // Check that the roster items retrieved from browserStorage
  952. // have the same attributes values as the original ones.
  953. attrs = ['jid', 'fullname', 'subscription', 'ask'];
  954. for (var i=0; i<attrs.length; i++) {
  955. new_attrs = _.map(_.map(new_roster.models, 'attributes'), attrs[i]);
  956. old_attrs = _.map(_.map(_converse.roster.models, 'attributes'), attrs[i]);
  957. // Roster items in storage are not necessarily sorted,
  958. // so we have to sort them here to do a proper
  959. // comparison
  960. expect(_.isEqual(new_attrs.sort(), old_attrs.sort())).toEqual(true);
  961. }
  962. }));
  963. it("will show fullname and jid properties on tooltip", mock.initConverseWithAsync(function (done, _converse) {
  964. test_utils.createContacts(_converse, 'all').openControlBox();
  965. test_utils.openContactsPanel(_converse);
  966. test_utils.waitUntil(function () {
  967. return _converse.rosterview.$el.find('dt').length;
  968. }, 500)
  969. .then(function () {
  970. var jid, name, i;
  971. for (i=0; i<mock.cur_names.length; i++) {
  972. name = mock.cur_names[i];
  973. jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
  974. var $dd = _converse.rosterview.$el.find("dd:contains('"+name+"')").children().first();
  975. var dd_text = $dd.text();
  976. var dd_title = $dd.attr('title');
  977. expect(dd_text).toBe(name);
  978. expect(dd_title).toContain(name);
  979. expect(dd_title).toContain(jid);
  980. }
  981. done();
  982. });
  983. }));
  984. });
  985. });
  986. describe("The 'Add Contact' widget", function () {
  987. it("opens up an add form when you click on it", mock.initConverse(function (_converse) {
  988. var panel = _converse.chatboxviews.get('controlbox').contactspanel;
  989. spyOn(panel, 'toggleContactForm').and.callThrough();
  990. panel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  991. panel.$el.find('a.toggle-xmpp-contact-form').click();
  992. expect(panel.toggleContactForm).toHaveBeenCalled();
  993. // XXX: Awaiting more tests, close it again for now...
  994. panel.$el.find('a.toggle-xmpp-contact-form').click();
  995. }));
  996. it("can be used to add contact and it checks for case-sensivity", mock.initConverseWithAsync(function (done, _converse) {
  997. spyOn(_converse, 'emit');
  998. spyOn(_converse.rosterview, 'update').and.callThrough();
  999. test_utils.openControlBox();
  1000. // Adding two contacts one with Capital initials and one with small initials of same JID (Case sensitive check)
  1001. _converse.roster.create({
  1002. jid: mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
  1003. subscription: 'none',
  1004. ask: 'subscribe',
  1005. fullname: mock.pend_names[0]
  1006. });
  1007. _converse.roster.create({
  1008. jid: mock.pend_names[0].replace(/ /g,'.') + '@localhost',
  1009. subscription: 'none',
  1010. ask: 'subscribe',
  1011. fullname: mock.pend_names[0]
  1012. });
  1013. test_utils.waitUntil(function () {
  1014. return _converse.rosterview.$el.find('dt').length;
  1015. }, 500)
  1016. .then(function () {
  1017. // Checking that only one entry is created because both JID is same (Case sensitive check)
  1018. expect(_converse.rosterview.$el.find('dd:visible').length).toBe(1);
  1019. expect(_converse.rosterview.update).toHaveBeenCalled();
  1020. done();
  1021. });
  1022. }));
  1023. });
  1024. describe("The Controlbox Tabs", function () {
  1025. it("contains two tabs, 'Contacts' and 'ChatRooms'", mock.initConverse(function (_converse) {
  1026. test_utils.openControlBox();
  1027. var cbview = _converse.chatboxviews.get('controlbox');
  1028. var $panels = cbview.$el.find('.controlbox-panes');
  1029. expect($panels.children().length).toBe(2);
  1030. expect($panels.children().first().attr('id')).toBe('users');
  1031. expect($panels.children().first().is(':visible')).toBe(true);
  1032. expect($panels.children().last().attr('id')).toBe('chatrooms');
  1033. expect($panels.children().last().is(':visible')).toBe(false);
  1034. }));
  1035. it("remembers which tab was open last", mock.initConverse(function (_converse) {
  1036. test_utils.openControlBox();
  1037. var cbview = _converse.chatboxviews.get('controlbox');
  1038. var $tabs = cbview.$el.find('#controlbox-tabs');
  1039. expect(cbview.model.get('active-panel')).toBe('users');
  1040. $tabs.find('li').last().find('a').click();
  1041. expect(cbview.model.get('active-panel')).toBe('chatrooms');
  1042. $tabs.find('li').first().find('a').click();
  1043. expect(cbview.model.get('active-panel')).toBe('users');
  1044. }));
  1045. describe("chatrooms panel", function () {
  1046. it("is opened by clicking the 'Chatrooms' tab", mock.initConverse(function (_converse) {
  1047. test_utils.openControlBox();
  1048. var cbview = _converse.chatboxviews.get('controlbox');
  1049. var $tabs = cbview.$el.find('#controlbox-tabs');
  1050. var $panels = cbview.$el.find('.controlbox-panes');
  1051. var $contacts = $panels.children().first();
  1052. var $chatrooms = $panels.children().last();
  1053. spyOn(cbview, 'switchTab').and.callThrough();
  1054. cbview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  1055. $tabs.find('li').last().find('a').click(); // Clicks the chatrooms tab
  1056. expect($contacts.is(':visible')).toBe(false);
  1057. expect($chatrooms.is(':visible')).toBe(true);
  1058. expect(cbview.switchTab).toHaveBeenCalled();
  1059. }));
  1060. it("contains a form through which a new chatroom can be created", mock.initConverse(function (_converse) {
  1061. test_utils.openControlBox();
  1062. var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
  1063. var $input = roomspanel.$el.find('input.new-chatroom-name');
  1064. var $nick = roomspanel.$el.find('input.new-chatroom-nick');
  1065. var $server = roomspanel.$el.find('input.new-chatroom-server');
  1066. expect($input.length).toBe(1);
  1067. expect($server.length).toBe(1);
  1068. expect($('.chatroom:visible').length).toBe(0); // There shouldn't be any chatrooms open currently
  1069. spyOn(roomspanel, 'createChatRoom').and.callThrough();
  1070. spyOn(_converse.ChatRoomView.prototype, 'getRoomFeatures').and.callFake(function () {
  1071. var deferred = new $.Deferred();
  1072. deferred.resolve();
  1073. return deferred.promise();
  1074. });
  1075. roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  1076. $input.val('Lounge');
  1077. $nick.val('dummy');
  1078. $server.val('muc.localhost');
  1079. roomspanel.$el.find('form').submit();
  1080. expect(roomspanel.createChatRoom).toHaveBeenCalled();
  1081. expect($('.chatroom:visible').length).toBe(1); // There should now be an open chatroom
  1082. }));
  1083. it("can list rooms publically available on the server", mock.initConverse(function (_converse) {
  1084. test_utils.openControlBox();
  1085. var panel = _converse.chatboxviews.get('controlbox').roomspanel;
  1086. panel.$tabs.find('li').last().find('a').click(); // Click the chatrooms tab
  1087. panel.model.set({'muc_domain': 'muc.localhost'}); // Make sure the domain is set
  1088. // See: http://xmpp.org/extensions/xep-0045.html#disco-rooms
  1089. expect($('#available-chatrooms').children('dt').length).toBe(0);
  1090. expect($('#available-chatrooms').children('dd').length).toBe(0);
  1091. var iq = $iq({
  1092. from:'muc.localhost',
  1093. to:'dummy@localhost/pda',
  1094. type:'result'
  1095. }).c('query')
  1096. .c('item', { jid:'heath@chat.shakespeare.lit', name:'A Lonely Heath'}).up()
  1097. .c('item', { jid:'coven@chat.shakespeare.lit', name:'A Dark Cave'}).up()
  1098. .c('item', { jid:'forres@chat.shakespeare.lit', name:'The Palace'}).up()
  1099. .c('item', { jid:'inverness@chat.shakespeare.lit', name:'Macbeth&apos;s Castle'}).nodeTree;
  1100. panel.onRoomsFound(iq);
  1101. expect(panel.$('#available-chatrooms').children('dt').length).toBe(1);
  1102. expect(panel.$('#available-chatrooms').children('dt').first().text()).toBe("Rooms on muc.localhost");
  1103. expect(panel.$('#available-chatrooms').children('dd').length).toBe(4);
  1104. }));
  1105. });
  1106. });
  1107. }));