roster.js 68 KB

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