2
0

roster.js 68 KB

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