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.
This commit is contained in:
JC Brand 2019-10-24 14:29:15 +02:00
parent 66c052f3fd
commit 1fa203c990
53 changed files with 19732 additions and 19016 deletions

View File

@ -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",

View File

@ -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 @@
### 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 @@
* `_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`.

36825
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -83,7 +83,7 @@
"install": "^0.9.5", "install": "^0.9.5",
"jasmine-core": "2.99.1", "jasmine-core": "2.99.1",
"jsdoc": "^3.6.2", "jsdoc": "^3.6.2",
"lerna": "^3.16.4", "lerna": "^3.18.1",
"lodash-template-webpack-loader": "jcbrand/lodash-template-webpack-loader", "lodash-template-webpack-loader": "jcbrand/lodash-template-webpack-loader",
"mini-css-extract-plugin": "^0.7.0", "mini-css-extract-plugin": "^0.7.0",
"minimist": "^1.2.0", "minimist": "^1.2.0",

View File

@ -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 @@
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 @@
})); }));
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.waitUntilBookmarksReturned(_converse);
const muc_jid = 'theplay@conference.shakespeare.lit';
await test_utils.waitUntilDiscoConfirmed( await _converse.api.rooms.open(muc_jid);
_converse, _converse.bare_jid, const view = _converse.chatboxviews.get(muc_jid);
[{'category': 'pubsub', 'type': 'pep'}], await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
['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';
const view = _converse.chatboxviews.get(jid);
await u.waitUntil(() => !_.isNull(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 @@
'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 @@
// 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 @@
[{'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 @@
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 @@
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 @@
'</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 @@
{ 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

View File

@ -19,7 +19,7 @@
it("has a /help command to show the available commands", mock.initConverse(['rosterGroupsFetched', 'chatBoxesFetched'], {}, async function (done, _converse) { it("has a /help command to show the available commands", mock.initConverse(['rosterGroupsFetched', 'chatBoxesFetched'], {}, async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
test_utils.openControlBox(); await 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);
@ -49,7 +49,7 @@
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']); await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
await u.waitUntil(() => _converse.xmppstatus.vcard.get('fullname')); await u.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
await test_utils.openControlBox(); await test_utils.openControlBox(_converse);
expect(_converse.chatboxes.length).toEqual(1); expect(_converse.chatboxes.length).toEqual(1);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
let message = '/me is tired'; let message = '/me is tired';
@ -95,7 +95,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);
// openControlBox was called earlier, so the controlbox is // openControlBox was called earlier, so the controlbox is
// visible, but no other chat boxes have been created. // visible, but no other chat boxes have been created.
@ -105,7 +105,7 @@
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
const online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat'); const online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat');
expect(online_contacts.length).toBe(15); expect(online_contacts.length).toBe(17);
let el = online_contacts[0]; let el = online_contacts[0];
el.click(); el.click();
await u.waitUntil(() => document.querySelectorAll("#conversejs .chatbox").length == 2); await u.waitUntil(() => document.querySelectorAll("#conversejs .chatbox").length == 2);
@ -124,7 +124,7 @@
['rosterGroupsFetched'], {'allow_non_roster_messaging': true}, ['rosterGroupsFetched'], {'allow_non_roster_messaging': true},
async function (done, _converse) { async function (done, _converse) {
_converse.api.trigger('rosterContactsFetched'); await test_utils.waitForRoster(_converse, 'current', 0);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const stanza = u.toStanza(` const stanza = u.toStanza(`
<message from="${sender_jid}" <message from="${sender_jid}"
@ -171,7 +171,7 @@
spyOn(trimmed_chatboxes, 'removeChat').and.callThrough(); spyOn(trimmed_chatboxes, 'removeChat').and.callThrough();
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
let jid, chatboxview; let jid, chatboxview;
// openControlBox was called earlier, so the controlbox is // openControlBox was called earlier, so the controlbox is
@ -183,7 +183,7 @@
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length);
// Test that they can be maximized again // Test that they can be maximized again
const online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat'); const online_contacts = _converse.rosterview.el.querySelectorAll('.roster-group .current-xmpp-contact a.open-chat');
expect(online_contacts.length).toBe(15); expect(online_contacts.length).toBe(17);
let i; let i;
for (i=0; i<online_contacts.length; i++) { for (i=0; i<online_contacts.length; i++) {
const el = online_contacts[i]; const el = online_contacts[i];
@ -192,11 +192,11 @@
await u.waitUntil(() => _converse.chatboxes.length == 16); await u.waitUntil(() => _converse.chatboxes.length == 16);
expect(_converse.chatboxviews.trimChats.calls.count()).toBe(16); expect(_converse.chatboxviews.trimChats.calls.count()).toBe(16);
_converse.api.chatviews.get().forEach(v => spyOn(v, 'onMinimized').and.callThrough());
for (i=0; i<online_contacts.length; i++) { for (i=0; i<online_contacts.length; i++) {
const el = online_contacts[i]; const el = online_contacts[i];
jid = _.trim(el.textContent.trim()).replace(/ /g,'.').toLowerCase() + '@montague.lit'; jid = _.trim(el.textContent.trim()).replace(/ /g,'.').toLowerCase() + '@montague.lit';
chatboxview = _converse.chatboxviews.get(jid); chatboxview = _converse.chatboxviews.get(jid);
spyOn(chatboxview, 'onMinimized').and.callThrough();
chatboxview.model.set({'minimized': true}); chatboxview.model.set({'minimized': true});
expect(trimmed_chatboxes.addChat).toHaveBeenCalled(); expect(trimmed_chatboxes.addChat).toHaveBeenCalled();
expect(chatboxview.onMinimized).toHaveBeenCalled(); expect(chatboxview.onMinimized).toHaveBeenCalled();
@ -240,7 +240,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);
expect(_converse.chatboxes.length).toEqual(1); expect(_converse.chatboxes.length).toEqual(1);
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@ -267,10 +267,9 @@
spyOn(_converse.ChatBoxViews.prototype, 'trimChats'); spyOn(_converse.ChatBoxViews.prototype, 'trimChats');
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
test_utils.openControlBox();
test_utils.openChatBoxes(_converse, 6); test_utils.openChatBoxes(_converse, 6);
await u.waitUntil(() => _converse.chatboxes.length == 7); await u.waitUntil(() => _converse.chatboxes.length == 7);
@ -282,6 +281,7 @@
// The chatboxes will then be fetched from browserStorage inside the // The chatboxes will then be fetched from browserStorage inside the
// onConnected method // onConnected method
newchatboxes.onConnected(); newchatboxes.onConnected();
await new Promise(resolve => _converse.api.listen.on('chatBoxesFetched', resolve));
expect(newchatboxes.length).toEqual(7); expect(newchatboxes.length).toEqual(7);
// Check that the chatboxes items retrieved from browserStorage // Check that the chatboxes items retrieved from browserStorage
// have the same attributes values as the original ones. // have the same attributes values as the original ones.
@ -302,7 +302,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 contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
@ -337,7 +337,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 contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
@ -377,13 +377,11 @@
spyOn(_converse.ChatBoxViews.prototype, 'trimChats'); spyOn(_converse.ChatBoxViews.prototype, 'trimChats');
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
_converse.chatboxes.browserStorage._clear();
test_utils.closeControlBox(); test_utils.closeControlBox();
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
expect(_converse.chatboxes.length).toEqual(1); expect(_converse.chatboxes.length).toEqual(1);
expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']); expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
@ -392,18 +390,19 @@
expect(_converse.chatboxviews.trimChats).toHaveBeenCalled(); expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
expect(_converse.chatboxes.length).toEqual(7); expect(_converse.chatboxes.length).toEqual(7);
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxInitialized', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxInitialized', jasmine.any(Object));
test_utils.closeAllChatBoxes(_converse); await test_utils.closeAllChatBoxes(_converse);
expect(_converse.chatboxes.length).toEqual(1); expect(_converse.chatboxes.length).toEqual(1);
expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']); expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
var newchatboxes = new _converse.ChatBoxes(); const newchatboxes = new _converse.ChatBoxes();
expect(newchatboxes.length).toEqual(0); expect(newchatboxes.length).toEqual(0);
expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']); expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
// onConnected will fetch chatboxes in browserStorage, but // onConnected will fetch chatboxes in browserStorage, but
// because there aren't any open chatboxes, there won't be any // because there aren't any open chatboxes, there won't be any
// in browserStorage either. XXX except for the controlbox // in browserStorage either. XXX except for the controlbox
newchatboxes.onConnected(); newchatboxes.onConnected();
await new Promise(resolve => _converse.api.listen.on('chatBoxesFetched', resolve));
expect(newchatboxes.length).toEqual(1); expect(newchatboxes.length).toEqual(1);
expect(newchatboxes.models[0].id).toBe("controlbox"); expect(newchatboxes.models[0].id).toBe("controlbox");
done(); done();
@ -417,7 +416,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(); await 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);
const chatbox = _converse.chatboxes.get(contact_jid); const chatbox = _converse.chatboxes.get(contact_jid);
@ -436,7 +435,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(); await 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);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
@ -476,7 +475,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(); await 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);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
@ -492,7 +491,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);
let toolbar, call_button; let toolbar, call_button;
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@ -527,7 +526,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 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);
@ -559,7 +558,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);
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@ -585,7 +584,7 @@
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
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.openControlBox(); await test_utils.openControlBox(_converse);
u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length); u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
spyOn(_converse.connection, 'send'); spyOn(_converse.connection, 'send');
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
@ -606,7 +605,7 @@
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
test_utils.openControlBox(); await 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 u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
@ -640,7 +639,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 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(() => _converse.rosterview.el.querySelectorAll('.roster-group').length); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
@ -679,7 +678,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 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(() => _converse.rosterview.el.querySelectorAll('.roster-group').length); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
@ -703,7 +702,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);
// See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
@ -794,7 +793,7 @@
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
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.openControlBox(); await test_utils.openControlBox(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
_converse.TIMEOUTS.PAUSED = 200; // Make the timeout shorter so that we can test _converse.TIMEOUTS.PAUSED = 200; // Make the timeout shorter so that we can test
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
@ -847,7 +846,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);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
// TODO: only show paused state if the previous state was composing // TODO: only show paused state if the previous state was composing
// See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
@ -926,7 +925,7 @@
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
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.openControlBox(); await test_utils.openControlBox(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 1000); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 1000);
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);
@ -983,7 +982,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 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);
@ -1005,7 +1004,7 @@
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
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.openControlBox(); await test_utils.openControlBox(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
const view = await test_utils.openChatBoxFor(_converse, contact_jid); const view = await test_utils.openChatBoxFor(_converse, contact_jid);
expect(view.model.get('chat_state')).toBe('active'); expect(view.model.get('chat_state')).toBe('active');
@ -1028,7 +1027,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 sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
// See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
@ -1070,7 +1069,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(); await test_utils.openControlBox(_converse);
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@ -1101,7 +1100,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 contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
@ -1110,8 +1109,9 @@
let message = 'This message is another sent from this chatbox'; let message = 'This message is another sent from this chatbox';
await test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
expect(view.model.messages.length > 0).toBeTruthy(); expect(view.model.messages.length === 1).toBeTruthy();
expect(view.model.messages.browserStorage.records.length > 0).toBeTruthy(); let stored_messages = await view.model.messages.browserStorage.findAll();
expect(stored_messages.length).toBe(1);
await u.waitUntil(() => view.el.querySelector('.chat-msg')); await u.waitUntil(() => view.el.querySelector('.chat-msg'));
message = '/clear'; message = '/clear';
@ -1125,10 +1125,12 @@
preventDefault: function preventDefault () {}, preventDefault: function preventDefault () {},
keyCode: 13 keyCode: 13
}); });
expect(view.clearMessages).toHaveBeenCalled(); expect(view.clearMessages.calls.all().length).toBe(1);
await view.clearMessages.calls.all()[0].returnValue;
expect(window.confirm).toHaveBeenCalled(); expect(window.confirm).toHaveBeenCalled();
expect(view.model.messages.length, 0); // The messages must be removed from the chatbox expect(view.model.messages.length, 0); // The messages must be removed from the chatbox
expect(view.model.messages.browserStorage.records.length, 0); // And also from browserStorage stored_messages = await view.model.messages.browserStorage.findAll();
expect(stored_messages.length).toBe(0);
expect(_converse.api.trigger.calls.count(), 1); expect(_converse.api.trigger.calls.count(), 1);
expect(_converse.api.trigger.calls.mostRecent().args, ['messageSend', message]); expect(_converse.api.trigger.calls.mostRecent().args, ['messageSend', message]);
done(); done();
@ -1143,7 +1145,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);
expect(document.title).toBe('Converse Tests'); expect(document.title).toBe('Converse Tests');
@ -1181,7 +1183,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);
_converse.windowState = 'hidden'; _converse.windowState = 'hidden';
spyOn(_converse, 'clearMsgCounter').and.callThrough(); spyOn(_converse, 'clearMsgCounter').and.callThrough();
_converse.saveWindowState(null, 'focus'); _converse.saveWindowState(null, 'focus');
@ -1196,7 +1198,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);
expect(document.title).toBe('Converse Tests'); expect(document.title).toBe('Converse Tests');
spyOn(_converse, 'incrementMsgCounter').and.callThrough(); spyOn(_converse, 'incrementMsgCounter').and.callThrough();
@ -1466,7 +1468,7 @@
['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'], {}, ['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
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 u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500);

