converse.js
Version:
Browser based XMPP chat client
873 lines (761 loc) • 145 kB
JavaScript
/*global mock, converse */
const { $pres, Strophe, sizzle, stx, u } = converse.env;
describe("Groupchats", function () {
beforeAll(() => jasmine.addMatchers({ toEqualStanza: jasmine.toEqualStanza }));
describe("An instant groupchat", function () {
it("will be created when muc_instant_rooms is set to true",
mock.initConverse(['chatBoxesFetched'], { vcard: { nickname: '' } }, async function (_converse) {
let IQ_stanzas = _converse.api.connection.get().IQ_stanzas;
const { api } = _converse;
const muc_jid = 'lounge@montague.lit';
const nick = 'nicky';
const promise = api.rooms.open(muc_jid);
await mock.waitForMUCDiscoInfo(_converse, muc_jid);
await mock.waitForReservedNick(_converse, muc_jid, '');
const muc = await promise;
await muc.initialized;
spyOn(muc, 'join').and.callThrough();
const view = _converse.chatboxviews.get(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();
await mock.waitForNewMUCDiscoInfo(_converse, muc_jid);
_converse.api.connection.get().IQ_stanzas = [];
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
const presence =
stx`<presence
id="5025e055-036c-4bc5-a227-706e7e352053"
to="romeo@montague.lit/orchard"
from="lounge@montague.lit/nicky"
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="owner" jid="romeo@montague.lit/orchard" role="moderator"/>
<status code="110"/>
<status code="201"/>
</x>
</presence>`;
_converse.api.connection.get()._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.api.connection.get().IQ_stanzas;
const iq = await u.waitUntil(() => IQ_stanzas.filter((s) => sizzle(selector, s).length).pop());
expect(iq).toEqualStanza(stx`
<iq id="${iq.getAttribute('id')}" to="lounge@montague.lit" type="set" xmlns="jabber:client">
<query xmlns="http://jabber.org/protocol/muc#owner">
<x type="submit" xmlns="jabber:x:data"/>
</query>
</iq>`);
}));
});
describe("A Groupchat", function () {
it("will be visible when opened as the first chat in fullscreen-view",
mock.initConverse(['discoInitialized'],
{ 'view_mode': 'fullscreen', 'auto_join_rooms': ['orchard@chat.shakespeare.lit']},
async function (_converse) {
const { api } = _converse;
const muc_jid = 'orchard@chat.shakespeare.lit';
api.rooms.get(muc_jid);
await mock.waitForMUCDiscoInfo(_converse, muc_jid);
await mock.waitForReservedNick(_converse, muc_jid, 'romeo');
await mock.receiveOwnMUCPresence(_converse, muc_jid, 'romeo');
await api.waitUntil('roomsAutoJoined');
const room = await u.waitUntil(() => _converse.chatboxes.get(muc_jid));
expect(room.get('hidden')).toBe(false);
}));
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,
vcard: { nickname: '' },
}, async function (_converse) {
const { api } = _converse;
const muc_jid = 'orchard@chat.shakespeare.lit';
const nick = 'romeo';
api.rooms.open(muc_jid);
await mock.waitForMUCDiscoInfo(_converse, muc_jid);
await mock.waitForReservedNick(_converse, muc_jid);
const view = await u.waitUntil(() => _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')
const nick_input = await u.waitUntil(() => view.querySelector('[name="nick"]'));
nick_input.value = nick;
view.querySelector('.muc-nickname-form input[type="submit"]').click();
_converse.api.connection.get().IQ_stanzas = [];
await mock.waitForMUCDiscoInfo(_converse, muc_jid);
await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING);
await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
}));
it("maintains its state across reloads",
mock.initConverse([], {
clear_messages_on_reconnection: true,
enable_smacks: false
}, async function (_converse) {
const { api } = _converse;
const nick = 'romeo';
const sent_IQs = _converse.api.connection.get().IQ_stanzas;
const muc_jid = 'lounge@montague.lit'
await mock.openAndEnterMUC(_converse, muc_jid, nick, [], []);
const view = _converse.chatboxviews.get(muc_jid);
let iq_get = await u.waitUntil(() => sent_IQs.filter((iq) => sizzle(`query[xmlns="${Strophe.NS.MAM}"]`, iq).length).pop());
expect(iq_get).toEqualStanza(stx`
<iq id="${iq_get.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client">
<query queryid="${iq_get.querySelector('query').getAttribute('queryid')}" xmlns="${Strophe.NS.MAM}">
<set xmlns="http://jabber.org/protocol/rsm"><before></before><max>50</max></set>
</query>
</iq>`);
const first_msg_id = _converse.api.connection.get().getUniqueId();
const last_msg_id = _converse.api.connection.get().getUniqueId();
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<message xmlns="jabber:client"
to="romeo@montague.lit/orchard"
from="${muc_jid}">
<result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${first_msg_id}">
<forwarded xmlns="urn:xmpp:forward:0">
<delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:15:23Z"/>
<message from="${muc_jid}/some1" type="groupchat">
<body>1st Message</body>
</message>
</forwarded>
</result>
</message>`));
let message = stx`<message xmlns="jabber:client"
to="romeo@montague.lit/orchard"
from="${muc_jid}">
<result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${last_msg_id}">
<forwarded xmlns="urn:xmpp:forward:0">
<delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:16:23Z"/>
<message from="${muc_jid}/some1" type="groupchat">
<body>2nd Message</body>
</message>
</forwarded>
</result>
</message>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(message));
const result = stx`<iq type='result' id='${iq_get.getAttribute('id')}' xmlns="jabber:client">
<fin xmlns='urn:xmpp:mam:2' complete="true">
<set xmlns='http://jabber.org/protocol/rsm'>
<first index='0'>${first_msg_id}</first>
<last>${last_msg_id}</last>
<count>2</count>
</set>
</fin>
</iq>`;
_converse.api.connection.get()._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.waitForMUCDiscoInfo(_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 = stx`
<message xmlns="jabber:client" type="groupchat" id="918172de-d5c5-4da4-b388-446ef4a05bec" to="${_converse.jid}" xml:lang="en" from="${muc_jid}/juliet">
<body>Wherefore art though?</body>
<active xmlns="http://jabber.org/protocol/chatstates"/>
<origin-id xmlns="urn:xmpp:sid:0" id="918172de-d5c5-4da4-b388-446ef4a05bec"/>
<stanza-id xmlns="urn:xmpp:sid:0" id="88cc9c93-a8f4-4dd5-b02a-d19855eb6303" by="${muc_jid}"/>
<delay xmlns="urn:xmpp:delay" stamp="2020-07-14T17:46:47Z" from="juliet@shakespeare.lit"/>
</message>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(message));
message = stx`
<message xmlns="jabber:client" type="groupchat" id="awQo6a-mi-Wa6NYh" to="${_converse.jid}" from="${muc_jid}/ews000" xml:lang="en">
<composing xmlns="http://jabber.org/protocol/chatstates"/>
<no-store xmlns="urn:xmpp:hints"/>
<no-permanent-store xmlns="urn:xmpp:hints"/>
<delay xmlns="urn:xmpp:delay" stamp="2020-07-14T17:46:54Z" from="juliet@shakespeare.lit"/>
</message>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(message));
const affs = api.settings.get('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) => sizzle(`query[xmlns="${Strophe.NS.MAM}"]`, iq).length).pop());
expect(iq_get).toEqualStanza(stx`
<iq id="${iq_get.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client">
<query queryid="${iq_get.querySelector('query').getAttribute('queryid')}" xmlns="${Strophe.NS.MAM}">
<x xmlns="jabber:x:data" type="submit">
<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>
<field var="start"><value>2020-07-14T17:46:47.000Z</value></field>
</x>
<set xmlns="http://jabber.org/protocol/rsm"><before></before><max>50</max></set>
</query>
</iq>`);
}));
it("shows a new messages indicator when you're scrolled up",
mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterMUC(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
const message = stx`
<message xmlns="jabber:client" type="groupchat" id="918172de-d5c5-4da4-b388-446ef4a05bec" to="${_converse.jid}" xml:lang="en" from="${muc_jid}/juliet">
<body>Wherefore art though?</body>
<active xmlns="http://jabber.org/protocol/chatstates"/>
<origin-id xmlns="urn:xmpp:sid:0" id="918172de-d5c5-4da4-b388-446ef4a05bec"/>
<stanza-id xmlns="urn:xmpp:sid:0" id="88cc9c93-a8f4-4dd5-b02a-d19855eb6303" by="${muc_jid}"/>
<delay xmlns="urn:xmpp:delay" stamp="2020-07-14T17:46:47Z" from="juliet@shakespeare.lit"/>
</message>`;
view.model.ui.set('scrolled', true); // hack
_converse.api.connection.get()._dataRecv(mock.createRequest(message));
await u.waitUntil(() => view.model.messages.length);
const chat_new_msgs_indicator = await u.waitUntil(() => view.querySelector('.new-msgs-indicator'));
chat_new_msgs_indicator.click();
expect(view.model.ui.get('scrolled')).toBeFalsy();
await u.waitUntil(() => !u.isVisible(chat_new_msgs_indicator));
}));
describe("topic", function () {
it("is shown the header", mock.initConverse([], {}, async function (_converse) {
await mock.openAndEnterMUC(_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 = stx`
<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm">
<subject>${text}</subject>
<delay xmlns="urn:xmpp:delay" stamp="2014-02-04T09:35:39Z" from="jdev@conference.jabber.org"/>
<x xmlns="jabber:x:delay" stamp="20140204T09:35:39" from="jdev@conference.jabber.org"/>
</message>`;
_converse.api.connection.get()._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 = stx`<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm">
<subject>This is a message subject</subject>
<body>This is a message</body>
</message>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
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);
}));
it("can be toggled by the user", mock.initConverse([], {}, async function (_converse) {
await mock.openAndEnterMUC(_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 = stx`
<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm">
<subject>${text}</subject>
<delay xmlns="urn:xmpp:delay" stamp="2014-02-04T09:35:39Z" from="jdev@conference.jabber.org"/>
<x xmlns="jabber:x:delay" stamp="20140204T09:35:39" from="jdev@conference.jabber.org"/>
</message>`;
_converse.api.connection.get()._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 = stx`<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm">
<subject>This is a message subject</subject>
<body>This is a message</body>
</message>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
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);
await u.waitUntil(() => view.querySelector('.hide-topic').textContent.trim() === 'Hide topic');
const toggle = view.querySelector('.hide-topic');
expect(toggle.textContent.trim()).toBe('Hide topic');
toggle.click();
await u.waitUntil(() => view.querySelector('.hide-topic').textContent.trim() === 'Show topic');
}));
it("will always be shown when it's new", mock.initConverse([], {}, async function (_converse) {
await mock.openAndEnterMUC(_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 = stx`<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm">
<subject>${text}</subject>
</message>`;
_converse.api.connection.get()._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.trim()).toBe('Hide topic');
toggle.click();
await u.waitUntil(() => !u.isVisible(topic_el));
stanza = stx`<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm">
<subject>Another topic</subject>
</message>`;
_converse.api.connection.get()._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');
}));
it("causes an info message to be shown when received in real-time", mock.initConverse([], {}, async function (_converse) {
spyOn(_converse.ChatRoom.prototype, 'handleSubjectChange').and.callThrough();
await mock.openAndEnterMUC(_converse, 'jdev@conference.jabber.org', 'romeo');
const view = _converse.chatboxviews.get('jdev@conference.jabber.org');
_converse.api.connection.get()._dataRecv(mock.createRequest(stx`
<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm">
<subject>This is an older topic</subject>
<delay xmlns="urn:xmpp:delay" stamp="2014-02-04T09:35:39Z" from="jdev@conference.jabber.org"/>
<x xmlns="jabber:x:delay" stamp="20140204T09:35:39" from="jdev@conference.jabber.org"/>
</message>`));
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.api.connection.get()._dataRecv(mock.createRequest(stx`
<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm">
<subject>This is a new topic</subject>
</message>`));
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.api.connection.get()._dataRecv(mock.createRequest(stx`
<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/ralphm">
<subject>Yet another topic</subject>
</message>`));
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.api.connection.get()._dataRecv(mock.createRequest(stx`
<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/some1">
<subject>Some1's topic</subject>
</message>`));
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 = stx`<message xmlns="jabber:client" to="${_converse.jid}" type="groupchat" from="jdev@conference.jabber.org/some1">
<subject/>
</message>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => view.model.handleSubjectChange.calls.count() === 5);
await u.waitUntil(() => view.querySelector('.chat-head__desc') === null);
await u.waitUntil(() => view.querySelector('converse-chat-message:last-child .chat-info').textContent.trim() === "Topic cleared by some1");
}));
});
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 (_converse) {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterMUC(_converse, muc_jid , 'romeo');
const model = _converse.chatboxes.get(muc_jid);
const message = 'Hello world';
const nick = mock.chatroom_names[0];
const msg =
stx`<message from="lounge@montague.lit/${nick}"
id="${u.getUniqueId()}"
to="romeo@montague.lit"
type="groupchat"
xmlns="jabber:client">
<body>${message}</body>
</message>`;
await model.handleMessageStanza(msg);
await u.waitUntil(() => document.querySelector('converse-chat-message'));
await model.close();
await u.waitUntil(() => !document.querySelector('converse-chat-message'));
_converse.api.connection.get().IQ_stanzas = [];
await mock.openAndEnterMUC(_converse, muc_jid , 'romeo');
await u.waitUntil(() => document.querySelector('converse-chat-message'));
expect(model.messages.length).toBe(1);
expect(document.querySelectorAll('converse-chat-message').length).toBe(1);
}));
it("clears cached messages when it reconnects and clear_messages_on_reconnection is true",
mock.initConverse([], {'clear_messages_on_reconnection': true}, async function (_converse) {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterMUC(_converse, muc_jid , 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
const message = 'Hello world';
const nick = mock.chatroom_names[0];
const msg =
stx`<message from="lounge@montague.lit/${nick}"
id="${u.getUniqueId()}"
to="romeo@montague.lit"
type="groupchat"
xmlns="jabber:client">
<body>${message}</body>
</message>`;
await view.model.handleMessageStanza(msg);
await view.model.close();
_converse.api.connection.get().IQ_stanzas = [];
await mock.openAndEnterMUC(_converse, muc_jid , 'romeo');
expect(view.model.messages.length).toBe(0);
expect(view.querySelector('converse-chat-history')).toBe(null);
}));
it("is opened when an xmpp: URI is clicked inside another groupchat",
mock.initConverse([], {}, async function (_converse) {
await mock.waitForRoster(_converse, 'current');
await mock.openAndEnterMUC(_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';
const nick = mock.chatroom_names[0];
const msg =
stx`<message from="lounge@montague.lit/${nick}"
id="${u.getUniqueId()}"
type="groupchat"
to="romeo@montague.lit"
xmlns="jabber:client">
<body>${message}</body>
</message>`;
await view.model.handleMessageStanza(msg);
await u.waitUntil(() => view.querySelector('.chat-msg__text a'));
view.querySelector('.chat-msg__text a').click();
await mock.waitForMUCDiscoInfo(_converse, 'coven@chat.shakespeare.lit');
await mock.waitForReservedNick(_converse, 'coven@chat.shakespeare.lit', 'romeo');
await u.waitUntil(() => _converse.chatboxes.length === 3)
expect(_converse.chatboxes.pluck('id').includes('coven@chat.shakespeare.lit')).toBe(true);
}));
it("shows a notification if it's not anonymous",
mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
const muc_jid = 'coven@chat.shakespeare.lit';
const nick = 'romeo';
_converse.api.rooms.open(muc_jid);
await mock.waitForMUCDiscoInfo(_converse, muc_jid);
await mock.waitForReservedNick(_converse, muc_jid, nick);
const view = await u.waitUntil(() => _converse.chatboxviews.get(muc_jid));
const presence =
stx`<presence to="romeo@montague.lit/orchard"
from="coven@chat.shakespeare.lit/some1"
xmlns="jabber:client">
<x xmlns="${Strophe.NS.MUC_USER}">
<item affiliation="owner" jid="romeo@montague.lit/_converse.js-29092160" role="moderator"/>
<status code="110"/>
<status code="100"/>
</x>
</presence>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
await mock.returnMemberLists(_converse, muc_jid, [], ['member', 'admin', 'owner']);
const num_info_msgs = await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-info').length);
expect(num_info_msgs).toBe(1);
expect(sizzle('div.chat-info', view).pop().textContent.trim()).toBe("This groupchat is not anonymous");
const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent);
expect(csntext.trim()).toEqual("some1 has entered the groupchat");
}));
it("shows join/leave messages when users enter or exit a groupchat",
mock.initConverse(['chatBoxesFetched'], {'muc_fetch_members': false}, async function (_converse) {
const muc_jid = 'coven@chat.shakespeare.lit';
const nick = 'some1';
const room_creation_promise = _converse.api.rooms.open(muc_jid, {nick});
await mock.waitForMUCDiscoInfo(_converse, muc_jid);
const sent_stanzas = _converse.api.connection.get().sent_stanzas;
await u.waitUntil(() => sent_stanzas.filter(iq => sizzle('presence history', iq).length));
await _converse.api.waitUntil('chatRoomViewInitialized');
/* We don't show join/leave messages for existing occupants. We
* know about them because we receive their presences before we
* receive our own.
*/
let presence = $pres({
to: 'romeo@montague.lit/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/oldguy'
}).c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'oldguy@montague.lit/_converse.js-290929789',
'role': 'participant'
});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
/* <presence to="romeo@montague.lit/_converse.js-29092160"
* from="coven@chat.shakespeare.lit/some1">
* <x xmlns="http://jabber.org/protocol/muc#user">
* <item affiliation="owner" jid="romeo@montague.lit/_converse.js-29092160" role="moderator"/>
* <status code="110"/>
* </x>
* </presence></body>
*/
presence = $pres({
to: 'romeo@montague.lit/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/some1'
}).c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'owner',
'jid': 'romeo@montague.lit/_converse.js-29092160',
'role': 'moderator'
}).up()
.c('status', {code: '110'});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
const view = await u.waitUntil(() => _converse.chatboxviews.get('coven@chat.shakespeare.lit'));
const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications')?.textContent);
expect(csntext.trim()).toEqual("some1 has entered the groupchat");
await room_creation_promise;
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
await view.model.messages.fetched;
presence = $pres({
to: 'romeo@montague.lit/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/newguy'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'newguy@montague.lit/_converse.js-290929789',
'role': 'participant'
});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"some1 and newguy have entered the groupchat");
const msg =
stx`<message from="coven@chat.shakespeare.lit/some1"
to="romeo@montague.lit"
id="${u.getUniqueId()}"
type="groupchat"
xmlns="jabber:client">
<body>hello world</body>
</message>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(msg));
await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
// Add another entrant, otherwise the above message will be
// collapsed if "newguy" leaves immediately again
presence = $pres({
to: 'romeo@montague.lit/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/newgirl'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'newgirl@montague.lit/_converse.js-213098781',
'role': 'participant'
});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"some1, newguy and newgirl have entered the groupchat");
// Don't show duplicate join messages
presence = $pres({
to: 'romeo@montague.lit/_converse.js-290918392',
from: 'coven@chat.shakespeare.lit/newguy'
}).c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'newguy@montague.lit/_converse.js-290929789',
'role': 'participant'
});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
/* <presence
* from='coven@chat.shakespeare.lit/thirdwitch'
* to='crone1@shakespeare.lit/desktop'
* type='unavailable'>
* <status>Disconnected: Replaced by new connection</status>
* <x xmlns='http://jabber.org/protocol/muc#user'>
* <item affiliation='member'
* jid='hag66@shakespeare.lit/pda'
* role='none'/>
* </x>
* </presence>
*/
presence = $pres({
to: 'romeo@montague.lit/_converse.js-29092160',
type: 'unavailable',
from: 'coven@chat.shakespeare.lit/newguy'
})
.c('status', 'Disconnected: Replaced by new connection').up()
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'newguy@montague.lit/_converse.js-290929789',
'role': 'none'
});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"some1 and newgirl have entered the groupchat\nnewguy has left the groupchat");
// When the user immediately joins again, we collapse the
// multiple join/leave messages.
presence = $pres({
to: 'romeo@montague.lit/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/newguy'
}).c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'newguy@montague.lit/_converse.js-290929789',
'role': 'participant'
});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"some1, newgirl and newguy have entered the groupchat");
presence = $pres({
to: 'romeo@montague.lit/_converse.js-29092160',
type: 'unavailable',
from: 'coven@chat.shakespeare.lit/newguy'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'newguy@montague.lit/_converse.js-290929789',
'role': 'none'
});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"some1 and newgirl have entered the groupchat\nnewguy has left the groupchat");
presence = $pres({
to: 'romeo@montague.lit/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/nomorenicks'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'nomorenicks@montague.lit/_converse.js-290929789',
'role': 'participant'
});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"some1, newgirl and nomorenicks have entered the groupchat\nnewguy has left the groupchat");
presence = $pres({
to: 'romeo@montague.lit/_converse.js-290918392',
type: 'unavailable',
from: 'coven@chat.shakespeare.lit/nomorenicks'
}).c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'nomorenicks@montague.lit/_converse.js-290929789',
'role': 'none'
});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"some1 and newgirl have entered the groupchat\nnewguy and nomorenicks have left the groupchat");
presence = $pres({
to: 'romeo@montague.lit/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/nomorenicks'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'nomorenicks@montague.lit/_converse.js-290929789',
'role': 'participant'
});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"some1, newgirl and nomorenicks have entered the groupchat\nnewguy has left the groupchat");
// Test a member joining and leaving
presence = $pres({
to: 'romeo@montague.lit/_converse.js-290918392',
from: 'coven@chat.shakespeare.lit/insider'
}).c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'member',
'jid': 'insider@montague.lit/_converse.js-290929789',
'role': 'participant'
});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
/* <presence
* from='coven@chat.shakespeare.lit/thirdwitch'
* to='crone1@shakespeare.lit/desktop'
* type='unavailable'>
* <status>Disconnected: Replaced by new connection</status>
* <x xmlns='http://jabber.org/protocol/muc#user'>
* <item affiliation='member'
* jid='hag66@shakespeare.lit/pda'
* role='none'/>
* </x>
* </presence>
*/
presence = $pres({
to: 'romeo@montague.lit/_converse.js-29092160',
type: 'unavailable',
from: 'coven@chat.shakespeare.lit/insider'
})
.c('status', 'Disconnected: Replaced by new connection').up()
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'member',
'jid': 'insider@montague.lit/_converse.js-290929789',
'role': 'none'
});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"some1, newgirl and nomorenicks have entered the groupchat\nnewguy and insider have left the groupchat");
expect(view.model.occupants.length).toBe(5);
expect(view.model.occupants.findWhere({'jid': 'insider@montague.lit'}).get('presence')).toBe('offline');
// New girl leaves
presence = $pres({
'to': 'romeo@montague.lit/_converse.js-29092160',
'type': 'unavailable',
'from': 'coven@chat.shakespeare.lit/newgirl'
})
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'newgirl@montague.lit/_converse.js-213098781',
'role': 'none'
});
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"some1 and nomorenicks have entered the groupchat\nnewguy, insider and newgirl have left the groupchat");
expect(view.model.occupants.length).toBe(4);
}));
it("combines subsequent join/leave messages when users enter or exit a groupchat",
mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
await mock.openAndEnterMUC(_converse, 'coven@chat.shakespeare.lit', 'romeo')
const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo has entered the groupchat");
let presence = stx`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/fabio">
<c xmlns="http://jabber.org/protocol/caps" node="http://conversations.im" ver="INI3xjRUioclBTP/aACfWi5m9UY=" hash="sha-1"/>
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="fabio@montefuscolo.com.br/Conversations.ZvLu" role="participant"/>
</x>
</presence>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo and fabio have entered the groupchat");
presence = stx`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/Dele Olajide">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="deleo@traderlynk.4ng.net/converse.js-39320524" role="participant"/>
</x>
</presence>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo, fabio and Dele Olajide have entered the groupchat");
presence = stx`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/jcbrand">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="owner" jid="jc@opkode.com/converse.js-30645022" role="moderator"/>
<status code="110"/>
</x>
</presence>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo, fabio and others have entered the groupchat");
presence = stx`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" type="unavailable" from="coven@chat.shakespeare.lit/Dele Olajide">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="deleo@traderlynk.4ng.net/converse.js-39320524" role="none"/>
</x>
</presence>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"romeo, fabio and jcbrand have entered the groupchat\nDele Olajide has left the groupchat");
presence = stx`<presence xmlns="jabber:client" to="romeo@montague.lit/orchard" from="coven@chat.shakespeare.lit/Dele Olajide">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="deleo@traderlynk.4ng.net/converse.js-74567907" role="participant"/>