mock.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. const mock = {};
  2. window.mock = mock;
  3. let _converse;
  4. const converse = window.converse;
  5. converse.load();
  6. const { u, sizzle, Strophe, dayjs, $iq, $msg, $pres } = converse.env;
  7. jasmine.DEFAULT_TIMEOUT_INTERVAL = 7000;
  8. mock.initConverse = function (promise_names=[], settings=null, func) {
  9. if (typeof promise_names === "function") {
  10. func = promise_names;
  11. promise_names = []
  12. settings = null;
  13. }
  14. return async () => {
  15. if (_converse && _converse.api.connection.connected()) {
  16. await _converse.api.user.logout();
  17. }
  18. const el = document.querySelector('#conversejs');
  19. if (el) {
  20. el.parentElement.removeChild(el);
  21. }
  22. document.title = "Converse Tests";
  23. await initConverse(settings);
  24. await Promise.all((promise_names || []).map(_converse.api.waitUntil));
  25. try {
  26. await func(_converse);
  27. } catch(e) {
  28. console.error(e);
  29. fail(e);
  30. }
  31. }
  32. };
  33. mock.waitUntilDiscoConfirmed = async function (_converse, entity_jid, identities, features=[], items=[], type='info') {
  34. const sel = `iq[to="${entity_jid}"] query[xmlns="http://jabber.org/protocol/disco#${type}"]`;
  35. const iq = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(iq => sizzle(sel, iq).length).pop(), 300);
  36. const stanza = $iq({
  37. 'type': 'result',
  38. 'from': entity_jid,
  39. 'to': 'romeo@montague.lit/orchard',
  40. 'id': iq.getAttribute('id'),
  41. }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#'+type});
  42. identities?.forEach(identity => stanza.c('identity', {'category': identity.category, 'type': identity.type}).up());
  43. features?.forEach(feature => stanza.c('feature', {'var': feature}).up());
  44. items?.forEach(item => stanza.c('item', {'jid': item}).up());
  45. _converse.connection._dataRecv(mock.createRequest(stanza));
  46. }
  47. mock.createRequest = function (iq) {
  48. iq = typeof iq.tree == "function" ? iq.tree() : iq;
  49. var req = new Strophe.Request(iq, function() {});
  50. req.getResponse = function () {
  51. var env = new Strophe.Builder('env', {type: 'mock'}).tree();
  52. env.appendChild(iq);
  53. return env;
  54. };
  55. return req;
  56. };
  57. mock.closeAllChatBoxes = function (_converse) {
  58. return Promise.all(_converse.chatboxviews.map(view => view.close()));
  59. };
  60. mock.toggleControlBox = function () {
  61. const toggle = document.querySelector(".toggle-controlbox");
  62. if (!u.isVisible(document.querySelector("#controlbox"))) {
  63. if (!u.isVisible(toggle)) {
  64. u.removeClass('hidden', toggle);
  65. }
  66. toggle.click();
  67. }
  68. }
  69. mock.openControlBox = async function (_converse) {
  70. const model = await _converse.api.controlbox.open();
  71. await u.waitUntil(() => model.get('connected'));
  72. mock.toggleControlBox();
  73. return this;
  74. };
  75. mock.closeControlBox = function () {
  76. const controlbox = document.querySelector("#controlbox");
  77. if (u.isVisible(controlbox)) {
  78. const button = controlbox.querySelector(".close-chatbox-button");
  79. (button !== null) && button.click();
  80. }
  81. return this;
  82. };
  83. mock.waitUntilBookmarksReturned = async function (_converse, bookmarks=[]) {
  84. await mock.waitUntilDiscoConfirmed(
  85. _converse, _converse.bare_jid,
  86. [{'category': 'pubsub', 'type': 'pep'}],
  87. ['http://jabber.org/protocol/pubsub#publish-options']
  88. );
  89. const IQ_stanzas = _converse.connection.IQ_stanzas;
  90. const sent_stanza = await u.waitUntil(
  91. () => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop()
  92. );
  93. const stanza = $iq({
  94. 'to': _converse.connection.jid,
  95. 'type':'result',
  96. 'id':sent_stanza.getAttribute('id')
  97. }).c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
  98. .c('items', {'node': 'storage:bookmarks'})
  99. .c('item', {'id': 'current'})
  100. .c('storage', {'xmlns': 'storage:bookmarks'});
  101. bookmarks.forEach(bookmark => {
  102. stanza.c('conference', {
  103. 'name': bookmark.name,
  104. 'autojoin': bookmark.autojoin,
  105. 'jid': bookmark.jid
  106. }).c('nick').t(bookmark.nick).up().up()
  107. });
  108. _converse.connection._dataRecv(mock.createRequest(stanza));
  109. await _converse.api.waitUntil('bookmarksInitialized');
  110. };
  111. mock.openChatBoxes = function (converse, amount) {
  112. for (let i=0; i<amount; i++) {
  113. const jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
  114. converse.roster.get(jid).openChat();
  115. }
  116. };
  117. mock.openChatBoxFor = async function (_converse, jid) {
  118. await _converse.api.waitUntil('rosterContactsFetched');
  119. _converse.roster.get(jid).openChat();
  120. return u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
  121. };
  122. mock.openChatRoomViaModal = async function (_converse, jid, nick='') {
  123. // Opens a new chatroom
  124. const model = await _converse.api.controlbox.open('controlbox');
  125. await u.waitUntil(() => model.get('connected'));
  126. await mock.openControlBox(_converse);
  127. document.querySelector('converse-rooms-list .show-add-muc-modal').click();
  128. mock.closeControlBox(_converse);
  129. const modal = _converse.api.modal.get('add-chatroom-modal');
  130. await u.waitUntil(() => u.isVisible(modal.el), 1500)
  131. modal.el.querySelector('input[name="chatroom"]').value = jid;
  132. if (nick) {
  133. modal.el.querySelector('input[name="nickname"]').value = nick;
  134. }
  135. modal.el.querySelector('form input[type="submit"]').click();
  136. await u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
  137. return _converse.chatboxviews.get(jid);
  138. };
  139. mock.openChatRoom = function (_converse, room, server) {
  140. return _converse.api.rooms.open(`${room}@${server}`);
  141. };
  142. mock.getRoomFeatures = async function (_converse, muc_jid, features=[]) {
  143. const room = Strophe.getNodeFromJid(muc_jid);
  144. muc_jid = muc_jid.toLowerCase();
  145. const stanzas = _converse.connection.IQ_stanzas;
  146. const stanza = await u.waitUntil(() => stanzas.filter(
  147. iq => iq.querySelector(
  148. `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
  149. )).pop()
  150. );
  151. const features_stanza = $iq({
  152. 'from': muc_jid,
  153. 'id': stanza.getAttribute('id'),
  154. 'to': 'romeo@montague.lit/desktop',
  155. 'type': 'result'
  156. }).c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
  157. .c('identity', {
  158. 'category': 'conference',
  159. 'name': room[0].toUpperCase() + room.slice(1),
  160. 'type': 'text'
  161. }).up();
  162. features = features.length ? features : mock.default_muc_features;
  163. features.forEach(f => features_stanza.c('feature', {'var': f}).up());
  164. features_stanza.c('x', { 'xmlns':'jabber:x:data', 'type':'result'})
  165. .c('field', {'var':'FORM_TYPE', 'type':'hidden'})
  166. .c('value').t('http://jabber.org/protocol/muc#roominfo').up().up()
  167. .c('field', {'type':'text-single', 'var':'muc#roominfo_description', 'label':'Description'})
  168. .c('value').t('This is the description').up().up()
  169. .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'})
  170. .c('value').t(0);
  171. _converse.connection._dataRecv(mock.createRequest(features_stanza));
  172. };
  173. mock.waitForReservedNick = async function (_converse, muc_jid, nick) {
  174. const stanzas = _converse.connection.IQ_stanzas;
  175. const selector = `iq[to="${muc_jid.toLowerCase()}"] query[node="x-roomuser-item"]`;
  176. const iq = await u.waitUntil(() => stanzas.filter(s => sizzle(selector, s).length).pop());
  177. // We remove the stanza, otherwise we might get stale stanzas returned in our filter above.
  178. stanzas.splice(stanzas.indexOf(iq), 1)
  179. // The XMPP server returns the reserved nick for this user.
  180. const IQ_id = iq.getAttribute('id');
  181. const stanza = $iq({
  182. 'type': 'result',
  183. 'id': IQ_id,
  184. 'from': muc_jid,
  185. 'to': _converse.connection.jid
  186. }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'});
  187. if (nick) {
  188. stanza.c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
  189. }
  190. _converse.connection._dataRecv(mock.createRequest(stanza));
  191. if (nick) {
  192. return u.waitUntil(() => nick);
  193. }
  194. };
  195. mock.returnMemberLists = async function (_converse, muc_jid, members=[], affiliations=['member', 'owner', 'admin']) {
  196. if (affiliations.length === 0) {
  197. return;
  198. }
  199. const stanzas = _converse.connection.IQ_stanzas;
  200. if (affiliations.includes('member')) {
  201. const member_IQ = await u.waitUntil(() =>
  202. stanzas.filter(s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="member"]`, s).length
  203. ).pop());
  204. const member_list_stanza = $iq({
  205. 'from': 'coven@chat.shakespeare.lit',
  206. 'id': member_IQ.getAttribute('id'),
  207. 'to': 'romeo@montague.lit/orchard',
  208. 'type': 'result'
  209. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
  210. members.filter(m => m.affiliation === 'member').forEach(m => {
  211. member_list_stanza.c('item', {
  212. 'affiliation': m.affiliation,
  213. 'jid': m.jid,
  214. 'nick': m.nick
  215. });
  216. });
  217. _converse.connection._dataRecv(mock.createRequest(member_list_stanza));
  218. }
  219. if (affiliations.includes('admin')) {
  220. const admin_IQ = await u.waitUntil(() => stanzas.filter(
  221. s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="admin"]`, s).length
  222. ).pop());
  223. const admin_list_stanza = $iq({
  224. 'from': 'coven@chat.shakespeare.lit',
  225. 'id': admin_IQ.getAttribute('id'),
  226. 'to': 'romeo@montague.lit/orchard',
  227. 'type': 'result'
  228. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
  229. members.filter(m => m.affiliation === 'admin').forEach(m => {
  230. admin_list_stanza.c('item', {
  231. 'affiliation': m.affiliation,
  232. 'jid': m.jid,
  233. 'nick': m.nick
  234. });
  235. });
  236. _converse.connection._dataRecv(mock.createRequest(admin_list_stanza));
  237. }
  238. if (affiliations.includes('owner')) {
  239. const owner_IQ = await u.waitUntil(() => stanzas.filter(
  240. s => sizzle(`iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_ADMIN}"] item[affiliation="owner"]`, s).length
  241. ).pop());
  242. const owner_list_stanza = $iq({
  243. 'from': 'coven@chat.shakespeare.lit',
  244. 'id': owner_IQ.getAttribute('id'),
  245. 'to': 'romeo@montague.lit/orchard',
  246. 'type': 'result'
  247. }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN});
  248. members.filter(m => m.affiliation === 'owner').forEach(m => {
  249. owner_list_stanza.c('item', {
  250. 'affiliation': m.affiliation,
  251. 'jid': m.jid,
  252. 'nick': m.nick
  253. });
  254. });
  255. _converse.connection._dataRecv(mock.createRequest(owner_list_stanza));
  256. }
  257. return new Promise(resolve => _converse.api.listen.on('membersFetched', resolve));
  258. };
  259. mock.receiveOwnMUCPresence = async function (_converse, muc_jid, nick) {
  260. const sent_stanzas = _converse.connection.sent_stanzas;
  261. await u.waitUntil(() => sent_stanzas.filter(iq => sizzle('presence history', iq).length).pop());
  262. const presence = $pres({
  263. to: _converse.connection.jid,
  264. from: `${muc_jid}/${nick}`,
  265. id: u.getUniqueId()
  266. }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
  267. .c('item').attrs({
  268. affiliation: 'owner',
  269. jid: _converse.bare_jid,
  270. role: 'moderator'
  271. }).up()
  272. .c('status').attrs({code:'110'});
  273. _converse.connection._dataRecv(mock.createRequest(presence));
  274. };
  275. mock.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[], members=[], force_open=true, settings={}) {
  276. const { api } = _converse;
  277. muc_jid = muc_jid.toLowerCase();
  278. const room_creation_promise = api.rooms.open(muc_jid, settings, force_open);
  279. await mock.getRoomFeatures(_converse, muc_jid, features);
  280. await mock.waitForReservedNick(_converse, muc_jid, nick);
  281. // The user has just entered the room (because join was called)
  282. // and receives their own presence from the server.
  283. // See example 24: https://xmpp.org/extensions/xep-0045.html#enter-pres
  284. await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
  285. await room_creation_promise;
  286. const model = _converse.chatboxes.get(muc_jid);
  287. await u.waitUntil(() => (model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
  288. const affs = _converse.muc_fetch_members;
  289. const all_affiliations = Array.isArray(affs) ? affs : (affs ? ['member', 'admin', 'owner'] : []);
  290. await mock.returnMemberLists(_converse, muc_jid, members, all_affiliations);
  291. await model.messages.fetched;
  292. return model;
  293. };
  294. mock.createContact = async function (_converse, name, ask, requesting, subscription) {
  295. const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
  296. if (_converse.roster.get(jid)) {
  297. return Promise.resolve();
  298. }
  299. const contact = await new Promise((success, error) => {
  300. _converse.roster.create({
  301. 'ask': ask,
  302. 'fullname': name,
  303. 'jid': jid,
  304. 'requesting': requesting,
  305. 'subscription': subscription
  306. }, {success, error});
  307. });
  308. return contact;
  309. };
  310. mock.createContacts = async function (_converse, type, length) {
  311. /* Create current (as opposed to requesting or pending) contacts
  312. * for the user's roster.
  313. *
  314. * These contacts are not grouped. See below.
  315. */
  316. await _converse.api.waitUntil('rosterContactsFetched');
  317. let names, subscription, requesting, ask;
  318. if (type === 'requesting') {
  319. names = mock.req_names;
  320. subscription = 'none';
  321. requesting = true;
  322. ask = null;
  323. } else if (type === 'pending') {
  324. names = mock.pend_names;
  325. subscription = 'none';
  326. requesting = false;
  327. ask = 'subscribe';
  328. } else if (type === 'current') {
  329. names = mock.cur_names;
  330. subscription = 'both';
  331. requesting = false;
  332. ask = null;
  333. } else if (type === 'all') {
  334. await this.createContacts(_converse, 'current');
  335. await this.createContacts(_converse, 'requesting')
  336. await this.createContacts(_converse, 'pending');
  337. return this;
  338. } else {
  339. throw Error("Need to specify the type of contact to create");
  340. }
  341. const promises = names.slice(0, length).map(n => this.createContact(_converse, n, ask, requesting, subscription));
  342. await Promise.all(promises);
  343. };
  344. mock.waitForRoster = async function (_converse, type='current', length=-1, include_nick=true, grouped=true) {
  345. const s = `iq[type="get"] query[xmlns="${Strophe.NS.ROSTER}"]`;
  346. const iq = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(iq => sizzle(s, iq).length).pop());
  347. const result = $iq({
  348. 'to': _converse.connection.jid,
  349. 'type': 'result',
  350. 'id': iq.getAttribute('id')
  351. }).c('query', {
  352. 'xmlns': 'jabber:iq:roster'
  353. });
  354. if (type === 'pending' || type === 'all') {
  355. const pend_names = (length > -1) ? mock.pend_names.slice(0, length) : mock.pend_names;
  356. pend_names.map(name =>
  357. result.c('item', {
  358. jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
  359. name: include_nick ? name : undefined,
  360. subscription: 'none',
  361. ask: 'subscribe'
  362. }).up()
  363. );
  364. }
  365. if (type === 'current' || type === 'all') {
  366. const cur_names = Object.keys(mock.current_contacts_map);
  367. const names = (length > -1) ? cur_names.slice(0, length) : cur_names;
  368. names.forEach(name => {
  369. result.c('item', {
  370. jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
  371. name: include_nick ? name : undefined,
  372. subscription: 'both',
  373. ask: null
  374. });
  375. if (grouped) {
  376. mock.current_contacts_map[name].forEach(g => result.c('group').t(g).up());
  377. }
  378. result.up();
  379. });
  380. }
  381. _converse.connection._dataRecv(mock.createRequest(result));
  382. await _converse.api.waitUntil('rosterContactsFetched');
  383. };
  384. mock.createChatMessage = function (_converse, sender_jid, message) {
  385. return $msg({
  386. from: sender_jid,
  387. to: _converse.connection.jid,
  388. type: 'chat',
  389. id: (new Date()).getTime()
  390. })
  391. .c('body').t(message).up()
  392. .c('markable', {'xmlns': Strophe.NS.MARKERS}).up()
  393. .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
  394. }
  395. mock.sendMessage = async function (view, message) {
  396. const promise = new Promise(resolve => view.model.messages.once('rendered', resolve));
  397. const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
  398. textarea.value = message;
  399. const message_form = view.querySelector('converse-message-form') || view.querySelector('converse-muc-message-form');
  400. message_form.onKeyDown({
  401. target: view.querySelector('textarea.chat-textarea'),
  402. preventDefault: () => {},
  403. keyCode: 13
  404. });
  405. return promise;
  406. };
  407. window.libsignal = {
  408. 'SignalProtocolAddress': function (name, device_id) {
  409. this.name = name;
  410. this.deviceId = device_id;
  411. },
  412. 'SessionCipher': function (storage, remote_address) {
  413. this.remoteAddress = remote_address;
  414. this.storage = storage;
  415. this.encrypt = () => Promise.resolve({
  416. 'type': 1,
  417. 'body': 'c1ph3R73X7',
  418. 'registrationId': '1337'
  419. });
  420. this.decryptPreKeyWhisperMessage = (key_and_tag) => {
  421. return Promise.resolve(key_and_tag);
  422. };
  423. this.decryptWhisperMessage = (key_and_tag) => {
  424. return Promise.resolve(key_and_tag);
  425. }
  426. },
  427. 'SessionBuilder': function (storage, remote_address) { // eslint-disable-line no-unused-vars
  428. this.processPreKey = function () {
  429. return Promise.resolve();
  430. }
  431. },
  432. 'KeyHelper': {
  433. 'generateIdentityKeyPair': function () {
  434. return Promise.resolve({
  435. 'pubKey': new TextEncoder('utf-8').encode('1234'),
  436. 'privKey': new TextEncoder('utf-8').encode('4321')
  437. });
  438. },
  439. 'generateRegistrationId': function () {
  440. return '123456789';
  441. },
  442. 'generatePreKey': function (keyid) {
  443. return Promise.resolve({
  444. 'keyId': keyid,
  445. 'keyPair': {
  446. 'pubKey': new TextEncoder('utf-8').encode('1234'),
  447. 'privKey': new TextEncoder('utf-8').encode('4321')
  448. }
  449. });
  450. },
  451. 'generateSignedPreKey': function (identity_keypair, keyid) {
  452. return Promise.resolve({
  453. 'signature': new TextEncoder('utf-8').encode('11112222333344445555'),
  454. 'keyId': keyid,
  455. 'keyPair': {
  456. 'pubKey': new TextEncoder('utf-8').encode('1234'),
  457. 'privKey': new TextEncoder('utf-8').encode('4321')
  458. }
  459. });
  460. }
  461. }
  462. };
  463. mock.default_muc_features = [
  464. 'http://jabber.org/protocol/muc',
  465. 'jabber:iq:register',
  466. Strophe.NS.SID,
  467. Strophe.NS.MAM,
  468. 'muc_passwordprotected',
  469. 'muc_hidden',
  470. 'muc_temporary',
  471. 'muc_open',
  472. 'muc_unmoderated',
  473. 'muc_anonymous'
  474. ];
  475. mock.view_mode = 'overlayed';
  476. // Names from http://www.fakenamegenerator.com/
  477. mock.req_names = [
  478. 'Escalus, prince of Verona', 'The Nurse', 'Paris'
  479. ];
  480. mock.pend_names = [
  481. 'Lord Capulet', 'Guard', 'Servant'
  482. ];
  483. mock.current_contacts_map = {
  484. 'Mercutio': ['Colleagues', 'friends & acquaintences'],
  485. 'Juliet Capulet': ['friends & acquaintences'],
  486. 'Lady Montague': ['Colleagues', 'Family'],
  487. 'Lord Montague': ['Family'],
  488. 'Friar Laurence': ['friends & acquaintences'],
  489. 'Tybalt': ['friends & acquaintences'],
  490. 'Lady Capulet': ['ænemies'],
  491. 'Benviolo': ['friends & acquaintences'],
  492. 'Balthasar': ['Colleagues'],
  493. 'Peter': ['Colleagues'],
  494. 'Abram': ['Colleagues'],
  495. 'Sampson': ['Colleagues'],
  496. 'Gregory': ['friends & acquaintences'],
  497. 'Potpan': [],
  498. 'Friar John': []
  499. };
  500. const map = mock.current_contacts_map;
  501. const groups_map = {};
  502. Object.keys(map).forEach(k => {
  503. const groups = map[k].length ? map[k] : ["Ungrouped"];
  504. Object.values(groups).forEach(g => {
  505. groups_map[g] = groups_map[g] ? [...groups_map[g], k] : [k]
  506. });
  507. });
  508. mock.groups_map = groups_map;
  509. mock.cur_names = Object.keys(mock.current_contacts_map);
  510. mock.num_contacts = mock.req_names.length + mock.pend_names.length + mock.cur_names.length;
  511. mock.groups = {
  512. 'colleagues': 3,
  513. 'friends & acquaintences': 3,
  514. 'Family': 4,
  515. 'ænemies': 3,
  516. 'Ungrouped': 2
  517. };
  518. mock.chatroom_names = [
  519. 'Dyon van de Wege',
  520. 'Thomas Kalb',
  521. 'Dirk Theissen',
  522. 'Felix Hofmann',
  523. 'Ka Lek',
  524. 'Anne Ebersbacher'
  525. ];
  526. // TODO: need to also test other roles and affiliations
  527. mock.chatroom_roles = {
  528. 'Anne Ebersbacher': { affiliation: "owner", role: "moderator" },
  529. 'Dirk Theissen': { affiliation: "admin", role: "moderator" },
  530. 'Dyon van de Wege': { affiliation: "member", role: "occupant" },
  531. 'Felix Hofmann': { affiliation: "member", role: "occupant" },
  532. 'Ka Lek': { affiliation: "member", role: "occupant" },
  533. 'Thomas Kalb': { affiliation: "member", role: "occupant" }
  534. };
  535. mock.event = {
  536. 'preventDefault': function () {}
  537. };
  538. function clearIndexedDB () {
  539. const promise = u.getOpenPromise();
  540. const db_request = window.indexedDB.open("converse-test-persistent");
  541. db_request.onsuccess = function () {
  542. const db = db_request.result;
  543. const bare_jid = "romeo@montague.lit";
  544. let store;
  545. try {
  546. store= db.transaction([bare_jid], "readwrite").objectStore(bare_jid);
  547. } catch (e) {
  548. return promise.resolve();
  549. }
  550. const request = store.clear();
  551. request.onsuccess = promise.resolve();
  552. request.onerror = promise.resolve();
  553. };
  554. db_request.onerror = function (ev) {
  555. return promise.reject(ev.target.error);
  556. }
  557. return promise;
  558. }
  559. function clearStores () {
  560. [localStorage, sessionStorage].forEach(
  561. s => Object.keys(s).forEach(k => k.match(/^converse-test-/) && s.removeItem(k))
  562. );
  563. const cache_key = `converse.room-bookmarksromeo@montague.lit`;
  564. window.sessionStorage.removeItem(cache_key+'fetched');
  565. }
  566. const initConverse = async (settings) => {
  567. clearStores();
  568. await clearIndexedDB();
  569. _converse = await converse.initialize(Object.assign({
  570. 'animate': false,
  571. 'auto_subscribe': false,
  572. 'bosh_service_url': 'montague.lit/http-bind',
  573. 'discover_connection_methods': false,
  574. 'enable_smacks': false,
  575. 'i18n': 'en',
  576. // 'persistent_store': 'IndexedDB',
  577. 'loglevel': 'warn',
  578. 'no_trimming': true,
  579. 'play_sounds': false,
  580. 'use_emojione': false,
  581. 'view_mode': mock.view_mode
  582. }, settings || {}));
  583. window._converse = _converse;
  584. _converse.api.vcard.get = function (model, force) {
  585. let jid;
  586. if (typeof model === 'string' || model instanceof String) {
  587. jid = model;
  588. } else if (!model.get('vcard_updated') || force) {
  589. jid = model.get('jid') || model.get('muc_jid');
  590. }
  591. let fullname;
  592. if (!jid || jid == 'romeo@montague.lit') {
  593. jid = 'romeo@montague.lit';
  594. fullname = 'Romeo Montague' ;
  595. } else {
  596. const name = jid.split('@')[0].replace(/\./g, ' ').split(' ');
  597. const last = name.length-1;
  598. name[0] = name[0].charAt(0).toUpperCase()+name[0].slice(1);
  599. name[last] = name[last].charAt(0).toUpperCase()+name[last].slice(1);
  600. fullname = name.join(' ');
  601. }
  602. const vcard = $iq().c('vCard').c('FN').t(fullname).nodeTree;
  603. return {
  604. 'stanza': vcard,
  605. 'fullname': vcard.querySelector('FN')?.textContent,
  606. 'image': vcard.querySelector('PHOTO BINVAL')?.textContent,
  607. 'image_type': vcard.querySelector('PHOTO TYPE')?.textContent,
  608. 'url': vcard.querySelector('URL')?.textContent,
  609. 'vcard_updated': dayjs().format(),
  610. 'vcard_error': undefined
  611. };
  612. };
  613. if (settings?.auto_login !== false) {
  614. _converse.api.user.login('romeo@montague.lit/orchard', 'secret');
  615. await _converse.api.waitUntil('afterResourceBinding');
  616. }
  617. window.converse_disable_effects = true;
  618. return _converse;
  619. }
  620. mock.deviceListFetched = async function deviceListFetched (_converse, jid) {
  621. const selector = `iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.devicelist"]`;
  622. const stanza = await u.waitUntil(
  623. () => Array.from(_converse.connection.IQ_stanzas).filter(iq => iq.querySelector(selector)).pop()
  624. );
  625. await u.waitUntil(() => _converse.devicelists.get(jid));
  626. return stanza;
  627. }
  628. mock.ownDeviceHasBeenPublished = function ownDeviceHasBeenPublished (_converse) {
  629. return Array.from(_converse.connection.IQ_stanzas).filter(
  630. iq => iq.querySelector('iq[from="'+_converse.bare_jid+'"] publish[node="eu.siacs.conversations.axolotl.devicelist"]')
  631. ).pop();
  632. }
  633. mock.bundleHasBeenPublished = function bundleHasBeenPublished (_converse) {
  634. const selector = 'publish[node="eu.siacs.conversations.axolotl.bundles:123456789"]';
  635. return Array.from(_converse.connection.IQ_stanzas).filter(iq => iq.querySelector(selector)).pop();
  636. }
  637. mock.bundleFetched = function bundleFetched (_converse, jid, device_id) {
  638. return Array.from(_converse.connection.IQ_stanzas).filter(
  639. iq => iq.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.bundles:${device_id}"]`)
  640. ).pop();
  641. }
  642. mock.initializedOMEMO = async function initializedOMEMO (_converse) {
  643. await mock.waitUntilDiscoConfirmed(
  644. _converse, _converse.bare_jid,
  645. [{'category': 'pubsub', 'type': 'pep'}],
  646. ['http://jabber.org/protocol/pubsub#publish-options']
  647. );
  648. let iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, _converse.bare_jid));
  649. let stanza = $iq({
  650. 'from': _converse.bare_jid,
  651. 'id': iq_stanza.getAttribute('id'),
  652. 'to': _converse.bare_jid,
  653. 'type': 'result',
  654. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  655. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  656. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  657. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  658. .c('device', {'id': '482886413b977930064a5888b92134fe'});
  659. _converse.connection._dataRecv(mock.createRequest(stanza));
  660. iq_stanza = await u.waitUntil(() => mock.ownDeviceHasBeenPublished(_converse))
  661. stanza = $iq({
  662. 'from': _converse.bare_jid,
  663. 'id': iq_stanza.getAttribute('id'),
  664. 'to': _converse.bare_jid,
  665. 'type': 'result'});
  666. _converse.connection._dataRecv(mock.createRequest(stanza));
  667. iq_stanza = await u.waitUntil(() => mock.bundleHasBeenPublished(_converse))
  668. stanza = $iq({
  669. 'from': _converse.bare_jid,
  670. 'id': iq_stanza.getAttribute('id'),
  671. 'to': _converse.bare_jid,
  672. 'type': 'result'});
  673. _converse.connection._dataRecv(mock.createRequest(stanza));
  674. await _converse.api.waitUntil('OMEMOInitialized');
  675. }