View File

@ -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 @@
['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 @@
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 @@
['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 @@
['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 @@
['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 @@
['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 @@
['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 @@
'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 () {},

View File

@ -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 @@
['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';

View File

@ -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);

View File

@ -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 @@
* <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 @@
.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 @@
* </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 @@
_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 @@
})); }));
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 @@
.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();
})); }));
}); });

View File

@ -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 @@
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 @@
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 @@
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 @@
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);

View File

@ -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 @@
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);

View File

@ -61,7 +61,7 @@
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
test_utils.openControlBox(); await 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);
const view = _converse.api.chatviews.get(contact_jid); const view = _converse.api.chatviews.get(contact_jid);
@ -92,7 +92,7 @@
expect(view.model.messages.at(0).get('correcting')).toBe(true); expect(view.model.messages.at(0).get('correcting')).toBe(true);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await new Promise(resolve => view.model.messages.once('rendered', resolve)); await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true); await u.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')));
spyOn(_converse.connection, 'send'); spyOn(_converse.connection, 'send');
textarea.value = 'But soft, what light through yonder window breaks?'; textarea.value = 'But soft, what light through yonder window breaks?';
@ -189,10 +189,10 @@
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
test_utils.openControlBox(); await 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)
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.api.chatviews.get(contact_jid);
const textarea = view.el.querySelector('textarea.chat-textarea'); const textarea = view.el.querySelector('textarea.chat-textarea');
expect(textarea.value).toBe(''); expect(textarea.value).toBe('');
view.onKeyDown({ view.onKeyDown({
@ -348,7 +348,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 sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length) await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length)
@ -514,11 +514,10 @@
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);
/* Ideally we wouldn't have to filter out headline // Ideally we wouldn't have to filter out headline
* messages, but Prosody gives them the wrong 'type' :( // messages, but Prosody gives them the wrong 'type' :(
*/
sinon.spy(_converse, 'log'); sinon.spy(_converse, 'log');
sinon.spy(_converse.chatboxes, 'getChatBox'); sinon.spy(_converse.chatboxes, 'getChatBox');
sinon.spy(u, 'isHeadlineMessage'); sinon.spy(u, 'isHeadlineMessage');
@ -551,7 +550,7 @@
const include_nick = false; const include_nick = false;
await test_utils.waitForRoster(_converse, 'current', 2, include_nick); await test_utils.waitForRoster(_converse, 'current', 2, include_nick);
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
// Send a message from a different resource // Send a message from a different resource
const msgtext = 'This is a carbon message'; const msgtext = 'This is a carbon message';
@ -603,7 +602,7 @@
await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']); await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
// Send a message from a different resource // Send a message from a different resource
const msgtext = 'This is a sent carbon message'; const msgtext = 'This is a sent carbon message';
@ -622,13 +621,15 @@
'to': recipient_jid, 'to': recipient_jid,
'type': 'chat' 'type': 'chat'
}).c('body').t(msgtext).tree(); }).c('body').t(msgtext).tree();
await _converse.chatboxes.onMessage(msg); await _converse.chatboxes.onMessage(msg);
// Check that the chatbox and its view now exist // Check that the chatbox and its view now exist
const chatbox = _converse.chatboxes.get(recipient_jid); const chatbox = await _converse.api.chats.get(recipient_jid);
const view = _converse.chatboxviews.get(recipient_jid); const view = _converse.api.chatviews.get(recipient_jid);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(chatbox).toBeDefined(); expect(chatbox).toBeDefined();
expect(view).toBeDefined(); expect(view).toBeDefined();
await new Promise(resolve => view.once('messageInserted', resolve));
// Check that the message was received and check the message parameters // Check that the message was received and check the message parameters
expect(chatbox.messages.length).toEqual(1); expect(chatbox.messages.length).toEqual(1);
const msg_obj = chatbox.messages.models[0]; const msg_obj = chatbox.messages.models[0];
@ -648,7 +649,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);
/* <message from="mallory@evil.example" to="b@xmpp.example"> /* <message from="mallory@evil.example" to="b@xmpp.example">
* <received xmlns='urn:xmpp:carbons:2'> * <received xmlns='urn:xmpp:carbons:2'>
* <forwarded xmlns='urn:xmpp:forward:0'> * <forwarded xmlns='urn:xmpp:forward:0'>
@ -679,11 +680,11 @@
await _converse.chatboxes.onMessage(msg); await _converse.chatboxes.onMessage(msg);
// Check that chatbox for impersonated user is not created. // Check that chatbox for impersonated user is not created.
let chatbox = _converse.chatboxes.get(impersonated_jid); let chatbox = await _converse.api.chats.get(impersonated_jid);
expect(chatbox).not.toBeDefined(); expect(chatbox).not.toBeDefined();
// Check that the chatbox for the malicous user is not created // Check that the chatbox for the malicous user is not created
chatbox = _converse.chatboxes.get(sender_jid); chatbox = await _converse.api.chats.get(sender_jid);
expect(chatbox).not.toBeDefined(); expect(chatbox).not.toBeDefined();
done(); done();
})); }));
@ -699,7 +700,7 @@
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
const contact_name = mock.cur_names[0]; const contact_name = mock.cur_names[0];
const contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
@ -757,14 +758,14 @@
const include_nick = false; const include_nick = false;
await test_utils.waitForRoster(_converse, 'current', 2, include_nick); await test_utils.waitForRoster(_converse, 'current', 2, include_nick);
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
const contact_name = mock.cur_names[1]; const contact_name = mock.cur_names[1];
const contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = contact_name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
test_utils.clearChatBoxMessages(_converse, contact_jid); await test_utils.clearChatBoxMessages(_converse, contact_jid);
const one_day_ago = dayjs().subtract(1, 'day'); const one_day_ago = dayjs().subtract(1, 'day');
const chatbox = _converse.chatboxes.get(contact_jid); const chatbox = _converse.chatboxes.get(contact_jid);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
@ -849,10 +850,10 @@
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 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)
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.api.chatviews.get(contact_jid);
const message = '<p>This message contains <em>some</em> <b>markup</b></p>'; const message = '<p>This message contains <em>some</em> <b>markup</b></p>';
spyOn(view.model, 'sendMessage').and.callThrough(); spyOn(view.model, 'sendMessage').and.callThrough();
await test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
@ -869,10 +870,10 @@
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 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)
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.api.chatviews.get(contact_jid);
const message = 'This message contains a hyperlink: www.opkode.com'; const message = 'This message contains a hyperlink: www.opkode.com';
spyOn(view.model, 'sendMessage').and.callThrough(); spyOn(view.model, 'sendMessage').and.callThrough();
test_utils.sendMessage(view, message); test_utils.sendMessage(view, message);
@ -891,11 +892,11 @@
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 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)
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.api.chatviews.get(contact_jid);
let message = "http://www.opkode.com/'onmouseover='alert(1)'whatever"; let message = "http://www.opkode.com/'onmouseover='alert(1)'whatever";
await test_utils.sendMessage(view, message); await test_utils.sendMessage(view, message);
@ -990,11 +991,11 @@
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
let base_url = 'https://conversejs.org'; const base_url = 'https://conversejs.org';
let message = base_url+"/logo/conversejs-filled.svg"; let message = base_url+"/logo/conversejs-filled.svg";
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);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.api.chatviews.get(contact_jid);
spyOn(view.model, 'sendMessage').and.callThrough(); spyOn(view.model, 'sendMessage').and.callThrough();
test_utils.sendMessage(view, message); test_utils.sendMessage(view, message);
await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length, 1000) await u.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length, 1000)
@ -1024,7 +1025,6 @@
expect(msg.querySelectorAll('img').length).toEqual(2); expect(msg.querySelectorAll('img').length).toEqual(2);
// Non-https images aren't rendered // Non-https images aren't rendered
base_url = document.URL.split(window.location.pathname)[0];
message = base_url+"/logo/conversejs-filled.svg"; message = base_url+"/logo/conversejs-filled.svg";
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
expect(chat_content.querySelectorAll('img').length).toBe(4); expect(chat_content.querySelectorAll('img').length).toBe(4);
@ -1065,7 +1065,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 base_time = new Date(); const base_time = new Date();
const ONE_MINUTE_LATER = 60000; const ONE_MINUTE_LATER = 60000;
@ -1345,7 +1345,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);
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
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)
@ -1389,7 +1389,7 @@
const include_nick = false; const include_nick = false;
await test_utils.waitForRoster(_converse, 'current', 1, include_nick); await test_utils.waitForRoster(_converse, 'current', 1, include_nick);
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 300);
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
const message = 'This is a received message'; const message = 'This is a received message';
@ -1463,7 +1463,7 @@
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current', 1); await test_utils.waitForRoster(_converse, 'current', 1);
test_utils.openControlBox(); 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';
const msg_id = u.getUniqueId(); const msg_id = u.getUniqueId();
const view = await test_utils.openChatBoxFor(_converse, sender_jid); const view = await test_utils.openChatBoxFor(_converse, sender_jid);
@ -1492,7 +1492,6 @@
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
expect(view.model.messages.models.length).toBe(1); expect(view.model.messages.models.length).toBe(1);
expect(view.model.messages.browserStorage.records.length).toBe(1);
_converse.chatboxes.onMessage($msg({ _converse.chatboxes.onMessage($msg({
'from': sender_jid, 'from': sender_jid,
@ -1515,7 +1514,6 @@
expect(older_msgs[0].childNodes[0].nodeName).toBe('TIME'); expect(older_msgs[0].childNodes[0].nodeName).toBe('TIME');
expect(older_msgs[0].childNodes[1].textContent).toBe(': But soft, what light through yonder airlock breaks?'); expect(older_msgs[0].childNodes[1].textContent).toBe(': But soft, what light through yonder airlock breaks?');
expect(view.model.messages.models.length).toBe(1); expect(view.model.messages.models.length).toBe(1);
expect(view.model.messages.browserStorage.records.length).toBe(1);
done(); done();
})); }));
@ -1524,11 +1522,10 @@
it("the VCard for that user is fetched and the chatbox updated with the results", it("the VCard for that user is fetched and the chatbox updated with the results",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched', 'emojisInitialized'], {'allow_non_roster_messaging': true},
async function (done, _converse) { async function (done, _converse) {
_converse.api.trigger('rosterContactsFetched'); await test_utils.waitForRoster(_converse, 'current', 0);
_converse.allow_non_roster_messaging = true;
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@ -1558,14 +1555,14 @@
expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
// Check that the chatbox and its view now exist // Check that the chatbox and its view now exist
const chatbox = _converse.chatboxes.get(sender_jid); const chatbox = await _converse.api.chats.get(sender_jid);
const view = _converse.chatboxviews.get(sender_jid); const view = _converse.api.chatviews.get(sender_jid);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(chatbox).toBeDefined(); expect(chatbox).toBeDefined();
expect(view).toBeDefined(); expect(view).toBeDefined();
expect(chatbox.get('fullname') === sender_jid); expect(chatbox.get('fullname') === sender_jid);
await new Promise(resolve => view.once('messageInserted', resolve));
await u.waitUntil(() => view.el.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio'); await u.waitUntil(() => view.el.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio');
let author_el = view.el.querySelector('.chat-msg__author'); let author_el = view.el.querySelector('.chat-msg__author');
expect( _.includes(author_el.textContent.trim(), 'Mercutio')).toBeTruthy(); expect( _.includes(author_el.textContent.trim(), 'Mercutio')).toBeTruthy();
@ -1583,11 +1580,10 @@
it("will open a chatbox and be displayed inside it if allow_non_roster_messaging is true", it("will open a chatbox and be displayed inside it if allow_non_roster_messaging is true",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {'allow_non_roster_messaging': false},
async function (done, _converse) { async function (done, _converse) {
_converse.allow_non_roster_messaging = false; await test_utils.waitForRoster(_converse, 'current', 0);
_converse.api.trigger('rosterContactsFetched');
spyOn(_converse.api, "trigger").and.callThrough(); spyOn(_converse.api, "trigger").and.callThrough();
const message = 'This is a received message from someone not on the roster'; const message = 'This is a received message from someone not on the roster';
@ -1607,7 +1603,6 @@
expect(chatbox).not.toBeDefined(); expect(chatbox).not.toBeDefined();
// onMessage is a handler for received XMPP messages // onMessage is a handler for received XMPP messages
await _converse.chatboxes.onMessage(msg); await _converse.chatboxes.onMessage(msg);
expect(_converse.api.chats.get().length).toBe(1);
let view = _converse.chatboxviews.get(sender_jid); let view = _converse.chatboxviews.get(sender_jid);
expect(view).not.toBeDefined(); expect(view).not.toBeDefined();
@ -1618,7 +1613,7 @@
await new Promise(resolve => view.once('messageInserted', resolve)); await new Promise(resolve => view.once('messageInserted', resolve));
expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));
// Check that the chatbox and its view now exist // Check that the chatbox and its view now exist
chatbox = _converse.chatboxes.get(sender_jid); chatbox = await _converse.api.chats.get(sender_jid);
expect(chatbox).toBeDefined(); expect(chatbox).toBeDefined();
expect(view).toBeDefined(); expect(view).toBeDefined();
// Check that the message was received and check the message parameters // Check that the message was received and check the message parameters
@ -1755,6 +1750,7 @@
.c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" }) .c('text', { 'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas" })
.t('Something else went wrong as well'); .t('Something else went wrong as well');
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
await u.waitUntil(() => view.model.messages.length > 3);
await new Promise(resolve => view.once('messageInserted', resolve)); await new Promise(resolve => view.once('messageInserted', resolve));
expect(chat_content.querySelectorAll('.chat-error').length).toEqual(3); expect(chat_content.querySelectorAll('.chat-error').length).toEqual(3);
done(); done();
@ -1768,7 +1764,7 @@
// See #1317 // See #1317
// https://github.com/conversejs/converse.js/issues/1317 // https://github.com/conversejs/converse.js/issues/1317
await test_utils.waitForRoster(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
const contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.openChatBoxFor(_converse, contact_jid);
@ -1887,7 +1883,7 @@
type: 'chat', type: 'chat',
id: '134234623462346' id: '134234623462346'
}).c('body').t(message).up() }).c('body').t(message).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
await _converse.chatboxes.onMessage(msg); await _converse.chatboxes.onMessage(msg);
await u.waitUntil(() => _converse.chatboxviews.keys().length > 1, 1000); await u.waitUntil(() => _converse.chatboxviews.keys().length > 1, 1000);
const view = _converse.chatboxviews.get(sender_jid); const view = _converse.chatboxviews.get(sender_jid);
@ -2111,7 +2107,7 @@
['rosterGroupsFetched', 'emojisInitialized'], {'allow_non_roster_messaging': true}, ['rosterGroupsFetched', 'emojisInitialized'], {'allow_non_roster_messaging': true},
async function (done, _converse) { async function (done, _converse) {
_converse.api.trigger('rosterContactsFetched'); await test_utils.waitForRoster(_converse, 'current', 0);
const contact_jid = 'someone@montague.lit'; const contact_jid = 'someone@montague.lit';
const msgid = u.getUniqueId(); const msgid = u.getUniqueId();
const stanza = u.toStanza(` const stanza = u.toStanza(`

View File

@ -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 @@
['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 @@
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'); 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();
var i, contact_jid, chatview, msg; var i, contact_jid, chatview, msg;

View File

@ -28,12 +28,12 @@
expect(u.isVisible(_converse.chatboxviews.get('leisure@montague.lit').el)).toBeTruthy(); expect(u.isVisible(_converse.chatboxviews.get('leisure@montague.lit').el)).toBeTruthy();
expect(u.isVisible(_converse.chatboxviews.get('news@montague.lit').el)).toBeTruthy(); expect(u.isVisible(_converse.chatboxviews.get('news@montague.lit').el)).toBeTruthy();
_converse.api.roomviews.close('lounge@montague.lit'); await _converse.api.roomviews.close('lounge@montague.lit');
expect(_converse.chatboxviews.get('lounge@montague.lit')).toBeUndefined(); expect(_converse.chatboxviews.get('lounge@montague.lit')).toBeUndefined();
expect(u.isVisible(_converse.chatboxviews.get('leisure@montague.lit').el)).toBeTruthy(); expect(u.isVisible(_converse.chatboxviews.get('leisure@montague.lit').el)).toBeTruthy();
expect(u.isVisible(_converse.chatboxviews.get('news@montague.lit').el)).toBeTruthy(); expect(u.isVisible(_converse.chatboxviews.get('news@montague.lit').el)).toBeTruthy();
_converse.api.roomviews.close(['leisure@montague.lit', 'news@montague.lit']); await _converse.api.roomviews.close(['leisure@montague.lit', 'news@montague.lit']);
expect(_converse.chatboxviews.get('lounge@montague.lit')).toBeUndefined(); expect(_converse.chatboxviews.get('lounge@montague.lit')).toBeUndefined();
expect(_converse.chatboxviews.get('leisure@montague.lit')).toBeUndefined(); expect(_converse.chatboxviews.get('leisure@montague.lit')).toBeUndefined();
expect(_converse.chatboxviews.get('news@montague.lit')).toBeUndefined(); expect(_converse.chatboxviews.get('news@montague.lit')).toBeUndefined();
@ -41,7 +41,7 @@
await test_utils.openAndEnterChatRoom(_converse, 'leisure@montague.lit', 'romeo'); await test_utils.openAndEnterChatRoom(_converse, 'leisure@montague.lit', 'romeo');
expect(u.isVisible(_converse.chatboxviews.get('lounge@montague.lit').el)).toBeTruthy(); expect(u.isVisible(_converse.chatboxviews.get('lounge@montague.lit').el)).toBeTruthy();
expect(u.isVisible(_converse.chatboxviews.get('leisure@montague.lit').el)).toBeTruthy(); expect(u.isVisible(_converse.chatboxviews.get('leisure@montague.lit').el)).toBeTruthy();
_converse.api.roomviews.close(); await _converse.api.roomviews.close();
expect(_converse.chatboxviews.get('lounge@montague.lit')).toBeUndefined(); expect(_converse.chatboxviews.get('lounge@montague.lit')).toBeUndefined();
expect(_converse.chatboxviews.get('leisure@montague.lit')).toBeUndefined(); expect(_converse.chatboxviews.get('leisure@montague.lit')).toBeUndefined();
done(); done();
@ -52,7 +52,7 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group .group-toggle').length, 300); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group .group-toggle').length, 300);
let muc_jid = 'chillout@montague.lit'; let muc_jid = 'chillout@montague.lit';
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
@ -63,7 +63,7 @@
expect(chatroomview.is_chatroom).toBeTruthy(); expect(chatroomview.is_chatroom).toBeTruthy();
expect(u.isVisible(chatroomview.el)).toBeTruthy(); expect(u.isVisible(chatroomview.el)).toBeTruthy();
chatroomview.close(); await chatroomview.close();
// Test with mixed case // Test with mixed case
muc_jid = 'Leisure@montague.lit'; muc_jid = 'Leisure@montague.lit';
@ -105,8 +105,8 @@
let jid = 'lounge@montague.lit'; let jid = 'lounge@montague.lit';
let chatroomview, IQ_id; let chatroomview, IQ_id;
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
test_utils.createContacts(_converse, 'current'); await test_utils.waitForRoster(_converse, 'current');
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group .group-toggle').length); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group .group-toggle').length);
let room = await _converse.api.rooms.open(jid); let room = await _converse.api.rooms.open(jid);
// Test on groupchat that's not yet open // Test on groupchat that's not yet open
@ -121,7 +121,7 @@
chatroomview = _converse.chatboxviews.get(jid); chatroomview = _converse.chatboxviews.get(jid);
expect(chatroomview.is_chatroom).toBeTruthy(); expect(chatroomview.is_chatroom).toBeTruthy();
expect(u.isVisible(chatroomview.el)).toBeTruthy(); expect(u.isVisible(chatroomview.el)).toBeTruthy();
chatroomview.close(); await chatroomview.close();
// Test with mixed case in JID // Test with mixed case in JID
jid = 'Leisure@montague.lit'; jid = 'Leisure@montague.lit';
@ -489,10 +489,9 @@
}).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));
spyOn(view.model, 'clearMessages').and.callThrough(); spyOn(view.model, 'clearMessages').and.callThrough();
view.model.close(); await view.model.close();
await u.waitUntil(() => view.model.clearMessages.calls.count()); await u.waitUntil(() => view.model.clearMessages.calls.count());
expect(view.model.messages.length).toBe(0); expect(view.model.messages.length).toBe(0);
expect(view.content.innerHTML).toBe(''); expect(view.content.innerHTML).toBe('');
@ -504,7 +503,7 @@
['rosterGroupsFetched', 'emojisInitialized'], {}, ['rosterGroupsFetched', 'emojisInitialized'], {},
async function (done, _converse) { async function (done, _converse) {
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.chatboxviews.get('lounge@montague.lit'); const view = _converse.chatboxviews.get('lounge@montague.lit');
if (!view.el.querySelectorAll('.chat-area').length) { if (!view.el.querySelectorAll('.chat-area').length) {
@ -521,7 +520,6 @@
}).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));
view.el.querySelector('.chat-msg__text a').click(); view.el.querySelector('.chat-msg__text a').click();
await u.waitUntil(() => _converse.chatboxes.length === 3) await u.waitUntil(() => _converse.chatboxes.length === 3)
expect(_.includes(_converse.chatboxes.pluck('id'), 'coven@chat.shakespeare.lit')).toBe(true); expect(_.includes(_converse.chatboxes.pluck('id'), 'coven@chat.shakespeare.lit')).toBe(true);
@ -890,6 +888,8 @@
</x> </x>
</presence>`); </presence>`);
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
await u.waitUntil(() => sizzle('div.chat-info', chat_content).length > 3);
expect(sizzle('div.chat-info', chat_content).length).toBe(4); expect(sizzle('div.chat-info', chat_content).length).toBe(4);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent.trim()).toBe("jcbrand has entered the groupchat"); expect(sizzle('div.chat-info:last', chat_content).pop().textContent.trim()).toBe("jcbrand has entered the groupchat");
@ -1106,7 +1106,6 @@
}).c('body').t('Some message').tree(); }).c('body').t('Some message').tree();
await view.model.onMessage(msg); await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
let stanza = u.toStanza( let stanza = u.toStanza(
`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" type="unavailable" from="conversations@conference.siacs.eu/Guus"> `<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" type="unavailable" from="conversations@conference.siacs.eu/Guus">
@ -1307,7 +1306,7 @@
await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']); await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
await u.waitUntil(() => _converse.xmppstatus.vcard.get('fullname')); await u.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
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.chatboxviews.get('lounge@montague.lit'); const view = _converse.chatboxviews.get('lounge@montague.lit');
if (!view.el.querySelectorAll('.chat-area').length) { if (!view.el.querySelectorAll('.chat-area').length) {
@ -1322,7 +1321,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(_.includes(view.el.querySelector('.chat-msg__author').textContent, '**Dyon van de Wege')).toBeTruthy(); expect(_.includes(view.el.querySelector('.chat-msg__author').textContent, '**Dyon van de Wege')).toBeTruthy();
expect(view.el.querySelector('.chat-msg__text').textContent.trim()).toBe('is tired'); expect(view.el.querySelector('.chat-msg__text').textContent.trim()).toBe('is tired');
@ -1334,7 +1332,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(_.includes(sizzle('.chat-msg__author:last', view.el).pop().textContent, '**Romeo Montague')).toBeTruthy(); expect(_.includes(sizzle('.chat-msg__author:last', view.el).pop().textContent, '**Romeo Montague')).toBeTruthy();
expect(sizzle('.chat-msg__text:last', view.el).pop().textContent.trim()).toBe('is as well'); expect(sizzle('.chat-msg__text:last', view.el).pop().textContent.trim()).toBe('is as well');
done(); done();
@ -1931,11 +1928,8 @@
['rosterGroupsFetched', 'chatBoxesFetched'], {'view_mode': 'fullscreen'}, ['rosterGroupsFetched', 'chatBoxesFetched'], {'view_mode': 'fullscreen'},
async function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current'); // We need roster contacts, so that we have someone to invite // We need roster contacts, so that we have someone to invite
// Since we don't actually fetch roster contacts, we need to await test_utils.waitForRoster(_converse, 'current');
// cheat here and emit the event.
_converse.api.trigger('rosterContactsFetched');
const features = [ const features = [
'http://jabber.org/protocol/muc', 'http://jabber.org/protocol/muc',
'jabber:iq:register', 'jabber:iq:register',
@ -1994,14 +1988,16 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current'); // We need roster contacts, who can invite us await test_utils.waitForRoster(_converse, 'current'); // We need roster contacts, who can invite us
const name = mock.cur_names[0];
const from_jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => _converse.roster.get(from_jid).vcard.get('fullname'));
spyOn(window, 'confirm').and.callFake(() => true); spyOn(window, 'confirm').and.callFake(() => true);
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit'); const view = _converse.chatboxviews.get('lounge@montague.lit');
view.close(); // Hack, otherwise we have to mock stanzas. await view.close(); // Hack, otherwise we have to mock stanzas.
const name = mock.cur_names[0];
const from_jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
const muc_jid = 'lounge@montague.lit'; const muc_jid = 'lounge@montague.lit';
const reason = "Please join this groupchat"; const reason = "Please join this groupchat";
@ -2012,7 +2008,8 @@
<message xmlns="jabber:client" to="${_converse.bare_jid}" from="${from_jid}" id="9bceb415-f34b-4fa4-80d5-c0d076a24231"> <message xmlns="jabber:client" to="${_converse.bare_jid}" from="${from_jid}" id="9bceb415-f34b-4fa4-80d5-c0d076a24231">
<x xmlns="jabber:x:conference" jid="${muc_jid}" reason="${reason}"/> <x xmlns="jabber:x:conference" jid="${muc_jid}" reason="${reason}"/>
</message>`); </message>`);
_converse.onDirectMUCInvitation(stanza); await _converse.onDirectMUCInvitation(stanza);
expect(window.confirm).toHaveBeenCalledWith( expect(window.confirm).toHaveBeenCalledWith(
name + ' has invited you to join a groupchat: '+ muc_jid + name + ' has invited you to join a groupchat: '+ muc_jid +
', and left the following reason: "'+reason+'"'); ', and left the following reason: "'+reason+'"');
@ -2047,7 +2044,6 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t(text); }).c('body').t(text);
await view.model.onMessage(message.nodeTree); await view.model.onMessage(message.nodeTree);
await new Promise(resolve => view.once('messageInserted', resolve));
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
expect(chat_content.querySelectorAll('.chat-msg').length).toBe(1); expect(chat_content.querySelectorAll('.chat-msg').length).toBe(1);
expect(chat_content.querySelector('.chat-msg__text').textContent.trim()).toBe(text); expect(chat_content.querySelector('.chat-msg__text').textContent.trim()).toBe(text);
@ -2107,21 +2103,22 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
var message = 'This message is received while the chat area is scrolled up'; const message = 'This message is received while the chat area is scrolled up';
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
var view = _converse.chatboxviews.get('lounge@montague.lit'); const view = _converse.chatboxviews.get('lounge@montague.lit');
spyOn(view, 'scrollDown').and.callThrough(); spyOn(view, 'scrollDown').and.callThrough();
// Create enough messages so that there's a scrollbar. // Create enough messages so that there's a scrollbar.
const promises = []; const promises = [];
for (var i=0; i<20; i++) { for (let i=0; i<20; i++) {
view.model.onMessage( promises.push(
$msg({ view.model.onMessage(
from: 'lounge@montague.lit/someone', $msg({
to: 'romeo@montague.lit.com', from: 'lounge@montague.lit/someone',
type: 'groupchat', to: 'romeo@montague.lit.com',
id: (new Date()).getTime(), type: 'groupchat',
}).c('body').t('Message: '+i).tree()); id: (new Date()).getTime(),
promises.push(new Promise(resolve => view.once('messageInserted', resolve))) }).c('body').t('Message: '+i).tree())
);
} }
await Promise.all(promises); await Promise.all(promises);
// Give enough time for `markScrolled` to have been called // Give enough time for `markScrolled` to have been called
@ -2134,8 +2131,6 @@
type: 'groupchat', type: 'groupchat',
id: (new Date()).getTime(), id: (new Date()).getTime(),
}).c('body').t(message).tree()); }).c('body').t(message).tree());
await new Promise(resolve => view.once('messageInserted', resolve));
// Now check that the message appears inside the chatbox in the DOM // Now check that the message appears inside the chatbox in the DOM
const chat_content = view.el.querySelector('.chat-content'); const chat_content = view.el.querySelector('.chat-content');
const msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent; const msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent;
@ -2765,12 +2760,14 @@
await test_utils.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo'); await test_utils.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
// We instantiate a new ChatBoxes collection, which by default // We instantiate a new ChatBoxes collection, which by default
// will be empty. // will be empty.
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
const newchatboxes = new _converse.ChatBoxes(); const newchatboxes = new _converse.ChatBoxes();
expect(newchatboxes.length).toEqual(0); expect(newchatboxes.length).toEqual(0);
// The chatboxes will then be fetched from browserStorage inside the // The chatboxes will then be fetched from browserStorage inside the
// onConnected method // onConnected method
newchatboxes.onConnected(); newchatboxes.onConnected();
await new Promise(resolve => _converse.api.listen.once('chatBoxesFetched', resolve));
expect(newchatboxes.length).toEqual(2); expect(newchatboxes.length).toEqual(2);
// Check that the chatrooms retrieved from browserStorage // Check that the chatrooms retrieved from browserStorage
// have the same attributes values as the original ones. // have the same attributes values as the original ones.
@ -2834,6 +2831,7 @@
view.el.querySelector('.close-chatbox-button').click(); view.el.querySelector('.close-chatbox-button').click();
expect(view.close).toHaveBeenCalled(); expect(view.close).toHaveBeenCalled();
expect(view.model.leave).toHaveBeenCalled(); expect(view.model.leave).toHaveBeenCalled();
await u.waitUntil(() => _converse.api.trigger.calls.count());
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
done(); done();
})); }));
@ -2952,12 +2950,15 @@
textarea = view.el.querySelector('.chat-textarea'); textarea = view.el.querySelector('.chat-textarea');
textarea.value = '/clear'; textarea.value = '/clear';
view.onKeyDown(enter); view.onKeyDown(enter);
await u.waitUntil(() => sizzle('.chat-info:not(.chat-event)', view.el).length === 0);
textarea.value = '/help'; textarea.value = '/help';
view.onKeyDown(enter); view.onKeyDown(enter);
info_messages = sizzle('.chat-info', view.el).slice(1); info_messages = sizzle('.chat-info:not(.chat-event)', view.el);
expect(info_messages.length).toBe(18); expect(info_messages.length).toBe(19);
let commands = info_messages.map(m => m.textContent.replace(/:.*$/, '')); let commands = info_messages.map(m => m.textContent.replace(/:.*$/, ''));
expect(commands).toEqual([ expect(commands).toEqual([
"You can run the following commands",
"/admin", "/ban", "/clear", "/deop", "/destroy", "/admin", "/ban", "/clear", "/deop", "/destroy",
"/help", "/kick", "/me", "/member", "/modtools", "/mute", "/nick", "/help", "/kick", "/me", "/member", "/modtools", "/mute", "/nick",
"/op", "/register", "/revoke", "/subject", "/topic", "/voice" "/op", "/register", "/revoke", "/subject", "/topic", "/voice"
@ -3004,7 +3005,7 @@
textarea.value = '/help'; textarea.value = '/help';
view.onKeyDown(enter); view.onKeyDown(enter);
const info_messages = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); const info_messages = sizzle('.chat-info:not(.chat-event)', view.el);
expect(info_messages.length).toBe(18); expect(info_messages.length).toBe(18);
expect(info_messages.pop().textContent.trim()).toBe('/topic: Set groupchat subject (alias for /subject)'); expect(info_messages.pop().textContent.trim()).toBe('/topic: Set groupchat subject (alias for /subject)');
expect(info_messages.pop().textContent.trim()).toBe('/subject: Set groupchat subject'); expect(info_messages.pop().textContent.trim()).toBe('/subject: Set groupchat subject');
@ -3854,7 +3855,7 @@
expect(_converse.chatboxes.length).toBe(2); expect(_converse.chatboxes.length).toBe(2);
_converse.connection._dataRecv(test_utils.createRequest(result_stanza)); _converse.connection._dataRecv(test_utils.createRequest(result_stanza));
await u.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED)); await u.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED));
expect(_converse.chatboxes.length).toBe(1); await u.waitUntil(() => _converse.chatboxes.length === 1);
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object)); expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
done(); done();
})); }));
@ -4269,6 +4270,7 @@
['rosterGroupsFetched', 'chatBoxesFetched'], {}, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current', 0);
spyOn(_converse.ChatRoomOccupants.prototype, 'fetchMembers').and.callThrough(); spyOn(_converse.ChatRoomOccupants.prototype, 'fetchMembers').and.callThrough();
const sent_IQs = _converse.connection.IQ_stanzas; const sent_IQs = _converse.connection.IQ_stanzas;
const muc_jid = 'coven@chat.shakespeare.lit'; const muc_jid = 'coven@chat.shakespeare.lit';
@ -4304,7 +4306,7 @@
await u.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING)); await u.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
expect(view.model.features.get('membersonly')).toBeTruthy(); expect(view.model.features.get('membersonly')).toBeTruthy();
test_utils.createContacts(_converse, 'current'); await test_utils.createContacts(_converse, 'current');
let sent_stanza, sent_id; let sent_stanza, sent_id;
spyOn(_converse.connection, 'send').and.callFake(function (stanza) { spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
@ -4480,7 +4482,7 @@
['rosterGroupsFetched', 'chatBoxesFetched'], {}, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current', 0); await test_utils.waitForRoster(_converse, 'current', 0);
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
@ -4522,7 +4524,7 @@
['rosterGroupsFetched', 'chatBoxesFetched'], {'locked_muc_nickname': true, 'muc_nickname_from_jid': true}, ['rosterGroupsFetched', 'chatBoxesFetched'], {'locked_muc_nickname': true, 'muc_nickname_from_jid': true},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current', 0); await test_utils.waitForRoster(_converse, 'current', 0);
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
@ -4545,7 +4547,7 @@
['rosterGroupsFetched', 'chatBoxesFetched'], {'muc_nickname_from_jid': true}, ['rosterGroupsFetched', 'chatBoxesFetched'], {'muc_nickname_from_jid': true},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current', 0); await test_utils.waitForRoster(_converse, 'current', 0);
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
@ -4564,7 +4566,7 @@
['rosterGroupsFetched', 'chatBoxesFetched'], {'nickname': 'st.nick'}, ['rosterGroupsFetched', 'chatBoxesFetched'], {'nickname': 'st.nick'},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current', 0); await test_utils.waitForRoster(_converse, 'current', 0);
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
@ -4583,7 +4585,7 @@
['rosterGroupsFetched', 'chatBoxesFetched'], {'muc_domain': 'muc.example.org'}, ['rosterGroupsFetched', 'chatBoxesFetched'], {'muc_domain': 'muc.example.org'},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
const modal = roomspanel.add_room_modal; const modal = roomspanel.add_room_modal;
@ -4623,7 +4625,7 @@
['rosterGroupsFetched', 'chatBoxesFetched'], {'muc_domain': 'muc.example.org', 'locked_muc_domain': true}, ['rosterGroupsFetched', 'chatBoxesFetched'], {'muc_domain': 'muc.example.org', 'locked_muc_domain': true},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-add-muc-modal').click(); roomspanel.el.querySelector('.show-add-muc-modal').click();
const modal = roomspanel.add_room_modal; const modal = roomspanel.add_room_modal;
@ -4665,7 +4667,7 @@
['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'], {}, ['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-list-muc-modal').click(); roomspanel.el.querySelector('.show-list-muc-modal').click();
test_utils.closeControlBox(_converse); test_utils.closeControlBox(_converse);
@ -4742,7 +4744,7 @@
{'muc_domain': 'muc.example.org'}, {'muc_domain': 'muc.example.org'},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-list-muc-modal').click(); roomspanel.el.querySelector('.show-list-muc-modal').click();
test_utils.closeControlBox(_converse); test_utils.closeControlBox(_converse);
@ -4759,7 +4761,7 @@
{'muc_domain': 'chat.shakespeare.lit', 'locked_muc_domain': true}, {'muc_domain': 'chat.shakespeare.lit', 'locked_muc_domain': true},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
roomspanel.el.querySelector('.show-list-muc-modal').click(); roomspanel.el.querySelector('.show-list-muc-modal').click();
test_utils.closeControlBox(_converse); test_utils.closeControlBox(_converse);
@ -4809,7 +4811,7 @@
['rosterGroupsFetched', 'emojisInitialized'], {'allow_bookmarks': false}, ['rosterGroupsFetched', 'emojisInitialized'], {'allow_bookmarks': false},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(0); expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(0);
@ -4890,7 +4892,7 @@
// Romeo loses his voice // Romeo loses his voice
const presence = $pres({ const presence = $pres({
to: 'romeo@montague.lit/orchard', to: 'romeo@montague.lit/orchard',
from: `${muc_jid}/some1` from: `${muc_jid}/romeo`
}).c('x', {xmlns: Strophe.NS.MUC_USER}) }).c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {'affiliation': 'none', 'role': 'visitor'}).up() .c('item', {'affiliation': 'none', 'role': 'visitor'}).up()
.c('status', {code: '110'}); .c('status', {code: '110'});
@ -5027,7 +5029,6 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t('hello world').tree(); }).c('body').t('hello world').tree();
await view.model.onMessage(msg); await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
const messages = view.el.querySelectorAll('.message'); const messages = view.el.querySelectorAll('.message');
expect(messages.length).toBe(7); expect(messages.length).toBe(7);

View File

@ -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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
'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 @@
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 @@
'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 @@
.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);

View File

@ -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 @@
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 @@
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()
})); }));

View File

@ -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 @@
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 @@
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 @@
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 @@
['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 @@
['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 @@
_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 @@
['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 @@
['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

View File

@ -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 @@
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"

View File

@ -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 @@
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 @@
// _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({

View File

@ -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 @@
*/ */
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 @@
{ 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 @@
* </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 @@
// 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 @@
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 @@
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);

