roster.js 69 KB

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