UNPKG

converse.js

Version:
899 lines (800 loc) 51.4 kB
/*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&apos;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&apos;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&apos;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&apos;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&apos;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