View File

@ -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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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;

View File

@ -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 @@
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 @@
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 @@
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 @@
})); }));
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 @@
})); }));
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 @@
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 @@
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 @@
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 @@
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 @@
}).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();
})); }));

View File

@ -74,11 +74,9 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
spyOn(_converse.api, "trigger").and.callThrough();
const IQs = _converse.connection.IQ_stanzas; const IQs = _converse.connection.IQ_stanzas;
const stanza = await u.waitUntil( const stanza = await u.waitUntil(
() => _.filter(IQs, iq => iq.querySelector('iq query[xmlns="jabber:iq:roster"]')).pop()); () => _.filter(IQs, iq => iq.querySelector('iq query[xmlns="jabber:iq:roster"]')).pop());
expect(_converse.api.trigger.calls.all().map(c => c.args[0]).includes('rosterContactsFetched')).toBeFalsy();
expect(Strophe.serialize(stanza)).toBe( expect(Strophe.serialize(stanza)).toBe(
`<iq id="${stanza.getAttribute('id')}" type="get" xmlns="jabber:client">`+ `<iq id="${stanza.getAttribute('id')}" type="get" xmlns="jabber:client">`+
@ -93,7 +91,7 @@
}).c('item', {'jid': 'nurse@example.com'}).up() }).c('item', {'jid': 'nurse@example.com'}).up()
.c('item', {'jid': 'romeo@example.com'}) .c('item', {'jid': 'romeo@example.com'})
_converse.connection._dataRecv(test_utils.createRequest(result)); _converse.connection._dataRecv(test_utils.createRequest(result));
await u.waitUntil(() => _converse.api.trigger.calls.all().map(c => c.args[0]).includes('rosterContactsFetched')); await u.waitUntil(() => _converse.promises['rosterContactsFetched'].isResolved === true);
done(); done();
})); }));
@ -160,9 +158,9 @@
async function (done, _converse) { async function (done, _converse) {
const filter = _converse.rosterview.el.querySelector('.roster-filter'); const filter = _converse.rosterview.el.querySelector('.roster-filter');
test_utils.openControlBox();
expect(filter === null).toBe(false); expect(filter === null).toBe(false);
test_utils.createContacts(_converse, 'current').openControlBox(); await test_utils.waitForRoster(_converse, 'current');
await test_utils.openControlBox(_converse);
const view = _converse.chatboxviews.get('controlbox'); const view = _converse.chatboxviews.get('controlbox');
const flyout = view.el.querySelector('.box-flyout'); const flyout = view.el.querySelector('.box-flyout');
@ -180,13 +178,13 @@
['rosterGroupsFetched'], {'roster_groups': true}, ['rosterGroupsFetched'], {'roster_groups': true},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
test_utils.createGroupedContacts(_converse); await test_utils.waitForRoster(_converse, 'current');
let filter = _converse.rosterview.el.querySelector('.roster-filter'); let filter = _converse.rosterview.el.querySelector('.roster-filter');
const roster = _converse.rosterview.roster_el; const roster = _converse.rosterview.roster_el;
_converse.rosterview.filter_view.delegateEvents(); _converse.rosterview.filter_view.delegateEvents();
await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 15), 600); await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 17), 600);
expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(5); expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(5);
filter.value = "juliet"; filter.value = "juliet";
u.triggerEvent(filter, "keydown", "KeyboardEvent"); u.triggerEvent(filter, "keydown", "KeyboardEvent");
@ -198,7 +196,7 @@
// Only one foster group is still visible // Only one foster group is still visible
expect(sizzle('.roster-group', roster).filter(u.isVisible).length).toBe(1); expect(sizzle('.roster-group', roster).filter(u.isVisible).length).toBe(1);
const visible_group = sizzle('.roster-group', roster).filter(u.isVisible).pop(); const visible_group = sizzle('.roster-group', roster).filter(u.isVisible).pop();
expect(visible_group.querySelector('a.group-toggle').textContent.trim()).toBe('colleagues'); expect(visible_group.querySelector('a.group-toggle').textContent.trim()).toBe('friends & acquaintences');
filter = _converse.rosterview.el.querySelector('.roster-filter'); filter = _converse.rosterview.el.querySelector('.roster-filter');
filter.value = "j"; filter.value = "j";
@ -210,7 +208,7 @@
let visible_groups = sizzle('.roster-group', roster).filter(u.isVisible).map(el => el.querySelector('a.group-toggle')); let visible_groups = sizzle('.roster-group', roster).filter(u.isVisible).map(el => el.querySelector('a.group-toggle'));
expect(visible_groups.length).toBe(2); expect(visible_groups.length).toBe(2);
expect(visible_groups[0].textContent.trim()).toBe('colleagues'); expect(visible_groups[0].textContent.trim()).toBe('friends & acquaintences');
expect(visible_groups[1].textContent.trim()).toBe('Ungrouped'); expect(visible_groups[1].textContent.trim()).toBe('Ungrouped');
filter = _converse.rosterview.el.querySelector('.roster-filter'); filter = _converse.rosterview.el.querySelector('.roster-filter');
@ -223,7 +221,7 @@
filter = _converse.rosterview.el.querySelector('.roster-filter'); filter = _converse.rosterview.el.querySelector('.roster-filter');
filter.value = ""; filter.value = "";
u.triggerEvent(filter, "keydown", "KeyboardEvent"); u.triggerEvent(filter, "keydown", "KeyboardEvent");
await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 15), 600); await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 17), 600);
expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(5); expect(sizzle('ul.roster-group-contacts', roster).filter(u.isVisible).length).toBe(5);
done(); done();
})); }));
@ -233,26 +231,27 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
test_utils.createGroupedContacts(_converse); await test_utils.waitForRoster(_converse, 'current');
const filter = _converse.rosterview.el.querySelector('.roster-filter'); const filter = _converse.rosterview.el.querySelector('.roster-filter');
const roster = _converse.rosterview.roster_el; const roster = _converse.rosterview.roster_el;
_converse.rosterview.filter_view.delegateEvents(); _converse.rosterview.filter_view.delegateEvents();
await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 15), 600); await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 17), 800);
filter.value = "la"; filter.value = "la";
u.triggerEvent(filter, "keydown", "KeyboardEvent"); u.triggerEvent(filter, "keydown", "KeyboardEvent");
await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 3), 600); await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 4), 800);
// Five roster contact is now visible // Five roster contact is now visible
const visible_contacts = sizzle('li', roster).filter(u.isVisible); const visible_contacts = sizzle('li', roster).filter(u.isVisible);
expect(visible_contacts.length).toBe(3); expect(visible_contacts.length).toBe(4);
let visible_groups = sizzle('.roster-group', roster).filter(u.isVisible).map(el => el.querySelector('a.group-toggle')); let visible_groups = sizzle('.roster-group', roster).filter(u.isVisible).map(el => el.querySelector('a.group-toggle'));
expect(visible_groups.length).toBe(3); expect(visible_groups.length).toBe(4);
expect(visible_groups[0].textContent.trim()).toBe('colleagues'); expect(visible_groups[0].textContent.trim()).toBe('Colleagues');
expect(visible_groups[1].textContent.trim()).toBe('Family'); expect(visible_groups[1].textContent.trim()).toBe('Family');
expect(visible_groups[2].textContent.trim()).toBe('friends & acquaintences'); expect(visible_groups[2].textContent.trim()).toBe('friends & acquaintences');
expect(visible_groups[3].textContent.trim()).toBe('ænemies');
_converse.roster.create({ _converse.roster.create({
jid: 'valentine@montague.lit', jid: 'valentine@montague.lit',
@ -264,10 +263,11 @@
await u.waitUntil(() => sizzle('.roster-group[data-group="newgroup"] li', roster).length, 300); await u.waitUntil(() => sizzle('.roster-group[data-group="newgroup"] li', roster).length, 300);
visible_groups = sizzle('.roster-group', roster).filter(u.isVisible).map(el => el.querySelector('a.group-toggle')); visible_groups = sizzle('.roster-group', roster).filter(u.isVisible).map(el => el.querySelector('a.group-toggle'));
// The "newgroup" group doesn't appear // The "newgroup" group doesn't appear
expect(visible_groups.length).toBe(3); expect(visible_groups.length).toBe(4);
expect(visible_groups[0].textContent.trim()).toBe('colleagues'); expect(visible_groups[0].textContent.trim()).toBe('Colleagues');
expect(visible_groups[1].textContent.trim()).toBe('Family'); expect(visible_groups[1].textContent.trim()).toBe('Family');
expect(visible_groups[2].textContent.trim()).toBe('friends & acquaintences'); expect(visible_groups[2].textContent.trim()).toBe('friends & acquaintences');
expect(visible_groups[3].textContent.trim()).toBe('ænemies');
expect(roster.querySelectorAll('.roster-group').length).toBe(6); expect(roster.querySelectorAll('.roster-group').length).toBe(6);
done(); done();
})); }));
@ -277,15 +277,15 @@
['rosterGroupsFetched'], {'roster_groups': true}, ['rosterGroupsFetched'], {'roster_groups': true},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
test_utils.createGroupedContacts(_converse); await test_utils.waitForRoster(_converse, 'current');
_converse.rosterview.filter_view.delegateEvents(); _converse.rosterview.filter_view.delegateEvents();
var roster = _converse.rosterview.roster_el; var roster = _converse.rosterview.roster_el;
var button = _converse.rosterview.el.querySelector('span[data-type="groups"]'); var button = _converse.rosterview.el.querySelector('span[data-type="groups"]');
button.click(); button.click();
await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 15), 600); await u.waitUntil(() => (sizzle('li', roster).filter(u.isVisible).length === 17), 600);
expect(sizzle('.roster-group', roster).filter(u.isVisible).length).toBe(5); expect(sizzle('.roster-group', roster).filter(u.isVisible).length).toBe(5);
var filter = _converse.rosterview.el.querySelector('.roster-filter'); var filter = _converse.rosterview.el.querySelector('.roster-filter');
@ -293,8 +293,8 @@
u.triggerEvent(filter, "keydown", "KeyboardEvent"); u.triggerEvent(filter, "keydown", "KeyboardEvent");
await u.waitUntil(() => (sizzle('div.roster-group:not(.collapsed)', roster).length === 1), 600); await u.waitUntil(() => (sizzle('div.roster-group:not(.collapsed)', roster).length === 1), 600);
expect(sizzle('div.roster-group:not(.collapsed)', roster).pop().firstElementChild.textContent.trim()).toBe('colleagues'); expect(sizzle('div.roster-group:not(.collapsed)', roster).pop().firstElementChild.textContent.trim()).toBe('Colleagues');
expect(sizzle('div.roster-group:not(.collapsed) li', roster).filter(u.isVisible).length).toBe(3); expect(sizzle('div.roster-group:not(.collapsed) li', roster).filter(u.isVisible).length).toBe(6);
// Check that all contacts under the group are shown // Check that all contacts under the group are shown
expect(sizzle('div.roster-group:not(.collapsed) li', roster).filter(l => !u.isVisible(l)).length).toBe(0); expect(sizzle('div.roster-group:not(.collapsed) li', roster).filter(l => !u.isVisible(l)).length).toBe(0);
@ -310,18 +310,17 @@
u.triggerEvent(filter, "keydown", "KeyboardEvent"); u.triggerEvent(filter, "keydown", "KeyboardEvent");
await u.waitUntil(() => (roster.querySelectorAll('div.roster-group.collapsed').length === 0), 700); await u.waitUntil(() => (roster.querySelectorAll('div.roster-group.collapsed').length === 0), 700);
expect(sizzle('div.roster-group:not(collapsed)', roster).length).toBe(5); expect(sizzle('div.roster-group:not(collapsed)', roster).length).toBe(5);
expect(sizzle('div.roster-group:not(collapsed) li', roster).length).toBe(15); expect(sizzle('div.roster-group:not(collapsed) li', roster).length).toBe(17);
done(); done();
})); }));
it("has a button with which its contents can be cleared", it("has a button with which its contents can be cleared",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {'roster_groups': true},
async function (done, _converse) { async function (done, _converse) {
_converse.roster_groups = true; await test_utils.openControlBox(_converse);
test_utils.openControlBox(); await test_utils.waitForRoster(_converse, 'current');
test_utils.createGroupedContacts(_converse);
const filter = _converse.rosterview.el.querySelector('.roster-filter'); const filter = _converse.rosterview.el.querySelector('.roster-filter');
filter.value = "xxx"; filter.value = "xxx";
@ -343,12 +342,12 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.createGroupedContacts(_converse); test_utils.waitForRoster(_converse, 'all');
let jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@montague.lit'; let jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'online'); _converse.roster.get(jid).presence.set('show', 'online');
jid = mock.cur_names[4].replace(/ /g,'.').toLowerCase() + '@montague.lit'; jid = mock.cur_names[4].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'dnd'); _converse.roster.get(jid).presence.set('show', 'dnd');
test_utils.openControlBox(); test_utils.openControlBox(_converse);
const button = _converse.rosterview.el.querySelector('span[data-type="state"]'); const button = _converse.rosterview.el.querySelector('span[data-type="state"]');
button.click(); button.click();
const roster = _converse.rosterview.roster_el; const roster = _converse.rosterview.roster_el;
@ -375,26 +374,21 @@
it("can be used to organize existing contacts", it("can be used to organize existing contacts",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {'roster_groups': true},
async function (done, _converse) { async function (done, _converse) {
_converse.roster_groups = true;
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
_converse.rosterview.render(); _converse.rosterview.render();
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
test_utils.createContacts(_converse, 'pending'); await test_utils.waitForRoster(_converse, 'all');
test_utils.createContacts(_converse, 'requesting'); await test_utils.createContacts(_converse, 'requesting');
test_utils.createGroupedContacts(_converse);
// Check that the groups appear alphabetically and that // Check that the groups appear alphabetically and that
// requesting and pending contacts are last. // requesting and pending contacts are last.
await u.waitUntil(() => sizzle('.roster-group a.group-toggle', _converse.rosterview.el).length); await u.waitUntil(() => sizzle('.roster-group a.group-toggle', _converse.rosterview.el).length);
const group_titles = _.map( const group_titles = sizzle('.roster-group a.group-toggle', _converse.rosterview.el).map(o => o.textContent.trim());
sizzle('.roster-group a.group-toggle', _converse.rosterview.el),
o => o.textContent.trim()
);
expect(group_titles).toEqual([ expect(group_titles).toEqual([
"Contact requests", "Contact requests",
"colleagues", "Colleagues",
"Family", "Family",
"friends & acquaintences", "friends & acquaintences",
"ænemies", "ænemies",
@ -402,7 +396,7 @@
"Pending contacts" "Pending contacts"
]); ]);
// Check that usernames appear alphabetically per group // Check that usernames appear alphabetically per group
_.each(_.keys(mock.groups), function (name) { Object.keys(mock.groups).forEach(name => {
const contacts = sizzle('.roster-group[data-group="'+name+'"] ul', _converse.rosterview.el); const contacts = sizzle('.roster-group[data-group="'+name+'"] ul', _converse.rosterview.el);
const names = _.map(contacts, o => o.textContent.trim()); const names = _.map(contacts, o => o.textContent.trim());
expect(names).toEqual(_.clone(names).sort()); expect(names).toEqual(_.clone(names).sort());
@ -412,14 +406,14 @@
it("gets created when a contact's \"groups\" attribute changes", it("gets created when a contact's \"groups\" attribute changes",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {'roster_groups': true},
async function (done, _converse) { async function (done, _converse) {
_converse.roster_groups = true;
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
_converse.rosterview.render(); _converse.rosterview.render();
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current', 0);
_converse.roster.create({ _converse.roster.create({
jid: 'groupchanger@montague.lit', jid: 'groupchanger@montague.lit',
@ -460,9 +454,9 @@
['rosterGroupsFetched'], {'roster_groups': true}, ['rosterGroupsFetched'], {'roster_groups': true},
async function (done, _converse) { async function (done, _converse) {
const groups = ['colleagues', 'friends']; const groups = ['Colleagues', 'friends'];
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
test_utils.openControlBox(); test_utils.openControlBox(_converse);
_converse.rosterview.render(); _converse.rosterview.render();
for (var i=0; i<mock.cur_names.length; i++) { for (var i=0; i<mock.cur_names.length; i++) {
_converse.roster.create({ _converse.roster.create({
@ -490,11 +484,11 @@
async function (done, _converse) { async function (done, _converse) {
_converse.roster_groups = true; _converse.roster_groups = true;
test_utils.openControlBox(); test_utils.openControlBox(_converse);
var i=0, j=0; var i=0, j=0;
var groups = { var groups = {
'colleagues': 3, 'Colleagues': 3,
'friends & acquaintences': 3, 'friends & acquaintences': 3,
'Ungrouped': 2 'Ungrouped': 2
}; };
@ -510,7 +504,7 @@
}); });
} }
}); });
const view = _converse.rosterview.get('colleagues'); const view = _converse.rosterview.get('Colleagues');
const toggle = view.el.querySelector('a.group-toggle'); const toggle = view.el.querySelector('a.group-toggle');
expect(view.model.get('state')).toBe('opened'); expect(view.model.get('state')).toBe('opened');
toggle.click(); toggle.click();
@ -523,18 +517,14 @@
describe("Pending Contacts", function () { describe("Pending Contacts", function () {
async function _addContacts (_converse) {
// Must be initialized, so that render is called and documentFragment set up.
test_utils.createContacts(_converse, 'pending').openControlBox()
await Promise.all(_converse.roster.forEach(contact => u.waitUntil(() => contact.vcard.get('fullname'))));
}
it("can be collapsed under their own header", it("can be collapsed under their own header",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
await _addContacts(_converse); await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'all');
await Promise.all(_converse.roster.forEach(contact => u.waitUntil(() => contact.vcard.get('fullname'))));
await u.waitUntil(() => sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).map(e => e.querySelector('li')).length, 1000); await u.waitUntil(() => sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).map(e => e.querySelector('li')).length, 1000);
await checkHeaderToggling.apply( await checkHeaderToggling.apply(
_converse, _converse,
@ -546,10 +536,10 @@
it("can be added to the roster", it("can be added to the roster",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
_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',
subscription: 'none', subscription: 'none',
@ -560,30 +550,15 @@
done(); done();
})); }));
it("are shown in the roster when show_only_online_users",
mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
_converse.show_only_online_users = true;
test_utils.openControlBox();
spyOn(_converse.rosterview, 'update').and.callThrough();
_addContacts(_converse);
await u.waitUntil(() => sizzle('li', _converse.rosterview.el).filter(u.isVisible).length, 500)
expect(u.isVisible(_converse.rosterview.el)).toEqual(true);
expect(_converse.rosterview.update).toHaveBeenCalled();
expect(_converse.rosterview.el.querySelectorAll('li').length).toBe(3);
expect(_.filter(_converse.rosterview.el.querySelectorAll('ul.roster-group-contacts'), u.isVisible).length).toBe(1);
done();
}));
it("are shown in the roster when hide_offline_users", it("are shown in the roster when hide_offline_users",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {'hide_offline_users': true}, ['rosterGroupsFetched'], {'hide_offline_users': true},
async function (done, _converse) { async function (done, _converse) {
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
_addContacts(_converse); await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'pending');
await Promise.all(_converse.roster.forEach(contact => u.waitUntil(() => contact.vcard.get('fullname'))));
await u.waitUntil(() => sizzle('li', _converse.rosterview.el).filter(u.isVisible).length, 500) await u.waitUntil(() => sizzle('li', _converse.rosterview.el).filter(u.isVisible).length, 500)
expect(_converse.rosterview.update).toHaveBeenCalled(); expect(_converse.rosterview.update).toHaveBeenCalled();
expect(u.isVisible(_converse.rosterview.el)).toBe(true); expect(u.isVisible(_converse.rosterview.el)).toBe(true);
@ -597,7 +572,8 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
_addContacts(_converse); await test_utils.waitForRoster(_converse, 'all');
await Promise.all(_converse.roster.forEach(contact => u.waitUntil(() => contact.vcard.get('fullname'))));
const name = mock.pend_names[0]; const name = mock.pend_names[0];
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit'; const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
const contact = _converse.roster.get(jid); const contact = _converse.roster.get(jid);
@ -628,7 +604,8 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current', 0);
const name = mock.pend_names[0]; const name = mock.pend_names[0];
_converse.roster.create({ _converse.roster.create({
jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit', jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
@ -666,7 +643,8 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
_addContacts(_converse); await test_utils.waitForRoster(_converse, 'all');
await Promise.all(_converse.roster.forEach(contact => u.waitUntil(() => contact.vcard.get('fullname'))));
await u.waitUntil(() => _converse.roster.at(0).vcard.get('fullname')) await u.waitUntil(() => _converse.roster.at(0).vcard.get('fullname'))
spyOn(window, 'confirm').and.returnValue(true); spyOn(window, 'confirm').and.returnValue(true);
for (var i=0; i<mock.pend_names.length; i++) { for (var i=0; i<mock.pend_names.length; i++) {
@ -682,9 +660,11 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
let i; await test_utils.openControlBox(_converse);
test_utils.openControlBox(); await test_utils.waitForRoster(_converse, 'current');
await Promise.all(_converse.roster.forEach(contact => u.waitUntil(() => contact.vcard.get('fullname'))));
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
let i;
for (i=0; i<mock.pend_names.length; i++) { for (i=0; i<mock.pend_names.length; i++) {
_converse.roster.create({ _converse.roster.create({
jid: mock.pend_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit', jid: mock.pend_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit',
@ -694,7 +674,7 @@
}); });
expect(_converse.rosterview.update).toHaveBeenCalled(); expect(_converse.rosterview.update).toHaveBeenCalled();
} }
await u.waitUntil(() => sizzle('li', _converse.rosterview.get('Pending contacts').el).filter(u.isVisible).length, 700); await u.waitUntil(() => sizzle('li', _converse.rosterview.get('Pending contacts').el).filter(u.isVisible).length, 900);
// Check that they are sorted alphabetically // Check that they are sorted alphabetically
const view = _converse.rosterview.get('Pending contacts'); const view = _converse.rosterview.get('Pending contacts');
const spans = view.el.querySelectorAll('.pending-xmpp-contact span'); const spans = view.el.querySelectorAll('.pending-xmpp-contact span');
@ -706,7 +686,8 @@
describe("Existing Contacts", function () { describe("Existing Contacts", function () {
async function _addContacts (_converse) { async function _addContacts (_converse) {
test_utils.createContacts(_converse, 'current').openControlBox() await test_utils.waitForRoster(_converse, 'current');
await test_utils.openControlBox(_converse);
await Promise.all(_converse.roster.forEach(contact => u.waitUntil(() => contact.vcard.get('fullname')))); await Promise.all(_converse.roster.forEach(contact => u.waitUntil(() => contact.vcard.get('fullname'))));
} }
@ -749,7 +730,7 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
await Promise.all(mock.cur_names.map(name => { await Promise.all(mock.cur_names.map(name => {
const contact = _converse.roster.create({ const contact = _converse.roster.create({
@ -802,10 +783,10 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
var name = mock.cur_names[0]; await test_utils.waitForRoster(_converse, 'current', 0);
var contact; const name = mock.cur_names[0];
contact = _converse.roster.create({ const contact = _converse.roster.create({
jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit', jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
subscription: 'both', subscription: 'both',
ask: null, ask: null,
@ -833,17 +814,21 @@
await _addContacts(_converse); await _addContacts(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700); await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length, 700);
let jid, t;
spyOn(_converse.rosterview, 'update').and.callThrough();
const roster = _converse.rosterview.el; const roster = _converse.rosterview.el;
const groups = roster.querySelectorAll('.roster-group');
const groupnames = Array.from(groups).map(g => g.getAttribute('data-group'));
expect(groupnames.join(' ')).toBe("Colleagues Family friends & acquaintences ænemies Ungrouped");
for (let i=0; i<mock.cur_names.length; i++) { for (let i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'online'); _converse.roster.get(jid).presence.set('show', 'online');
expect(_converse.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically // Check that they are sorted alphabetically
const chat_els = roster.querySelectorAll('.roster-group .current-xmpp-contact.online a.open-chat'); for (let j=0; j<groups.length; j++) {
t = _.reduce(chat_els, (result, value) => result + _.trim(value.textContent), ''); const group = groups[j];
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join('')); const groupname = groupnames[j];
const els = group.querySelectorAll('.current-xmpp-contact.online a.open-chat');
const t = _.reduce(els, (result, value) => result + _.trim(value.textContent), '');
expect(t).toEqual(mock.groups_map[groupname].slice(0, els.length).sort().join(''));
}
} }
done(); done();
})); }));
@ -855,17 +840,21 @@
await _addContacts(_converse); await _addContacts(_converse);
await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700); await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
let jid, t;
spyOn(_converse.rosterview, 'update').and.callThrough();
const roster = _converse.rosterview.el; const roster = _converse.rosterview.el;
const groups = roster.querySelectorAll('.roster-group');
const groupnames = Array.from(groups).map(g => g.getAttribute('data-group'));
expect(groupnames.join(' ')).toBe("Colleagues Family friends & acquaintences ænemies Ungrouped");
for (let i=0; i<mock.cur_names.length; i++) { for (let i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'dnd'); _converse.roster.get(jid).presence.set('show', 'dnd');
expect(_converse.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically // Check that they are sorted alphabetically
const chat_els = roster.querySelectorAll('.roster-group .current-xmpp-contact.dnd a.open-chat'); for (let j=0; j<groups.length; j++) {
t = _.reduce(chat_els, (result, value) => result + _.trim(value.textContent), ''); const group = groups[j];
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join('')); const groupname = groupnames[j];
const els = group.querySelectorAll('.current-xmpp-contact.dnd a.open-chat');
const t = _.reduce(els, (result, value) => result + _.trim(value.textContent), '');
expect(t).toEqual(mock.groups_map[groupname].slice(0, els.length).sort().join(''));
}
} }
done(); done();
})); }));
@ -877,17 +866,21 @@
await _addContacts(_converse); await _addContacts(_converse);
await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700); await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
let jid, t;
spyOn(_converse.rosterview, 'update').and.callThrough();
const roster = _converse.rosterview.el; const roster = _converse.rosterview.el;
const groups = roster.querySelectorAll('.roster-group');
const groupnames = Array.from(groups).map(g => g.getAttribute('data-group'));
expect(groupnames.join(' ')).toBe("Colleagues Family friends & acquaintences ænemies Ungrouped");
for (let i=0; i<mock.cur_names.length; i++) { for (let i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'away'); _converse.roster.get(jid).presence.set('show', 'away');
expect(_converse.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically // Check that they are sorted alphabetically
const chat_els = roster.querySelectorAll('.roster-group .current-xmpp-contact.away a.open-chat'); for (let j=0; j<groups.length; j++) {
t = _.reduce(chat_els, (result, value) => result + _.trim(value.textContent), ''); const group = groups[j];
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join('')); const groupname = groupnames[j];
const els = group.querySelectorAll('.current-xmpp-contact.away a.open-chat');
const t = _.reduce(els, (result, value) => result + _.trim(value.textContent), '');
expect(t).toEqual(mock.groups_map[groupname].slice(0, els.length).sort().join(''));
}
} }
done(); done();
})); }));
@ -899,19 +892,21 @@
await _addContacts(_converse); await _addContacts(_converse);
await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700); await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
var jid, t;
spyOn(_converse.rosterview, 'update').and.callThrough();
const roster = _converse.rosterview.el; const roster = _converse.rosterview.el;
for (var i=0; i<mock.cur_names.length; i++) { const groups = roster.querySelectorAll('.roster-group');
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const groupnames = Array.from(groups).map(g => g.getAttribute('data-group'));
expect(groupnames.join(' ')).toBe("Colleagues Family friends & acquaintences ænemies Ungrouped");
for (let i=0; i<mock.cur_names.length; i++) {
const jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'xa'); _converse.roster.get(jid).presence.set('show', 'xa');
expect(_converse.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically // Check that they are sorted alphabetically
t = _.reduce(roster.querySelectorAll('.roster-group .current-xmpp-contact.xa a.open-chat'), for (let j=0; j<groups.length; j++) {
function (result, value) { const group = groups[j];
return result + _.trim(value.textContent); const groupname = groupnames[j];
}, ''); const els = group.querySelectorAll('.current-xmpp-contact.xa a.open-chat');
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join('')); const t = _.reduce(els, (result, value) => result + _.trim(value.textContent), '');
expect(t).toEqual(mock.groups_map[groupname].slice(0, els.length).sort().join(''));
}
} }
done(); done();
})); }));
@ -923,19 +918,21 @@
await _addContacts(_converse); await _addContacts(_converse);
await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 500) await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 500)
var jid, t; const roster = _converse.rosterview.el;
spyOn(_converse.rosterview, 'update').and.callThrough(); const groups = roster.querySelectorAll('.roster-group');
var roster = _converse.rosterview.el; const groupnames = Array.from(groups).map(g => g.getAttribute('data-group'));
for (var i=0; i<mock.cur_names.length; i++) { expect(groupnames.join(' ')).toBe("Colleagues Family friends & acquaintences ænemies Ungrouped");
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit'; for (let i=0; i<mock.cur_names.length; i++) {
const jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'unavailable'); _converse.roster.get(jid).presence.set('show', 'unavailable');
expect(_converse.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically // Check that they are sorted alphabetically
t = _.reduce(roster.querySelectorAll('.roster-group .current-xmpp-contact.unavailable a.open-chat'), for (let j=0; j<groups.length; j++) {
function (result, value) { const group = groups[j];
return result + _.trim(value.textContent); const groupname = groupnames[j];
}, ''); const els = group.querySelectorAll('.current-xmpp-contact.unavailable a.open-chat');
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join('')); const t = _.reduce(els, (result, value) => result + _.trim(value.textContent), '');
expect(t).toEqual(mock.groups_map[groupname].slice(0, els.length).sort().join(''));
}
} }
done(); done();
})); }));
@ -968,62 +965,54 @@
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit'; jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'unavailable'); _converse.roster.get(jid).presence.set('show', 'unavailable');
} }
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('li.online').length)
await u.waitUntil(() => _converse.rosterview.el.querySelector('li:first-child').textContent.trim() === 'Juliet Capulet', 900); await u.waitUntil(() => u.isVisible(_converse.rosterview.el.querySelector('li:first-child')), 900);
const contacts = _converse.rosterview.el.querySelectorAll('.current-xmpp-contact'); const roster = _converse.rosterview.el;
for (i=0; i<3; i++) { const groups = roster.querySelectorAll('.roster-group');
expect(u.hasClass('online', contacts[i])).toBe(true); const groupnames = Array.from(groups).map(g => g.getAttribute('data-group'));
expect(u.hasClass('both', contacts[i])).toBe(true); expect(groupnames.join(' ')).toBe("Colleagues Family friends & acquaintences ænemies Ungrouped");
expect(u.hasClass('dnd', contacts[i])).toBe(false); for (let j=0; j<groups.length; j++) {
expect(u.hasClass('away', contacts[i])).toBe(false); const group = groups[j];
expect(u.hasClass('xa', contacts[i])).toBe(false); const groupname = groupnames[j];
expect(u.hasClass('unavailable', contacts[i])).toBe(false); const els = Array.from(group.querySelectorAll('.current-xmpp-contact'));
expect(u.hasClass('offline', contacts[i])).toBe(false); expect(els.length).toBe(mock.groups_map[groupname].length);
}
for (i=3; i<6; i++) { if (groupname === "Colleagues") {
expect(u.hasClass('dnd', contacts[i])).toBe(true); const statuses = els.map(e => e.getAttribute('data-status'));
expect(u.hasClass('both', contacts[i])).toBe(true); const subscription_classes = els.map(e => e.classList[3]);
expect(u.hasClass('online', contacts[i])).toBe(false); const status_classes = els.map(e => e.classList[4]);
expect(u.hasClass('away', contacts[i])).toBe(false); expect(statuses.join(" ")).toBe("online online away xa xa xa");
expect(u.hasClass('xa', contacts[i])).toBe(false); expect(status_classes.join(" ")).toBe("online online away xa xa xa");
expect(u.hasClass('unavailable', contacts[i])).toBe(false); expect(subscription_classes.join(" ")).toBe("both both both both both both");
expect(u.hasClass('offline', contacts[i])).toBe(false); } else if (groupname === "friends & acquaintences") {
} const statuses = els.map(e => e.getAttribute('data-status'));
for (i=6; i<9; i++) { const subscription_classes = els.map(e => e.classList[3]);
expect(u.hasClass('away', contacts[i])).toBe(true); const status_classes = els.map(e => e.classList[4]);
expect(u.hasClass('both', contacts[i])).toBe(true); expect(statuses.join(" ")).toBe("online online dnd dnd away unavailable");
expect(u.hasClass('online', contacts[i])).toBe(false); expect(status_classes.join(" ")).toBe("online online dnd dnd away unavailable");
expect(u.hasClass('dnd', contacts[i])).toBe(false); expect(subscription_classes.join(" ")).toBe("both both both both both both");
expect(u.hasClass('xa', contacts[i])).toBe(false); } else if (groupname === "Family") {
expect(u.hasClass('unavailable', contacts[i])).toBe(false); const statuses = els.map(e => e.getAttribute('data-status'));
expect(u.hasClass('offline', contacts[i])).toBe(false); const subscription_classes = els.map(e => e.classList[3]);
} const status_classes = els.map(e => e.classList[4]);
for (i=9; i<12; i++) { expect(statuses.join(" ")).toBe("online dnd");
expect(u.hasClass('xa', contacts[i])).toBe(true); expect(status_classes.join(" ")).toBe("online dnd");
expect(u.hasClass('both', contacts[i])).toBe(true); expect(subscription_classes.join(" ")).toBe("both both");
expect(u.hasClass('online', contacts[i])).toBe(false); } else if (groupname === "ænemies") {
expect(u.hasClass('dnd', contacts[i])).toBe(false); const statuses = els.map(e => e.getAttribute('data-status'));
expect(u.hasClass('away', contacts[i])).toBe(false); const subscription_classes = els.map(e => e.classList[3]);
expect(u.hasClass('unavailable', contacts[i])).toBe(false); const status_classes = els.map(e => e.classList[4]);
expect(u.hasClass('offline', contacts[i])).toBe(false); expect(statuses.join(" ")).toBe("away");
} expect(status_classes.join(" ")).toBe("away");
for (i=12; i<15; i++) { expect(subscription_classes.join(" ")).toBe("both");
expect(u.hasClass('unavailable', contacts[i])).toBe(true); } else if (groupname === "Ungrouped") {
expect(u.hasClass('both', contacts[i])).toBe(true); const statuses = els.map(e => e.getAttribute('data-status'));
expect(u.hasClass('online', contacts[i])).toBe(false); const subscription_classes = els.map(e => e.classList[3]);
expect(u.hasClass('dnd', contacts[i])).toBe(false); const status_classes = els.map(e => e.classList[4]);
expect(u.hasClass('away', contacts[i])).toBe(false); expect(statuses.join(" ")).toBe("unavailable unavailable");
expect(u.hasClass('xa', contacts[i])).toBe(false); expect(status_classes.join(" ")).toBe("unavailable unavailable");
expect(u.hasClass('offline', contacts[i])).toBe(false); expect(subscription_classes.join(" ")).toBe("both both");
} }
for (i=15; i<mock.cur_names.length; i++) {
expect(u.hasClass('offline', contacts[i])).toBe(true);
expect(u.hasClass('both', contacts[i])).toBe(true);
expect(u.hasClass('online', contacts[i])).toBe(false);
expect(u.hasClass('dnd', contacts[i])).toBe(false);
expect(u.hasClass('away', contacts[i])).toBe(false);
expect(u.hasClass('xa', contacts[i])).toBe(false);
expect(u.hasClass('unavailable', contacts[i])).toBe(false);
} }
done(); done();
})); }));
@ -1036,7 +1025,7 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); test_utils.openControlBox(_converse);
let names = []; let names = [];
const addName = function (item) { const addName = function (item) {
if (!u.hasClass('request-actions', item)) { if (!u.hasClass('request-actions', item)) {
@ -1069,7 +1058,8 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, "current", 0);
const name = mock.req_names[0]; const name = mock.req_names[0];
spyOn(window, 'confirm').and.returnValue(true); spyOn(window, 'confirm').and.returnValue(true);
_converse.roster.create({ _converse.roster.create({
@ -1093,7 +1083,9 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'requesting').openControlBox(); await test_utils.waitForRoster(_converse, 'current', 0);
test_utils.createContacts(_converse, 'requesting');
await test_utils.openControlBox(_converse);
await u.waitUntil(() => sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).length, 700); await u.waitUntil(() => sizzle('.roster-group', _converse.rosterview.el).filter(u.isVisible).length, 700);
await checkHeaderToggling.apply( await checkHeaderToggling.apply(
_converse, _converse,
@ -1107,8 +1099,9 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); await test_utils.openControlBox(_converse);
test_utils.createContacts(_converse, 'requesting').openControlBox(); await test_utils.waitForRoster(_converse, 'current', 0);
await test_utils.createContacts(_converse, 'requesting');
const name = mock.req_names.sort()[0]; const name = mock.req_names.sort()[0];
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit'; const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
const contact = _converse.roster.get(jid); const contact = _converse.roster.get(jid);
@ -1131,7 +1124,9 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'requesting').openControlBox(); await test_utils.waitForRoster(_converse, 'current', 0);
await test_utils.createContacts(_converse, 'requesting');
await test_utils.openControlBox(_converse);
await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700); await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
_converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced. _converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced.
const name = mock.req_names.sort()[1]; const name = mock.req_names.sort()[1];
@ -1206,16 +1201,18 @@
it("are saved to, and can be retrieved from browserStorage", it("are saved to, and can be retrieved from browserStorage",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'all').openControlBox(); await test_utils.waitForRoster(_converse, 'current', 0);
await test_utils.createContacts(_converse, 'requesting');
await test_utils.openControlBox(_converse);
var new_attrs, old_attrs, attrs; var new_attrs, old_attrs, attrs;
var num_contacts = _converse.roster.length; var num_contacts = _converse.roster.length;
var new_roster = new _converse.RosterContacts(); var new_roster = new _converse.RosterContacts();
// Roster items are yet to be fetched from browserStorage // Roster items are yet to be fetched from browserStorage
expect(new_roster.length).toEqual(0); expect(new_roster.length).toEqual(0);
new_roster.browserStorage = _converse.roster.browserStorage; new_roster.browserStorage = _converse.roster.browserStorage;
new_roster.fetch(); await new Promise(success => new_roster.fetch({success}));
expect(new_roster.length).toEqual(num_contacts); expect(new_roster.length).toEqual(num_contacts);
// Check that the roster items retrieved from browserStorage // Check that the roster items retrieved from browserStorage
// have the same attributes values as the original ones. // have the same attributes values as the original ones.
@ -1236,7 +1233,9 @@
['rosterGroupsFetched'], {}, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'all').openControlBox(); await test_utils.waitForRoster(_converse, 'current', 'all');
await test_utils.createContacts(_converse, 'requesting');
await test_utils.openControlBox(_converse);
await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700); await u.waitUntil(() => sizzle('.roster-group li', _converse.rosterview.el).length, 700);
await Promise.all(mock.cur_names.map(async name => { await Promise.all(mock.cur_names.map(async name => {
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit'; const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
@ -1246,6 +1245,13 @@
expect(child.getAttribute('title')).toContain(name); expect(child.getAttribute('title')).toContain(name);
expect(child.getAttribute('title')).toContain(jid); expect(child.getAttribute('title')).toContain(jid);
})); }));
await Promise.all(mock.req_names.map(async name => {
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
const el = await u.waitUntil(() => sizzle("li:contains('"+name+"')", _converse.rosterview.el).pop());
const child = el.firstElementChild;
expect(child.textContent.trim()).toBe(name);
expect(child.firstElementChild.getAttribute('title')).toContain(jid);
}));
done(); done();
})); }));
}); });

