/*global mock, converse, _ */
const $pres = converse.env.$pres;
const $iq = converse.env.$iq;
const $msg = converse.env.$msg;
const Model = converse.env.Model;
const Strophe = converse.env.Strophe;
const Promise = converse.env.Promise;
const sizzle = converse.env.sizzle;
const u = converse.env.utils;
describe("Groupchats", function () {
describe("The \"rooms\" API", function () {
it("has a method 'close' which closes rooms by JID or all rooms when called with no arguments",
mock.initConverse([], {}, async function (done, _converse) {
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
_converse.connection.IQ_stanzas = [];
await mock.openAndEnterChatRoom(_converse, 'leisure@montague.lit', 'romeo');
_converse.connection.IQ_stanzas = [];
await mock.openAndEnterChatRoom(_converse, 'news@montague.lit', 'romeo');
expect(u.isVisible(_converse.chatboxviews.get('lounge@montague.lit'))).toBeTruthy();
expect(u.isVisible(_converse.chatboxviews.get('leisure@montague.lit'))).toBeTruthy();
expect(u.isVisible(_converse.chatboxviews.get('news@montague.lit'))).toBeTruthy();
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'))).toBeTruthy();
expect(u.isVisible(_converse.chatboxviews.get('news@montague.lit'))).toBeTruthy();
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();
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
await mock.openAndEnterChatRoom(_converse, 'leisure@montague.lit', 'romeo');
expect(u.isVisible(_converse.chatboxviews.get('lounge@montague.lit'))).toBeTruthy();
expect(u.isVisible(_converse.chatboxviews.get('leisure@montague.lit'))).toBeTruthy();
await _converse.api.roomviews.close();
expect(_converse.chatboxviews.get('lounge@montague.lit')).toBeUndefined();
expect(_converse.chatboxviews.get('leisure@montague.lit')).toBeUndefined();
done();
}));
it("has a method 'get' which returns a wrapped groupchat (if it exists)",
mock.initConverse([], {}, async function (done, _converse) {
await mock.waitForRoster(_converse, 'current');
const rosterview = document.querySelector('converse-roster');
await u.waitUntil(() => rosterview.querySelectorAll('.roster-group .group-toggle').length, 300);
let muc_jid = 'chillout@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
let room = await _converse.api.rooms.get(muc_jid);
expect(room instanceof Object).toBeTruthy();
let chatroomview = _converse.chatboxviews.get(muc_jid);
expect(chatroomview.is_chatroom).toBeTruthy();
expect(u.isVisible(chatroomview)).toBeTruthy();
await chatroomview.close();
// Test with mixed case
muc_jid = 'Leisure@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
room = await _converse.api.rooms.get(muc_jid);
expect(room instanceof Object).toBeTruthy();
chatroomview = _converse.chatboxviews.get(muc_jid.toLowerCase());
expect(u.isVisible(chatroomview)).toBeTruthy();
muc_jid = 'leisure@montague.lit';
room = await _converse.api.rooms.get(muc_jid);
expect(room instanceof Object).toBeTruthy();
chatroomview = _converse.chatboxviews.get(muc_jid.toLowerCase());
expect(u.isVisible(chatroomview)).toBeTruthy();
muc_jid = 'leiSure@montague.lit';
room = await _converse.api.rooms.get(muc_jid);
expect(room instanceof Object).toBeTruthy();
chatroomview = _converse.chatboxviews.get(muc_jid.toLowerCase());
expect(u.isVisible(chatroomview)).toBeTruthy();
chatroomview.close();
// Non-existing room
muc_jid = 'chillout2@montague.lit';
room = await _converse.api.rooms.get(muc_jid);
expect(room).toBe(null);
done();
}));
it("has a method 'open' which opens (optionally configures) and returns a wrapped chat box",
mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
// Mock 'getDiscoInfo', otherwise the room won't be
// displayed as it waits first for the features to be returned
// (when it's a new room being created).
spyOn(_converse.ChatRoom.prototype, 'getDiscoInfo').and.callFake(() => Promise.resolve());
let jid = 'lounge@montague.lit';
let chatroomview, IQ_id;
await mock.openControlBox(_converse);
await mock.waitForRoster(_converse, 'current');
const rosterview = document.querySelector('converse-roster');
await u.waitUntil(() => rosterview.querySelectorAll('.roster-group .group-toggle').length);
let room = await _converse.api.rooms.open(jid);
// Test on groupchat that's not yet open
expect(room instanceof Model).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid);
expect(chatroomview.is_chatroom).toBeTruthy();
await u.waitUntil(() => u.isVisible(chatroomview));
// Test again, now that the room exists.
room = await _converse.api.rooms.open(jid);
expect(room instanceof Model).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid);
expect(chatroomview.is_chatroom).toBeTruthy();
expect(u.isVisible(chatroomview)).toBeTruthy();
await chatroomview.close();
// Test with mixed case in JID
jid = 'Leisure@montague.lit';
room = await _converse.api.rooms.open(jid);
expect(room instanceof Model).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
await u.waitUntil(() => u.isVisible(chatroomview));
jid = 'leisure@montague.lit';
room = await _converse.api.rooms.open(jid);
expect(room instanceof Model).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
await u.waitUntil(() => u.isVisible(chatroomview));
jid = 'leiSure@montague.lit';
room = await _converse.api.rooms.open(jid);
expect(room instanceof Model).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
await u.waitUntil(() => u.isVisible(chatroomview));
chatroomview.close();
_converse.muc_instant_rooms = false;
const sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
IQ_id = sendIQ.bind(this)(iq, callback, errback);
});
// Test with configuration
room = await _converse.api.rooms.open('room@conference.example.org', {
'nick': 'some1',
'auto_configure': true,
'roomconfig': {
'getmemberlist': ['moderator', 'participant'],
'changesubject': false,
'membersonly': true,
'persistentroom': true,
'publicroom': true,
'roomdesc': 'Welcome to this groupchat',
'whois': 'anyone'
}
});
expect(room instanceof Model).toBeTruthy();
chatroomview = _converse.chatboxviews.get('room@conference.example.org');
// We pretend this is a new room, so no disco info is returned.
const features_stanza = $iq({
from: 'room@conference.example.org',
'id': IQ_id,
'to': 'romeo@montague.lit/desktop',
'type': 'error'
}).c('error', {'type': 'cancel'})
.c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
_converse.connection._dataRecv(mock.createRequest(features_stanza));
/*
*
*
*
*
*
*
*/
const presence = $pres({
from:'room@conference.example.org/some1',
to:'romeo@montague.lit/pda'
})
.c('x', {xmlns:'http://jabber.org/protocol/muc#user'})
.c('item', {
affiliation: 'owner',
jid: 'romeo@montague.lit/pda',
role: 'moderator'
}).up()
.c('status', {code:'110'}).up()
.c('status', {code:'201'});
_converse.connection._dataRecv(mock.createRequest(presence));
expect(_converse.connection.sendIQ).toHaveBeenCalled();
const IQ_stanzas = _converse.connection.IQ_stanzas;
const iq = await u.waitUntil(() => IQ_stanzas.filter(s => s.querySelector(`query[xmlns="${Strophe.NS.MUC_OWNER}"]`)).pop());
expect(Strophe.serialize(iq)).toBe(
``+
``);
const node = u.toStanza(`
Configuration for room@conference.example.org
Complete and submit this form to configure the room.
http://jabber.org/protocol/muc#roomconfig
Room
1
moderator
participant
visitor
20
`);
spyOn(chatroomview.model, 'sendConfiguration').and.callThrough();
_converse.connection._dataRecv(mock.createRequest(node));
await u.waitUntil(() => chatroomview.model.sendConfiguration.calls.count() === 1);
const sent_stanza = IQ_stanzas.filter(s => s.getAttribute('type') === 'set').pop();
expect(sizzle('field[var="muc#roomconfig_roomname"] value', sent_stanza).pop().textContent.trim()).toBe('Room');
expect(sizzle('field[var="muc#roomconfig_roomdesc"] value', sent_stanza).pop().textContent.trim()).toBe('Welcome to this groupchat');
expect(sizzle('field[var="muc#roomconfig_persistentroom"] value', sent_stanza).pop().textContent.trim()).toBe('1');
expect(sizzle('field[var="muc#roomconfig_getmemberlist"] value', sent_stanza).map(e => e.textContent.trim()).join(' ')).toBe('moderator participant');
expect(sizzle('field[var="muc#roomconfig_publicroom"] value ', sent_stanza).pop().textContent.trim()).toBe('1');
expect(sizzle('field[var="muc#roomconfig_changesubject"] value', sent_stanza).pop().textContent.trim()).toBe('0');
expect(sizzle('field[var="muc#roomconfig_whois"] value ', sent_stanza).pop().textContent.trim()).toBe('anyone');
expect(sizzle('field[var="muc#roomconfig_membersonly"] value', sent_stanza).pop().textContent.trim()).toBe('1');
expect(sizzle('field[var="muc#roomconfig_historylength"] value', sent_stanza).pop().textContent.trim()).toBe('20');
done();
}));
});
describe("An instant groupchat", function () {
it("will be created when muc_instant_rooms is set to true",
mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
let IQ_stanzas = _converse.connection.IQ_stanzas;
const muc_jid = 'lounge@montague.lit';
const nick = 'nicky';
await mock.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
const disco_selector = `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`;
const stanza = await u.waitUntil(() => IQ_stanzas.filter(iq => iq.querySelector(disco_selector)).pop());
// We pretend this is a new room, so no disco info is returned.
const features_stanza = $iq({
'from': 'lounge@montague.lit',
'id': stanza.getAttribute('id'),
'to': 'romeo@montague.lit/desktop',
'type': 'error'
}).c('error', {'type': 'cancel'})
.c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
_converse.connection._dataRecv(mock.createRequest(features_stanza));
const view = _converse.chatboxviews.get('lounge@montague.lit');
spyOn(view.model, 'join').and.callThrough();
await mock.waitForReservedNick(_converse, muc_jid, '');
const input = await u.waitUntil(() => view.querySelector('input[name="nick"]'), 1000);
expect(view.model.session.get('connection_status')).toBe(converse.ROOMSTATUS.NICKNAME_REQUIRED);
input.value = nick;
view.querySelector('input[type=submit]').click();
expect(view.model.join).toHaveBeenCalled();
_converse.connection.IQ_stanzas = [];
await mock.getRoomFeatures(_converse, muc_jid);
await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING);
await mock.receiveOwnMUCPresence(_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
//
/*
*
*
*
*
*
*
*/
const presence = $pres({
to:'romeo@montague.lit/orchard',
from:'lounge@montague.lit/nicky',
id:'5025e055-036c-4bc5-a227-706e7e352053'
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
.c('item').attrs({
affiliation: 'owner',
jid: 'romeo@montague.lit/orchard',
role: 'moderator'
}).up()
.c('status').attrs({code:'110'}).up()
.c('status').attrs({code:'201'}).nodeTree;
_converse.connection._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED);
await mock.returnMemberLists(_converse, muc_jid);
const num_info_msgs = await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-info').length);
expect(num_info_msgs).toBe(1);
const info_texts = Array.from(view.querySelectorAll('.chat-content .chat-info')).map(e => e.textContent.trim());
expect(info_texts[0]).toBe('A new groupchat has been created');
const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent);
expect(csntext.trim()).toEqual("nicky has entered the groupchat");
// An instant room is created by saving the default configuratoin.
//
/*
*
*
*/
const selector = `query[xmlns="${Strophe.NS.MUC_OWNER}"]`;
IQ_stanzas = _converse.connection.IQ_stanzas;
const iq = await u.waitUntil(() => IQ_stanzas.filter(s => s.querySelector(selector)).pop());
expect(Strophe.serialize(iq)).toBe(
``+
``+
``);
done();
}));
});
describe("A Groupchat", function () {
it("Can be configured to show cached messages before being joined",
mock.initConverse(['discoInitialized'],
{
'muc_show_logs_before_join': true,
'archived_messages_page_size': 2,
'muc_nickname_from_jid': false,
'muc_clear_messages_on_leave': false,
}, async function (done, _converse) {
const { api } = _converse;
const muc_jid = 'orchard@chat.shakespeare.lit';
const nick = 'romeo';
api.rooms.open(muc_jid);
await mock.getRoomFeatures(_converse, muc_jid);
await mock.waitForReservedNick(_converse, muc_jid);
const view = _converse.chatboxviews.get(muc_jid);
await view.model.messages.fetched;
view.model.messages.create({
'type': 'groupchat',
'to': muc_jid,
'from': `${_converse.bare_jid}/orchard`,
'body': 'Hello world',
'message': 'Hello world',
'time': '2021-02-02T12:00:00Z'
});
expect(view.model.session.get('connection_status')).toBe(converse.ROOMSTATUS.NICKNAME_REQUIRED);
await u.waitUntil(() => view.querySelectorAll('converse-chat-message').length === 1);
const sel = 'converse-message-history converse-chat-message .chat-msg__text';
await u.waitUntil(() => view.querySelector(sel)?.textContent.trim());
expect(view.querySelector(sel).textContent.trim()).toBe('Hello world')
view.querySelector('[name="nick"]').value = nick;
view.querySelector('.muc-nickname-form input[type="submit"]').click();
_converse.connection.IQ_stanzas = [];
await mock.getRoomFeatures(_converse, muc_jid);
await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING);
await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
return done();
}));
it("maintains its state across reloads",
mock.initConverse([], {
'clear_messages_on_reconnection': true,
'enable_smacks': false
}, async function (done, _converse) {
const nick = 'romeo';
const sent_IQs = _converse.connection.IQ_stanzas;
const muc_jid = 'lounge@montague.lit'
await mock.openAndEnterChatRoom(_converse, muc_jid, nick, [], []);
const view = _converse.chatboxviews.get(muc_jid);
let iq_get = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq query[xmlns="${Strophe.NS.MAM}"]`)).pop());
const first_msg_id = _converse.connection.getUniqueId();
const last_msg_id = _converse.connection.getUniqueId();
let message = u.toStanza(
`
1st Message
`);
_converse.connection._dataRecv(mock.createRequest(message));
message = u.toStanza(
`
2nd Message
`);
_converse.connection._dataRecv(mock.createRequest(message));
const result = u.toStanza(
`
${first_msg_id}
${last_msg_id}
2
`);
_converse.connection._dataRecv(mock.createRequest(result));
await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2);
while (sent_IQs.length) { sent_IQs.pop(); } // Clear so that we don't match the older query
await _converse.api.connection.reconnect();
await mock.getRoomFeatures(_converse, muc_jid, []);
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
// 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);
message = u.toStanza(`
Wherefore art though?
`);
_converse.connection._dataRecv(mock.createRequest(message));
message = u.toStanza(`
`);
_converse.connection._dataRecv(mock.createRequest(message));
const affs = _converse.muc_fetch_members;
const all_affiliations = Array.isArray(affs) ? affs : (affs ? ['member', 'admin', 'owner'] : []);
await mock.returnMemberLists(_converse, muc_jid, [], all_affiliations);
iq_get = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq query[xmlns="${Strophe.NS.MAM}"]`)).pop());
expect(Strophe.serialize(iq_get)).toBe(
``+
``+
``+
`urn:xmpp:mam:2`+
``+
`50`+
``+
``);
done();
}));
describe("upon being entered", function () {
it("will fetch the member list if muc_fetch_members is true",
mock.initConverse([], {'muc_fetch_members': true}, async function (done, _converse) {
let sent_IQs = _converse.connection.IQ_stanzas;
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
let view = _converse.chatboxviews.get(muc_jid);
expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation]')).length).toBe(3);
// Check in reverse order that we requested all three lists
const owner_iq = sent_IQs.pop();
expect(Strophe.serialize(owner_iq)).toBe(
``+
` `+
``);
const admin_iq = sent_IQs.pop();
expect(Strophe.serialize(admin_iq)).toBe(
``+
` `+
``);
const member_iq = sent_IQs.pop();
expect(Strophe.serialize(member_iq)).toBe(
``+
` `+
``);
view.close();
_converse.connection.IQ_stanzas = [];
sent_IQs = _converse.connection.IQ_stanzas;
_converse.muc_fetch_members = false;
await mock.openAndEnterChatRoom(_converse, 'orchard@montague.lit', 'romeo');
view = _converse.chatboxviews.get('orchard@montague.lit');
expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation]')).length).toBe(0);
await view.close();
_converse.connection.IQ_stanzas = [];
sent_IQs = _converse.connection.IQ_stanzas;
_converse.muc_fetch_members = ['admin'];
await mock.openAndEnterChatRoom(_converse, 'courtyard@montague.lit', 'romeo');
view = _converse.chatboxviews.get('courtyard@montague.lit');
expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation]')).length).toBe(1);
expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation="admin"]')).length).toBe(1);
view.close();
_converse.connection.IQ_stanzas = [];
sent_IQs = _converse.connection.IQ_stanzas;
_converse.muc_fetch_members = ['owner'];
await mock.openAndEnterChatRoom(_converse, 'garden@montague.lit', 'romeo');
view = _converse.chatboxviews.get('garden@montague.lit');
expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation]')).length).toBe(1);
expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation="owner"]')).length).toBe(1);
view.close();
done();
}));
describe("when fetching the member lists", function () {
it("gracefully handles being forbidden from fetching the lists for certain affiliations",
mock.initConverse([], {'muc_fetch_members': true}, async function (done, _converse) {
const sent_IQs = _converse.connection.IQ_stanzas;
const muc_jid = 'lounge@montague.lit';
const features = [
'http://jabber.org/protocol/muc',
'jabber:iq:register',
'muc_hidden',
'muc_membersonly',
'muc_passwordprotected',
Strophe.NS.MAM,
Strophe.NS.SID
];
const nick = 'romeo';
await _converse.api.rooms.open(muc_jid);
await mock.getRoomFeatures(_converse, muc_jid, features);
await mock.waitForReservedNick(_converse, muc_jid, nick);
mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
// Check in reverse order that we requested all three lists
const owner_iq = sent_IQs.pop();
expect(Strophe.serialize(owner_iq)).toBe(
``+
` `+
``);
const admin_iq = sent_IQs.pop();
expect(Strophe.serialize(admin_iq)).toBe(
``+
` `+
``);
const member_iq = sent_IQs.pop();
expect(Strophe.serialize(member_iq)).toBe(
``+
` `+
``);
// It might be that the user is not allowed to fetch certain lists.
let err_stanza = u.toStanza(
`
`);
_converse.connection._dataRecv(mock.createRequest(err_stanza));
err_stanza = u.toStanza(
`
`);
_converse.connection._dataRecv(mock.createRequest(err_stanza));
// Now the service sends the member lists to the user
const member_list_stanza = $iq({
'from': muc_jid,
'id': member_iq.getAttribute('id'),
'to': 'romeo@montague.lit/orchard',
'type': 'result'
}).c('query', {'xmlns': Strophe.NS.MUC_ADMIN})
.c('item', {
'affiliation': 'member',
'jid': 'hag66@shakespeare.lit',
'nick': 'thirdwitch',
'role': 'participant'
});
_converse.connection._dataRecv(mock.createRequest(member_list_stanza));
await u.waitUntil(() => view.model.occupants.length > 1);
expect(view.model.occupants.length).toBe(2);
// The existing owner occupant should not have their
// affiliation removed due to the owner list
// not being returned (forbidden err).
expect(view.model.occupants.findWhere({'jid': _converse.bare_jid}).get('affiliation')).toBe('owner');
expect(view.model.occupants.findWhere({'jid': 'hag66@shakespeare.lit'}).get('affiliation')).toBe('member');
done();
}));
});
});
describe("topic", function () {
it("is shown the header", mock.initConverse([], {}, async function (done, _converse) {
await mock.openAndEnterChatRoom(_converse, 'jdev@conference.jabber.org', 'jc');
const text = 'Jabber/XMPP Development | RFCs and Extensions: https://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org';
let stanza = u.toStanza(`
${text}
`);
_converse.connection._dataRecv(mock.createRequest(stanza));
const view = _converse.chatboxviews.get('jdev@conference.jabber.org');
await new Promise(resolve => view.model.once('change:subject', resolve));
const head_desc = await u.waitUntil(() => view.querySelector('.chat-head__desc'), 1000);
expect(head_desc?.textContent.trim()).toBe(text);
stanza = u.toStanza(
`
This is a message subject
This is a message
`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(sizzle('.chat-msg__subject', view).length).toBe(1);
expect(sizzle('.chat-msg__subject', view).pop().textContent.trim()).toBe('This is a message subject');
expect(sizzle('.chat-msg__text').length).toBe(1);
expect(sizzle('.chat-msg__text').pop().textContent.trim()).toBe('This is a message');
expect(view.querySelector('.chat-head__desc').textContent.trim()).toBe(text);
done();
}));
it("can be toggled by the user", mock.initConverse([], {}, async function (done, _converse) {
await mock.openAndEnterChatRoom(_converse, 'jdev@conference.jabber.org', 'jc');
const text = 'Jabber/XMPP Development | RFCs and Extensions: https://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org';
let stanza = u.toStanza(`
${text}
`);
_converse.connection._dataRecv(mock.createRequest(stanza));
const view = _converse.chatboxviews.get('jdev@conference.jabber.org');
await new Promise(resolve => view.model.once('change:subject', resolve));
const head_desc = await u.waitUntil(() => view.querySelector('.chat-head__desc'));
expect(head_desc?.textContent.trim()).toBe(text);
stanza = u.toStanza(
`
This is a message subject
This is a message
`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(sizzle('.chat-msg__subject', view).length).toBe(1);
expect(sizzle('.chat-msg__subject', view).pop().textContent.trim()).toBe('This is a message subject');
expect(sizzle('.chat-msg__text').length).toBe(1);
expect(sizzle('.chat-msg__text').pop().textContent.trim()).toBe('This is a message');
const topic_el = view.querySelector('.chat-head__desc');
expect(topic_el.textContent.trim()).toBe(text);
expect(u.isVisible(topic_el)).toBe(true);
const toggle = view.querySelector('.hide-topic');
expect(toggle.textContent).toBe('Hide topic');
toggle.click();
await u.waitUntil(() => view.querySelector('.hide-topic').textContent === 'Show topic');
done();
}));
it("will always be shown when it's new", mock.initConverse([], {}, async function (done, _converse) {
await mock.openAndEnterChatRoom(_converse, 'jdev@conference.jabber.org', 'jc');
const text = 'Jabber/XMPP Development | RFCs and Extensions: https://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org';
let stanza = u.toStanza(`
${text}
`);
_converse.connection._dataRecv(mock.createRequest(stanza));
const view = _converse.chatboxviews.get('jdev@conference.jabber.org');
await new Promise(resolve => view.model.once('change:subject', resolve));
const head_desc = await u.waitUntil(() => view.querySelector('.chat-head__desc'));
expect(head_desc?.textContent.trim()).toBe(text);
let topic_el = view.querySelector('.chat-head__desc');
expect(topic_el.textContent.trim()).toBe(text);
expect(u.isVisible(topic_el)).toBe(true);
const toggle = view.querySelector('.hide-topic');
expect(toggle.textContent).toBe('Hide topic');
toggle.click();
await u.waitUntil(() => !u.isVisible(topic_el));
stanza = u.toStanza(`
Another topic
`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => u.isVisible(view.querySelector('.chat-head__desc')));
topic_el = view.querySelector('.chat-head__desc');
expect(topic_el.textContent.trim()).toBe('Another topic');
done();
}));
it("causes an info message to be shown when received in real-time", mock.initConverse([], {}, async function (done, _converse) {
spyOn(_converse.ChatRoom.prototype, 'handleSubjectChange').and.callThrough();
await mock.openAndEnterChatRoom(_converse, 'jdev@conference.jabber.org', 'romeo');
const view = _converse.chatboxviews.get('jdev@conference.jabber.org');
_converse.connection._dataRecv(mock.createRequest(u.toStanza(`
This is an older topic
`)));
await u.waitUntil(() => view.model.handleSubjectChange.calls.count());
expect(sizzle('.chat-info__message', view).length).toBe(0);
const desc = await u.waitUntil(() => view.querySelector('.chat-head__desc'));
expect(desc.textContent.trim()).toBe('This is an older topic');
_converse.connection._dataRecv(mock.createRequest(u.toStanza(`
This is a new topic
`)));
await u.waitUntil(() => view.model.handleSubjectChange.calls.count() === 2);
await u.waitUntil(() => sizzle('.chat-info__message', view).pop()?.textContent.trim() === 'Topic set by ralphm');
await u.waitUntil(() => desc.textContent.trim() === 'This is a new topic');
// Doesn't show multiple subsequent topic change notifications
_converse.connection._dataRecv(mock.createRequest(u.toStanza(`
Yet another topic
`)));
await u.waitUntil(() => view.model.handleSubjectChange.calls.count() === 3);
await u.waitUntil(() => desc.textContent.trim() === 'Yet another topic');
expect(sizzle('.chat-info__message', view).length).toBe(1);
// Sow multiple subsequent topic change notification from someone else
_converse.connection._dataRecv(mock.createRequest(u.toStanza(`
Some1's topic
`)));
await u.waitUntil(() => view.model.handleSubjectChange.calls.count() === 4);
await u.waitUntil(() => desc.textContent.trim() === "Some1's topic");
expect(sizzle('.chat-info__message', view).length).toBe(2);
const el = sizzle('.chat-info__message', view).pop();
expect(el.textContent.trim()).toBe('Topic set by some1');
// Removes current topic
const stanza = u.toStanza(
`
`);
_converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.model.handleSubjectChange.calls.count() === 5);
await u.waitUntil(() => view.querySelector('.chat-head__desc').textContent.replace(//g, '') === '');
await u.waitUntil(() => view.querySelector('converse-chat-message:last-child .chat-info').textContent.trim() === "Topic cleared by some1");
done();
}));
});
it("restores cached messages when it reconnects and clear_messages_on_reconnection and muc_clear_messages_on_leave are false",
mock.initConverse([], {
'clear_messages_on_reconnection': false,
'muc_clear_messages_on_leave': false
},
async function (done, _converse) {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid , 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
const message = 'Hello world',
nick = mock.chatroom_names[0],
msg = $msg({
'from': 'lounge@montague.lit/'+nick,
'id': u.getUniqueId(),
'to': 'romeo@montague.lit',
'type': 'groupchat'
}).c('body').t(message).tree();
await view.model.handleMessageStanza(msg);
await view.model.close();
_converse.connection.IQ_stanzas = [];
await mock.openAndEnterChatRoom(_converse, muc_jid , 'romeo');
await u.waitUntil(() => view.querySelector('converse-chat-message'));
expect(view.model.messages.length).toBe(1);
expect(view.querySelectorAll('converse-chat-message').length).toBe(1);
done()
}));
it("clears cached messages when it reconnects and clear_messages_on_reconnection is true",
mock.initConverse([], {'clear_messages_on_reconnection': true}, async function (done, _converse) {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterChatRoom(_converse, muc_jid , 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
const message = 'Hello world',
nick = mock.chatroom_names[0],
msg = $msg({
'from': 'lounge@montague.lit/'+nick,
'id': u.getUniqueId(),
'to': 'romeo@montague.lit',
'type': 'groupchat'
}).c('body').t(message).tree();
await view.model.handleMessageStanza(msg);
await view.model.close();
_converse.connection.IQ_stanzas = [];
await mock.openAndEnterChatRoom(_converse, muc_jid , 'romeo');
expect(view.model.messages.length).toBe(0);
expect(view.querySelector('converse-chat-history')).toBe(null);
done()
}));
it("is opened when an xmpp: URI is clicked inside another groupchat",
mock.initConverse([], {}, async function (done, _converse) {
await mock.waitForRoster(_converse, 'current');
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
if (!view.querySelectorAll('.chat-area').length) {
view.renderChatArea();
}
expect(_converse.chatboxes.length).toEqual(2);
const message = 'Please go to xmpp:coven@chat.shakespeare.lit?join',
nick = mock.chatroom_names[0],
msg = $msg({
'from': 'lounge@montague.lit/'+nick,
'id': u.getUniqueId(),
'to': 'romeo@montague.lit',
'type': 'groupchat'
}).c('body').t(message).tree();
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.querySelector('.chat-msg__text a'));
view.querySelector('.chat-msg__text a').click();
await u.waitUntil(() => _converse.chatboxes.length === 3)
expect(_converse.chatboxes.pluck('id').includes('coven@chat.shakespeare.lit')).toBe(true);
done()
}));
it("shows a notification if it's not anonymous",
mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
const muc_jid = 'coven@chat.shakespeare.lit';
const nick = 'romeo';
await _converse.api.rooms.open(muc_jid);
await mock.getRoomFeatures(_converse, muc_jid);
await mock.waitForReservedNick(_converse, muc_jid, nick);
const view = _converse.chatboxviews.get(muc_jid);
/*
*
*
*
*
*
*