Clear unread message counters when chat becomes visible

This commit is contained in:
JC Brand 2021-07-29 21:19:48 +02:00
parent ac36407d1d
commit d0627f800c
8 changed files with 264 additions and 203 deletions

View File

@ -209,7 +209,6 @@ eslint: node_modules
$(ESLINT) src/utils/*.js
$(ESLINT) src/headless/*.js
$(ESLINT) src/headless/utils/*.js
$(ESLINT) spec/ --global converse
.PHONY: check
check: eslint | dist/converse.js dist/converse.css

View File

@ -22,13 +22,13 @@ module.exports = function(config) {
served: true,
nocache: false
},
{ pattern: "node_modules/sinon/pkg/sinon.js", type: 'module' },
{ pattern: "spec/mock.js", type: 'module' },
{ pattern: "src/shared/tests/mock.js", type: 'module' },
{ pattern: "src/headless/plugins/caps/tests/caps.js", type: 'module' },
{ pattern: "src/headless/plugins/chat/tests/api.js", type: 'module' },
{ pattern: "src/headless/plugins/disco/tests/disco.js", type: 'module' },
{ pattern: "src/headless/plugins/muc/tests/affiliations.js", type: 'module' },
{ pattern: "src/headless/plugins/muc/tests/muc.js", type: 'module' },
{ pattern: "src/headless/plugins/muc/tests/pruning.js", type: 'module' },
{ pattern: "src/headless/plugins/muc/tests/registration.js", type: 'module' },
{ pattern: "src/headless/plugins/ping/tests/ping.js", type: 'module' },

View File

@ -1066,8 +1066,7 @@ const ChatBox = ModelWithContact.extend({
*/
isHidden () {
// Note: This methods gets overridden by converse-minimize
const hidden = _converse.isUniView() && this.get('hidden');
return hidden || this.isScrolledUp() || _converse.windowState === 'hidden';
return this.get('hidden') || this.isScrolledUp() || _converse.windowState === 'hidden';
},
/**
@ -1088,21 +1087,25 @@ const ChatBox = ModelWithContact.extend({
// when the user writes a message as opposed to when a
// message is received.
this.ui.set('scrolled', false);
} else if (this.isHidden() || this.ui.get('scrolled')) {
const settings = {
'num_unread': this.get('num_unread') + 1
};
if (this.get('num_unread') === 0) {
settings['first_unread_id'] = message.get('id');
}
this.save(settings);
} else if (this.isHidden()) {
this.incrementUnreadMsgsCounter(message);
} else {
this.sendMarkerForMessage(message);
}
}
},
clearUnreadMsgCounter() {
incrementUnreadMsgsCounter (message) {
const settings = {
'num_unread': this.get('num_unread') + 1
};
if (this.get('num_unread') === 0) {
settings['first_unread_id'] = message.get('id');
}
this.save(settings);
},
clearUnreadMsgCounter () {
if (this.get('num_unread') > 0) {
this.sendMarkerForMessage(this.messages.last());
}

View File

@ -257,16 +257,21 @@ const ChatRoomMixin = {
*/
async onHiddenChange () {
const conn_status = this.session.get('connection_status');
if (this.get('hidden') && conn_status === converse.ROOMSTATUS.ENTERED) {
if (api.settings.get('muc_subscribe_to_rai') && this.getOwnAffiliation() !== 'none') {
if (this.get('hidden')) {
if (conn_status === converse.ROOMSTATUS.ENTERED &&
api.settings.get('muc_subscribe_to_rai') &&
this.getOwnAffiliation() !== 'none') {
if (conn_status !== converse.ROOMSTATUS.DISCONNECTED) {
this.sendMarkerForLastMessage('received', true);
await this.leave();
}
this.enableRAI();
}
} else if (conn_status === converse.ROOMSTATUS.DISCONNECTED) {
this.rejoin();
} else {
if (conn_status === converse.ROOMSTATUS.DISCONNECTED) {
this.rejoin();
}
this.clearUnreadMsgCounter();
}
},
@ -2570,39 +2575,17 @@ const ChatRoomMixin = {
}
},
/**
* Given a newly received {@link _converse.Message} instance,
* update the unread counter if necessary.
* @private
* @method _converse.ChatRoom#handleUnreadMessage
* @param { XMLElement } - The <messsage> stanza
*/
handleUnreadMessage (message) {
if (!message?.get('body')) {
return;
incrementUnreadMsgsCounter (message) {
const settings = {
'num_unread_general': this.get('num_unread_general') + 1
};
if (this.get('num_unread_general') === 0) {
settings['first_unread_id'] = message.get('id');
}
if (u.isNewMessage(message)) {
if (message.get('sender') === 'me') {
// We remove the "scrolled" flag so that the chat area
// gets scrolled down. We always want to scroll down
// when the user writes a message as opposed to when a
// message is received.
this.ui.set('scrolled', false);
} else if (this.isHidden() || this.ui.get('scrolled')) {
const settings = {
'num_unread_general': this.get('num_unread_general') + 1
};
if (this.get('num_unread_general') === 0) {
settings['first_unread_id'] = message.get('id');
}
if (this.isUserMentioned(message)) {
settings.num_unread = this.get('num_unread') + 1;
}
this.save(settings);
} else {
this.sendMarkerForMessage(message);
}
if (this.isUserMentioned(message)) {
settings.num_unread = this.get('num_unread') + 1;
}
this.save(settings);
},
clearUnreadMsgCounter () {

View File

@ -0,0 +1,39 @@
/*global mock, converse */
const { u } = converse.env;
describe("Groupchats", function () {
it("keeps track of unread messages and mentions",
mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
const nick = 'romeo';
const muc_jid = 'lounge@montague.lit';
// Open a hidden room
await mock.openAndEnterChatRoom(_converse, muc_jid, nick, [], [], false, {'hidden': true});
const model = _converse.chatboxes.get(muc_jid);
_converse.connection._dataRecv(mock.createRequest(u.toStanza(`
<message xmlns="jabber:client" type="groupchat" id="1" to="${_converse.jid}" xml:lang="en" from="${muc_jid}/juliet">
<body>Romeo oh romeo</body>
</message>`)));
await u.waitUntil(() => model.messages.length);
expect(model.get('num_unread_general')).toBe(1);
expect(model.get('num_unread')).toBe(1);
_converse.connection._dataRecv(mock.createRequest(u.toStanza(`
<message xmlns="jabber:client" type="groupchat" id="2" to="${_converse.jid}" xml:lang="en" from="${muc_jid}/juliet">
<body>Wherefore art though?</body>
</message>`)));
await u.waitUntil(() => model.messages.length === 2);
expect(model.get('num_unread_general')).toBe(2);
expect(model.get('num_unread')).toBe(1);
// Check that unread counters are cleared when chat becomes visible
model.set('hidden', false);
expect(model.get('num_unread_general')).toBe(0);
expect(model.get('num_unread')).toBe(0);
}));
});

View File

@ -4584,55 +4584,6 @@ describe("Groupchats", function () {
}));
});
describe("The \"Groupchats\" section", function () {
it("shows the number of unread mentions received",
mock.initConverse(
[], {'allow_bookmarks': false},
async function (_converse) {
await mock.openControlBox(_converse);
const roomspanel = _converse.chatboxviews.get('controlbox').querySelector('converse-rooms-list');
expect(roomspanel.querySelectorAll('.available-room').length).toBe(0);
const muc_jid = 'kitchen@conference.shakespeare.lit';
const message = 'fires: Your attention is required';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'fires');
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => roomspanel.querySelectorAll('.available-room').length);
expect(roomspanel.querySelectorAll('.available-room').length).toBe(1);
expect(roomspanel.querySelectorAll('.msgs-indicator').length).toBe(0);
view.model.set({'minimized': true});
const nick = mock.chatroom_names[0];
await view.model.handleMessageStanza($msg({
from: muc_jid+'/'+nick,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(message).tree());
await u.waitUntil(() => view.model.messages.length);
expect(roomspanel.querySelectorAll('.available-room').length).toBe(1);
expect(roomspanel.querySelectorAll('.msgs-indicator').length).toBe(1);
expect(roomspanel.querySelector('.msgs-indicator').textContent.trim()).toBe('1');
await view.model.handleMessageStanza($msg({
'from': muc_jid+'/'+nick,
'id': u.getUniqueId(),
'to': 'romeo@montague.lit',
'type': 'groupchat'
}).c('body').t(message).tree());
await u.waitUntil(() => view.model.messages.length > 1);
expect(roomspanel.querySelectorAll('.available-room').length).toBe(1);
expect(roomspanel.querySelectorAll('.msgs-indicator').length).toBe(1);
expect(roomspanel.querySelector('.msgs-indicator').textContent.trim()).toBe('2');
view.model.set({'minimized': false});
expect(roomspanel.querySelectorAll('.available-room').length).toBe(1);
expect(roomspanel.querySelectorAll('.msgs-indicator').length).toBe(0);
}));
});
describe("A XEP-0085 Chat Status Notification", function () {
it("is is not sent out to a MUC if the user is a visitor in a moderated room",

View File

@ -1,6 +1,6 @@
/* global mock, converse */
const u = converse.env.utils;
const { $msg, u } = converse.env;
describe("A list of open groupchats", function () {
@ -46,6 +46,52 @@ describe("A list of open groupchats", function () {
expect(Array.from(list.classList).includes('hidden')).toBeTruthy();
}));
it("shows the number of unread mentions received",
mock.initConverse(
[], {'allow_bookmarks': false},
async function (_converse) {
await mock.openControlBox(_converse);
const roomspanel = _converse.chatboxviews.get('controlbox').querySelector('converse-rooms-list');
expect(roomspanel.querySelectorAll('.available-room').length).toBe(0);
const muc_jid = 'kitchen@conference.shakespeare.lit';
const message = 'fires: Your attention is required';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'fires');
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => roomspanel.querySelectorAll('.available-room').length);
expect(roomspanel.querySelectorAll('.available-room').length).toBe(1);
expect(roomspanel.querySelectorAll('.msgs-indicator').length).toBe(0);
view.model.set({'minimized': true});
const nick = mock.chatroom_names[0];
await view.model.handleMessageStanza($msg({
from: muc_jid+'/'+nick,
id: u.getUniqueId(),
to: 'romeo@montague.lit',
type: 'groupchat'
}).c('body').t(message).tree());
await u.waitUntil(() => view.model.messages.length);
expect(roomspanel.querySelectorAll('.available-room').length).toBe(1);
expect(roomspanel.querySelectorAll('.msgs-indicator').length).toBe(1);
expect(roomspanel.querySelector('.msgs-indicator').textContent.trim()).toBe('1');
await view.model.handleMessageStanza($msg({
'from': muc_jid+'/'+nick,
'id': u.getUniqueId(),
'to': 'romeo@montague.lit',
'type': 'groupchat'
}).c('body').t(message).tree());
await u.waitUntil(() => view.model.messages.length > 1);
expect(roomspanel.querySelectorAll('.available-room').length).toBe(1);
expect(roomspanel.querySelectorAll('.msgs-indicator').length).toBe(1);
expect(roomspanel.querySelector('.msgs-indicator').textContent.trim()).toBe('2');
view.model.set({'minimized': false});
expect(roomspanel.querySelectorAll('.available-room').length).toBe(1);
expect(roomspanel.querySelectorAll('.msgs-indicator').length).toBe(0);
}));
it("uses bookmarks to determine groupchat names",
mock.initConverse(
['chatBoxesFetched'],

View File

@ -1,14 +1,12 @@
const mock = {};
window.mock = mock;
let _converse;
const mock = {};
const converse = window.converse;
converse.load();
const { u, sizzle, Strophe, dayjs, $iq, $msg, $pres } = converse.env;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 7000;
mock.initConverse = function (promise_names=[], settings=null, func) {
function initConverse (promise_names=[], settings=null, func) {
if (typeof promise_names === "function") {
func = promise_names;
promise_names = []
@ -25,7 +23,7 @@ mock.initConverse = function (promise_names=[], settings=null, func) {
}
document.title = "Converse Tests";
await initConverse(settings);
await _initConverse(settings);
await Promise.all((promise_names || []).map(_converse.api.waitUntil));
try {
await func(_converse);
@ -34,9 +32,9 @@ mock.initConverse = function (promise_names=[], settings=null, func) {
fail(e);
}
}
};
}
mock.waitUntilDiscoConfirmed = async function (_converse, entity_jid, identities, features=[], items=[], type='info') {
async function waitUntilDiscoConfirmed (_converse, entity_jid, identities, features=[], items=[], type='info') {
const sel = `iq[to="${entity_jid}"] query[xmlns="http://jabber.org/protocol/disco#${type}"]`;
const iq = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(iq => sizzle(sel, iq).length).pop(), 300);
const stanza = $iq({
@ -49,10 +47,10 @@ mock.waitUntilDiscoConfirmed = async function (_converse, entity_jid, identities
identities?.forEach(identity => stanza.c('identity', {'category': identity.category, 'type': identity.type}).up());
features?.forEach(feature => stanza.c('feature', {'var': feature}).up());
items?.forEach(item => stanza.c('item', {'jid': item}).up());
_converse.connection._dataRecv(mock.createRequest(stanza));
_converse.connection._dataRecv(createRequest(stanza));
}
mock.createRequest = function (iq) {
function createRequest (iq) {
iq = typeof iq.tree == "function" ? iq.tree() : iq;
var req = new Strophe.Request(iq, function() {});
req.getResponse = function () {
@ -61,13 +59,13 @@ mock.createRequest = function (iq) {
return env;
};
return req;
};
}
mock.closeAllChatBoxes = function (_converse) {
function closeAllChatBoxes (_converse) {
return Promise.all(_converse.chatboxviews.map(view => view.close()));
};
}
mock.toggleControlBox = function () {
function toggleControlBox () {
const toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
@ -77,24 +75,24 @@ mock.toggleControlBox = function () {
}
}
mock.openControlBox = async function (_converse) {
async function openControlBox(_converse) {
const model = await _converse.api.controlbox.open();
await u.waitUntil(() => model.get('connected'));
mock.toggleControlBox();
toggleControlBox();
return this;
};
}
mock.closeControlBox = function () {
function closeControlBox () {
const controlbox = document.querySelector("#controlbox");
if (u.isVisible(controlbox)) {
const button = controlbox.querySelector(".close-chatbox-button");
(button !== null) && button.click();
}
return this;
};
}
mock.waitUntilBookmarksReturned = async function (_converse, bookmarks=[]) {
await mock.waitUntilDiscoConfirmed(
async function waitUntilBookmarksReturned (_converse, bookmarks=[]) {
await waitUntilDiscoConfirmed(
_converse, _converse.bare_jid,
[{'category': 'pubsub', 'type': 'pep'}],
['http://jabber.org/protocol/pubsub#publish-options']
@ -118,30 +116,30 @@ mock.waitUntilBookmarksReturned = async function (_converse, bookmarks=[]) {
'jid': bookmark.jid
}).c('nick').t(bookmark.nick).up().up()
});
_converse.connection._dataRecv(mock.createRequest(stanza));
_converse.connection._dataRecv(createRequest(stanza));
await _converse.api.waitUntil('bookmarksInitialized');
};
}
mock.openChatBoxes = function (converse, amount) {
function openChatBoxes (converse, amount) {
for (let i=0; i<amount; i++) {
const jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const jid = cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
converse.roster.get(jid).openChat();
}
};
}
mock.openChatBoxFor = async function (_converse, jid) {
async function openChatBoxFor (_converse, jid) {
await _converse.api.waitUntil('rosterContactsFetched');
_converse.roster.get(jid).openChat();
return u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
};
}
mock.openChatRoomViaModal = async function (_converse, jid, nick='') {
async function openChatRoomViaModal (_converse, jid, nick='') {
// Opens a new chatroom
const model = await _converse.api.controlbox.open('controlbox');
await u.waitUntil(() => model.get('connected'));
await mock.openControlBox(_converse);
await openControlBox(_converse);
document.querySelector('converse-rooms-list .show-add-muc-modal').click();
mock.closeControlBox(_converse);
closeControlBox(_converse);
const modal = _converse.api.modal.get('add-chatroom-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1500)
modal.el.querySelector('input[name="chatroom"]').value = jid;
@ -151,13 +149,13 @@ mock.openChatRoomViaModal = async function (_converse, jid, nick='') {
modal.el.querySelector('form input[type="submit"]').click();
await u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
return _converse.chatboxviews.get(jid);
};
}
mock.openChatRoom = function (_converse, room, server) {
function openChatRoom (_converse, room, server) {
return _converse.api.rooms.open(`${room}@${server}`);
};
}
mock.getRoomFeatures = async function (_converse, muc_jid, features=[]) {
async function getRoomFeatures (_converse, muc_jid, features=[]) {
const room = Strophe.getNodeFromJid(muc_jid);
muc_jid = muc_jid.toLowerCase();
const stanzas = _converse.connection.IQ_stanzas;
@ -178,7 +176,7 @@ mock.getRoomFeatures = async function (_converse, muc_jid, features=[]) {
'type': 'text'
}).up();
features = features.length ? features : mock.default_muc_features;
features = features.length ? features : default_muc_features;
features.forEach(f => features_stanza.c('feature', {'var': f}).up());
features_stanza.c('x', { 'xmlns':'jabber:x:data', 'type':'result'})
.c('field', {'var':'FORM_TYPE', 'type':'hidden'})
@ -187,11 +185,11 @@ mock.getRoomFeatures = async function (_converse, muc_jid, features=[]) {
.c('value').t('This is the description').up().up()
.c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'})
.c('value').t(0);
_converse.connection._dataRecv(mock.createRequest(features_stanza));
};
_converse.connection._dataRecv(createRequest(features_stanza));
}
mock.waitForReservedNick = async function (_converse, muc_jid, nick) {
async function waitForReservedNick (_converse, muc_jid, nick) {
const stanzas = _converse.connection.IQ_stanzas;
const selector = `iq[to="${muc_jid.toLowerCase()}"] query[node="x-roomuser-item"]`;
const iq = await u.waitUntil(() => stanzas.filter(s => sizzle(selector, s).length).pop());
@ -210,14 +208,14 @@ mock.waitForReservedNick = async function (_converse, muc_jid, nick) {
if (nick) {
stanza.c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
}
_converse.connection._dataRecv(mock.createRequest(stanza));
_converse.connection._dataRecv(createRequest(stanza));
if (nick) {
return u.waitUntil(() => nick);
}
};
}
mock.returnMemberLists = async function (_converse, muc_jid, members=[], affiliations=['member', 'owner', 'admin']) {
async function returnMemberLists (_converse, muc_jid, members=[], affiliations=['member', 'owner', 'admin']) {
if (affiliations.length === 0) {
return;
}
@ -240,7 +238,7 @@ mock.returnMemberLists = async function (_converse, muc_jid, members=[], affilia
'nick': m.nick
});
});
_converse.connection._dataRecv(mock.createRequest(member_list_stanza));
_converse.connection._dataRecv(createRequest(member_list_stanza));
}
if (affiliations.includes('admin')) {
@ -260,7 +258,7 @@ mock.returnMemberLists = async function (_converse, muc_jid, members=[], affilia
'nick': m.nick
});
});
_converse.connection._dataRecv(mock.createRequest(admin_list_stanza));
_converse.connection._dataRecv(createRequest(admin_list_stanza));
}
if (affiliations.includes('owner')) {
@ -280,12 +278,12 @@ mock.returnMemberLists = async function (_converse, muc_jid, members=[], affilia
'nick': m.nick
});
});
_converse.connection._dataRecv(mock.createRequest(owner_list_stanza));
_converse.connection._dataRecv(createRequest(owner_list_stanza));
}
return new Promise(resolve => _converse.api.listen.on('membersFetched', resolve));
};
}
mock.receiveOwnMUCPresence = async function (_converse, muc_jid, nick) {
async function receiveOwnMUCPresence (_converse, muc_jid, nick) {
const sent_stanzas = _converse.connection.sent_stanzas;
await u.waitUntil(() => sent_stanzas.filter(iq => sizzle('presence history', iq).length).pop());
const presence = $pres({
@ -299,20 +297,20 @@ mock.receiveOwnMUCPresence = async function (_converse, muc_jid, nick) {
role: 'moderator'
}).up()
.c('status').attrs({code:'110'});
_converse.connection._dataRecv(mock.createRequest(presence));
};
_converse.connection._dataRecv(createRequest(presence));
}
mock.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[], members=[], force_open=true, settings={}) {
async function openAndEnterChatRoom (_converse, muc_jid, nick, features=[], members=[], force_open=true, settings={}) {
const { api } = _converse;
muc_jid = muc_jid.toLowerCase();
const room_creation_promise = api.rooms.open(muc_jid, settings, force_open);
await mock.getRoomFeatures(_converse, muc_jid, features);
await mock.waitForReservedNick(_converse, muc_jid, nick);
await getRoomFeatures(_converse, muc_jid, features);
await waitForReservedNick(_converse, muc_jid, nick);
// The user has just entered the room (because join was called)
// and receives their own presence from the server.
// See example 24: https://xmpp.org/extensions/xep-0045.html#enter-pres
await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
await receiveOwnMUCPresence(_converse, muc_jid, nick);
await room_creation_promise;
const model = _converse.chatboxes.get(muc_jid);
@ -320,12 +318,12 @@ mock.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[
const affs = _converse.muc_fetch_members;
const all_affiliations = Array.isArray(affs) ? affs : (affs ? ['member', 'admin', 'owner'] : []);
await mock.returnMemberLists(_converse, muc_jid, members, all_affiliations);
await returnMemberLists(_converse, muc_jid, members, all_affiliations);
await model.messages.fetched;
return model;
};
}
mock.createContact = async function (_converse, name, ask, requesting, subscription) {
async function createContact (_converse, name, ask, requesting, subscription) {
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
if (_converse.roster.get(jid)) {
return Promise.resolve();
@ -340,9 +338,9 @@ mock.createContact = async function (_converse, name, ask, requesting, subscript
}, {success, error});
});
return contact;
};
}
mock.createContacts = async function (_converse, type, length) {
async function createContacts (_converse, type, length) {
/* Create current (as opposed to requesting or pending) contacts
* for the user's roster.
*
@ -351,17 +349,17 @@ mock.createContacts = async function (_converse, type, length) {
await _converse.api.waitUntil('rosterContactsFetched');
let names, subscription, requesting, ask;
if (type === 'requesting') {
names = mock.req_names;
names = req_names;
subscription = 'none';
requesting = true;
ask = null;
} else if (type === 'pending') {
names = mock.pend_names;
names = pend_names;
subscription = 'none';
requesting = false;
ask = 'subscribe';
} else if (type === 'current') {
names = mock.cur_names;
names = cur_names;
subscription = 'both';
requesting = false;
ask = null;
@ -375,9 +373,9 @@ mock.createContacts = async function (_converse, type, length) {
}
const promises = names.slice(0, length).map(n => this.createContact(_converse, n, ask, requesting, subscription));
await Promise.all(promises);
};
}
mock.waitForRoster = async function (_converse, type='current', length=-1, include_nick=true, grouped=true) {
async function waitForRoster (_converse, type='current', length=-1, include_nick=true, grouped=true) {
const s = `iq[type="get"] query[xmlns="${Strophe.NS.ROSTER}"]`;
const iq = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(iq => sizzle(s, iq).length).pop());
@ -389,8 +387,7 @@ mock.waitForRoster = async function (_converse, type='current', length=-1, inclu
'xmlns': 'jabber:iq:roster'
});
if (type === 'pending' || type === 'all') {
const pend_names = (length > -1) ? mock.pend_names.slice(0, length) : mock.pend_names;
pend_names.map(name =>
((length > -1) ? pend_names.slice(0, length) : pend_names).map(name =>
result.c('item', {
jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit',
name: include_nick ? name : undefined,
@ -400,7 +397,7 @@ mock.waitForRoster = async function (_converse, type='current', length=-1, inclu
);
}
if (type === 'current' || type === 'all') {
const cur_names = Object.keys(mock.current_contacts_map);
const cur_names = Object.keys(current_contacts_map);
const names = (length > -1) ? cur_names.slice(0, length) : cur_names;
names.forEach(name => {
result.c('item', {
@ -410,16 +407,16 @@ mock.waitForRoster = async function (_converse, type='current', length=-1, inclu
ask: null
});
if (grouped) {
mock.current_contacts_map[name].forEach(g => result.c('group').t(g).up());
current_contacts_map[name].forEach(g => result.c('group').t(g).up());
}
result.up();
});
}
_converse.connection._dataRecv(mock.createRequest(result));
_converse.connection._dataRecv(createRequest(result));
await _converse.api.waitUntil('rosterContactsFetched');
};
}
mock.createChatMessage = function (_converse, sender_jid, message) {
function createChatMessage (_converse, sender_jid, message) {
return $msg({
from: sender_jid,
to: _converse.connection.jid,
@ -431,7 +428,7 @@ mock.createChatMessage = function (_converse, sender_jid, message) {
.c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
}
mock.sendMessage = async function (view, message) {
async function sendMessage (view, message) {
const promise = new Promise(resolve => view.model.messages.once('rendered', resolve));
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = message;
@ -442,8 +439,7 @@ mock.sendMessage = async function (view, message) {
keyCode: 13
});
return promise;
};
}
window.libsignal = {
'SignalProtocolAddress': function (name, device_id) {
@ -500,9 +496,9 @@ window.libsignal = {
});
}
}
};
}
mock.default_muc_features = [
const default_muc_features = [
'http://jabber.org/protocol/muc',
'jabber:iq:register',
Strophe.NS.SID,
@ -515,16 +511,16 @@ mock.default_muc_features = [
'muc_anonymous'
];
mock.view_mode = 'overlayed';
const view_mode = 'overlayed';
// Names from http://www.fakenamegenerator.com/
mock.req_names = [
const req_names = [
'Escalus, prince of Verona', 'The Nurse', 'Paris'
];
mock.pend_names = [
const pend_names = [
'Lord Capulet', 'Guard', 'Servant'
];
mock.current_contacts_map = {
const current_contacts_map = {
'Mercutio': ['Colleagues', 'friends & acquaintences'],
'Juliet Capulet': ['friends & acquaintences'],
'Lady Montague': ['Colleagues', 'Family'],
@ -540,9 +536,9 @@ mock.current_contacts_map = {
'Gregory': ['friends & acquaintences'],
'Potpan': [],
'Friar John': []
};
}
const map = mock.current_contacts_map;
const map = current_contacts_map;
const groups_map = {};
Object.keys(map).forEach(k => {
const groups = map[k].length ? map[k] : ["Ungrouped"];
@ -550,20 +546,19 @@ Object.keys(map).forEach(k => {
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;
const cur_names = Object.keys(current_contacts_map);
const num_contacts = req_names.length + pend_names.length + cur_names.length;
mock.groups = {
const groups = {
'colleagues': 3,
'friends & acquaintences': 3,
'Family': 4,
'ænemies': 3,
'Ungrouped': 2
};
}
mock.chatroom_names = [
const chatroom_names = [
'Dyon van de Wege',
'Thomas Kalb',
'Dirk Theissen',
@ -571,19 +566,20 @@ mock.chatroom_names = [
'Ka Lek',
'Anne Ebersbacher'
];
// TODO: need to also test other roles and affiliations
mock.chatroom_roles = {
const chatroom_roles = {
'Anne Ebersbacher': { affiliation: "owner", role: "moderator" },
'Dirk Theissen': { affiliation: "admin", role: "moderator" },
'Dyon van de Wege': { affiliation: "member", role: "occupant" },
'Felix Hofmann': { affiliation: "member", role: "occupant" },
'Ka Lek': { affiliation: "member", role: "occupant" },
'Thomas Kalb': { affiliation: "member", role: "occupant" }
};
}
mock.event = {
const event = {
'preventDefault': function () {}
};
}
function clearIndexedDB () {
const promise = u.getOpenPromise();
@ -615,7 +611,7 @@ function clearStores () {
window.sessionStorage.removeItem(cache_key+'fetched');
}
const initConverse = async (settings) => {
async function _initConverse (settings) {
clearStores();
await clearIndexedDB();
@ -631,7 +627,7 @@ const initConverse = async (settings) => {
'no_trimming': true,
'play_sounds': false,
'use_emojione': false,
'view_mode': mock.view_mode
'view_mode': view_mode
}, settings || {}));
window._converse = _converse;
@ -674,7 +670,7 @@ const initConverse = async (settings) => {
}
mock.deviceListFetched = async function deviceListFetched (_converse, jid) {
async function deviceListFetched (_converse, jid) {
const selector = `iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.devicelist"]`;
const stanza = await u.waitUntil(
() => Array.from(_converse.connection.IQ_stanzas).filter(iq => iq.querySelector(selector)).pop()
@ -683,30 +679,30 @@ mock.deviceListFetched = async function deviceListFetched (_converse, jid) {
return stanza;
}
mock.ownDeviceHasBeenPublished = function ownDeviceHasBeenPublished (_converse) {
function ownDeviceHasBeenPublished (_converse) {
return Array.from(_converse.connection.IQ_stanzas).filter(
iq => iq.querySelector('iq[from="'+_converse.bare_jid+'"] publish[node="eu.siacs.conversations.axolotl.devicelist"]')
).pop();
}
mock.bundleHasBeenPublished = function bundleHasBeenPublished (_converse) {
function bundleHasBeenPublished (_converse) {
const selector = 'publish[node="eu.siacs.conversations.axolotl.bundles:123456789"]';
return Array.from(_converse.connection.IQ_stanzas).filter(iq => iq.querySelector(selector)).pop();
}
mock.bundleFetched = function bundleFetched (_converse, jid, device_id) {
function bundleFetched (_converse, jid, device_id) {
return Array.from(_converse.connection.IQ_stanzas).filter(
iq => iq.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.bundles:${device_id}"]`)
).pop();
}
mock.initializedOMEMO = async function initializedOMEMO (_converse) {
await mock.waitUntilDiscoConfirmed(
async function initializedOMEMO (_converse) {
await waitUntilDiscoConfirmed(
_converse, _converse.bare_jid,
[{'category': 'pubsub', 'type': 'pep'}],
['http://jabber.org/protocol/pubsub#publish-options']
);
let iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, _converse.bare_jid));
let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
let stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
@ -717,23 +713,67 @@ mock.initializedOMEMO = async function initializedOMEMO (_converse) {
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '482886413b977930064a5888b92134fe'});
_converse.connection._dataRecv(mock.createRequest(stanza));
iq_stanza = await u.waitUntil(() => mock.ownDeviceHasBeenPublished(_converse))
_converse.connection._dataRecv(createRequest(stanza));
iq_stanza = await u.waitUntil(() => ownDeviceHasBeenPublished(_converse))
stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(mock.createRequest(stanza));
_converse.connection._dataRecv(createRequest(stanza));
iq_stanza = await u.waitUntil(() => mock.bundleHasBeenPublished(_converse))
iq_stanza = await u.waitUntil(() => bundleHasBeenPublished(_converse))
stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(mock.createRequest(stanza));
_converse.connection._dataRecv(createRequest(stanza));
await _converse.api.waitUntil('OMEMOInitialized');
}
Object.assign(mock, {
bundleFetched,
bundleHasBeenPublished,
chatroom_names,
chatroom_roles,
closeAllChatBoxes,
closeControlBox,
createChatMessage,
createContact,
createContacts,
createRequest,
cur_names,
current_contacts_map,
default_muc_features,
deviceListFetched,
event,
getRoomFeatures,
groups,
groups_map,
initConverse,
initializedOMEMO,
num_contacts,
openAndEnterChatRoom,
openChatBoxFor,
openChatBoxes,
openChatRoom,
openChatRoomViaModal,
openControlBox,
ownDeviceHasBeenPublished,
pend_names,
receiveOwnMUCPresence,
req_names,
returnMemberLists,
sendMessage,
toggleControlBox,
view_mode,
waitForReservedNick,
waitForRoster,
waitUntilBookmarksReturned,
waitUntilDiscoConfirmed
});
window.mock = mock;