View File

@ -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 @@
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 @@
// 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 @@
// 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 @@
}, },
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 @@
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"/>');

View File

@ -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 @@
'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 @@
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 @@
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 @@
_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');

View File

@ -1,14 +1,13 @@
(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 = [

View File

@ -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 @@
['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 @@
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();

View File

@ -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', {
} }
}, },
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', {
} }
}, },
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', {
} }
}, },
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', {
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', {
}, },
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', {
* @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', {
* // 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);

View File

@ -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', {
) )
}, },
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', {
_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 () {

View File

@ -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')),

View File

@ -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', {
* @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', {
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', {
* *
* @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', {
} 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();
}
});
} }
} }
}); });

View File

@ -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', {
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', {
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', {
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', {
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();

View File

@ -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();

View File

@ -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
}
);
});
} }
} }
}); });

View File

@ -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', {
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', {
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;
} }
}); });

View File

@ -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;
} }
}); });

View File

@ -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");

View File

@ -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', {
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', {
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', {
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', {
} }
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', {
* }); * });
*/ */
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', {
}, },
/** /**
* 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']

View File

@ -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";
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 () {
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 () {
} }
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) {
} }
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 () {
_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
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) {
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 () {
async function finishInitialization () { async function finishInitialization () {
await initStorage();
initClientConfig(); initClientConfig();
initPlugins(); initPlugins();
_converse.registerGlobalEventHandlers(); _converse.registerGlobalEventHandlers();
@ -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 () {
_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 = {
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 = {
complete(); complete();
} }
return promise; return promise;
}, }
}, },
/** /**

View File

@ -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', {
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', {
* @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', {
/******************** 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;
} }
}); });

View File

@ -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
} }

View File

@ -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();
} }
}); });

View File

@ -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', {
* 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', {
* @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', {
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', {
} }
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', {
} }
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', {
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', {
}, },
/** /**
* 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', {
!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', {
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', {
} }
} }
} }
this.save({'connection_status': converse.ROOMSTATUS.ENTERED});
}, },
/** /**
@ -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', {
*/ */
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', {
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).

View File

@ -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', {
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', {
* @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.
* *

View File

@ -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;
} }
}); });

View File

@ -1,9 +1,23 @@
{ {
"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 @@
"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
} }
} }
} }

View File

@ -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",

View File

@ -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);
} }
}; };

View File

@ -1,8 +1,7 @@
<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) { ]}

View File

@ -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 @@
'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');

View File

@ -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",

View File

@ -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 @@
}; };
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 @@
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 @@
}); });
_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 @@
}).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 @@
}; };
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.createContacts = function (converse, type, length) { 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 = 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 @@
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 @@
'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',
}).up() ask: 'subscribe'
);
} else if (type === 'current' || type === 'all') {
mock.cur_names.slice(0, length).map(name =>
result.c('item', {
jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
name: include_nick ? name : undefined,
subscription: 'both'
}).up() }).up()
); );
} }
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', {
jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
name: include_nick ? name : undefined,
subscription: 'both',
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,