Commit 1fa203c9 authored by JC Brand's avatar JC Brand

Support for IndexedDB. updates #1105

Depend on latest backbone.browserStorage which has support for IndexedDB
via localforage.

Storage operations are now asynchronous and transactional.

Bugs fixed (mostly by waiting for operations to complete):

* Rooms are now fetched asynchronously, so wait before triggering `show`
  or when closing.
* Make sure chat create/update transactions complete before firing events
* Make sure chats and messages have been fetched before creating new ones.
* When doing a `fetch` with `wait: false` on a collection and then
  creating a model in that collection, then once the read
  operation finishes (after creating the model), the collection is emptied again.
* Patch and wait when saving.
  Otherwise we have previously set attributes overriding later ones.
* Make sure api.roomviews.close returns a promise

Test fixes:

* Chats are now asynchronously returned, so we need to use `await`
* Wait for the storage transaction to complete when creating and updating messages
* Wait for all chatboxes to close
    Otherwise we get sessionStorage inconsistencies due to the async nature of localforage.
* Wait for room views to close in spec/chatroom.js

In the process, remove the `closeAllChatBoxes` override in
converse-controlbox by letting the `close` method decide whether it
should be closed or not.
parent 66c052f3
...@@ -22,13 +22,7 @@ ...@@ -22,13 +22,7 @@
}, },
"rules": { "rules": {
"lodash/prefer-lodash-chain": "off", "lodash/prefer-lodash-chain": "off",
"lodash/prefer-lodash-method": [2, { "lodash/prefer-lodash-method": "off",
"ignoreMethods": [
"assign", "every", "keys", "find", "endsWith", "startsWith", "filter",
"reduce", "isArray", "create", "map", "replace", "some", "toLower",
"split", "trim", "forEach", "toUpperCase", "includes", "values", "padStart"
]
}],
"lodash/import-scope": "off", "lodash/import-scope": "off",
"lodash/prefer-constant": "off", "lodash/prefer-constant": "off",
"lodash/prefer-get": "off", "lodash/prefer-get": "off",
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
## 6.0.0 (Unreleased) ## 6.0.0 (Unreleased)
- #129: Add support for XEP-0156: Disovering Alternative XMPP Connection Methods. Only XML is supported for now. - #129: Add support for XEP-0156: Disovering Alternative XMPP Connection Methods. Only XML is supported for now.
- #1105: Preliminary support for storing persistent data in IndexedDB instead of localStorage
- #1089: When filtering the roster for `online` users, show all non-offline users. - #1089: When filtering the roster for `online` users, show all non-offline users.
- #1691: Fix `collection.chatbox is undefined` errors - #1691: Fix `collection.chatbox is undefined` errors
- #1733: New message notifications for a minimized chat stack on top of each other - #1733: New message notifications for a minimized chat stack on top of each other
...@@ -15,6 +16,10 @@ ...@@ -15,6 +16,10 @@
### Breaking changes ### Breaking changes
- In contrast to sessionStorage and localStorage, IndexedDB is an asynchronous database.
A lot of code that relied on database access to be synchronous had to be
updated to work with asynchronous access via promises.
- In order to add support for XEP-0156, the XMPP connection needs to be created - In order to add support for XEP-0156, the XMPP connection needs to be created
only once we know the JID of the user that's logging in. This means that the only once we know the JID of the user that's logging in. This means that the
[connectionInitialized](https://conversejs.org/docs/html/api/-_converse.html#event:connectionInitialized) [connectionInitialized](https://conversejs.org/docs/html/api/-_converse.html#event:connectionInitialized)
...@@ -26,6 +31,7 @@ ...@@ -26,6 +31,7 @@
* `_converse.api.chats.create` * `_converse.api.chats.create`
* `_converse.api.rooms.get` * `_converse.api.rooms.get`
* `_converse.api.rooms.create` * `_converse.api.rooms.create`
* `_converse.api.roomviews.close`
- The `show_only_online_users` setting has been removed. - The `show_only_online_users` setting has been removed.
- The order of certain events have now changed: `statusInitialized` is now triggered after `initialized` and `connected` and `reconnected`. - The order of certain events have now changed: `statusInitialized` is now triggered after `initialized` and `connected` and `reconnected`.
......
This diff is collapsed.
...@@ -168,7 +168,8 @@ ...@@ -168,7 +168,8 @@
'name': 'The Play', 'name': 'The Play',
'nick': ' Othello' 'nick': ' Othello'
}); });
expect(_converse.chatboxviews.get(jid) === undefined).toBeFalsy(); await u.waitUntil(() => _converse.api.rooms.get().length);
expect(_.isUndefined(_converse.chatboxviews.get(jid))).toBeFalsy();
// Check that we don't auto-join if muc_respect_autojoin is false // Check that we don't auto-join if muc_respect_autojoin is false
_converse.muc_respect_autojoin = false; _converse.muc_respect_autojoin = false;
...@@ -188,20 +189,20 @@ ...@@ -188,20 +189,20 @@
it("will use the nickname from the bookmark", mock.initConverse( it("will use the nickname from the bookmark", mock.initConverse(
['rosterGroupsFetched'], {}, async function (done, _converse) { ['rosterGroupsFetched'], {}, async function (done, _converse) {
await test_utils.waitUntilBookmarksReturned(_converse); await test_utils.waitUntilBookmarksReturned(_converse);
const room_jid = 'coven@chat.shakespeare.lit'; const room_jid = 'coven@chat.shakespeare.lit';
await u.waitUntil(() => _converse.bookmarks);
_converse.bookmarks.create({ _converse.bookmarks.create({
'jid': room_jid, 'jid': room_jid,
'autojoin': false, 'autojoin': false,
'name': 'The Play', 'name': 'The Play',
'nick': 'Othello' 'nick': 'Othello'
}); });
const model = await _converse.api.rooms.open(room_jid); const room = await _converse.api.rooms.open(room_jid);
spyOn(model, 'join').and.callThrough(); spyOn(room, 'join').and.callThrough();
await test_utils.getRoomFeatures(_converse, 'coven', 'chat.shakespeare.lit'); await test_utils.getRoomFeatures(_converse, 'coven', 'chat.shakespeare.lit');
await u.waitUntil(() => model.join.calls.count()); await u.waitUntil(() => room.join.calls.count());
expect(model.get('nick')).toBe('Othello'); expect(room.get('nick')).toBe('Othello');
done(); done();
})); }));
...@@ -234,22 +235,13 @@ ...@@ -234,22 +235,13 @@
})); }));
it("can be unbookmarked", mock.initConverse( it("can be unbookmarked", mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {}, async function (done, _converse) {
async function (done, _converse) {
let sent_stanza, IQ_id;
await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.bare_jid,
[{'category': 'pubsub', 'type': 'pep'}],
['http://jabber.org/protocol/pubsub#publish-options']
);
const sendIQ = _converse.connection.sendIQ;
await test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC');
const jid = 'theplay@conference.shakespeare.lit'; await test_utils.waitUntilBookmarksReturned(_converse);
const view = _converse.chatboxviews.get(jid); const muc_jid = 'theplay@conference.shakespeare.lit';
await u.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark'))); await _converse.api.rooms.open(muc_jid);
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
spyOn(view, 'toggleBookmark').and.callThrough(); spyOn(view, 'toggleBookmark').and.callThrough();
spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough(); spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough();
...@@ -261,16 +253,13 @@ ...@@ -261,16 +253,13 @@
'name': 'The Play', 'name': 'The Play',
'nick': ' Othello' 'nick': ' Othello'
}); });
expect(_converse.bookmarks.length).toBe(1); expect(_converse.bookmarks.length).toBe(1);
await u.waitUntil(() => _converse.chatboxes.length >= 1); await u.waitUntil(() => _converse.chatboxes.length >= 1);
expect(view.model.get('bookmarked')).toBeTruthy(); expect(view.model.get('bookmarked')).toBeTruthy();
let bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark')); let bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
expect(u.hasClass('button-on', bookmark_icon)).toBeTruthy(); expect(u.hasClass('button-on', bookmark_icon)).toBeTruthy();
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback);
});
spyOn(_converse.connection, 'getUniqueId').and.callThrough(); spyOn(_converse.connection, 'getUniqueId').and.callThrough();
bookmark_icon.click(); bookmark_icon.click();
bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark')); bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
...@@ -281,8 +270,9 @@ ...@@ -281,8 +270,9 @@
// Check that an IQ stanza is sent out, containing no // Check that an IQ stanza is sent out, containing no
// conferences to bookmark (since we removed the one and // conferences to bookmark (since we removed the one and
// only bookmark). // only bookmark).
expect(sent_stanza.toLocaleString()).toBe( const sent_stanza = _converse.connection.IQ_stanzas.pop();
`<iq from="romeo@montague.lit/orchard" id="${IQ_id}" type="set" xmlns="jabber:client">`+ expect(Strophe.serialize(sent_stanza)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${sent_stanza.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+ `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
`<publish node="storage:bookmarks">`+ `<publish node="storage:bookmarks">`+
`<item id="current">`+ `<item id="current">`+
...@@ -478,7 +468,7 @@ ...@@ -478,7 +468,7 @@
[{'category': 'pubsub', 'type': 'pep'}], [{'category': 'pubsub', 'type': 'pep'}],
['http://jabber.org/protocol/pubsub#publish-options'] ['http://jabber.org/protocol/pubsub#publish-options']
); );
test_utils.openControlBox(); test_utils.openControlBox(_converse);
const IQ_stanzas = _converse.connection.IQ_stanzas; const IQ_stanzas = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil( const sent_stanza = await u.waitUntil(
...@@ -546,10 +536,9 @@ ...@@ -546,10 +536,9 @@
it("remembers the toggle state of the bookmarks list", mock.initConverse( it("remembers the toggle state of the bookmarks list", mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {}, async function (done, _converse) {
async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
await test_utils.waitUntilDiscoConfirmed( await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.bare_jid, _converse, _converse.bare_jid,
[{'category': 'pubsub', 'type': 'pep'}], [{'category': 'pubsub', 'type': 'pep'}],
...@@ -558,7 +547,7 @@ ...@@ -558,7 +547,7 @@
const IQ_stanzas = _converse.connection.IQ_stanzas; const IQ_stanzas = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil( const sent_stanza = await u.waitUntil(
() => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop()); () => IQ_stanzas.filter(s => sizzle('iq items[node="storage:bookmarks"]', s).length).pop());
expect(Strophe.serialize(sent_stanza)).toBe( expect(Strophe.serialize(sent_stanza)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${sent_stanza.getAttribute('id')}" type="get" xmlns="jabber:client">`+ `<iq from="romeo@montague.lit/orchard" id="${sent_stanza.getAttribute('id')}" type="get" xmlns="jabber:client">`+
...@@ -567,13 +556,13 @@ ...@@ -567,13 +556,13 @@
'</pubsub>'+ '</pubsub>'+
'</iq>' '</iq>'
); );
const stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id': sent_stanza.getAttribute('id')})
const stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':sent_stanza.getAttribute('id')})
.c('pubsub', {'xmlns': Strophe.NS.PUBSUB}) .c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
.c('items', {'node': 'storage:bookmarks'}) .c('items', {'node': 'storage:bookmarks'})
.c('item', {'id': 'current'}) .c('item', {'id': 'current'})
.c('storage', {'xmlns': 'storage:bookmarks'}); .c('storage', {'xmlns': 'storage:bookmarks'});
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
await _converse.api.waitUntil('bookmarksInitialized');
_converse.bookmarks.create({ _converse.bookmarks.create({
'jid': 'theplay@conference.shakespeare.lit', 'jid': 'theplay@conference.shakespeare.lit',
...@@ -606,7 +595,7 @@ ...@@ -606,7 +595,7 @@
{ hide_open_bookmarks: true }, { hide_open_bookmarks: true },
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
await test_utils.waitUntilBookmarksReturned(_converse); await test_utils.waitUntilBookmarksReturned(_converse);
// Check that it's there // Check that it's there
......
This diff is collapsed.
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
// Adding two contacts one with Capital initials and one with small initials of same JID (Case sensitive check) // Adding two contacts one with Capital initials and one with small initials of same JID (Case sensitive check)
_converse.roster.create({ _converse.roster.create({
jid: mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit', jid: mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
...@@ -69,8 +69,8 @@ ...@@ -69,8 +69,8 @@
['rosterGroupsFetched', 'chatBoxesFetched'], {}, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'all').openControlBox(); await test_utils.waitForRoster(_converse, 'all');
_converse.api.trigger('rosterContactsFetched'); await test_utils.openControlBox(_converse);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, sender_jid); await test_utils.openChatBoxFor(_converse, sender_jid);
...@@ -78,8 +78,8 @@ ...@@ -78,8 +78,8 @@
const chatview = _converse.chatboxviews.get(sender_jid); const chatview = _converse.chatboxviews.get(sender_jid);
chatview.model.set({'minimized': true}); chatview.model.set({'minimized': true});
expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy(); expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count') === null).toBeTruthy();
expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy(); expect(_converse.rosterview.el.querySelector('.msgs-indicator') === null).toBeTruthy();
let msg = $msg({ let msg = $msg({
from: sender_jid, from: sender_jid,
...@@ -119,7 +119,7 @@ ...@@ -119,7 +119,7 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
function (done, _converse) { function (done, _converse) {
test_utils.openControlBox(); test_utils.openControlBox(_converse);
var view = _converse.xmppstatusview; var view = _converse.xmppstatusview;
expect(u.hasClass('online', view.el.querySelector('.xmpp-status span:first-child'))).toBe(true); expect(u.hasClass('online', view.el.querySelector('.xmpp-status span:first-child'))).toBe(true);
expect(view.el.querySelector('.xmpp-status span.online').textContent.trim()).toBe('I am online'); expect(view.el.querySelector('.xmpp-status span.online').textContent.trim()).toBe('I am online');
...@@ -131,8 +131,7 @@ ...@@ -131,8 +131,7 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
var cbview = _converse.chatboxviews.get('controlbox'); var cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.change-status').click() cbview.el.querySelector('.change-status').click()
var modal = _converse.xmppstatusview.status_modal; var modal = _converse.xmppstatusview.status_modal;
...@@ -160,8 +159,7 @@ ...@@ -160,8 +159,7 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.change-status').click() cbview.el.querySelector('.change-status').click()
const modal = _converse.xmppstatusview.status_modal; const modal = _converse.xmppstatusview.status_modal;
...@@ -194,7 +192,8 @@ ...@@ -194,7 +192,8 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'all').openControlBox(); await test_utils.waitForRoster(_converse, 'all');
await test_utils.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click() cbview.el.querySelector('.add-contact').click()
...@@ -229,7 +228,7 @@ ...@@ -229,7 +228,7 @@
['rosterGroupsFetched'], {'autocomplete_add_contact': false}, ['rosterGroupsFetched'], {'autocomplete_add_contact': false},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); test_utils.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click() cbview.el.querySelector('.add-contact').click()
const modal = _converse.rosterview.add_contact_modal; const modal = _converse.rosterview.add_contact_modal;
...@@ -319,7 +318,8 @@ ...@@ -319,7 +318,8 @@
'xhr_user_search_url': 'http://example.org/?' }, 'xhr_user_search_url': 'http://example.org/?' },
async function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'all').openControlBox(); await test_utils.waitForRoster(_converse, 'all');
await test_utils.openControlBox(_converse);
var modal; var modal;
const xhr = { const xhr = {
'open': function open () {}, 'open': function open () {},
......
...@@ -258,9 +258,8 @@ ...@@ -258,9 +258,8 @@
['rosterInitialized', 'chatBoxesInitialized'], {}, ['rosterInitialized', 'chatBoxesInitialized'], {},
async (done, _converse) => { async (done, _converse) => {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
test_utils.createContacts(_converse, 'current', 2); await test_utils.waitForRoster(_converse, 'current', 2);
_converse.api.trigger('rosterContactsFetched');
// Test on chat that doesn't exist. // Test on chat that doesn't exist.
let chat = await _converse.api.chats.get('non-existing@jabber.org'); let chat = await _converse.api.chats.get('non-existing@jabber.org');
...@@ -294,9 +293,8 @@ ...@@ -294,9 +293,8 @@
['rosterGroupsFetched', 'chatBoxesInitialized'], {}, ['rosterGroupsFetched', 'chatBoxesInitialized'], {},
async (done, _converse) => { async (done, _converse) => {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
test_utils.createContacts(_converse, 'current', 2); await test_utils.waitForRoster(_converse, 'current', 2);
_converse.api.trigger('rosterContactsFetched');
const jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox(); test_utils.openControlBox(_converse);
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
......
...@@ -6,13 +6,16 @@ ...@@ -6,13 +6,16 @@
], factory); ], factory);
} (this, function (jasmine, mock, test_utils) { } (this, function (jasmine, mock, test_utils) {
"use strict"; "use strict";
var $msg = converse.env.$msg, const $msg = converse.env.$msg,
_ = converse.env._, _ = converse.env._,
utils = converse.env.utils; u = converse.env.utils;
describe("A headlines box", function () { describe("A headlines box", function () {
it("will not open nor display non-headline messages", mock.initConverse((done, _converse) => { it("will not open nor display non-headline messages",
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {}, async function (done, _converse) {
/* XMPP spam message: /* XMPP spam message:
* *
* <message xmlns="jabber:client" * <message xmlns="jabber:client"
...@@ -23,7 +26,7 @@ ...@@ -23,7 +26,7 @@
* <body>SORRY FOR THIS ADVERT</body * <body>SORRY FOR THIS ADVERT</body
* </message * </message
*/ */
sinon.spy(utils, 'isHeadlineMessage'); sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({ const stanza = $msg({
'xmlns': 'jabber:client', 'xmlns': 'jabber:client',
'to': 'romeo@montague.lit', 'to': 'romeo@montague.lit',
...@@ -33,9 +36,10 @@ ...@@ -33,9 +36,10 @@
.c('nick', {'xmlns': "http://jabber.org/protocol/nick"}).t("-wwdmz").up() .c('nick', {'xmlns': "http://jabber.org/protocol/nick"}).t("-wwdmz").up()
.c('body').t('SORRY FOR THIS ADVERT'); .c('body').t('SORRY FOR THIS ADVERT');
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(utils.isHeadlineMessage.called).toBeTruthy(); await u.waitUntil(() => _converse.api.chats.get().length);
expect(utils.isHeadlineMessage.returned(false)).toBeTruthy(); expect(u.isHeadlineMessage.called).toBeTruthy();
utils.isHeadlineMessage.restore(); expect(u.isHeadlineMessage.returned(false)).toBeTruthy();
u.isHeadlineMessage.restore();
done(); done();
})); }));
...@@ -55,7 +59,7 @@ ...@@ -55,7 +59,7 @@
* </x> * </x>
* </message> * </message>
*/ */
sinon.spy(utils, 'isHeadlineMessage'); sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({ const stanza = $msg({
'type': 'headline', 'type': 'headline',
'from': 'notify.example.com', 'from': 'notify.example.com',
...@@ -73,9 +77,9 @@ ...@@ -73,9 +77,9 @@
_converse.chatboxviews.keys(), _converse.chatboxviews.keys(),
'notify.example.com') 'notify.example.com')
).toBeTruthy(); ).toBeTruthy();
expect(utils.isHeadlineMessage.called).toBeTruthy(); expect(u.isHeadlineMessage.called).toBeTruthy();
expect(utils.isHeadlineMessage.returned(true)).toBeTruthy(); expect(u.isHeadlineMessage.returned(true)).toBeTruthy();
utils.isHeadlineMessage.restore(); // unwraps u.isHeadlineMessage.restore(); // unwraps
// Headlines boxes don't show an avatar // Headlines boxes don't show an avatar
const view = _converse.chatboxviews.get('notify.example.com'); const view = _converse.chatboxviews.get('notify.example.com');
expect(view.model.get('show_avatar')).toBeFalsy(); expect(view.model.get('show_avatar')).toBeFalsy();
...@@ -84,11 +88,12 @@ ...@@ -84,11 +88,12 @@
})); }));
it("will not show a headline messages from a full JID if allow_non_roster_messaging is false", it("will not show a headline messages from a full JID if allow_non_roster_messaging is false",
mock.initConverse((done, _converse) => { mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) {
_converse.allow_non_roster_messaging = false; _converse.allow_non_roster_messaging = false;
sinon.spy(utils, 'isHeadlineMessage'); sinon.spy(u, 'isHeadlineMessage');
var stanza = $msg({ const stanza = $msg({
'type': 'headline', 'type': 'headline',
'from': 'andre5114@jabber.snc.ru/Spark', 'from': 'andre5114@jabber.snc.ru/Spark',
'to': 'romeo@montague.lit', 'to': 'romeo@montague.lit',
...@@ -98,9 +103,9 @@ ...@@ -98,9 +103,9 @@
.c('body').t('Здравствуйте друзья'); .c('body').t('Здравствуйте друзья');
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_.without('controlbox', _converse.chatboxviews.keys()).length).toBe(0); expect(_.without('controlbox', _converse.chatboxviews.keys()).length).toBe(0);
expect(utils.isHeadlineMessage.called).toBeTruthy(); expect(u.isHeadlineMessage.called).toBeTruthy();
expect(utils.isHeadlineMessage.returned(true)).toBeTruthy(); expect(u.isHeadlineMessage.returned(true)).toBeTruthy();
utils.isHeadlineMessage.restore(); // unwraps u.isHeadlineMessage.restore(); // unwraps
done(); done();
})); }));
}); });
......
...@@ -176,7 +176,7 @@ ...@@ -176,7 +176,7 @@
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current', 3); await test_utils.waitForRoster(_converse, 'current', 3);
test_utils.openControlBox(); test_utils.openControlBox(_converse);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
await test_utils.waitUntilDiscoConfirmed( await test_utils.waitUntilDiscoConfirmed(
...@@ -222,8 +222,7 @@ ...@@ -222,8 +222,7 @@
await test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.lit'], 'items') await test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.lit'], 'items')
await test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.lit', [], [Strophe.NS.HTTPUPLOAD], []); await test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.lit', [], [Strophe.NS.HTTPUPLOAD], []);
test_utils.createContacts(_converse, 'current', 3); await test_utils.waitForRoster(_converse, 'current', 3);
_converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
...@@ -264,8 +263,7 @@ ...@@ -264,8 +263,7 @@
await test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items'); await test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items');
await test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []); await test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []);
test_utils.createContacts(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
_converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
...@@ -561,8 +559,7 @@ ...@@ -561,8 +559,7 @@
entities = await _converse.api.disco.entities.get(); entities = await _converse.api.disco.entities.get();
expect(entities.get('montague.lit').items.get('upload.montague.lit').identities.where({'category': 'store'}).length).toBe(1); expect(entities.get('montague.lit').items.get('upload.montague.lit').identities.where({'category': 'store'}).length).toBe(1);
await _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain); await _converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain);
test_utils.createContacts(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
_converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
...@@ -599,8 +596,7 @@ ...@@ -599,8 +596,7 @@
await test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items'); await test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items');
await test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []); await test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []);
test_utils.createContacts(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
_converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
allow_registration: false }, allow_registration: false },
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); test_utils.openControlBox(_converse);
const cbview = await u.waitUntil(() => _converse.chatboxviews.get('controlbox')); const cbview = await u.waitUntil(() => _converse.chatboxviews.get('controlbox'));
const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]'); const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
expect(checkboxes.length).toBe(1); expect(checkboxes.length).toBe(1);
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
u.waitUntil(() => _converse.chatboxviews.get('controlbox')) u.waitUntil(() => _converse.chatboxviews.get('controlbox'))
.then(() => { .then(() => {
var cbview = _converse.chatboxviews.get('controlbox'); var cbview = _converse.chatboxviews.get('controlbox');
test_utils.openControlBox(); test_utils.openControlBox(_converse);
const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]'); const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
expect(checkboxes.length).toBe(1); expect(checkboxes.length).toBe(1);
......
This diff is collapsed.
...@@ -13,11 +13,8 @@ ...@@ -13,11 +13,8 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
_converse.api.trigger('rosterContactsFetched'); await test_utils.openControlBox(_converse);
test_utils.openControlBox();
_converse.minimized_chats.toggleview.model.browserStorage._clear();
_converse.minimized_chats.initToggle(); _converse.minimized_chats.initToggle();
let contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; let contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
...@@ -48,11 +45,8 @@ ...@@ -48,11 +45,8 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
_converse.api.trigger('rosterContactsFetched'); await test_utils.openControlBox(_converse);
test_utils.openControlBox();
_converse.minimized_chats.toggleview.model.browserStorage._clear();
_converse.minimized_chats.initToggle(); _converse.minimized_chats.initToggle();
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
...@@ -74,13 +68,10 @@ ...@@ -74,13 +68,10 @@
it("shows the number messages received to minimized chats", it("shows the number messages received to minimized chats",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {}, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current');
_converse.api.trigger('rosterContactsFetched');
test_utils.openControlBox(); await test_utils.waitForRoster(_converse, 'current');
_converse.minimized_chats.toggleview.model.browserStorage._clear(); await test_utils.openControlBox(_converse);
_converse.minimized_chats.initToggle(); _converse.minimized_chats.initToggle();
var i, contact_jid, chatview, msg; var i, contact_jid, chatview, msg;
......
This diff is collapsed.
...@@ -65,7 +65,6 @@ ...@@ -65,7 +65,6 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t(message).tree(); }).c('body').t(message).tree();
await view.model.onMessage(msg); await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(u.hasClass('mentioned', view.el.querySelector('.chat-msg'))).toBeTruthy(); expect(u.hasClass('mentioned', view.el.querySelector('.chat-msg'))).toBeTruthy();
done(); done();
})); }));
...@@ -87,8 +86,7 @@ ...@@ -87,8 +86,7 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t('First message').tree(); }).c('body').t('First message').tree();
await view.model.onMessage(msg); await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve)); await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
msg = $msg({ msg = $msg({
from: 'lounge@montague.lit/some2', from: 'lounge@montague.lit/some2',
...@@ -97,8 +95,8 @@ ...@@ -97,8 +95,8 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t('Another message').tree(); }).c('body').t('Another message').tree();
await view.model.onMessage(msg); await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve)); await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 2);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); expect(view.model.messages.length).toBe(2);
done(); done();
})); }));
...@@ -154,7 +152,7 @@ ...@@ -154,7 +152,7 @@
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
const muc_jid = 'xsf@muc.xmpp.org'; const muc_jid = 'xsf@muc.xmpp.org';
const sender_jid = `${muc_jid}/romeo`; const sender_jid = `${muc_jid}/romeo`;
const impersonated_jid = `${muc_jid}/i_am_groot` const impersonated_jid = `${muc_jid}/i_am_groot`
...@@ -223,7 +221,6 @@ ...@@ -223,7 +221,6 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t('I wrote this message!').tree(); }).c('body').t('I wrote this message!').tree();
await view.model.onMessage(msg); await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.model.messages.last().occupant.get('affiliation')).toBe('owner'); expect(view.model.messages.last().occupant.get('affiliation')).toBe('owner');
expect(view.model.messages.last().occupant.get('role')).toBe('moderator'); expect(view.model.messages.last().occupant.get('role')).toBe('moderator');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
...@@ -250,7 +247,6 @@ ...@@ -250,7 +247,6 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t('Another message!').tree(); }).c('body').t('Another message!').tree();
await view.model.onMessage(msg); await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.model.messages.last().occupant.get('affiliation')).toBe('member'); expect(view.model.messages.last().occupant.get('affiliation')).toBe('member');
expect(view.model.messages.last().occupant.get('role')).toBe('participant'); expect(view.model.messages.last().occupant.get('role')).toBe('participant');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
...@@ -286,7 +282,6 @@ ...@@ -286,7 +282,6 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t('Message from someone not in the MUC right now').tree(); }).c('body').t('Message from someone not in the MUC right now').tree();
await view.model.onMessage(msg); await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.model.messages.last().occupant).toBeUndefined(); expect(view.model.messages.last().occupant).toBeUndefined();
// Check that there's a new "add" event handler, for when the occupant appears. // Check that there's a new "add" event handler, for when the occupant appears.
expect(view.model.occupants._events.add.length).toBe(add_events+1); expect(view.model.occupants._events.add.length).toBe(add_events+1);
...@@ -351,7 +346,6 @@ ...@@ -351,7 +346,6 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t('I wrote this message!').tree(); }).c('body').t('I wrote this message!').tree();
await view.model.onMessage(msg); await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.model.messages.last().get('sender')).toBe('me'); expect(view.model.messages.last().get('sender')).toBe('me');
done(); done();
})); }));
...@@ -382,7 +376,6 @@ ...@@ -382,7 +376,6 @@
'type': 'groupchat', 'type': 'groupchat',
'id': msg_id, 'id': msg_id,
}).c('body').t('But soft, what light through yonder airlock breaks?').tree()); }).c('body').t('But soft, what light through yonder airlock breaks?').tree());
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelector('.chat-msg__text').textContent) expect(view.el.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder airlock breaks?'); .toBe('But soft, what light through yonder airlock breaks?');
...@@ -445,8 +438,7 @@ ...@@ -445,8 +438,7 @@
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 // Enter keyCode: 13 // Enter
}); });
await new Promise(resolve => view.once('messageInserted', resolve)); await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelector('.chat-msg__text').textContent) expect(view.el.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder airlock breaks?'); .toBe('But soft, what light through yonder airlock breaks?');
...@@ -503,7 +495,6 @@ ...@@ -503,7 +495,6 @@
'to': 'romeo@montague.lit', 'to': 'romeo@montague.lit',
'type': 'groupchat' 'type': 'groupchat'
}).c('body').t('Hello world').tree()); }).c('body').t('Hello world').tree());
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
// Test that pressing the down arrow cancels message correction // Test that pressing the down arrow cancels message correction
...@@ -744,7 +735,6 @@ ...@@ -744,7 +735,6 @@
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'11', 'end':'14', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).up() .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'11', 'end':'14', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).up()
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'15', 'end':'23', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree; .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'15', 'end':'23', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree;
await view.model.onMessage(msg); await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
const messages = view.el.querySelectorAll('.chat-msg__text'); const messages = view.el.querySelectorAll('.chat-msg__text');
expect(messages.length).toBe(1); expect(messages.length).toBe(1);
expect(messages[0].classList.length).toEqual(1); expect(messages[0].classList.length).toEqual(1);
......
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
it("is shown when you are mentioned in a groupchat", it("is shown when you are mentioned in a groupchat",
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => { mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
await test_utils.createContacts(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.api.chatviews.get('lounge@montague.lit'); const view = _converse.api.chatviews.get('lounge@montague.lit');
if (!view.el.querySelectorAll('.chat-area').length) { if (!view.el.querySelectorAll('.chat-area').length) {
...@@ -66,10 +66,10 @@ ...@@ -66,10 +66,10 @@
to: 'romeo@montague.lit', to: 'romeo@montague.lit',
type: 'groupchat' type: 'groupchat'
}).c('body').t(message).tree(); }).c('body').t(message).tree();
_converse.connection._dataRecv(test_utils.createRequest(msg)); _converse.connection._dataRecv(test_utils.createRequest(msg));
await new Promise(resolve => view.once('messageInserted', resolve)); await new Promise(resolve => view.once('messageInserted', resolve));
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
expect(_converse.showMessageNotification).toHaveBeenCalled(); expect(_converse.showMessageNotification).toHaveBeenCalled();
if (no_notification) { if (no_notification) {
delete window.Notification; delete window.Notification;
...@@ -128,16 +128,16 @@ ...@@ -128,16 +128,16 @@
done(); done();
})); }));
it("is shown when a user changes their chat state (if show_chat_state_notifications is true)", mock.initConverse((done, _converse) => { it("is shown when a user changes their chat state (if show_chat_state_notifications is true)",
// TODO: not yet testing show_desktop_notifications setting mock.initConverse(['rosterGroupsFetched'], {show_chat_state_notifications: true},
_converse.show_chat_state_notifications = true; async (done, _converse) => {
test_utils.createContacts(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current', 3);
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true); spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
spyOn(_converse, 'showChatStateNotification'); spyOn(_converse, 'showChatStateNotification');
const jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'busy'); // This will emit 'contactStatusChanged' _converse.roster.get(jid).presence.set('show', 'busy'); // This will emit 'contactStatusChanged'
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled(); await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
expect(_converse.showChatStateNotification).toHaveBeenCalled(); expect(_converse.showChatStateNotification).toHaveBeenCalled();
done() done()
})); }));
......
...@@ -81,8 +81,7 @@ ...@@ -81,8 +81,7 @@
async function (done, _converse) { async function (done, _converse) {
const message = 'This message will be encrypted' const message = 'This message will be encrypted'
test_utils.createContacts(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const view = await test_utils.openChatBoxFor(_converse, contact_jid); const view = await test_utils.openChatBoxFor(_converse, contact_jid);
const payload = await view.model.encryptMessage(message); const payload = await view.model.encryptMessage(message);
...@@ -98,8 +97,7 @@ ...@@ -98,8 +97,7 @@
async function (done, _converse) { async function (done, _converse) {
let sent_stanza; let sent_stanza;
test_utils.createContacts(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => initializedOMEMO(_converse)); await u.waitUntil(() => initializedOMEMO(_converse));
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
...@@ -386,8 +384,7 @@ ...@@ -386,8 +384,7 @@
await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], [Strophe.NS.SID]); await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], [Strophe.NS.SID]);
test_utils.createContacts(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => initializedOMEMO(_converse)); await u.waitUntil(() => initializedOMEMO(_converse));
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
...@@ -605,8 +602,7 @@ ...@@ -605,8 +602,7 @@
async function (done, _converse) { async function (done, _converse) {
_converse.NUM_PREKEYS = 5; // Restrict to 5, otherwise the resulting stanza is too large to easily test _converse.NUM_PREKEYS = 5; // Restrict to 5, otherwise the resulting stanza is too large to easily test
test_utils.createContacts(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => initializedOMEMO(_converse)); await u.waitUntil(() => initializedOMEMO(_converse));
...@@ -710,7 +706,7 @@ ...@@ -710,7 +706,7 @@
['http://jabber.org/protocol/pubsub#publish-options'] ['http://jabber.org/protocol/pubsub#publish-options']
); );
test_utils.createContacts(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
// Wait until own devices are fetched // Wait until own devices are fetched
...@@ -884,7 +880,7 @@ ...@@ -884,7 +880,7 @@
['http://jabber.org/protocol/pubsub#publish-options'] ['http://jabber.org/protocol/pubsub#publish-options']
); );
test_utils.createContacts(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@montague.lit';
let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid)); let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
expect(Strophe.serialize(iq_stanza)).toBe( expect(Strophe.serialize(iq_stanza)).toBe(
...@@ -1036,8 +1032,7 @@ ...@@ -1036,8 +1032,7 @@
_converse.NUM_PREKEYS = 2; // Restrict to 2, otherwise the resulting stanza is too large to easily test _converse.NUM_PREKEYS = 2; // Restrict to 2, otherwise the resulting stanza is too large to easily test
test_utils.createContacts(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid)); let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
let stanza = $iq({ let stanza = $iq({
...@@ -1113,8 +1108,7 @@ ...@@ -1113,8 +1108,7 @@
['http://jabber.org/protocol/pubsub#publish-options'] ['http://jabber.org/protocol/pubsub#publish-options']
); );
test_utils.createContacts(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid)); let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
...@@ -1460,8 +1454,7 @@ ...@@ -1460,8 +1454,7 @@
['http://jabber.org/protocol/pubsub#publish-options'] ['http://jabber.org/protocol/pubsub#publish-options']
); );
test_utils.createContacts(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid) await test_utils.openChatBoxFor(_converse, contact_jid)
// We simply emit, to avoid doing all the setup work // We simply emit, to avoid doing all the setup work
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async (done, _converse) => { async (done, _converse) => {
test_utils.openControlBox(); test_utils.openControlBox(_converse);
const view = _converse.xmppstatusview; const view = _converse.xmppstatusview;
spyOn(view.model, 'sendPresence').and.callThrough(); spyOn(view.model, 'sendPresence').and.callThrough();
spyOn(_converse.connection, 'send').and.callThrough(); spyOn(_converse.connection, 'send').and.callThrough();
...@@ -112,12 +112,12 @@ ...@@ -112,12 +112,12 @@
it("has its priority taken into account", it("has its priority taken into account",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
(done, _converse) => { async (done, _converse) => {
test_utils.openControlBox(); test_utils.openControlBox(_converse);
test_utils.createContacts(_converse, 'current'); // Create some contacts so that we can test positioning await test_utils.waitForRoster(_converse, 'current');
const contact_jid = mock.cur_names[8].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[8].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const contact = _converse.roster.get(contact_jid); const contact = await _converse.api.contacts.get(contact_jid);
let stanza = u.toStanza(` let stanza = u.toStanza(`
<presence xmlns="jabber:client" <presence xmlns="jabber:client"
to="romeo@montague.lit/converse.js-21770972" to="romeo@montague.lit/converse.js-21770972"
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
['rosterGroupsFetched'], {'muc_show_join_leave': false}, ['rosterGroupsFetched'], {'muc_show_join_leave': false},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); test_utils.openControlBox(_converse);
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
_.rangeRight(3000, 0).forEach(i => { _.rangeRight(3000, 0).forEach(i => {
const name = `User ${i.toString().padStart(5, '0')}`; const name = `User ${i.toString().padStart(5, '0')}`;
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
function (done, _converse) { function (done, _converse) {
_converse.roster_groups = false; _converse.roster_groups = false;
test_utils.openControlBox(); test_utils.openControlBox(_converse);
expect(_converse.roster.pluck('jid').length).toBe(0); expect(_converse.roster.pluck('jid').length).toBe(0);
var stanza = $iq({ var stanza = $iq({
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
// _converse.show_only_online_users = true; // _converse.show_only_online_users = true;
_converse.roster_groups = true; _converse.roster_groups = true;
test_utils.openControlBox(); test_utils.openControlBox(_converse);
expect(_converse.roster.pluck('jid').length).toBe(0); expect(_converse.roster.pluck('jid').length).toBe(0);
var stanza = $iq({ var stanza = $iq({
......
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
"test-utils"], factory); "test-utils"], factory);
} (this, function (jasmine, mock, test_utils) { } (this, function (jasmine, mock, test_utils) {
"use strict"; "use strict";
const Strophe = converse.env.Strophe;
const $iq = converse.env.$iq; const $iq = converse.env.$iq;
const $pres = converse.env.$pres; const $pres = converse.env.$pres;
const Strophe = converse.env.Strophe;
const _ = converse.env._; const _ = converse.env._;
const sizzle = converse.env.sizzle; const sizzle = converse.env.sizzle;
const u = converse.env.utils; const u = converse.env.utils;
...@@ -175,7 +175,7 @@ ...@@ -175,7 +175,7 @@
*/ */
const sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => s.match('presence')).pop()); const sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => s.match('presence')).pop());
expect(contact.subscribe).toHaveBeenCalled(); expect(contact.subscribe).toHaveBeenCalled();
expect(sent_presence).toBe( // Strophe adds the xmlns attr (although not in spec) expect(sent_presence).toBe(
`<presence to="contact@example.org" type="subscribe" xmlns="jabber:client">`+ `<presence to="contact@example.org" type="subscribe" xmlns="jabber:client">`+
`<nick xmlns="http://jabber.org/protocol/nick">Romeo Montague</nick>`+ `<nick xmlns="http://jabber.org/protocol/nick">Romeo Montague</nick>`+
`</presence>` `</presence>`
...@@ -455,18 +455,13 @@ ...@@ -455,18 +455,13 @@
{ roster_groups: false }, { roster_groups: false },
async function (done, _converse) { async function (done, _converse) {
var sent_IQ, IQ_id, jid = 'abram@montague.lit'; const jid = 'abram@montague.lit';
test_utils.openControlBox(_converse); await test_utils.openControlBox(_converse);
test_utils.createContacts(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
spyOn(window, 'confirm').and.returnValue(true); spyOn(window, 'confirm').and.returnValue(true);
// We now have a contact we want to remove // We now have a contact we want to remove
expect(_converse.roster.get(jid) instanceof _converse.RosterContact).toBeTruthy(); expect(_converse.roster.get(jid) instanceof _converse.RosterContact).toBeTruthy();
var sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_IQ = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback);
});
const header = sizzle('a:contains("My contacts")', _converse.rosterview.el).pop(); const header = sizzle('a:contains("My contacts")', _converse.rosterview.el).pop();
await u.waitUntil(() => header.parentElement.querySelectorAll('li').length); await u.waitUntil(() => header.parentElement.querySelectorAll('li').length);
...@@ -493,8 +488,9 @@ ...@@ -493,8 +488,9 @@
* </query> * </query>
* </iq> * </iq>
*/ */
expect(sent_IQ.toLocaleString()).toBe( const sent_iq = _converse.connection.IQ_stanzas.pop();
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+ expect(Strophe.serialize(sent_iq)).toBe(
`<iq id="${sent_iq.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster">`+ `<query xmlns="jabber:iq:roster">`+
`<item jid="abram@montague.lit" subscription="remove"/>`+ `<item jid="abram@montague.lit" subscription="remove"/>`+
`</query>`+ `</query>`+
...@@ -502,7 +498,7 @@ ...@@ -502,7 +498,7 @@
// Receive confirmation from the contact's server // Receive confirmation from the contact's server
// <iq type='result' id='remove1'/> // <iq type='result' id='remove1'/>
const stanza = $iq({'type': 'result', 'id':IQ_id}); const stanza = $iq({'type': 'result', 'id': sent_iq.getAttribute('id')});
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
// Our contact has now been removed // Our contact has now been removed
await u.waitUntil(() => typeof _converse.roster.get(jid) === "undefined"); await u.waitUntil(() => typeof _converse.roster.get(jid) === "undefined");
...@@ -514,9 +510,8 @@ ...@@ -514,9 +510,8 @@
async function (done, _converse) { async function (done, _converse) {
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
test_utils.openControlBox(_converse); await test_utils.openControlBox(_converse);
// Create some contacts so that we can test positioning await test_utils.waitForRoster(_converse, 'current');
test_utils.createContacts(_converse, 'current');
/* <presence /* <presence
* from='user@example.com' * from='user@example.com'
* to='contact@example.org' * to='contact@example.org'
...@@ -534,7 +529,7 @@ ...@@ -534,7 +529,7 @@
const header = sizzle('a:contains("Contact requests")', _converse.rosterview.el).pop(); const header = sizzle('a:contains("Contact requests")', _converse.rosterview.el).pop();
const contacts = _.filter(header.parentElement.querySelectorAll('li'), u.isVisible); const contacts = _.filter(header.parentElement.querySelectorAll('li'), u.isVisible);
return contacts.length; return contacts.length;
}, 300); }, 500);
expect(_converse.api.trigger).toHaveBeenCalledWith('contactRequest', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('contactRequest', jasmine.any(Object));
const header = sizzle('a:contains("Contact requests")', _converse.rosterview.el).pop(); const header = sizzle('a:contains("Contact requests")', _converse.rosterview.el).pop();
expect(u.isVisible(header)).toBe(true); expect(u.isVisible(header)).toBe(true);
......
...@@ -16,8 +16,7 @@ ...@@ -16,8 +16,7 @@
async function (done, _converse) { async function (done, _converse) {
await u.waitUntil(() => _converse.chatboxviews.get('controlbox')); await u.waitUntil(() => _converse.chatboxviews.get('controlbox'));
test_utils.openControlBox(); const cbview = _converse.api.controlbox.get();
const cbview = _converse.chatboxviews.get('controlbox');
expect(cbview.el.querySelectorAll('a.register-account').length).toBe(0); expect(cbview.el.querySelectorAll('a.register-account').length).toBe(0);
done(); done();
})); }));
...@@ -29,15 +28,22 @@ ...@@ -29,15 +28,22 @@
allow_registration: true }, allow_registration: true },
async function (done, _converse) { async function (done, _converse) {
const toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
toggle.click();
}
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'), 300); await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'), 300);
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
test_utils.openControlBox();
const panels = cbview.el.querySelector('.controlbox-panes'); const panels = cbview.el.querySelector('.controlbox-panes');
const login = panels.firstElementChild; const login = panels.firstElementChild;
const registration = panels.childNodes[1]; const registration = panels.childNodes[1];
const register_link = cbview.el.querySelector('a.register-account'); const register_link = cbview.el.querySelector('a.register-account');
expect(register_link.textContent).toBe("Create an account"); expect(register_link.textContent).toBe("Create an account");
register_link.click(); register_link.click();
await u.waitUntil(() => u.isVisible(registration)); await u.waitUntil(() => u.isVisible(registration));
expect(u.isVisible(login)).toBe(false); expect(u.isVisible(login)).toBe(false);
done(); done();
...@@ -52,8 +58,7 @@ ...@@ -52,8 +58,7 @@
spyOn(Strophe.Connection.prototype, 'connect'); spyOn(Strophe.Connection.prototype, 'connect');
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel')); await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
test_utils.openControlBox(); const cbview = _converse.api.controlbox.get();
const cbview = _converse.chatboxviews.get('controlbox');
const registerview = cbview.registerpanel; const registerview = cbview.registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough(); spyOn(registerview, 'onProviderChosen').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
...@@ -88,8 +93,7 @@ ...@@ -88,8 +93,7 @@
spyOn(Strophe.Connection.prototype, 'connect'); spyOn(Strophe.Connection.prototype, 'connect');
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel')); await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
test_utils.openControlBox(); const cbview = _converse.api.controlbox.get();
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.toggle-register-login').click(); cbview.el.querySelector('.toggle-register-login').click();
const registerview = _converse.chatboxviews.get('controlbox').registerpanel; const registerview = _converse.chatboxviews.get('controlbox').registerpanel;
...@@ -100,7 +104,7 @@ ...@@ -100,7 +104,7 @@
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
expect(registerview._registering).toBeFalsy(); expect(registerview._registering).toBeFalsy();
expect(_converse.connection.connected).toBeFalsy(); expect(_converse.api.connection.connected()).toBeFalsy();
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org'; registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click(); registerview.el.querySelector('input[type=submit]').click();
expect(registerview.onProviderChosen).toHaveBeenCalled(); expect(registerview.onProviderChosen).toHaveBeenCalled();
...@@ -143,8 +147,14 @@ ...@@ -143,8 +147,14 @@
async function (done, _converse) { async function (done, _converse) {
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel')); await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
test_utils.openControlBox(); const toggle = document.querySelector(".toggle-controlbox");
const cbview = _converse.chatboxviews.get('controlbox'); if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
toggle.click();
}
const cbview = _converse.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click(); cbview.el.querySelector('.toggle-register-login').click();
const registerview = cbview.registerpanel; const registerview = cbview.registerpanel;
...@@ -153,7 +163,6 @@ ...@@ -153,7 +163,6 @@
spyOn(registerview, 'onRegistrationFields').and.callThrough(); spyOn(registerview, 'onRegistrationFields').and.callThrough();
spyOn(registerview, 'renderRegistrationForm').and.callThrough(); spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
spyOn(_converse.connection, 'connect').and.callThrough();
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org'; registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click(); registerview.el.querySelector('input[type=submit]').click();
...@@ -200,8 +209,14 @@ ...@@ -200,8 +209,14 @@
async function (done, _converse) { async function (done, _converse) {
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel')); await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
test_utils.openControlBox(); const toggle = document.querySelector(".toggle-controlbox");
const cbview = _converse.chatboxviews.get('controlbox'); if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
toggle.click();
}
const cbview = _converse.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click(); cbview.el.querySelector('.toggle-register-login').click();
const registerview = _converse.chatboxviews.get('controlbox').registerpanel; const registerview = _converse.chatboxviews.get('controlbox').registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough(); spyOn(registerview, 'onProviderChosen').and.callThrough();
...@@ -209,7 +224,6 @@ ...@@ -209,7 +224,6 @@
spyOn(registerview, 'onRegistrationFields').and.callThrough(); spyOn(registerview, 'onRegistrationFields').and.callThrough();
spyOn(registerview, 'renderRegistrationForm').and.callThrough(); spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
spyOn(_converse.connection, 'connect').and.callThrough();
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org'; registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
registerview.el.querySelector('input[type=submit]').click(); registerview.el.querySelector('input[type=submit]').click();
...@@ -274,7 +288,13 @@ ...@@ -274,7 +288,13 @@
async function (done, _converse) { async function (done, _converse) {
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel')); await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
test_utils.openControlBox(); const toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle);
}
toggle.click();
}
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.toggle-register-login').click(); cbview.el.querySelector('.toggle-register-login').click();
const registerview = _converse.chatboxviews.get('controlbox').registerpanel; const registerview = _converse.chatboxviews.get('controlbox').registerpanel;
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
// have to mock stanza traffic. // have to mock stanza traffic.
}, async function (done, _converse) { }, async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
const controlbox = _converse.chatboxviews.get('controlbox'); const controlbox = _converse.chatboxviews.get('controlbox');
let list = controlbox.el.querySelector('.list-container--openrooms'); let list = controlbox.el.querySelector('.list-container--openrooms');
expect(_.includes(list.classList, 'hidden')).toBeTruthy(); expect(_.includes(list.classList, 'hidden')).toBeTruthy();
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
expect(room_els.length).toBe(2); expect(room_els.length).toBe(2);
let view = _converse.chatboxviews.get('room@conference.shakespeare.lit'); let view = _converse.chatboxviews.get('room@conference.shakespeare.lit');
view.close(); await view.close();
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(1); expect(room_els.length).toBe(1);
expect(room_els[0].innerText).toBe('lounge@montague.lit'); expect(room_els[0].innerText).toBe('lounge@montague.lit');
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
u.waitUntil(() => _.includes(list.classList, 'hidden')); u.waitUntil(() => _.includes(list.classList, 'hidden'));
view = _converse.chatboxviews.get('lounge@montague.lit'); view = _converse.chatboxviews.get('lounge@montague.lit');
view.close(); await view.close();
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(0); expect(room_els.length).toBe(0);
...@@ -110,19 +110,21 @@ ...@@ -110,19 +110,21 @@
describe("A groupchat shown in the groupchats list", function () { describe("A groupchat shown in the groupchats list", function () {
it("is highlighted if it's currently open", mock.initConverse( it("is highlighted if it's currently open", mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'], ['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'],
{ view_mode: 'fullscreen', { view_mode: 'fullscreen',
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic. allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
}, async function (done, _converse) { }, async function (done, _converse) {
await _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'}); const muc_jid = 'coven@chat.shakespeare.lit';
await _converse.api.rooms.open(muc_jid, {'nick': 'some1'});
const lview = _converse.rooms_list_view const lview = _converse.rooms_list_view
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length); await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
let room_els = lview.el.querySelectorAll(".available-chatroom"); let room_els = lview.el.querySelectorAll(".available-chatroom");
expect(room_els.length).toBe(1); expect(room_els.length).toBe(1);
let item = room_els[0]; let item = room_els[0];
expect(u.hasClass('open', item)).toBe(true); await u.waitUntil(() => lview.model.get(muc_jid).get('hidden') === false);
await u.waitUntil(() => u.hasClass('open', item), 1000);
expect(item.textContent.trim()).toBe('coven@chat.shakespeare.lit'); expect(item.textContent.trim()).toBe('coven@chat.shakespeare.lit');
await _converse.api.rooms.open('balcony@chat.shakespeare.lit', {'nick': 'some1'}); await _converse.api.rooms.open('balcony@chat.shakespeare.lit', {'nick': 'some1'});
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length > 1); await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length > 1);
...@@ -137,15 +139,15 @@ ...@@ -137,15 +139,15 @@
})); }));
it("has an info icon which opens a details modal when clicked", mock.initConverse( it("has an info icon which opens a details modal when clicked", mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'], ['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'],
{ whitelisted_plugins: ['converse-roomslist'], { whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we allow_bookmarks: false // Makes testing easier, otherwise we
// have to mock stanza traffic. // have to mock stanza traffic.
}, async function (done, _converse) { }, async function (done, _converse) {
const IQ_stanzas = _converse.connection.IQ_stanzas; const IQ_stanzas = _converse.connection.IQ_stanzas;
const room_jid = 'coven@chat.shakespeare.lit'; const room_jid = 'coven@chat.shakespeare.lit';
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
await _converse.api.rooms.open(room_jid, {'nick': 'some1'}); await _converse.api.rooms.open(room_jid, {'nick': 'some1'});
const view = _converse.chatboxviews.get(room_jid); const view = _converse.chatboxviews.get(room_jid);
...@@ -242,11 +244,11 @@ ...@@ -242,11 +244,11 @@
})); }));
it("can be closed", mock.initConverse( it("can be closed", mock.initConverse(
['rosterGroupsFetched', 'emojisInitialized'], ['rosterGroupsFetched', 'emojisInitialized'],
{ whitelisted_plugins: ['converse-roomslist'], { whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic. allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
}, },
async function (done, _converse) { async function (done, _converse) {
spyOn(window, 'confirm').and.callFake(() => true); spyOn(window, 'confirm').and.callFake(() => true);
expect(_converse.chatboxes.length).toBe(1); expect(_converse.chatboxes.length).toBe(1);
...@@ -260,6 +262,8 @@ ...@@ -260,6 +262,8 @@
close_el.click(); close_el.click();
expect(window.confirm).toHaveBeenCalledWith( expect(window.confirm).toHaveBeenCalledWith(
'Are you sure you want to leave the groupchat lounge@conference.shakespeare.lit?'); 'Are you sure you want to leave the groupchat lounge@conference.shakespeare.lit?');
await u.waitUntil(() => !_converse.api.rooms.get().length);
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room"); room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(0); expect(room_els.length).toBe(0);
expect(_converse.chatboxes.length).toBe(1); expect(_converse.chatboxes.length).toBe(1);
...@@ -272,7 +276,7 @@ ...@@ -272,7 +276,7 @@
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic. allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
}, async (done, _converse) => { }, async (done, _converse) => {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
const room_jid = 'kitchen@conference.shakespeare.lit'; const room_jid = 'kitchen@conference.shakespeare.lit';
await u.waitUntil(() => _converse.rooms_list_view !== undefined, 500); await u.waitUntil(() => _converse.rooms_list_view !== undefined, 500);
await test_utils.openAndEnterChatRoom(_converse, 'kitchen@conference.shakespeare.lit', 'romeo'); await test_utils.openAndEnterChatRoom(_converse, 'kitchen@conference.shakespeare.lit', 'romeo');
...@@ -287,12 +291,10 @@ ...@@ -287,12 +291,10 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t('foo').tree()); }).c('body').t('foo').tree());
const lview = _converse.rooms_list_view
await u.waitUntil(() => lview.el.querySelectorAll(".available-chatroom").length, 500);
// If the user isn't mentioned, the counter doesn't get incremented, but the text of the groupchat is bold // If the user isn't mentioned, the counter doesn't get incremented, but the text of the groupchat is bold
let room_el = lview.el.querySelector(".available-chatroom"); const lview = _converse.rooms_list_view
expect(_.includes(room_el.classList, 'unread-msgs')).toBeTruthy(); let room_el = await u.waitUntil(() => lview.el.querySelector(".available-chatroom"));
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeTruthy();
// If the user is mentioned, the counter also gets updated // If the user is mentioned, the counter also gets updated
await view.model.onMessage( await view.model.onMessage(
...@@ -303,10 +305,11 @@ ...@@ -303,10 +305,11 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t('romeo: Your attention is required').tree() }).c('body').t('romeo: Your attention is required').tree()
); );
await u.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".msgs-indicator").length);
spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough(); let indicator_el = await u.waitUntil(() => lview.el.querySelector(".msgs-indicator"));
let indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
expect(indicator_el.textContent).toBe('1'); expect(indicator_el.textContent).toBe('1');
spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough();
await view.model.onMessage( await view.model.onMessage(
$msg({ $msg({
from: room_jid+'/'+nick, from: room_jid+'/'+nick,
...@@ -316,14 +319,13 @@ ...@@ -316,14 +319,13 @@
}).c('body').t('romeo: and another thing...').tree() }).c('body').t('romeo: and another thing...').tree()
); );
await u.waitUntil(() => view.model.incrementUnreadMsgCounter.calls.count()); await u.waitUntil(() => view.model.incrementUnreadMsgCounter.calls.count());
indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator"); await u.waitUntil(() => lview.el.querySelector(".msgs-indicator").textContent === '2', 1000);
expect(indicator_el.textContent).toBe('2');
// When the chat gets maximized again, the unread indicators are removed // When the chat gets maximized again, the unread indicators are removed
view.model.set({'minimized': false}); view.model.set({'minimized': false});
indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator"); indicator_el = lview.el.querySelector(".msgs-indicator");
expect(indicator_el === null); expect(indicator_el === null);
room_el = _converse.rooms_list_view.el.querySelector(".available-chatroom"); room_el = lview.el.querySelector(".available-chatroom");
expect(_.includes(room_el.classList, 'unread-msgs')).toBeFalsy(); expect(_.includes(room_el.classList, 'unread-msgs')).toBeFalsy();
done(); done();
})); }));
......
This diff is collapsed.
...@@ -39,18 +39,12 @@ ...@@ -39,18 +39,12 @@
let IQ_stanzas = _converse.connection.IQ_stanzas; let IQ_stanzas = _converse.connection.IQ_stanzas;
await u.waitUntil(() => IQ_stanzas.length === 4); await u.waitUntil(() => IQ_stanzas.length === 4);
let iq = IQ_stanzas.pop(); let iq = IQ_stanzas[IQ_stanzas.length-1];
expect(Strophe.serialize(iq)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`);
iq = IQ_stanzas[IQ_stanzas.length-1];
expect(Strophe.serialize(iq)).toBe( expect(Strophe.serialize(iq)).toBe(
`<iq id="${iq.getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`); `<iq id="${iq.getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
await test_utils.waitForRoster(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
IQ_stanzas.pop(); IQ_stanzas.pop();
const disco_iq = IQ_stanzas.pop(); const disco_iq = IQ_stanzas.pop();
expect(Strophe.serialize(disco_iq)).toBe( expect(Strophe.serialize(disco_iq)).toBe(
`<iq from="romeo@montague.lit" id="${disco_iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+ `<iq from="romeo@montague.lit" id="${disco_iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
...@@ -58,9 +52,13 @@ ...@@ -58,9 +52,13 @@
iq = IQ_stanzas.pop(); iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe( expect(Strophe.serialize(iq)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="montague.lit" type="get" xmlns="jabber:client">`+ `<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`); `<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`);
iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`);
expect(sent_stanzas.filter(s => (s.nodeName === 'r')).length).toBe(2); expect(sent_stanzas.filter(s => (s.nodeName === 'r')).length).toBe(2);
expect(_converse.session.get('unacked_stanzas').length).toBe(5); expect(_converse.session.get('unacked_stanzas').length).toBe(5);
...@@ -104,7 +102,7 @@ ...@@ -104,7 +102,7 @@
// test session resumption // test session resumption
_converse.connection.IQ_stanzas = []; _converse.connection.IQ_stanzas = [];
IQ_stanzas = _converse.connection.IQ_stanzas; IQ_stanzas = _converse.connection.IQ_stanzas;
_converse.api.connection.reconnect(); await _converse.api.connection.reconnect();
stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop()); stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop());
expect(Strophe.serialize(stanza)).toEqual('<resume h="2" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>'); expect(Strophe.serialize(stanza)).toEqual('<resume h="2" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
...@@ -120,9 +118,7 @@ ...@@ -120,9 +118,7 @@
// Test that unacked stanzas get resent out // Test that unacked stanzas get resent out
iq = IQ_stanzas.pop(); iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe( expect(Strophe.serialize(iq)).toBe(`<iq id="${iq.getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
`<iq from="romeo@montague.lit/orchard" id="${iq.getAttribute('id')}" to="romeo@montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`);
expect(IQ_stanzas.filter(iq => sizzle('query[xmlns="jabber:iq:roster"]', iq).pop()).length).toBe(0); expect(IQ_stanzas.filter(iq => sizzle('query[xmlns="jabber:iq:roster"]', iq).pop()).length).toBe(0);
...@@ -141,9 +137,6 @@ ...@@ -141,9 +137,6 @@
}, },
async function (done, _converse) { async function (done, _converse) {
const view = _converse.chatboxviews.get('controlbox');
spyOn(view, 'renderControlBoxPane').and.callThrough();
_converse.api.user.login('romeo@montague.lit/orchard', 'secret'); _converse.api.user.login('romeo@montague.lit/orchard', 'secret');
const sent_stanzas = _converse.connection.sent_stanzas; const sent_stanzas = _converse.connection.sent_stanzas;
let stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'enable')).pop()); let stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'enable')).pop());
...@@ -154,7 +147,7 @@ ...@@ -154,7 +147,7 @@
await test_utils.waitForRoster(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
// test session resumption // test session resumption
_converse.api.connection.reconnect(); await _converse.api.connection.reconnect();
stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop()); stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'resume')).pop());
expect(Strophe.serialize(stanza)).toEqual('<resume h="1" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>'); expect(Strophe.serialize(stanza)).toEqual('<resume h="1" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
}).t(spoiler_hint) }).t(spoiler_hint)
.tree(); .tree();
await _converse.chatboxes.onMessage(msg); await _converse.chatboxes.onMessage(msg);
await u.waitUntil(() => _converse.api.chats.get().length === 2);
const view = _converse.chatboxviews.get(sender_jid); const view = _converse.chatboxviews.get(sender_jid);
await new Promise(resolve => view.once('messageInserted', resolve)); await new Promise(resolve => view.once('messageInserted', resolve));
await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio') await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
...@@ -69,6 +70,7 @@ ...@@ -69,6 +70,7 @@
'xmlns': 'urn:xmpp:spoiler:0', 'xmlns': 'urn:xmpp:spoiler:0',
}).tree(); }).tree();
await _converse.chatboxes.onMessage(msg); await _converse.chatboxes.onMessage(msg);
await u.waitUntil(() => _converse.api.chats.get().length === 2);
const view = _converse.chatboxviews.get(sender_jid); const view = _converse.chatboxviews.get(sender_jid);
await new Promise(resolve => view.once('messageInserted', resolve)); await new Promise(resolve => view.once('messageInserted', resolve));
await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio') await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
...@@ -86,7 +88,7 @@ ...@@ -86,7 +88,7 @@
async (done, _converse) => { async (done, _converse) => {
await test_utils.waitForRoster(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
test_utils.openControlBox(); test_utils.openControlBox(_converse);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
// XXX: We need to send a presence from the contact, so that we // XXX: We need to send a presence from the contact, so that we
...@@ -159,7 +161,7 @@ ...@@ -159,7 +161,7 @@
async (done, _converse) => { async (done, _converse) => {
await test_utils.waitForRoster(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
test_utils.openControlBox(); test_utils.openControlBox(_converse);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
// XXX: We need to send a presence from the contact, so that we // XXX: We need to send a presence from the contact, so that we
...@@ -173,7 +175,7 @@ ...@@ -173,7 +175,7 @@
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
await test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]); await test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.api.chatviews.get(contact_jid);
await u.waitUntil(() => view.el.querySelector('.toggle-compose-spoiler')); await u.waitUntil(() => view.el.querySelector('.toggle-compose-spoiler'));
let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
......
(function (root, factory) { (function (root, factory) {
define([ define([
"jasmine", "jasmine",
"converse-core",
"mock", "mock",
"test-utils", "test-utils",
"utils", "utils",
"transcripts" "transcripts"
], factory ], factory
); );
} (this, function (jasmine, converse, mock, test_utils, utils, transcripts) { } (this, function (jasmine, mock, test_utils, utils, transcripts) {
var Strophe = converse.env.Strophe; var Strophe = converse.env.Strophe;
var _ = converse.env._; var _ = converse.env._;
var IGNORED_TAGS = [ var IGNORED_TAGS = [
......
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'], {}, ['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched'); _converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
await u.waitUntil(() => _converse.chatboxes.length > 1); await u.waitUntil(() => _converse.chatboxes.length > 1);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
let show_modal_button = view.el.querySelector('.show-user-details-modal'); let show_modal_button = view.el.querySelector('.show-user-details-modal');
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
['rosterGroupsFetched', 'emojisInitialized'], {}, ['rosterGroupsFetched', 'emojisInitialized'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched'); _converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
...@@ -60,9 +60,8 @@ ...@@ -60,9 +60,8 @@
const modal = view.user_details_modal; const modal = view.user_details_modal;
await u.waitUntil(() => u.isVisible(modal.el), 2000); await u.waitUntil(() => u.isVisible(modal.el), 2000);
spyOn(window, 'confirm').and.returnValue(true); spyOn(window, 'confirm').and.returnValue(true);
spyOn(view.model.contact, 'removeFromRoster').and.callFake(function (callback, errback) {
errback(); spyOn(view.model.contact, 'removeFromRoster').and.callFake((callback, errback) => errback());
});
let remove_contact_button = modal.el.querySelector('button.remove-contact'); let remove_contact_button = modal.el.querySelector('button.remove-contact');
expect(u.isVisible(remove_contact_button)).toBeTruthy(); expect(u.isVisible(remove_contact_button)).toBeTruthy();
remove_contact_button.click(); remove_contact_button.click();
......
...@@ -445,6 +445,14 @@ converse.plugins.add('converse-chatview', { ...@@ -445,6 +445,14 @@ converse.plugins.add('converse-chatview', {
this.insertIntoDOM(); this.insertIntoDOM();
this.scrollDown(); this.scrollDown();
this.content.addEventListener('scroll', this.markScrolled.bind(this)); this.content.addEventListener('scroll', this.markScrolled.bind(this));
/**
* Triggered whenever a `_converse.ChatBox` instance has fetched its messages from
* `sessionStorage` but **NOT** from the server.
* @event _converse#afterMessagesFetched
* @type {_converse.ChatBoxView | _converse.ChatRoomView}
* @example _converse.api.listen.on('afterMessagesFetched', view => { ... });
*/
_converse.api.trigger('afterMessagesFetched', this);
}, },
insertIntoDOM () { insertIntoDOM () {
...@@ -589,7 +597,7 @@ converse.plugins.add('converse-chatview', { ...@@ -589,7 +597,7 @@ converse.plugins.add('converse-chatview', {
} }
}, },
showHelpMessages (msgs, type, spinner) { showHelpMessages (msgs, type='info', spinner) {
msgs.forEach(msg => { msgs.forEach(msg => {
this.content.insertAdjacentHTML( this.content.insertAdjacentHTML(
'beforeend', 'beforeend',
...@@ -997,11 +1005,11 @@ converse.plugins.add('converse-chatview', { ...@@ -997,11 +1005,11 @@ converse.plugins.add('converse-chatview', {
} }
}, },
clearMessages (ev) { async clearMessages (ev) {
if (ev && ev.preventDefault) { ev.preventDefault(); } if (ev && ev.preventDefault) { ev.preventDefault(); }
const result = confirm(__("Are you sure you want to clear the messages from this conversation?")); const result = confirm(__("Are you sure you want to clear the messages from this conversation?"));
if (result === true) { if (result === true) {
this.model.clearMessages(); await this.model.clearMessages();
} }
return this; return this;
}, },
...@@ -1119,7 +1127,7 @@ converse.plugins.add('converse-chatview', { ...@@ -1119,7 +1127,7 @@ converse.plugins.add('converse-chatview', {
} }
}, },
close (ev) { async close (ev) {
if (ev && ev.preventDefault) { ev.preventDefault(); } if (ev && ev.preventDefault) { ev.preventDefault(); }
if (Backbone.history.getFragment() === "converse/chat?jid="+this.model.get('jid')) { if (Backbone.history.getFragment() === "converse/chat?jid="+this.model.get('jid')) {
_converse.router.navigate(''); _converse.router.navigate('');
...@@ -1130,7 +1138,7 @@ converse.plugins.add('converse-chatview', { ...@@ -1130,7 +1138,7 @@ converse.plugins.add('converse-chatview', {
this.model.setChatState(_converse.INACTIVE); this.model.setChatState(_converse.INACTIVE);
this.model.sendChatState(); this.model.sendChatState();
} }
this.model.close(); await this.model.close();
this.remove(); this.remove();
/** /**
* Triggered once a chatbox has been closed. * Triggered once a chatbox has been closed.
...@@ -1247,10 +1255,7 @@ converse.plugins.add('converse-chatview', { ...@@ -1247,10 +1255,7 @@ converse.plugins.add('converse-chatview', {
}, },
viewUnreadMessages () { viewUnreadMessages () {
this.model.save({ this.model.save({'scrolled': false, 'top_visible_message': null});
'scrolled': false,
'top_visible_message': null
});
this.scrollDown(); this.scrollDown();
}, },
...@@ -1315,7 +1320,7 @@ converse.plugins.add('converse-chatview', { ...@@ -1315,7 +1320,7 @@ converse.plugins.add('converse-chatview', {
* @namespace _converse.api.chatviews * @namespace _converse.api.chatviews
* @memberOf _converse.api * @memberOf _converse.api
*/ */
'chatviews': { chatviews: {
/** /**
* Get the view of an already open chat. * Get the view of an already open chat.
* @method _converse.api.chatviews.get * @method _converse.api.chatviews.get
...@@ -1329,13 +1334,9 @@ converse.plugins.add('converse-chatview', { ...@@ -1329,13 +1334,9 @@ converse.plugins.add('converse-chatview', {
* // To return an array of views, provide an array of JIDs: * // To return an array of views, provide an array of JIDs:
* _converse.api.chatviews.get(['buddy1@example.com', 'buddy2@example.com']) * _converse.api.chatviews.get(['buddy1@example.com', 'buddy2@example.com'])
*/ */
'get' (jids) { get (jids) {
if (jids === undefined) { if (jids === undefined) {
_converse.log( return Object.values(_converse.chatboxviews.getAll());
"chatviews.get: You need to provide at least one JID",
Strophe.LogLevel.ERROR
);
return null;
} }
if (isString(jids)) { if (isString(jids)) {
return _converse.chatboxviews.get(jids); return _converse.chatboxviews.get(jids);
......
...@@ -85,7 +85,7 @@ converse.plugins.add('converse-controlbox', { ...@@ -85,7 +85,7 @@ converse.plugins.add('converse-controlbox', {
ChatBoxes: { ChatBoxes: {
model (attrs, options) { model (attrs, options) {
const { _converse } = this.__super__; const { _converse } = this.__super__;
if (attrs.id == 'controlbox') { if (attrs && attrs.id == 'controlbox') {
return new _converse.ControlBox(attrs, options); return new _converse.ControlBox(attrs, options);
} else { } else {
return this.__super__.model.apply(this, arguments); return this.__super__.model.apply(this, arguments);
...@@ -292,14 +292,24 @@ converse.plugins.add('converse-controlbox', { ...@@ -292,14 +292,24 @@ converse.plugins.add('converse-controlbox', {
) )
}, },
close (ev) { async close (ev) {
if (ev && ev.preventDefault) { ev.preventDefault(); } if (ev && ev.preventDefault) { ev.preventDefault(); }
if (get(ev, 'name') === 'closeAllChatBoxes' &&
(_converse.disconnection_cause !== _converse.LOGOUT ||
_converse.show_controlbox_by_default)) {
return;
}
if (_converse.sticky_controlbox) { if (_converse.sticky_controlbox) {
return; return;
} }
const connection = get(_converse, 'connection', {}); const connection = get(_converse, 'connection', {});
if (connection.connected && !connection.disconnecting) { if (connection.connected && !connection.disconnecting) {
this.model.save({'closed': true}); await new Promise((resolve, reject) => {
return this.model.save(
{'closed': true},
{'success': resolve, 'error': reject}
);
});
} else { } else {
this.model.trigger('hide'); this.model.trigger('hide');
} }
...@@ -591,7 +601,7 @@ converse.plugins.add('converse-controlbox', { ...@@ -591,7 +601,7 @@ converse.plugins.add('converse-controlbox', {
_converse.api.listen.on('chatBoxesFetched', () => { _converse.api.listen.on('chatBoxesFetched', () => {
const controlbox = _converse.chatboxes.get('controlbox') || addControlBox(); const controlbox = _converse.chatboxes.get('controlbox') || addControlBox();
controlbox.save({connected:true}); controlbox.save({'connected': true});
}); });
const disconnect = function () { const disconnect = function () {
......
...@@ -49,9 +49,12 @@ converse.plugins.add('converse-dragresize', { ...@@ -49,9 +49,12 @@ converse.plugins.add('converse-dragresize', {
ChatBox: { ChatBox: {
initialize () { initialize () {
const { _converse } = this.__super__; const { _converse } = this.__super__;
const result = this.__super__.initialize.apply(this, arguments), const result = this.__super__.initialize.apply(this, arguments);
height = this.get('height'), width = this.get('width'), const height = this.get('height'), width = this.get('width');
save = this.get('id') === 'controlbox' ? this.set.bind(this) : this.save.bind(this); const save = this.get('id') === 'controlbox' ?
(attrs) => this.set(attrs) :
(attrs) => this.save(attrs);
save({ save({
'height': _converse.applyDragResistance(height, this.get('default_height')), 'height': _converse.applyDragResistance(height, this.get('default_height')),
'width': _converse.applyDragResistance(width, this.get('default_width')), 'width': _converse.applyDragResistance(width, this.get('default_width')),
......
...@@ -885,7 +885,6 @@ converse.plugins.add('converse-muc-views', { ...@@ -885,7 +885,6 @@ converse.plugins.add('converse-muc-views', {
*/ */
if (u.isPersistableModel(this.model)) { if (u.isPersistableModel(this.model)) {
this.model.clearUnreadMsgCounter(); this.model.clearUnreadMsgCounter();
this.model.save();
} }
this.scrollDown(); this.scrollDown();
}, },
...@@ -925,12 +924,12 @@ converse.plugins.add('converse-muc-views', { ...@@ -925,12 +924,12 @@ converse.plugins.add('converse-muc-views', {
* @private * @private
* @method _converse.ChatRoomView#close * @method _converse.ChatRoomView#close
*/ */
close () { async close () {
this.hide(); this.hide();
if (Backbone.history.getFragment() === "converse/room?jid="+this.model.get('jid')) { if (Backbone.history.getFragment() === "converse/room?jid="+this.model.get('jid')) {
_converse.router.navigate(''); _converse.router.navigate('');
} }
this.model.leave(); await this.model.leave();
return _converse.ChatBoxView.prototype.close.apply(this, arguments); return _converse.ChatBoxView.prototype.close.apply(this, arguments);
}, },
...@@ -2128,7 +2127,6 @@ converse.plugins.add('converse-muc-views', { ...@@ -2128,7 +2127,6 @@ converse.plugins.add('converse-muc-views', {
const view = _converse.chatboxviews.get('controlbox'); const view = _converse.chatboxviews.get('controlbox');
if (view && view.roomspanel) { if (view && view.roomspanel) {
view.roomspanel.model.destroy(); view.roomspanel.model.destroy();
view.roomspanel.model.browserStorage._clear();
view.roomspanel.remove(); view.roomspanel.remove();
delete view.roomspanel; delete view.roomspanel;
} }
...@@ -2197,6 +2195,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -2197,6 +2195,7 @@ converse.plugins.add('converse-muc-views', {
* *
* @method _converse.api.roomviews.close * @method _converse.api.roomviews.close
* @param {(String[]|String)} jids The JID or array of JIDs of the chatroom(s) * @param {(String[]|String)} jids The JID or array of JIDs of the chatroom(s)
* @returns { Promise } - Promise which resolves once the views have been closed.
*/ */
close (jids) { close (jids) {
let views; let views;
...@@ -2207,11 +2206,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -2207,11 +2206,7 @@ converse.plugins.add('converse-muc-views', {
} else if (Array.isArray(jids)) { } else if (Array.isArray(jids)) {
views = jids.map(jid => _converse.chatboxviews.get(jid)); views = jids.map(jid => _converse.chatboxviews.get(jid));
} }
views.forEach(view => { return Promise.all(views.map(v => (v.is_chatroom && v.model && v.close())))
if (view.is_chatroom && view.model) {
view.close();
}
});
} }
} }
}); });
......
...@@ -714,9 +714,8 @@ converse.plugins.add('converse-omemo', { ...@@ -714,9 +714,8 @@ converse.plugins.add('converse-omemo', {
if (identifier === null || identifier === undefined) { if (identifier === null || identifier === undefined) {
throw new Error("Can't save identity_key for invalid identifier"); throw new Error("Can't save identity_key for invalid identifier");
} }
const address = new libsignal.SignalProtocolAddress.fromString(identifier), const address = new libsignal.SignalProtocolAddress.fromString(identifier);
existing = this.get('identity_key'+address.getName()); const existing = this.get('identity_key'+address.getName());
const b64_idkey = u.arrayBufferToBase64(identity_key); const b64_idkey = u.arrayBufferToBase64(identity_key);
this.save('identity_key'+address.getName(), b64_idkey) this.save('identity_key'+address.getName(), b64_idkey)
...@@ -986,8 +985,7 @@ converse.plugins.add('converse-omemo', { ...@@ -986,8 +985,7 @@ converse.plugins.add('converse-omemo', {
initialize () { initialize () {
this.devices = new _converse.Devices(); this.devices = new _converse.Devices();
const id = `converse.devicelist-${_converse.bare_jid}-${this.get('jid')}`; const id = `converse.devicelist-${_converse.bare_jid}-${this.get('jid')}`;
const storage = _converse.config.get('storage'); this.devices.browserStorage = _converse.createStore(id);
this.devices.browserStorage = _converse.createStore(id, storage);
this.fetchDevices(); this.fetchDevices();
}, },
...@@ -1096,7 +1094,7 @@ converse.plugins.add('converse-omemo', { ...@@ -1096,7 +1094,7 @@ converse.plugins.add('converse-omemo', {
function fetchDeviceLists () { function fetchDeviceLists () {
return new Promise((success, error) => _converse.devicelists.fetch({success, error})); return new Promise((success, error) => _converse.devicelists.fetch({success, 'error': (m, e) => error(e)}));
} }
async function fetchOwnDevices () { async function fetchOwnDevices () {
...@@ -1171,10 +1169,9 @@ converse.plugins.add('converse-omemo', { ...@@ -1171,10 +1169,9 @@ converse.plugins.add('converse-omemo', {
function restoreOMEMOSession () { function restoreOMEMOSession () {
if (_converse.omemo_store === undefined) { if (_converse.omemo_store === undefined) {
const storage = _converse.config.get('storage'), const id = `converse.omemosession-${_converse.bare_jid}`;
id = `converse.omemosession-${_converse.bare_jid}`;
_converse.omemo_store = new _converse.OMEMOStore({'id': id}); _converse.omemo_store = new _converse.OMEMOStore({'id': id});
_converse.omemo_store.browserStorage = _converse.createStore(id, storage); _converse.omemo_store.browserStorage = _converse.createStore(id);
} }
return _converse.omemo_store.fetchSession(); return _converse.omemo_store.fetchSession();
} }
...@@ -1184,9 +1181,8 @@ converse.plugins.add('converse-omemo', { ...@@ -1184,9 +1181,8 @@ converse.plugins.add('converse-omemo', {
return; return;
} }
_converse.devicelists = new _converse.DeviceLists(); _converse.devicelists = new _converse.DeviceLists();
const storage = _converse.config.get('storage'), const id = `converse.devicelists-${_converse.bare_jid}`;
id = `converse.devicelists-${_converse.bare_jid}`; _converse.devicelists.browserStorage = _converse.createStore(id);
_converse.devicelists.browserStorage = _converse.createStore(id, storage);
try { try {
await fetchOwnDevices(); await fetchOwnDevices();
......
...@@ -808,9 +808,8 @@ converse.plugins.add('converse-rosterview', { ...@@ -808,9 +808,8 @@ converse.plugins.add('converse-rosterview', {
createRosterFilter () { createRosterFilter () {
// Create a model on which we can store filter properties // Create a model on which we can store filter properties
const model = new _converse.RosterFilter(); const model = new _converse.RosterFilter();
model.id = `_converse.rosterfilter${_converse.bare_jid}`; model.id = `_converse.rosterfilter-${_converse.bare_jid}`;
const storage = _converse.config.get('storage'); model.browserStorage = _converse.createStore(model.id);
model.browserStorage = _converse.createStore(this.filter.id, storage);
this.filter_view = new _converse.RosterFilterView({'model': model}); this.filter_view = new _converse.RosterFilterView({'model': model});
this.listenTo(this.filter_view.model, 'change', this.updateFilter); this.listenTo(this.filter_view.model, 'change', this.updateFilter);
this.filter_view.model.fetch(); this.filter_view.model.fetch();
......
...@@ -96,7 +96,15 @@ converse.plugins.add('converse-uniview', { ...@@ -96,7 +96,15 @@ converse.plugins.add('converse-uniview', {
.forEach(hideChat); .forEach(hideChat);
if (view.model.get('hidden')) { if (view.model.get('hidden')) {
u.safeSave(view.model, {'hidden': false}); return new Promise(resolve => {
u.safeSave(
view.model,
{'hidden': false}, {
'success': resolve,
'failure': resolve
}
);
});
} }
} }
}); });
......
...@@ -127,19 +127,13 @@ converse.plugins.add('converse-bookmarks', { ...@@ -127,19 +127,13 @@ converse.plugins.add('converse-bookmarks', {
fetchBookmarks () { fetchBookmarks () {
const deferred = u.getResolveablePromise(); const deferred = u.getResolveablePromise();
if (this.browserStorage.records.length > 0) { if (window.sessionStorage.getItem(this.fetched_flag)) {
this.fetch({ this.fetch({
'success': () => deferred.resolve(), 'success': () => deferred.resolve(),
'error': () => deferred.resolve() 'error': () => deferred.resolve()
}); });
} else if (! window.sessionStorage.getItem(this.fetched_flag)) {
// There aren't any cached bookmarks and the
// `fetched_flag` is off, so we query the XMPP server.
// If nothing is returned from the XMPP server, we set
// the `fetched_flag` to avoid calling the server again.
this.fetchBookmarksFromServer(deferred);
} else { } else {
deferred.resolve(); this.fetchBookmarksFromServer(deferred);
} }
return deferred; return deferred;
}, },
...@@ -231,6 +225,7 @@ converse.plugins.add('converse-bookmarks', { ...@@ -231,6 +225,7 @@ converse.plugins.add('converse-bookmarks', {
onBookmarksReceived (deferred, iq) { onBookmarksReceived (deferred, iq) {
this.createBookmarksFromStanza(iq); this.createBookmarksFromStanza(iq);
window.sessionStorage.setItem(this.fetched_flag, true);
if (deferred !== undefined) { if (deferred !== undefined) {
return deferred.resolve(); return deferred.resolve();
} }
...@@ -301,6 +296,7 @@ converse.plugins.add('converse-bookmarks', { ...@@ -301,6 +296,7 @@ converse.plugins.add('converse-bookmarks', {
if (_converse.bookmarks !== undefined) { if (_converse.bookmarks !== undefined) {
_converse.bookmarks.clearSession({'silent': true}); _converse.bookmarks.clearSession({'silent': true});
window.sessionStorage.removeItem(_converse.bookmarks.fetched_flag); window.sessionStorage.removeItem(_converse.bookmarks.fetched_flag);
delete _converse.bookmarks;
} }
}); });
......
...@@ -114,7 +114,6 @@ converse.plugins.add('converse-bosh', { ...@@ -114,7 +114,6 @@ converse.plugins.add('converse-bosh', {
sessionStorage.removeItem(`${id}-${id}`); sessionStorage.removeItem(`${id}-${id}`);
} else { } else {
_converse.bosh_session.destroy(); _converse.bosh_session.destroy();
_converse.bosh_session.browserStorage._clear();
delete _converse.bosh_session; delete _converse.bosh_session;
} }
}); });
......
...@@ -18,8 +18,8 @@ function propertySort (array, property) { ...@@ -18,8 +18,8 @@ function propertySort (array, property) {
} }
function generateVerificationString (_converse) { function generateVerificationString (_converse) {
const identities = _converse.api.disco.own.identities.get(), const identities = _converse.api.disco.own.identities.get();
features = _converse.api.disco.own.features.get(); const features = _converse.api.disco.own.features.get();
if (identities.length > 1) { if (identities.length > 1) {
propertySort(identities, "category"); propertySort(identities, "category");
......
...@@ -353,8 +353,7 @@ converse.plugins.add('converse-chatboxes', { ...@@ -353,8 +353,7 @@ converse.plugins.add('converse-chatboxes', {
initMessages () { initMessages () {
this.messages = new this.messagesCollection(); this.messages = new this.messagesCollection();
this.messages.chatbox = this; this.messages.chatbox = this;
const storage = _converse.config.get('storage'); this.messages.browserStorage = _converse.createStore(this.getMessagesCacheKey());
this.messages.browserStorage = _converse.createStore(this.getMessagesCacheKey(), storage);
this.listenTo(this.messages, 'change:upload', message => { this.listenTo(this.messages, 'change:upload', message => {
if (message.get('upload') === _converse.SUCCESS) { if (message.get('upload') === _converse.SUCCESS) {
_converse.api.send(this.createMessageStanza(message)); _converse.api.send(this.createMessageStanza(message));
...@@ -388,27 +387,28 @@ converse.plugins.add('converse-chatboxes', { ...@@ -388,27 +387,28 @@ converse.plugins.add('converse-chatboxes', {
return this.messages.fetched; return this.messages.fetched;
}, },
clearMessages () { async clearMessages () {
try { try {
this.messages.models.forEach(m => m.destroy()); await Promise.all(this.messages.models.map(m => m.destroy()));
this.messages.reset(); this.messages.reset();
} catch (e) { } catch (e) {
this.messages.trigger('reset'); this.messages.trigger('reset');
_converse.log(e, Strophe.LogLevel.ERROR); _converse.log(e, Strophe.LogLevel.ERROR);
} finally { } finally {
delete this.messages.fetched; delete this.messages.fetched;
this.messages.browserStorage._clear();
} }
}, },
close () { async close () {
try { try {
this.destroy(); await new Promise((success, reject) => {
return this.destroy({success, 'error': (m, e) => reject(e)})
});
} catch (e) { } catch (e) {
_converse.log(e, Strophe.LogLevel.ERROR); _converse.log(e, Strophe.LogLevel.ERROR);
} finally { } finally {
if (_converse.clear_messages_on_reconnection) { if (_converse.clear_messages_on_reconnection) {
this.clearMessages(); await this.clearMessages();
} }
} }
}, },
...@@ -1172,9 +1172,7 @@ converse.plugins.add('converse-chatboxes', { ...@@ -1172,9 +1172,7 @@ converse.plugins.add('converse-chatboxes', {
if (reconnecting) { if (reconnecting) {
return; return;
} }
const storage = _converse.config.get('storage'); this.browserStorage = _converse.createStore(`converse.chatboxes-${_converse.bare_jid}`);
const id = `converse.chatboxes-${_converse.bare_jid}`;
this.browserStorage = _converse.createStore(id, storage);
this.registerMessageHandler(); this.registerMessageHandler();
this.fetch({ this.fetch({
'add': true, 'add': true,
...@@ -1202,7 +1200,7 @@ converse.plugins.add('converse-chatboxes', { ...@@ -1202,7 +1200,7 @@ converse.plugins.add('converse-chatboxes', {
return; return;
} }
const attrs = await chatbox.getMessageAttributesFromStanza(stanza, stanza); const attrs = await chatbox.getMessageAttributesFromStanza(stanza, stanza);
chatbox.messages.create(attrs); await chatbox.messages.create(attrs);
}, },
/** /**
...@@ -1353,6 +1351,7 @@ converse.plugins.add('converse-chatboxes', { ...@@ -1353,6 +1351,7 @@ converse.plugins.add('converse-chatboxes', {
} }
jid = Strophe.getBareJidFromJid(jid.toLowerCase()); jid = Strophe.getBareJidFromJid(jid.toLowerCase());
await _converse.api.waitUntil('chatBoxesFetched');
let chatbox = this.get(Strophe.getBareJidFromJid(jid)); let chatbox = this.get(Strophe.getBareJidFromJid(jid));
if (chatbox) { if (chatbox) {
chatbox.save(attrs); chatbox.save(attrs);
...@@ -1519,11 +1518,7 @@ converse.plugins.add('converse-chatboxes', { ...@@ -1519,11 +1518,7 @@ converse.plugins.add('converse-chatboxes', {
* }); * });
*/ */
async open (jids, attrs, force) { async open (jids, attrs, force) {
await Promise.all([ await _converse.api.waitUntil('chatBoxesFetched');
_converse.api.waitUntil('rosterContactsFetched'),
_converse.api.waitUntil('chatBoxesFetched')
]);
if (isString(jids)) { if (isString(jids)) {
const chat = await _converse.api.chats.create(jids, attrs); const chat = await _converse.api.chats.create(jids, attrs);
if (chat) { if (chat) {
...@@ -1542,7 +1537,7 @@ converse.plugins.add('converse-chatboxes', { ...@@ -1542,7 +1537,7 @@ converse.plugins.add('converse-chatboxes', {
}, },
/** /**
* Returns a chat model. The chat should already be open. * Retrieves a chat model. The chat should already be open.
* *
* @method _converse.api.chats.get * @method _converse.api.chats.get
* @param {String|string[]} jids - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com'] * @param {String|string[]} jids - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com']
......
...@@ -26,6 +26,7 @@ const $msg = strophe.default.$msg; ...@@ -26,6 +26,7 @@ const $msg = strophe.default.$msg;
const $pres = strophe.default.$pres; const $pres = strophe.default.$pres;
Backbone = Backbone.noConflict(); Backbone = Backbone.noConflict();
BrowserStorage.patch(Backbone);
dayjs.extend(advancedFormat); dayjs.extend(advancedFormat);
...@@ -109,9 +110,20 @@ _converse.VERSION_NAME = "v5.0.5dev"; ...@@ -109,9 +110,20 @@ _converse.VERSION_NAME = "v5.0.5dev";
Object.assign(_converse, Backbone.Events); Object.assign(_converse, Backbone.Events);
_converse.Collection = Backbone.Collection.extend({ _converse.Collection = Backbone.Collection.extend({
clearSession (options) { async clearSession (options={}) {
Array.from(this.models).forEach(m => m.destroy(options)); await Promise.all(Array.from(this.models).map(m => {
this.browserStorage._clear(); return new Promise(
success => m.destroy(
Object.assign(options, {
success,
'error': (m, e) => {
_converse.log(e, Strophe.LogLevel.ERROR);
success()
}
})
)
);
}));
this.reset(); this.reset();
} }
}); });
...@@ -377,9 +389,40 @@ _converse.isUniView = function () { ...@@ -377,9 +389,40 @@ _converse.isUniView = function () {
return _.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode); return _.includes(['mobile', 'fullscreen', 'embedded'], _converse.view_mode);
}; };
async function initStorage () {
await BrowserStorage.sessionStorageInitialized;
// Sets up Backbone.BrowserStorage and localForage for the 3 different stores.
_converse.localStorage = BrowserStorage.localForage.createInstance({
'name': 'local',
'description': 'localStorage instance',
'driver': [BrowserStorage.localForage.LOCALSTORAGE]
});
_converse.indexedDB = BrowserStorage.localForage.createInstance({
'name': 'indexed',
'description': 'indexedDB instance',
'driver': [BrowserStorage.localForage.INDEXEDDB]
});
_converse.sessionStorage = BrowserStorage.localForage.createInstance({
'name': 'session',
'description': 'sessionStorage instance',
'driver': ['sessionStorageWrapper']
});
_converse.storage = {
'session': _converse.sessionStorage,
'local': _converse.localStorage,
'indexed': _converse.indexedDB
}
}
_converse.createStore = function (id, storage) { _converse.createStore = function (id, storage) {
const s = storage ? storage : _converse.config.get('storage'); const s = _converse.storage[storage ? storage : _converse.config.get('storage')];
return new BrowserStorage[s](id); return new Backbone.BrowserStorage(id, s);
} }
...@@ -455,8 +498,8 @@ function initClientConfig () { ...@@ -455,8 +498,8 @@ function initClientConfig () {
} }
function tearDown () { async function tearDown () {
_converse.api.trigger('beforeTearDown'); await _converse.api.trigger('beforeTearDown', {'synchronous': true});
window.removeEventListener('click', _converse.onUserActivity); window.removeEventListener('click', _converse.onUserActivity);
window.removeEventListener('focus', _converse.onUserActivity); window.removeEventListener('focus', _converse.onUserActivity);
window.removeEventListener('keypress', _converse.onUserActivity); window.removeEventListener('keypress', _converse.onUserActivity);
...@@ -531,7 +574,7 @@ function connect (credentials) { ...@@ -531,7 +574,7 @@ function connect (credentials) {
} }
function reconnect () { async function reconnect () {
_converse.log('RECONNECTING: the connection has dropped, attempting to reconnect.'); _converse.log('RECONNECTING: the connection has dropped, attempting to reconnect.');
_converse.setConnectionStatus( _converse.setConnectionStatus(
Strophe.Status.RECONNECTING, Strophe.Status.RECONNECTING,
...@@ -546,7 +589,7 @@ function reconnect () { ...@@ -546,7 +589,7 @@ function reconnect () {
_converse.api.trigger('will-reconnect'); _converse.api.trigger('will-reconnect');
_converse.connection.reconnecting = true; _converse.connection.reconnecting = true;
tearDown(); await tearDown();
return _converse.api.user.login(); return _converse.api.user.login();
} }
...@@ -558,7 +601,6 @@ _converse.shouldClearCache = () => (!_converse.config.get('trusted') || _convers ...@@ -558,7 +601,6 @@ _converse.shouldClearCache = () => (!_converse.config.get('trusted') || _convers
function clearSession () { function clearSession () {
if (_converse.session !== undefined) { if (_converse.session !== undefined) {
_converse.session.destroy(); _converse.session.destroy();
_converse.session.browserStorage._clear();
delete _converse.session; delete _converse.session;
} }
/** /**
...@@ -669,7 +711,7 @@ async function initSession (jid) { ...@@ -669,7 +711,7 @@ async function initSession (jid) {
await new Promise(r => _converse.session.fetch({'success': r, 'error': r})); await new Promise(r => _converse.session.fetch({'success': r, 'error': r}));
if (_converse.session.get('active')) { if (_converse.session.get('active')) {
_converse.session.clear(); _converse.session.clear();
_converse.session.save({'id': id}); _converse.session.save({id});
} }
saveJIDtoSession(jid); saveJIDtoSession(jid);
/** /**
...@@ -822,6 +864,7 @@ function setUpXMLLogging () { ...@@ -822,6 +864,7 @@ function setUpXMLLogging () {
async function finishInitialization () { async function finishInitialization () {
await initStorage();
initClientConfig(); initClientConfig();
initPlugins(); initPlugins();
_converse.registerGlobalEventHandlers(); _converse.registerGlobalEventHandlers();
...@@ -920,10 +963,15 @@ function unregisterGlobalEventHandlers () { ...@@ -920,10 +963,15 @@ function unregisterGlobalEventHandlers () {
_converse.api.trigger('unregisteredGlobalEventHandlers'); _converse.api.trigger('unregisteredGlobalEventHandlers');
} }
function cleanup () { async function cleanup () {
// Looks like _converse.initialized was called again without logging // Make sure everything is reset in case this is a subsequent call to
// out or disconnecting in the previous session. // convesre.initialize (happens during tests).
// This happens in tests. We therefore first clean up. if (_converse.localStorage) {
await Promise.all([
BrowserStorage.localForage.dropInstance({'name': 'local'}),
BrowserStorage.localForage.dropInstance({'name': 'indexed'}),
BrowserStorage.localForage.dropInstance({'name': 'session'})]);
}
Backbone.history.stop(); Backbone.history.stop();
unregisterGlobalEventHandlers(); unregisterGlobalEventHandlers();
delete _converse.controlboxtoggle; delete _converse.controlboxtoggle;
...@@ -939,7 +987,7 @@ function cleanup () { ...@@ -939,7 +987,7 @@ function cleanup () {
_converse.initialize = async function (settings, callback) { _converse.initialize = async function (settings, callback) {
cleanup(); await cleanup();
settings = settings !== undefined ? settings : {}; settings = settings !== undefined ? settings : {};
PROMISES.forEach(addPromise); PROMISES.forEach(addPromise);
...@@ -1256,7 +1304,7 @@ _converse.api = { ...@@ -1256,7 +1304,7 @@ _converse.api = {
const conn_status = _converse.connfeedback.get('connection_status'); const conn_status = _converse.connfeedback.get('connection_status');
if (_converse.authentication === _converse.ANONYMOUS) { if (_converse.authentication === _converse.ANONYMOUS) {
tearDown(); await tearDown();
clearSession(); clearSession();
} }
if (conn_status === Strophe.Status.CONNFAIL) { if (conn_status === Strophe.Status.CONNFAIL) {
...@@ -1421,8 +1469,7 @@ _converse.api = { ...@@ -1421,8 +1469,7 @@ _converse.api = {
complete(); complete();
} }
return promise; return promise;
}, }
}, },
/** /**
......
...@@ -120,18 +120,24 @@ converse.plugins.add('converse-disco', { ...@@ -120,18 +120,24 @@ converse.plugins.add('converse-disco', {
_converse.api.trigger('discoExtensionFieldDiscovered', field); _converse.api.trigger('discoExtensionFieldDiscovered', field);
}, },
fetchFeatures (options) { async fetchFeatures (options) {
if (options.ignore_cache || this.features.browserStorage.records.length === 0) { if (options.ignore_cache) {
this.queryInfo(); this.queryInfo();
} else { } else {
this.features.fetch({ const store_id = this.features.browserStorage.name;
add: true, const result = await this.features.browserStorage.store.getItem(store_id);
success: () => { if (result && result.length === 0 || result === null) {
this.waitUntilFeaturesDiscovered.resolve(this); this.queryInfo();
this.trigger('featuresDiscovered'); } else {
} this.features.fetch({
}); add: true,
this.identities.fetch({add: true}); success: () => {
this.waitUntilFeaturesDiscovered.resolve(this);
this.trigger('featuresDiscovered');
}
});
this.identities.fetch({add: true});
}
} }
}, },
...@@ -259,7 +265,7 @@ converse.plugins.add('converse-disco', { ...@@ -259,7 +265,7 @@ converse.plugins.add('converse-disco', {
function initStreamFeatures () { function initStreamFeatures () {
const bare_jid = Strophe.getBareJidFromJid(_converse.jid); const bare_jid = Strophe.getBareJidFromJid(_converse.jid);
const id = `converse.stream-features-${bare_jid}`; const id = `converse.stream-features-${bare_jid}`;
if (!_converse.stream_features || _converse.stream_features.browserStorage.id !== id) { if (!_converse.stream_features || _converse.stream_features.browserStorage.name !== id) {
_converse.stream_features = new _converse.Collection(); _converse.stream_features = new _converse.Collection();
_converse.stream_features.browserStorage = _converse.createStore(id, "session"); _converse.stream_features.browserStorage = _converse.createStore(id, "session");
_converse.stream_features.fetch({ _converse.stream_features.fetch({
...@@ -281,6 +287,9 @@ converse.plugins.add('converse-disco', { ...@@ -281,6 +287,9 @@ converse.plugins.add('converse-disco', {
* @example _converse.api.listen.on('streamFeaturesAdded', () => { ... }); * @example _converse.api.listen.on('streamFeaturesAdded', () => { ... });
*/ */
_converse.api.trigger('streamFeaturesAdded'); _converse.api.trigger('streamFeaturesAdded');
},
error (m, e) {
_converse.log(e, Strophe.LogLevel.ERROR);
} }
}); });
} }
...@@ -345,15 +354,17 @@ converse.plugins.add('converse-disco', { ...@@ -345,15 +354,17 @@ converse.plugins.add('converse-disco', {
/******************** Event Handlers ********************/ /******************** Event Handlers ********************/
// Re-create promise upon reconnection
_converse.api.listen.on('userSessionInitialized', initStreamFeatures); _converse.api.listen.on('userSessionInitialized', initStreamFeatures);
_converse.api.listen.on('beforeResourceBinding', initStreamFeatures); _converse.api.listen.on('beforeResourceBinding', initStreamFeatures);
_converse.api.listen.on('reconnected', initializeDisco); _converse.api.listen.on('reconnected', initializeDisco);
_converse.api.listen.on('connected', initializeDisco); _converse.api.listen.on('connected', initializeDisco);
_converse.api.listen.on('beforeTearDown', () => { _converse.api.listen.on('beforeTearDown', async () => {
_converse.api.promises.add('streamFeaturesAdded')
if (_converse.stream_features) { if (_converse.stream_features) {
_converse.stream_features.clearSession(); await _converse.stream_features.clearSession();
delete _converse.stream_features; delete _converse.stream_features;
} }
}); });
......
...@@ -388,7 +388,6 @@ converse.plugins.add('converse-emoji', { ...@@ -388,7 +388,6 @@ converse.plugins.add('converse-emoji', {
/************************ BEGIN Event Handlers ************************/ /************************ BEGIN Event Handlers ************************/
_converse.api.listen.on('clearSession', () => { _converse.api.listen.on('clearSession', () => {
if (_converse.emojipicker) { if (_converse.emojipicker) {
_converse.emojipicker.browserStorage._clear();
_converse.emojipicker.destroy(); _converse.emojipicker.destroy();
delete _converse.emojipicker delete _converse.emojipicker
} }
......
...@@ -253,16 +253,14 @@ converse.plugins.add('converse-mam', { ...@@ -253,16 +253,14 @@ converse.plugins.add('converse-mam', {
chat.fetchNewestMessages(); chat.fetchNewestMessages();
} }
}); });
_converse.api.listen.on('afterMessagesFetched', chat => { _converse.api.listen.on('afterMessagesFetched', chat => {
// XXX: We don't want to query MAM every time this is triggered // XXX: We don't want to query MAM every time this is triggered
// since it's not necessary when the chat is restored from cache. // since it's not necessary when the chat is restored from cache.
// (given that BOSH or SMACKS will ensure that you get messages // (given that BOSH or SMACKS will ensure that you get messages
// sent during the reload). // sent during the reload).
// // With MUCs we can listen for `enteredNewRoom`.
// With MUCs we can listen for `enteredNewRoom` but for if (chat.get('type') === _converse.PRIVATE_CHAT_TYPE && !_converse.connection.restored) {
// one-on-one we have to use this hacky solutoin for now.
// `chat_state` is `undefined` only for newly created chats.
if (chat.get('type') === _converse.PRIVATE_CHAT_TYPE && chat.get('chat_state') === undefined) {
chat.fetchNewestMessages(); chat.fetchNewestMessages();
} }
}); });
......
...@@ -84,10 +84,17 @@ converse.plugins.add('converse-muc', { ...@@ -84,10 +84,17 @@ converse.plugins.add('converse-muc', {
dependencies: ["converse-chatboxes", "converse-disco", "converse-controlbox"], dependencies: ["converse-chatboxes", "converse-disco", "converse-controlbox"],
overrides: { overrides: {
tearDown () {
const { _converse } = this.__super__;
const groupchats = this.chatboxes.where({'type': _converse.CHATROOMS_TYPE});
_.each(groupchats, gc => u.safeSave(gc, {'connection_status': converse.ROOMSTATUS.DISCONNECTED}));
this.__super__.tearDown.call(this, arguments);
},
ChatBoxes: { ChatBoxes: {
model (attrs, options) { model (attrs, options) {
const { _converse } = this.__super__; const { _converse } = this.__super__;
if (attrs.type == _converse.CHATROOMS_TYPE) { if (attrs && attrs.type == _converse.CHATROOMS_TYPE) {
return new _converse.ChatRoom(attrs, options); return new _converse.ChatRoom(attrs, options);
} else { } else {
return this.__super__.model.apply(this, arguments); return this.__super__.model.apply(this, arguments);
...@@ -528,11 +535,15 @@ converse.plugins.add('converse-muc', { ...@@ -528,11 +535,15 @@ converse.plugins.add('converse-muc', {
* registered for this groupchat. * registered for this groupchat.
*/ */
if (this.message_handler) { if (this.message_handler) {
_converse.connection.deleteHandler(this.message_handler); if (_converse.connection) {
_converse.connection.deleteHandler(this.message_handler);
}
delete this.message_handler; delete this.message_handler;
} }
if (this.presence_handler) { if (this.presence_handler) {
_converse.connection.deleteHandler(this.presence_handler); if (_converse.connection) {
_converse.connection.deleteHandler(this.presence_handler);
}
delete this.presence_handler; delete this.presence_handler;
} }
return this; return this;
...@@ -612,14 +623,13 @@ converse.plugins.add('converse-muc', { ...@@ -612,14 +623,13 @@ converse.plugins.add('converse-muc', {
* @method _converse.ChatRoom#leave * @method _converse.ChatRoom#leave
* @param { string } [exit_msg] - Message to indicate your reason for leaving * @param { string } [exit_msg] - Message to indicate your reason for leaving
*/ */
leave (exit_msg) { async leave (exit_msg) {
this.features.destroy(); this.features.destroy();
this.occupants.browserStorage._clear(); this.occupants.clearSession();
this.occupants.reset();
if (_converse.disco_entities) { if (_converse.disco_entities) {
const disco_entity = _converse.disco_entities.get(this.get('jid')); const disco_entity = _converse.disco_entities.get(this.get('jid'));
if (disco_entity) { if (disco_entity) {
disco_entity.destroy(); await new Promise((success, error) => disco_entity.destroy({success, error}));
} }
} }
if (_converse.api.connection.connected()) { if (_converse.api.connection.connected()) {
...@@ -629,10 +639,11 @@ converse.plugins.add('converse-muc', { ...@@ -629,10 +639,11 @@ converse.plugins.add('converse-muc', {
this.removeHandlers(); this.removeHandlers();
}, },
close () { async close () {
try { try {
this.features.destroy(); await new Promise((success, reject) => {
this.features.browserStorage._clear(); return this.features.destroy({success, 'error': (m, e) => reject(e)})
});
} catch (e) { } catch (e) {
_converse.log(e, Strophe.LogLevel.ERROR); _converse.log(e, Strophe.LogLevel.ERROR);
} }
...@@ -838,9 +849,10 @@ converse.plugins.add('converse-muc', { ...@@ -838,9 +849,10 @@ converse.plugins.add('converse-muc', {
} }
const fields = await _converse.api.disco.getFields(this.get('jid')); const fields = await _converse.api.disco.getFields(this.get('jid'));
this.save({ this.save({
'name': identity && identity.get('name'), 'name': identity && identity.get('name'),
'description': _.get(fields.findWhere({'var': "muc#roominfo_description"}), 'attributes.value') 'description': _.get(fields.findWhere({'var': "muc#roominfo_description"}), 'attributes.value')
}); }
);
const features = await _converse.api.disco.getFeatures(this.get('jid')); const features = await _converse.api.disco.getFeatures(this.get('jid'));
const attrs = Object.assign( const attrs = Object.assign(
...@@ -857,6 +869,7 @@ converse.plugins.add('converse-muc', { ...@@ -857,6 +869,7 @@ converse.plugins.add('converse-muc', {
} }
attrs[fieldname.replace('muc_', '')] = true; attrs[fieldname.replace('muc_', '')] = true;
}); });
attrs.description = _.get(fields.findWhere({'var': "muc#roominfo_description"}), 'attributes.value');
this.features.save(attrs); this.features.save(attrs);
}, },
...@@ -1012,33 +1025,6 @@ converse.plugins.add('converse-muc', { ...@@ -1012,33 +1025,6 @@ converse.plugins.add('converse-muc', {
return this.occupants.findWhere({'jid': _converse.bare_jid}); return this.occupants.findWhere({'jid': _converse.bare_jid});
}, },
/**
* Parse the presence stanza for the current user's affiliation and
* role and save them on the relevant {@link _converse.ChatRoomOccupant}
* instance.
* @private
* @method _converse.ChatRoom#saveAffiliationAndRole
* @param { XMLElement } pres - A <presence> stanza.
*/
saveAffiliationAndRole (pres) {
const item = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"] item`, pres).pop();
const is_self = (pres.querySelector("status[code='110']") !== null);
if (is_self && item) {
const affiliation = item.getAttribute('affiliation');
const role = item.getAttribute('role');
const changes = {};
if (affiliation) {
changes['affiliation'] = affiliation;
}
if (role) {
changes['role'] = role;
}
if (!_.isEmpty(changes)) {
this.getOwnOccupant().save(changes);
}
}
},
/** /**
* Send an IQ stanza specifying an affiliation change. * Send an IQ stanza specifying an affiliation change.
* @private * @private
...@@ -1265,8 +1251,7 @@ converse.plugins.add('converse-muc', { ...@@ -1265,8 +1251,7 @@ converse.plugins.add('converse-muc', {
}, },
/** /**
* Given a presence stanza, update the occupant model * Given a presence stanza, update the occupant model based on its contents.
* based on its contents.
* @private * @private
* @method _converse.ChatRoom#updateOccupantsOnPresence * @method _converse.ChatRoom#updateOccupantsOnPresence
* @param { XMLElement } pres - The presence stanza * @param { XMLElement } pres - The presence stanza
...@@ -1572,7 +1557,13 @@ converse.plugins.add('converse-muc', { ...@@ -1572,7 +1557,13 @@ converse.plugins.add('converse-muc', {
!this.ignorableCSN(attrs) && !this.ignorableCSN(attrs) &&
(attrs['chat_state'] || !u.isEmptyMessage(attrs))) { (attrs['chat_state'] || !u.isEmptyMessage(attrs))) {
const msg = this.correctMessage(attrs) || this.messages.create(attrs); const msg = this.correctMessage(attrs) ||
await new Promise((success, reject) => {
this.messages.create(
attrs,
{ success, 'erorr': (m, e) => reject(e) }
)
});
this.incrementUnreadMsgCounter(msg); this.incrementUnreadMsgCounter(msg);
} }
_converse.api.trigger('message', {'stanza': original_stanza, 'chatbox': this}); _converse.api.trigger('message', {'stanza': original_stanza, 'chatbox': this});
...@@ -1802,7 +1793,6 @@ converse.plugins.add('converse-muc', { ...@@ -1802,7 +1793,6 @@ converse.plugins.add('converse-muc', {
this.save('connection_status', converse.ROOMSTATUS.ENTERED); this.save('connection_status', converse.ROOMSTATUS.ENTERED);
} }
this.updateOccupantsOnPresence(stanza); this.updateOccupantsOnPresence(stanza);
this.saveAffiliationAndRole(stanza);
if (stanza.getAttribute('type') === 'unavailable') { if (stanza.getAttribute('type') === 'unavailable') {
this.handleDisconnection(stanza); this.handleDisconnection(stanza);
...@@ -1839,6 +1829,7 @@ converse.plugins.add('converse-muc', { ...@@ -1839,6 +1829,7 @@ converse.plugins.add('converse-muc', {
} }
} }
} }
this.save({'connection_status': converse.ROOMSTATUS.ENTERED});
}, },
/** /**
...@@ -1983,6 +1974,12 @@ converse.plugins.add('converse-muc', { ...@@ -1983,6 +1974,12 @@ converse.plugins.add('converse-muc', {
this.create(attrs); this.create(attrs);
} }
}); });
/**
* Triggered once the member lists for this MUC have been fetched and processed.
* @event _converse#membersFetched
* @example _converse.api.listen.on('membersFetched', () => { ... });
*/
_converse.api.trigger('membersFetched');
}, },
findOccupant (data) { findOccupant (data) {
...@@ -2142,7 +2139,7 @@ converse.plugins.add('converse-muc', { ...@@ -2142,7 +2139,7 @@ converse.plugins.add('converse-muc', {
*/ */
return _converse.chatboxes return _converse.chatboxes
.filter(m => (m.get('type') === _converse.CHATROOMS_TYPE)) .filter(m => (m.get('type') === _converse.CHATROOMS_TYPE))
.forEach(m => m.save('connection_status', converse.ROOMSTATUS.DISCONNECTED)) .forEach(m => m.save({'connection_status': converse.ROOMSTATUS.DISCONNECTED}));
} }
_converse.api.listen.on('disconnected', disconnectChatRooms); _converse.api.listen.on('disconnected', disconnectChatRooms);
...@@ -2267,15 +2264,14 @@ converse.plugins.add('converse-muc', { ...@@ -2267,15 +2264,14 @@ converse.plugins.add('converse-muc', {
room && room.maybeShow(force); room && room.maybeShow(force);
return room; return room;
} else { } else {
return jids.map(async jid => { const rooms = await Promise.all(jids.map(jid => _converse.api.rooms.create(jid, attrs)));
const room = await _converse.api.rooms.create(jid, attrs); rooms.forEach(r => r.maybeShow(force));
room.maybeShow(force); return rooms;
});
} }
}, },
/** /**
* Returns an object representing a MUC chatroom (aka groupchat) * Fetches the object representing a MUC chatroom (aka groupchat)
* *
* @method _converse.api.rooms.get * @method _converse.api.rooms.get
* @param {string} [jid] The room JID (if not specified, all rooms will be returned). * @param {string} [jid] The room JID (if not specified, all rooms will be returned).
......
...@@ -684,11 +684,10 @@ converse.plugins.add('converse-roster', { ...@@ -684,11 +684,10 @@ converse.plugins.add('converse-roster', {
const jid = item.getAttribute('jid'); const jid = item.getAttribute('jid');
if (this.isSelf(jid)) { return; } if (this.isSelf(jid)) { return; }
const contact = this.get(jid), const contact = this.get(jid);
subscription = item.getAttribute("subscription"), const subscription = item.getAttribute("subscription");
ask = item.getAttribute("ask"), const ask = item.getAttribute("ask");
groups = _.map(item.getElementsByTagName('group'), Strophe.getText); const groups = Array.from(item.getElementsByTagName('group')).map(e => e.textContent);
if (!contact) { if (!contact) {
if ((subscription === "none" && ask === null) || (subscription === "remove")) { if ((subscription === "none" && ask === null) || (subscription === "remove")) {
return; // We're lazy when adding contacts. return; // We're lazy when adding contacts.
...@@ -959,7 +958,6 @@ converse.plugins.add('converse-roster', { ...@@ -959,7 +958,6 @@ converse.plugins.add('converse-roster', {
if (_converse.shouldClearCache()) { if (_converse.shouldClearCache()) {
if (_converse.roster) { if (_converse.roster) {
_.invoke(_converse, 'roster.data.destroy'); _.invoke(_converse, 'roster.data.destroy');
_.invoke(_converse, 'roster.data.browserStorage._clear');
_converse.roster.clearSession(); _converse.roster.clearSession();
delete _converse.roster; delete _converse.roster;
} }
...@@ -1024,7 +1022,7 @@ converse.plugins.add('converse-roster', { ...@@ -1024,7 +1022,7 @@ converse.plugins.add('converse-roster', {
* @namespace _converse.api.contacts * @namespace _converse.api.contacts
* @memberOf _converse.api * @memberOf _converse.api
*/ */
'contacts': { contacts: {
/** /**
* This method is used to retrieve roster contacts. * This method is used to retrieve roster contacts.
* *
......
...@@ -225,7 +225,6 @@ converse.plugins.add('converse-status', { ...@@ -225,7 +225,6 @@ converse.plugins.add('converse-status', {
_converse.api.listen.on('clearSession', () => { _converse.api.listen.on('clearSession', () => {
if (_converse.shouldClearCache() && _converse.xmppstatus) { if (_converse.shouldClearCache() && _converse.xmppstatus) {
_converse.xmppstatus.destroy(); _converse.xmppstatus.destroy();
_converse.xmppstatus.browserStorage._clear();
delete _converse.xmppstatus; delete _converse.xmppstatus;
} }
}); });
......
{ {
"name": "@converse/headless", "name": "@converse/headless",
"version": "4.2.0", "version": "5.0.3",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"backbone": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.0.tgz",
"integrity": "sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ==",
"dev": true,
"requires": {
"underscore": ">=1.8.3"
}
},
"backbone.browserStorage": {
"version": "github:conversejs/backbone.browserStorage#4fcb17861023becb3b25dec7b3238253873c8cd6",
"from": "github:conversejs/backbone.browserStorage#4fcb17861023becb3b25dec7b3238253873c8cd6",
"dev": true
},
"es6-promise": { "es6-promise": {
"version": "4.2.8", "version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
...@@ -15,6 +29,96 @@ ...@@ -15,6 +29,96 @@
"resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz",
"integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==",
"dev": true "dev": true
},
"immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=",
"dev": true
},
"jed": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/jed/-/jed-1.1.1.tgz",
"integrity": "sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ=",
"dev": true
},
"lie": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
"integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
"dev": true,
"requires": {
"immediate": "~3.0.5"
}
},
"localforage": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.7.3.tgz",
"integrity": "sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==",
"dev": true,
"requires": {
"lie": "3.1.1"
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"moment": {
"version": "2.19.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.19.4.tgz",
"integrity": "sha512-1xFTAknSLfc47DIxHDUbnJWC+UwgWxATmymaxIPQpmMh7LBm7ZbwVEsuushqwL2GYZU0jie4xO+TK44hJPjNSQ==",
"dev": true
},
"pluggable.js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pluggable.js/-/pluggable.js-2.0.0.tgz",
"integrity": "sha512-FgrSayXWfQQWL+RSDiCAFZbkEsY7hTZCiSuN9Ar/mcHpesxOPfrSzJKp+YbimOt9QFtSd+lR8Uob5tgkdQSOzg==",
"dev": true,
"requires": {
"lodash": "^4.17.4"
}
},
"strophe.js": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.3.2.tgz",
"integrity": "sha512-N6n93B0+0/qRazWUtgunJNE3DTfEpz363i17Uvnqh0lvl9iATnMtSoZtKjqN3reKPtjtBpbBRMWAQJRc688QkQ==",
"dev": true
},
"strophejs-plugin-ping": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/strophejs-plugin-ping/-/strophejs-plugin-ping-0.0.3.tgz",
"integrity": "sha512-HS/ArEGKXfu36fihjUSfjqmqOSyppQTJUbrkfEtOBRJmnaP3LsRRe5T2S3dmCdsWHKORaJYc/OHSKfFlxHPdqw==",
"dev": true,
"requires": {
"strophe.js": "^1.2.12"
}
},
"strophejs-plugin-rsm": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/strophejs-plugin-rsm/-/strophejs-plugin-rsm-0.0.2.tgz",
"integrity": "sha512-Yn/VpxNz3Gkb790rJkwMyjlwHWCjWA9UxIl5kwGnsr7Ofo1MHztgyQ8XwQF1DGFp3Y4oiXbjZ/whG3S/cIgIew==",
"dev": true
},
"twemoji": {
"version": "11.3.0",
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-11.3.0.tgz",
"integrity": "sha512-xN/vlR6+gDmfjt6LInAqwGAv3Agwrmzx5TD1jEFwKS19IOGDrX0/3OB8GP1wUYPVIdkaer5hw6qd+52jzvz0Lg==",
"dev": true
},
"underscore": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
"integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==",
"dev": true
},
"urijs": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.1.tgz",
"integrity": "sha512-xVrGVi94ueCJNrBSTjWqjvtgvl3cyOTThp2zaMaFNGp3F542TR6sM3f2o8RqZl+AwteClSVmoCyt0ka4RjQOQg==",
"dev": true
} }
} }
} }
...@@ -23,9 +23,10 @@ ...@@ -23,9 +23,10 @@
"gitHead": "9641dcdc820e029b05930479c242d2b707bbe8e2", "gitHead": "9641dcdc820e029b05930479c242d2b707bbe8e2",
"devDependencies": { "devDependencies": {
"backbone": "1.4", "backbone": "1.4",
"backbone.browserStorage": "0.0.5", "backbone.browserStorage": "conversejs/backbone.browserStorage#2d0ceaa2f38eedc60122bb0aa23c826dc37a9194",
"filesize": "^4.1.2", "filesize": "^4.1.2",
"jed": "1.1.1", "jed": "1.1.1",
"localforage": "^1.7.3",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"pluggable.js": "2.0.1", "pluggable.js": "2.0.1",
"strophe.js": "1.3.4", "strophe.js": "1.3.4",
......
...@@ -437,11 +437,11 @@ u.onMultipleEvents = function (events=[], callback) { ...@@ -437,11 +437,11 @@ u.onMultipleEvents = function (events=[], callback) {
events.forEach(e => e.object.on(e.event, handler)); events.forEach(e => e.object.on(e.event, handler));
}; };
u.safeSave = function (model, attributes) { u.safeSave = function (model, attributes, options) {
if (u.isPersistableModel(model)) { if (u.isPersistableModel(model)) {
model.save(attributes); model.save(attributes, options);
} else { } else {
model.set(attributes); model.set(attributes, options);
} }
}; };
......
<div class="list-container list-container--openrooms {{{ !o.rooms.length && 'hidden' || '' }}}"> <div class="list-container list-container--openrooms {{{ !o.rooms.length && 'hidden' || '' }}}">
<a href="#" class="list-toggle open-rooms-toggle controlbox-padded" title="{{{o.desc_rooms}}}"> <a href="#" class="list-toggle open-rooms-toggle controlbox-padded" title="{{{o.desc_rooms}}}">
<span class="fa {[ if (o.toggle_state === o._converse.OPENED) { ]} fa-caret-down {[ } else { ]} fa-caret-right {[ } ]}"> <span class="fa {[ if (o.toggle_state === o._converse.OPENED) { ]} fa-caret-down {[ } else { ]} fa-caret-right {[ } ]}"></span>{{{o.label_rooms}}}</a>
</span> {{{o.label_rooms}}}</a>
<div class="items-list rooms-list open-rooms-list {{{ o.collapsed && 'collapsed' }}}"> <div class="items-list rooms-list open-rooms-list {{{ o.collapsed && 'collapsed' }}}">
{[o.rooms.forEach(function (room) { ]} {[o.rooms.forEach(function (room) { ]}
......
...@@ -76,13 +76,37 @@ ...@@ -76,13 +76,37 @@
'Escalus, prince of Verona', 'The Nurse', 'Paris' 'Escalus, prince of Verona', 'The Nurse', 'Paris'
]; ];
mock.pend_names = [ mock.pend_names = [
'Lord Capulet', 'Lady Capulet', 'Servant' 'Lord Capulet', 'Guard', 'Servant'
];
mock.cur_names = [
'Mercutio', 'Juliet Capulet', 'Lady Montague', 'Lord Montague', 'Friar Laurence',
'Tybalt', 'Lady Capulet', 'Benviolo', 'Balthasar',
'Peter', 'Abram', 'Sampson', 'Gregory', 'Potpan', 'Friar John'
]; ];
mock.current_contacts_map = {
'Mercutio': ['Colleagues', 'friends & acquaintences'],
'Juliet Capulet': ['friends & acquaintences'],
'Lady Montague': ['Colleagues', 'Family'],
'Lord Montague': ['Family'],
'Friar Laurence': ['friends & acquaintences'],
'Tybalt': ['friends & acquaintences'],
'Lady Capulet': ['ænemies'],
'Benviolo': ['friends & acquaintences'],
'Balthasar': ['Colleagues'],
'Peter': ['Colleagues'],
'Abram': ['Colleagues'],
'Sampson': ['Colleagues'],
'Gregory': ['friends & acquaintences'],
'Potpan': [],
'Friar John': []
};
const map = mock.current_contacts_map;
const groups_map = {};
Object.keys(map).forEach(k => {
const groups = map[k].length ? map[k] : ["Ungrouped"];
Object.values(groups).forEach(g => {
groups_map[g] = groups_map[g] ? [...groups_map[g], k] : [k]
});
});
mock.groups_map = groups_map;
mock.cur_names = Object.keys(mock.current_contacts_map);
mock.num_contacts = mock.req_names.length + mock.pend_names.length + mock.cur_names.length; mock.num_contacts = mock.req_names.length + mock.pend_names.length + mock.cur_names.length;
mock.groups = { mock.groups = {
...@@ -195,42 +219,39 @@ ...@@ -195,42 +219,39 @@
'no_trimming': true, 'no_trimming': true,
'play_sounds': false, 'play_sounds': false,
'use_emojione': false, 'use_emojione': false,
'view_mode': mock.view_mode, 'view_mode': mock.view_mode
}, settings || {})); }, settings || {}));
_converse.ChatBoxViews.prototype.trimChat = function () {}; _converse.ChatBoxViews.prototype.trimChat = function () {};
_converse.api.vcard.get = function (model, force) { _converse.api.vcard.get = function (model, force) {
return new Promise(resolve => { let jid;
let jid; if (_.isString(model)) {
if (_.isString(model)) { jid = model;
jid = model; } else if (!model.get('vcard_updated') || force) {
} else if (!model.get('vcard_updated') || force) { jid = model.get('jid') || model.get('muc_jid');
jid = model.get('jid') || model.get('muc_jid'); }
} let fullname;
let fullname; if (!jid || jid == 'romeo@montague.lit') {
if (!jid || jid == 'romeo@montague.lit') { jid = 'romeo@montague.lit';
jid = 'romeo@montague.lit'; fullname = 'Romeo Montague' ;
fullname = 'Romeo Montague' ; } else {
} else { const name = jid.split('@')[0].replace(/\./g, ' ').split(' ');
const name = jid.split('@')[0].replace(/\./g, ' ').split(' '); const last = name.length-1;
const last = name.length-1; name[0] = name[0].charAt(0).toUpperCase()+name[0].slice(1);
name[0] = name[0].charAt(0).toUpperCase()+name[0].slice(1); name[last] = name[last].charAt(0).toUpperCase()+name[last].slice(1);
name[last] = name[last].charAt(0).toUpperCase()+name[last].slice(1); fullname = name.join(' ');
fullname = name.join(' '); }
} const vcard = $iq().c('vCard').c('FN').t(fullname).nodeTree;
const vcard = $iq().c('vCard').c('FN').t(fullname).nodeTree; return {
const result = { 'vcard': vcard,
'vcard': vcard, 'fullname': _.get(vcard.querySelector('FN'), 'textContent'),
'fullname': _.get(vcard.querySelector('FN'), 'textContent'), 'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'),
'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'), 'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'),
'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'), 'url': _.get(vcard.querySelector('URL'), 'textContent'),
'url': _.get(vcard.querySelector('URL'), 'textContent'), 'vcard_updated': dayjs().format(),
'vcard_updated': dayjs().format(), 'vcard_error': undefined
'vcard_error': undefined };
};
resolve(result);
}).catch(e => _converse.log(e, Strophe.LogLevel.FATAL));
}; };
if (_.get(settings, 'auto_login') !== false) { if (_.get(settings, 'auto_login') !== false) {
_converse.api.user.login('romeo@montague.lit/orchard', 'secret'); _converse.api.user.login('romeo@montague.lit/orchard', 'secret');
......
...@@ -31,10 +31,10 @@ require.config(config); ...@@ -31,10 +31,10 @@ require.config(config);
var specs = [ var specs = [
"jasmine", "jasmine",
//"spec/transcripts", // "spec/transcripts",
// "spec/profiling",
"spec/spoilers", "spec/spoilers",
"spec/roomslist", "spec/roomslist",
"spec/profiling",
"spec/utils", "spec/utils",
"spec/converse", "spec/converse",
"spec/bookmarks", "spec/bookmarks",
......
...@@ -47,17 +47,14 @@ ...@@ -47,17 +47,14 @@
return req; return req;
}; };
utils.closeAllChatBoxes = function (converse) { utils.closeAllChatBoxes = function (_converse) {
var i, chatbox; return Promise.all(_converse.chatboxviews.map(view => view.close()));
for (i=converse.chatboxes.models.length-1; i>-1; i--) {
chatbox = converse.chatboxes.models[i];
converse.chatboxviews.get(chatbox.get('id')).close();
}
return this;
}; };
utils.openControlBox = function () { utils.openControlBox = async function (_converse) {
const toggle = document.querySelector(".toggle-controlbox"); const model = await _converse.api.chats.open('controlbox');
await u.waitUntil(() => model.get('connected'));
var toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) { if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) { if (!u.isVisible(toggle)) {
u.removeClass('hidden', toggle); u.removeClass('hidden', toggle);
...@@ -68,9 +65,9 @@ ...@@ -68,9 +65,9 @@
}; };
utils.closeControlBox = function () { utils.closeControlBox = function () {
var controlbox = document.querySelector("#controlbox"); const controlbox = document.querySelector("#controlbox");
if (u.isVisible(controlbox)) { if (u.isVisible(controlbox)) {
var button = controlbox.querySelector(".close-chatbox-button"); const button = controlbox.querySelector(".close-chatbox-button");
if (!_.isNull(button)) { if (!_.isNull(button)) {
button.click(); button.click();
} }
...@@ -116,15 +113,19 @@ ...@@ -116,15 +113,19 @@
return views; return views;
}; };
utils.openChatBoxFor = function (_converse, jid) { utils.openChatBoxFor = async function (_converse, jid) {
await _converse.api.waitUntil('rosterContactsFetched');
_converse.roster.get(jid).trigger("open"); _converse.roster.get(jid).trigger("open");
return u.waitUntil(() => _converse.chatboxviews.get(jid), 1000); return u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
}; };
utils.openChatRoomViaModal = async function (_converse, jid, nick='') { utils.openChatRoomViaModal = async function (_converse, jid, nick='') {
// Opens a new chatroom // Opens a new chatroom
utils.openControlBox(_converse); const model = await _converse.api.chats.open('controlbox');
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; await u.waitUntil(() => model.get('connected'));
utils.openControlBox();
const view = await _converse.chatboxviews.get('controlbox');
const roomspanel = view.roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
utils.closeControlBox(_converse); utils.closeControlBox(_converse);
const modal = roomspanel.add_room_modal; const modal = roomspanel.add_room_modal;
...@@ -281,6 +282,7 @@ ...@@ -281,6 +282,7 @@
}); });
_converse.connection._dataRecv(utils.createRequest(owner_list_stanza)); _converse.connection._dataRecv(utils.createRequest(owner_list_stanza));
} }
return new Promise(resolve => _converse.api.listen.on('membersFetched', resolve));
}; };
utils.receiveOwnMUCPresence = function (_converse, muc_jid, nick) { utils.receiveOwnMUCPresence = function (_converse, muc_jid, nick) {
...@@ -296,7 +298,8 @@ ...@@ -296,7 +298,8 @@
}).up() }).up()
.c('status').attrs({code:'110'}); .c('status').attrs({code:'110'});
_converse.connection._dataRecv(utils.createRequest(presence)); _converse.connection._dataRecv(utils.createRequest(presence));
} // return utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.ENTERED));
};
utils.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[], members=[]) { utils.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[], members=[]) {
...@@ -318,19 +321,36 @@ ...@@ -318,19 +321,36 @@
}; };
utils.clearChatBoxMessages = function (converse, jid) { utils.clearChatBoxMessages = function (converse, jid) {
var view = converse.chatboxviews.get(jid); const view = converse.chatboxviews.get(jid);
view.el.querySelector('.chat-content').innerHTML = ''; view.el.querySelector('.chat-content').innerHTML = '';
view.model.messages.reset(); return view.model.messages.clearSession();
view.model.messages.browserStorage._clear(); };
utils.createContact = async function (_converse, name, ask, requesting, subscription) {
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
if (_converse.roster.get(jid)) {
return Promise.resolve();
}
const contact = await new Promise((success, error) => {
_converse.roster.create({
'ask': ask,
'fullname': name,
'jid': jid,
'requesting': requesting,
'subscription': subscription
}, {success, error});
});
return contact;
}; };
utils.createContacts = function (converse, type, length) { utils.createContacts = async function (_converse, type, length) {
/* Create current (as opposed to requesting or pending) contacts /* Create current (as opposed to requesting or pending) contacts
* for the user's roster. * for the user's roster.
* *
* These contacts are not grouped. See below. * These contacts are not grouped. See below.
*/ */
var names, jid, subscription, requesting, ask; await _converse.api.waitUntil('rosterContactsFetched');
let names, subscription, requesting, ask;
if (type === 'requesting') { if (type === 'requesting') {
names = mock.req_names; names = mock.req_names;
subscription = 'none'; subscription = 'none';
...@@ -347,33 +367,18 @@ ...@@ -347,33 +367,18 @@
requesting = false; requesting = false;
ask = null; ask = null;
} else if (type === 'all') { } else if (type === 'all') {
this.createContacts(converse, 'current') await this.createContacts(_converse, 'current');
.createContacts(converse, 'requesting') await this.createContacts(_converse, 'requesting')
.createContacts(converse, 'pending'); await this.createContacts(_converse, 'pending');
return this; return this;
} else { } else {
throw Error("Need to specify the type of contact to create"); throw Error("Need to specify the type of contact to create");
} }
const promises = names.slice(0, length).map(n => this.createContact(_converse, n, ask, requesting, subscription));
if (typeof length === 'undefined') { await Promise.all(promises);
length = names.length;
}
for (var i=0; i<length; i++) {
jid = names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
if (!converse.roster.get(jid)) {
converse.roster.create({
'ask': ask,
'name': names[i],
'jid': jid,
'requesting': requesting,
'subscription': subscription
});
}
}
return this;
}; };
utils.waitForRoster = async function (_converse, type='current', length, include_nick=true) { utils.waitForRoster = async function (_converse, type='current', length=-1, include_nick=true, grouped=true) {
const iq = await u.waitUntil(() => const iq = await u.waitUntil(() =>
_.filter( _.filter(
_converse.connection.IQ_stanzas, _converse.connection.IQ_stanzas,
...@@ -388,44 +393,36 @@ ...@@ -388,44 +393,36 @@
'xmlns': 'jabber:iq:roster' 'xmlns': 'jabber:iq:roster'
}); });
if (type === 'pending' || type === 'all') { if (type === 'pending' || type === 'all') {
mock.pend_names.slice(0, length).map(name => const pend_names = (length > -1) ? mock.pend_names.slice(0, length) : mock.pend_names;
pend_names.map(name =>
result.c('item', { result.c('item', {
jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit', jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
name: include_nick ? name : undefined, name: include_nick ? name : undefined,
subscription: 'to' subscription: 'none',
ask: 'subscribe'
}).up() }).up()
); );
} else if (type === 'current' || type === 'all') { }
mock.cur_names.slice(0, length).map(name => if (type === 'current' || type === 'all') {
const cur_names = Object.keys(mock.current_contacts_map);
const names = (length > -1) ? cur_names.slice(0, length) : cur_names;
names.forEach(name => {
result.c('item', { result.c('item', {
jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit', jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
name: include_nick ? name : undefined, name: include_nick ? name : undefined,
subscription: 'both' subscription: 'both',
}).up() ask: null
); });
if (grouped) {
mock.current_contacts_map[name].forEach(g => result.c('group').t(g).up());
}
result.up();
});
} }
_converse.connection._dataRecv(utils.createRequest(result)); _converse.connection._dataRecv(utils.createRequest(result));
await _converse.api.waitUntil('rosterContactsFetched'); await _converse.api.waitUntil('rosterContactsFetched');
}; };
utils.createGroupedContacts = function (converse) {
/* Create grouped contacts
*/
let i=0, j=0;
_.each(_.keys(mock.groups), function (name) {
j = i;
for (i=j; i<j+mock.groups[name]; i++) {
converse.roster.create({
'jid': mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit',
'subscription': 'both',
'ask': null,
'groups': name === 'ungrouped'? [] : [name],
'name': mock.cur_names[i]
});
}
});
};
utils.createChatMessage = function (_converse, sender_jid, message) { utils.createChatMessage = function (_converse, sender_jid, message) {
return $msg({ return $msg({
from: sender_jid, from: sender_jid,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment