2
0

roster.js 68 KB

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