controlbox.js 70 KB

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