2
0

bookmarks.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. /*global waitUntilPromise */
  2. (function (root, factory) {
  3. define([
  4. "jasmine",
  5. "mock",
  6. "test-utils"
  7. ], factory);
  8. } (this, function (jasmine, mock, test_utils) {
  9. "use strict";
  10. const $iq = converse.env.$iq,
  11. $msg = converse.env.$msg,
  12. Backbone = converse.env.Backbone,
  13. Strophe = converse.env.Strophe,
  14. sizzle = converse.env.sizzle,
  15. _ = converse.env._,
  16. u = converse.env.utils;
  17. describe("A chat room", function () {
  18. it("can be bookmarked", mock.initConverse(
  19. null, ['rosterGroupsFetched'], {},
  20. async function (done, _converse) {
  21. await test_utils.waitUntilDiscoConfirmed(
  22. _converse, _converse.bare_jid,
  23. [{'category': 'pubsub', 'type': 'pep'}],
  24. ['http://jabber.org/protocol/pubsub#publish-options']
  25. );
  26. let sent_stanza, IQ_id;
  27. const sendIQ = _converse.connection.sendIQ;
  28. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  29. sent_stanza = iq;
  30. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  31. });
  32. spyOn(_converse.connection, 'getUniqueId').and.callThrough();
  33. await test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC');
  34. var jid = 'theplay@conference.shakespeare.lit';
  35. const view = _converse.chatboxviews.get(jid);
  36. spyOn(view, 'renderBookmarkForm').and.callThrough();
  37. spyOn(view, 'closeForm').and.callThrough();
  38. await test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark')));
  39. let toggle = view.el.querySelector('.toggle-bookmark');
  40. expect(toggle.title).toBe('Bookmark this groupchat');
  41. toggle.click();
  42. expect(view.renderBookmarkForm).toHaveBeenCalled();
  43. view.el.querySelector('.button-cancel').click();
  44. expect(view.closeForm).toHaveBeenCalled();
  45. expect(u.hasClass('on-button', toggle), false);
  46. expect(toggle.title).toBe('Bookmark this groupchat');
  47. toggle.click();
  48. expect(view.renderBookmarkForm).toHaveBeenCalled();
  49. /* Client uploads data:
  50. * --------------------
  51. * <iq from='juliet@capulet.lit/balcony' type='set' id='pip1'>
  52. * <pubsub xmlns='http://jabber.org/protocol/pubsub'>
  53. * <publish node='storage:bookmarks'>
  54. * <item id='current'>
  55. * <storage xmlns='storage:bookmarks'>
  56. * <conference name='The Play&apos;s the Thing'
  57. * autojoin='true'
  58. * jid='theplay@conference.shakespeare.lit'>
  59. * <nick>JC</nick>
  60. * </conference>
  61. * </storage>
  62. * </item>
  63. * </publish>
  64. * <publish-options>
  65. * <x xmlns='jabber:x:data' type='submit'>
  66. * <field var='FORM_TYPE' type='hidden'>
  67. * <value>http://jabber.org/protocol/pubsub#publish-options</value>
  68. * </field>
  69. * <field var='pubsub#persist_items'>
  70. * <value>true</value>
  71. * </field>
  72. * <field var='pubsub#access_model'>
  73. * <value>whitelist</value>
  74. * </field>
  75. * </x>
  76. * </publish-options>
  77. * </pubsub>
  78. * </iq>
  79. */
  80. expect(view.model.get('bookmarked')).toBeFalsy();
  81. const form = view.el.querySelector('.chatroom-form');
  82. form.querySelector('input[name="name"]').value = 'Play&apos;s the Thing';
  83. form.querySelector('input[name="autojoin"]').checked = 'checked';
  84. form.querySelector('input[name="nick"]').value = 'JC';
  85. _converse.connection.IQ_stanzas = [];
  86. view.el.querySelector('.btn-primary').click();
  87. await test_utils.waitUntil(() => sent_stanza);
  88. expect(sent_stanza.toLocaleString()).toBe(
  89. `<iq from="dummy@localhost/resource" id="${IQ_id}" type="set" xmlns="jabber:client">`+
  90. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  91. `<publish node="storage:bookmarks">`+
  92. `<item id="current">`+
  93. `<storage xmlns="storage:bookmarks">`+
  94. `<conference autojoin="true" jid="theplay@conference.shakespeare.lit" name="Play&amp;apos;s the Thing">`+
  95. `<nick>JC</nick>`+
  96. `</conference>`+
  97. `</storage>`+
  98. `</item>`+
  99. `</publish>`+
  100. `<publish-options>`+
  101. `<x type="submit" xmlns="jabber:x:data">`+
  102. `<field type="hidden" var="FORM_TYPE">`+
  103. `<value>http://jabber.org/protocol/pubsub#publish-options</value>`+
  104. `</field>`+
  105. `<field var="pubsub#persist_items">`+
  106. `<value>true</value>`+
  107. `</field>`+
  108. `<field var="pubsub#access_model">`+
  109. `<value>whitelist</value>`+
  110. `</field>`+
  111. `</x>`+
  112. `</publish-options>`+
  113. `</pubsub>`+
  114. `</iq>`
  115. );
  116. /* Server acknowledges successful storage
  117. *
  118. * <iq to='juliet@capulet.lit/balcony' type='result' id='pip1'/>
  119. */
  120. const stanza = $iq({
  121. 'to':_converse.connection.jid,
  122. 'type':'result',
  123. 'id':IQ_id
  124. });
  125. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  126. await test_utils.waitUntil(() => view.model.get('bookmarked'));
  127. toggle = view.el.querySelector('.toggle-bookmark');
  128. expect(view.model.get('bookmarked')).toBeTruthy();
  129. expect(toggle.title).toBe('Unbookmark this groupchat');
  130. expect(u.hasClass('on-button', toggle), true);
  131. // We ignore this IQ stanza... (unless it's an error stanza), so
  132. // nothing to test for here.
  133. done();
  134. }));
  135. it("will be automatically opened if 'autojoin' is set on the bookmark", mock.initConverse(
  136. null, ['rosterGroupsFetched'], {},
  137. async function (done, _converse) {
  138. await test_utils.waitUntilDiscoConfirmed(
  139. _converse, _converse.bare_jid,
  140. [{'category': 'pubsub', 'type': 'pep'}],
  141. ['http://jabber.org/protocol/pubsub#publish-options']
  142. );
  143. await test_utils.waitUntil(() => _converse.bookmarks);
  144. let jid = 'lounge@localhost';
  145. _converse.bookmarks.create({
  146. 'jid': jid,
  147. 'autojoin': false,
  148. 'name': 'The Lounge',
  149. 'nick': ' Othello'
  150. });
  151. expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBeTruthy();
  152. jid = 'theplay@conference.shakespeare.lit';
  153. _converse.bookmarks.create({
  154. 'jid': jid,
  155. 'autojoin': true,
  156. 'name': 'The Play',
  157. 'nick': ' Othello'
  158. });
  159. expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBeFalsy();
  160. // Check that we don't auto-join if muc_respect_autojoin is false
  161. _converse.muc_respect_autojoin = false;
  162. jid = 'balcony@conference.shakespeare.lit';
  163. _converse.bookmarks.create({
  164. 'jid': jid,
  165. 'autojoin': true,
  166. 'name': 'Balcony',
  167. 'nick': ' Othello'
  168. });
  169. expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBe(true);
  170. done();
  171. }));
  172. describe("when bookmarked", function () {
  173. it("will use the nickname from the bookmark", mock.initConverse(
  174. null, ['rosterGroupsFetched'], {},
  175. async function (done, _converse) {
  176. await test_utils.waitUntilDiscoConfirmed(
  177. _converse, _converse.bare_jid,
  178. [{'category': 'pubsub', 'type': 'pep'}],
  179. ['http://jabber.org/protocol/pubsub#publish-options']
  180. );
  181. const room_jid = 'coven@chat.shakespeare.lit';
  182. await test_utils.waitUntil(() => _converse.bookmarks);
  183. _converse.bookmarks.create({
  184. 'jid': room_jid,
  185. 'autojoin': false,
  186. 'name': 'The Play',
  187. 'nick': 'Othello'
  188. });
  189. const model = await _converse.api.rooms.open(room_jid);
  190. spyOn(model, 'join').and.callThrough();
  191. await test_utils.getRoomFeatures(_converse, 'coven', 'chat.shakespeare.lit');
  192. await test_utils.waitUntil(() => model.join.calls.count());
  193. expect(model.get('nick')).toBe('Othello');
  194. done();
  195. }));
  196. it("displays that it's bookmarked through its bookmark icon", mock.initConverse(
  197. null, ['rosterGroupsFetched'], {},
  198. async function (done, _converse) {
  199. test_utils.waitUntilDiscoConfirmed(
  200. _converse, _converse.bare_jid,
  201. [{'category': 'pubsub', 'type': 'pep'}],
  202. ['http://jabber.org/protocol/pubsub#publish-options']
  203. );
  204. await _converse.api.rooms.open(`lounge@localhost`);
  205. const view = _converse.chatboxviews.get('lounge@localhost');
  206. await test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark')));
  207. var bookmark_icon = view.el.querySelector('.toggle-bookmark');
  208. expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy();
  209. view.model.set('bookmarked', true);
  210. expect(_.includes(bookmark_icon.classList, 'button-on')).toBeTruthy();
  211. view.model.set('bookmarked', false);
  212. expect(_.includes(bookmark_icon.classList, 'button-on')).toBeFalsy();
  213. done();
  214. }));
  215. it("can be unbookmarked", mock.initConverse(
  216. null, ['rosterGroupsFetched'], {},
  217. async function (done, _converse) {
  218. let sent_stanza, IQ_id;
  219. await test_utils.waitUntilDiscoConfirmed(
  220. _converse, _converse.bare_jid,
  221. [{'category': 'pubsub', 'type': 'pep'}],
  222. ['http://jabber.org/protocol/pubsub#publish-options']
  223. );
  224. const sendIQ = _converse.connection.sendIQ;
  225. await test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC');
  226. const jid = 'theplay@conference.shakespeare.lit';
  227. const view = _converse.chatboxviews.get(jid);
  228. await test_utils.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark')));
  229. spyOn(view, 'toggleBookmark').and.callThrough();
  230. spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough();
  231. view.delegateEvents();
  232. _converse.bookmarks.create({
  233. 'jid': view.model.get('jid'),
  234. 'autojoin': false,
  235. 'name': 'The Play',
  236. 'nick': ' Othello'
  237. });
  238. expect(_converse.bookmarks.length).toBe(1);
  239. expect(view.model.get('bookmarked')).toBeTruthy();
  240. var bookmark_icon = view.el.querySelector('.toggle-bookmark');
  241. expect(u.hasClass('button-on', bookmark_icon)).toBeTruthy();
  242. spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
  243. sent_stanza = iq;
  244. IQ_id = sendIQ.bind(this)(iq, callback, errback);
  245. });
  246. spyOn(_converse.connection, 'getUniqueId').and.callThrough();
  247. bookmark_icon.click();
  248. expect(view.toggleBookmark).toHaveBeenCalled();
  249. expect(u.hasClass('button-on', bookmark_icon)).toBeFalsy();
  250. expect(_converse.bookmarks.length).toBe(0);
  251. // Check that an IQ stanza is sent out, containing no
  252. // conferences to bookmark (since we removed the one and
  253. // only bookmark).
  254. expect(sent_stanza.toLocaleString()).toBe(
  255. `<iq from="dummy@localhost/resource" id="${IQ_id}" type="set" xmlns="jabber:client">`+
  256. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  257. `<publish node="storage:bookmarks">`+
  258. `<item id="current">`+
  259. `<storage xmlns="storage:bookmarks"/>`+
  260. `</item>`+
  261. `</publish>`+
  262. `<publish-options>`+
  263. `<x type="submit" xmlns="jabber:x:data">`+
  264. `<field type="hidden" var="FORM_TYPE">`+
  265. `<value>http://jabber.org/protocol/pubsub#publish-options</value>`+
  266. `</field>`+
  267. `<field var="pubsub#persist_items">`+
  268. `<value>true</value>`+
  269. `</field>`+
  270. `<field var="pubsub#access_model">`+
  271. `<value>whitelist</value>`+
  272. `</field>`+
  273. `</x>`+
  274. `</publish-options>`+
  275. `</pubsub>`+
  276. `</iq>`
  277. );
  278. done();
  279. }));
  280. });
  281. describe("and when autojoin is set", function () {
  282. it("will be be opened and joined automatically upon login", mock.initConverse(
  283. null, ['rosterGroupsFetched'], {},
  284. async function (done, _converse) {
  285. await test_utils.waitUntilDiscoConfirmed(
  286. _converse, _converse.bare_jid,
  287. [{'category': 'pubsub', 'type': 'pep'}],
  288. ['http://jabber.org/protocol/pubsub#publish-options']
  289. );
  290. spyOn(_converse.api.rooms, 'create').and.callThrough();
  291. const jid = 'theplay@conference.shakespeare.lit';
  292. const model = _converse.bookmarks.create({
  293. 'jid': jid,
  294. 'autojoin': false,
  295. 'name': 'The Play',
  296. 'nick': ''
  297. });
  298. expect(_converse.api.rooms.create).not.toHaveBeenCalled();
  299. _converse.bookmarks.remove(model);
  300. _converse.bookmarks.create({
  301. 'jid': jid,
  302. 'autojoin': true,
  303. 'name': 'Hamlet',
  304. 'nick': ''
  305. });
  306. expect(_converse.api.rooms.create).toHaveBeenCalled();
  307. done();
  308. }));
  309. });
  310. });
  311. describe("Bookmarks", function () {
  312. it("can be pushed from the XMPP server", mock.initConverse(
  313. ['send'], ['rosterGroupsFetched', 'connected'], {},
  314. async function (done, _converse) {
  315. await test_utils.waitUntilDiscoConfirmed(
  316. _converse, _converse.bare_jid,
  317. [{'category': 'pubsub', 'type': 'pep'}],
  318. ['http://jabber.org/protocol/pubsub#publish-options']
  319. );
  320. await test_utils.waitUntil(() => _converse.bookmarks);
  321. // Emit here instead of mocking fetching of bookmarks.
  322. _converse.api.trigger('bookmarksInitialized');
  323. /* The stored data is automatically pushed to all of the user's
  324. * connected resources.
  325. *
  326. * Publisher receives event notification
  327. * -------------------------------------
  328. * <message from='juliet@capulet.lit'
  329. * to='juliet@capulet.lit/balcony'
  330. * type='headline'
  331. * id='rnfoo1'>
  332. * <event xmlns='http://jabber.org/protocol/pubsub#event'>
  333. * <items node='storage:bookmarks'>
  334. * <item id='current'>
  335. * <storage xmlns='storage:bookmarks'>
  336. * <conference name='The Play&apos;s the Thing'
  337. * autojoin='true'
  338. * jid='theplay@conference.shakespeare.lit'>
  339. * <nick>JC</nick>
  340. * </conference>
  341. * </storage>
  342. * </item>
  343. * </items>
  344. * </event>
  345. * </message>
  346. */
  347. var stanza = $msg({
  348. 'from': 'dummy@localhost',
  349. 'to': 'dummy@localhost/resource',
  350. 'type': 'headline',
  351. 'id': 'rnfoo1'
  352. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  353. .c('items', {'node': 'storage:bookmarks'})
  354. .c('item', {'id': 'current'})
  355. .c('storage', {'xmlns': 'storage:bookmarks'})
  356. .c('conference', {'name': 'The Play&apos;s the Thing',
  357. 'autojoin': 'true',
  358. 'jid':'theplay@conference.shakespeare.lit'})
  359. .c('nick').t('JC');
  360. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  361. await test_utils.waitUntil(() => _converse.bookmarks.length);
  362. expect(_converse.bookmarks.length).toBe(1);
  363. expect(_converse.chatboxviews.get('theplay@conference.shakespeare.lit')).not.toBeUndefined();
  364. done();
  365. }));
  366. it("can be retrieved from the XMPP server", mock.initConverse(
  367. {'connection': ['send']}, ['chatBoxesFetched', 'roomsPanelRendered', 'rosterGroupsFetched'], {},
  368. async function (done, _converse) {
  369. await test_utils.waitUntilDiscoConfirmed(
  370. _converse, _converse.bare_jid,
  371. [{'category': 'pubsub', 'type': 'pep'}],
  372. ['http://jabber.org/protocol/pubsub#publish-options']
  373. );
  374. /* Client requests all items
  375. * -------------------------
  376. *
  377. * <iq from='juliet@capulet.lit/randomID' type='get' id='retrieve1'>
  378. * <pubsub xmlns='http://jabber.org/protocol/pubsub'>
  379. * <items node='storage:bookmarks'/>
  380. * </pubsub>
  381. * </iq>
  382. */
  383. let IQ_id;
  384. const call = await test_utils.waitUntil(() =>
  385. _.filter(
  386. _converse.connection.send.calls.all(),
  387. call => {
  388. const stanza = call.args[0];
  389. if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
  390. return;
  391. }
  392. if (sizzle('items[node="storage:bookmarks"]', stanza).length) {
  393. IQ_id = stanza.getAttribute('id');
  394. return true;
  395. }
  396. }
  397. ).pop()
  398. );
  399. expect(Strophe.serialize(call.args[0])).toBe(
  400. `<iq from="dummy@localhost/resource" id="${IQ_id}" type="get" xmlns="jabber:client">`+
  401. '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
  402. '<items node="storage:bookmarks"/>'+
  403. '</pubsub>'+
  404. '</iq>');
  405. /*
  406. * Server returns all items
  407. * ------------------------
  408. * <iq type='result'
  409. * to='juliet@capulet.lit/randomID'
  410. * id='retrieve1'>
  411. * <pubsub xmlns='http://jabber.org/protocol/pubsub'>
  412. * <items node='storage:bookmarks'>
  413. * <item id='current'>
  414. * <storage xmlns='storage:bookmarks'>
  415. * <conference name='The Play&apos;s the Thing'
  416. * autojoin='true'
  417. * jid='theplay@conference.shakespeare.lit'>
  418. * <nick>JC</nick>
  419. * </conference>
  420. * </storage>
  421. * </item>
  422. * </items>
  423. * </pubsub>
  424. * </iq>
  425. */
  426. expect(_converse.bookmarks.models.length).toBe(0);
  427. spyOn(_converse.bookmarks, 'onBookmarksReceived').and.callThrough();
  428. var stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id})
  429. .c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
  430. .c('items', {'node': 'storage:bookmarks'})
  431. .c('item', {'id': 'current'})
  432. .c('storage', {'xmlns': 'storage:bookmarks'})
  433. .c('conference', {
  434. 'name': 'The Play&apos;s the Thing',
  435. 'autojoin': 'true',
  436. 'jid': 'theplay@conference.shakespeare.lit'
  437. }).c('nick').t('JC').up().up()
  438. .c('conference', {
  439. 'name': 'Another room',
  440. 'autojoin': 'false',
  441. 'jid': 'another@conference.shakespeare.lit'
  442. }); // Purposefully exclude the <nick> element to test #1043
  443. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  444. await test_utils.waitUntil(() => _converse.bookmarks.onBookmarksReceived.calls.count());
  445. expect(_converse.bookmarks.models.length).toBe(2);
  446. expect(_converse.bookmarks.findWhere({'jid': 'theplay@conference.shakespeare.lit'}).get('autojoin')).toBe(true);
  447. expect(_converse.bookmarks.findWhere({'jid': 'another@conference.shakespeare.lit'}).get('autojoin')).toBe(false);
  448. done();
  449. }));
  450. describe("The rooms panel", function () {
  451. it("shows a list of bookmarks", mock.initConverse(
  452. {'connection': ['send']}, ['rosterGroupsFetched'], {},
  453. async function (done, _converse) {
  454. await test_utils.waitUntilDiscoConfirmed(
  455. _converse, _converse.bare_jid,
  456. [{'category': 'pubsub', 'type': 'pep'}],
  457. ['http://jabber.org/protocol/pubsub#publish-options']
  458. );
  459. test_utils.openControlBox();
  460. let IQ_id;
  461. const call = await test_utils.waitUntil(() =>
  462. _.filter(
  463. _converse.connection.send.calls.all(),
  464. call => {
  465. const stanza = call.args[0];
  466. if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
  467. return;
  468. }
  469. if (sizzle('items[node="storage:bookmarks"]', stanza).length) {
  470. IQ_id = stanza.getAttribute('id');
  471. return true;
  472. }
  473. }
  474. ).pop()
  475. );
  476. expect(Strophe.serialize(call.args[0])).toBe(
  477. `<iq from="dummy@localhost/resource" id="${IQ_id}" type="get" xmlns="jabber:client">`+
  478. '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
  479. '<items node="storage:bookmarks"/>'+
  480. '</pubsub>'+
  481. '</iq>'
  482. );
  483. const stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id})
  484. .c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
  485. .c('items', {'node': 'storage:bookmarks'})
  486. .c('item', {'id': 'current'})
  487. .c('storage', {'xmlns': 'storage:bookmarks'})
  488. .c('conference', {
  489. 'name': 'The Play&apos;s the Thing',
  490. 'autojoin': 'false',
  491. 'jid': 'theplay@conference.shakespeare.lit'
  492. }).c('nick').t('JC').up().up()
  493. .c('conference', {
  494. 'name': '1st Bookmark',
  495. 'autojoin': 'false',
  496. 'jid': 'first@conference.shakespeare.lit'
  497. }).c('nick').t('JC').up().up()
  498. .c('conference', {
  499. 'autojoin': 'false',
  500. 'jid': 'noname@conference.shakespeare.lit'
  501. }).c('nick').t('JC').up().up()
  502. .c('conference', {
  503. 'name': 'Bookmark with a very very long name that will be shortened',
  504. 'autojoin': 'false',
  505. 'jid': 'longname@conference.shakespeare.lit'
  506. }).c('nick').t('JC').up().up()
  507. .c('conference', {
  508. 'name': 'Another room',
  509. 'autojoin': 'false',
  510. 'jid': 'another@conference.shakespeare.lit'
  511. }).c('nick').t('JC').up().up();
  512. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  513. await test_utils.waitUntil(() => document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length);
  514. expect(document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length).toBe(5);
  515. let els = document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item a.list-item-link');
  516. expect(els[0].textContent).toBe("1st Bookmark");
  517. expect(els[1].textContent).toBe("Another room");
  518. expect(els[2].textContent).toBe("Bookmark with a very very long name that will be shortened");
  519. expect(els[3].textContent).toBe("noname@conference.shakespeare.lit");
  520. expect(els[4].textContent).toBe("The Play's the Thing");
  521. spyOn(window, 'confirm').and.returnValue(true);
  522. document.querySelector('#chatrooms .bookmarks.rooms-list .room-item:nth-child(2) a:nth-child(2)').click();
  523. expect(window.confirm).toHaveBeenCalled();
  524. await test_utils.waitUntil(() => document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item').length === 4)
  525. els = document.querySelectorAll('#chatrooms div.bookmarks.rooms-list .room-item a.list-item-link');
  526. expect(els[0].textContent).toBe("1st Bookmark");
  527. expect(els[1].textContent).toBe("Bookmark with a very very long name that will be shortened");
  528. expect(els[2].textContent).toBe("noname@conference.shakespeare.lit");
  529. expect(els[3].textContent).toBe("The Play's the Thing");
  530. done();
  531. }));
  532. it("remembers the toggle state of the bookmarks list", mock.initConverse(
  533. {'connection': ['send']}, ['rosterGroupsFetched'], {},
  534. async function (done, _converse) {
  535. test_utils.openControlBox();
  536. await test_utils.waitUntilDiscoConfirmed(
  537. _converse, _converse.bare_jid,
  538. [{'category': 'pubsub', 'type': 'pep'}],
  539. ['http://jabber.org/protocol/pubsub#publish-options']
  540. );
  541. let IQ_id;
  542. const call = await test_utils.waitUntil(() =>
  543. _.filter(
  544. _converse.connection.send.calls.all(),
  545. call => {
  546. const stanza = call.args[0];
  547. if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
  548. return;
  549. }
  550. if (sizzle('items[node="storage:bookmarks"]', stanza).length) {
  551. IQ_id = stanza.getAttribute('id');
  552. return true;
  553. }
  554. }
  555. ).pop()
  556. );
  557. expect(Strophe.serialize(call.args[0])).toBe(
  558. `<iq from="dummy@localhost/resource" id="${IQ_id}" type="get" xmlns="jabber:client">`+
  559. '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
  560. '<items node="storage:bookmarks"/>'+
  561. '</pubsub>'+
  562. '</iq>'
  563. );
  564. const stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id})
  565. .c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
  566. .c('items', {'node': 'storage:bookmarks'})
  567. .c('item', {'id': 'current'})
  568. .c('storage', {'xmlns': 'storage:bookmarks'});
  569. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  570. _converse.bookmarks.create({
  571. 'jid': 'theplay@conference.shakespeare.lit',
  572. 'autojoin': false,
  573. 'name': 'The Play',
  574. 'nick': ''
  575. });
  576. const el = _converse.chatboxviews.el
  577. const selector = '#chatrooms .bookmarks.rooms-list .room-item';
  578. await test_utils.waitUntil(() => sizzle(selector, el).filter(u.isVisible).length);
  579. expect(u.hasClass('collapsed', sizzle('#chatrooms .bookmarks.rooms-list', el).pop())).toBeFalsy();
  580. expect(sizzle(selector, el).filter(u.isVisible).length).toBe(1);
  581. expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
  582. sizzle('#chatrooms .bookmarks-toggle', el).pop().click();
  583. expect(u.hasClass('collapsed', sizzle('#chatrooms .bookmarks.rooms-list', el).pop())).toBeTruthy();
  584. expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.CLOSED);
  585. sizzle('#chatrooms .bookmarks-toggle', el).pop().click();
  586. expect(u.hasClass('collapsed', sizzle('#chatrooms .bookmarks.rooms-list', el).pop())).toBeFalsy();
  587. expect(sizzle(selector, el).filter(u.isVisible).length).toBe(1);
  588. expect(_converse.bookmarksview.list_model.get('toggle-state')).toBe(_converse.OPENED);
  589. done();
  590. }));
  591. });
  592. });
  593. describe("When hide_open_bookmarks is true and a bookmarked room is opened", function () {
  594. it("can be closed", mock.initConverse(
  595. null, ['rosterGroupsFetched'],
  596. { hide_open_bookmarks: true },
  597. async function (done, _converse) {
  598. const jid = 'room@conference.example.org';
  599. await test_utils.waitUntilDiscoConfirmed(
  600. _converse, _converse.bare_jid,
  601. [{'category': 'pubsub', 'type': 'pep'}],
  602. ['http://jabber.org/protocol/pubsub#publish-options']
  603. );
  604. // XXX Create bookmarks view here, otherwise we need to mock stanza
  605. // traffic for it to get created.
  606. _converse.bookmarksview = new _converse.BookmarksView(
  607. {'model': _converse.bookmarks}
  608. );
  609. _converse.api.trigger('bookmarksInitialized');
  610. // Check that it's there
  611. _converse.bookmarks.create({
  612. 'jid': jid,
  613. 'autojoin': false,
  614. 'name': 'The Play',
  615. 'nick': ' Othello'
  616. });
  617. expect(_converse.bookmarks.length).toBe(1);
  618. const room_els = _converse.bookmarksview.el.querySelectorAll(".open-room");
  619. expect(room_els.length).toBe(1);
  620. // Check that it disappears once the room is opened
  621. const bookmark = _converse.bookmarksview.el.querySelector(".open-room");
  622. bookmark.click();
  623. await test_utils.waitUntil(() => _converse.chatboxviews.get(jid));
  624. expect(u.hasClass('hidden', _converse.bookmarksview.el.querySelector(".available-chatroom"))).toBeTruthy();
  625. // Check that it reappears once the room is closed
  626. const view = _converse.chatboxviews.get(jid);
  627. view.close();
  628. expect(u.hasClass('hidden', _converse.bookmarksview.el.querySelector(".available-chatroom"))).toBeFalsy();
  629. done();
  630. }));
  631. });
  632. }));