roster.js 66 KB

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