2
0

roster.js 65 KB

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