From a9e3b237b24388d3eac4de6bd34bb85a30a99764 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Wed, 4 Sep 2024 17:32:39 +0200 Subject: [PATCH] More usage of the `stx` tagged template literal --- karma.conf.js | 4 + src/headless/plugins/muc/tests/muc.js | 19 +- src/plugins/muc-views/tests/actions.js | 122 +- src/plugins/muc-views/tests/autocomplete.js | 204 +- src/plugins/muc-views/tests/commands.js | 939 +++++ src/plugins/muc-views/tests/component.js | 1 - src/plugins/muc-views/tests/corrections.js | 199 +- src/plugins/muc-views/tests/csn.js | 266 ++ src/plugins/muc-views/tests/disco.js | 65 +- src/plugins/muc-views/tests/emojis.js | 23 +- src/plugins/muc-views/tests/hats.js | 34 +- .../muc-views/tests/http-file-upload.js | 23 +- src/plugins/muc-views/tests/info-messages.js | 17 +- src/plugins/muc-views/tests/mam.js | 97 +- src/plugins/muc-views/tests/markers.js | 20 +- src/plugins/muc-views/tests/me-messages.js | 53 +- src/plugins/muc-views/tests/member-lists.js | 130 +- src/plugins/muc-views/tests/mentions.js | 238 +- src/plugins/muc-views/tests/mep.js | 133 +- src/plugins/muc-views/tests/modtools.js | 112 +- src/plugins/muc-views/tests/muc-api.js | 48 +- src/plugins/muc-views/tests/muc-avatar.js | 132 +- src/plugins/muc-views/tests/muc-list-modal.js | 61 +- src/plugins/muc-views/tests/muc-mentions.js | 19 +- src/plugins/muc-views/tests/muc-messages.js | 215 +- .../muc-views/tests/muc-registration.js | 29 +- src/plugins/muc-views/tests/muc.js | 3163 +++++------------ src/plugins/muc-views/tests/mute.js | 124 + src/plugins/muc-views/tests/nickname.js | 272 +- .../muc-views/tests/occupants-filter.js | 21 +- src/plugins/muc-views/tests/probes.js | 77 + src/plugins/muc-views/tests/rai.js | 52 +- src/plugins/muc-views/tests/retractions.js | 387 +- src/plugins/muc-views/tests/styling.js | 34 +- src/plugins/muc-views/tests/unfurls.js | 82 +- src/plugins/muc-views/tests/xss.js | 32 +- 36 files changed, 3691 insertions(+), 3756 deletions(-) create mode 100644 src/plugins/muc-views/tests/commands.js create mode 100644 src/plugins/muc-views/tests/csn.js create mode 100644 src/plugins/muc-views/tests/mute.js create mode 100644 src/plugins/muc-views/tests/probes.js diff --git a/karma.conf.js b/karma.conf.js index 4f843a87ad..5591d076cb 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -73,8 +73,10 @@ module.exports = function(config) { { pattern: "src/plugins/minimize/tests/minchats.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/actions.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/autocomplete.js", type: 'module' }, + { pattern: "src/plugins/muc-views/tests/commands.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/component.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/corrections.js", type: 'module' }, + { pattern: "src/plugins/muc-views/tests/csn.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/disco.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/emojis.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/hats.js", type: 'module' }, @@ -95,9 +97,11 @@ module.exports = function(config) { { pattern: "src/plugins/muc-views/tests/muc-messages.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/muc-registration.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/muc.js", type: 'module' }, + { pattern: "src/plugins/muc-views/tests/mute.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/nickname.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/occupants-filter.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/occupants.js", type: 'module' }, + { pattern: "src/plugins/muc-views/tests/probes.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/rai.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/retractions.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/styling.js", type: 'module' }, diff --git a/src/headless/plugins/muc/tests/muc.js b/src/headless/plugins/muc/tests/muc.js index 776bddaa20..eeabff31b8 100644 --- a/src/headless/plugins/muc/tests/muc.js +++ b/src/headless/plugins/muc/tests/muc.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { Strophe, sizzle, u } = converse.env; +const { Strophe, sizzle, stx, u } = converse.env; describe("Groupchats", function () { @@ -13,18 +13,18 @@ describe("Groupchats", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, nick, [], [], false, {'hidden': true}); const model = _converse.chatboxes.get(muc_jid); - _converse.api.connection.get()._dataRecv(mock.createRequest(u.toStanza(` + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` Romeo oh romeo - `))); + `)); await u.waitUntil(() => model.messages.length); expect(model.get('num_unread_general')).toBe(1); expect(model.get('num_unread')).toBe(1); - _converse.api.connection.get()._dataRecv(mock.createRequest(u.toStanza(` + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` Wherefore art though? - `))); + `)); await u.waitUntil(() => model.messages.length === 2); @@ -101,7 +101,7 @@ describe("Groupchats", function () { expect(model.session.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED); model.sendMessage({'body': 'hello world'}); - const stanza = u.toStanza(` + const stanza = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); let sent_stanzas = _converse.api.connection.get().sent_stanzas; @@ -119,15 +119,16 @@ describe("Groupchats", function () { ``+ ``); - const result = u.toStanza(` + const result = stx` - `); + `; sent_stanzas = _converse.api.connection.get().sent_stanzas; const index = sent_stanzas.length -1; diff --git a/src/plugins/muc-views/tests/actions.js b/src/plugins/muc-views/tests/actions.js index 98991007f2..0aa0395508 100644 --- a/src/plugins/muc-views/tests/actions.js +++ b/src/plugins/muc-views/tests/actions.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { $msg, u } = converse.env; +const { Strophe, u, stx } = converse.env; describe("A Groupchat Message", function () { @@ -9,16 +9,16 @@ describe("A Groupchat Message", function () { const muc_jid = 'lounge@montague.lit'; const model = await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); - const stanza = $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' - }).tree(); + + const stanza = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); const view = _converse.chatboxviews.get(muc_jid); @@ -28,12 +28,15 @@ describe("A Groupchat Message", function () { const firstMessageText = 'But soft, what light through yonder airlock breaks?'; const msg_id = u.getUniqueId(); - await model.handleMessageStanza($msg({ - 'from': 'lounge@montague.lit/newguy', - 'to': _converse.api.connection.get().jid, - 'type': 'groupchat', - 'id': msg_id, - }).c('body').t(firstMessageText).tree()); + await model.handleMessageStanza(stx` + + ${firstMessageText} + `); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1); let firstAction = view.querySelector('.chat-msg__action-copy'); expect(firstAction).not.toBeNull(); @@ -61,26 +64,28 @@ describe("A Groupchat Message", function () { const muc_jid = 'lounge@montague.lit'; const model = await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); - const stanza = $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' - }).tree(); + const stanza = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); const firstMessageText = 'But soft, what light through yonder airlock breaks?'; const msg_id = u.getUniqueId(); - await model.handleMessageStanza($msg({ - 'from': 'lounge@montague.lit/newguy', - 'to': _converse.api.connection.get().jid, - 'type': 'groupchat', - 'id': msg_id, - }).c('body').t(firstMessageText).tree()); + await model.handleMessageStanza(stx` + + ${firstMessageText} + `); const view = _converse.chatboxviews.get(muc_jid); const textarea = await u.waitUntil(() => view.querySelector('textarea.chat-textarea')); @@ -107,37 +112,44 @@ describe("A Groupchat Message", function () { mock.initConverse([], {}, async function (_converse) { const muc_jid = 'lounge@montague.lit'; const model = await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', ['muc_moderated']); - const stanza = $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' - }).tree(); + const stanza = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); const view = _converse.chatboxviews.get(muc_jid); const msg_id = u.getUniqueId(); - await model.handleMessageStanza($msg({ - 'from': 'lounge@montague.lit/newguy', - 'to': _converse.api.connection.get().jid, - 'type': 'groupchat', - 'id': msg_id, - }).c('body').t('But soft, what light through yonder airlock breaks?').tree()); + await model.handleMessageStanza(stx` + + But soft, what light through yonder airlock breaks? + `); + await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1); // Quoting should be available before losing permission to speak expect(view.querySelector('.chat-msg__action-quote')).not.toBeNull(); - const presence = $pres({ - to: 'romeo@montague.lit/orchard', - from: `${muc_jid}/romeo` - }).c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', {'affiliation': 'none', 'role': 'visitor'}).up() - .c('status', {code: '110'}); + const presence = stx` + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); const occupant = view.model.occupants.findWhere({'jid': _converse.bare_jid}); await u.waitUntil(() => occupant.get('role') === 'visitor'); diff --git a/src/plugins/muc-views/tests/autocomplete.js b/src/plugins/muc-views/tests/autocomplete.js index c906bb318c..257aeef867 100644 --- a/src/plugins/muc-views/tests/autocomplete.js +++ b/src/plugins/muc-views/tests/autocomplete.js @@ -1,9 +1,6 @@ /*global mock, converse */ -const $pres = converse.env.$pres; -const $msg = converse.env.$msg; -const Strophe = converse.env.Strophe; -const u = converse.env.utils; +const { Strophe, u, stx } = converse.env; describe("The nickname autocomplete feature", function () { @@ -13,30 +10,29 @@ describe("The nickname autocomplete feature", function () { await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'tom'); const view = _converse.chatboxviews.get('lounge@montague.lit'); - // Nicknames from presences ['dick', 'harry'].forEach((nick) => { _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({ - 'to': 'tom@montague.lit/resource', - 'from': `lounge@montague.lit/${nick}` - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `${nick}@montague.lit/resource`, - 'role': 'participant' - }))); + stx` + + + + `)); }); // Nicknames from messages - const msg = $msg({ - from: 'lounge@montague.lit/jane', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t('Hello world').tree(); - await view.model.handleMessageStanza(msg); + await view.model.handleMessageStanza( + stx` + Hello world + `.tree()); await u.waitUntil(() => view.model.messages.last()?.get('received')); // Test that pressing @ brings up all options @@ -81,26 +77,26 @@ describe("The nickname autocomplete feature", function () { // Nicknames from presences ['dick', 'harry'].forEach((nick) => { _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({ - 'to': 'tom@montague.lit/resource', - 'from': `lounge@montague.lit/${nick}` - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `${nick}@montague.lit/resource`, - 'role': 'participant' - }))); + stx` + + + + `)); }); // Nicknames from messages - const msg = $msg({ - from: 'lounge@montague.lit/jane', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t('Hello world').tree(); - await view.model.handleMessageStanza(msg); + await view.model.handleMessageStanza( + stx` + Hello world + `.tree()); await u.waitUntil(() => view.model.messages.last()?.get('received')); // Test that pressing @ brings up all options @@ -147,26 +143,27 @@ describe("The nickname autocomplete feature", function () { // Nicknames from presences ['dick', 'harry'].forEach((nick) => { _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({ - 'to': 'tom@montague.lit/resource', - 'from': `lounge@montague.lit/${nick}` - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `${nick}@montague.lit/resource`, - 'role': 'participant' - }))); + stx` + + + + `)) }); // Nicknames from messages - const msg = $msg({ - from: 'lounge@montague.lit/jane', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t('Hello world').tree(); - await view.model.handleMessageStanza(msg); + await view.model.handleMessageStanza( + stx` + Hello world + `); + await u.waitUntil(() => view.model.messages.last()?.get('received')); // Test that pressing @ brings up all options @@ -210,16 +207,14 @@ describe("The nickname autocomplete feature", function () { // Nicknames from presences ['bernard', 'naber', 'helberlo', 'john', 'jones'].forEach((nick) => { _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({ - 'to': 'tom@montague.lit/resource', - 'from': `lounge@montague.lit/${nick}` - }) - .c('x', { xmlns: Strophe.NS.MUC_USER }) - .c('item', { - 'affiliation': 'none', - 'jid': `${nick}@montague.lit/resource`, - 'role': 'participant' - }))); + stx` + + + + `)); }); const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea')); @@ -270,16 +265,14 @@ describe("The nickname autocomplete feature", function () { await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); const view = _converse.chatboxviews.get('lounge@montague.lit'); expect(view.model.occupants.length).toBe(1); - let presence = $pres({ - 'to': 'romeo@montague.lit/orchard', - 'from': 'lounge@montague.lit/some1' - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'some1@montague.lit/resource', - 'role': 'participant' - }); + let presence = stx` + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); expect(view.model.occupants.length).toBe(2); @@ -316,16 +309,14 @@ describe("The nickname autocomplete feature", function () { } await u.waitUntil(() => view.querySelector('.suggestion-box__results').hidden === true); - presence = $pres({ - 'to': 'romeo@montague.lit/orchard', - 'from': 'lounge@montague.lit/some2' - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'some2@montague.lit/resource', - 'role': 'participant' - }); + presence = stx` + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); textarea.value = "hello s s"; @@ -356,17 +347,16 @@ describe("The nickname autocomplete feature", function () { expect(textarea.value).toBe('hello s @some2 '); // Test that pressing tab twice selects - presence = $pres({ - 'to': 'romeo@montague.lit/orchard', - 'from': 'lounge@montague.lit/z3r0' - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'z3r0@montague.lit/resource', - 'role': 'participant' - }); - _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); + _converse.api.connection.get()._dataRecv(mock.createRequest( + stx` + + + + `)); + textarea.value = "hello z"; message_form.onKeyDown(tab_event); message_form.onKeyUp(tab_event); @@ -383,17 +373,15 @@ describe("The nickname autocomplete feature", function () { await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); const view = _converse.chatboxviews.get('lounge@montague.lit'); expect(view.model.occupants.length).toBe(1); - const presence = $pres({ - 'to': 'romeo@montague.lit/orchard', - 'from': 'lounge@montague.lit/some1' - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'some1@montague.lit/resource', - 'role': 'participant' - }); - _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); + _converse.api.connection.get()._dataRecv(mock.createRequest( + stx` + + + + `)); expect(view.model.occupants.length).toBe(2); const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea')); diff --git a/src/plugins/muc-views/tests/commands.js b/src/plugins/muc-views/tests/commands.js new file mode 100644 index 0000000000..189d113bfd --- /dev/null +++ b/src/plugins/muc-views/tests/commands.js @@ -0,0 +1,939 @@ +/*global mock, converse */ + +const { Strophe, Promise, sizzle, stx, u } = converse.env; + +describe("Groupchats", function () { + 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.openAndEnterChatRoom(_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 () {}, 'keyCode': 13 }; + 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.openAndEnterChatRoom(_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 () {}, 'keyCode': 13 }; + 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; + await mock.openAndEnterChatRoom(_converse, 'lounge@muc.montague.lit', 'romeo'); + const view = _converse.chatboxviews.get('lounge@muc.montague.lit'); + + /* 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` + + + + ` + )); + expect(view.model.occupants.length).toBe(2); + + 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 () {}, + keyCode: 13 + }); + 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 () {}, + keyCode: 13 + }); + await u.waitUntil(() => Strophe.serialize(sent_stanza) === + ``+ + ``+ + ``+ + `Welcome to the club!`+ + ``+ + ``+ + ``); + + let result = stx``; + _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( + ``+ + ``+ + ``+ + ``+ + ``) + expect(view.model.occupants.length).toBe(2); + + result = stx` + + + + `; + _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( + ``+ + ``+ + ``+ + ``+ + ``) + expect(view.model.occupants.length).toBe(2); + + result = stx` + + + + `; + _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( + ``+ + ``+ + ``+ + ``+ + ``) + expect(view.model.occupants.length).toBe(2); + + result = stx` + + `; + _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.openAndEnterChatRoom(_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 () {}, + keyCode: 13 + }); + 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 () {}, + keyCode: 13 + }); + + let sent_stanza = await u.waitUntil(() => sent_stanzas.filter(s => s.textContent.trim() === 'This is a new subject').pop()); + expect(Strophe.serialize(sent_stanza).toLocaleString()).toBe( + ''+ + 'This is a new subject'+ + ''); + + // Check case insensitivity + textarea.value = '/Subject This is yet another subject'; + message_form.onKeyDown({ + target: textarea, + preventDefault: function preventDefault () {}, + keyCode: 13 + }); + sent_stanza = await u.waitUntil(() => sent_stanzas.filter(s => s.textContent.trim() === 'This is yet another subject').pop()); + expect(Strophe.serialize(sent_stanza).toLocaleString()).toBe( + ''+ + 'This is yet another subject'+ + ''); + + while (sent_stanzas.length) { + sent_stanzas.pop(); + } + // Check unsetting the topic + textarea.value = '/topic'; + message_form.onKeyDown({ + target: textarea, + preventDefault: function preventDefault () {}, + keyCode: 13 + }); + sent_stanza = await u.waitUntil(() => sent_stanzas.pop()); + expect(Strophe.serialize(sent_stanza).toLocaleString()).toBe( + ''+ + ''+ + ''); + })); + + it("takes /clear to clear messages", mock.initConverse([], {}, async function (_converse) { + await mock.openAndEnterChatRoom(_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 () {}, + keyCode: 13 + }); + await u.waitUntil(() => _converse.api.confirm.calls.count() === 1); + expect(_converse.api.confirm).toHaveBeenCalledWith('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.openAndEnterChatRoom(_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` + + + + `)); + + 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 () {}, + keyCode: 13 + }); + 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(Strophe.serialize(sent_IQ)).toBe( + ``+ + ``+ + ``+ + `You're responsible`+ + ``+ + ``+ + ``); + + _converse.api.connection.get()._dataRecv(mock.createRequest( + stx` + + + + `)); + 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.openAndEnterChatRoom(_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` + + + + `)); + + 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 () {}, + keyCode: 13 + }); + 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(Strophe.serialize(sent_IQ)).toBe( + ``+ + ``+ + ``+ + `You're annoying`+ + ``+ + ``+ + ``); + + _converse.api.connection.get()._dataRecv(mock.createRequest( + stx` + + + + You're annoying + + + + `)); + + 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` + + + + ` + )); + + 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.openAndEnterChatRoom(_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` + + + + `; + _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 () {}, + keyCode: 13 + }); + 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( + ``+ + ``+ + ``+ + `You're annoying`+ + ``+ + ``+ + ``); + + presence = stx` + + + + You're annoying + + + + `; + + _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.openAndEnterChatRoom(_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` + + + + `)); + + 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 () {}, + keyCode: 13 + }); + + 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( + ``+ + ``+ + ``+ + `You're trustworthy`+ + ``+ + ``+ + ``); + + /* + * + * + * + * + */ + _converse.api.connection.get()._dataRecv(mock.createRequest( + stx` + + + + `)); + // 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(Strophe.serialize(sent_IQ)).toBe( + ``+ + ``+ + ``+ + `Perhaps not`+ + ``+ + ``+ + ``); + + _converse.api.connection.get()._dataRecv(mock.createRequest( + stx` + + + + `)); + 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.openAndEnterChatRoom(_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` + + + + `)); + 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 () {}, + keyCode: 13 + }); + + 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(Strophe.serialize(sent_IQ)).toBe( + ``+ + ``+ + ``+ + `You're annoying`+ + ``+ + ``+ + ``); + + /* + * + * + * + * + */ + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + + + + `)); + 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(Strophe.serialize(sent_IQ)).toBe( + ``+ + ``+ + ``+ + `Now you can talk again`+ + ``+ + ``+ + ``); + + _converse.api.connection.get()._dataRecv(mock.createRequest( + stx` + + + + `)); + 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.openAndEnterChatRoom(_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('.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(); + expect(u.hasClass('error', challenge_el)).toBeTruthy(); + 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(Strophe.serialize(sent_IQ)).toBe( + ``+ + ``+ + ``+ + ``+ + `Moved to a new location`+ + ``+ + ``+ + ``+ + ``); + + let result_stanza = stx`` + 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.openAndEnterChatRoom(_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.onFormSubmitted(new Event('submit')); + modal = await u.waitUntil(() => document.querySelector('.modal-dialog')); + await u.waitUntil(() => u.isVisible(modal)); + + challenge_el = modal.querySelector('[name="challenge"]'); + challenge_el.value = new_muc_jid; + submit = modal.querySelector('[type="submit"]'); + submit.click(); + + sent_IQ = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector('destroy')).pop()); + expect(Strophe.serialize(sent_IQ)).toBe( + ``+ + ``+ + ``+ + ``+ + ``); + + result_stanza = stx`` + expect(_converse.chatboxes.length).toBe(2); + _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); + })); + }); +}); diff --git a/src/plugins/muc-views/tests/component.js b/src/plugins/muc-views/tests/component.js index de695adc97..f566939d71 100644 --- a/src/plugins/muc-views/tests/component.js +++ b/src/plugins/muc-views/tests/component.js @@ -60,7 +60,6 @@ describe("The component", function () { span_el.classList.add('conversejs'); span_el.classList.add('converse-embedded'); - const muc_el = document.createElement('converse-muc'); muc_el.classList.add('chatbox'); muc_el.classList.add('chatroom'); diff --git a/src/plugins/muc-views/tests/corrections.js b/src/plugins/muc-views/tests/corrections.js index 6dfb8f960d..baefceaeab 100644 --- a/src/plugins/muc-views/tests/corrections.js +++ b/src/plugins/muc-views/tests/corrections.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { $msg, $pres, Strophe, u, stx } = converse.env; +const { Strophe, u, stx } = converse.env; describe("A Groupchat Message", function () { @@ -9,24 +9,26 @@ describe("A Groupchat Message", function () { const muc_jid = 'lounge@montague.lit'; const model = await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); - const stanza = $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' - }).tree(); - _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + + + + + `)); + const msg_id = u.getUniqueId(); - await model.handleMessageStanza($msg({ - 'from': 'lounge@montague.lit/newguy', - 'to': _converse.api.connection.get().jid, - 'type': 'groupchat', - 'id': msg_id, - }).c('body').t('But soft, what light through yonder airlock breaks?').tree()); + await model.handleMessageStanza(stx` + + But soft, what light through yonder airlock breaks? + `); const view = _converse.chatboxviews.get(muc_jid); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length); @@ -34,25 +36,32 @@ describe("A Groupchat Message", function () { expect(view.querySelector('.chat-msg__text').textContent) .toBe('But soft, what light through yonder airlock breaks?'); - await view.model.handleMessageStanza($msg({ - 'from': 'lounge@montague.lit/newguy', - 'to': _converse.api.connection.get().jid, - 'type': 'groupchat', - 'id': u.getUniqueId(), - }).c('body').t('But soft, what light through yonder chimney breaks?').up() - .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree()); + await view.model.handleMessageStanza(stx` + + But soft, what light through yonder chimney breaks? + + `); + await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent === 'But soft, what light through yonder chimney breaks?', 500); expect(view.querySelectorAll('.chat-msg').length).toBe(1); await u.waitUntil(() => view.querySelector('.chat-msg__content .fa-edit')); - await view.model.handleMessageStanza($msg({ - 'from': 'lounge@montague.lit/newguy', - 'to': _converse.api.connection.get().jid, - 'type': 'groupchat', - 'id': u.getUniqueId(), - }).c('body').t('But soft, what light through yonder window breaks?').up() - .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree()); + await view.model.handleMessageStanza(stx` + + But soft, what light through yonder window breaks? + + `); await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent === 'But soft, what light through yonder window breaks?', 500); @@ -74,72 +83,74 @@ describe("A Groupchat Message", function () { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); - const stanza = $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' - }).tree(); - _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + + + + + `)); + const msg_id = u.getUniqueId(); // Receiving the first message - await view.model.handleMessageStanza($msg({ - 'from': 'lounge@montague.lit/newguy', - 'to': _converse.api.connection.get().jid, - 'type': 'groupchat', - 'id': msg_id, - }).c('body').t('But soft, what light through yonder airlock breaks?').tree()); + await view.model.handleMessageStanza(stx` + + But soft, what light through yonder airlock breaks? + `); // Receiving own message to check order against - await view.model.handleMessageStanza($msg({ - 'from': 'lounge@montague.lit/romeo', - 'to': _converse.api.connection.get().jid, - 'type': 'groupchat', - 'id': u.getUniqueId(), - }).c('body').t('But soft, what light through yonder airlock breaks?').tree()); - - await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2); - expect(view.querySelectorAll('.chat-msg').length).toBe(2); - expect(view.querySelectorAll('.chat-msg__text')[0].textContent) - .toBe('But soft, what light through yonder airlock breaks?'); - expect(view.querySelectorAll('.chat-msg__text')[1].textContent) - .toBe('But soft, what light through yonder airlock breaks?'); + await view.model.handleMessageStanza(stx` + + But soft, what light through yonder airlock breaks? + `); // First message correction - await view.model.handleMessageStanza($msg({ - 'from': 'lounge@montague.lit/newguy', - 'to': _converse.api.connection.get().jid, - 'type': 'groupchat', - 'id': u.getUniqueId(), - }).c('body').t('But soft, what light through yonder chimney breaks?').up() - .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree()); - - await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent === - 'But soft, what light through yonder chimney breaks?', 500); - expect(view.querySelectorAll('.chat-msg').length).toBe(2); - await u.waitUntil(() => view.querySelector('.chat-msg__content .fa-edit')); + await view.model.handleMessageStanza(stx` + + But soft, what light through yonder chimney breaks? + + `); // Second message correction - await view.model.handleMessageStanza($msg({ - 'from': 'lounge@montague.lit/newguy', - 'to': _converse.api.connection.get().jid, - 'type': 'groupchat', - 'id': u.getUniqueId(), - }).c('body').t('But soft, what light through yonder window breaks?').up() - .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree()); + await view.model.handleMessageStanza(stx` + + But soft, what light through yonder window breaks? + + `); // Second own message - await view.model.handleMessageStanza($msg({ - 'from': 'lounge@montague.lit/romeo', - 'to': _converse.api.connection.get().jid, - 'type': 'groupchat', - 'id': u.getUniqueId(), - }).c('body').t('But soft, what light through yonder window breaks?').tree()); + await view.model.handleMessageStanza(stx` + + But soft, what light through yonder window breaks? + `); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text')[0].textContent === 'But soft, what light through yonder window breaks?', 500); @@ -232,12 +243,14 @@ describe("A Groupchat Message", function () { expect(u.hasClass('correcting', view.querySelector('.chat-msg'))).toBe(false); // Check that messages from other users are skipped - await view.model.handleMessageStanza($msg({ - 'from': muc_jid+'/someone-else', - 'id': u.getUniqueId(), - 'to': 'romeo@montague.lit', - 'type': 'groupchat' - }).c('body').t('Hello world').tree()); + await view.model.handleMessageStanza(stx` + + Hello world + `); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2); expect(view.querySelectorAll('.chat-msg').length).toBe(2); diff --git a/src/plugins/muc-views/tests/csn.js b/src/plugins/muc-views/tests/csn.js new file mode 100644 index 0000000000..e65c2d5cfc --- /dev/null +++ b/src/plugins/muc-views/tests/csn.js @@ -0,0 +1,266 @@ +/*global mock, converse */ + +const { Strophe, stx, u } = converse.env; + +describe("Groupchats", function () { + describe("A XEP-0085 Chat Status Notification", function () { + + it("is is not sent out to a MUC if the user is a visitor in a moderated room", + mock.initConverse( + ['chatBoxesFetched'], {}, + async function (_converse) { + + spyOn(_converse.ChatRoom.prototype, 'sendChatState').and.callThrough(); + + const muc_jid = 'lounge@montague.lit'; + const features = [ + 'http://jabber.org/protocol/muc', + 'jabber:iq:register', + 'muc_passwordprotected', + 'muc_hidden', + 'muc_temporary', + 'muc_membersonly', + 'muc_moderated', + 'muc_anonymous' + ] + await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', features); + + const view = _converse.chatboxviews.get(muc_jid); + view.model.setChatState(_converse.ACTIVE); + + expect(view.model.sendChatState).toHaveBeenCalled(); + const last_stanza = _converse.api.connection.get().sent_stanzas.pop(); + expect(Strophe.serialize(last_stanza)).toBe( + ``+ + ``+ + ``+ + ``+ + ``); + + // Romeo loses his voice + _converse.api.connection.get()._dataRecv( + mock.createRequest( + stx` + + + + + `) + ); + + const occupant = view.model.occupants.findWhere({'jid': _converse.bare_jid}); + await u.waitUntil(() => occupant.get('role') === 'visitor'); + + spyOn(_converse.api.connection.get(), 'send'); + view.model.setChatState(_converse.INACTIVE); + expect(view.model.sendChatState.calls.count()).toBe(2); + expect(_converse.api.connection.get().send).not.toHaveBeenCalled(); + })); + + + describe("A composing notification", function () { + + it("will be shown if received", mock.initConverse([], {}, async function (_converse) { + const muc_jid = 'coven@chat.shakespeare.lit'; + const members = [ + {'affiliation': 'member', 'nick': 'majortom', 'jid': 'majortom@example.org'}, + {'affiliation': 'admin', 'nick': 'groundcontrol', 'jid': 'groundcontrol@example.org'} + ]; + await mock.openAndEnterChatRoom(_converse, muc_jid, 'some1', [], members); + const view = _converse.chatboxviews.get(muc_jid); + + let csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); + expect(csntext.trim()).toEqual("some1 has entered the groupchat"); + + _converse.api.connection.get()._dataRecv(mock.createRequest( + stx` + + + + `)); + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === + "some1 and newguy have entered the groupchat"); + + _converse.api.connection.get()._dataRecv(mock.createRequest( + stx` + + + + `)); + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === + "some1, newguy and nomorenicks have entered the groupchat", 1000); + + // Manually clear so that we can more easily test + view.model.notifications.set('entered', []); + await u.waitUntil(() => !view.querySelector('.chat-content__notifications').textContent, 1000); + + // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions + + const remove_notifications_timeouts = []; + const setTimeout = window.setTimeout; + spyOn(window, 'setTimeout').and.callFake((f, w) => { + if (f.toString() === "() => this.removeNotification(actor, state)") { + remove_notifications_timeouts.push(f) + } + setTimeout(f, w); + }); + + // state + let msg = stx` + + + + `; + _converse.api.connection.get()._dataRecv(mock.createRequest(msg)); + + csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent, 1000); + expect(csntext.trim()).toEqual('newguy is typing'); + expect(remove_notifications_timeouts.length).toBe(1); + expect(view.querySelector('.chat-content__notifications').textContent.trim()).toEqual('newguy is typing'); + + msg = stx` + + + + `; + await view.model.handleMessageStanza(msg.tree()); + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === 'newguy and nomorenicks are typing', 1000); + + msg = stx` + + + + `; + await view.model.handleMessageStanza(msg.tree()); + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === 'newguy, nomorenicks and majortom are typing', 1000); + + msg = stx` + + + + `; + await view.model.handleMessageStanza(msg.tree()); + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === 'newguy, nomorenicks and others are typing', 1000); + + msg = stx` + hello world + `; + await view.model.handleMessageStanza(msg.tree()); + + await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1); + expect(view.querySelector('.chat-msg .chat-msg__text').textContent.trim()).toBe('hello world'); + + // Test that the composing notifications get removed via timeout. + if (remove_notifications_timeouts.length) { + remove_notifications_timeouts[0](); + } + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === 'nomorenicks, majortom and groundcontrol are typing', 1000); + })); + }); + + describe("A paused notification", function () { + + it("will be shown if received", mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) { + const muc_jid = 'coven@chat.shakespeare.lit'; + await mock.openAndEnterChatRoom(_converse, muc_jid, 'some1'); + const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); + + /* + * + * + * + * + * + */ + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + + + + + `)); + const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); + expect(csntext.trim()).toEqual("some1 has entered the groupchat"); + + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + + + + `)); + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "some1 and newguy have entered the groupchat"); + + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + + + + `)); + + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === + "some1, newguy and nomorenicks have entered the groupchat"); + + // Manually clear so that we can more easily test + view.model.notifications.set('entered', []); + await u.waitUntil(() => !view.querySelector('.chat-content__notifications').textContent); + + // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions + + // state + let msg = stx` + + + + `; + await view.model.handleMessageStanza(msg.tree()); + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); + expect(view.querySelector('.chat-content__notifications').textContent.trim()).toBe('newguy is typing'); + + // state for a different occupant + msg = stx` + + + + `; + await view.model.handleMessageStanza(msg.tree()); + + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() == 'newguy and nomorenicks are typing'); + + // state from occupant who typed first + msg = stx` + + + + `; + await view.model.handleMessageStanza(msg.tree()); + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() == 'nomorenicks is typing\nnewguy has stopped typing'); + })); + }); + }); +}); diff --git a/src/plugins/muc-views/tests/disco.js b/src/plugins/muc-views/tests/disco.js index e01dbfb23b..6f2f7f3375 100644 --- a/src/plugins/muc-views/tests/disco.js +++ b/src/plugins/muc-views/tests/disco.js @@ -1,9 +1,10 @@ -/*global mock, converse */ +/* global mock, converse */ + +const { Strophe, u, stx } = converse.env; describe("Service Discovery", function () { it("can be used to set the muc_domain", mock.initConverse( ['discoInitialized'], {}, async function (_converse) { - const { u, $iq } = converse.env; const IQ_stanzas = _converse.api.connection.get().IQ_stanzas; const IQ_ids = _converse.api.connection.get().IQ_ids; const { api } = _converse; @@ -16,42 +17,50 @@ describe("Service Discovery", function () { let stanza = IQ_stanzas.find((iq) => iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]')); const info_IQ_id = IQ_ids[IQ_stanzas.indexOf(stanza)]; - stanza = $iq({ - 'type': 'result', - 'from': 'montague.lit', - 'to': 'romeo@montague.lit/orchard', - 'id': info_IQ_id - }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', { 'category': 'server', 'type': 'im'}).up() - .c('identity', { 'category': 'conference', 'name': 'Play-Specific Chatrooms'}).up() - .c('feature', { 'var': 'http://jabber.org/protocol/disco#info'}).up() - .c('feature', { 'var': 'http://jabber.org/protocol/disco#items'}).up(); - _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); + _converse.api.connection.get()._dataRecv(mock.createRequest( + stx` + + + + + + + `)); stanza = await u.waitUntil(() => IQ_stanzas.filter( iq => iq.querySelector('iq[to="montague.lit"] query[xmlns="http://jabber.org/protocol/disco#items"]')).pop() ); - _converse.api.connection.get()._dataRecv(mock.createRequest($iq({ - 'type': 'result', - 'from': 'montague.lit', - 'to': 'romeo@montague.lit/orchard', - 'id': IQ_ids[IQ_stanzas.indexOf(stanza)] - }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#items'}) - .c('item', { 'jid': 'chat.shakespeare.lit', 'name': 'Chatroom Service'}))); + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + + + + + `)); stanza = await u.waitUntil(() => IQ_stanzas.filter( iq => iq.querySelector('iq[to="chat.shakespeare.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]')).pop() ); - _converse.api.connection.get()._dataRecv(mock.createRequest($iq({ - 'type': 'result', - 'from': 'chat.shakespeare.lit', - 'to': 'romeo@montague.lit/orchard', - 'id': IQ_ids[IQ_stanzas.indexOf(stanza)] - }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', { 'category': 'conference', 'name': 'Play-Specific Chatrooms', 'type': 'text'}).up() - .c('feature', { 'var': 'http://jabber.org/protocol/muc'}).up())); + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + + + + + + `)); const entities = await _converse.api.disco.entities.get(); expect(entities.length).toBe(3); // We have an extra entity, which is the user's JID diff --git a/src/plugins/muc-views/tests/emojis.js b/src/plugins/muc-views/tests/emojis.js index 702522fafb..5e3a937941 100644 --- a/src/plugins/muc-views/tests/emojis.js +++ b/src/plugins/muc-views/tests/emojis.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { $pres, sizzle } = converse.env; +const { sizzle, stx } = converse.env; const u = converse.env.utils; describe("Emojis", function () { @@ -53,17 +53,16 @@ describe("Emojis", function () { await u.waitUntil(() => textarea.value === ':grimacing: '); // Test that username starting with : doesn't cause issues - const presence = $pres({ - 'from': `${muc_jid}/:username`, - 'id': '27C55F89-1C6A-459A-9EB5-77690145D624', - 'to': _converse.jid - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'some1@montague.lit', - 'affiliation': 'member', - 'role': 'participant' - }); + const presence = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); textarea.value = ':use'; diff --git a/src/plugins/muc-views/tests/hats.js b/src/plugins/muc-views/tests/hats.js index b0845ecf00..edb9e2d0d5 100644 --- a/src/plugins/muc-views/tests/hats.js +++ b/src/plugins/muc-views/tests/hats.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const u = converse.env.utils; +const { u, stx } = converse.env; describe("A XEP-0317 MUC Hat", function () { @@ -9,10 +9,22 @@ describe("A XEP-0317 MUC Hat", function () { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); + + + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + + + + + `)); + const hat1_id = u.getUniqueId(); const hat2_id = u.getUniqueId(); - _converse.api.connection.get()._dataRecv(mock.createRequest(u.toStanza(` - + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + @@ -20,8 +32,8 @@ describe("A XEP-0317 MUC Hat", function () { - - `))); + `)); + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo and Terry have entered the groupchat"); @@ -41,8 +53,8 @@ describe("A XEP-0317 MUC Hat", function () { expect(badges.map(b => b.textContent.trim()).join(' ' )).toBe("Teacher's Assistant Dark Mage"); const hat3_id = u.getUniqueId(); - _converse.api.connection.get()._dataRecv(mock.createRequest(u.toStanza(` - + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + @@ -52,7 +64,7 @@ describe("A XEP-0317 MUC Hat", function () { - `))); + `)); await u.waitUntil(() => view.model.getOccupant("Terry").get('hats').length === 3); hats = view.model.getOccupant("Terry").get('hats'); @@ -61,13 +73,13 @@ describe("A XEP-0317 MUC Hat", function () { badges = Array.from(view.querySelectorAll('.chat-msg .badge')); expect(badges.map(b => b.textContent.trim()).join(' ' )).toBe("Teacher's Assistant Dark Mage Mad hatter"); - _converse.api.connection.get()._dataRecv(mock.createRequest(u.toStanza(` - + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + - `))); + `)); await u.waitUntil(() => view.model.getOccupant("Terry").get('hats').length === 0); await u.waitUntil(() => view.querySelectorAll('.chat-msg .badge').length === 0); })); diff --git a/src/plugins/muc-views/tests/http-file-upload.js b/src/plugins/muc-views/tests/http-file-upload.js index 21423748d9..52e0b0f3fe 100644 --- a/src/plugins/muc-views/tests/http-file-upload.js +++ b/src/plugins/muc-views/tests/http-file-upload.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { Strophe, sizzle, u } = converse.env; +const { Strophe, sizzle, u, stx } = converse.env; describe("XEP-0363: HTTP File Upload", function () { @@ -87,19 +87,20 @@ describe("XEP-0363: HTTP File Upload", function () { ``); const message = base_url+"/logo/conversejs-filled.svg"; - const stanza = u.toStanza(` - - - -
Basic Base64String==
-
foo=bar; user=romeo
+ const stanza = stx` + + + +
Basic Base64String==
+
foo=bar; user=romeo
-
`); +
`.tree(); spyOn(XMLHttpRequest.prototype, 'send').and.callFake(async function () { const message = view.model.messages.at(0); diff --git a/src/plugins/muc-views/tests/info-messages.js b/src/plugins/muc-views/tests/info-messages.js index 748b50306c..c17626c036 100644 --- a/src/plugins/muc-views/tests/info-messages.js +++ b/src/plugins/muc-views/tests/info-messages.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const u = converse.env.utils; +const { u, stx } = converse.env; describe("an info message", function () { @@ -11,27 +11,25 @@ describe("an info message", function () { const nick = 'romeo'; await mock.openAndEnterChatRoom(_converse, muc_jid, nick); const view = _converse.chatboxviews.get(muc_jid); - let presence = u.toStanza(` + let presence = stx` - - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 1); - presence = u.toStanza(` + presence = stx` - - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 2); @@ -46,15 +44,14 @@ describe("an info message", function () { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); - const presence = u.toStanza(` + const presence = stx` - - `); + `; // XXX: We wait for createInfoMessages to complete, if we don't // we still get two info messages due to messages // created from presences not being queued and run diff --git a/src/plugins/muc-views/tests/mam.js b/src/plugins/muc-views/tests/mam.js index b51ae9ac89..cb6a224772 100644 --- a/src/plugins/muc-views/tests/mam.js +++ b/src/plugins/muc-views/tests/mam.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { Strophe, $msg, $pres } = converse.env; +const { Strophe, stx } = converse.env; const u = converse.env.utils; describe("A MAM archived message", function () { @@ -13,8 +13,7 @@ describe("A MAM archived message", function () { const model = await mock.openAndEnterChatRoom(_converse, muc_jid, nick); const messages = [ - u.toStanza(` - + stx` @@ -25,10 +24,9 @@ describe("A MAM archived message", function () { - `), + `, - u.toStanza(` - + stx` @@ -39,10 +37,9 @@ describe("A MAM archived message", function () { - `), + `, - u.toStanza(` - + stx` @@ -64,10 +61,9 @@ describe("A MAM archived message", function () { - `), + `, - u.toStanza(` - + stx` @@ -78,10 +74,10 @@ describe("A MAM archived message", function () { - `) + ` ] spyOn(model, 'updateMessage'); - _converse.handleMAMResult(model, { messages }); + _converse.handleMAMResult(model, { messages: messages.map((m) => m.tree()) }); await u.waitUntil(() => model.messages.length === 4); expect(model.messages.at(0).get('time')).toBe('2021-10-13T17:51:20.000Z'); @@ -96,7 +92,7 @@ describe("A MAM archived message", function () { const muc_jid = 'room@muc.example.com'; const model = await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); spyOn(model, 'getDuplicateMessage').and.callThrough(); - let stanza = u.toStanza(` + let stanza = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => model.messages.length === 1); await u.waitUntil(() => model.getDuplicateMessage.calls.count() === 1); let result = await model.getDuplicateMessage.calls.all()[0].returnValue; expect(result).toBe(undefined); - stanza = u.toStanza(` + stanza = stx` @@ -124,10 +120,10 @@ describe("A MAM archived message", function () { - `); + `; spyOn(model, 'updateMessage'); - _converse.handleMAMResult(model, { 'messages': [stanza] }); + _converse.handleMAMResult(model, { 'messages': [stanza.tree()] }); await u.waitUntil(() => model.getDuplicateMessage.calls.count() === 2); result = await model.getDuplicateMessage.calls.all()[1].returnValue; expect(result instanceof _converse.Message).toBe(true); @@ -144,45 +140,34 @@ describe("A MAM archived message", function () { const sender_jid = `${muc_jid}/romeo`; const impersonated_jid = `${muc_jid}/i_am_groot` const model = await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); - const stanza = $pres({ - to: 'romeo@montague.lit/_converse.js-29092160', - from: sender_jid - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'owner', - 'jid': 'newguy@montague.lit/_converse.js-290929789', - 'role': 'participant' - }).tree(); - _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); - /* - * - * - * - * - * I am groot. - * - * - * - * - */ - const msg = $msg({ - 'from': sender_jid, - 'id': _converse.api.connection.get().getUniqueId(), - 'to': _converse.api.connection.get().jid, - 'type': 'groupchat', - 'xmlns': 'jabber:client' - }).c('received', {'xmlns': 'urn:xmpp:carbons:2'}) - .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'}) - .c('message', { - 'xmlns': 'jabber:client', - 'from': impersonated_jid, - 'to': muc_jid, - 'type': 'groupchat' - }).c('body').t('I am groot').tree(); + _converse.api.connection.get()._dataRecv(mock.createRequest( + stx` + + + + `) + ); + + const msg = stx` + + + + + I am groot + + + + `; const view = _converse.chatboxviews.get(muc_jid); spyOn(converse.env.log, 'error'); - await _converse.handleMAMResult(model, { 'messages': [msg] }); + await _converse.handleMAMResult(model, { 'messages': [msg.tree()] }); await u.waitUntil(() => converse.env.log.error.calls.count()); expect(converse.env.log.error).toHaveBeenCalledWith( 'Invalid Stanza: MUC messages SHOULD NOT be XEP-0280 carbon copied' diff --git a/src/plugins/muc-views/tests/markers.js b/src/plugins/muc-views/tests/markers.js index c655fa10e2..2be569dcf2 100644 --- a/src/plugins/muc-views/tests/markers.js +++ b/src/plugins/muc-views/tests/markers.js @@ -1,8 +1,8 @@ /*global mock, converse */ -const u = converse.env.utils; -// See: https://xmpp.org/rfcs/rfc3921.html +const { u, stx } = converse.env; +// See: https://xmpp.org/rfcs/rfc3921.html describe("A XEP-0333 Chat Marker", function () { it("may be returned for a MUC message", @@ -26,41 +26,41 @@ describe("A XEP-0333 Chat Marker", function () { .toBe("But soft, what light through yonder airlock breaks?"); const msg_obj = view.model.messages.at(0); - let stanza = u.toStanza(` + let stanza = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1); expect(view.querySelectorAll('.chat-msg__receipt').length).toBe(0); - stanza = u.toStanza(` + stanza = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); expect(view.querySelectorAll('.chat-msg').length).toBe(1); expect(view.querySelectorAll('.chat-msg__receipt').length).toBe(0); - stanza = u.toStanza(` + stanza = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); expect(view.querySelectorAll('.chat-msg').length).toBe(1); expect(view.querySelectorAll('.chat-msg__receipt').length).toBe(0); - stanza = u.toStanza(` + stanza = stx` 'tis I! - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2); expect(view.querySelectorAll('.chat-msg__receipt').length).toBe(0); diff --git a/src/plugins/muc-views/tests/me-messages.js b/src/plugins/muc-views/tests/me-messages.js index b42e00c87e..46040d5154 100644 --- a/src/plugins/muc-views/tests/me-messages.js +++ b/src/plugins/muc-views/tests/me-messages.js @@ -1,39 +1,43 @@ /*global mock, converse */ -const { u, sizzle, $msg } = converse.env; +const { u, sizzle, stx } = converse.env; describe("A Groupchat Message", function () { it("supports the /me command", mock.initConverse([], {}, async function (_converse) { + const muc_jid = 'lounge@montague.lit'; await mock.waitUntilDiscoConfirmed(_converse, 'montague.lit', [], ['vcard-temp']); await u.waitUntil(() => _converse.xmppstatus.vcard.get('fullname')); await mock.waitForRoster(_converse, 'current'); - await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); - const view = _converse.chatboxviews.get('lounge@montague.lit'); + await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); + const view = _converse.chatboxviews.get(muc_jid); if (!view.querySelectorAll('.chat-area').length) { view.renderChatArea(); } let message = '/me is tired'; const nick = mock.chatroom_names[0]; - let msg = $msg({ - 'from': 'lounge@montague.lit/'+nick, - 'id': u.getUniqueId(), - 'to': 'romeo@montague.lit', - 'type': 'groupchat' - }).c('body').t(message).tree(); + + let msg = stx` + ${message} + `; await view.model.handleMessageStanza(msg); await u.waitUntil(() => sizzle('.chat-msg:last .chat-msg__text', view).pop()); await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent.trim() === 'is tired'); expect(view.querySelector('.chat-msg__author').textContent.includes('**Dyon van de Wege')).toBeTruthy(); message = '/me is as well'; - msg = $msg({ - from: 'lounge@montague.lit/Romeo Montague', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t(message).tree(); + msg = stx` + ${message} + `; await view.model.handleMessageStanza(msg); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2); await u.waitUntil(() => Array.from(view.querySelectorAll('.chat-msg__text')).pop().textContent.trim() === 'is as well'); @@ -41,13 +45,18 @@ describe("A Groupchat Message", function () { // Check rendering of a mention inside a me message const msg_text = "/me mentions romeo"; - msg = $msg({ - from: 'lounge@montague.lit/gibson', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t(msg_text).up() - .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'13', 'end':'19', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).nodeTree; + msg = stx` + ${msg_text} + + `; await view.model.handleMessageStanza(msg); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 3); await u.waitUntil(() => sizzle('.chat-msg__text:last', view).pop().innerHTML.replace(//g, '') === diff --git a/src/plugins/muc-views/tests/member-lists.js b/src/plugins/muc-views/tests/member-lists.js index 6986931dd4..ca0a6749a6 100644 --- a/src/plugins/muc-views/tests/member-lists.js +++ b/src/plugins/muc-views/tests/member-lists.js @@ -1,5 +1,5 @@ /*global mock, converse */ -const { $iq, Strophe, u } = converse.env; +const { $iq, Strophe, u, stx } = converse.env; describe("A Groupchat", function () { @@ -15,6 +15,7 @@ describe("A Groupchat", function () { let view = _converse.chatboxviews.get(muc_jid); expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation]')).length).toBe(3); + // Check in reverse order that we requested all three lists const owner_iq = sent_IQs.pop(); expect(Strophe.serialize(owner_iq)).toBe( @@ -129,18 +130,16 @@ describe("A Groupchat", function () { _converse.api.connection.get()._dataRecv(mock.createRequest(err_stanza)); // Now the service sends the member lists to the user - const member_list_stanza = $iq({ - 'from': muc_jid, - 'id': member_iq.getAttribute('id'), - 'to': 'romeo@montague.lit/orchard', - 'type': 'result' - }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN}) - .c('item', { - 'affiliation': 'member', - 'jid': 'hag66@shakespeare.lit', - 'nick': 'thirdwitch', - 'role': 'participant' - }); + const member_list_stanza = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(member_list_stanza)); await u.waitUntil(() => view.model.occupants.length > 1); @@ -176,22 +175,20 @@ describe("Someone being invited to a groupchat", function () { // State that the chat is members-only via the features IQ const view = _converse.chatboxviews.get(muc_jid); - const features_stanza = $iq({ - from: 'coven@chat.shakespeare.lit', - 'id': stanza.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'result' - }) - .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', { - 'category': 'conference', - 'name': 'A Dark Cave', - 'type': 'text' - }).up() - .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() - .c('feature', {'var': 'muc_hidden'}).up() - .c('feature', {'var': 'muc_temporary'}).up() - .c('feature', {'var': 'muc_membersonly'}).up(); + const features_stanza = stx` + + + + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); const sent_stanzas = _converse.api.connection.get().sent_stanzas; await u.waitUntil(() => sent_stanzas.filter(s => s.matches(`presence[to="${muc_jid}/${nick}"]`)).pop()); @@ -231,43 +228,40 @@ describe("Someone being invited to a groupchat", function () { `
`); // Now the service sends the member lists to the user - const member_list_stanza = $iq({ - 'from': 'coven@chat.shakespeare.lit', - 'id': member_iq.getAttribute('id'), - 'to': 'romeo@montague.lit/orchard', - 'type': 'result' - }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN}) - .c('item', { - 'affiliation': 'member', - 'jid': 'hag66@shakespeare.lit', - 'nick': 'thirdwitch', - 'role': 'participant' - }); + const member_list_stanza = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(member_list_stanza)); - const admin_list_stanza = $iq({ - 'from': 'coven@chat.shakespeare.lit', - 'id': admin_iq.getAttribute('id'), - 'to': 'romeo@montague.lit/orchard', - 'type': 'result' - }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN}) - .c('item', { - 'affiliation': 'admin', - 'jid': 'wiccarocks@shakespeare.lit', - 'nick': 'secondwitch' - }); + const admin_list_stanza = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(admin_list_stanza)); - const owner_list_stanza = $iq({ - 'from': 'coven@chat.shakespeare.lit', - 'id': owner_iq.getAttribute('id'), - 'to': 'romeo@montague.lit/orchard', - 'type': 'result' - }).c('query', {'xmlns': Strophe.NS.MUC_ADMIN}) - .c('item', { - 'affiliation': 'owner', - 'jid': 'crone1@shakespeare.lit', - }); + const owner_list_stanza = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(owner_list_stanza)); // Converse puts the user on the member list @@ -281,12 +275,12 @@ describe("Someone being invited to a groupchat", function () { ``+ `
`); - const result = $iq({ - 'from': 'coven@chat.shakespeare.lit', - 'id': stanza.getAttribute('id'), - 'to': 'romeo@montague.lit/orchard', - 'type': 'result' - }); + const result = stx` + `; _converse.api.connection.get()._dataRecv(mock.createRequest(result)); await u.waitUntil(() => view.model.occupants.fetchMembers.calls.count()); diff --git a/src/plugins/muc-views/tests/mentions.js b/src/plugins/muc-views/tests/mentions.js index a4a0270eaf..c684bec0c9 100644 --- a/src/plugins/muc-views/tests/mentions.js +++ b/src/plugins/muc-views/tests/mentions.js @@ -1,7 +1,6 @@ /*global mock, converse */ -const { Strophe, $msg, $pres, sizzle } = converse.env; -const u = converse.env.utils; +const { Strophe, sizzle, stx, u } = converse.env; describe("An incoming groupchat message", function () { @@ -14,19 +13,20 @@ describe("An incoming groupchat message", function () { const view = _converse.chatboxviews.get(muc_jid); if (!view.querySelectorAll('.chat-area').length) { view.renderChatArea(); } const message = 'romeo: Your attention is required'; - const nick = mock.chatroom_names[0], - msg = $msg({ - from: 'lounge@montague.lit/'+nick, - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t(message).tree(); + const nick = mock.chatroom_names[0]; + const msg = + stx` + ${message} + `; await view.model.handleMessageStanza(msg); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length); expect(u.hasClass('mentioned', view.querySelector('.chat-msg'))).toBeTruthy(); })); - it("highlights all users mentioned via XEP-0372 references", mock.initConverse([], {}, async function (_converse) { @@ -35,28 +35,28 @@ describe("An incoming groupchat message", function () { const view = _converse.chatboxviews.get(muc_jid); ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => { _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({ - 'to': 'tom@montague.lit/resource', - 'from': `lounge@montague.lit/${nick}` - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `${nick}@montague.lit/resource`, - 'role': 'participant' - })) - ); + stx` + + + + ` + )); }); - let msg = $msg({ - from: 'lounge@montague.lit/gibson', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t('hello z3r0 tom mr.robot, how are you?').up() - .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'6', 'end':'10', 'type':'mention', 'uri':'xmpp:z3r0@montague.lit'}).up() - .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'11', 'end':'14', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).up() - .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'15', 'end':'23', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree; - await view.model.handleMessageStanza(msg); + await view.model.handleMessageStanza( + stx` + hello z3r0 tom mr.robot, how are you? + + + + `.tree()); + await u.waitUntil(() => view.querySelector('.chat-msg__text')?.innerHTML.replace(//g, '') === 'hello z3r0 '+ 'tom '+ @@ -64,14 +64,15 @@ describe("An incoming groupchat message", function () { let message = view.querySelector('.chat-msg__text'); expect(message.classList.length).toEqual(1); - msg = $msg({ - from: 'lounge@montague.lit/sw0rdf1sh', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t('@gibson').up() - .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'1', 'end':'7', 'type':'mention', 'uri':'xmpp:gibson@montague.lit'}).nodeTree; - await view.model.handleMessageStanza(msg); + await view.model.handleMessageStanza( + stx` + @gibson + + `.tree()); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2); @@ -88,17 +89,15 @@ describe("An incoming groupchat message", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, nick); const view = _converse.chatboxviews.get(muc_jid); _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({ - 'to': 'romeo@montague.lit/resource', - 'from': `lounge@montague.lit/ThUnD3r|Gr33n` - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `${nick}@montague.lit/resource`, - 'role': 'participant' - })) - ); + stx` + + + + ` + )); const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea')); textarea.value = 'hello @ThUnD3r|Gr33n' const enter_event = { @@ -136,29 +135,29 @@ describe("An incoming groupchat message", function () { const view = _converse.chatboxviews.get(muc_jid); ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => { _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({ - 'to': 'tom@montague.lit/resource', - 'from': `lounge@montague.lit/${nick}` - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `${nick}@montague.lit/resource`, - 'role': 'participant' - })) - ); + stx` + + + + ` + )); }); - const msg = $msg({ - from: 'lounge@montague.lit/gibson', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t('>hello z3r0 tom mr.robot, how are you?').up() - .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'7', 'end':'11', 'type':'mention', 'uri':'xmpp:z3r0@montague.lit'}).up() - .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'12', 'end':'15', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).up() - .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'16', 'end':'24', 'type':'mention', 'uri':'xmpp:mr.robot@montague.lit'}).nodeTree; - await view.model.handleMessageStanza(msg); + await view.model.handleMessageStanza( + stx` + >hello z3r0 tom mr.robot, how are you? + + + + `.tree()); + await u.waitUntil(() => view.querySelector('.chat-msg__text')?.innerHTML.replace(//g, '') === '
hello z3r0 '+ 'tom '+ @@ -195,26 +194,24 @@ describe("A sent groupchat message", function () { const view = _converse.chatboxviews.get(muc_jid); ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh', 'Link Mauve', 'robot'].forEach((nick) => { _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({ - 'to': 'tom@montague.lit/resource', - 'from': `lounge@montague.lit/${nick}` - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `${nick.replace(/\s/g, '-')}@montague.lit/resource`, - 'role': 'participant' - }))); + stx` + + + + `)) }); // Also check that nicks from received messages, (but for which we don't have occupant objects) can be mentioned. - const stanza = u.toStanza(` + const stanza = stx` Boo! - `); + `; await view.model.handleMessageStanza(stanza); // Run a few unit tests for the parseTextForReferences method @@ -310,16 +307,14 @@ describe("A sent groupchat message", function () { const view = _converse.chatboxviews.get(muc_jid); ['NotAnAdress', 'darnuria'].forEach((nick) => { _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({ - 'to': 'tom@montague.lit/resource', - 'from': `lounge@montague.lit/${nick}` - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `${nick.replace(/\s/g, '-')}@montague.lit/resource`, - 'role': 'participant' - }))); + stx` + + + + `)) }); // Test that we don't match @nick in email adresses. @@ -340,15 +335,14 @@ describe("A sent groupchat message", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, 'tom'); const view = _converse.chatboxviews.get(muc_jid); _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({ - 'to': 'tom@montague.lit/resource', - 'from': `lounge@montague.lit/Link Mauve` - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'role': 'participant' - }))); + stx` + + + + `)); await u.waitUntil(() => view.model.occupants.length === 2); const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea')); @@ -397,16 +391,15 @@ describe("A sent groupchat message", function () { const view = _converse.chatboxviews.get(muc_jid); ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => { _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({ - 'to': 'tom@montague.lit/resource', - 'from': `lounge@montague.lit/${nick}` - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `${nick}@montague.lit/resource`, - 'role': 'participant' - }))); + stx` + + + + ` + )); }); await u.waitUntil(() => view.model.occupants.length === 5); @@ -479,18 +472,17 @@ describe("A sent groupchat message", function () { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); + ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => { _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({ - 'to': 'tom@montague.lit/resource', - 'from': `lounge@montague.lit/${nick}` - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `${nick}@montague.lit/resource`, - 'role': 'participant' - }))); + stx` + + + + `)); }); await u.waitUntil(() => view.model.occupants.length === 5); diff --git a/src/plugins/muc-views/tests/mep.js b/src/plugins/muc-views/tests/mep.js index 62fe37216b..d6fd2864a9 100644 --- a/src/plugins/muc-views/tests/mep.js +++ b/src/plugins/muc-views/tests/mep.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { u, Strophe } = converse.env; +const { u, Strophe, stx } = converse.env; describe("A XEP-0316 MEP notification", function () { @@ -13,16 +13,17 @@ describe("A XEP-0316 MEP notification", function () { const view = _converse.chatboxviews.get(muc_jid); let msg = 'An anonymous user has saluted romeo'; let reason = 'Thank you for helping me yesterday'; - let message = u.toStanza(` - - - - - - + let message = stx` + + + + + + ${msg} @@ -32,7 +33,7 @@ describe("A XEP-0316 MEP notification", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 1); @@ -51,16 +52,17 @@ describe("A XEP-0316 MEP notification", function () { // Also check a MEP message of type "groupchat" msg = 'An anonymous user has poked romeo'; reason = 'Can you please help me with something else?'; - message = u.toStanza(` - - - - - - + message = stx` + + + + + + ${msg} @@ -70,7 +72,7 @@ describe("A XEP-0316 MEP notification", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 2); @@ -98,16 +100,17 @@ describe("A XEP-0316 MEP notification", function () { const model = await mock.openAndEnterChatRoom(_converse, muc_jid, nick, [], [], true, {'hidden': true}); const msg = 'An anonymous user has saluted romeo'; const reason = 'Thank you for helping me yesterday'; - const message = u.toStanza(` - - - - - - + const message = stx` + + + + + + ${msg} @@ -117,7 +120,7 @@ describe("A XEP-0316 MEP notification", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); await u.waitUntil(() => model.messages.length === 1); // expect(window.Notification.calls.count()).toBe(1); @@ -136,16 +139,17 @@ describe("A XEP-0316 MEP notification", function () { const model = await mock.openAndEnterChatRoom(_converse, muc_jid, nick, [], [], true); const msg = 'An anonymous user has waved at romeo'; const reason = 'Check out https://conversejs.org'; - const message = u.toStanza(` - - - - - - + const message = stx` + + + + + + ${msg} @@ -155,7 +159,7 @@ describe("A XEP-0316 MEP notification", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); await u.waitUntil(() => model.messages.length === 1); @@ -176,16 +180,17 @@ describe("A XEP-0316 MEP notification", function () { const view = _converse.chatboxviews.get(muc_jid); const msg = 'An anonymous user has saluted romeo'; const reason = 'Thank you for helping me yesterday'; - _converse.api.connection.get()._dataRecv(mock.createRequest(u.toStanza(` - - - - - - + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + + + + + + ${msg} @@ -195,9 +200,9 @@ describe("A XEP-0316 MEP notification", function () { - + ` - ))); + )); await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 1); expect(view.querySelector('.chat-info__message converse-rich-text').textContent.trim()).toBe(msg); @@ -226,15 +231,19 @@ describe("A XEP-0316 MEP notification", function () { ``); // The server responds with a retraction message - const retraction = u.toStanza(` - + const retraction = stx` + - - + + - `); + `; await view.model.handleMessageStanza(retraction); expect(view.model.messages.length).toBe(1); expect(view.model.messages.at(0).get('moderated')).toBe('retracted'); diff --git a/src/plugins/muc-views/tests/modtools.js b/src/plugins/muc-views/tests/modtools.js index 704e55f9cb..57e3b94e76 100644 --- a/src/plugins/muc-views/tests/modtools.js +++ b/src/plugins/muc-views/tests/modtools.js @@ -1,12 +1,6 @@ /*global mock, converse, _ */ - -const $iq = converse.env.$iq; -const $pres = converse.env.$pres; -const sizzle = converse.env.sizzle; -const Strophe = converse.env.Strophe; -const u = converse.env.utils; - +const { stx, sizzle, Strophe, u } = converse.env; async function openModtools (_converse, view) { const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea')); @@ -101,7 +95,8 @@ describe("The groupchat moderator tool", function () { 'type': 'result', 'id': sent_IQ.getAttribute('id'), 'from': view.model.get('jid'), - 'to': _converse.api.connection.get().jid + 'to': _converse.api.connection.get().jid, + 'xmlns': 'jabber:client' }); _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => view.model.occupants.fetchMembers.calls.count()); @@ -200,58 +195,46 @@ describe("The groupchat moderator tool", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', []); const view = _converse.chatboxviews.get(muc_jid); _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({to: _converse.jid, from: `${muc_jid}/nomorenicks`}) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `nomorenicks@montague.lit`, - 'role': 'participant' - }) + stx` + + + + ` )); _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({to: _converse.jid, from: `${muc_jid}/newb`}) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `newb@montague.lit`, - 'role': 'participant' - }) + stx` + + + + ` )); _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({to: _converse.jid, from: `${muc_jid}/some1`}) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `some1@montague.lit`, - 'role': 'participant' - }) + stx` + + + + ` )); _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({to: _converse.jid, from: `${muc_jid}/oldhag`}) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `oldhag@montague.lit`, - 'role': 'participant' - }) + stx` + + + + ` )); _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({to: _converse.jid, from: `${muc_jid}/crone`}) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `crone@montague.lit`, - 'role': 'participant' - }) + stx` + + + + ` )); _converse.api.connection.get()._dataRecv(mock.createRequest( - $pres({to: _converse.jid, from: `${muc_jid}/tux`}) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': `tux@montague.lit`, - 'role': 'participant' - }) + stx` + + + + ` )); await u.waitUntil(() => (view.model.occupants.length === 7), 1000); @@ -314,6 +297,7 @@ describe("The groupchat moderator tool", function () { await u.waitUntil(() => (view.model.occupants.length === 5)); const modal = await openModtools(_converse, view); const tab = modal.querySelector('#affiliations-tab'); + // Clear so that we don't match older stanzas _converse.api.connection.get().IQ_stanzas = []; const IQ_stanzas = _converse.api.connection.get().IQ_stanzas; @@ -330,16 +314,16 @@ describe("The groupchat moderator tool", function () { ).length ).pop()); - const error = u.toStanza( - ` - + const error = + stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(error)); await u.waitUntil(() => !modal.loading_users_with_affiliation); @@ -401,16 +385,16 @@ describe("The groupchat moderator tool", function () { ``+ ``); - const error = u.toStanza( - ` - + const error = + stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(error)); })); diff --git a/src/plugins/muc-views/tests/muc-api.js b/src/plugins/muc-views/tests/muc-api.js index 0c232131d4..52fd8f38ec 100644 --- a/src/plugins/muc-views/tests/muc-api.js +++ b/src/plugins/muc-views/tests/muc-api.js @@ -1,7 +1,7 @@ /*global mock, converse */ const Model = converse.env.Model; -const { $pres, $iq, Strophe, sizzle, u } = converse.env; +const { Strophe, sizzle, u, stx } = converse.env; describe("Groupchats", function () { @@ -173,42 +173,30 @@ describe("Groupchats", function () { // We pretend this is a new room, so no disco info is returned. const features_stanza = $iq({ - from: 'room@conference.example.org', - 'id': features_query.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'error' - }).c('error', {'type': 'cancel'}) - .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); + from: "room@conference.example.org", + id: features_query.getAttribute("id"), + to: "romeo@montague.lit/desktop", + type: "error", + xmlns: "jabber:client" + }).c("error", {"type": "cancel"}) + .c("item-not-found", {"xmlns": "urn:ietf:params:xml:ns:xmpp-stanzas"}); _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); - /* - * - * - * - * - * - * - */ - const presence = $pres({ - from:'room@conference.example.org/some1', - to:'romeo@montague.lit/pda' - }) - .c('x', {xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item', { - affiliation: 'owner', - jid: 'romeo@montague.lit/pda', - role: 'moderator' - }).up() - .c('status', {code:'110'}).up() - .c('status', {code:'201'}); - _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` + + + + + + + `)); const iq = await u.waitUntil(() => IQ_stanzas.filter(s => s.querySelector(`query[xmlns="${Strophe.NS.MUC_OWNER}"]`)).pop()); expect(Strophe.serialize(iq)).toBe( ``+ ``); - const node = u.toStanza(` + const node = stx` 20 - `); + `; mucview = _converse.chatboxviews.get('room@conference.example.org'); spyOn(mucview.model, 'sendConfiguration').and.callThrough(); diff --git a/src/plugins/muc-views/tests/muc-avatar.js b/src/plugins/muc-views/tests/muc-avatar.js index 6d47366075..2d340b49f0 100644 --- a/src/plugins/muc-views/tests/muc-avatar.js +++ b/src/plugins/muc-views/tests/muc-avatar.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { u } = converse.env; +const { u, stx } = converse.env; describe('Groupchats', () => { describe('A Groupchat', () => { @@ -30,8 +30,7 @@ describe('Groupchats', () => { }) ); - it( - 'has an avatar which opens a details modal when clicked', + it('has an avatar which opens a details modal when clicked', mock.initConverse( ['chatBoxesFetched'], { @@ -40,7 +39,7 @@ describe('Groupchats', () => { // have to mock stanza traffic. }, async function (_converse) { - const { Strophe, $iq, $pres, u } = converse.env; + const { Strophe, u } = converse.env; const IQ_stanzas = _converse.api.connection.get().IQ_stanzas; const muc_jid = 'coven@chat.shakespeare.lit'; await mock.waitForRoster(_converse, 'current', 0); @@ -51,79 +50,52 @@ describe('Groupchats', () => { const features_query = await u.waitUntil(() => IQ_stanzas.filter((iq) => iq.querySelector(selector)).pop() ); - const features_stanza = $iq({ - 'from': 'coven@chat.shakespeare.lit', - 'id': features_query.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'result', - }) - .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info' }) - .c('identity', { - 'category': 'conference', - 'name': 'A Dark Cave', - 'type': 'text', - }) - .up() - .c('feature', { 'var': 'http://jabber.org/protocol/muc' }) - .up() - .c('feature', { 'var': 'muc_passwordprotected' }) - .up() - .c('feature', { 'var': 'muc_hidden' }) - .up() - .c('feature', { 'var': 'muc_temporary' }) - .up() - .c('feature', { 'var': 'muc_open' }) - .up() - .c('feature', { 'var': 'muc_unmoderated' }) - .up() - .c('feature', { 'var': 'muc_nonanonymous' }) - .up() - .c('feature', { 'var': 'urn:xmpp:mam:0' }) - .up() - .c('x', { 'xmlns': 'jabber:x:data', 'type': 'result' }) - .c('field', { 'var': 'FORM_TYPE', 'type': 'hidden' }) - .c('value') - .t('http://jabber.org/protocol/muc#roominfo') - .up() - .up() - .c('field', { - 'type': 'text-single', - 'var': 'muc#roominfo_description', - 'label': 'Description', - }) - .c('value') - .t('This is the description') - .up() - .up() - .c('field', { - 'type': 'text-single', - 'var': 'muc#roominfo_occupants', - 'label': 'Number of occupants', - }) - .c('value') - .t(0); + + const features_stanza = stx` + + + + + + + + + + + + + + http://jabber.org/protocol/muc#roominfo + + + This is the description + + + 0 + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); const view = _converse.chatboxviews.get(muc_jid); await u.waitUntil( () => view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING ); - let presence = $pres({ - to: _converse.api.connection.get().jid, - from: 'coven@chat.shakespeare.lit/some1', - id: 'DC352437-C019-40EC-B590-AF29E879AF97', - }) - .c('x') - .attrs({ xmlns: 'http://jabber.org/protocol/muc#user' }) - .c('item') - .attrs({ - affiliation: 'member', - jid: _converse.bare_jid, - role: 'participant', - }) - .up() - .c('status') - .attrs({ code: '110' }); + let presence = stx` + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); const avatar_el = await u.waitUntil( @@ -163,16 +135,14 @@ describe('Groupchats', () => { 'Not anonymous - All other groupchat participants can see your XMPP address' + 'Not moderated - Participants entering this groupchat can write right away ' ); - 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', - }); + presence = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); els = modal.querySelectorAll('p.room-info'); diff --git a/src/plugins/muc-views/tests/muc-list-modal.js b/src/plugins/muc-views/tests/muc-list-modal.js index 5976458426..e261fa32ed 100644 --- a/src/plugins/muc-views/tests/muc-list-modal.js +++ b/src/plugins/muc-views/tests/muc-list-modal.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { $iq, Strophe, Promise, sizzle, u } = converse.env; +const { Strophe, Promise, sizzle, u, stx } = converse.env; describe('The "Groupchats" List modal', function () { @@ -36,23 +36,26 @@ describe('The "Groupchats" List modal', function () { `` + `` ); - const iq = $iq({ - 'from': 'muc.montague.lit', - 'to': 'romeo@montague.lit/pda', - 'id': id, - 'type': 'result', - }) - .c('query') - .c('item', { jid: 'heath@chat.shakespeare.lit', name: 'A Lonely Heath' }).up() - .c('item', { jid: 'coven@chat.shakespeare.lit', name: 'A Dark Cave' }).up() - .c('item', { jid: 'forres@chat.shakespeare.lit', name: 'The Palace' }).up() - .c('item', { jid: 'inverness@chat.shakespeare.lit', name: 'Macbeth's Castle' }).up() - .c('item', { jid: 'orchard@chat.shakespeare.lit', name: "Capulet's Orchard" }).up() - .c('item', { jid: 'friar@chat.shakespeare.lit', name: "Friar Laurence's cell" }).up() - .c('item', { jid: 'hall@chat.shakespeare.lit', name: "Hall in Capulet's house" }).up() - .c('item', { jid: 'chamber@chat.shakespeare.lit', name: "Juliet's chamber" }).up() - .c('item', { jid: 'public@chat.shakespeare.lit', name: 'A public place' }).up() - .c('item', { jid: 'street@chat.shakespeare.lit', name: 'A street' }).nodeTree; + + const iq = stx` + + + + + + + + + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(iq)); await u.waitUntil(() => modal.querySelectorAll('.available-chatrooms li').length === 11); @@ -117,16 +120,18 @@ describe('The "Groupchats" List modal', function () { `` + `` ); - const iq = $iq({ - from: 'muc.montague.lit', - to: 'romeo@montague.lit/pda', - id: sent_stanza.getAttribute('id'), - type: 'result', - }) - .c('query') - .c('item', { jid: 'heath@chat.shakespeare.lit', name: 'A Lonely Heath' }).up() - .c('item', { jid: 'coven@chat.shakespeare.lit', name: 'A Dark Cave' }).up() - .c('item', { jid: 'forres@chat.shakespeare.lit', name: 'The Palace' }).up(); + const iq = stx` + + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(iq)); await u.waitUntil(() => modal.querySelectorAll('.available-chatrooms li').length === 4); diff --git a/src/plugins/muc-views/tests/muc-mentions.js b/src/plugins/muc-views/tests/muc-mentions.js index 8525d16511..ace88b8e1f 100644 --- a/src/plugins/muc-views/tests/muc-mentions.js +++ b/src/plugins/muc-views/tests/muc-mentions.js @@ -1,9 +1,8 @@ /*global mock, converse */ -const { dayjs } = converse.env; -const u = converse.env.utils; -// See: https://xmpp.org/rfcs/rfc3921.html +const { dayjs, stx, u } = converse.env; +// See: https://xmpp.org/rfcs/rfc3921.html describe("MUC Mention Notfications", function () { @@ -34,8 +33,8 @@ describe("MUC Mention Notfications", function () { expect(Array.from(room_el.classList).includes('unread-msgs')).toBeFalsy(); const base_time = new Date(); - let message = u.toStanza(` - + let message = stx` + @@ -52,15 +51,14 @@ describe("MUC Mention Notfications", function () { - - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); await u.waitUntil(() => Array.from(room_el.classList).includes('unread-msgs')); expect(room_el.querySelector('.msgs-indicator')?.textContent.trim()).toBe('1'); - message = u.toStanza(` - + message = stx` + @@ -77,8 +75,7 @@ describe("MUC Mention Notfications", function () { - - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); expect(Array.from(room_el.classList).includes('unread-msgs')).toBeTruthy(); await u.waitUntil(() => room_el.querySelector('.msgs-indicator')?.textContent.trim() === '2'); diff --git a/src/plugins/muc-views/tests/muc-messages.js b/src/plugins/muc-views/tests/muc-messages.js index 1d2c4c9ad6..730141e5af 100644 --- a/src/plugins/muc-views/tests/muc-messages.js +++ b/src/plugins/muc-views/tests/muc-messages.js @@ -1,7 +1,7 @@ /*global mock, converse */ - const { Promise, Strophe, $msg, $pres, sizzle,u } = converse.env; - const original_timeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; +const { Promise, Strophe, sizzle, u, stx } = converse.env; +const original_timeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; describe("A Groupchat Message", function () { @@ -30,15 +30,14 @@ describe("A Groupchat Message", function () { const msg = view.model.messages.at(0); const err_msg_text = "Message rejected because you're sending messages too quickly"; - const error = u.toStanza(` + const error = stx` ${err_msg_text} hello world - - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(error)); expect(await u.waitUntil(() => view.querySelector('.chat-msg__error')?.textContent?.trim())).toBe(err_msg_text); expect(view.model.messages.length).toBe(1); @@ -58,15 +57,16 @@ describe("A Groupchat Message", function () { const view = _converse.chatboxviews.get(muc_jid); if (!view.querySelectorAll('.chat-area').length) { view.renderChatArea(); } const message = 'romeo: Your attention is required'; - const nick = mock.chatroom_names[0], - msg = $msg({ - from: 'lounge@montague.lit/'+nick, - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t(message) - .c('active', {'xmlns': "http://jabber.org/protocol/chatstates"}) - .tree(); + const nick = mock.chatroom_names[0]; + const msg = stx` + + ${message} + + `; await view.model.handleMessageStanza(msg); const el = await u.waitUntil(() => view.querySelector('.chat-msg__text')); expect(el.textContent).toBe(message); @@ -80,21 +80,25 @@ describe("A Groupchat Message", function () { const view = _converse.chatboxviews.get(muc_jid); if (!view.querySelectorAll('.chat-area').length) { view.renderChatArea(); } const id = u.getUniqueId(); - let msg = $msg({ - from: 'lounge@montague.lit/some1', - id: id, - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t('First message').tree(); + let msg = stx` + + First message + `; await view.model.handleMessageStanza(msg); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1); - msg = $msg({ - from: 'lounge@montague.lit/some2', - id: id, - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t('Another message').tree(); + msg = stx` + + Another message + `; await view.model.handleMessageStanza(msg); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2); expect(view.model.messages.length).toBe(2); @@ -107,7 +111,7 @@ describe("A Groupchat Message", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); spyOn(view.model, 'getStanzaIdQueryAttrs').and.callThrough(); - let stanza = u.toStanza(` + let stanza = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => view.model.messages.length === 1); await u.waitUntil(() => view.model.getStanzaIdQueryAttrs.calls.count() === 1); @@ -125,7 +129,7 @@ describe("A Groupchat Message", function () { expect(result[0] instanceof Object).toBe(true); expect(result[0]['stanza_id room@muc.example.com']).toBe("5f3dbc5e-e1d3-4077-a492-693f3769c7ad"); - stanza = u.toStanza(` + stanza = stx` - `); + `; spyOn(view.model, 'updateMessage'); spyOn(view.model, 'getDuplicateMessage').and.callThrough(); _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); @@ -151,58 +155,64 @@ describe("A Groupchat Message", function () { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); - let msg = $msg({ - from: 'lounge@montague.lit/romeo', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t('I wrote this message!').tree(); + let msg = stx` + + I wrote this message! + `; await view.model.handleMessageStanza(msg); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length); expect(view.model.messages.last().occupant.get('affiliation')).toBe('owner'); expect(view.model.messages.last().occupant.get('role')).toBe('moderator'); expect(view.querySelectorAll('.chat-msg').length).toBe(1); expect(sizzle('.chat-msg', view).pop().classList.value.trim()).toBe('message chat-msg groupchat chat-msg--with-avatar moderator owner'); - let presence = $pres({ - to:'romeo@montague.lit/orchard', - from:'lounge@montague.lit/romeo', - id: u.getUniqueId() - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({ - affiliation: 'member', - jid: 'romeo@montague.lit/orchard', - role: 'participant' - }).up() - .c('status').attrs({code:'110'}).up() - .c('status').attrs({code:'210'}).nodeTree; + let presence = stx` + + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.model.messages.length === 4); - msg = $msg({ - from: 'lounge@montague.lit/romeo', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t('Another message!').tree(); + msg = stx` + + Another message! + `; await view.model.handleMessageStanza(msg); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2); expect(view.model.messages.last().occupant.get('affiliation')).toBe('member'); expect(view.model.messages.last().occupant.get('role')).toBe('participant'); expect(sizzle('.chat-msg', view).pop().classList.value.trim()).toBe('message chat-msg groupchat chat-msg--with-avatar participant member'); - presence = $pres({ - to:'romeo@montague.lit/orchard', - from:'lounge@montague.lit/romeo', - id: u.getUniqueId() - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({ - affiliation: 'owner', - jid: 'romeo@montague.lit/orchard', - role: 'moderator' - }).up() - .c('status').attrs({code:'110'}).up() - .c('status').attrs({code:'210'}).nodeTree; + presence = stx` + + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); view.model.sendMessage({'body': 'hello world'}); @@ -214,12 +224,14 @@ describe("A Groupchat Message", function () { expect(view.querySelectorAll('.chat-msg').length).toBe(3); await u.waitUntil(() => sizzle('.chat-msg', view).pop().classList.value.trim() === 'message chat-msg groupchat chat-msg--with-avatar moderator owner'); - msg = $msg({ - from: 'lounge@montague.lit/some1', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t('Message from someone not in the MUC right now').tree(); + msg = stx` + + Message from someone not in the MUC right now + `; await view.model.handleMessageStanza(msg); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 4); @@ -228,36 +240,45 @@ describe("A Groupchat Message", function () { // Check that the occupant gets added/removed to the message as it // gets removed or added. - presence = $pres({ - to:'romeo@montague.lit/orchard', - from:'lounge@montague.lit/some1', - id: u.getUniqueId() - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({jid: 'some1@montague.lit/orchard'}); + presence = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.model.messages.last().occupant); expect(view.model.messages.last().get('message')).toBe('Message from someone not in the MUC right now'); expect(view.model.messages.last().occupant.get('nick')).toBe('some1'); expect(view.model.messages.last().occupant.get('jid')).toBe('some1@montague.lit'); - presence = $pres({ - to:'romeo@montague.lit/orchard', - type: 'unavailable', - from:'lounge@montague.lit/some1', - id: u.getUniqueId() - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({jid: 'some1@montague.lit/orchard'}); + presence = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => !view.model.messages.last().occupant); expect(view.model.messages.last().get('message')).toBe('Message from someone not in the MUC right now'); expect(view.model.messages.last().occupant).toBeUndefined(); - presence = $pres({ - to:'romeo@montague.lit/orchard', - from:'lounge@montague.lit/some1', - id: u.getUniqueId() - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({jid: 'some1@montague.lit/orchard'}); + presence = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.model.messages.last().occupant); expect(view.model.messages.last().get('message')).toBe('Message from someone not in the MUC right now'); @@ -285,7 +306,7 @@ describe("A Groupchat Message", function () { expect(view.querySelectorAll('.chat-msg__body.chat-msg__body--received').length).toBe(0); const msg_obj = view.model.messages.at(0); - const stanza = u.toStanza(` + const stanza = stx` - `); + `; await view.model.handleMessageStanza(stanza); await u.waitUntil(() => view.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500); expect(view.querySelectorAll('.chat-msg__receipt').length).toBe(0); @@ -328,7 +349,7 @@ describe("A Groupchat Message", function () { expect(view.querySelectorAll('.chat-msg').length).toBe(1); const msg_obj = view.model.messages.at(0); - let stanza = u.toStanza(` + let stanza = stx` - `); + `; await view.model.handleMessageStanza(stanza); await u.waitUntil(() => view.model.messages.last().get('received')); - stanza = u.toStanza(` + stanza = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); expect(view.querySelectorAll('.chat-msg').length).toBe(1); })); diff --git a/src/plugins/muc-views/tests/muc-registration.js b/src/plugins/muc-views/tests/muc-registration.js index df48ef94ab..9f14a9b971 100644 --- a/src/plugins/muc-views/tests/muc-registration.js +++ b/src/plugins/muc-views/tests/muc-registration.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { $iq, Strophe, sizzle, u } = converse.env; +const { Strophe, sizzle, u, stx } = converse.env; describe("Chatrooms", function () { @@ -28,18 +28,21 @@ describe("Chatrooms", function () { .toBe(``+ ``); - const result = $iq({ - 'from': view.model.get('jid'), - 'id': stanza.getAttribute('id'), - 'to': _converse.bare_jid, - 'type': 'result', - }).c('query', {'type': 'jabber:iq:register'}) - .c('x', {'xmlns': 'jabber:x:data', 'type': 'form'}) - .c('field', { - 'label': 'Desired Nickname', - 'type': 'text-single', - 'var': 'muc#register_roomnick' - }).c('required'); + + const result = stx` + + + + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(result)); stanza = await u.waitUntil(() => _converse.api.connection.get().IQ_stanzas.filter( iq => sizzle(`iq[to="${muc_jid}"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length diff --git a/src/plugins/muc-views/tests/muc.js b/src/plugins/muc-views/tests/muc.js index 3eb7b8e136..d967c3786d 100644 --- a/src/plugins/muc-views/tests/muc.js +++ b/src/plugins/muc-views/tests/muc.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { $pres, $iq, $msg, Strophe, Promise, sizzle, u } = converse.env; +const { $pres, Strophe, Promise, sizzle, stx, u } = converse.env; describe("Groupchats", function () { @@ -16,14 +16,18 @@ describe("Groupchats", function () { const disco_selector = `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`; const stanza = await u.waitUntil(() => IQ_stanzas.filter(iq => iq.querySelector(disco_selector)).pop()); + // We pretend this is a new room, so no disco info is returned. - const features_stanza = $iq({ - 'from': 'lounge@montague.lit', - 'id': stanza.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'error' - }).c('error', {'type': 'cancel'}) - .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); + const features_stanza = + stx` + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); const view = _converse.chatboxviews.get(muc_jid); @@ -44,27 +48,18 @@ describe("Groupchats", function () { // and receives their own presence from the server. // See example 24: // https://xmpp.org/extensions/xep-0045.html#enter-pres - // - /* - * - * - * - * - * - * - */ - const presence = $pres({ - to:'romeo@montague.lit/orchard', - from:'lounge@montague.lit/nicky', - id:'5025e055-036c-4bc5-a227-706e7e352053' - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({ - affiliation: 'owner', - jid: 'romeo@montague.lit/orchard', - role: 'moderator' - }).up() - .c('status').attrs({code:'110'}).up() - .c('status').attrs({code:'201'}).nodeTree; + const presence = + stx` + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED); @@ -165,23 +160,21 @@ describe("Groupchats", function () { let iq_get = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq query[xmlns="${Strophe.NS.MAM}"]`)).pop()); const first_msg_id = _converse.api.connection.get().getUniqueId(); const last_msg_id = _converse.api.connection.get().getUniqueId(); - let message = u.toStanza( - ` - - - - - 1st Message - - - - `); - _converse.api.connection.get()._dataRecv(mock.createRequest(message)); - - message = u.toStanza( - ` + + + + + 1st Message + + + + `)); + + let message = stx` @@ -192,11 +185,10 @@ describe("Groupchats", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); - const result = u.toStanza( - ` + const result = stx` ${first_msg_id} @@ -204,7 +196,7 @@ describe("Groupchats", function () { 2 - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(result)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2); @@ -218,23 +210,23 @@ describe("Groupchats", function () { // See example 24: https://xmpp.org/extensions/xep-0045.html#enter-pres await mock.receiveOwnMUCPresence(_converse, muc_jid, nick); - message = u.toStanza(` + message = stx` Wherefore art though? - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); - message = u.toStanza(` + message = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); const affs = api.settings.get('muc_fetch_members'); @@ -259,14 +251,14 @@ describe("Groupchats", function () { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); - const message = u.toStanza(` + const message = stx` Wherefore art though? - `); + `; view.model.ui.set('scrolled', true); // hack _converse.api.connection.get()._dataRecv(mock.createRequest(message)); @@ -284,23 +276,22 @@ describe("Groupchats", function () { it("is shown the header", mock.initConverse([], {}, async function (_converse) { await mock.openAndEnterChatRoom(_converse, 'jdev@conference.jabber.org', 'jc'); const text = 'Jabber/XMPP Development | RFCs and Extensions: https://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org'; - let stanza = u.toStanza(` + let stanza = stx` ${text} - `); + `; _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 = u.toStanza( - ` + stanza = stx` This is a message subject This is a 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); @@ -313,12 +304,12 @@ describe("Groupchats", function () { it("can be toggled by the user", mock.initConverse([], {}, async function (_converse) { await mock.openAndEnterChatRoom(_converse, 'jdev@conference.jabber.org', 'jc'); const text = 'Jabber/XMPP Development | RFCs and Extensions: https://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org'; - let stanza = u.toStanza(` + let stanza = stx` ${text} - `); + `; _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)); @@ -326,11 +317,10 @@ describe("Groupchats", function () { const head_desc = await u.waitUntil(() => view.querySelector('.chat-head__desc')); expect(head_desc?.textContent.trim()).toBe(text); - stanza = u.toStanza( - ` + stanza = stx` This is a message subject This is a 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); @@ -351,10 +341,9 @@ describe("Groupchats", function () { it("will always be shown when it's new", mock.initConverse([], {}, async function (_converse) { await mock.openAndEnterChatRoom(_converse, 'jdev@conference.jabber.org', 'jc'); const text = 'Jabber/XMPP Development | RFCs and Extensions: https://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org'; - let stanza = u.toStanza(` - + let stanza = stx` ${text} - `); + `; _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)); @@ -371,10 +360,9 @@ describe("Groupchats", function () { toggle.click(); await u.waitUntil(() => !u.isVisible(topic_el)); - stanza = u.toStanza(` - + stanza = stx` Another topic - `); + `; _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'); @@ -387,41 +375,41 @@ describe("Groupchats", function () { await mock.openAndEnterChatRoom(_converse, 'jdev@conference.jabber.org', 'romeo'); const view = _converse.chatboxviews.get('jdev@conference.jabber.org'); - _converse.api.connection.get()._dataRecv(mock.createRequest(u.toStanza(` + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` This is an older topic - `))); + `)); await u.waitUntil(() => view.model.handleSubjectChange.calls.count()); expect(sizzle('.chat-info__message', view).length).toBe(0); const desc = await u.waitUntil(() => view.querySelector('.chat-head__desc')); expect(desc.textContent.trim()).toBe('This is an older topic'); - _converse.api.connection.get()._dataRecv(mock.createRequest(u.toStanza(` + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` This is a new topic - `))); + `)); await u.waitUntil(() => view.model.handleSubjectChange.calls.count() === 2); await u.waitUntil(() => sizzle('.chat-info__message', view).pop()?.textContent.trim() === 'Topic set by ralphm'); await u.waitUntil(() => desc.textContent.trim() === 'This is a new topic'); // Doesn't show multiple subsequent topic change notifications - _converse.api.connection.get()._dataRecv(mock.createRequest(u.toStanza(` + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` Yet another topic - `))); + `)); await u.waitUntil(() => view.model.handleSubjectChange.calls.count() === 3); await u.waitUntil(() => desc.textContent.trim() === 'Yet another topic'); expect(sizzle('.chat-info__message', view).length).toBe(1); // Sow multiple subsequent topic change notification from someone else - _converse.api.connection.get()._dataRecv(mock.createRequest(u.toStanza(` + _converse.api.connection.get()._dataRecv(mock.createRequest(stx` Some1's topic - `))); + `)); await u.waitUntil(() => view.model.handleSubjectChange.calls.count() === 4); await u.waitUntil(() => desc.textContent.trim() === "Some1's topic"); expect(sizzle('.chat-info__message', view).length).toBe(2); @@ -429,10 +417,9 @@ describe("Groupchats", function () { expect(el.textContent.trim()).toBe('Topic set by some1'); // Removes current topic - const stanza = u.toStanza( - ` + const stanza = stx` - `); + `; _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); @@ -450,14 +437,16 @@ describe("Groupchats", function () { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterChatRoom(_converse, muc_jid , 'romeo'); const model = _converse.chatboxes.get(muc_jid); - const message = 'Hello world', - nick = mock.chatroom_names[0], - msg = $msg({ - 'from': 'lounge@montague.lit/'+nick, - 'id': u.getUniqueId(), - 'to': 'romeo@montague.lit', - 'type': 'groupchat' - }).c('body').t(message).tree(); + const message = 'Hello world'; + const nick = mock.chatroom_names[0]; + const msg = + stx` + ${message} + `; await model.handleMessageStanza(msg); await u.waitUntil(() => document.querySelector('converse-chat-message')); @@ -477,14 +466,16 @@ describe("Groupchats", function () { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterChatRoom(_converse, muc_jid , 'romeo'); const view = _converse.chatboxviews.get(muc_jid); - const message = 'Hello world', - nick = mock.chatroom_names[0], - msg = $msg({ - 'from': 'lounge@montague.lit/'+nick, - 'id': u.getUniqueId(), - 'to': 'romeo@montague.lit', - 'type': 'groupchat' - }).c('body').t(message).tree(); + const message = 'Hello world'; + const nick = mock.chatroom_names[0]; + const msg = + stx` + ${message} + `; await view.model.handleMessageStanza(msg); await view.model.close(); @@ -505,14 +496,16 @@ describe("Groupchats", function () { view.renderChatArea(); } expect(_converse.chatboxes.length).toEqual(2); - const message = 'Please go to xmpp:coven@chat.shakespeare.lit?join', - nick = mock.chatroom_names[0], - msg = $msg({ - 'from': 'lounge@montague.lit/'+nick, - 'id': u.getUniqueId(), - 'to': 'romeo@montague.lit', - 'type': 'groupchat' - }).c('body').t(message).tree(); + const message = 'Please go to xmpp:coven@chat.shakespeare.lit?join'; + const nick = mock.chatroom_names[0]; + const msg = + stx` + ${message} + `; await view.model.handleMessageStanza(msg); await u.waitUntil(() => view.querySelector('.chat-msg__text a')); @@ -531,26 +524,16 @@ describe("Groupchats", function () { await mock.waitForReservedNick(_converse, muc_jid, nick); const view = _converse.chatboxviews.get(muc_jid); - /* - * - * - * - * - * - * - */ - const presence = $pres({ - to: 'romeo@montague.lit/orchard', - 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'}).up() - .c('status', {code: '100'}); + const presence = + stx` + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED)); @@ -634,12 +617,14 @@ describe("Groupchats", function () { await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "some1 and newguy have entered the groupchat"); - const msg = $msg({ - 'from': 'coven@chat.shakespeare.lit/some1', - 'id': u.getUniqueId(), - 'to': 'romeo@montague.lit', - 'type': 'groupchat' - }).c('body').t('hello world').tree(); + const msg = + stx` + hello world + `; _converse.api.connection.get()._dataRecv(mock.createRequest(msg)); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length); @@ -841,127 +826,115 @@ describe("Groupchats", function () { 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 = u.toStanza( - ` + let presence = stx` - `); + `; _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 = u.toStanza( - ` + presence = stx` - `); + `; _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 = u.toStanza( - ` + presence = stx` - `); + `; _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 = u.toStanza( - ` + presence = stx` - `); + `; _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 = u.toStanza( - ` + presence = stx` - `); + `; _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 = u.toStanza( - ` + presence = stx` - `); + `; _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 = u.toStanza( - ` + presence = stx` - `); + `; _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\nfuvuv has left the groupchat"); - presence = u.toStanza( - ` + presence = stx` Disconnected: Replaced by new connection - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo, jcbrand and Dele Olajide have entered the groupchat\nfuvuv and fabio have left the groupchat"); - presence = u.toStanza( - ` + presence = stx` Ready for a new day - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo, jcbrand and others have entered the groupchat\nfuvuv has left the groupchat"); - presence = u.toStanza( - ` + presence = stx` Disconnected: closed - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo, jcbrand and Dele Olajide have entered the groupchat\nfuvuv and fabio have left the groupchat"); - presence = u.toStanza( - ` + presence = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo and jcbrand have entered the groupchat\nfuvuv, fabio and Dele Olajide have left the groupchat"); - presence = u.toStanza( - ` + presence = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo, jcbrand and fabio have entered the groupchat\nfuvuv and Dele Olajide have left the groupchat"); @@ -991,13 +964,12 @@ describe("Groupchats", function () { expect(view.querySelector('.chat-content__notifications').textContent.trim()).toBe(''); await mock.sendMessage(view, 'hello world'); - presence = u.toStanza( - ` + presence = stx` Gotta go! - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.model.onOccupantRemoved.calls.count()); @@ -1014,47 +986,57 @@ describe("Groupchats", function () { await mock.openAndEnterChatRoom(_converse, 'conversations@conference.siacs.eu', 'romeo'); - const presence = $pres({ - to: 'romeo@montague.lit/orchard', - from: 'conversations@conference.siacs.eu/Guus' - }).c('x', { - 'xmlns': Strophe.NS.MUC_USER - }).c('item', { - 'affiliation': 'none', - 'jid': 'Guus@montague.lit/xxx', - 'role': 'visitor' - }); + const presence = + stx` + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); const view = _converse.chatboxviews.get('conversations@conference.siacs.eu'); - const msg = $msg({ - 'from': 'conversations@conference.siacs.eu/romeo', - 'id': u.getUniqueId(), - 'to': 'romeo@montague.lit', - 'type': 'groupchat' - }).c('body').t('Some message').tree(); + const msg = + stx` + Some message + `; await view.model.handleMessageStanza(msg); await u.waitUntil(() => sizzle('.chat-msg:last .chat-msg__text', view).pop()); - let stanza = u.toStanza( - ` + let stanza = + stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); - stanza = u.toStanza( - ` - + stanza = + stx` + bf987c486c51fbc05a6a4a9f20dd19b5efba3758 - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo and Guus have entered the groupchat"); @@ -1062,9 +1044,9 @@ describe("Groupchats", function () { })); it("must first be configured if it's a new", - mock.initConverse(['chatBoxesFetched'], - { muc_instant_rooms: false }, - async function (_converse) { + mock.initConverse(['chatBoxesFetched'], + { muc_instant_rooms: false }, + async function (_converse) { let sent_IQ, IQ_id; const sendIQ = _converse.api.connection.get().sendIQ; @@ -1079,34 +1061,28 @@ describe("Groupchats", function () { await u.waitUntil(() => u.isVisible(view)); // We pretend this is a new room, so no disco info is returned. - const features_stanza = $iq({ - from: 'coven@chat.shakespeare.lit', - 'id': IQ_id, - 'to': 'romeo@montague.lit/desktop', - 'type': 'error' - }).c('error', {'type': 'cancel'}) - .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); + const features_stanza = + stx` + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); - /* - * - * - * - * - * - */ - const 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'}).up() - .c('status', {code: '201'}); // This is a locked room that needs to be configured first + const presence = + stx` + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.model.getOwnOccupant()?.get('affiliation') === 'owner'); @@ -1118,13 +1094,6 @@ describe("Groupchats", function () { /* Check that an IQ is sent out, asking for the * configuration form. * See: // https://xmpp.org/extensions/xep-0045.html#example-163 - * - * - * - * */ expect(Strophe.serialize(iq)).toBe( ``+ @@ -1134,119 +1103,100 @@ describe("Groupchats", function () { /* Server responds with the configuration form. * See: // https://xmpp.org/extensions/xep-0045.html#example-165 */ - const config_stanza = $iq({from: muc_jid, - 'id': iq.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'result'}) - .c('query', { 'xmlns': 'http://jabber.org/protocol/muc#owner'}) - .c('x', { 'xmlns': 'jabber:x:data', 'type': 'form'}) - .c('title').t('Configuration for "coven" Room').up() - .c('instructions').t('Complete this form to modify the configuration of your room.').up() - .c('field', {'type': 'hidden', 'var': 'FORM_TYPE'}) - .c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up() - .c('field', { - 'label': 'Natural-Language Room Name', - 'type': 'text-single', - 'var': 'muc#roomconfig_roomname'}) - .c('value').t('A Dark Cave').up().up() - .c('field', { - 'label': 'Short Description of Room', - 'type': 'text-single', - 'var': 'muc#roomconfig_roomdesc'}) - .c('value').t('The place for all good witches!').up().up() - .c('field', { - 'label': 'Enable Public Logging?', - 'type': 'boolean', - 'var': 'muc#roomconfig_enablelogging'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Allow Occupants to Change Subject?', - 'type': 'boolean', - 'var': 'muc#roomconfig_changesubject'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Allow Occupants to Invite Others?', - 'type': 'boolean', - 'var': 'muc#roomconfig_allowinvites'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Who Can Send Private Messages?', - 'type': 'list-single', - 'var': 'muc#roomconfig_allowpm'}) - .c('value').t('anyone').up() - .c('option', {'label': 'Anyone'}) - .c('value').t('anyone').up().up() - .c('option', {'label': 'Anyone with Voice'}) - .c('value').t('participants').up().up() - .c('option', {'label': 'Moderators Only'}) - .c('value').t('moderators').up().up() - .c('option', {'label': 'Nobody'}) - .c('value').t('none').up().up().up() - .c('field', { - 'label': 'Roles for which Presence is Broadcasted', - 'type': 'list-multi', - 'var': 'muc#roomconfig_presencebroadcast'}) - .c('value').t('moderator').up() - .c('value').t('participant').up() - .c('value').t('visitor').up() - .c('option', {'label': 'Moderator'}) - .c('value').t('moderator').up().up() - .c('option', {'label': 'Participant'}) - .c('value').t('participant').up().up() - .c('option', {'label': 'Visitor'}) - .c('value').t('visitor').up().up().up() - .c('field', { - 'label': 'Roles and Affiliations that May Retrieve Member List', - 'type': 'list-multi', - 'var': 'muc#roomconfig_getmemberlist'}) - .c('value').t('moderator').up() - .c('value').t('participant').up() - .c('value').t('visitor').up() - .c('option', {'label': 'Moderator'}) - .c('value').t('moderator').up().up() - .c('option', {'label': 'Participant'}) - .c('value').t('participant').up().up() - .c('option', {'label': 'Visitor'}) - .c('value').t('visitor').up().up().up() - .c('field', { - 'label': 'Make Room Publicly Searchable?', - 'type': 'boolean', - 'var': 'muc#roomconfig_publicroom'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Make Room Publicly Searchable?', - 'type': 'boolean', - 'var': 'muc#roomconfig_publicroom'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Make Room Persistent?', - 'type': 'boolean', - 'var': 'muc#roomconfig_persistentroom'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Make Room Moderated?', - 'type': 'boolean', - 'var': 'muc#roomconfig_moderatedroom'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Make Room Members Only?', - 'type': 'boolean', - 'var': 'muc#roomconfig_membersonly'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Password Required for Entry?', - 'type': 'boolean', - 'var': 'muc#roomconfig_passwordprotectedroom'}) - .c('value').t(1).up().up() - .c('field', {'type': 'fixed'}) - .c('value').t( - 'If a password is required to enter this groupchat, you must specify the password below.' - ).up().up() - .c('field', { - 'label': 'Password', - 'type': 'text-private', - 'var': 'muc#roomconfig_roomsecret'}) - .c('value').t('cauldronburn'); + const config_stanza = + stx` + + + Configuration for "coven" Room + Complete this form to modify the configuration of your room. + + http://jabber.org/protocol/muc#roomconfig + + + A Dark Cave + + + The place for all good witches! + + + 0 + + + 0 + + + 0 + + + anyone + + + + + + + moderator + participant + visitor + + + + + + moderator + participant + visitor + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 1 + + + If a password is required to enter this groupchat, you must specify the password below. + + + cauldronburn + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(config_stanza)); const modal = _converse.api.modal.get('converse-muc-config-modal'); @@ -1290,7 +1240,6 @@ describe("Groupchats", function () { `moderator`+ `moderator,participant,visitor`+ `0`+ - `0`+ `0`+ `1`+ `1`+ @@ -1330,34 +1279,28 @@ describe("Groupchats", function () { await u.waitUntil(() => u.isVisible(view)); // We pretend this is a new room, so no disco info is returned. - const features_stanza = $iq({ - from: muc_jid, - 'id': IQ_id, - 'to': 'romeo@montague.lit/desktop', - 'type': 'error' - }).c('error', {'type': 'cancel'}) - .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); + const features_stanza = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); - /* - * - * - * - * - * - */ - const 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'}).up() - .c('status', {code: '201'}); // This is a locked room that needs to be configured first + const presence = + stx` + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.model.getOwnOccupant()?.get('affiliation') === 'owner'); @@ -1385,119 +1328,105 @@ describe("Groupchats", function () { /* Server responds with the configuration form. * See: // https://xmpp.org/extensions/xep-0045.html#example-165 */ - const config_stanza = $iq({from: muc_jid, - 'id': iq.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'result'}) - .c('query', { 'xmlns': 'http://jabber.org/protocol/muc#owner'}) - .c('x', { 'xmlns': 'jabber:x:data', 'type': 'form'}) - .c('title').t('Configuration for "coven" Room').up() - .c('instructions').t('Complete this form to modify the configuration of your room.').up() - .c('field', {'type': 'hidden', 'var': 'FORM_TYPE'}) - .c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up() - .c('field', { - 'label': 'Natural-Language Room Name', - 'type': 'text-single', - 'var': 'muc#roomconfig_roomname'}) - .c('value').t('A Dark Cave').up().up() - .c('field', { - 'label': 'Short Description of Room', - 'type': 'text-single', - 'var': 'muc#roomconfig_roomdesc'}) - .c('value').t('The place for all good witches!').up().up() - .c('field', { - 'label': 'Enable Public Logging?', - 'type': 'boolean', - 'var': 'muc#roomconfig_enablelogging'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Allow Occupants to Change Subject?', - 'type': 'boolean', - 'var': 'muc#roomconfig_changesubject'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Allow Occupants to Invite Others?', - 'type': 'boolean', - 'var': 'muc#roomconfig_allowinvites'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Who Can Send Private Messages?', - 'type': 'list-single', - 'var': 'muc#roomconfig_allowpm'}) - .c('value').t('anyone').up() - .c('option', {'label': 'Anyone'}) - .c('value').t('anyone').up().up() - .c('option', {'label': 'Anyone with Voice'}) - .c('value').t('participants').up().up() - .c('option', {'label': 'Moderators Only'}) - .c('value').t('moderators').up().up() - .c('option', {'label': 'Nobody'}) - .c('value').t('none').up().up().up() - .c('field', { - 'label': 'Roles for which Presence is Broadcasted', - 'type': 'list-multi', - 'var': 'muc#roomconfig_presencebroadcast'}) - .c('value').t('moderator').up() - .c('value').t('participant').up() - .c('value').t('visitor').up() - .c('option', {'label': 'Moderator'}) - .c('value').t('moderator').up().up() - .c('option', {'label': 'Participant'}) - .c('value').t('participant').up().up() - .c('option', {'label': 'Visitor'}) - .c('value').t('visitor').up().up().up() - .c('field', { - 'label': 'Roles and Affiliations that May Retrieve Member List', - 'type': 'list-multi', - 'var': 'muc#roomconfig_getmemberlist'}) - .c('value').t('moderator').up() - .c('value').t('participant').up() - .c('value').t('visitor').up() - .c('option', {'label': 'Moderator'}) - .c('value').t('moderator').up().up() - .c('option', {'label': 'Participant'}) - .c('value').t('participant').up().up() - .c('option', {'label': 'Visitor'}) - .c('value').t('visitor').up().up().up() - .c('field', { - 'label': 'Make Room Publicly Searchable?', - 'type': 'boolean', - 'var': 'muc#roomconfig_publicroom'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Make Room Publicly Searchable?', - 'type': 'boolean', - 'var': 'muc#roomconfig_publicroom'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Make Room Persistent?', - 'type': 'boolean', - 'var': 'muc#roomconfig_persistentroom'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Make Room Moderated?', - 'type': 'boolean', - 'var': 'muc#roomconfig_moderatedroom'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Make Room Members Only?', - 'type': 'boolean', - 'var': 'muc#roomconfig_membersonly'}) - .c('value').t(0).up().up() - .c('field', { - 'label': 'Password Required for Entry?', - 'type': 'boolean', - 'var': 'muc#roomconfig_passwordprotectedroom'}) - .c('value').t(1).up().up() - .c('field', {'type': 'fixed'}) - .c('value').t( - 'If a password is required to enter this groupchat, you must specify the password below.' - ).up().up() - .c('field', { - 'label': 'Password', - 'type': 'text-private', - 'var': 'muc#roomconfig_roomsecret'}) - .c('value').t('cauldronburn'); + const config_stanza = + stx` + + + Configuration for "coven" Room + Complete this form to modify the configuration of your room. + + http://jabber.org/protocol/muc#roomconfig + + + A Dark Cave + + + The place for all good witches! + + + 0 + + + 0 + + + 0 + + + anyone + + + + + + + moderator + participant + visitor + + + + + + moderator + participant + visitor + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 1 + + + If a password is required to enter this groupchat, you must specify the password below. + + + cauldronburn + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(config_stanza)); const modal = _converse.api.modal.get('converse-muc-config-modal'); @@ -1535,47 +1464,45 @@ describe("Groupchats", function () { modal.querySelector('.chatroom-form input[type="submit"]').click(); - console.log(Strophe.serialize(sent_IQ)); - expect(Strophe.serialize(sent_IQ)).toBe( - ``+ - ``+ - ``+ - `http://jabber.org/protocol/muc#roomconfig`+ - `A Dark Cave`+ - `The place for all good witches!`+ - `0`+ - `0`+ - `0`+ - `moderators`+ - `moderator`+ - `moderator,participant,visitor`+ - `0`+ - `0`+ - `0`+ - `1`+ - `1`+ - `1`+ - `cauldronburn`+ - ``+ - ``+ - ``); + ``+ + ``+ + ``+ + `http://jabber.org/protocol/muc#roomconfig`+ + `A Dark Cave`+ + `The place for all good witches!`+ + `0`+ + `0`+ + `0`+ + `moderators`+ + `moderator`+ + `moderator,participant,visitor`+ + `0`+ + `0`+ + `1`+ + `1`+ + `1`+ + `cauldronburn`+ + ``+ + ``+ + ``); })); it("properly handles notification that a room has been destroyed", mock.initConverse([], {}, async function (_converse) { await mock.openChatRoomViaModal(_converse, 'problematic@muc.montague.lit', 'romeo') - const presence = $pres().attrs({ - from:'problematic@muc.montague.lit', - id:'n13mt3l', - to:'romeo@montague.lit/pda', - type:'error'}) - .c('error').attrs({'type':'cancel'}) - .c('gone').attrs({'xmlns':'urn:ietf:params:xml:ns:xmpp-stanzas'}) - .t('xmpp:other-room@chat.jabberfr.org?join').up() - .c('text').attrs({'xmlns':'urn:ietf:params:xml:ns:xmpp-stanzas'}) - .t("We didn't like the name").nodeTree; + const presence = + stx` + + xmpp:other-room@chat.jabberfr.org?join + We didn't like the name + + `; const view = _converse.chatboxviews.get('problematic@muc.montague.lit'); _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); @@ -1683,10 +1610,13 @@ describe("Groupchats", function () { expect(_converse.chatboxes.models.length).toBe(1); expect(_converse.chatboxes.models[0].id).toBe("controlbox"); - const stanza = u.toStanza(` - + const stanza = stx` + - `); + `.tree(); await _converse.onDirectMUCInvitation(stanza); expect(_converse.api.confirm).toHaveBeenCalledWith( @@ -1710,13 +1640,16 @@ describe("Groupchats", function () { 'muc_jid': `${view.model.get('jid')}/${nick}` }); - const message = $msg({ - from: 'lounge@montague.lit/'+nick, - id: '1', - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t(text); - await view.model.handleMessageStanza(message.nodeTree); + const message = + stx` + ${text} + `; + await view.model.handleMessageStanza(message.tree()); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length); expect(view.querySelectorAll('.chat-msg').length).toBe(1); expect(view.querySelector('.chat-msg__text').textContent.trim()).toBe(text); @@ -1743,7 +1676,7 @@ describe("Groupchats", function () { // Let's check that if we receive the same message again, it's // not shown. - const stanza = u.toStanza(` + const stanza = stx` - `); + `; await view.model.handleMessageStanza(stanza); expect(view.querySelectorAll('.chat-msg').length).toBe(1); expect(sizzle('.chat-msg__text:last').pop().textContent.trim()).toBe(text); @@ -1773,13 +1706,14 @@ describe("Groupchats", function () { for (let i=0; i<20; i++) { promises.push( view.model.handleMessageStanza( - $msg({ - from: 'lounge@montague.lit/someone', - to: 'romeo@montague.lit.com', - type: 'groupchat', - id: u.getUniqueId(), - }).c('body').t('Message: '+i).tree()) - ); + stx` + Message: ${i} + ` + )); } await Promise.all(promises); const promise = u.getOpenPromise(); @@ -1789,12 +1723,14 @@ describe("Groupchats", function () { const content = view.querySelector('.chat-content'); content.scrollTop = 0; await view.model.handleMessageStanza( - $msg({ - from: 'lounge@montague.lit/someone', - to: 'romeo@montague.lit.com', - type: 'groupchat', - id: u.getUniqueId(), - }).c('body').t(message).tree()); + stx` + ${message} + ` + ); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 21); // Now check that the message appears inside the chatbox in the DOM const msg_txt = sizzle('.chat-msg:last .chat-msg__text', content).pop().textContent; @@ -1815,15 +1751,16 @@ describe("Groupchats", function () { const view = _converse.chatboxviews.get(muc_jid); expect(view.model.session.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED); - const stanza = u.toStanza(` - - - - - `); + const stanza = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-info').length); const info_messages = view.querySelectorAll('.chat-content .chat-info'); @@ -1849,47 +1786,26 @@ describe("Groupchats", function () { ``+ ``); - /* - * - * - * - * - * - * - * - * - * - * - * - */ - const features_stanza = $iq({ - 'from': muc_jid, - 'id': stanza.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'result' - }) - .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', { - 'category': 'conference', - 'name': 'A Dark Cave', - 'type': 'text' - }).up() - .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() - .c('feature', {'var': 'muc_passwordprotected'}).up() - .c('feature', {'var': 'muc_hidden'}).up() - .c('feature', {'var': 'muc_temporary'}).up() - .c('feature', {'var': 'muc_open'}).up() - .c('feature', {'var': 'muc_unmoderated'}).up() - .c('feature', {'var': 'muc_nonanonymous'}); + const features_stanza = + stx` + + + + + + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); - let view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); + let view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); const sent_stanzas = _converse.api.connection.get().sent_stanzas; await u.waitUntil(() => sent_stanzas.filter(s => s.matches(`presence[to="${muc_jid}/${nick}"]`)).pop()); view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); @@ -1953,8 +1869,7 @@ describe("Groupchats", function () { const s = `iq[to="${muc_jid}"] query[xmlns="${Strophe.NS.MUC_OWNER}"]`; let iq = await u.waitUntil(() => IQs.filter(iq => iq.querySelector(s)).pop()); - const response_el = u.toStanza( - ` @@ -2017,10 +1932,9 @@ describe("Groupchats", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(response_el)); - modal = _converse.api.modal.get('converse-muc-config-modal'); await u.waitUntil(() => modal.querySelector('.chatroom-form input')); expect(modal.querySelector('.chatroom-form legend').textContent.trim()).toBe("Configuration for room@conference.example.org"); @@ -2029,13 +1943,13 @@ describe("Groupchats", function () { modal.querySelector('.chatroom-form input[type="submit"]').click(); iq = await u.waitUntil(() => IQs.filter(iq => iq.matches(`iq[to="${muc_jid}"][type="set"]`)).pop()); - const result = $iq({ - "xmlns": "jabber:client", - "type": "result", - "to": "romeo@montague.lit/orchard", - "from": "lounge@muc.montague.lit", - "id": iq.getAttribute('id') - }); + + const result = + stx``; IQs.length = 0; // Empty the array _converse.api.connection.get()._dataRecv(mock.createRequest(result)); @@ -2045,17 +1959,6 @@ describe("Groupchats", function () { `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]` )).pop()); - const features_stanza = $iq({ - 'from': muc_jid, - 'id': iq.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'result' - }).c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', { - 'category': 'conference', - 'name': 'New room name', - 'type': 'text' - }).up(); features = [ 'http://jabber.org/protocol/muc', 'jabber:iq:register', @@ -2066,15 +1969,29 @@ describe("Groupchats", function () { 'muc_unmoderated', 'muc_nonanonymous' ]; - features.forEach(f => features_stanza.c('feature', {'var': f}).up()); - features_stanza.c('x', { 'xmlns':'jabber:x:data', 'type':'result'}) - .c('field', {'var':'FORM_TYPE', 'type':'hidden'}) - .c('value').t('http://jabber.org/protocol/muc#roominfo').up().up() - .c('field', {'type':'text-single', 'var':'muc#roominfo_description', 'label':'Description'}) - .c('value').t('This is the description').up().up() - .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'}) - .c('value').t(0); + const features_stanza = + stx` + + + ${features.map(f => stx``).join('')} + + + http://jabber.org/protocol/muc#roominfo + + + This is the description + + + 0 + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); await u.waitUntil(() => new Promise(success => view.model.features.on('change', success))); @@ -2112,40 +2029,34 @@ describe("Groupchats", function () { let IQ_id; const sendIQ = _converse.api.connection.get().sendIQ; - - await mock.openAndEnterChatRoom(_converse, 'coven@chat.shakespeare.lit', 'some1'); spyOn(_converse.api.connection.get(), 'sendIQ').and.callFake(function (iq, callback, errback) { IQ_id = sendIQ.bind(this)(iq, callback, errback); }); + await mock.openAndEnterChatRoom(_converse, 'coven@chat.shakespeare.lit', 'some1'); - // We pretend this is a new room, so no disco info is returned. - const features_stanza = $iq({ - from: 'coven@chat.shakespeare.lit', - 'id': IQ_id, - 'to': 'romeo@montague.lit/desktop', - 'type': 'error' - }).c('error', {'type': 'cancel'}) - .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); + const features_stanza = + stx` + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); - /* - * - * - * - * - * - */ - const message = $msg({ - type:'groupchat', - to: 'romeo@montague.lit/_converse.js-27854181', - from: 'coven@chat.shakespeare.lit' - }).c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('status', {code: '104'}).up() - .c('status', {code: '172'}); + const message = + stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-info').length); const chat_body = view.querySelector('.chatroom-body'); @@ -2156,40 +2067,24 @@ describe("Groupchats", function () { it("informs users if they have been kicked out of the groupchat", mock.initConverse([], {}, async function (_converse) { - /* - * - * - * - * Avaunt, you cullion! - * - * - * - * - * - */ await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); const view = _converse.chatboxviews.get('lounge@montague.lit'); expect(view.model.session.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED); - const presence = $pres().attrs({ - from:'lounge@montague.lit/romeo', - to:'romeo@montague.lit/pda', - type:'unavailable' - }) - .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({ - affiliation: 'none', - jid: 'romeo@montague.lit/pda', - role: 'none' - }) - .c('actor').attrs({nick: 'Fluellen'}).up() - .c('reason').t('Avaunt, you cullion!').up() - .up() - .c('status').attrs({code:'110'}).up() - .c('status').attrs({code:'307'}).nodeTree; + const presence = + stx` + + + + Avaunt, you cullion! + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); @@ -2210,38 +2105,22 @@ describe("Groupchats", function () { it("informs users if they have exited the groupchat due to a technical reason", mock.initConverse([], {}, async function (_converse) { - /* - * - * - * - * Avaunt, you cullion! - * - * - * - * - * - */ await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); - const presence = $pres().attrs({ - from:'lounge@montague.lit/romeo', - to:'romeo@montague.lit/pda', - type:'unavailable' - }) - .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({ - affiliation: 'none', - jid: 'romeo@montague.lit/pda', - role: 'none' - }) - .c('reason').t('Flux capacitor overload!').up() - .up() - .c('status').attrs({code:'110'}).up() - .c('status').attrs({code:'333'}).up() - .c('status').attrs({code:'307'}).nodeTree; + const presence = + stx` + + + Flux capacitor overload! + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); const view = _converse.chatboxviews.get('lounge@montague.lit'); @@ -2305,61 +2184,54 @@ describe("Groupchats", function () { const muc_jid = 'lounge@montague.lit'; await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); - let presence = $pres({ - 'from': 'lounge@montague.lit/annoyingGuy', - 'id':'27C55F89-1C6A-459A-9EB5-77690145D624', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'annoyingguy@montague.lit', - 'affiliation': 'member', - 'role': 'participant' - }); + let presence = + stx` + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo and annoyingGuy have entered the groupchat"); - presence = $pres({ - 'from': 'lounge@montague.lit/annoyingGuy', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'annoyingguy@montague.lit', - 'affiliation': 'member', - 'role': 'visitor' - }); + presence = + stx` + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo has entered the groupchat\nannoyingGuy has been muted"); - presence = $pres({ - 'from': 'lounge@montague.lit/annoyingGuy', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'annoyingguy@montague.lit', - 'affiliation': 'member', - 'role': 'participant' - }); + presence = + stx` + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "romeo has entered the groupchat\nannoyingGuy has been given a voice"); // Check that we don't see an info message concerning the role, // if the affiliation has changed. - presence = $pres({ - 'from': 'lounge@montague.lit/annoyingGuy', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'annoyingguy@montague.lit', - 'affiliation': 'none', - 'role': 'visitor' - }); + presence = + stx` + + + + `; + _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => Array.from(view.querySelectorAll('.chat-info__message')).pop()?.textContent.trim() === @@ -2375,1103 +2247,111 @@ describe("Groupchats", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); - let message = $msg({ - from: 'lounge@montague.lit', - id: '2CF9013B-E8A8-42A1-9633-85AD7CA12F40', - to: 'romeo@montague.lit' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'absentguy@montague.lit', - 'affiliation': 'member', - 'role': 'none' - }); + let message = + stx` + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); await u.waitUntil(() => view.model.occupants.length > 1); expect(view.model.occupants.length).toBe(2); expect(view.model.occupants.findWhere({'jid': 'absentguy@montague.lit'}).get('affiliation')).toBe('member'); - message = $msg({ - from: 'lounge@montague.lit', - id: '2CF9013B-E8A8-42A1-9633-85AD7CA12F41', - to: 'romeo@montague.lit' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'absentguy@montague.lit', - 'affiliation': 'none', - 'role': 'none' - }); + message = + stx` + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); expect(view.model.occupants.length).toBe(2); expect(view.model.occupants.findWhere({'jid': 'absentguy@montague.lit'}).get('affiliation')).toBe('none'); - })); }); + describe("When attempting to enter a groupchat", function () { - describe("Each chat groupchat can take special commands", function () { + it("will show an error message if the groupchat requires a password", + mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) { - it("takes /help to show the available commands", - mock.initConverse([], {}, async function (_converse) { + const muc_jid = 'protected'; + await mock.openChatRoomViaModal(_converse, muc_jid, 'romeo'); + const view = _converse.chatboxviews.get(muc_jid); - spyOn(window, 'confirm').and.callFake(() => true); - await mock.openAndEnterChatRoom(_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 () {}, 'keyCode': 13 }; - 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"]); - })); + const presence = + stx` + + + + + `; - 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) { + _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); - await mock.openAndEnterChatRoom(_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 () {}, 'keyCode': 13 }; - 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'); + const chat_body = view.querySelector('.chatroom-body'); + await u.waitUntil(() => chat_body.querySelectorAll('form.chatroom-form').length === 1); + expect(chat_body.querySelector('.chatroom-form label').textContent.trim()) + .toBe('This groupchat requires a password'); + + // Let's submit the form + spyOn(view.model, 'join'); + const input_el = view.querySelector('[name="password"]'); + input_el.value = 'secret'; + view.querySelector('input[type=submit]').click(); + expect(view.model.join).toHaveBeenCalledWith('romeo', 'secret'); })); - it("takes /member to make an occupant a member", + it("will show an error message if the groupchat is members-only and the user not included", mock.initConverse([], {}, async function (_converse) { - let iq_stanza; - await mock.openAndEnterChatRoom(_converse, 'lounge@muc.montague.lit', 'romeo'); - const view = _converse.chatboxviews.get('lounge@muc.montague.lit'); - /* We don't show join/leave messages for existing occupants. We - * know about them because we receive their presences before we - * receive our own. - */ - const presence = $pres({ - to: 'romeo@montague.lit/orchard', - from: 'lounge@muc.montague.lit/marc' - }).c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'none', - 'jid': 'marc@montague.lit/_converse.js-290929789', - 'role': 'participant' - }); - _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); - expect(view.model.occupants.length).toBe(2); + const muc_jid = 'members-only@muc.montague.lit' + await mock.openChatRoomViaModal(_converse, muc_jid, 'romeo'); + const view = _converse.chatboxviews.get(muc_jid); + const iq = await u.waitUntil(() => _converse.api.connection.get().IQ_stanzas.filter( + iq => iq.querySelector( + `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]` + )).pop()); - const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea')); - let sent_stanza; - spyOn(_converse.api.connection.get(), 'send').and.callFake((stanza) => { - sent_stanza = stanza; - }); + // State that the chat is members-only via the features IQ + const features_stanza = + stx` + + + + + + + + `; + _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); + await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING); - // 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 () {}, - keyCode: 13 - }); - 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 () {}, - keyCode: 13 - }); - await u.waitUntil(() => Strophe.serialize(sent_stanza) === - ``+ - ``+ - ``+ - `Welcome to the club!`+ - ``+ - ``+ - ``); - - let result = $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( - ``+ - ``+ - ``+ - ``+ - ``) - expect(view.model.occupants.length).toBe(2); - - result = $iq({ - "xmlns": "jabber:client", - "type": "result", - "to": "romeo@montague.lit/orchard", - "from": "lounge@muc.montague.lit", - "id": iq_stanza.getAttribute("id") - }).c("query", {"xmlns": "http://jabber.org/protocol/muc#admin"}) - .c("item", {"jid": "marc", "affiliation": "member"}); - _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( - ``+ - ``+ - ``+ - ``+ - ``) - expect(view.model.occupants.length).toBe(2); - - result = $iq({ - "xmlns": "jabber:client", - "type": "result", - "to": "romeo@montague.lit/orchard", - "from": "lounge@muc.montague.lit", - "id": iq_stanza.getAttribute("id") - }).c("query", {"xmlns": "http://jabber.org/protocol/muc#admin"}) - .c("item", {"jid": "romeo@montague.lit", "affiliation": "owner"}); - _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( - ``+ - ``+ - ``+ - ``+ - ``) - expect(view.model.occupants.length).toBe(2); - - result = $iq({ - "xmlns": "jabber:client", - "type": "result", - "to": "romeo@montague.lit/orchard", - "from": "lounge@muc.montague.lit", - "id": iq_stanza.getAttribute("id") - }).c("query", {"xmlns": "http://jabber.org/protocol/muc#admin"}) - _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.openAndEnterChatRoom(_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 () {}, - keyCode: 13 - }); - 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 () {}, - keyCode: 13 - }); - - let sent_stanza = await u.waitUntil(() => sent_stanzas.filter(s => s.textContent.trim() === 'This is a new subject').pop()); - expect(Strophe.serialize(sent_stanza).toLocaleString()).toBe( - ''+ - 'This is a new subject'+ - ''); - - // Check case insensitivity - textarea.value = '/Subject This is yet another subject'; - message_form.onKeyDown({ - target: textarea, - preventDefault: function preventDefault () {}, - keyCode: 13 - }); - sent_stanza = await u.waitUntil(() => sent_stanzas.filter(s => s.textContent.trim() === 'This is yet another subject').pop()); - expect(Strophe.serialize(sent_stanza).toLocaleString()).toBe( - ''+ - 'This is yet another subject'+ - ''); - - while (sent_stanzas.length) { - sent_stanzas.pop(); - } - // Check unsetting the topic - textarea.value = '/topic'; - message_form.onKeyDown({ - target: textarea, - preventDefault: function preventDefault () {}, - keyCode: 13 - }); - sent_stanza = await u.waitUntil(() => sent_stanzas.pop()); - expect(Strophe.serialize(sent_stanza).toLocaleString()).toBe( - ''+ - ''+ - ''); - })); - - it("takes /clear to clear messages", mock.initConverse([], {}, async function (_converse) { - await mock.openAndEnterChatRoom(_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 () {}, - keyCode: 13 - }); - await u.waitUntil(() => _converse.api.confirm.calls.count() === 1); - expect(_converse.api.confirm).toHaveBeenCalledWith('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.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); - const view = _converse.chatboxviews.get('lounge@montague.lit'); - spyOn(view.model, 'validateRoleOrAffiliationChangeArgs').and.callThrough(); - - let presence = $pres({ - 'from': 'lounge@montague.lit/annoyingGuy', - 'id':'27C55F89-1C6A-459A-9EB5-77690145D624', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'annoyingguy@montague.lit', - 'affiliation': 'member', - 'role': 'participant' - }); - _converse.api.connection.get()._dataRecv(mock.createRequest(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 () {}, - keyCode: 13 - }); - 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(Strophe.serialize(sent_IQ)).toBe( - ``+ - ``+ - ``+ - `You're responsible`+ - ``+ - ``+ - ``); - - presence = $pres({ - 'from': 'lounge@montague.lit/annoyingGuy', - 'id':'27C55F89-1C6A-459A-9EB5-77690145D628', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'annoyingguy@montague.lit', - 'affiliation': 'owner', - 'role': 'participant' - }); - _converse.api.connection.get()._dataRecv(mock.createRequest(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.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); - const view = _converse.chatboxviews.get('lounge@montague.lit'); - spyOn(view.model, 'validateRoleOrAffiliationChangeArgs').and.callThrough(); - - let presence = $pres({ - 'from': 'lounge@montague.lit/annoyingGuy', - 'id':'27C55F89-1C6A-459A-9EB5-77690145D624', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'annoyingguy@montague.lit', - 'affiliation': 'member', - 'role': 'participant' - }); - _converse.api.connection.get()._dataRecv(mock.createRequest(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 () {}, - keyCode: 13 - }); - 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(Strophe.serialize(sent_IQ)).toBe( - ``+ - ``+ - ``+ - `You're annoying`+ - ``+ - ``+ - ``); - - presence = $pres({ - 'from': 'lounge@montague.lit/annoyingGuy', - 'id':'27C55F89-1C6A-459A-9EB5-77690145D628', - 'to': 'romeo@montague.lit/desktop' - }).c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'annoyingguy@montague.lit', - 'affiliation': 'outcast', - 'role': 'participant' - }).c('actor', {'nick': 'romeo'}).up() - .c('reason').t("You're annoying").up().up() - .c('status', {'code': '301'}); - - _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("annoyingGuy has been banned by romeo"); - expect(view.querySelector('.chat-info:last-child q').textContent.trim()).toBe("You're annoying"); - presence = $pres({ - 'from': 'lounge@montague.lit/joe2', - 'id':'27C55F89-1C6A-459A-9EB5-77690145D624', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'joe2@montague.lit', - 'affiliation': 'member', - 'role': 'participant' - }); - _converse.api.connection.get()._dataRecv(mock.createRequest(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.openAndEnterChatRoom(_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 = $pres({ - 'from': 'lounge@montague.lit/annoying guy', - 'id':'27C55F89-1C6A-459A-9EB5-77690145D624', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'annoyingguy@montague.lit', - 'affiliation': 'none', - 'role': 'participant' - }); - _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 () {}, - keyCode: 13 - }); - 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( - ``+ - ``+ - ``+ - `You're annoying`+ - ``+ - ``+ - ``); - - /* - * - * - * - * - * - */ - presence = $pres({ - 'from': 'lounge@montague.lit/annoying guy', - 'to': 'romeo@montague.lit/desktop', - 'type': 'unavailable' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'affiliation': 'none', - 'role': 'none' - }).c('actor', {'nick': 'romeo'}).up() - .c('reason').t("You're annoying").up().up() - .c('status', {'code': '307'}); - - _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.openAndEnterChatRoom(_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 - /* - * - * - * - * - */ - let presence = $pres({ - 'from': 'lounge@montague.lit/trustworthyguy', - 'id':'27C55F89-1C6A-459A-9EB5-77690145D624', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'trustworthyguy@montague.lit', - 'affiliation': 'member', - 'role': 'participant' - }); - _converse.api.connection.get()._dataRecv(mock.createRequest(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 () {}, - keyCode: 13 - }); - - 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( - ``+ - ``+ - ``+ - `You're trustworthy`+ - ``+ - ``+ - ``); - - /* - * - * - * - * - */ - presence = $pres({ - 'from': 'lounge@montague.lit/trustworthyguy', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'trustworthyguy@montague.lit', - 'affiliation': 'member', - 'role': 'moderator' - }); - _converse.api.connection.get()._dataRecv(mock.createRequest(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(Strophe.serialize(sent_IQ)).toBe( - ``+ - ``+ - ``+ - `Perhaps not`+ - ``+ - ``+ - ``); - - /* - * - * - * - * - */ - presence = $pres({ - 'from': 'lounge@montague.lit/trustworthyguy', - 'to': 'romeo@montague.lit/desktop' - }).c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'trustworthyguy@montague.lit', - 'affiliation': 'member', - 'role': 'participant' - }); - _converse.api.connection.get()._dataRecv(mock.createRequest(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.openAndEnterChatRoom(_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 - /* - * - * - * - * - */ - let presence = $pres({ - 'from': 'lounge@montague.lit/annoyingGuy', - 'id':'27C55F89-1C6A-459A-9EB5-77690145D624', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'annoyingguy@montague.lit', - 'affiliation': 'member', - 'role': 'participant' - }); - _converse.api.connection.get()._dataRecv(mock.createRequest(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 () {}, - keyCode: 13 - }); - - 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(Strophe.serialize(sent_IQ)).toBe( - ``+ - ``+ - ``+ - `You're annoying`+ - ``+ - ``+ - ``); - - /* - * - * - * - * - */ - presence = $pres({ - 'from': 'lounge@montague.lit/annoyingGuy', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'annoyingguy@montague.lit', - 'affiliation': 'member', - 'role': 'visitor' - }); - _converse.api.connection.get()._dataRecv(mock.createRequest(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(Strophe.serialize(sent_IQ)).toBe( - ``+ - ``+ - ``+ - `Now you can talk again`+ - ``+ - ``+ - ``); - - /* - * - * - * - * - */ - presence = $pres({ - 'from': 'lounge@montague.lit/annoyingGuy', - 'to': 'romeo@montague.lit/desktop' - }) - .c('x', { 'xmlns': 'http://jabber.org/protocol/muc#user'}) - .c('item', { - 'jid': 'annoyingguy@montague.lit', - 'affiliation': 'member', - 'role': 'participant' - }); - _converse.api.connection.get()._dataRecv(mock.createRequest(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.openAndEnterChatRoom(_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('.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(); - expect(u.hasClass('error', challenge_el)).toBeTruthy(); - 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(Strophe.serialize(sent_IQ)).toBe( - ``+ - ``+ - ``+ - ``+ - `Moved to a new location`+ - ``+ - ``+ - ``+ - ``); - - let result_stanza = $iq({ - 'type': 'result', - 'id': sent_IQ.getAttribute('id'), - 'from': view.model.get('jid'), - 'to': _converse.api.connection.get().jid - }); - 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.openAndEnterChatRoom(_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.onFormSubmitted(new Event('submit')); - modal = await u.waitUntil(() => document.querySelector('.modal-dialog')); - await u.waitUntil(() => u.isVisible(modal)); - - challenge_el = modal.querySelector('[name="challenge"]'); - challenge_el.value = new_muc_jid; - submit = modal.querySelector('[type="submit"]'); - submit.click(); - - sent_IQ = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector('destroy')).pop()); - expect(Strophe.serialize(sent_IQ)).toBe( - ``+ - ``+ - ``+ - ``+ - ``); - - result_stanza = $iq({ - 'type': 'result', - 'id': sent_IQ.getAttribute('id'), - 'from': view.model.get('jid'), - 'to': _converse.api.connection.get().jid - }); - expect(_converse.chatboxes.length).toBe(2); - _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); - })); - }); - - describe("When attempting to enter a groupchat", function () { - - it("will show an error message if the groupchat requires a password", - mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) { - - const muc_jid = 'protected'; - await mock.openChatRoomViaModal(_converse, muc_jid, 'romeo'); - const view = _converse.chatboxviews.get(muc_jid); - - const presence = $pres().attrs({ - 'from': `${muc_jid}/romeo`, - 'id': u.getUniqueId(), - 'to': 'romeo@montague.lit/pda', - 'type': 'error' - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() - .c('error').attrs({by:'lounge@montague.lit', type:'auth'}) - .c('not-authorized').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}); - - _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); - - const chat_body = view.querySelector('.chatroom-body'); - await u.waitUntil(() => chat_body.querySelectorAll('form.chatroom-form').length === 1); - expect(chat_body.querySelector('.chatroom-form label').textContent.trim()) - .toBe('This groupchat requires a password'); - - // Let's submit the form - spyOn(view.model, 'join'); - const input_el = view.querySelector('[name="password"]'); - input_el.value = 'secret'; - view.querySelector('input[type=submit]').click(); - expect(view.model.join).toHaveBeenCalledWith('romeo', 'secret'); - })); - - it("will show an error message if the groupchat is members-only and the user not included", - mock.initConverse([], {}, async function (_converse) { - - const muc_jid = 'members-only@muc.montague.lit' - await mock.openChatRoomViaModal(_converse, muc_jid, 'romeo'); - const view = _converse.chatboxviews.get(muc_jid); - const iq = await u.waitUntil(() => _converse.api.connection.get().IQ_stanzas.filter( - iq => iq.querySelector( - `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]` - )).pop()); - - // State that the chat is members-only via the features IQ - const features_stanza = $iq({ - 'from': muc_jid, - 'id': iq.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'result' - }) - .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', { - 'category': 'conference', - 'name': 'A Dark Cave', - 'type': 'text' - }).up() - .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() - .c('feature', {'var': 'muc_hidden'}).up() - .c('feature', {'var': 'muc_temporary'}).up() - .c('feature', {'var': 'muc_membersonly'}).up(); - _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); - await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING); - - const presence = $pres().attrs({ - from: `${muc_jid}/romeo`, - id: u.getUniqueId(), - to: 'romeo@montague.lit/pda', - type: 'error' - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() - .c('error').attrs({by:'lounge@montague.lit', type:'auth'}) - .c('registration-required').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + const presence = + stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelector('.chatroom-body converse-muc-disconnected .disconnect-msg:last-child')?.textContent?.trim() === @@ -3489,30 +2369,36 @@ describe("Groupchats", function () { `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]` )).pop()); - const features_stanza = $iq({ - 'from': muc_jid, - 'id': iq.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'result' - }) - .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', {'category': 'conference', 'name': 'A Dark Cave', 'type': 'text'}).up() - .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() - .c('feature', {'var': 'muc_hidden'}).up() - .c('feature', {'var': 'muc_temporary'}).up() + const features_stanza = + stx` + + + + + + + ` _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); const view = _converse.chatboxviews.get(muc_jid); await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING); - const presence = $pres().attrs({ - from: `${muc_jid}/romeo`, - id: u.getUniqueId(), - to: 'romeo@montague.lit/pda', - type: 'error' - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() - .c('error').attrs({by:'lounge@montague.lit', type:'auth'}) - .c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + const presence = + stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); const el = await u.waitUntil(() => view.querySelector('.chatroom-body converse-muc-disconnected .disconnect-msg:last-child')); @@ -3531,26 +2417,33 @@ describe("Groupchats", function () { iq => iq.querySelector( `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]` )).pop()); - const features_stanza = $iq({ - 'from': 'room@conference.example.org', - 'id': iq.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'error' - }).c('error', {'type': 'cancel'}) - .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); + + const features_stanza = + stx` + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); const view = _converse.chatboxviews.get(muc_jid); await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING)); - const presence = $pres().attrs({ - from: `${muc_jid}/romeo`, - id: u.getUniqueId(), - to:'romeo@montague.lit/pda', - type:'error' - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() - .c('error').attrs({by:'lounge@montague.lit', type:'cancel'}) - .c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + const presence = + stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); const el = await u.waitUntil(() => view.querySelector('.chatroom-body converse-muc-disconnected .disconnect-msg:last-child')); expect(el.textContent.trim()).toBe('You are not allowed to create new groupchats.'); @@ -3566,27 +2459,34 @@ describe("Groupchats", function () { iq => iq.querySelector( `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]` )).pop()); - const features_stanza = $iq({ - 'from': muc_jid, - 'id': iq.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'result' - }).c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', {'category': 'conference', 'name': 'A Dark Cave', 'type': 'text'}).up() - .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() + + const features_stanza = + stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); const view = _converse.chatboxviews.get(muc_jid); await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING)); - const presence = $pres().attrs({ - from: `${muc_jid}/romeo`, - id: u.getUniqueId(), - to: 'romeo@montague.lit/pda', - type:'error' - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() - .c('error').attrs({by:'lounge@montague.lit', type:'cancel'}) - .c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + const presence = + stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); const el = await u.waitUntil(() => view.querySelector('.chatroom-body converse-muc-disconnected .disconnect-msg:last-child')); @@ -3603,28 +2503,34 @@ describe("Groupchats", function () { iq => iq.querySelector( `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]` )).pop()); - const features_stanza = $iq({ - 'from': muc_jid, - 'id': iq.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'result' - }).c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', {'category': 'conference', 'name': 'A Dark Cave', 'type': 'text'}).up() - .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() + + const features_stanza = + stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); const view = _converse.chatboxviews.get(muc_jid); await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING)); - const presence = $pres().attrs({ - from: `${muc_jid}/romeo`, - id: u.getUniqueId(), - to:'romeo@montague.lit/pda', - type:'error' - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() - .c('error').attrs({by:'lounge@montague.lit', type:'cancel'}) - .c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; - + const presence = + stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); const el = await u.waitUntil(() => view.querySelector('.chatroom-body converse-muc-disconnected .disconnect-msg:last-child')); expect(el.textContent.trim()).toBe("This groupchat has reached its maximum number of participants."); @@ -3636,10 +2542,10 @@ describe("Groupchats", function () { it("can be computed in various ways", mock.initConverse([], {}, async function (_converse) { await mock.openChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'romeo'); - var exclude_existing = false; - var remove_absentees = false; - var new_list = []; - var old_list = []; + let exclude_existing = false; + let remove_absentees = false; + let new_list = []; + let old_list = []; const muc_utils = converse.env.muc_utils; let delta = muc_utils.computeAffiliationsDelta(exclude_existing, remove_absentees, new_list, old_list); expect(delta.length).toBe(0); @@ -3693,471 +2599,4 @@ describe("Groupchats", function () { expect(delta.length).toBe(0); })); }); - - - describe("A XEP-0085 Chat Status Notification", function () { - - it("is is not sent out to a MUC if the user is a visitor in a moderated room", - mock.initConverse( - ['chatBoxesFetched'], {}, - async function (_converse) { - - spyOn(_converse.ChatRoom.prototype, 'sendChatState').and.callThrough(); - - const muc_jid = 'lounge@montague.lit'; - const features = [ - 'http://jabber.org/protocol/muc', - 'jabber:iq:register', - 'muc_passwordprotected', - 'muc_hidden', - 'muc_temporary', - 'muc_membersonly', - 'muc_moderated', - 'muc_anonymous' - ] - await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', features); - - const view = _converse.chatboxviews.get(muc_jid); - view.model.setChatState(_converse.ACTIVE); - - expect(view.model.sendChatState).toHaveBeenCalled(); - const last_stanza = _converse.api.connection.get().sent_stanzas.pop(); - expect(Strophe.serialize(last_stanza)).toBe( - ``+ - ``+ - ``+ - ``+ - ``); - - // Romeo loses his voice - const presence = $pres({ - to: 'romeo@montague.lit/orchard', - from: `${muc_jid}/romeo` - }).c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', {'affiliation': 'none', 'role': 'visitor'}).up() - .c('status', {code: '110'}); - _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); - - const occupant = view.model.occupants.findWhere({'jid': _converse.bare_jid}); - await u.waitUntil(() => occupant.get('role') === 'visitor'); - - spyOn(_converse.api.connection.get(), 'send'); - view.model.setChatState(_converse.INACTIVE); - expect(view.model.sendChatState.calls.count()).toBe(2); - expect(_converse.api.connection.get().send).not.toHaveBeenCalled(); - })); - - - describe("A composing notification", function () { - - it("will be shown if received", mock.initConverse([], {}, async function (_converse) { - const muc_jid = 'coven@chat.shakespeare.lit'; - const members = [ - {'affiliation': 'member', 'nick': 'majortom', 'jid': 'majortom@example.org'}, - {'affiliation': 'admin', 'nick': 'groundcontrol', 'jid': 'groundcontrol@example.org'} - ]; - await mock.openAndEnterChatRoom(_converse, muc_jid, 'some1', [], members); - const view = _converse.chatboxviews.get(muc_jid); - - let csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); - expect(csntext.trim()).toEqual("some1 has entered the groupchat"); - - let 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"); - - 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, newguy and nomorenicks have entered the groupchat", 1000); - - // Manually clear so that we can more easily test - view.model.notifications.set('entered', []); - await u.waitUntil(() => !view.querySelector('.chat-content__notifications').textContent, 1000); - - // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions - - const remove_notifications_timeouts = []; - const setTimeout = window.setTimeout; - spyOn(window, 'setTimeout').and.callFake((f, w) => { - if (f.toString() === "() => this.removeNotification(actor, state)") { - remove_notifications_timeouts.push(f) - } - setTimeout(f, w); - }); - - // state - let msg = $msg({ - from: muc_jid+'/newguy', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); - _converse.api.connection.get()._dataRecv(mock.createRequest(msg)); - - csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent, 1000); - expect(csntext.trim()).toEqual('newguy is typing'); - expect(remove_notifications_timeouts.length).toBe(1); - expect(view.querySelector('.chat-content__notifications').textContent.trim()).toEqual('newguy is typing'); - - // state for a different occupant - msg = $msg({ - from: muc_jid+'/nomorenicks', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); - await view.model.handleMessageStanza(msg); - await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === 'newguy and nomorenicks are typing', 1000); - - // state for a different occupant - msg = $msg({ - from: muc_jid+'/majortom', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); - await view.model.handleMessageStanza(msg); - await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === 'newguy, nomorenicks and majortom are typing', 1000); - - // state for a different occupant - msg = $msg({ - from: muc_jid+'/groundcontrol', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); - await view.model.handleMessageStanza(msg); - await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === 'newguy, nomorenicks and others are typing', 1000); - - msg = $msg({ - from: `${muc_jid}/some1`, - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t('hello world').tree(); - await view.model.handleMessageStanza(msg); - - await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1); - expect(view.querySelector('.chat-msg .chat-msg__text').textContent.trim()).toBe('hello world'); - - // Test that the composing notifications get removed via timeout. - if (remove_notifications_timeouts.length) { - remove_notifications_timeouts[0](); - } - await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === 'nomorenicks, majortom and groundcontrol are typing', 1000); - })); - }); - - describe("A paused notification", function () { - - it("will be shown if received", mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) { - const muc_jid = 'coven@chat.shakespeare.lit'; - await mock.openAndEnterChatRoom(_converse, muc_jid, 'some1'); - const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); - - /* - * - * - * - * - * - */ - let 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 csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); - expect(csntext.trim()).toEqual("some1 has entered the groupchat"); - - 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"); - - 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, newguy and nomorenicks have entered the groupchat"); - - // Manually clear so that we can more easily test - view.model.notifications.set('entered', []); - await u.waitUntil(() => !view.querySelector('.chat-content__notifications').textContent); - - // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions - - // state - let msg = $msg({ - from: muc_jid+'/newguy', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); - await view.model.handleMessageStanza(msg); - await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); - expect(view.querySelector('.chat-content__notifications').textContent.trim()).toBe('newguy is typing'); - - // state for a different occupant - msg = $msg({ - from: muc_jid+'/nomorenicks', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); - await view.model.handleMessageStanza(msg); - - await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() == 'newguy and nomorenicks are typing'); - - // state from occupant who typed first - msg = $msg({ - from: muc_jid+'/newguy', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree(); - await view.model.handleMessageStanza(msg); - await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() == 'nomorenicks is typing\nnewguy has stopped typing'); - })); - }); - }); - - describe("A muted user", function () { - - it("will receive a user-friendly error message when trying to send a message", - mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) { - - const muc_jid = 'trollbox@montague.lit'; - await mock.openAndEnterChatRoom(_converse, muc_jid, 'troll'); - const view = _converse.chatboxviews.get(muc_jid); - const textarea = await u.waitUntil(() => view.querySelector('textarea.chat-textarea')); - textarea.value = 'Hello world'; - const message_form = view.querySelector('converse-muc-message-form'); - message_form.onFormSubmitted(new Event('submit')); - await new Promise(resolve => view.model.messages.once('rendered', resolve)); - - let stanza = u.toStanza(` - - - `); - _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); - await u.waitUntil(() => view.querySelector('.chat-msg__error')?.textContent.trim(), 1000); - expect(view.querySelector('.chat-msg__error').textContent.trim()).toBe( - "Your message was not delivered because you weren't allowed to send it."); - - textarea.value = 'Hello again'; - message_form.onFormSubmitted(new Event('submit')); - await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2); - - stanza = u.toStanza(` - - - - Thou shalt not! - - `); - _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); - - await u.waitUntil(() => view.querySelectorAll('.chat-msg__error').length === 2); - const sel = 'converse-message-history converse-chat-message:last-child .chat-msg__error'; - await u.waitUntil(() => view.querySelector(sel)?.textContent.trim()); - expect(view.querySelector(sel).textContent.trim()).toBe('Thou shalt not!') - })); - - it("will see an explanatory message instead of a textarea", - mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) { - - const features = [ - 'http://jabber.org/protocol/muc', - 'jabber:iq:register', - Strophe.NS.SID, - 'muc_moderated', - ] - const muc_jid = 'trollbox@montague.lit'; - await mock.openAndEnterChatRoom(_converse, muc_jid, 'troll', features); - const view = _converse.chatboxviews.get(muc_jid); - await u.waitUntil(() => view.querySelector('.chat-textarea')); - - let stanza = u.toStanza(` - - - - - - `); - _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); - - await u.waitUntil(() => view.querySelector('.chat-textarea') === null); - let bottom_panel = view.querySelector('.muc-bottom-panel'); - expect(bottom_panel.textContent.trim()).toBe("You're not allowed to send messages in this room"); - - // This only applies to moderated rooms, so let's check that - // the textarea becomes visible when the room's - // configuration changes to be non-moderated - view.model.features.set('moderated', false); - await u.waitUntil(() => view.querySelector('.muc-bottom-panel') === null); - const textarea = await u.waitUntil(() => view.querySelector('textarea.chat-textarea')); - expect(textarea === null).toBe(false); - - view.model.features.set('moderated', true); - await u.waitUntil(() => view.querySelector('.chat-textarea') === null); - bottom_panel = view.querySelector('.muc-bottom-panel'); - expect(bottom_panel.textContent.trim()).toBe("You're not allowed to send messages in this room"); - - // Check now that things get restored when the user is given a voice - await u.waitUntil(() => - Array.from(view.querySelectorAll('.chat-info__message')).pop()?.textContent.trim() === - "troll is no longer an owner of this groupchat" - ); - - stanza = u.toStanza(` - - - - - - `); - _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); - await u.waitUntil(() => view.querySelector('.muc-bottom-panel') === null); - expect(textarea === null).toBe(false); - // Check now that things get restored when the user is given a voice - await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "troll has been given a voice"); - })); - }); - - describe("when muc_send_probes is true", function () { - - it("sends presence probes when muc_send_probes is true", - mock.initConverse([], {'muc_send_probes': true}, async function (_converse) { - - const muc_jid = 'lounge@montague.lit'; - await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); - - let stanza = u.toStanza(` - - This message will trigger a presence probe - `); - _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); - const view = _converse.chatboxviews.get(muc_jid); - - await u.waitUntil(() => view.model.messages.length); - let occupant = view.model.messages.at(0)?.occupant; - expect(occupant).toBeDefined(); - expect(occupant.get('nick')).toBe('ralphm'); - expect(occupant.get('affiliation')).toBeUndefined(); - expect(occupant.get('role')).toBeUndefined(); - - const sent_stanzas = _converse.api.connection.get().sent_stanzas; - let probe = await u.waitUntil(() => sent_stanzas.filter(s => s.matches('presence[type="probe"]')).pop()); - expect(Strophe.serialize(probe)).toBe( - ``+ - `0`+ - ``+ - ``); - - let presence = u.toStanza( - ` - - - - `); - _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); - - expect(occupant.get('affiliation')).toBe('member'); - expect(occupant.get('role')).toBe('participant'); - - // Check that unavailable but affiliated occupants don't get destroyed - stanza = u.toStanza(` - - This message from an unavailable user will trigger a presence probe - `); - _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); - - await u.waitUntil(() => view.model.messages.length === 2); - occupant = view.model.messages.at(1)?.occupant; - expect(occupant).toBeDefined(); - expect(occupant.get('nick')).toBe('gonePhising'); - expect(occupant.get('affiliation')).toBeUndefined(); - expect(occupant.get('role')).toBeUndefined(); - - probe = await u.waitUntil(() => sent_stanzas.filter(s => s.matches(`presence[to="${muc_jid}/gonePhising"]`)).pop()); - expect(Strophe.serialize(probe)).toBe( - ``+ - `0`+ - ``+ - ``); - - presence = u.toStanza( - ` - - - - `); - _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); - - expect(view.model.occupants.length).toBe(3); - expect(occupant.get('affiliation')).toBe('member'); - expect(occupant.get('role')).toBe('participant'); - })); - }); }); diff --git a/src/plugins/muc-views/tests/mute.js b/src/plugins/muc-views/tests/mute.js new file mode 100644 index 0000000000..148f426134 --- /dev/null +++ b/src/plugins/muc-views/tests/mute.js @@ -0,0 +1,124 @@ +/*global mock, converse */ + +const { Strophe, Promise, stx, u } = converse.env; + +describe("Groupchats", function () { + describe("A muted user", function () { + + it("will receive a user-friendly error message when trying to send a message", + mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) { + + const muc_jid = 'trollbox@montague.lit'; + await mock.openAndEnterChatRoom(_converse, muc_jid, 'troll'); + const view = _converse.chatboxviews.get(muc_jid); + const textarea = await u.waitUntil(() => view.querySelector('textarea.chat-textarea')); + textarea.value = 'Hello world'; + const message_form = view.querySelector('converse-muc-message-form'); + message_form.onFormSubmitted(new Event('submit')); + await new Promise(resolve => view.model.messages.once('rendered', resolve)); + + let stanza = + stx` + + `; + _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); + await u.waitUntil(() => view.querySelector('.chat-msg__error')?.textContent.trim(), 1000); + expect(view.querySelector('.chat-msg__error').textContent.trim()).toBe( + "Your message was not delivered because you weren't allowed to send it."); + + textarea.value = 'Hello again'; + message_form.onFormSubmitted(new Event('submit')); + await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 2); + + stanza = stx` + + + Thou shalt not! + + `; + _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); + + await u.waitUntil(() => view.querySelectorAll('.chat-msg__error').length === 2); + const sel = 'converse-message-history converse-chat-message:last-child .chat-msg__error'; + await u.waitUntil(() => view.querySelector(sel)?.textContent.trim()); + expect(view.querySelector(sel).textContent.trim()).toBe('Thou shalt not!') + })); + + it("will see an explanatory message instead of a textarea", + mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) { + + const features = [ + 'http://jabber.org/protocol/muc', + 'jabber:iq:register', + Strophe.NS.SID, + 'muc_moderated', + ] + const muc_jid = 'trollbox@montague.lit'; + await mock.openAndEnterChatRoom(_converse, muc_jid, 'troll', features); + const view = _converse.chatboxviews.get(muc_jid); + await u.waitUntil(() => view.querySelector('.chat-textarea')); + + let stanza = + stx` + + + + + `; + _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); + + await u.waitUntil(() => view.querySelector('.chat-textarea') === null); + let bottom_panel = view.querySelector('.muc-bottom-panel'); + expect(bottom_panel.textContent.trim()).toBe("You're not allowed to send messages in this room"); + + // This only applies to moderated rooms, so let's check that + // the textarea becomes visible when the room's + // configuration changes to be non-moderated + view.model.features.set('moderated', false); + await u.waitUntil(() => view.querySelector('.muc-bottom-panel') === null); + const textarea = await u.waitUntil(() => view.querySelector('textarea.chat-textarea')); + expect(textarea === null).toBe(false); + + view.model.features.set('moderated', true); + await u.waitUntil(() => view.querySelector('.chat-textarea') === null); + bottom_panel = view.querySelector('.muc-bottom-panel'); + expect(bottom_panel.textContent.trim()).toBe("You're not allowed to send messages in this room"); + + // Check now that things get restored when the user is given a voice + await u.waitUntil(() => + Array.from(view.querySelectorAll('.chat-info__message')).pop()?.textContent.trim() === + "troll is no longer an owner of this groupchat" + ); + + stanza = stx` + + + + + `; + _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); + await u.waitUntil(() => view.querySelector('.muc-bottom-panel') === null); + expect(textarea === null).toBe(false); + // Check now that things get restored when the user is given a voice + await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent.trim() === "troll has been given a voice"); + })); + }); +}); diff --git a/src/plugins/muc-views/tests/nickname.js b/src/plugins/muc-views/tests/nickname.js index 77986b9fc6..7117c883ef 100644 --- a/src/plugins/muc-views/tests/nickname.js +++ b/src/plugins/muc-views/tests/nickname.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { $pres, $iq, Strophe, sizzle, u, stx } = converse.env; +const { Strophe, sizzle, u, stx } = converse.env; describe("A MUC", function () { @@ -128,21 +128,22 @@ describe("A MUC", function () { const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent); expect(csntext.trim()).toEqual("oldnick has entered the groupchat"); - let presence = $pres().attrs({ - from:'lounge@montague.lit/oldnick', - id:'DC352437-C019-40EC-B590-AF29E879AF98', - to:'romeo@montague.lit/pda', - type:'unavailable' - }) - .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({ - affiliation: 'owner', - jid: 'romeo@montague.lit/pda', - nick: 'newnick', - role: 'moderator' - }).up() - .c('status').attrs({code:'303'}).up() - .c('status').attrs({code:'110'}).nodeTree; + let presence = stx` + + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); await u.waitUntil(() => view.querySelectorAll('.chat-info').length); @@ -155,18 +156,19 @@ describe("A MUC", function () { occupants = view.querySelector('.occupant-list'); expect(occupants.querySelectorAll('.occupant-nick').length).toBe(1); - presence = $pres().attrs({ - from:'lounge@montague.lit/newnick', - id:'5B4F27A4-25ED-43F7-A699-382C6B4AFC67', - to:'romeo@montague.lit/pda' - }) - .c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({ - affiliation: 'owner', - jid: 'romeo@montague.lit/pda', - role: 'moderator' - }).up() - .c('status').attrs({code:'110'}).nodeTree; + presence = stx` + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); expect(view.model.session.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED); @@ -195,13 +197,16 @@ describe("A MUC", function () { )).pop() ); // We pretend this is a new room, so no disco info is returned. - const features_stanza = $iq({ - from: 'lounge@montague.lit', - 'id': stanza.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'error' - }).c('error', {'type': 'cancel'}) - .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"}); + const features_stanza = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); @@ -222,45 +227,37 @@ describe("A MUC", function () { `type="get" xmlns="jabber:client">`+ ``); - /* - * - * - * - * - */ const view = _converse.chatboxviews.get('lounge@montague.lit'); - stanza = $iq({ - 'type': 'result', - 'id': iq.getAttribute('id'), - 'from': view.model.get('jid'), - 'to': _converse.api.connection.get().jid - }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'}) - .c('identity', {'category': 'conference', 'name': 'thirdwitch', 'type': 'text'}); + stanza = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); // The user has just entered the groupchat (because join was called) // and receives their own presence from the server. // See example 24: // https://xmpp.org/extensions/xep-0045.html#enter-pres - const presence = $pres({ - to:'romeo@montague.lit/orchard', - from:'lounge@montague.lit/thirdwitch', - id:'DC352437-C019-40EC-B590-AF29E879AF97' - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({ - affiliation: 'member', - jid: 'romeo@montague.lit/orchard', - role: 'participant' - }).up() - .c('status').attrs({code:'110'}).up() - .c('status').attrs({code:'210'}).nodeTree; + const presence = stx` + + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); @@ -290,30 +287,36 @@ describe("A MUC", function () { `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]` )).pop()); - const features_stanza = $iq({ - 'from': muc_jid, - 'id': iq.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'result' - }) - .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', {'category': 'conference', 'name': 'A Dark Cave', 'type': 'text'}).up() - .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() - .c('feature', {'var': 'muc_hidden'}).up() - .c('feature', {'var': 'muc_temporary'}).up() + const features_stanza = stx` + + + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); const view = _converse.chatboxviews.get(muc_jid); await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING); - const presence = $pres().attrs({ - from: `${muc_jid}/romeo`, - id: u.getUniqueId(), - to: 'romeo@montague.lit/pda', - type: 'error' - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() - .c('error').attrs({by: muc_jid, type:'cancel'}) - .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + const presence = stx` + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); const el = await u.waitUntil(() => view.querySelector('.muc-nickname-form .validation-message')); @@ -340,16 +343,18 @@ describe("A MUC", function () { */ api.settings.set('muc_nickname_from_jid', true); - const attrs = { - 'from': `${muc_jid}/romeo`, - 'id': u.getUniqueId(), - 'to': 'romeo@montague.lit/pda', - 'type': 'error' - }; - let presence = $pres().attrs(attrs) - .c('x').attrs({'xmlns':'http://jabber.org/protocol/muc'}).up() - .c('error').attrs({'by': muc_jid, 'type':'cancel'}) - .c('conflict').attrs({'xmlns':'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + let presence = stx` + + + + + + `; const view = _converse.chatboxviews.get(muc_jid); spyOn(view.model, 'join').and.callThrough(); @@ -359,22 +364,34 @@ describe("A MUC", function () { _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); expect(view.model.join).toHaveBeenCalledWith('romeo-2'); - attrs.from = `${muc_jid}/romeo-2`; - attrs.id = u.getUniqueId(); - presence = $pres().attrs(attrs) - .c('x').attrs({'xmlns':'http://jabber.org/protocol/muc'}).up() - .c('error').attrs({'by': muc_jid, type:'cancel'}) - .c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + presence = stx` + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); expect(view.model.join).toHaveBeenCalledWith('romeo-3'); - attrs.from = `${muc_jid}/romeo-3`; - attrs.id = new Date().getTime(); - presence = $pres().attrs(attrs) - .c('x').attrs({'xmlns': 'http://jabber.org/protocol/muc'}).up() - .c('error').attrs({'by': muc_jid, 'type': 'cancel'}) - .c('conflict').attrs({'xmlns':'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + presence = stx` + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); expect(view.model.join).toHaveBeenCalledWith('romeo-4'); })); @@ -389,27 +406,34 @@ describe("A MUC", function () { iq => iq.querySelector( `iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]` )).pop()); - const features_stanza = $iq({ - 'from': muc_jid, - 'id': iq.getAttribute('id'), - 'to': 'romeo@montague.lit/desktop', - 'type': 'result' - }).c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'}) - .c('identity', {'category': 'conference', 'name': 'A Dark Cave', 'type': 'text'}).up() - .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up() + const features_stanza = stx` + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza)); const view = _converse.chatboxviews.get(muc_jid); await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING)); - const presence = $pres().attrs({ - from: `${muc_jid}/romeo`, - id: u.getUniqueId(), - to:'romeo@montague.lit/pda', - type:'error' - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up() - .c('error').attrs({by:'lounge@montague.lit', type:'cancel'}) - .c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree; + const presence = stx` + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); const el = await u.waitUntil(() => view.querySelector('.chatroom-body converse-muc-disconnected .disconnect-msg:last-child')); diff --git a/src/plugins/muc-views/tests/occupants-filter.js b/src/plugins/muc-views/tests/occupants-filter.js index f75e2a655e..c9b0a7d563 100644 --- a/src/plugins/muc-views/tests/occupants-filter.js +++ b/src/plugins/muc-views/tests/occupants-filter.js @@ -1,6 +1,6 @@ /* global mock, converse */ -const { $pres, u } = converse.env; +const { u, stx } = converse.env; describe("The MUC occupants filter", function () { @@ -30,15 +30,16 @@ describe("The MUC occupants filter", function () { const name = mock.chatroom_names[i]; const role = mock.chatroom_roles[name].role; // See example 21 https://xmpp.org/extensions/xep-0045.html#enter-pres - const presence = $pres({ - to:'romeo@montague.lit/pda', - from:'lounge@montague.lit/'+name - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({ - affiliation: mock.chatroom_roles[name].affiliation, - jid: name.replace(/ /g,'.').toLowerCase() + '@montague.lit', - role: role - }); + const presence = stx` + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); } diff --git a/src/plugins/muc-views/tests/probes.js b/src/plugins/muc-views/tests/probes.js new file mode 100644 index 0000000000..9739d619d8 --- /dev/null +++ b/src/plugins/muc-views/tests/probes.js @@ -0,0 +1,77 @@ +/*global mock, converse */ + +const { Strophe, stx, u } = converse.env; + +describe("Groupchats", function () { + describe("when muc_send_probes is true", function () { + + it("sends presence probes when muc_send_probes is true", + mock.initConverse([], {'muc_send_probes': true}, async function (_converse) { + + const muc_jid = 'lounge@montague.lit'; + await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); + + let stanza = stx` + This message will trigger a presence probe + `; + _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); + const view = _converse.chatboxviews.get(muc_jid); + + await u.waitUntil(() => view.model.messages.length); + let occupant = view.model.messages.at(0)?.occupant; + expect(occupant).toBeDefined(); + expect(occupant.get('nick')).toBe('ralphm'); + expect(occupant.get('affiliation')).toBeUndefined(); + expect(occupant.get('role')).toBeUndefined(); + + const sent_stanzas = _converse.api.connection.get().sent_stanzas; + let probe = await u.waitUntil(() => sent_stanzas.filter(s => s.matches('presence[type="probe"]')).pop()); + expect(Strophe.serialize(probe)).toBe( + ``+ + `0`+ + ``+ + ``); + + let presence = stx` + + + + `; + _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); + + expect(occupant.get('affiliation')).toBe('member'); + expect(occupant.get('role')).toBe('participant'); + + // Check that unavailable but affiliated occupants don't get destroyed + stanza = stx` + This message from an unavailable user will trigger a presence probe + `; + _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); + + await u.waitUntil(() => view.model.messages.length === 2); + occupant = view.model.messages.at(1)?.occupant; + expect(occupant).toBeDefined(); + expect(occupant.get('nick')).toBe('gonePhising'); + expect(occupant.get('affiliation')).toBeUndefined(); + expect(occupant.get('role')).toBeUndefined(); + + probe = await u.waitUntil(() => sent_stanzas.filter(s => s.matches(`presence[to="${muc_jid}/gonePhising"]`)).pop()); + expect(Strophe.serialize(probe)).toBe( + ``+ + `0`+ + ``+ + ``); + + presence = stx` + + + + `; + _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); + + expect(view.model.occupants.length).toBe(3); + expect(occupant.get('affiliation')).toBe('member'); + expect(occupant.get('role')).toBe('participant'); + })); + }); +}); diff --git a/src/plugins/muc-views/tests/rai.js b/src/plugins/muc-views/tests/rai.js index afe9821ae3..d877d4510e 100644 --- a/src/plugins/muc-views/tests/rai.js +++ b/src/plugins/muc-views/tests/rai.js @@ -1,9 +1,8 @@ /*global mock, converse */ -const { Strophe } = converse.env; -const u = converse.env.utils; -// See: https://xmpp.org/rfcs/rfc3921.html +const { Strophe, u, stx } = converse.env; +// See: https://xmpp.org/rfcs/rfc3921.html describe("XEP-0437 Room Activity Indicators", function () { @@ -26,8 +25,8 @@ describe("XEP-0437 Room Activity Indicators", function () { const iq_get = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq query[xmlns="${Strophe.NS.MAM}"]`)).pop()); const first_msg_id = _converse.api.connection.get().getUniqueId(); const last_msg_id = _converse.api.connection.get().getUniqueId(); - let message = u.toStanza( - ` @@ -38,11 +37,10 @@ describe("XEP-0437 Room Activity Indicators", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); - message = u.toStanza( - ` @@ -53,19 +51,21 @@ describe("XEP-0437 Room Activity Indicators", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message)); - const result = u.toStanza( - ` - - - ${first_msg_id} + const result = + stx` + + + ${first_msg_id} ${last_msg_id} 2 - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(result)); await u.waitUntil(() => view.model.messages.length === 2); @@ -99,13 +99,14 @@ describe("XEP-0437 Room Activity Indicators", function () { const room_el = await u.waitUntil(() => document.querySelector("converse-rooms-list .available-chatroom")); expect(Array.from(room_el.classList).includes('unread-msgs')).toBeFalsy(); - const activity_stanza = u.toStanza(` - + const activity_stanza = stx` + ${muc_jid} - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(activity_stanza)); await u.waitUntil(() => view.model.get('has_activity')); @@ -161,13 +162,14 @@ describe("XEP-0437 Room Activity Indicators", function () { const room_el = await u.waitUntil(() => document.querySelector("converse-rooms-list .available-chatroom")); expect(Array.from(room_el.classList).includes('unread-msgs')).toBeFalsy(); - const activity_stanza = u.toStanza(` - + const activity_stanza = stx` + ${muc_jid} - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(activity_stanza)); await u.waitUntil(() => model.get('has_activity')); @@ -208,11 +210,13 @@ describe("XEP-0437 Room Activity Indicators", function () { `` ); // If an error presence with "resource-constraint" is returned, we rejoin - const activity_stanza = u.toStanza(` - + const activity_stanza = stx` + - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(activity_stanza)); await u.waitUntil(() => model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING); diff --git a/src/plugins/muc-views/tests/retractions.js b/src/plugins/muc-views/tests/retractions.js index bacfff5d9b..35c9c687e8 100644 --- a/src/plugins/muc-views/tests/retractions.js +++ b/src/plugins/muc-views/tests/retractions.js @@ -1,14 +1,12 @@ /*global mock, converse */ -const { Strophe, $iq } = converse.env; -const u = converse.env.utils; - +const { Strophe, u, stx } = converse.env; async function sendAndThenRetractMessage (_converse, view) { view.model.sendMessage({'body': 'hello world'}); await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 1); const msg_obj = view.model.messages.last(); - const reflection_stanza = u.toStanza(` + const reflection_stanza = stx` - `); + `; await view.model.handleMessageStanza(reflection_stanza); await u.waitUntil(() => view.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500); @@ -33,7 +31,6 @@ async function sendAndThenRetractMessage (_converse, view) { describe("Message Retractions", function () { - describe("A groupchat message retraction", function () { it("is not applied if it's not from the right author", @@ -43,25 +40,33 @@ describe("Message Retractions", function () { const features = [...mock.default_muc_features, Strophe.NS.MODERATE]; await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', features); - const received_stanza = u.toStanza(` - + const received_stanza = stx` + Hello world - + - `); + `; const view = _converse.chatboxviews.get(muc_jid); await view.model.handleMessageStanza(received_stanza); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1); expect(view.model.messages.at(0).get('retracted')).toBeFalsy(); expect(view.model.messages.at(0).get('is_ephemeral')).toBeFalsy(); - const retraction_stanza = u.toStanza(` - + const retraction_stanza = stx` + - `); + `; spyOn(view.model, 'handleRetraction').and.callThrough(); _converse.api.connection.get()._dataRecv(mock.createRequest(retraction_stanza)); @@ -85,13 +90,17 @@ describe("Message Retractions", function () { const features = [...mock.default_muc_features, Strophe.NS.MODERATE]; await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', features); - const retraction_stanza = u.toStanza(` - + const retraction_stanza = stx` + - `); + `; const view = _converse.chatboxviews.get(muc_jid); spyOn(converse.env.log, 'warn'); spyOn(view.model, 'handleRetraction').and.callThrough(); @@ -104,14 +113,18 @@ describe("Message Retractions", function () { expect(view.model.messages.at(0).get('retracted')).toBeTruthy(); expect(view.model.messages.at(0).get('dangling_retraction')).toBe(true); - const received_stanza = u.toStanza(` - + const received_stanza = stx` + Hello world - - + + - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(received_stanza)); await u.waitUntil(() => view.model.handleRetraction.calls.count() === 2); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1, 1000); @@ -137,7 +150,7 @@ describe("Message Retractions", function () { const muc_jid = 'lounge@montague.lit'; const features = [...mock.default_muc_features, Strophe.NS.MODERATE]; await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', features); - const retraction_stanza = u.toStanza(` + const retraction_stanza = stx` @@ -146,7 +159,7 @@ describe("Message Retractions", function () { - `); + `; const view = _converse.chatboxviews.get(muc_jid); spyOn(converse.env.log, 'warn'); spyOn(view.model, 'handleModeration').and.callThrough(); @@ -159,14 +172,16 @@ describe("Message Retractions", function () { expect(view.model.messages.at(0).get('moderated')).toBe('retracted'); expect(view.model.messages.at(0).get('dangling_moderation')).toBe(true); - const received_stanza = u.toStanza(` - + const received_stanza = stx` + Hello world - - - - - `); + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(received_stanza)); await u.waitUntil(() => view.model.handleModeration.calls.count() === 2); @@ -198,7 +213,7 @@ describe("Message Retractions", function () { const view = await mock.openChatBoxFor(_converse, contact_jid); spyOn(view.model, 'handleRetraction').and.callThrough(); - const retraction_stanza = u.toStanza(` + const retraction_stanza = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(retraction_stanza)); await u.waitUntil(() => view.model.messages.length === 1); @@ -218,7 +233,7 @@ describe("Message Retractions", function () { expect(message.get('retracted')).toBeTruthy(); expect(view.querySelectorAll('.chat-msg').length).toBe(0); - const stanza = u.toStanza(` + const stanza = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => view.model.handleRetraction.calls.count() === 2); expect(view.model.messages.length).toBe(1); @@ -249,7 +264,7 @@ describe("Message Retractions", function () { const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const view = await mock.openChatBoxFor(_converse, contact_jid); - let stanza = u.toStanza(` + let stanza = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => view.model.messages.length === 1); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1); - stanza = u.toStanza(` + stanza = stx` - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(stanza)); await u.waitUntil(() => view.model.messages.length === 2); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2); - const retraction_stanza = u.toStanza(` + const retraction_stanza = stx` - - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(retraction_stanza)); await u.waitUntil(() => view.querySelectorAll('.chat-msg--retracted').length === 1); @@ -358,26 +372,32 @@ describe("Message Retractions", function () { const features = [...mock.default_muc_features, Strophe.NS.MODERATE]; await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', features); - const received_stanza = u.toStanza(` - + const received_stanza = stx` + Hello world - - - - `); + + + `; const view = _converse.chatboxviews.get(muc_jid); await view.model.handleMessageStanza(received_stanza); await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1); expect(view.model.messages.at(0).get('retracted')).toBeFalsy(); expect(view.model.messages.at(0).get('is_ephemeral')).toBeFalsy(); - const retraction_stanza = u.toStanza(` - + const retraction_stanza = stx` + - - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(retraction_stanza)); // We opportunistically save the message as retracted, even before receiving the retraction message @@ -403,12 +423,15 @@ describe("Message Retractions", function () { const occupant = view.model.getOwnOccupant(); expect(occupant.get('role')).toBe('moderator'); - const received_stanza = u.toStanza(` - + const received_stanza = stx` + Visit this site to get free Bitcoin! - - - `); + + `; await view.model.handleMessageStanza(received_stanza); await u.waitUntil(() => view.model.messages.length === 1); expect(view.model.messages.at(0).get('retracted')).toBeFalsy(); @@ -439,7 +462,12 @@ describe("Message Retractions", function () { ``+ ``); - const result_iq = $iq({'from': muc_jid, 'id': stanza.getAttribute('id'), 'to': _converse.bare_jid, 'type': 'result'}); + const result_iq = stx` + `; _converse.api.connection.get()._dataRecv(mock.createRequest(result_iq)); // We opportunistically save the message as retracted, even before receiving the retraction message @@ -458,15 +486,19 @@ describe("Message Retractions", function () { expect(qel.textContent.trim()).toBe('This content is inappropriate for this forum!'); // The server responds with a retraction message - const retraction = u.toStanza(` - + const retraction = stx` + - - + + ${reason} - `); + `; await view.model.handleMessageStanza(retraction); expect(view.model.messages.length).toBe(1); expect(view.model.messages.at(0).get('moderated')).toBe('retracted'); @@ -484,12 +516,15 @@ describe("Message Retractions", function () { const occupant = view.model.getOwnOccupant(); expect(occupant.get('role')).toBe('moderator'); - const received_stanza = u.toStanza(` - + const received_stanza = stx` + Visit this site to get free Bitcoin! - - - `); + + `; await view.model.handleMessageStanza(received_stanza); await u.waitUntil(() => view.querySelector('.chat-msg__content')); expect(view.querySelector('.chat-msg__content .chat-msg__action-retract')).toBe(null); @@ -508,12 +543,15 @@ describe("Message Retractions", function () { const occupant = view.model.getOwnOccupant(); expect(occupant.get('role')).toBe('moderator'); - const received_stanza = u.toStanza(` - + const received_stanza = stx` + Visit this site to get free Bitcoin! - - - `); + + `; await view.model.handleMessageStanza(received_stanza); await u.waitUntil(() => view.model.messages.length === 1); expect(view.model.messages.length).toBe(1); @@ -533,15 +571,19 @@ describe("Message Retractions", function () { const message = view.model.messages.at(0); const stanza_id = message.get(`stanza_id ${view.model.get('jid')}`); // The server responds with a retraction message - const retraction = u.toStanza(` - + const retraction = stx` + ${reason} - `); + `; await view.model.handleMessageStanza(retraction); await u.waitUntil(() => view.querySelectorAll('.chat-msg--retracted').length === 1); @@ -553,7 +595,12 @@ describe("Message Retractions", function () { const qel = view.querySelector('.chat-msg--retracted .chat-msg__message q'); expect(qel.textContent).toBe('This content is inappropriate for this forum!'); - const result_iq = $iq({'from': muc_jid, 'id': stanza.getAttribute('id'), 'to': _converse.bare_jid, 'type': 'result'}); + const result_iq = stx` + `; _converse.api.connection.get()._dataRecv(mock.createRequest(result_iq)); expect(view.model.messages.length).toBe(1); expect(view.model.messages.at(0).get('moderated')).toBe('retracted'); @@ -594,12 +641,16 @@ describe("Message Retractions", function () { const stanza_id = message.get(`stanza_id ${muc_jid}`); // The server responds with a retraction message - const reflection = u.toStanza(` - + const reflection = stx` + - `); + `; spyOn(view.model, 'handleRetraction').and.callThrough(); _converse.api.connection.get()._dataRecv(mock.createRequest(reflection)); @@ -637,15 +688,19 @@ describe("Message Retractions", function () { const message = view.model.messages.last(); const stanza_id = message.get(`stanza_id ${view.model.get('jid')}`); // The server responds with an error message - const error = u.toStanza(` - + const error = stx` + - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(error)); @@ -704,7 +759,7 @@ describe("Message Retractions", function () { await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1); const stanza_id = 'retraction-id-1'; const msg_obj = view.model.messages.at(0); - const reflection_stanza = u.toStanza(` + const reflection_stanza = stx` - `); + `; await view.model.handleMessageStanza(reflection_stanza); await u.waitUntil(() => view.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500); expect(view.model.messages.length).toBe(1); @@ -722,15 +777,19 @@ describe("Message Retractions", function () { // The server responds with a retraction message const reason = "This content is inappropriate for this forum!" - const retraction = u.toStanza(` - + const retraction = stx` + - - + + ${reason} - `); + `; await view.model.handleMessageStanza(retraction); expect(view.model.messages.length).toBe(1); await u.waitUntil(() => view.model.messages.at(0).get('moderated') === 'retracted'); @@ -759,7 +818,7 @@ describe("Message Retractions", function () { const stanza_id = 'retraction-id-1'; const msg_obj = view.model.messages.at(0); - const reflection_stanza = u.toStanza(` + const reflection_stanza = stx` - `); + `; await view.model.handleMessageStanza(reflection_stanza); await u.waitUntil(() => view.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500); @@ -795,7 +854,12 @@ describe("Message Retractions", function () { ``+ ``); - const result_iq = $iq({'from': muc_jid, 'id': stanza.getAttribute('id'), 'to': _converse.bare_jid, 'type': 'result'}); + const result_iq = stx` + `; _converse.api.connection.get()._dataRecv(mock.createRequest(result_iq)); // We opportunistically save the message as retracted, even before receiving the retraction message @@ -812,14 +876,18 @@ describe("Message Retractions", function () { expect(msg_el.querySelector('q')).toBe(null); // The server responds with a retraction message - const retraction = u.toStanza(` - + const retraction = stx` + - - + + - `); + `; await view.model.handleMessageStanza(retraction); expect(view.model.messages.length).toBe(1); expect(view.model.messages.at(0).get('moderated')).toBe('retracted'); @@ -848,59 +916,60 @@ describe("Message Retractions", function () { const first_id = u.getUniqueId(); spyOn(view.model, 'handleRetraction').and.callThrough(); - const first_message = u.toStanza(` - - - - + const first_message = stx` + + + + - + 😊 - - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(first_message)); - const tombstone = u.toStanza(` - - - - + const tombstone = stx` + + + + - - + + - - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(tombstone)); const last_id = u.getUniqueId(); - const retraction = u.toStanza(` - - - - - + const retraction = stx` + + + + + - + - - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(retraction)); - const iq_result = $iq({'type': 'result', 'id': stanza.getAttribute('id')}) - .c('fin', {'xmlns': 'urn:xmpp:mam:2'}) - .c('set', {'xmlns': 'http://jabber.org/protocol/rsm'}) - .c('first', {'index': '0'}).t(first_id).up() - .c('last').t(last_id).up() - .c('count').t('2'); + const iq_result = stx` + + + + ${first_id} + ${last_id} + 2 + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(iq_result)); await u.waitUntil(() => view.model.handleRetraction.calls.count() === 3); @@ -934,8 +1003,8 @@ describe("Message Retractions", function () { const queryid = stanza.querySelector('query').getAttribute('queryid'); const first_id = u.getUniqueId(); - const tombstone = u.toStanza(` - + const tombstone = stx` + @@ -945,14 +1014,13 @@ describe("Message Retractions", function () { - - `); + `; spyOn(view.model, 'handleRetraction').and.callThrough(); _converse.api.connection.get()._dataRecv(mock.createRequest(tombstone)); const last_id = u.getUniqueId(); - const retraction = u.toStanza(` - + const retraction = stx` + @@ -963,16 +1031,19 @@ describe("Message Retractions", function () { - - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(retraction)); - const iq_result = $iq({'type': 'result', 'id': stanza.getAttribute('id')}) - .c('fin', {'xmlns': 'urn:xmpp:mam:2'}) - .c('set', {'xmlns': 'http://jabber.org/protocol/rsm'}) - .c('first', {'index': '0'}).t(first_id).up() - .c('last').t(last_id).up() - .c('count').t('2'); + const iq_result = stx` + + + + ${first_id} + ${last_id} + 2 + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(iq_result)); await u.waitUntil(() => view.model.messages.length === 1); @@ -1009,8 +1080,8 @@ describe("Message Retractions", function () { const queryid = stanza.querySelector('query').getAttribute('queryid'); const first_id = u.getUniqueId(); - const tombstone = u.toStanza(` - + const tombstone = stx` + @@ -1022,14 +1093,13 @@ describe("Message Retractions", function () { - - `); + `; spyOn(view.model, 'handleModeration').and.callThrough(); _converse.api.connection.get()._dataRecv(mock.createRequest(tombstone)); const last_id = u.getUniqueId(); - const retraction = u.toStanza(` - + const retraction = stx` + @@ -1043,16 +1113,19 @@ describe("Message Retractions", function () { - - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(retraction)); - const iq_result = $iq({'type': 'result', 'id': stanza.getAttribute('id')}) - .c('fin', {'xmlns': 'urn:xmpp:mam:2'}) - .c('set', {'xmlns': 'http://jabber.org/protocol/rsm'}) - .c('first', {'index': '0'}).t(first_id).up() - .c('last').t(last_id).up() - .c('count').t('2'); + const iq_result = stx` + + + + ${first_id} + ${last_id} + 2 + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(iq_result)); await u.waitUntil(() => view.model.messages.length); diff --git a/src/plugins/muc-views/tests/styling.js b/src/plugins/muc-views/tests/styling.js index e285892433..c386b22e0d 100644 --- a/src/plugins/muc-views/tests/styling.js +++ b/src/plugins/muc-views/tests/styling.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { u, $msg } = converse.env; +const { u, stx } = converse.env; describe("An incoming groupchat Message", function () { @@ -12,13 +12,15 @@ describe("An incoming groupchat Message", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); const msg_text = "This *message mentions romeo*"; - const msg = $msg({ - from: 'lounge@montague.lit/gibson', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t(msg_text).up() - .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'23', 'end':'29', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).nodeTree; + const msg = stx` + + ${msg_text} + + `; await view.model.handleMessageStanza(msg); const message = await u.waitUntil(() => view.querySelector('.chat-msg__text')); expect(message.classList.length).toEqual(1); @@ -39,13 +41,15 @@ describe("An incoming groupchat Message", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); const view = _converse.chatboxviews.get(muc_jid); const msg_text = "x_y_z_ hello"; - const msg = $msg({ - from: 'lounge@montague.lit/gibson', - id: u.getUniqueId(), - to: 'romeo@montague.lit', - type: 'groupchat' - }).c('body').t(msg_text).up() - .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'0', 'end':'6', 'type':'mention', 'uri':'xmpp:xyz@montague.lit'}).nodeTree; + const msg = stx` + + ${msg_text} + + `; await view.model.handleMessageStanza(msg); const message = await u.waitUntil(() => view.querySelector('.chat-msg__text')); expect(message.classList.length).toEqual(1); diff --git a/src/plugins/muc-views/tests/unfurls.js b/src/plugins/muc-views/tests/unfurls.js index b7a441f193..85bef850b3 100644 --- a/src/plugins/muc-views/tests/unfurls.js +++ b/src/plugins/muc-views/tests/unfurls.js @@ -1,6 +1,6 @@ /*global mock, converse */ -const { Strophe, u } = converse.env; +const { Strophe, u, stx } = converse.env; describe("A Groupchat Message", function () { @@ -13,19 +13,19 @@ describe("A Groupchat Message", function () { const unfurl_image_src = "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg"; const unfurl_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"; - const message_stanza = u.toStanza(` + const message_stanza = stx` https://www.youtube.com/watch?v=dQw4w9WgXcQ - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message_stanza)); const el = await u.waitUntil(() => view.querySelector('.chat-msg__text')); expect(el.textContent).toBe('https://www.youtube.com/watch?v=dQw4w9WgXcQ'); - const metadata_stanza = u.toStanza(` + const metadata_stanza = stx` @@ -42,7 +42,7 @@ describe("A Groupchat Message", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(metadata_stanza)); const unfurl = await u.waitUntil(() => view.querySelector('converse-message-unfurl')); @@ -58,19 +58,19 @@ describe("A Groupchat Message", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, nick); const view = _converse.chatboxviews.get(muc_jid); - const message_stanza = u.toStanza(` + const message_stanza = stx` https://mempool.space - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message_stanza)); const el = await u.waitUntil(() => view.querySelector('.chat-msg__text')); expect(el.textContent).toBe('https://mempool.space'); - const metadata_stanza = u.toStanza(` + const metadata_stanza = stx` @@ -78,7 +78,7 @@ describe("A Groupchat Message", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(metadata_stanza)); const unfurl = await u.waitUntil(() => view.querySelector('converse-message-unfurl')); @@ -94,19 +94,19 @@ describe("A Groupchat Message", function () { const unfurl_url = "https://giphy.com/gifs/giphyqa-4YY4DnqeUDBXNTcYMu"; const gif_url = "https://media4.giphy.com/media/4YY4DnqeUDBXNTcYMu/giphy.gif?foo=bar"; - const message_stanza = u.toStanza(` + const message_stanza = stx` ${unfurl_url} - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message_stanza)); const el = await u.waitUntil(() => view.querySelector('.chat-msg__text')); expect(el.textContent).toBe(unfurl_url); - const metadata_stanza = u.toStanza(` + const metadata_stanza = stx` @@ -117,7 +117,7 @@ describe("A Groupchat Message", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(metadata_stanza)); const unfurl = await u.waitUntil(() => view.querySelector('converse-message-unfurl')); @@ -130,19 +130,19 @@ describe("A Groupchat Message", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, nick); const view = _converse.chatboxviews.get(muc_jid); - const message_stanza = u.toStanza(` + const message_stanza = stx` Check out https://www.youtube.com/watch?v=dQw4w9WgXcQ and https://duckduckgo.com - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message_stanza)); const el = await u.waitUntil(() => view.querySelector('.chat-msg__text')); expect(el.textContent).toBe('Check out https://www.youtube.com/watch?v=dQw4w9WgXcQ and https://duckduckgo.com'); - let metadata_stanza = u.toStanza(` + let metadata_stanza = stx` @@ -159,11 +159,11 @@ describe("A Groupchat Message", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(metadata_stanza)); await u.waitUntil(() => view.querySelectorAll('converse-message-unfurl').length === 1); - metadata_stanza = u.toStanza(` + metadata_stanza = stx` @@ -172,7 +172,7 @@ describe("A Groupchat Message", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(metadata_stanza)); await u.waitUntil(() => view.querySelectorAll('converse-message-unfurl').length === 2); @@ -184,21 +184,21 @@ describe("A Groupchat Message", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, nick); const view = _converse.chatboxviews.get(muc_jid); - const message_stanza = u.toStanza(` + const message_stanza = stx` https://www.youtube.com/watch?v=dQw4w9WgXcQ - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message_stanza)); const el = await u.waitUntil(() => view.querySelector('.chat-msg__text')); expect(el.textContent).toBe('https://www.youtube.com/watch?v=dQw4w9WgXcQ'); spyOn(view.model, 'handleMetadataFastening').and.callThrough(); - const metadata_stanza = u.toStanza(` + const metadata_stanza = stx` @@ -207,7 +207,7 @@ describe("A Groupchat Message", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(metadata_stanza)); await u.waitUntil(() => view.model.handleMetadataFastening.calls.count()); @@ -226,21 +226,21 @@ describe("A Groupchat Message", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, nick); const view = _converse.chatboxviews.get(muc_jid); - const message_stanza = u.toStanza(` + const message_stanza = stx` https://www.youtube.com/watch?v=dQw4w9WgXcQ - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message_stanza)); const el = await u.waitUntil(() => view.querySelector('.chat-msg__text')); expect(el.textContent).toBe('https://www.youtube.com/watch?v=dQw4w9WgXcQ'); spyOn(view.model, 'handleMetadataFastening').and.callThrough(); - const metadata_stanza = u.toStanza(` + const metadata_stanza = stx` @@ -249,7 +249,7 @@ describe("A Groupchat Message", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(metadata_stanza)); expect(view.querySelector('converse-message-unfurl')).toBe(null); @@ -273,21 +273,21 @@ describe("A Groupchat Message", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, nick); const view = _converse.chatboxviews.get(muc_jid); - const message_stanza = u.toStanza(` + const message_stanza = stx` https://www.youtube.com/watch?v=dQw4w9WgXcQ - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message_stanza)); const el = await u.waitUntil(() => view.querySelector('.chat-msg__text')); expect(el.textContent).toBe('https://www.youtube.com/watch?v=dQw4w9WgXcQ'); spyOn(view.model, 'handleMetadataFastening').and.callThrough(); - const metadata_stanza = u.toStanza(` + const metadata_stanza = stx` @@ -296,7 +296,7 @@ describe("A Groupchat Message", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(metadata_stanza)); _converse.api.connection.get()._dataRecv(mock.createRequest(metadata_stanza)); _converse.api.connection.get()._dataRecv(mock.createRequest(metadata_stanza)); @@ -318,19 +318,19 @@ describe("A Groupchat Message", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, nick); const view = _converse.chatboxviews.get(muc_jid); - const message_stanza = u.toStanza(` + const message_stanza = stx` https://www.youtube.com/watch?v=dQw4w9WgXcQ - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message_stanza)); const el = await u.waitUntil(() => view.querySelector('.chat-msg__text')); expect(el.textContent).toBe('https://www.youtube.com/watch?v=dQw4w9WgXcQ'); - const metadata_stanza = u.toStanza(` + const metadata_stanza = stx` @@ -339,7 +339,7 @@ describe("A Groupchat Message", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(metadata_stanza)); await u.waitUntil(() => !view.querySelector('converse-message-unfurl')); @@ -360,19 +360,19 @@ describe("A Groupchat Message", function () { await mock.openAndEnterChatRoom(_converse, muc_jid, nick); const view = _converse.chatboxviews.get(muc_jid); - const message_stanza = u.toStanza(` + const message_stanza = stx` https://www.youtube.com/watch?v=dQw4w9WgXcQ - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(message_stanza)); const el = await u.waitUntil(() => view.querySelector('.chat-msg__text')); expect(el.textContent).toBe('https://www.youtube.com/watch?v=dQw4w9WgXcQ'); - const metadata_stanza = u.toStanza(` + const metadata_stanza = stx` @@ -381,7 +381,7 @@ describe("A Groupchat Message", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(metadata_stanza)); await u.waitUntil(() => view.querySelector('converse-message-unfurl')); @@ -440,7 +440,7 @@ describe("A Groupchat Message", function () { const el = await u.waitUntil(() => view.querySelector('.chat-msg__text')); expect(el.textContent).toBe(unfurl_url); - const metadata_stanza = u.toStanza(` + const metadata_stanza = stx` @@ -457,7 +457,7 @@ describe("A Groupchat Message", function () { - `); + `; _converse.api.connection.get()._dataRecv(mock.createRequest(metadata_stanza)); const unfurl = await u.waitUntil(() => view.querySelector('converse-message-unfurl')); diff --git a/src/plugins/muc-views/tests/xss.js b/src/plugins/muc-views/tests/xss.js index 37ef0ff7dc..77607ad6b4 100644 --- a/src/plugins/muc-views/tests/xss.js +++ b/src/plugins/muc-views/tests/xss.js @@ -1,7 +1,6 @@ /*global mock, converse */ -const $pres = converse.env.$pres; -const u = converse.env.utils; +const { stx, u } = converse.env; describe("XSS", function () { describe("A Groupchat", function () { @@ -10,30 +9,23 @@ describe("XSS", function () { mock.initConverse([], {}, async function (_converse) { await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo'); - /* - * - * - * - * - * " - */ - const presence = $pres({ - to:'romeo@montague.lit/pda', - from:"lounge@montague.lit/<img src="x" onerror="alert(123)"/>" - }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'}) - .c('item').attrs({ - jid: 'someone@montague.lit', - role: 'moderator', - }).up() - .c('status').attrs({code:'110'}).nodeTree; + + const presence = stx` + + + + + + `; _converse.api.connection.get()._dataRecv(mock.createRequest(presence)); const view = _converse.chatboxviews.get('lounge@montague.lit'); await u.waitUntil(() => view.querySelectorAll('.occupant-list .occupant-nick').length === 2); const occupants = view.querySelectorAll('.occupant-list li .occupant-nick'); expect(occupants.length).toBe(2); - expect(occupants[0].textContent.trim()).toBe("<img src="x" onerror="alert(123)"/>"); + expect(occupants[0].textContent.trim()).toBe(''); })); it("escapes the subject before rendering it, to avoid JS-injection attacks",