diff --git a/addons/mail/static/src/components/composer/composer_tests.js b/addons/mail/static/src/components/composer/composer_tests.js index 6df2cfa1f53..2505421d7e9 100644 --- a/addons/mail/static/src/components/composer/composer_tests.js +++ b/addons/mail/static/src/components/composer/composer_tests.js @@ -673,9 +673,20 @@ QUnit.test('add an emoji after a channel mention', async function (assert) { QUnit.test('display command suggestions on typing "/"', async function (assert) { assert.expect(2); + this.data['mail.channel'].records.push({ channel_type: 'channel', id: 20 }); + this.data['mail.channel_command'].records.push( + { + channel_types: ['channel'], + help: "List users in the current channel", + name: "who", + }, + ); await this.start(); - const composer = this.env.models['mail.composer'].create(); - await this.createComposerComponent(composer); + const thread = this.env.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }); + await this.createComposerComponent(thread.composer); assert.containsNone( document.body, @@ -697,12 +708,23 @@ QUnit.test('display command suggestions on typing "/"', async function (assert) ); }); -QUnit.test('use a command', async function (assert) { +QUnit.test('use a command for a specific channel type', async function (assert) { assert.expect(4); + this.data['mail.channel'].records.push({ channel_type: 'channel', id: 20 }); + this.data['mail.channel_command'].records.push( + { + channel_types: ['channel'], + help: "List users in the current channel", + name: "who", + }, + ); await this.start(); - const composer = this.env.models['mail.composer'].create(); - await this.createComposerComponent(composer); + const thread = this.env.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }); + await this.createComposerComponent(thread.composer); assert.containsNone( document.body, @@ -739,13 +761,56 @@ QUnit.test('use a command', async function (assert) { ); }); +QUnit.test("channel with no commands should not prompt any command suggestions on typing /", async function (assert) { + assert.expect(1); + + this.data['mail.channel'].records.push({ channel_type: 'chat', id: 20 }); + this.data['mail.channel_command'].records.push( + { + channel_types: ['channel'], + help: "bla bla bla", + name: "who", + }, + ); + await this.start(); + const thread = this.env.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }); + await this.createComposerComponent(thread.composer); + await afterNextRender(() => { + document.querySelector('.o_ComposerTextInput_textarea').focus(); + document.execCommand('insertText', false, "/"); + }); + await afterNextRender(() => { + const composer_text_input = document.querySelector('.o_ComposerTextInput_textarea'); + composer_text_input.dispatchEvent(new window.KeyboardEvent('keydown')); + composer_text_input.dispatchEvent(new window.KeyboardEvent('keyup')); + }); + assert.containsNone( + document.body, + '.o_ComposerSuggestion', + "should not prompt (command) suggestion after typing / (reason: no channel commands in chat channels)" + ); +}); + QUnit.test('use a command after some text', async function (assert) { assert.expect(5); + this.data['mail.channel'].records.push({ channel_type: 'channel', id: 20 }); + this.data['mail.channel_command'].records.push( + { + channel_types: ['channel'], + help: "List users in the current channel", + name: "who", + }, + ); await this.start(); - const composer = this.env.models['mail.composer'].create(); - await this.createComposerComponent(composer); - + const thread = this.env.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }); + await this.createComposerComponent(thread.composer); assert.containsNone( document.body, '.o_ComposerSuggestion', @@ -792,9 +857,20 @@ QUnit.test('use a command after some text', async function (assert) { QUnit.test('add an emoji after a command', async function (assert) { assert.expect(5); + this.data['mail.channel'].records.push({ channel_type: 'channel', id: 20 }); + this.data['mail.channel_command'].records.push( + { + channel_types: ['channel'], + help: "List users in the current channel", + name: "who", + }, + ); await this.start(); - const composer = this.env.models['mail.composer'].create(); - await this.createComposerComponent(composer); + const thread = this.env.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }); + await this.createComposerComponent(thread.composer); assert.containsNone( document.body, diff --git a/addons/mail/static/src/models/channel_command/channel_command.js b/addons/mail/static/src/models/channel_command/channel_command.js index 0eef37791a6..12054c1a35b 100644 --- a/addons/mail/static/src/models/channel_command/channel_command.js +++ b/addons/mail/static/src/models/channel_command/channel_command.js @@ -10,7 +10,10 @@ function factory(dependencies) { ChannelCommand.fields = { /** - * FIXME use this value task-2343850 + * Determines on which channel types `this` is available. + * Type of the channel (e.g. 'chat', 'channel' or 'groups') + * This field should contain an array when filtering is desired. + * Otherwise, it should be undefined when all types are allowed. */ channel_types: attr(), /** diff --git a/addons/mail/static/src/models/composer/composer.js b/addons/mail/static/src/models/composer/composer.js index 9544968f4cd..161e884279a 100644 --- a/addons/mail/static/src/models/composer/composer.js +++ b/addons/mail/static/src/models/composer/composer.js @@ -602,7 +602,15 @@ function factory(dependencies) { _getCommandFromText(content) { if (content.startsWith('/')) { const firstWord = content.substring(1).split(/\s/)[0]; - return this.env.messaging.commands.find(command => command.name === firstWord); + return this.env.messaging.commands.find(command => { + if (command.name !== firstWord) { + return false; + } + if (command.channel_types) { + return command.channel_types.includes(this.thread.channel_type); + } + return true; + }); } return undefined; } @@ -685,15 +693,16 @@ function factory(dependencies) { * @param {string} mentionKeyword */ _updateSuggestedChannelCommands(mentionKeyword) { - this.update({ - suggestedChannelCommands: [[ - 'replace', - this.env.messaging.commands.filter( - command => command.name.includes(mentionKeyword) - ) - ]], + const commands = this.env.messaging.commands.filter(command => { + if (!command.name.includes(mentionKeyword)) { + return false; + } + if (command.channel_types) { + return command.channel_types.includes(this.thread.channel_type); + } + return true; }); - + this.update({ suggestedChannelCommands: [['replace', commands]] }); if (this.suggestedChannelCommands[0]) { this.update({ activeSuggestedChannelCommand: [['link', this.suggestedChannelCommands[0]]], diff --git a/addons/mail/static/tests/helpers/mock_models.js b/addons/mail/static/tests/helpers/mock_models.js index ef985718edf..5f5b3e09b28 100644 --- a/addons/mail/static/tests/helpers/mock_models.js +++ b/addons/mail/static/tests/helpers/mock_models.js @@ -95,6 +95,15 @@ class MockModels { }, records: [], }, + // Fake model to simulate "hardcoded" commands from python + 'mail.channel_command': { + fields: { + channel_types: { type: 'binary' }, // array is expected + help: { type: 'char' }, + name: { type: 'char' }, + }, + records: [], + }, 'mail.followers': { fields: { channel_id: { type: 'integer' }, diff --git a/addons/mail/static/tests/helpers/mock_server.js b/addons/mail/static/tests/helpers/mock_server.js index d21f17e9bbd..86cbe2d6c0d 100644 --- a/addons/mail/static/tests/helpers/mock_server.js +++ b/addons/mail/static/tests/helpers/mock_server.js @@ -398,6 +398,8 @@ MockServer.include({ const shortcodes = this._getRecords('mail.shortcode', []); + const commands = this._getRecords('mail.channel_command', []); + const starredCounter = this._getRecords('mail.message', [ ['starred_partner_ids', 'in', this.currentPartnerId], ]).length; @@ -408,11 +410,7 @@ MockServer.include({ channel_direct_message: directMessageInfos, channel_private_group: privateGroupInfos, }, - commands: [{ - channel_types: ["channel", "chat"], - help: "List users in the current channel", - name: "who", - }], + commands, current_partner: currentPartnerFormat, current_user_id: this.currentUserId, mail_failures: mailFailures,