roster.js 68 KB

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