(function (root, factory) {
define(["jasmine", "mock", "test-utils" ], factory);
} (this, function (jasmine, mock, test_utils) {
const _ = converse.env._,
$pres = converse.env.$pres,
$iq = converse.env.$iq,
$msg = converse.env.$msg,
Strophe = converse.env.Strophe,
Promise = converse.env.Promise,
dayjs = converse.env.dayjs,
sizzle = converse.env.sizzle,
Backbone = converse.env.Backbone,
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(
null, ['rosterGroupsFetched'], {},
async function (done, _converse) {
await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
await test_utils.openAndEnterChatRoom(_converse, 'leisure', 'montague.lit', 'romeo');
await test_utils.openAndEnterChatRoom(_converse, 'news', '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();
expect(u.isVisible(_converse.chatboxviews.get('news@montague.lit').el)).toBeTruthy();
_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']);
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 test_utils.openAndEnterChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
await test_utils.openAndEnterChatRoom(_converse, 'leisure', 'montague.lit', 'romeo');
expect(u.isVisible(_converse.chatboxviews.get('lounge@montague.lit').el)).toBeTruthy();
expect(u.isVisible(_converse.chatboxviews.get('leisure@montague.lit').el)).toBeTruthy();
_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(
null, ['rosterGroupsFetched'], {},
async function (done, _converse) {
test_utils.createContacts(_converse, 'current');
await test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group .group-toggle').length, 300);
await test_utils.openAndEnterChatRoom(_converse, 'chillout', 'montague.lit', 'romeo');
let jid = 'chillout@montague.lit';
let room = _converse.api.rooms.get(jid);
expect(room instanceof Object).toBeTruthy();
let chatroomview = _converse.chatboxviews.get(jid);
expect(chatroomview.is_chatroom).toBeTruthy();
expect(u.isVisible(chatroomview.el)).toBeTruthy();
chatroomview.close();
// Test with mixed case
await test_utils.openAndEnterChatRoom(_converse, 'Leisure', 'montague.lit', 'romeo');
jid = 'Leisure@montague.lit';
room = _converse.api.rooms.get(jid);
expect(room instanceof Object).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
expect(u.isVisible(chatroomview.el)).toBeTruthy();
jid = 'leisure@montague.lit';
room = _converse.api.rooms.get(jid);
expect(room instanceof Object).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
expect(u.isVisible(chatroomview.el)).toBeTruthy();
jid = 'leiSure@montague.lit';
room = _converse.api.rooms.get(jid);
expect(room instanceof Object).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
expect(u.isVisible(chatroomview.el)).toBeTruthy();
chatroomview.close();
// Non-existing room
jid = 'chillout2@montague.lit';
room = _converse.api.rooms.get(jid);
expect(typeof room === 'undefined').toBeTruthy();
done();
}));
it("has a method 'open' which opens (optionally configures) and returns a wrapped chat box",
mock.initConverse(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
// Mock 'getRoomFeatures', 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, 'getRoomFeatures').and.callFake(() => Promise.resolve());
const sent_IQ_els = [];
let jid = 'lounge@montague.lit';
let chatroomview, sent_IQ, IQ_id;
test_utils.openControlBox();
test_utils.createContacts(_converse, 'current');
await test_utils.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
expect(room instanceof Backbone.Model).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid);
expect(chatroomview.is_chatroom).toBeTruthy();
await test_utils.waitUntil(() => u.isVisible(chatroomview.el));
// Test again, now that the room exists.
room = await _converse.api.rooms.open(jid);
expect(room instanceof Backbone.Model).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid);
expect(chatroomview.is_chatroom).toBeTruthy();
expect(u.isVisible(chatroomview.el)).toBeTruthy();
chatroomview.close();
// Test with mixed case in JID
jid = 'Leisure@montague.lit';
room = await _converse.api.rooms.open(jid);
expect(room instanceof Backbone.Model).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
await test_utils.waitUntil(() => u.isVisible(chatroomview.el));
jid = 'leisure@montague.lit';
room = await _converse.api.rooms.open(jid);
expect(room instanceof Backbone.Model).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
await test_utils.waitUntil(() => u.isVisible(chatroomview.el));
jid = 'leiSure@montague.lit';
room = await _converse.api.rooms.open(jid);
expect(room instanceof Backbone.Model).toBeTruthy();
chatroomview = _converse.chatboxviews.get(jid.toLowerCase());
await test_utils.waitUntil(() => u.isVisible(chatroomview.el));
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'
}
});
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(test_utils.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(test_utils.createRequest(presence));
expect(_converse.connection.sendIQ).toHaveBeenCalled();
const IQ_stanzas = _converse.connection.IQ_stanzas;
const iq = 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(test_utils.createRequest(node));
await test_utils.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).toBe('Room');
expect(sizzle('field[var="muc#roomconfig_roomdesc"] value', sent_stanza).pop().textContent).toBe('Welcome to this groupchat');
expect(sizzle('field[var="muc#roomconfig_persistentroom"] value', sent_stanza).pop().textContent).toBe('1');
expect(sizzle('field[var="muc#roomconfig_getmemberlist"] value', sent_stanza).map(e => e.textContent).join(' ')).toBe('moderator participant');
expect(sizzle('field[var="muc#roomconfig_publicroom"] value ', sent_stanza).pop().textContent).toBe('1');
expect(sizzle('field[var="muc#roomconfig_changesubject"] value', sent_stanza).pop().textContent).toBe('0');
expect(sizzle('field[var="muc#roomconfig_whois"] value ', sent_stanza).pop().textContent).toBe('anyone');
expect(sizzle('field[var="muc#roomconfig_membersonly"] value', sent_stanza).pop().textContent).toBe('1');
expect(sizzle('field[var="muc#roomconfig_historylength"] value', sent_stanza).pop().textContent).toBe('20');
done();
}));
});
describe("An instant groupchat", function () {
it("will be created when muc_instant_rooms is set to true",
mock.initConverse(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
const IQ_stanzas = _converse.connection.IQ_stanzas;
const sendIQ = _converse.connection.sendIQ;
const room_jid = 'lounge@montague.lit';
let sent_IQ, IQ_id;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
if (iq.nodeTree.getAttribute('to') === 'lounge@montague.lit') {
sent_IQ = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback);
} else {
sendIQ.bind(this)(iq, callback, errback);
}
});
await test_utils.openChatRoom(_converse, 'lounge', 'montague.lit', 'romeo');
let stanza = await test_utils.waitUntil(() => _.filter(
IQ_stanzas,
iq => iq.querySelector(
`iq[to="${room_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
)).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(test_utils.createRequest(features_stanza));
const view = _converse.chatboxviews.get('lounge@montague.lit');
spyOn(view.model, 'join').and.callThrough();
/*
*
*
*/
stanza = await test_utils.waitUntil(() => _.filter(
IQ_stanzas,
s => sizzle(`iq[to="${room_jid}"] query[node="x-roomuser-item"]`, s).length
).pop()
);
expect(Strophe.serialize(stanza)).toBe(
``+
``);
/*
*
*
*
*
*/
var result_stanza = $iq({
'type': 'error',
'id': stanza.getAttribute('id'),
'from': view.model.get('jid'),
'to': _converse.connection.jid
}).c('error', {'type': 'cancel'})
.c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
_converse.connection._dataRecv(test_utils.createRequest(result_stanza));
const input = await test_utils.waitUntil(() => view.el.querySelector('input[name="nick"]'));
input.value = 'nicky';
view.el.querySelector('input[type=submit]').click();
expect(view.model.join).toHaveBeenCalled();
// 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(test_utils.createRequest(presence));
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
const info_texts = Array.from(view.el.querySelectorAll('.chat-content .chat-info')).map(e => e.textContent);
expect(info_texts[1]).toBe('A new groupchat has been created');
expect(info_texts[0]).toBe('nicky has entered the groupchat');
// An instant room is created by saving the default configuratoin.
//
/*
*
*
*/
const iq = IQ_stanzas.filter(s => s.querySelector(`query[xmlns="${Strophe.NS.MUC_OWNER}"]`)).pop();
expect(Strophe.serialize(iq)).toBe(
``+
``+
``);
done();
}));
});
describe("A Groupchat", function () {
it("clears cached messages when it gets closed",
mock.initConverse(
null, ['rosterGroupsFetched'], {},
async function (done, _converse) {
await test_utils.waitUntil(() => test_utils.openAndEnterChatRoom(_converse, 'lounge', 'montague.lit', 'romeo'));
const view = _converse.chatboxviews.get('lounge@montague.lit');
const message = 'Hello world',
nick = mock.chatroom_names[0],
msg = $msg({
'from': 'lounge@montague.lit/'+nick,
'id': (new Date()).getTime(),
'to': 'romeo@montague.lit',
'type': 'groupchat'
}).c('body').t(message).tree();
await view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
spyOn(view.model, 'clearMessages').and.callThrough();
view.model.close();
await test_utils.waitUntil(() => view.model.clearMessages.calls.count());
expect(view.model.messages.length).toBe(0);
expect(view.content.innerHTML).toBe('');
done()
}));
it("is opened when an xmpp: URI is clicked inside another groupchat",
mock.initConverse(
null, ['rosterGroupsFetched'], {},
async function (done, _converse) {
test_utils.createContacts(_converse, 'current');
await test_utils.waitUntil(() => test_utils.openAndEnterChatRoom(_converse, 'lounge', 'montague.lit', 'romeo'));
const view = _converse.chatboxviews.get('lounge@montague.lit');
if (!view.el.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': (new Date()).getTime(),
'to': 'romeo@montague.lit',
'type': 'groupchat'
}).c('body').t(message).tree();
await view.model.onMessage(msg);
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
view.el.querySelector('.chat-msg__text a').click();
await test_utils.waitUntil(() => _converse.chatboxes.length === 3)
expect(_.includes(_converse.chatboxes.pluck('id'), 'coven@chat.shakespeare.lit')).toBe(true);
done()
}));
it("shows a notification if it's not anonymous",
mock.initConverse(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
await test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit');
const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
const chat_content = view.el.querySelector('.chat-content');
/*
*
*
*
*
*
*