converse.js
Version:
Browser based XMPP chat client
899 lines (800 loc) • 51.4 kB
JavaScript
/*global mock, converse */
const { Strophe, sizzle, stx, u } = converse.env;
describe("Groupchats", function () {
beforeAll(() => jasmine.addMatchers({ toEqualStanza: jasmine.toEqualStanza }));
describe("Each chat groupchat can take special commands", function () {
it("takes /help to show the available commands",
mock.initConverse([], {}, async function (_converse) {
spyOn(window, 'confirm').and.callFake(() => true);
await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
const enter = { 'target': textarea, 'preventDefault': function preventDefault () {}, key: "Enter" };
textarea.value = '/help';
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown(enter);
await u.waitUntil(() => sizzle('converse-chat-help .chat-info', view).length);
let chat_help_el = view.querySelector('converse-chat-help');
let info_messages = sizzle('.chat-info', chat_help_el);
expect(info_messages.length).toBe(19);
expect(info_messages.pop().textContent.trim()).toBe('/voice: Allow muted user to post messages');
expect(info_messages.pop().textContent.trim()).toBe('/topic: Set groupchat subject (alias for /subject)');
expect(info_messages.pop().textContent.trim()).toBe('/subject: Set groupchat subject');
expect(info_messages.pop().textContent.trim()).toBe('/revoke: Revoke the user\'s current affiliation');
expect(info_messages.pop().textContent.trim()).toBe('/register: Register your nickname');
expect(info_messages.pop().textContent.trim()).toBe('/owner: Grant ownership of this groupchat');
expect(info_messages.pop().textContent.trim()).toBe('/op: Grant moderator role to user');
expect(info_messages.pop().textContent.trim()).toBe('/nick: Change your nickname');
expect(info_messages.pop().textContent.trim()).toBe('/mute: Remove user\'s ability to post messages');
expect(info_messages.pop().textContent.trim()).toBe('/modtools: Opens up the moderator tools GUI');
expect(info_messages.pop().textContent.trim()).toBe('/member: Grant membership to a user');
expect(info_messages.pop().textContent.trim()).toBe('/me: Write in 3rd person');
expect(info_messages.pop().textContent.trim()).toBe('/kick: Kick user from groupchat');
expect(info_messages.pop().textContent.trim()).toBe('/help: Show this menu');
expect(info_messages.pop().textContent.trim()).toBe('/destroy: Remove this groupchat');
expect(info_messages.pop().textContent.trim()).toBe('/deop: Change user role to participant');
expect(info_messages.pop().textContent.trim()).toBe('/clear: Clear the chat area');
expect(info_messages.pop().textContent.trim()).toBe('/ban: Ban user by changing their affiliation to outcast');
expect(info_messages.pop().textContent.trim()).toBe('/admin: Change user\'s affiliation to admin');
const occupant = view.model.occupants.findWhere({'jid': _converse.bare_jid});
occupant.set('affiliation', 'admin');
view.querySelector('.close-chat-help').click();
expect(view.model.get('show_help_messages')).toBe(false);
await u.waitUntil(() => view.querySelector('converse-chat-help') === null);
textarea.value = '/help';
message_form.onKeyDown(enter);
chat_help_el = await u.waitUntil(() => view.querySelector('converse-chat-help'));
info_messages = sizzle('.chat-info', chat_help_el);
expect(info_messages.length).toBe(18);
let commands = info_messages.map(m => m.textContent.replace(/:.*$/, ''));
expect(commands).toEqual([
"/admin", "/ban", "/clear", "/deop", "/destroy",
"/help", "/kick", "/me", "/member", "/modtools", "/mute", "/nick",
"/op", "/register", "/revoke", "/subject", "/topic", "/voice"
]);
occupant.set('affiliation', 'member');
view.querySelector('.close-chat-help').click();
await u.waitUntil(() => view.querySelector('converse-chat-help') === null);
textarea.value = '/help';
message_form.onKeyDown(enter);
chat_help_el = await u.waitUntil(() => view.querySelector('converse-chat-help'));
info_messages = sizzle('.chat-info', chat_help_el);
expect(info_messages.length).toBe(9);
commands = info_messages.map(m => m.textContent.replace(/:.*$/, ''));
expect(commands).toEqual(["/clear", "/help", "/kick", "/me", "/modtools", "/mute", "/nick", "/register", "/voice"]);
view.querySelector('.close-chat-help').click();
await u.waitUntil(() => view.querySelector('converse-chat-help') === null);
expect(view.model.get('show_help_messages')).toBe(false);
occupant.set('role', 'participant');
// Role changes causes rerender, so we need to get the new textarea
textarea.value = '/help';
message_form.onKeyDown(enter);
await u.waitUntil(() => view.model.get('show_help_messages'));
chat_help_el = await u.waitUntil(() => view.querySelector('converse-chat-help'));
info_messages = sizzle('.chat-info', chat_help_el);
expect(info_messages.length).toBe(5);
commands = info_messages.map(m => m.textContent.replace(/:.*$/, ''));
expect(commands).toEqual(["/clear", "/help", "/me", "/nick", "/register"]);
// Test that /topic is available if all users may change the subject
// Note: we're making a shortcut here, this value should never be set manually
view.model.config.set('changesubject', true);
view.querySelector('.close-chat-help').click();
await u.waitUntil(() => view.querySelector('converse-chat-help') === null);
textarea.value = '/help';
message_form.onKeyDown(enter);
chat_help_el = await u.waitUntil(() => view.querySelector('converse-chat-help'));
info_messages = sizzle('.chat-info', chat_help_el);
expect(info_messages.length).toBe(7);
commands = info_messages.map(m => m.textContent.replace(/:.*$/, ''));
expect(commands).toEqual(["/clear", "/help", "/me", "/nick", "/register", "/subject", "/topic"]);
}));
it("takes /help to show the available commands and commands can be disabled by config",
mock.initConverse([], {muc_disable_slash_commands: ['mute', 'voice']}, async function (_converse) {
await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
const enter = { 'target': textarea, 'preventDefault': function () {}, key: "Enter" };
spyOn(window, 'confirm').and.callFake(() => true);
textarea.value = '/clear';
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown(enter);
textarea.value = '/help';
message_form.onKeyDown(enter);
await u.waitUntil(() => sizzle('.chat-info:not(.chat-event)', view).length);
const info_messages = sizzle('.chat-info:not(.chat-event)', view);
expect(info_messages.length).toBe(17);
expect(info_messages.pop().textContent.trim()).toBe('/topic: Set groupchat subject (alias for /subject)');
expect(info_messages.pop().textContent.trim()).toBe('/subject: Set groupchat subject');
expect(info_messages.pop().textContent.trim()).toBe('/revoke: Revoke the user\'s current affiliation');
expect(info_messages.pop().textContent.trim()).toBe('/register: Register your nickname');
expect(info_messages.pop().textContent.trim()).toBe('/owner: Grant ownership of this groupchat');
expect(info_messages.pop().textContent.trim()).toBe('/op: Grant moderator role to user');
expect(info_messages.pop().textContent.trim()).toBe('/nick: Change your nickname');
expect(info_messages.pop().textContent.trim()).toBe('/modtools: Opens up the moderator tools GUI');
expect(info_messages.pop().textContent.trim()).toBe('/member: Grant membership to a user');
expect(info_messages.pop().textContent.trim()).toBe('/me: Write in 3rd person');
expect(info_messages.pop().textContent.trim()).toBe('/kick: Kick user from groupchat');
expect(info_messages.pop().textContent.trim()).toBe('/help: Show this menu');
expect(info_messages.pop().textContent.trim()).toBe('/destroy: Remove this groupchat');
expect(info_messages.pop().textContent.trim()).toBe('/deop: Change user role to participant');
expect(info_messages.pop().textContent.trim()).toBe('/clear: Clear the chat area');
expect(info_messages.pop().textContent.trim()).toBe('/ban: Ban user by changing their affiliation to outcast');
expect(info_messages.pop().textContent.trim()).toBe('/admin: Change user\'s affiliation to admin');
}));
it("takes /member to make an occupant a member",
mock.initConverse([], {}, async function (_converse) {
let iq_stanza;
const nick = 'romeo';
const muc_jid = 'lounge@muc.montague.lit';
const muc = await mock.openAndEnterMUC(_converse, muc_jid, nick);
/* We don't show join/leave messages for existing occupants. We
* know about them because we receive their presences before we
* receive our own.
*/
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
xmlns="jabber:client"
to="romeo@montague.lit/orchard"
from="lounge@muc.montague.lit/marc">
<x xmlns="${Strophe.NS.MUC_USER}">
<item affiliation="none" jid="marc@montague.lit/_converse.js-290929789" role="participant"/>
</x>
</presence>`
));
await u.waitUntil(() => muc.occupants.length === 2);
const view = _converse.chatboxviews.get(muc_jid);
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
let sent_stanza;
spyOn(_converse.api.connection.get(), 'send').and.callFake((stanza) => {
sent_stanza = stanza;
});
// First check that an error message appears when a
// non-existent nick is used.
textarea.value = '/member chris Welcome to the club!';
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
key: "Enter",
});
expect(_converse.api.connection.get().send).not.toHaveBeenCalled();
await u.waitUntil(() => view.querySelectorAll('.chat-error').length);
expect(view.querySelector('.chat-error').textContent.trim())
.toBe('Error: couldn\'t find a groupchat participant based on your arguments');
// Now test with an existing nick
textarea.value = '/member marc Welcome to the club!';
message_form.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
key: "Enter",
});
await u.waitUntil(() => sent_stanza?.querySelector('item[affiliation="member"]'));
expect(sent_stanza).toEqualStanza(
stx`<iq id="${sent_stanza.getAttribute('id')}" to="lounge@muc.montague.lit" type="set" xmlns="jabber:client">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item affiliation="member" nick="marc" jid="marc@montague.lit">
<reason>Welcome to the club!</reason>
</item>
</query>
</iq>`);
let result = stx`<iq xmlns="jabber:client"
type="result"
to="romeo@montague.lit/orchard"
from="lounge@muc.montague.lit"
id="${sent_stanza.getAttribute('id')}"/>`;
_converse.api.connection.get().IQ_stanzas = [];
_converse.api.connection.get()._dataRecv(mock.createRequest(result));
iq_stanza = await u.waitUntil(() => _converse.api.connection.get().IQ_stanzas.filter(
iq => iq.querySelector('iq[to="lounge@muc.montague.lit"][type="get"] item[affiliation="member"]')).pop()
);
expect(Strophe.serialize(iq_stanza)).toBe(
`<iq id="${iq_stanza.getAttribute('id')}" to="lounge@muc.montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/muc#admin">`+
`<item affiliation="member"/>`+
`</query>`+
`</iq>`)
expect(view.model.occupants.length).toBe(2);
result = stx`<iq xmlns="jabber:client"
type="result"
to="romeo@montague.lit/orchard"
from="lounge@muc.montague.lit"
id="${iq_stanza.getAttribute("id")}">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item jid="marc" affiliation="member"/>
</query>
</iq>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(result));
expect(view.model.occupants.length).toBe(2);
iq_stanza = await u.waitUntil(() => _converse.api.connection.get().IQ_stanzas.filter(
iq => iq.querySelector('iq[to="lounge@muc.montague.lit"][type="get"] item[affiliation="owner"]')).pop()
);
expect(Strophe.serialize(iq_stanza)).toBe(
`<iq id="${iq_stanza.getAttribute('id')}" to="lounge@muc.montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/muc#admin">`+
`<item affiliation="owner"/>`+
`</query>`+
`</iq>`)
expect(view.model.occupants.length).toBe(2);
result = stx`<iq xmlns="jabber:client"
type="result"
to="romeo@montague.lit/orchard"
from="lounge@muc.montague.lit"
id="${iq_stanza.getAttribute("id")}">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item jid="romeo@montague.lit" affiliation="owner"/>
</query>
</iq>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(result));
expect(view.model.occupants.length).toBe(2);
iq_stanza = await u.waitUntil(() => _converse.api.connection.get().IQ_stanzas.filter(
iq => iq.querySelector('iq[to="lounge@muc.montague.lit"][type="get"] item[affiliation="admin"]')).pop()
);
expect(Strophe.serialize(iq_stanza)).toBe(
`<iq id="${iq_stanza.getAttribute('id')}" to="lounge@muc.montague.lit" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/muc#admin">`+
`<item affiliation="admin"/>`+
`</query>`+
`</iq>`)
expect(view.model.occupants.length).toBe(2);
result = stx`<iq xmlns="jabber:client"
type="result"
to="romeo@montague.lit/orchard"
from="lounge@muc.montague.lit"
id="${iq_stanza.getAttribute('id')}">
<query xmlns="http://jabber.org/protocol/muc#admin"></query>
</iq>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(result));
await u.waitUntil(() => view.querySelectorAll('.occupant').length, 500);
await u.waitUntil(() => view.querySelectorAll('.badge').length > 2);
expect(view.model.occupants.length).toBe(2);
expect(view.querySelectorAll('.occupant').length).toBe(2);
}));
it("takes /topic to set the groupchat topic", mock.initConverse([], {}, async function (_converse) {
await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
// Check the alias /topic
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = '/topic This is the groupchat subject';
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
key: "Enter",
});
const { sent_stanzas } = _converse.api.connection.get();
await u.waitUntil(() => sent_stanzas.filter(s => s.textContent.trim() === 'This is the groupchat subject'));
// Check /subject
textarea.value = '/subject This is a new subject';
message_form.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
key: "Enter",
});
let sent_stanza = await u.waitUntil(() => sent_stanzas.filter(s => s.textContent.trim() === 'This is a new subject').pop());
expect(sent_stanza).toEqualStanza(stx`
<message to="lounge@montague.lit" type="groupchat" xmlns="jabber:client">
<subject>This is a new subject</subject>
</message>`);
// Check case insensitivity
textarea.value = '/Subject This is yet another subject';
message_form.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
key: "Enter",
});
sent_stanza = await u.waitUntil(() => sent_stanzas.filter(s => s.textContent.trim() === 'This is yet another subject').pop());
expect(sent_stanza).toEqualStanza(stx`
<message to="lounge@montague.lit" type="groupchat" xmlns="jabber:client">
<subject>This is yet another subject</subject>
</message>`);
while (sent_stanzas.length) {
sent_stanzas.pop();
}
// Check unsetting the topic
textarea.value = '/topic';
message_form.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
key: "Enter",
});
sent_stanza = await u.waitUntil(() => sent_stanzas.pop());
expect(sent_stanza).toEqualStanza(stx`
<message to="lounge@montague.lit" type="groupchat" xmlns="jabber:client">
<subject></subject>
</message>`);
}));
it("takes /clear to clear messages", mock.initConverse([], {}, async function (_converse) {
await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = '/clear';
spyOn(_converse.api, 'confirm').and.callFake(() => Promise.resolve(false));
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
key: "Enter",
});
await u.waitUntil(() => _converse.api.confirm.calls.count() === 1);
expect(_converse.api.confirm).toHaveBeenCalledWith(
'Confirm',
'Are you sure you want to clear the messages from this conversation?'
);
}));
it("takes /owner to make a user an owner", mock.initConverse([], {}, async function (_converse) {
let sent_IQ, IQ_id;
const sendIQ = _converse.api.connection.get().sendIQ;
spyOn(_converse.api.connection.get(), 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_IQ = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback);
});
await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
spyOn(view.model, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
from="lounge@montague.lit/annoyingGuy"
id="27C55F89-1C6A-459A-9EB5-77690145D624"
to="romeo@montague.lit/desktop"
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<item jid="annoyingguy@montague.lit" affiliation="member" role="participant"/>
</x>
</presence>`));
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = '/owner';
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
key: "Enter",
});
await u.waitUntil(() => view.model.validateRoleOrAffiliationChangeArgs.calls.count());
const err_msg = await u.waitUntil(() => view.querySelector('.chat-error'));
expect(err_msg.textContent.trim()).toBe(
"Error: the \"owner\" command takes two arguments, the user's nickname and optionally a reason.");
const sel = 'iq[type="set"] query[xmlns="http://jabber.org/protocol/muc#admin"]';
const stanzas = _converse.api.connection.get().IQ_stanzas.filter(s => sizzle(sel, s).length);
expect(stanzas.length).toBe(0);
// XXX: Calling onFormSubmitted directly, trying
// again via triggering Event doesn't work for some weird
// reason.
textarea.value = '/owner nobody You\'re responsible';
message_form.onFormSubmitted(new Event('submit'));
await u.waitUntil(() => view.querySelectorAll('.chat-error').length === 2);
expect(Array.from(view.querySelectorAll('.chat-error')).pop().textContent.trim()).toBe(
"Error: couldn't find a groupchat participant based on your arguments");
expect(_converse.api.connection.get().IQ_stanzas.filter(s => sizzle(sel, s).length).length).toBe(0);
// Call now with the correct of arguments.
// XXX: Calling onFormSubmitted directly, trying
// again via triggering Event doesn't work for some weird
// reason.
textarea.value = '/owner annoyingGuy You\'re responsible';
message_form.onFormSubmitted(new Event('submit'));
await u.waitUntil(() => view.model.validateRoleOrAffiliationChangeArgs.calls.count() === 3);
// Check that the member list now gets updated
expect(sent_IQ).toEqualStanza(
stx`<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item affiliation="owner" nick="annoyingGuy" jid="annoyingguy@montague.lit">
<reason>You're responsible</reason>
</item>
</query>
</iq>`);
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
from="lounge@montague.lit/annoyingGuy"
id="27C55F89-1C6A-459A-9EB5-77690145D628"
to="romeo@montague.lit/desktop"
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<item jid="annoyingguy@montague.lit" affiliation="owner" role="participant"/>
</x>
</presence>`));
await u.waitUntil(() =>
Array.from(view.querySelectorAll('.chat-info__message')).pop()?.textContent.trim() ===
"annoyingGuy is now an owner of this groupchat"
);
}));
it("takes /ban to ban a user", mock.initConverse([], {}, async function (_converse) {
let sent_IQ, IQ_id;
const sendIQ = _converse.api.connection.get().sendIQ;
spyOn(_converse.api.connection.get(), 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_IQ = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback);
});
await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'romeo');
const view = _converse.chatboxviews.get('lounge@montague.lit');
spyOn(view.model, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
_converse.api.connection.get()._dataRecv(
mock.createRequest(
stx`<presence
from="lounge@montague.lit/annoyingGuy"
id="27C55F89-1C6A-459A-9EB5-77690145D624"
to="romeo@montague.lit/desktop"
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<item jid="annoyingguy@montague.lit" affiliation="member" role="participant"/>
</x>
</presence>`));
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = '/ban';
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
key: "Enter",
});
await u.waitUntil(() => view.model.validateRoleOrAffiliationChangeArgs.calls.count());
await u.waitUntil(() => view.querySelector('.message:last-child')?.textContent?.trim() ===
"Error: the \"ban\" command takes two arguments, the user's nickname and optionally a reason.");
const sel = 'iq[type="set"] query[xmlns="http://jabber.org/protocol/muc#admin"]';
const stanzas = _converse.api.connection.get().IQ_stanzas.filter(s => sizzle(sel, s).length);
expect(stanzas.length).toBe(0);
// Call now with the correct amount of arguments.
// XXX: Calling onFormSubmitted directly, trying
// again via triggering Event doesn't work for some weird
// reason.
textarea.value = '/ban annoyingGuy You\'re annoying';
message_form.onFormSubmitted(new Event('submit'));
await u.waitUntil(() => view.model.validateRoleOrAffiliationChangeArgs.calls.count() === 2);
// Check that the member list now gets updated
expect(sent_IQ).toEqualStanza(
stx`<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item affiliation="outcast" jid="annoyingguy@montague.lit">
<reason>You're annoying</reason>
</item>
</query>
</iq>`);
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
from='lounge@montague.lit/annoyingGuy'
id='27C55F89-1C6A-459A-9EB5-77690145D628'
to='romeo@montague.lit/desktop'
xmlns="jabber:client">
<x xmlns='http://jabber.org/protocol/muc#user'>
<item jid='annoyingguy@montague.lit' affiliation='outcast' role='participant'>
<actor nick='romeo'/>
<reason>You're annoying</reason>
<status code='301'/>
</item>
</x>
</presence>`));
await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 2);
expect(view.querySelectorAll('.chat-info__message')[1].textContent.trim()).toBe("annoyingGuy has been banned by romeo");
expect(view.querySelector('.chat-info:last-child q').textContent.trim()).toBe("You're annoying");
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
from="lounge@montague.lit/joe2"
id="27C55F89-1C6A-459A-9EB5-77690145D624"
to="romeo@montague.lit/desktop"
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<item jid="joe2@montague.lit" affiliation="member" role="participant"/>
</x>
</presence>`
));
textarea.value = '/ban joe22';
message_form.onFormSubmitted(new Event('submit'));
await u.waitUntil(() => view.querySelector('converse-chat-message:last-child')?.textContent?.trim() ===
"Error: couldn't find a groupchat participant based on your arguments");
}));
it("takes a /kick command to kick a user", mock.initConverse([], {}, async function (_converse) {
let sent_IQ, IQ_id;
const sendIQ = _converse.api.connection.get().sendIQ;
spyOn(_converse.api.connection.get(), 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_IQ = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback);
});
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterMUC(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
spyOn(view.model, 'setRole').and.callThrough();
spyOn(view.model, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
let presence = stx`<presence
from='lounge@montague.lit/annoying guy'
id='27C55F89-1C6A-459A-9EB5-77690145D624'
to='romeo@montague.lit/desktop'
xmlns="jabber:client">
<x xmlns='http://jabber.org/protocol/muc#user'>
<item jid='annoyingguy@montague.lit' affiliation='none' role='participant'/>
</x>
</presence>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = '/kick';
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
key: "Enter",
});
await u.waitUntil(() => view.model.validateRoleOrAffiliationChangeArgs.calls.count());
await u.waitUntil(() => view.querySelector('.message:last-child')?.textContent?.trim() ===
"Error: the \"kick\" command takes two arguments, the user's nickname and optionally a reason.");
expect(view.model.setRole).not.toHaveBeenCalled();
// Call now with the correct amount of arguments.
// XXX: Calling onFormSubmitted directly, trying
// again via triggering Event doesn't work for some weird
// reason.
textarea.value = '/kick @annoying guy You\'re annoying';
message_form.onFormSubmitted(new Event('submit'));
await u.waitUntil(() => view.model.validateRoleOrAffiliationChangeArgs.calls.count() === 2);
expect(view.model.setRole).toHaveBeenCalled();
expect(Strophe.serialize(sent_IQ)).toBe(
`<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/muc#admin">`+
`<item nick="annoying guy" role="none">`+
`<reason>You're annoying</reason>`+
`</item>`+
`</query>`+
`</iq>`);
presence = stx`<presence
from="lounge@montague.lit/annoying guy"
to="romeo@montague.lit/desktop"
type="unavailable"
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" role="none">
<actor nick="romeo"/>
<reason>You're annoying</reason>
<status code="307"/>
</item>
</x>
</presence>`;
_converse.api.connection.get()._dataRecv(mock.createRequest(presence));
await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 2);
expect(view.querySelectorAll('.chat-info__message')[1].textContent.trim()).toBe("annoying guy has been kicked out by romeo");
expect(view.querySelector('.chat-info:last-child q').textContent.trim()).toBe("You're annoying");
}));
it("takes /op and /deop to make a user a moderator or not",
mock.initConverse([], {}, async function (_converse) {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterMUC(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
let sent_IQ, IQ_id;
const sendIQ = _converse.api.connection.get().sendIQ;
spyOn(_converse.api.connection.get(), 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_IQ = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback);
});
spyOn(view.model, 'setRole').and.callThrough();
spyOn(view.model, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
// New user enters the groupchat
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
from="lounge@montague.lit/trustworthyguy"
id="27C55F89-1C6A-459A-9EB5-77690145D624"
to="romeo@montague.lit/desktop"
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<item jid="trustworthyguy@montague.lit" affiliation="member" role="participant"/>
</x>
</presence>`));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"romeo and trustworthyguy have entered the groupchat");
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = '/op';
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
key: "Enter",
});
await u.waitUntil(() => view.model.validateRoleOrAffiliationChangeArgs.calls.count());
await u.waitUntil(() => view.querySelector('.message:last-child')?.textContent?.trim() ===
"Error: the \"op\" command takes two arguments, the user's nickname and optionally a reason.");
expect(view.model.setRole).not.toHaveBeenCalled();
// Call now with the correct amount of arguments.
// XXX: Calling onFormSubmitted directly, trying
// again via triggering Event doesn't work for some weird
// reason.
textarea.value = '/op trustworthyguy You\'re trustworthy';
message_form.onFormSubmitted(new Event('submit'));
await u.waitUntil(() => view.model.validateRoleOrAffiliationChangeArgs.calls.count() === 2);
expect(view.model.setRole).toHaveBeenCalled();
expect(Strophe.serialize(sent_IQ)).toBe(
`<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/muc#admin">`+
`<item nick="trustworthyguy" role="moderator">`+
`<reason>You're trustworthy</reason>`+
`</item>`+
`</query>`+
`</iq>`);
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
from="lounge@montague.lit/trustworthyguy"
to="romeo@montague.lit/desktop"
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<item jid="trustworthyguy@montague.lit" affiliation="member" role="moderator"/>
</x>
</presence>`));
// Check now that things get restored when the user is given a voice
await u.waitUntil(
() => view.querySelector('.chat-content__notifications').textContent.split('\n', 2).pop()?.trim() ===
"trustworthyguy is now a moderator");
// Call now with the correct amount of arguments.
// XXX: Calling onFormSubmitted directly, trying
// again via triggering Event doesn't work for some weird
// reason.
textarea.value = '/deop trustworthyguy Perhaps not';
message_form.onFormSubmitted(new Event('submit'));
await u.waitUntil(() => view.model.validateRoleOrAffiliationChangeArgs.calls.count() === 3);
expect(view.model.setRole).toHaveBeenCalled();
expect(sent_IQ).toEqualStanza(stx`
<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item nick="trustworthyguy" role="participant">
<reason>Perhaps not</reason>
</item>
</query>
</iq>`);
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
from="lounge@montague.lit/trustworthyguy"
to="romeo@montague.lit/desktop"
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<item jid="trustworthyguy@montague.lit" affiliation="member" role="participant"/>
</x>
</presence>`));
await u.waitUntil(() => view.querySelector('.chat-content__notifications')
.textContent.includes("trustworthyguy is no longer a moderator"));
}));
it("takes /mute and /voice to mute and unmute a user",
mock.initConverse([], {}, async function (_converse) {
const muc_jid = 'lounge@montague.lit';
await mock.openAndEnterMUC(_converse, muc_jid, 'romeo');
const view = _converse.chatboxviews.get(muc_jid);
var sent_IQ, IQ_id;
var sendIQ = _converse.api.connection.get().sendIQ;
spyOn(_converse.api.connection.get(), 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_IQ = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback);
});
spyOn(view.model, 'setRole').and.callThrough();
spyOn(view.model, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
// New user enters the groupchat
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
from="lounge@montague.lit/annoyingGuy"
id="27C55F89-1C6A-459A-9EB5-77690145D624"
to="romeo@montague.lit/desktop"
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<item jid="annoyingguy@montague.lit" affiliation="member" role="participant"/>
</x>
</presence>`));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() ===
"romeo and annoyingGuy have entered the groupchat");
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = '/mute';
const message_form = view.querySelector('converse-muc-message-form');
message_form.onKeyDown({
target: textarea,
preventDefault: function preventDefault () {},
key: "Enter",
});
await u.waitUntil(() => view.model.validateRoleOrAffiliationChangeArgs.calls.count());
await u.waitUntil(() => view.querySelector('.message:last-child')?.textContent?.trim() ===
"Error: the \"mute\" command takes two arguments, the user's nickname and optionally a reason.");
expect(view.model.setRole).not.toHaveBeenCalled();
// Call now with the correct amount of arguments.
// XXX: Calling onFormSubmitted directly, trying
// again via triggering Event doesn't work for some weird
// reason.
textarea.value = '/mute annoyingGuy You\'re annoying';
message_form.onFormSubmitted(new Event('submit'));
await u.waitUntil(() => view.model.validateRoleOrAffiliationChangeArgs.calls.count() === 2)
expect(view.model.setRole).toHaveBeenCalled();
expect(sent_IQ).toEqualStanza(stx`
<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item nick="annoyingGuy" role="visitor">
<reason>You're annoying</reason>
</item>
</query>
</iq>`);
/* <presence
* from='coven@chat.shakespeare.lit/thirdwitch'
* to='crone1@shakespeare.lit/desktop'>
* <x xmlns='http://jabber.org/protocol/muc#user'>
* <item affiliation='member'
* jid='hag66@shakespeare.lit/pda'
* role='visitor'/>
* </x>
* </presence>
*/
_converse.api.connection.get()._dataRecv(mock.createRequest(stx`<presence
from="lounge@montague.lit/annoyingGuy"
to="romeo@montague.lit/desktop"
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<item jid="annoyingguy@montague.lit" affiliation="member" role="visitor"/>
</x>
</presence>`));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.includes("annoyingGuy has been muted"));
// Call now with the correct of arguments.
// XXX: Calling onFormSubmitted directly, trying
// again via triggering Event doesn't work for some weird
// reason.
textarea.value = '/voice annoyingGuy Now you can talk again';
message_form.onFormSubmitted(new Event('submit'));
await u.waitUntil(() => view.model.validateRoleOrAffiliationChangeArgs.calls.count() === 3);
expect(view.model.setRole).toHaveBeenCalled();
expect(sent_IQ).toEqualStanza(stx`
<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item nick="annoyingGuy" role="participant">
<reason>Now you can talk again</reason>
</item>
</query>
</iq>`);
_converse.api.connection.get()._dataRecv(mock.createRequest(
stx`<presence
from="lounge@montague.lit/annoyingGuy"
to="romeo@montague.lit/desktop"
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc#user">
<item jid="annoyingguy@montague.lit" affiliation="member" role="participant"/>
</x>
</presence>`));
await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.includes("annoyingGuy has been given a voice"));
}));
it("takes /destroy to destroy a muc",
mock.initConverse([], {}, async function (_converse) {
const muc_jid = 'lounge@montague.lit';
const new_muc_jid = 'foyer@montague.lit';
await mock.openAndEnterMUC(_converse, muc_jid, 'romeo');
let view = _converse.chatboxviews.get(muc_jid);
spyOn(_converse.api, 'confirm').and.callThrough();
let textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = '/destroy';
let message_form = view.querySelector('converse-muc-message-form');
message_form.onFormSubmitted(new Event('submit'));
let modal = await u.waitUntil(() => document.querySelector('converse-confirm-modal .modal-dialog'));
await u.waitUntil(() => u.isVisible(modal));
let challenge_el = modal.querySelector('[name="challenge"]');
challenge_el.value = muc_jid+'e';
const reason_el = modal.querySelector('[name="reason"]');
reason_el.value = 'Moved to a new location';
const newjid_el = modal.querySelector('[name="newjid"]');
newjid_el.value = new_muc_jid;
let submit = modal.querySelector('[type="submit"]');
submit.click();
expect(u.isVisible(modal)).toBeTruthy();
await u.waitUntil(() => u.hasClass('is-invalid', challenge_el));
challenge_el.value = muc_jid;
submit.click();
let sent_IQs = _converse.api.connection.get().IQ_stanzas;
let sent_IQ = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector('destroy')).pop());
expect(sent_IQ).toEqualStanza(stx`
<iq id="${sent_IQ.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client">
<query xmlns="http://jabber.org/protocol/muc#owner">
<destroy jid="${new_muc_jid}">
<reason>Moved to a new location</reason>
</destroy>
</query>
</iq>`);
let result_stanza = stx`<iq type="result"
id="${sent_IQ.getAttribute('id')}"
from="${view.model.get('jid')}"
to="${_converse.api.connection.get().jid}"
xmlns="jabber:client"/>`
expect(_converse.chatboxes.length).toBe(2);
spyOn(_converse.api, "trigger").and.callThrough();
_converse.api.connection.get()._dataRecv(mock.createRequest(result_stanza));
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED));
await u.waitUntil(() => _converse.chatboxes.length === 1);
expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
// Try again without reason or new JID
_converse.api.connection.get().IQ_stanzas = [];
sent_IQs = _converse.api.connection.get().IQ_stanzas;
await mock.openAndEnterMUC(_converse, new_muc_jid, 'romeo');
view = _converse.chatboxviews.get(new_muc_jid);
textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = '/destroy';
message_form = view.querySelector('converse-muc-message-form');
message_form.onFormSubmitte