roster.js 65 KB

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