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": {
"lodash/prefer-lodash-chain": "off",
"lodash/prefer-lodash-method": [2, {
"ignoreMethods": [
"assign", "every", "keys", "find", "endsWith", "startsWith", "filter",
"reduce", "isArray", "create", "map", "replace", "some", "toLower",
"split", "trim", "forEach", "toUpperCase", "includes", "values", "padStart"
]
}],
"lodash/prefer-lodash-method": "off",
"lodash/import-scope": "off",
"lodash/prefer-constant": "off",
"lodash/prefer-get": "off",

View File

@ -3,6 +3,7 @@
## 6.0.0 (Unreleased)
- #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.
- #1691: Fix `collection.chatbox is undefined` errors
- #1733: New message notifications for a minimized chat stack on top of each other
@ -15,6 +16,10 @@
### 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
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)
@ -26,6 +31,7 @@
* `_converse.api.chats.create`
* `_converse.api.rooms.get`
* `_converse.api.rooms.create`
* `_converse.api.roomviews.close`
- 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`.

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",
"jasmine-core": "2.99.1",
"jsdoc": "^3.6.2",
"lerna": "^3.16.4",
"lerna": "^3.18.1",
"lodash-template-webpack-loader": "jcbrand/lodash-template-webpack-loader",
"mini-css-extract-plugin": "^0.7.0",
"minimist": "^1.2.0",

View File

@ -168,7 +168,8 @@
'name': 'The Play',
'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
_converse.muc_respect_autojoin = false;
@ -188,20 +189,20 @@
it("will use the nickname from the bookmark", mock.initConverse(
['rosterGroupsFetched'], {}, async function (done, _converse) {
await test_utils.waitUntilBookmarksReturned(_converse);
const room_jid = 'coven@chat.shakespeare.lit';
await u.waitUntil(() => _converse.bookmarks);
_converse.bookmarks.create({
'jid': room_jid,
'autojoin': false,
'name': 'The Play',
'nick': 'Othello'
});
const model = await _converse.api.rooms.open(room_jid);
spyOn(model, 'join').and.callThrough();
const room = await _converse.api.rooms.open(room_jid);
spyOn(room, 'join').and.callThrough();
await test_utils.getRoomFeatures(_converse, 'coven', 'chat.shakespeare.lit');
await u.waitUntil(() => model.join.calls.count());
expect(model.get('nick')).toBe('Othello');
await u.waitUntil(() => room.join.calls.count());
expect(room.get('nick')).toBe('Othello');
done();
}));
@ -234,22 +235,13 @@
}));
it("can be unbookmarked", mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
['rosterGroupsFetched'], {}, async function (done, _converse) {
let sent_stanza, IQ_id;
await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.bare_jid,
[{'category': 'pubsub', 'type': 'pep'}],
['http://jabber.org/protocol/pubsub#publish-options']
);
const sendIQ = _converse.connection.sendIQ;
await test_utils.openChatRoom(_converse, 'theplay', 'conference.shakespeare.lit', 'JC');
const jid = 'theplay@conference.shakespeare.lit';
const view = _converse.chatboxviews.get(jid);
await u.waitUntil(() => !_.isNull(view.el.querySelector('.toggle-bookmark')));
await test_utils.waitUntilBookmarksReturned(_converse);
const muc_jid = 'theplay@conference.shakespeare.lit';
await _converse.api.rooms.open(muc_jid);
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
spyOn(view, 'toggleBookmark').and.callThrough();
spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough();
@ -261,16 +253,13 @@
'name': 'The Play',
'nick': ' Othello'
});
expect(_converse.bookmarks.length).toBe(1);
await u.waitUntil(() => _converse.chatboxes.length >= 1);
expect(view.model.get('bookmarked')).toBeTruthy();
let bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
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();
bookmark_icon.click();
bookmark_icon = await u.waitUntil(() => view.el.querySelector('.toggle-bookmark'));
@ -281,8 +270,9 @@
// Check that an IQ stanza is sent out, containing no
// conferences to bookmark (since we removed the one and
// only bookmark).
expect(sent_stanza.toLocaleString()).toBe(
`<iq from="romeo@montague.lit/orchard" id="${IQ_id}" type="set" xmlns="jabber:client">`+
const sent_stanza = _converse.connection.IQ_stanzas.pop();
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">`+
`<publish node="storage:bookmarks">`+
`<item id="current">`+
@ -478,7 +468,7 @@
[{'category': 'pubsub', 'type': 'pep'}],
['http://jabber.org/protocol/pubsub#publish-options']
);
test_utils.openControlBox();
test_utils.openControlBox(_converse);
const IQ_stanzas = _converse.connection.IQ_stanzas;
const sent_stanza = await u.waitUntil(
@ -546,10 +536,9 @@
it("remembers the toggle state of the bookmarks list", mock.initConverse(
['rosterGroupsFetched'], {},
async function (done, _converse) {
['rosterGroupsFetched'], {}, async function (done, _converse) {
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.bare_jid,
[{'category': 'pubsub', 'type': 'pep'}],
@ -558,7 +547,7 @@
const IQ_stanzas = _converse.connection.IQ_stanzas;
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(
`<iq from="romeo@montague.lit/orchard" id="${sent_stanza.getAttribute('id')}" type="get" xmlns="jabber:client">`+
@ -567,13 +556,13 @@
'</pubsub>'+
'</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('items', {'node': 'storage:bookmarks'})
.c('item', {'id': 'current'})
.c('storage', {'xmlns': 'storage:bookmarks'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await _converse.api.waitUntil('bookmarksInitialized');
_converse.bookmarks.create({
'jid': 'theplay@conference.shakespeare.lit',
@ -606,7 +595,7 @@
{ hide_open_bookmarks: true },
async function (done, _converse) {
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
await test_utils.waitUntilBookmarksReturned(_converse);
// 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) {
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';
await test_utils.openChatBoxFor(_converse, contact_jid);
@ -49,7 +49,7 @@
await test_utils.waitForRoster(_converse, 'current');
await test_utils.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']);
await u.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'));
await test_utils.openControlBox();
await test_utils.openControlBox(_converse);
expect(_converse.chatboxes.length).toEqual(1);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
let message = '/me is tired';
@ -95,7 +95,7 @@
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
// openControlBox was called earlier, so the controlbox is
// 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);
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];
el.click();
await u.waitUntil(() => document.querySelectorAll("#conversejs .chatbox").length == 2);
@ -124,7 +124,7 @@
['rosterGroupsFetched'], {'allow_non_roster_messaging': true},
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 stanza = u.toStanza(`
<message from="${sender_jid}"
@ -171,7 +171,7 @@
spyOn(trimmed_chatboxes, 'removeChat').and.callThrough();
await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
let jid, chatboxview;
// openControlBox was called earlier, so the controlbox is
@ -183,7 +183,7 @@
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group li').length);
// Test that they can be maximized again
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;
for (i=0; i<online_contacts.length; i++) {
const el = online_contacts[i];
@ -192,11 +192,11 @@
await u.waitUntil(() => _converse.chatboxes.length == 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++) {
const el = online_contacts[i];
jid = _.trim(el.textContent.trim()).replace(/ /g,'.').toLowerCase() + '@montague.lit';
chatboxview = _converse.chatboxviews.get(jid);
spyOn(chatboxview, 'onMinimized').and.callThrough();
chatboxview.model.set({'minimized': true});
expect(trimmed_chatboxes.addChat).toHaveBeenCalled();
expect(chatboxview.onMinimized).toHaveBeenCalled();
@ -240,7 +240,7 @@
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
expect(_converse.chatboxes.length).toEqual(1);
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@ -267,10 +267,9 @@
spyOn(_converse.ChatBoxViews.prototype, 'trimChats');
await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
spyOn(_converse.api, "trigger").and.callThrough();
test_utils.openControlBox();
test_utils.openChatBoxes(_converse, 6);
await u.waitUntil(() => _converse.chatboxes.length == 7);
@ -282,6 +281,7 @@
// The chatboxes will then be fetched from browserStorage inside the
// onConnected method
newchatboxes.onConnected();
await new Promise(resolve => _converse.api.listen.on('chatBoxesFetched', resolve));
expect(newchatboxes.length).toEqual(7);
// Check that the chatboxes items retrieved from browserStorage
// have the same attributes values as the original ones.
@ -302,7 +302,7 @@
async function (done, _converse) {
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';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
@ -337,7 +337,7 @@
async function (done, _converse) {
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';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
@ -377,13 +377,11 @@
spyOn(_converse.ChatBoxViews.prototype, 'trimChats');
await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
spyOn(_converse.api, "trigger").and.callThrough();
_converse.chatboxes.browserStorage._clear();
test_utils.closeControlBox();
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
expect(_converse.chatboxes.length).toEqual(1);
expect(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
@ -392,18 +390,19 @@
expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
expect(_converse.chatboxes.length).toEqual(7);
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.pluck('id')).toEqual(['controlbox']);
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(_converse.chatboxes.pluck('id')).toEqual(['controlbox']);
// onConnected will fetch chatboxes in browserStorage, but
// because there aren't any open chatboxes, there won't be any
// in browserStorage either. XXX except for the controlbox
newchatboxes.onConnected();
await new Promise(resolve => _converse.api.listen.on('chatBoxesFetched', resolve));
expect(newchatboxes.length).toEqual(1);
expect(newchatboxes.models[0].id).toBe("controlbox");
done();
@ -417,7 +416,7 @@
async function (done, _converse) {
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';
await test_utils.openChatBoxFor(_converse, contact_jid);
const chatbox = _converse.chatboxes.get(contact_jid);
@ -436,7 +435,7 @@
async function (done, _converse) {
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';
await test_utils.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
@ -476,7 +475,7 @@
async function (done, _converse) {
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';
await test_utils.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
@ -492,7 +491,7 @@
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
let toolbar, call_button;
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@ -527,7 +526,7 @@
async function (done, _converse) {
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';
await test_utils.openChatBoxFor(_converse, sender_jid);
@ -559,7 +558,7 @@
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
spyOn(_converse.api, "trigger").and.callThrough();
const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@ -585,7 +584,7 @@
await test_utils.waitForRoster(_converse, 'current');
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);
spyOn(_converse.connection, 'send');
await test_utils.openChatBoxFor(_converse, contact_jid);
@ -606,7 +605,7 @@
async function (done, _converse) {
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';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
@ -640,7 +639,7 @@
async function (done, _converse) {
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';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
@ -679,7 +678,7 @@
async function (done, _converse) {
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';
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
@ -703,7 +702,7 @@
async function (done, _converse) {
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
spyOn(_converse.api, "trigger").and.callThrough();
@ -794,7 +793,7 @@
await test_utils.waitForRoster(_converse, 'current');
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);
_converse.TIMEOUTS.PAUSED = 200; // Make the timeout shorter so that we can test
await test_utils.openChatBoxFor(_converse, contact_jid);
@ -847,7 +846,7 @@
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
// TODO: only show paused state if the previous state was composing
// See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
@ -926,7 +925,7 @@
await test_utils.waitForRoster(_converse, 'current');
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 test_utils.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
@ -983,7 +982,7 @@
async function (done, _converse) {
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';
await test_utils.openChatBoxFor(_converse, contact_jid);
@ -1005,7 +1004,7 @@
await test_utils.waitForRoster(_converse, 'current');
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);
const view = await test_utils.openChatBoxFor(_converse, contact_jid);
expect(view.model.get('chat_state')).toBe('active');
@ -1028,7 +1027,7 @@
async function (done, _converse) {
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';
// See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions
spyOn(_converse.api, "trigger").and.callThrough();
@ -1070,7 +1069,7 @@
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current', 3);
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
spyOn(_converse.api, "trigger").and.callThrough();
const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@ -1101,7 +1100,7 @@
async function (done, _converse) {
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';
spyOn(_converse.api, "trigger").and.callThrough();
@ -1110,8 +1109,9 @@
let message = 'This message is another sent from this chatbox';
await test_utils.sendMessage(view, message);
expect(view.model.messages.length > 0).toBeTruthy();
expect(view.model.messages.browserStorage.records.length > 0).toBeTruthy();
expect(view.model.messages.length === 1).toBeTruthy();
let stored_messages = await view.model.messages.browserStorage.findAll();
expect(stored_messages.length).toBe(1);
await u.waitUntil(() => view.el.querySelector('.chat-msg'));
message = '/clear';
@ -1125,10 +1125,12 @@
preventDefault: function preventDefault () {},
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(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.mostRecent().args, ['messageSend', message]);
done();
@ -1143,7 +1145,7 @@
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
expect(document.title).toBe('Converse Tests');
@ -1181,7 +1183,7 @@
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
_converse.windowState = 'hidden';
spyOn(_converse, 'clearMsgCounter').and.callThrough();
_converse.saveWindowState(null, 'focus');
@ -1196,7 +1198,7 @@
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
expect(document.title).toBe('Converse Tests');
spyOn(_converse, 'incrementMsgCounter').and.callThrough();
@ -1466,7 +1468,7 @@
['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'], {},
async function (done, _converse) {
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current', 1);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
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.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)
_converse.roster.create({
jid: mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
@ -69,8 +69,8 @@
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
test_utils.createContacts(_converse, 'all').openControlBox();
_converse.api.trigger('rosterContactsFetched');
await test_utils.waitForRoster(_converse, 'all');
await test_utils.openControlBox(_converse);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, sender_jid);
@ -78,8 +78,8 @@
const chatview = _converse.chatboxviews.get(sender_jid);
chatview.model.set({'minimized': true});
expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy();
expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy();
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count') === null).toBeTruthy();
expect(_converse.rosterview.el.querySelector('.msgs-indicator') === null).toBeTruthy();
let msg = $msg({
from: sender_jid,
@ -119,7 +119,7 @@
['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.openControlBox();
test_utils.openControlBox(_converse);
var view = _converse.xmppstatusview;
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');
@ -131,8 +131,7 @@
['rosterGroupsFetched'], {},
async function (done, _converse) {
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
var cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.change-status').click()
var modal = _converse.xmppstatusview.status_modal;
@ -160,8 +159,7 @@
['rosterGroupsFetched'], {},
async function (done, _converse) {
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.change-status').click()
const modal = _converse.xmppstatusview.status_modal;
@ -194,7 +192,8 @@
['rosterGroupsFetched'], {},
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');
cbview.el.querySelector('.add-contact').click()
@ -229,7 +228,7 @@
['rosterGroupsFetched'], {'autocomplete_add_contact': false},
async function (done, _converse) {
test_utils.openControlBox();
test_utils.openControlBox(_converse);
const cbview = _converse.chatboxviews.get('controlbox');
cbview.el.querySelector('.add-contact').click()
const modal = _converse.rosterview.add_contact_modal;
@ -319,7 +318,8 @@
'xhr_user_search_url': 'http://example.org/?' },
async function (done, _converse) {
test_utils.createContacts(_converse, 'all').openControlBox();
await test_utils.waitForRoster(_converse, 'all');
await test_utils.openControlBox(_converse);
var modal;
const xhr = {
'open': function open () {},

View File

@ -258,9 +258,8 @@
['rosterInitialized', 'chatBoxesInitialized'], {},
async (done, _converse) => {
test_utils.openControlBox();
test_utils.createContacts(_converse, 'current', 2);
_converse.api.trigger('rosterContactsFetched');
await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current', 2);
// Test on chat that doesn't exist.
let chat = await _converse.api.chats.get('non-existing@jabber.org');
@ -294,9 +293,8 @@
['rosterGroupsFetched', 'chatBoxesInitialized'], {},
async (done, _converse) => {
test_utils.openControlBox();
test_utils.createContacts(_converse, 'current', 2);
_converse.api.trigger('rosterContactsFetched');
await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current', 2);
const jid = mock.cur_names[0].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) {
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';
await test_utils.openChatBoxFor(_converse, contact_jid);

View File

@ -6,13 +6,16 @@
], factory);
} (this, function (jasmine, mock, test_utils) {
"use strict";
var $msg = converse.env.$msg,
_ = converse.env._,
utils = converse.env.utils;
const $msg = converse.env.$msg,
_ = converse.env._,
u = converse.env.utils;
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:
*
* <message xmlns="jabber:client"
@ -23,7 +26,7 @@
* <body>SORRY FOR THIS ADVERT</body
* </message
*/
sinon.spy(utils, 'isHeadlineMessage');
sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({
'xmlns': 'jabber:client',
'to': 'romeo@montague.lit',
@ -33,9 +36,10 @@
.c('nick', {'xmlns': "http://jabber.org/protocol/nick"}).t("-wwdmz").up()
.c('body').t('SORRY FOR THIS ADVERT');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(utils.isHeadlineMessage.called).toBeTruthy();
expect(utils.isHeadlineMessage.returned(false)).toBeTruthy();
utils.isHeadlineMessage.restore();
await u.waitUntil(() => _converse.api.chats.get().length);
expect(u.isHeadlineMessage.called).toBeTruthy();
expect(u.isHeadlineMessage.returned(false)).toBeTruthy();
u.isHeadlineMessage.restore();
done();
}));
@ -55,7 +59,7 @@
* </x>
* </message>
*/
sinon.spy(utils, 'isHeadlineMessage');
sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({
'type': 'headline',
'from': 'notify.example.com',
@ -73,9 +77,9 @@
_converse.chatboxviews.keys(),
'notify.example.com')
).toBeTruthy();
expect(utils.isHeadlineMessage.called).toBeTruthy();
expect(utils.isHeadlineMessage.returned(true)).toBeTruthy();
utils.isHeadlineMessage.restore(); // unwraps
expect(u.isHeadlineMessage.called).toBeTruthy();
expect(u.isHeadlineMessage.returned(true)).toBeTruthy();
u.isHeadlineMessage.restore(); // unwraps
// Headlines boxes don't show an avatar
const view = _converse.chatboxviews.get('notify.example.com');
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",
mock.initConverse((done, _converse) => {
mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {}, function (done, _converse) {
_converse.allow_non_roster_messaging = false;
sinon.spy(utils, 'isHeadlineMessage');
var stanza = $msg({
sinon.spy(u, 'isHeadlineMessage');
const stanza = $msg({
'type': 'headline',
'from': 'andre5114@jabber.snc.ru/Spark',
'to': 'romeo@montague.lit',
@ -98,9 +103,9 @@
.c('body').t('Здравствуйте друзья');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_.without('controlbox', _converse.chatboxviews.keys()).length).toBe(0);
expect(utils.isHeadlineMessage.called).toBeTruthy();
expect(utils.isHeadlineMessage.returned(true)).toBeTruthy();
utils.isHeadlineMessage.restore(); // unwraps
expect(u.isHeadlineMessage.called).toBeTruthy();
expect(u.isHeadlineMessage.returned(true)).toBeTruthy();
u.isHeadlineMessage.restore(); // unwraps
done();
}));
});

View File

@ -176,7 +176,7 @@
async function (done, _converse) {
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';
await test_utils.openChatBoxFor(_converse, contact_jid);
await test_utils.waitUntilDiscoConfirmed(
@ -222,8 +222,7 @@
await test_utils.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.lit'], 'items')
await test_utils.waitUntilDiscoConfirmed(_converse, 'upload.montague.lit', [], [Strophe.NS.HTTPUPLOAD], []);
test_utils.createContacts(_converse, 'current', 3);
_converse.api.trigger('rosterContactsFetched');
await test_utils.waitForRoster(_converse, 'current', 3);
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, 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, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []);
test_utils.createContacts(_converse, 'current');
_converse.api.trigger('rosterContactsFetched');
await test_utils.waitForRoster(_converse, 'current');
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
@ -561,8 +559,7 @@
entities = await _converse.api.disco.entities.get();
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);
test_utils.createContacts(_converse, 'current');
_converse.api.trigger('rosterContactsFetched');
await test_utils.waitForRoster(_converse, 'current');
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
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, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []);
test_utils.createContacts(_converse, 'current');
_converse.api.trigger('rosterContactsFetched');
await test_utils.waitForRoster(_converse, 'current');
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);

View File

@ -13,7 +13,7 @@
allow_registration: false },
async function (done, _converse) {
test_utils.openControlBox();
test_utils.openControlBox(_converse);
const cbview = await u.waitUntil(() => _converse.chatboxviews.get('controlbox'));
const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
expect(checkboxes.length).toBe(1);
@ -51,7 +51,7 @@
u.waitUntil(() => _converse.chatboxviews.get('controlbox'))
.then(() => {
var cbview = _converse.chatboxviews.get('controlbox');
test_utils.openControlBox();
test_utils.openControlBox(_converse);
const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
expect(checkboxes.length).toBe(1);

View File

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

View File

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

View File

@ -65,7 +65,6 @@
type: 'groupchat'
}).c('body').t(message).tree();
await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(u.hasClass('mentioned', view.el.querySelector('.chat-msg'))).toBeTruthy();
done();
}));
@ -87,8 +86,7 @@
type: 'groupchat'
}).c('body').t('First message').tree();
await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
msg = $msg({
from: 'lounge@montague.lit/some2',
@ -97,8 +95,8 @@
type: 'groupchat'
}).c('body').t('Another message').tree();
await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 2);
expect(view.model.messages.length).toBe(2);
done();
}));
@ -154,7 +152,7 @@
async function (done, _converse) {
await test_utils.waitForRoster(_converse, 'current');
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
const muc_jid = 'xsf@muc.xmpp.org';
const sender_jid = `${muc_jid}/romeo`;
const impersonated_jid = `${muc_jid}/i_am_groot`
@ -223,7 +221,6 @@
type: 'groupchat'
}).c('body').t('I wrote this message!').tree();
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('role')).toBe('moderator');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
@ -250,7 +247,6 @@
type: 'groupchat'
}).c('body').t('Another message!').tree();
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('role')).toBe('participant');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
@ -286,7 +282,6 @@
type: 'groupchat'
}).c('body').t('Message from someone not in the MUC right now').tree();
await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.model.messages.last().occupant).toBeUndefined();
// 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);
@ -351,7 +346,6 @@
type: 'groupchat'
}).c('body').t('I wrote this message!').tree();
await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.model.messages.last().get('sender')).toBe('me');
done();
}));
@ -382,7 +376,6 @@
'type': 'groupchat',
'id': msg_id,
}).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.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder airlock breaks?');
@ -445,8 +438,7 @@
preventDefault: function preventDefault () {},
keyCode: 13 // Enter
});
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
expect(view.el.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder airlock breaks?');
@ -503,7 +495,6 @@
'to': 'romeo@montague.lit',
'type': 'groupchat'
}).c('body').t('Hello world').tree());
await new Promise(resolve => view.once('messageInserted', resolve));
expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
// 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':'15', 'end':'23', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree;
await view.model.onMessage(msg);
await new Promise(resolve => view.once('messageInserted', resolve));
const messages = view.el.querySelectorAll('.chat-msg__text');
expect(messages.length).toBe(1);
expect(messages[0].classList.length).toEqual(1);

View File

@ -40,7 +40,7 @@
it("is shown when you are mentioned in a groupchat",
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');
const view = _converse.api.chatviews.get('lounge@montague.lit');
if (!view.el.querySelectorAll('.chat-area').length) {
@ -66,10 +66,10 @@
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(message).tree();
_converse.connection._dataRecv(test_utils.createRequest(msg));
await new Promise(resolve => view.once('messageInserted', resolve));
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
expect(_converse.showMessageNotification).toHaveBeenCalled();
if (no_notification) {
delete window.Notification;
@ -128,16 +128,16 @@
done();
}));
it("is shown when a user changes their chat state (if show_chat_state_notifications is true)", mock.initConverse((done, _converse) => {
// TODO: not yet testing show_desktop_notifications setting
_converse.show_chat_state_notifications = true;
it("is shown when a user changes their chat state (if show_chat_state_notifications is true)",
mock.initConverse(['rosterGroupsFetched'], {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, 'showChatStateNotification');
const jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_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();
done()
}));

View File

@ -81,8 +81,7 @@
async function (done, _converse) {
const message = 'This message will be encrypted'
test_utils.createContacts(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
await test_utils.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const view = await test_utils.openChatBoxFor(_converse, contact_jid);
const payload = await view.model.encryptMessage(message);
@ -98,8 +97,7 @@
async function (done, _converse) {
let sent_stanza;
test_utils.createContacts(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
await test_utils.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => initializedOMEMO(_converse));
await test_utils.openChatBoxFor(_converse, contact_jid);
@ -386,8 +384,7 @@
await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], [Strophe.NS.SID]);
test_utils.createContacts(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
await test_utils.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => initializedOMEMO(_converse));
await test_utils.openChatBoxFor(_converse, contact_jid);
@ -605,8 +602,7 @@
async function (done, _converse) {
_converse.NUM_PREKEYS = 5; // Restrict to 5, otherwise the resulting stanza is too large to easily test
test_utils.createContacts(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
await test_utils.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => initializedOMEMO(_converse));
@ -710,7 +706,7 @@
['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';
// Wait until own devices are fetched
@ -884,7 +880,7 @@
['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';
let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
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
test_utils.createContacts(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
await test_utils.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
let stanza = $iq({
@ -1113,8 +1108,7 @@
['http://jabber.org/protocol/pubsub#publish-options']
);
test_utils.createContacts(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
await test_utils.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
@ -1460,8 +1454,7 @@
['http://jabber.org/protocol/pubsub#publish-options']
);
test_utils.createContacts(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
await test_utils.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await test_utils.openChatBoxFor(_converse, contact_jid)
// We simply emit, to avoid doing all the setup work

View File

@ -74,7 +74,7 @@
['rosterGroupsFetched'], {},
async (done, _converse) => {
test_utils.openControlBox();
test_utils.openControlBox(_converse);
const view = _converse.xmppstatusview;
spyOn(view.model, 'sendPresence').and.callThrough();
spyOn(_converse.connection, 'send').and.callThrough();
@ -112,12 +112,12 @@
it("has its priority taken into account",
mock.initConverse(
['rosterGroupsFetched'], {},
(done, _converse) => {
async (done, _converse) => {
test_utils.openControlBox();
test_utils.createContacts(_converse, 'current'); // Create some contacts so that we can test positioning
test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current');
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(`
<presence xmlns="jabber:client"
to="romeo@montague.lit/converse.js-21770972"

View File

@ -13,7 +13,7 @@
['rosterGroupsFetched'], {'muc_show_join_leave': false},
async function (done, _converse) {
test_utils.openControlBox();
test_utils.openControlBox(_converse);
await test_utils.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
_.rangeRight(3000, 0).forEach(i => {
const name = `User ${i.toString().padStart(5, '0')}`;
@ -41,7 +41,7 @@
function (done, _converse) {
_converse.roster_groups = false;
test_utils.openControlBox();
test_utils.openControlBox(_converse);
expect(_converse.roster.pluck('jid').length).toBe(0);
var stanza = $iq({
@ -86,7 +86,7 @@
// _converse.show_only_online_users = true;
_converse.roster_groups = true;
test_utils.openControlBox();
test_utils.openControlBox(_converse);
expect(_converse.roster.pluck('jid').length).toBe(0);
var stanza = $iq({

View File

@ -5,9 +5,9 @@
"test-utils"], factory);
} (this, function (jasmine, mock, test_utils) {
"use strict";
const Strophe = converse.env.Strophe;
const $iq = converse.env.$iq;
const $pres = converse.env.$pres;
const Strophe = converse.env.Strophe;
const _ = converse.env._;
const sizzle = converse.env.sizzle;
const u = converse.env.utils;
@ -175,7 +175,7 @@
*/
const sent_presence = await u.waitUntil(() => sent_stanzas.filter(s => s.match('presence')).pop());
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">`+
`<nick xmlns="http://jabber.org/protocol/nick">Romeo Montague</nick>`+
`</presence>`
@ -455,18 +455,13 @@
{ roster_groups: false },
async function (done, _converse) {
var sent_IQ, IQ_id, jid = 'abram@montague.lit';
test_utils.openControlBox(_converse);
test_utils.createContacts(_converse, 'current');
const jid = 'abram@montague.lit';
await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current');
spyOn(window, 'confirm').and.returnValue(true);
// We now have a contact we want to remove
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();
await u.waitUntil(() => header.parentElement.querySelectorAll('li').length);
@ -493,8 +488,9 @@
* </query>
* </iq>
*/
expect(sent_IQ.toLocaleString()).toBe(
`<iq id="${IQ_id}" type="set" xmlns="jabber:client">`+
const sent_iq = _converse.connection.IQ_stanzas.pop();
expect(Strophe.serialize(sent_iq)).toBe(
`<iq id="${sent_iq.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster">`+
`<item jid="abram@montague.lit" subscription="remove"/>`+
`</query>`+
@ -502,7 +498,7 @@
// Receive confirmation from the contact's server
// <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));
// Our contact has now been removed
await u.waitUntil(() => typeof _converse.roster.get(jid) === "undefined");
@ -514,9 +510,8 @@
async function (done, _converse) {
spyOn(_converse.api, "trigger").and.callThrough();
test_utils.openControlBox(_converse);
// Create some contacts so that we can test positioning
test_utils.createContacts(_converse, 'current');
await test_utils.openControlBox(_converse);
await test_utils.waitForRoster(_converse, 'current');
/* <presence
* from='user@example.com'
* to='contact@example.org'
@ -534,7 +529,7 @@
const header = sizzle('a:contains("Contact requests")', _converse.rosterview.el).pop();
const contacts = _.filter(header.parentElement.querySelectorAll('li'), u.isVisible);
return contacts.length;
}, 300);
}, 500);
expect(_converse.api.trigger).toHaveBeenCalledWith('contactRequest', jasmine.any(Object));
const header = sizzle('a:contains("Contact requests")', _converse.rosterview.el).pop();
expect(u.isVisible(header)).toBe(true);

View File

@ -16,8 +16,7 @@
async function (done, _converse) {
await u.waitUntil(() => _converse.chatboxviews.get('controlbox'));
test_utils.openControlBox();
const cbview = _converse.chatboxviews.get('controlbox');
const cbview = _converse.api.controlbox.get();
expect(cbview.el.querySelectorAll('a.register-account').length).toBe(0);
done();
}));
@ -29,15 +28,22 @@
allow_registration: true },
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);
const cbview = _converse.chatboxviews.get('controlbox');
test_utils.openControlBox();
const panels = cbview.el.querySelector('.controlbox-panes');
const login = panels.firstElementChild;
const registration = panels.childNodes[1];
const register_link = cbview.el.querySelector('a.register-account');
expect(register_link.textContent).toBe("Create an account");
register_link.click();
await u.waitUntil(() => u.isVisible(registration));
expect(u.isVisible(login)).toBe(false);
done();
@ -52,8 +58,7 @@
spyOn(Strophe.Connection.prototype, 'connect');
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
test_utils.openControlBox();
const cbview = _converse.chatboxviews.get('controlbox');
const cbview = _converse.api.controlbox.get();
const registerview = cbview.registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
@ -88,8 +93,7 @@
spyOn(Strophe.Connection.prototype, 'connect');
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
test_utils.openControlBox();
const cbview = _converse.chatboxviews.get('controlbox');
const cbview = _converse.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click();
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
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[type=submit]').click();
expect(registerview.onProviderChosen).toHaveBeenCalled();
@ -143,8 +147,14 @@
async function (done, _converse) {
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
test_utils.openControlBox();
const cbview = _converse.chatboxviews.get('controlbox');
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.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click();
const registerview = cbview.registerpanel;
@ -153,7 +163,6 @@
spyOn(registerview, 'onRegistrationFields').and.callThrough();
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
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[type=submit]').click();
@ -200,8 +209,14 @@
async function (done, _converse) {
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
test_utils.openControlBox();
const cbview = _converse.chatboxviews.get('controlbox');
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.api.controlbox.get();
cbview.el.querySelector('.toggle-register-login').click();
const registerview = _converse.chatboxviews.get('controlbox').registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough();
@ -209,7 +224,6 @@
spyOn(registerview, 'onRegistrationFields').and.callThrough();
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
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[type=submit]').click();
@ -274,7 +288,13 @@
async function (done, _converse) {
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');
cbview.el.querySelector('.toggle-register-login').click();
const registerview = _converse.chatboxviews.get('controlbox').registerpanel;

View File

@ -13,7 +13,7 @@
// have to mock stanza traffic.
}, async function (done, _converse) {
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
const controlbox = _converse.chatboxviews.get('controlbox');
let list = controlbox.el.querySelector('.list-container--openrooms');
expect(_.includes(list.classList, 'hidden')).toBeTruthy();
@ -31,7 +31,7 @@
expect(room_els.length).toBe(2);
let view = _converse.chatboxviews.get('room@conference.shakespeare.lit');
view.close();
await view.close();
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(1);
expect(room_els[0].innerText).toBe('lounge@montague.lit');
@ -39,7 +39,7 @@
u.waitUntil(() => _.includes(list.classList, 'hidden'));
view = _converse.chatboxviews.get('lounge@montague.lit');
view.close();
await view.close();
room_els = _converse.rooms_list_view.el.querySelectorAll(".open-room");
expect(room_els.length).toBe(0);
@ -110,19 +110,21 @@
describe("A groupchat shown in the groupchats list", function () {
it("is highlighted if it's currently open", mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'],
{ view_mode: 'fullscreen',
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
}, async function (done, _converse) {
['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'],
{ view_mode: 'fullscreen',
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
}, 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
await u.waitUntil(() => lview.el.querySelectorAll(".open-room").length);
let room_els = lview.el.querySelectorAll(".available-chatroom");
expect(room_els.length).toBe(1);
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');
await _converse.api.rooms.open('balcony@chat.shakespeare.lit', {'nick': 'some1'});
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(
['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'],
{ whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we
// have to mock stanza traffic.
}, async function (done, _converse) {
['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'],
{ whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we
// have to mock stanza traffic.
}, async function (done, _converse) {
const IQ_stanzas = _converse.connection.IQ_stanzas;
const room_jid = 'coven@chat.shakespeare.lit';
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
await _converse.api.rooms.open(room_jid, {'nick': 'some1'});
const view = _converse.chatboxviews.get(room_jid);
@ -242,11 +244,11 @@
}));
it("can be closed", mock.initConverse(
['rosterGroupsFetched', 'emojisInitialized'],
{ whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
},
async function (done, _converse) {
['rosterGroupsFetched', 'emojisInitialized'],
{ whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
},
async function (done, _converse) {
spyOn(window, 'confirm').and.callFake(() => true);
expect(_converse.chatboxes.length).toBe(1);
@ -260,6 +262,8 @@
close_el.click();
expect(window.confirm).toHaveBeenCalledWith(
'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");
expect(room_els.length).toBe(0);
expect(_converse.chatboxes.length).toBe(1);
@ -272,7 +276,7 @@
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
}, async (done, _converse) => {
test_utils.openControlBox();
await test_utils.openControlBox(_converse);
const room_jid = 'kitchen@conference.shakespeare.lit';
await u.waitUntil(() => _converse.rooms_list_view !== undefined, 500);
await test_utils.openAndEnterChatRoom(_converse, 'kitchen@conference.shakespeare.lit', 'romeo');
@ -287,12 +291,10 @@
type: 'groupchat'
}).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
let room_el = lview.el.querySelector(".available-chatroom");
expect(_.includes(room_el.classList, 'unread-msgs')).toBeTruthy();
const lview = _converse.rooms_list_view
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
await view.model.onMessage(
@ -303,10 +305,11 @@
type: 'groupchat'
}).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 = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
let indicator_el = await u.waitUntil(() => lview.el.querySelector(".msgs-indicator"));
expect(indicator_el.textContent).toBe('1');
spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough();
await view.model.onMessage(
$msg({
from: room_jid+'/'+nick,
@ -316,14 +319,13 @@
}).c('body').t('romeo: and another thing...').tree()
);
await u.waitUntil(() => view.model.incrementUnreadMsgCounter.calls.count());
indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
expect(indicator_el.textContent).toBe('2');
await u.waitUntil(() => lview.el.querySelector(".msgs-indicator").textContent === '2', 1000);
// When the chat gets maximized again, the unread indicators are removed
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);
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();
done();
}));

View File

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

View File

@ -39,18 +39,12 @@
let IQ_stanzas = _converse.connection.IQ_stanzas;
await u.waitUntil(() => IQ_stanzas.length === 4);
let iq = IQ_stanzas.pop();
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];
let iq = IQ_stanzas[IQ_stanzas.length-1];
expect(Strophe.serialize(iq)).toBe(
`<iq id="${iq.getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
await test_utils.waitForRoster(_converse, 'current', 1);
IQ_stanzas.pop();
const disco_iq = IQ_stanzas.pop();
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">`+
@ -58,9 +52,13 @@
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">`+
`<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.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(_converse.session.get('unacked_stanzas').length).toBe(5);
@ -104,7 +102,7 @@
// test session resumption
_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());
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
iq = IQ_stanzas.pop();
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>`);
expect(Strophe.serialize(iq)).toBe(`<iq id="${iq.getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
expect(IQ_stanzas.filter(iq => sizzle('query[xmlns="jabber:iq:roster"]', iq).pop()).length).toBe(0);
@ -141,9 +137,6 @@
},
async function (done, _converse) {
const view = _converse.chatboxviews.get('controlbox');
spyOn(view, 'renderControlBoxPane').and.callThrough();
_converse.api.user.login('romeo@montague.lit/orchard', 'secret');
const sent_stanzas = _converse.connection.sent_stanzas;
let stanza = await u.waitUntil(() => sent_stanzas.filter(s => (s.tagName === 'enable')).pop());
@ -154,7 +147,7 @@
await test_utils.waitForRoster(_converse, 'current', 1);
// test session resumption
_converse.api.connection.reconnect();
await _converse.api.connection.reconnect();
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"/>');

View File

@ -35,6 +35,7 @@
}).t(spoiler_hint)
.tree();
await _converse.chatboxes.onMessage(msg);
await u.waitUntil(() => _converse.api.chats.get().length === 2);
const view = _converse.chatboxviews.get(sender_jid);
await new Promise(resolve => view.once('messageInserted', resolve));
await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
@ -69,6 +70,7 @@
'xmlns': 'urn:xmpp:spoiler:0',
}).tree();
await _converse.chatboxes.onMessage(msg);
await u.waitUntil(() => _converse.api.chats.get().length === 2);
const view = _converse.chatboxviews.get(sender_jid);
await new Promise(resolve => view.once('messageInserted', resolve));
await u.waitUntil(() => view.model.vcard.get('fullname') === 'Mercutio')
@ -86,7 +88,7 @@
async (done, _converse) => {
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';
// XXX: We need to send a presence from the contact, so that we
@ -159,7 +161,7 @@
async (done, _converse) => {
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';
// XXX: We need to send a presence from the contact, so that we
@ -173,7 +175,7 @@
_converse.connection._dataRecv(test_utils.createRequest(presence));
await test_utils.openChatBoxFor(_converse, contact_jid);
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'));
let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');

View File

@ -1,14 +1,13 @@
(function (root, factory) {
define([
"jasmine",
"converse-core",
"mock",
"test-utils",
"utils",
"transcripts"
], factory
);
} (this, function (jasmine, converse, mock, test_utils, utils, transcripts) {
} (this, function (jasmine, mock, test_utils, utils, transcripts) {
var Strophe = converse.env.Strophe;
var _ = converse.env._;
var IGNORED_TAGS = [

View File

@ -15,11 +15,11 @@
['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'], {},
async function (done, _converse) {
test_utils.createContacts(_converse, 'current');
await test_utils.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
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);
const view = _converse.chatboxviews.get(contact_jid);
let show_modal_button = view.el.querySelector('.show-user-details-modal');
@ -48,7 +48,7 @@
['rosterGroupsFetched', 'emojisInitialized'], {},
async function (done, _converse) {
test_utils.createContacts(_converse, 'current');
await test_utils.waitForRoster(_converse, 'current', 1);
_converse.api.trigger('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@ -60,9 +60,8 @@
const modal = view.user_details_modal;
await u.waitUntil(() => u.isVisible(modal.el), 2000);
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');
expect(u.isVisible(remove_contact_button)).toBeTruthy();
remove_contact_button.click();

View File

@ -445,6 +445,14 @@ converse.plugins.add('converse-chatview', {
this.insertIntoDOM();
this.scrollDown();
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 () {
@ -589,7 +597,7 @@ converse.plugins.add('converse-chatview', {
}
},
showHelpMessages (msgs, type, spinner) {
showHelpMessages (msgs, type='info', spinner) {
msgs.forEach(msg => {
this.content.insertAdjacentHTML(
'beforeend',
@ -997,11 +1005,11 @@ converse.plugins.add('converse-chatview', {
}
},
clearMessages (ev) {
async clearMessages (ev) {
if (ev && ev.preventDefault) { ev.preventDefault(); }
const result = confirm(__("Are you sure you want to clear the messages from this conversation?"));
if (result === true) {
this.model.clearMessages();
await this.model.clearMessages();
}
return this;
},
@ -1119,7 +1127,7 @@ converse.plugins.add('converse-chatview', {
}
},
close (ev) {
async close (ev) {
if (ev && ev.preventDefault) { ev.preventDefault(); }
if (Backbone.history.getFragment() === "converse/chat?jid="+this.model.get('jid')) {
_converse.router.navigate('');
@ -1130,7 +1138,7 @@ converse.plugins.add('converse-chatview', {
this.model.setChatState(_converse.INACTIVE);
this.model.sendChatState();
}
this.model.close();
await this.model.close();
this.remove();
/**
* Triggered once a chatbox has been closed.
@ -1247,10 +1255,7 @@ converse.plugins.add('converse-chatview', {
},
viewUnreadMessages () {
this.model.save({
'scrolled': false,
'top_visible_message': null
});
this.model.save({'scrolled': false, 'top_visible_message': null});
this.scrollDown();
},
@ -1315,7 +1320,7 @@ converse.plugins.add('converse-chatview', {
* @namespace _converse.api.chatviews
* @memberOf _converse.api
*/
'chatviews': {
chatviews: {
/**
* Get the view of an already open chat.
* @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:
* _converse.api.chatviews.get(['buddy1@example.com', 'buddy2@example.com'])
*/
'get' (jids) {
get (jids) {
if (jids === undefined) {
_converse.log(
"chatviews.get: You need to provide at least one JID",
Strophe.LogLevel.ERROR
);
return null;
return Object.values(_converse.chatboxviews.getAll());
}
if (isString(jids)) {
return _converse.chatboxviews.get(jids);

View File

@ -85,7 +85,7 @@ converse.plugins.add('converse-controlbox', {
ChatBoxes: {
model (attrs, options) {
const { _converse } = this.__super__;
if (attrs.id == 'controlbox') {
if (attrs && attrs.id == 'controlbox') {
return new _converse.ControlBox(attrs, options);
} else {
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 (get(ev, 'name') === 'closeAllChatBoxes' &&
(_converse.disconnection_cause !== _converse.LOGOUT ||
_converse.show_controlbox_by_default)) {
return;
}
if (_converse.sticky_controlbox) {
return;
}
const connection = get(_converse, 'connection', {});
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 {
this.model.trigger('hide');
}
@ -591,7 +601,7 @@ converse.plugins.add('converse-controlbox', {
_converse.api.listen.on('chatBoxesFetched', () => {
const controlbox = _converse.chatboxes.get('controlbox') || addControlBox();
controlbox.save({connected:true});
controlbox.save({'connected': true});
});
const disconnect = function () {

View File

@ -49,9 +49,12 @@ converse.plugins.add('converse-dragresize', {
ChatBox: {
initialize () {
const { _converse } = this.__super__;
const result = this.__super__.initialize.apply(this, arguments),
height = this.get('height'), width = this.get('width'),
save = this.get('id') === 'controlbox' ? this.set.bind(this) : this.save.bind(this);
const result = this.__super__.initialize.apply(this, arguments);
const height = this.get('height'), width = this.get('width');
const save = this.get('id') === 'controlbox' ?
(attrs) => this.set(attrs) :
(attrs) => this.save(attrs);
save({
'height': _converse.applyDragResistance(height, this.get('default_height')),
'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)) {
this.model.clearUnreadMsgCounter();
this.model.save();
}
this.scrollDown();
},
@ -925,12 +924,12 @@ converse.plugins.add('converse-muc-views', {
* @private
* @method _converse.ChatRoomView#close
*/
close () {
async close () {
this.hide();
if (Backbone.history.getFragment() === "converse/room?jid="+this.model.get('jid')) {
_converse.router.navigate('');
}
this.model.leave();
await this.model.leave();
return _converse.ChatBoxView.prototype.close.apply(this, arguments);
},
@ -2128,7 +2127,6 @@ converse.plugins.add('converse-muc-views', {
const view = _converse.chatboxviews.get('controlbox');
if (view && view.roomspanel) {
view.roomspanel.model.destroy();
view.roomspanel.model.browserStorage._clear();
view.roomspanel.remove();
delete view.roomspanel;
}
@ -2197,6 +2195,7 @@ converse.plugins.add('converse-muc-views', {
*
* @method _converse.api.roomviews.close
* @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) {
let views;
@ -2207,11 +2206,7 @@ converse.plugins.add('converse-muc-views', {
} else if (Array.isArray(jids)) {
views = jids.map(jid => _converse.chatboxviews.get(jid));
}
views.forEach(view => {
if (view.is_chatroom && view.model) {
view.close();
}
});
return Promise.all(views.map(v => (v.is_chatroom && v.model && v.close())))
}
}
});

View File

@ -714,9 +714,8 @@ converse.plugins.add('converse-omemo', {
if (identifier === null || identifier === undefined) {
throw new Error("Can't save identity_key for invalid identifier");
}
const address = new libsignal.SignalProtocolAddress.fromString(identifier),
existing = this.get('identity_key'+address.getName());
const address = new libsignal.SignalProtocolAddress.fromString(identifier);
const existing = this.get('identity_key'+address.getName());
const b64_idkey = u.arrayBufferToBase64(identity_key);
this.save('identity_key'+address.getName(), b64_idkey)
@ -986,8 +985,7 @@ converse.plugins.add('converse-omemo', {
initialize () {
this.devices = new _converse.Devices();
const id = `converse.devicelist-${_converse.bare_jid}-${this.get('jid')}`;
const storage = _converse.config.get('storage');
this.devices.browserStorage = _converse.createStore(id, storage);
this.devices.browserStorage = _converse.createStore(id);
this.fetchDevices();
},
@ -1096,7 +1094,7 @@ converse.plugins.add('converse-omemo', {
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 () {
@ -1171,10 +1169,9 @@ converse.plugins.add('converse-omemo', {
function restoreOMEMOSession () {
if (_converse.omemo_store === undefined) {
const storage = _converse.config.get('storage'),
id = `converse.omemosession-${_converse.bare_jid}`;
const id = `converse.omemosession-${_converse.bare_jid}`;
_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();
}
@ -1184,9 +1181,8 @@ converse.plugins.add('converse-omemo', {
return;
}
_converse.devicelists = new _converse.DeviceLists();
const storage = _converse.config.get('storage'),
id = `converse.devicelists-${_converse.bare_jid}`;
_converse.devicelists.browserStorage = _converse.createStore(id, storage);
const id = `converse.devicelists-${_converse.bare_jid}`;
_converse.devicelists.browserStorage = _converse.createStore(id);
try {
await fetchOwnDevices();

View File

@ -808,9 +808,8 @@ converse.plugins.add('converse-rosterview', {
createRosterFilter () {
// Create a model on which we can store filter properties
const model = new _converse.RosterFilter();
model.id = `_converse.rosterfilter${_converse.bare_jid}`;
const storage = _converse.config.get('storage');
model.browserStorage = _converse.createStore(this.filter.id, storage);
model.id = `_converse.rosterfilter-${_converse.bare_jid}`;
model.browserStorage = _converse.createStore(model.id);
this.filter_view = new _converse.RosterFilterView({'model': model});
this.listenTo(this.filter_view.model, 'change', this.updateFilter);
this.filter_view.model.fetch();

View File

@ -96,7 +96,15 @@ converse.plugins.add('converse-uniview', {
.forEach(hideChat);
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 () {
const deferred = u.getResolveablePromise();
if (this.browserStorage.records.length > 0) {
if (window.sessionStorage.getItem(this.fetched_flag)) {
this.fetch({
'success': () => 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 {
deferred.resolve();
this.fetchBookmarksFromServer(deferred);
}
return deferred;
},
@ -231,6 +225,7 @@ converse.plugins.add('converse-bookmarks', {
onBookmarksReceived (deferred, iq) {
this.createBookmarksFromStanza(iq);
window.sessionStorage.setItem(this.fetched_flag, true);
if (deferred !== undefined) {
return deferred.resolve();
}
@ -301,6 +296,7 @@ converse.plugins.add('converse-bookmarks', {
if (_converse.bookmarks !== undefined) {
_converse.bookmarks.clearSession({'silent': true});
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}`);
} else {
_converse.bosh_session.destroy();
_converse.bosh_session.browserStorage._clear();
delete _converse.bosh_session;
}
});

View File

@ -18,8 +18,8 @@ function propertySort (array, property) {
}
function generateVerificationString (_converse) {
const identities = _converse.api.disco.own.identities.get(),
features = _converse.api.disco.own.features.get();
const identities = _converse.api.disco.own.identities.get();
const features = _converse.api.disco.own.features.get();
if (identities.length > 1) {
propertySort(identities, "category");

View File

@ -353,8 +353,7 @@ converse.plugins.add('converse-chatboxes', {
initMessages () {
this.messages = new this.messagesCollection();
this.messages.chatbox = this;
const storage = _converse.config.get('storage');
this.messages.browserStorage = _converse.createStore(this.getMessagesCacheKey(), storage);
this.messages.browserStorage = _converse.createStore(this.getMessagesCacheKey());
this.listenTo(this.messages, 'change:upload', message => {
if (message.get('upload') === _converse.SUCCESS) {
_converse.api.send(this.createMessageStanza(message));
@ -388,27 +387,28 @@ converse.plugins.add('converse-chatboxes', {
return this.messages.fetched;
},
clearMessages () {
async clearMessages () {
try {
this.messages.models.forEach(m => m.destroy());
await Promise.all(this.messages.models.map(m => m.destroy()));
this.messages.reset();
} catch (e) {
this.messages.trigger('reset');
_converse.log(e, Strophe.LogLevel.ERROR);
} finally {
delete this.messages.fetched;
this.messages.browserStorage._clear();
}
},
close () {
async close () {
try {
this.destroy();
await new Promise((success, reject) => {
return this.destroy({success, 'error': (m, e) => reject(e)})
});
} catch (e) {
_converse.log(e, Strophe.LogLevel.ERROR);
} finally {
if (_converse.clear_messages_on_reconnection) {
this.clearMessages();
await this.clearMessages();
}
}
},
@ -1172,9 +1172,7 @@ converse.plugins.add('converse-chatboxes', {
if (reconnecting) {
return;
}
const storage = _converse.config.get('storage');
const id = `converse.chatboxes-${_converse.bare_jid}`;
this.browserStorage = _converse.createStore(id, storage);
this.browserStorage = _converse.createStore(`converse.chatboxes-${_converse.bare_jid}`);
this.registerMessageHandler();
this.fetch({
'add': true,
@ -1202,7 +1200,7 @@ converse.plugins.add('converse-chatboxes', {
return;
}
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());
await _converse.api.waitUntil('chatBoxesFetched');
let chatbox = this.get(Strophe.getBareJidFromJid(jid));
if (chatbox) {
chatbox.save(attrs);
@ -1519,11 +1518,7 @@ converse.plugins.add('converse-chatboxes', {
* });
*/
async open (jids, attrs, force) {
await Promise.all([
_converse.api.waitUntil('rosterContactsFetched'),
_converse.api.waitUntil('chatBoxesFetched')
]);
await _converse.api.waitUntil('chatBoxesFetched');
if (isString(jids)) {
const chat = await _converse.api.chats.create(jids, attrs);
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
* @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;
Backbone = Backbone.noConflict();
BrowserStorage.patch(Backbone);
dayjs.extend(advancedFormat);
@ -109,9 +110,20 @@ _converse.VERSION_NAME = "v5.0.5dev";
Object.assign(_converse, Backbone.Events);
_converse.Collection = Backbone.Collection.extend({
clearSession (options) {
Array.from(this.models).forEach(m => m.destroy(options));
this.browserStorage._clear();
async clearSession (options={}) {
await Promise.all(Array.from(this.models).map(m => {
return new Promise(
success => m.destroy(
Object.assign(options, {
success,
'error': (m, e) => {
_converse.log(e, Strophe.LogLevel.ERROR);
success()
}
})
)
);
}));
this.reset();
}
});
@ -377,9 +389,40 @@ _converse.isUniView = function () {
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) {
const s = storage ? storage : _converse.config.get('storage');
return new BrowserStorage[s](id);
const s = _converse.storage[storage ? storage : _converse.config.get('storage')];
return new Backbone.BrowserStorage(id, s);
}
@ -455,8 +498,8 @@ function initClientConfig () {
}
function tearDown () {
_converse.api.trigger('beforeTearDown');
async function tearDown () {
await _converse.api.trigger('beforeTearDown', {'synchronous': true});
window.removeEventListener('click', _converse.onUserActivity);
window.removeEventListener('focus', _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.setConnectionStatus(
Strophe.Status.RECONNECTING,
@ -546,7 +589,7 @@ function reconnect () {
_converse.api.trigger('will-reconnect');
_converse.connection.reconnecting = true;
tearDown();
await tearDown();
return _converse.api.user.login();
}
@ -558,7 +601,6 @@ _converse.shouldClearCache = () => (!_converse.config.get('trusted') || _convers
function clearSession () {
if (_converse.session !== undefined) {
_converse.session.destroy();
_converse.session.browserStorage._clear();
delete _converse.session;
}
/**
@ -669,7 +711,7 @@ async function initSession (jid) {
await new Promise(r => _converse.session.fetch({'success': r, 'error': r}));
if (_converse.session.get('active')) {
_converse.session.clear();
_converse.session.save({'id': id});
_converse.session.save({id});
}
saveJIDtoSession(jid);
/**
@ -822,6 +864,7 @@ function setUpXMLLogging () {
async function finishInitialization () {
await initStorage();
initClientConfig();
initPlugins();
_converse.registerGlobalEventHandlers();
@ -920,10 +963,15 @@ function unregisterGlobalEventHandlers () {
_converse.api.trigger('unregisteredGlobalEventHandlers');
}
function cleanup () {
// Looks like _converse.initialized was called again without logging
// out or disconnecting in the previous session.
// This happens in tests. We therefore first clean up.
async function cleanup () {
// Make sure everything is reset in case this is a subsequent call to
// convesre.initialize (happens during tests).
if (_converse.localStorage) {
await Promise.all([
BrowserStorage.localForage.dropInstance({'name': 'local'}),
BrowserStorage.localForage.dropInstance({'name': 'indexed'}),
BrowserStorage.localForage.dropInstance({'name': 'session'})]);
}
Backbone.history.stop();
unregisterGlobalEventHandlers();
delete _converse.controlboxtoggle;
@ -939,7 +987,7 @@ function cleanup () {
_converse.initialize = async function (settings, callback) {
cleanup();
await cleanup();
settings = settings !== undefined ? settings : {};
PROMISES.forEach(addPromise);
@ -1256,7 +1304,7 @@ _converse.api = {
const conn_status = _converse.connfeedback.get('connection_status');
if (_converse.authentication === _converse.ANONYMOUS) {
tearDown();
await tearDown();
clearSession();
}
if (conn_status === Strophe.Status.CONNFAIL) {
@ -1421,8 +1469,7 @@ _converse.api = {
complete();
}
return promise;
},
}
},
/**

View File

@ -120,18 +120,24 @@ converse.plugins.add('converse-disco', {
_converse.api.trigger('discoExtensionFieldDiscovered', field);
},
fetchFeatures (options) {
if (options.ignore_cache || this.features.browserStorage.records.length === 0) {
async fetchFeatures (options) {
if (options.ignore_cache) {
this.queryInfo();
} else {
this.features.fetch({
add: true,
success: () => {
this.waitUntilFeaturesDiscovered.resolve(this);
this.trigger('featuresDiscovered');
}
});
this.identities.fetch({add: true});
const store_id = this.features.browserStorage.name;
const result = await this.features.browserStorage.store.getItem(store_id);
if (result && result.length === 0 || result === null) {
this.queryInfo();
} else {
this.features.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 () {
const bare_jid = Strophe.getBareJidFromJid(_converse.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.browserStorage = _converse.createStore(id, "session");
_converse.stream_features.fetch({
@ -281,6 +287,9 @@ converse.plugins.add('converse-disco', {
* @example _converse.api.listen.on('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 ********************/
// Re-create promise upon reconnection
_converse.api.listen.on('userSessionInitialized', initStreamFeatures);
_converse.api.listen.on('beforeResourceBinding', initStreamFeatures);
_converse.api.listen.on('reconnected', 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) {
_converse.stream_features.clearSession();
await _converse.stream_features.clearSession();
delete _converse.stream_features;
}
});

View File

@ -388,7 +388,6 @@ converse.plugins.add('converse-emoji', {
/************************ BEGIN Event Handlers ************************/
_converse.api.listen.on('clearSession', () => {
if (_converse.emojipicker) {
_converse.emojipicker.browserStorage._clear();
_converse.emojipicker.destroy();
delete _converse.emojipicker
}

View File

@ -253,16 +253,14 @@ converse.plugins.add('converse-mam', {
chat.fetchNewestMessages();
}
});
_converse.api.listen.on('afterMessagesFetched', chat => {
// 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.
// (given that BOSH or SMACKS will ensure that you get messages
// sent during the reload).
//
// With MUCs we can listen for `enteredNewRoom` but for
// 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) {
// With MUCs we can listen for `enteredNewRoom`.
if (chat.get('type') === _converse.PRIVATE_CHAT_TYPE && !_converse.connection.restored) {
chat.fetchNewestMessages();
}
});

View File

@ -84,10 +84,17 @@ converse.plugins.add('converse-muc', {
dependencies: ["converse-chatboxes", "converse-disco", "converse-controlbox"],
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: {
model (attrs, options) {
const { _converse } = this.__super__;
if (attrs.type == _converse.CHATROOMS_TYPE) {
if (attrs && attrs.type == _converse.CHATROOMS_TYPE) {
return new _converse.ChatRoom(attrs, options);
} else {
return this.__super__.model.apply(this, arguments);
@ -528,11 +535,15 @@ converse.plugins.add('converse-muc', {
* registered for this groupchat.
*/
if (this.message_handler) {
_converse.connection.deleteHandler(this.message_handler);
if (_converse.connection) {
_converse.connection.deleteHandler(this.message_handler);
}
delete this.message_handler;
}
if (this.presence_handler) {
_converse.connection.deleteHandler(this.presence_handler);
if (_converse.connection) {
_converse.connection.deleteHandler(this.presence_handler);
}
delete this.presence_handler;
}
return this;
@ -612,14 +623,13 @@ converse.plugins.add('converse-muc', {
* @method _converse.ChatRoom#leave
* @param { string } [exit_msg] - Message to indicate your reason for leaving
*/
leave (exit_msg) {
async leave (exit_msg) {
this.features.destroy();
this.occupants.browserStorage._clear();
this.occupants.reset();
this.occupants.clearSession();
if (_converse.disco_entities) {
const disco_entity = _converse.disco_entities.get(this.get('jid'));
if (disco_entity) {
disco_entity.destroy();
await new Promise((success, error) => disco_entity.destroy({success, error}));
}
}
if (_converse.api.connection.connected()) {
@ -629,10 +639,11 @@ converse.plugins.add('converse-muc', {
this.removeHandlers();
},
close () {
async close () {
try {
this.features.destroy();
this.features.browserStorage._clear();
await new Promise((success, reject) => {
return this.features.destroy({success, 'error': (m, e) => reject(e)})
});
} catch (e) {
_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'));
this.save({
'name': identity && identity.get('name'),
'description': _.get(fields.findWhere({'var': "muc#roominfo_description"}), 'attributes.value')
});
'name': identity && identity.get('name'),
'description': _.get(fields.findWhere({'var': "muc#roominfo_description"}), 'attributes.value')
}
);
const features = await _converse.api.disco.getFeatures(this.get('jid'));
const attrs = Object.assign(
@ -857,6 +869,7 @@ converse.plugins.add('converse-muc', {
}
attrs[fieldname.replace('muc_', '')] = true;
});
attrs.description = _.get(fields.findWhere({'var': "muc#roominfo_description"}), 'attributes.value');
this.features.save(attrs);
},
@ -1012,33 +1025,6 @@ converse.plugins.add('converse-muc', {
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.
* @private
@ -1265,8 +1251,7 @@ converse.plugins.add('converse-muc', {
},
/**
* Given a presence stanza, update the occupant model
* based on its contents.
* Given a presence stanza, update the occupant model based on its contents.
* @private
* @method _converse.ChatRoom#updateOccupantsOnPresence
* @param { XMLElement } pres - The presence stanza
@ -1572,7 +1557,13 @@ converse.plugins.add('converse-muc', {
!this.ignorableCSN(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);
}
_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.updateOccupantsOnPresence(stanza);
this.saveAffiliationAndRole(stanza);
if (stanza.getAttribute('type') === 'unavailable') {
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);
}
});
/**
* 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) {
@ -2142,7 +2139,7 @@ converse.plugins.add('converse-muc', {
*/
return _converse.chatboxes
.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);
@ -2267,15 +2264,14 @@ converse.plugins.add('converse-muc', {
room && room.maybeShow(force);
return room;
} else {
return jids.map(async jid => {
const room = await _converse.api.rooms.create(jid, attrs);
room.maybeShow(force);
});
const rooms = await Promise.all(jids.map(jid => _converse.api.rooms.create(jid, attrs)));
rooms.forEach(r => r.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
* @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');
if (this.isSelf(jid)) { return; }
const contact = this.get(jid),
subscription = item.getAttribute("subscription"),
ask = item.getAttribute("ask"),
groups = _.map(item.getElementsByTagName('group'), Strophe.getText);
const contact = this.get(jid);
const subscription = item.getAttribute("subscription");
const ask = item.getAttribute("ask");
const groups = Array.from(item.getElementsByTagName('group')).map(e => e.textContent);
if (!contact) {
if ((subscription === "none" && ask === null) || (subscription === "remove")) {
return; // We're lazy when adding contacts.
@ -959,7 +958,6 @@ converse.plugins.add('converse-roster', {
if (_converse.shouldClearCache()) {
if (_converse.roster) {
_.invoke(_converse, 'roster.data.destroy');
_.invoke(_converse, 'roster.data.browserStorage._clear');
_converse.roster.clearSession();
delete _converse.roster;
}
@ -1024,7 +1022,7 @@ converse.plugins.add('converse-roster', {
* @namespace _converse.api.contacts
* @memberOf _converse.api
*/
'contacts': {
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', () => {
if (_converse.shouldClearCache() && _converse.xmppstatus) {
_converse.xmppstatus.destroy();
_converse.xmppstatus.browserStorage._clear();
delete _converse.xmppstatus;
}
});

View File

@ -1,9 +1,23 @@
{
"name": "@converse/headless",
"version": "4.2.0",
"version": "5.0.3",
"lockfileVersion": 1,
"requires": true,
"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": {
"version": "4.2.8",
"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",
"integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==",
"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",
"devDependencies": {
"backbone": "1.4",
"backbone.browserStorage": "0.0.5",
"backbone.browserStorage": "conversejs/backbone.browserStorage#2d0ceaa2f38eedc60122bb0aa23c826dc37a9194",
"filesize": "^4.1.2",
"jed": "1.1.1",
"localforage": "^1.7.3",
"lodash": "^4.17.15",
"pluggable.js": "2.0.1",
"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));
};
u.safeSave = function (model, attributes) {
u.safeSave = function (model, attributes, options) {
if (u.isPersistableModel(model)) {
model.save(attributes);
model.save(attributes, options);
} 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' || '' }}}">
<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> {{{o.label_rooms}}}</a>
<span class="fa {[ if (o.toggle_state === o._converse.OPENED) { ]} fa-caret-down {[ } else { ]} fa-caret-right {[ } ]}"></span>{{{o.label_rooms}}}</a>
<div class="items-list rooms-list open-rooms-list {{{ o.collapsed && 'collapsed' }}}">
{[o.rooms.forEach(function (room) { ]}

View File

@ -76,13 +76,37 @@
'Escalus, prince of Verona', 'The Nurse', 'Paris'
];
mock.pend_names = [
'Lord Capulet', 'Lady Capulet', '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'
'Lord Capulet', 'Guard', 'Servant'
];
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.groups = {
@ -195,42 +219,39 @@
'no_trimming': true,
'play_sounds': false,
'use_emojione': false,
'view_mode': mock.view_mode,
'view_mode': mock.view_mode
}, settings || {}));
_converse.ChatBoxViews.prototype.trimChat = function () {};
_converse.api.vcard.get = function (model, force) {
return new Promise(resolve => {
let jid;
if (_.isString(model)) {
jid = model;
} else if (!model.get('vcard_updated') || force) {
jid = model.get('jid') || model.get('muc_jid');
}
let fullname;
if (!jid || jid == 'romeo@montague.lit') {
jid = 'romeo@montague.lit';
fullname = 'Romeo Montague' ;
} else {
const name = jid.split('@')[0].replace(/\./g, ' ').split(' ');
const last = name.length-1;
name[0] = name[0].charAt(0).toUpperCase()+name[0].slice(1);
name[last] = name[last].charAt(0).toUpperCase()+name[last].slice(1);
fullname = name.join(' ');
}
const vcard = $iq().c('vCard').c('FN').t(fullname).nodeTree;
const result = {
'vcard': vcard,
'fullname': _.get(vcard.querySelector('FN'), 'textContent'),
'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'),
'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'),
'url': _.get(vcard.querySelector('URL'), 'textContent'),
'vcard_updated': dayjs().format(),
'vcard_error': undefined
};
resolve(result);
}).catch(e => _converse.log(e, Strophe.LogLevel.FATAL));
let jid;
if (_.isString(model)) {
jid = model;
} else if (!model.get('vcard_updated') || force) {
jid = model.get('jid') || model.get('muc_jid');
}
let fullname;
if (!jid || jid == 'romeo@montague.lit') {
jid = 'romeo@montague.lit';
fullname = 'Romeo Montague' ;
} else {
const name = jid.split('@')[0].replace(/\./g, ' ').split(' ');
const last = name.length-1;
name[0] = name[0].charAt(0).toUpperCase()+name[0].slice(1);
name[last] = name[last].charAt(0).toUpperCase()+name[last].slice(1);
fullname = name.join(' ');
}
const vcard = $iq().c('vCard').c('FN').t(fullname).nodeTree;
return {
'vcard': vcard,
'fullname': _.get(vcard.querySelector('FN'), 'textContent'),
'image': _.get(vcard.querySelector('PHOTO BINVAL'), 'textContent'),
'image_type': _.get(vcard.querySelector('PHOTO TYPE'), 'textContent'),
'url': _.get(vcard.querySelector('URL'), 'textContent'),
'vcard_updated': dayjs().format(),
'vcard_error': undefined
};
};
if (_.get(settings, 'auto_login') !== false) {
_converse.api.user.login('romeo@montague.lit/orchard', 'secret');

View File

@ -31,10 +31,10 @@ require.config(config);
var specs = [
"jasmine",
//"spec/transcripts",
// "spec/transcripts",
// "spec/profiling",
"spec/spoilers",
"spec/roomslist",
"spec/profiling",
"spec/utils",
"spec/converse",
"spec/bookmarks",

View File

@ -47,17 +47,14 @@
return req;
};
utils.closeAllChatBoxes = function (converse) {
var i, chatbox;
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.closeAllChatBoxes = function (_converse) {
return Promise.all(_converse.chatboxviews.map(view => view.close()));
};
utils.openControlBox = function () {
const toggle = document.querySelector(".toggle-controlbox");
utils.openControlBox = async function (_converse) {
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(toggle)) {
u.removeClass('hidden', toggle);
@ -68,9 +65,9 @@
};
utils.closeControlBox = function () {
var controlbox = document.querySelector("#controlbox");
const controlbox = document.querySelector("#controlbox");
if (u.isVisible(controlbox)) {
var button = controlbox.querySelector(".close-chatbox-button");
const button = controlbox.querySelector(".close-chatbox-button");
if (!_.isNull(button)) {
button.click();
}
@ -116,15 +113,19 @@
return views;
};
utils.openChatBoxFor = function (_converse, jid) {
utils.openChatBoxFor = async function (_converse, jid) {
await _converse.api.waitUntil('rosterContactsFetched');
_converse.roster.get(jid).trigger("open");
return u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
};
utils.openChatRoomViaModal = async function (_converse, jid, nick='') {
// Opens a new chatroom
utils.openControlBox(_converse);
const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
const model = await _converse.api.chats.open('controlbox');
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();
utils.closeControlBox(_converse);
const modal = roomspanel.add_room_modal;
@ -281,6 +282,7 @@
});
_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) {
@ -296,7 +298,8 @@
}).up()
.c('status').attrs({code:'110'});
_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=[]) {
@ -318,19 +321,36 @@
};
utils.clearChatBoxMessages = function (converse, jid) {
var view = converse.chatboxviews.get(jid);
const view = converse.chatboxviews.get(jid);
view.el.querySelector('.chat-content').innerHTML = '';
view.model.messages.reset();
view.model.messages.browserStorage._clear();
return view.model.messages.clearSession();
};
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
* for the user's roster.
*
* 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') {
names = mock.req_names;
subscription = 'none';
@ -347,33 +367,18 @@
requesting = false;
ask = null;
} else if (type === 'all') {
this.createContacts(converse, 'current')
.createContacts(converse, 'requesting')
.createContacts(converse, 'pending');
await this.createContacts(_converse, 'current');
await this.createContacts(_converse, 'requesting')
await this.createContacts(_converse, 'pending');
return this;
} else {
throw Error("Need to specify the type of contact to create");
}
if (typeof length === 'undefined') {
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;
const promises = names.slice(0, length).map(n => this.createContact(_converse, n, ask, requesting, subscription));
await Promise.all(promises);
};
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(() =>
_.filter(
_converse.connection.IQ_stanzas,
@ -388,44 +393,36 @@
'xmlns': 'jabber:iq:roster'
});
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', {
jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
name: include_nick ? name : undefined,
subscription: 'to'
}).up()
);
} 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'
subscription: 'none',
ask: 'subscribe'
}).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));
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) {
return $msg({
from: sender_jid,