summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Dubuc <xdu@odoo.com>2020-06-15 14:36:47 +0000
committerAlexandre Kühn <aku@odoo.com>2020-06-25 11:13:09 +0000
commite5de39d03669b98224e2ebc88bfc2003a64e1550 (patch)
tree4fe911b033ffd9c39ab70e8325970b2b4bc605e1
parent281329361dccd68895f39a69652329420fb52014 (diff)
[FIX] mail: fix disappearing moderated messages
task-2277748 task-2277811 closes odoo/odoo#53652 X-original-commit: 5ef6aedb8271fa05f79fb6f93f679caa9cd418ff Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
-rw-r--r--addons/mail/static/src/components/discuss/tests/discuss_moderation_tests.js1223
-rw-r--r--addons/mail/static/src/components/discuss/tests/discuss_tests.js (renamed from addons/mail/static/src/components/discuss/discuss_tests.js)954
-rw-r--r--addons/mail/views/assets.xml3
3 files changed, 1227 insertions, 953 deletions
diff --git a/addons/mail/static/src/components/discuss/tests/discuss_moderation_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_moderation_tests.js
new file mode 100644
index 00000000000..40a5af69337
--- /dev/null
+++ b/addons/mail/static/src/components/discuss/tests/discuss_moderation_tests.js
@@ -0,0 +1,1223 @@
+odoo.define('mail/static/src/components/discuss/tests/discuss_moderation_tests.js', function (require) {
+'use strict';
+
+const {
+ afterEach: utilsAfterEach,
+ afterNextRender,
+ beforeEach: utilsBeforeEach,
+ start: utilsStart,
+} = require('mail/static/src/utils/test_utils.js');
+
+QUnit.module('mail', {}, function () {
+QUnit.module('components', {}, function () {
+QUnit.module('discuss', {}, function () {
+QUnit.module('discuss_moderation_tests.js', {
+ beforeEach() {
+ utilsBeforeEach(this);
+
+ this.start = async params => {
+ if (this.widget) {
+ this.widget.destroy();
+ }
+ let { env, widget } = await utilsStart(Object.assign({}, params, {
+ autoOpenDiscuss: true,
+ data: this.data,
+ hasDiscuss: true,
+ }));
+ this.env = env;
+ this.widget = widget;
+ };
+ },
+ afterEach() {
+ if (this.widget) {
+ this.widget.destroy();
+ }
+ utilsAfterEach(this);
+ },
+});
+
+QUnit.test('moderated channel with pending moderation message', async function (assert) {
+ assert.expect(37);
+
+ Object.assign(this.data.initMessaging, {
+ channel_slots: {
+ channel_channel: [{
+ id: 20,
+ channel_type: "channel",
+ name: "general",
+ moderation: true,
+ }],
+ },
+ is_moderator: true,
+ moderation_counter: 1,
+ moderation_channel_ids: [20],
+ });
+ this.data['mail.message'].records = [{
+ author_id: [2, "Someone"],
+ body: "<p>test</p>",
+ channel_ids: [20],
+ id: 100,
+ model: 'mail.channel',
+ moderation_status: 'pending_moderation',
+ need_moderation: true,
+ res_id: 20,
+ }];
+
+ await this.start();
+
+ assert.ok(
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.messaging.moderation.localId
+ }"]
+ `),
+ "should display the moderation box in the sidebar"
+ );
+ const mailboxCounter = document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.messaging.moderation.localId
+ }"]
+ .o_DiscussSidebarItem_counter
+ `);
+ assert.ok(
+ mailboxCounter,
+ "there should be a counter next to the moderation mailbox in the sidebar"
+ );
+ assert.strictEqual(
+ mailboxCounter.textContent.trim(),
+ "1",
+ "the mailbox counter of the moderation mailbox should display '1'"
+ );
+
+ // 1. go to moderation mailbox
+ await afterNextRender(() =>
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.messaging.moderation.localId
+ }"]
+ `).click()
+ );
+ // check message
+ assert.containsOnce(
+ document.body,
+ '.o_Message',
+ "should be only one message in moderation box"
+ );
+ assert.strictEqual(
+ document.querySelector('.o_Message_content').textContent,
+ "test",
+ "this message pending moderation should have the correct content"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_Message_originThreadLink',
+ "thee message should have one origin"
+ );
+ assert.strictEqual(
+ document.querySelector('.o_Message_originThreadLink').textContent,
+ "#general",
+ "the message pending moderation should have correct origin as its linked document"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_Message_checkbox',
+ "there should be a moderation checkbox next to the message"
+ );
+ assert.notOk(
+ document.querySelector('.o_Message_checkbox').checked,
+ "the moderation checkbox should be unchecked by default"
+ );
+ // check select all (enabled) / unselect all (disabled) buttons
+ assert.containsOnce(
+ document.body,
+ '.o_widget_Discuss_controlPanelButtonSelectAll',
+ "there should be a 'Select All' button in the control panel"
+ );
+ assert.doesNotHaveClass(
+ document.querySelector('.o_widget_Discuss_controlPanelButtonSelectAll'),
+ 'disabled',
+ "the 'Select All' button should not be disabled"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_widget_Discuss_controlPanelButtonUnselectAll',
+ "there should be a 'Unselect All' button in the control panel"
+ );
+ assert.hasClass(
+ document.querySelector('.o_widget_Discuss_controlPanelButtonUnselectAll'),
+ 'disabled',
+ "the 'Unselect All' button should be disabled"
+ );
+ // check moderate all buttons (invisible)
+ assert.containsN(
+ document.body,
+ '.o_widget_Discuss_controlPanelButtonModeration',
+ 3,
+ "there should be 3 buttons to moderate selected messages in the control panel"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_widget_Discuss_controlPanelButtonModeration.o-accept',
+ "there should one moderate button to accept messages pending moderation"
+ );
+ assert.isNotVisible(
+ document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept'),
+ "the moderate button 'Accept' should be invisible by default"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_widget_Discuss_controlPanelButtonModeration.o-reject',
+ "there should one moderate button to reject messages pending moderation"
+ );
+ assert.isNotVisible(
+ document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-reject'),
+ "the moderate button 'Reject' should be invisible by default"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_widget_Discuss_controlPanelButtonModeration.o-discard',
+ "there should one moderate button to discard messages pending moderation"
+ );
+ assert.isNotVisible(
+ document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-discard'),
+ "the moderate button 'Discard' should be invisible by default"
+ );
+
+ // click on message moderation checkbox
+ await afterNextRender(() => document.querySelector('.o_Message_checkbox').click());
+ assert.ok(
+ document.querySelector('.o_Message_checkbox').checked,
+ "the moderation checkbox should become checked after click"
+ );
+ // check select all (disabled) / unselect all buttons (enabled)
+ assert.hasClass(
+ document.querySelector('.o_widget_Discuss_controlPanelButtonSelectAll'),
+ 'disabled',
+ "the 'Select All' button should be disabled"
+ );
+ assert.doesNotHaveClass(
+ document.querySelector('.o_widget_Discuss_controlPanelButtonUnselectAll'),
+ 'disabled',
+ "the 'Unselect All' button should not be disabled"
+ );
+ // check moderate all buttons updated (visible)
+ assert.isVisible(
+ document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept'),
+ "the moderate button 'Accept' should be visible"
+ );
+ assert.isVisible(
+ document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-reject'),
+ "the moderate button 'Reject' should be visible"
+ );
+ assert.isVisible(
+ document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-discard'),
+ "the moderate button 'Discard' should be visible"
+ );
+
+ // test select buttons
+ await afterNextRender(() =>
+ document.querySelector('.o_widget_Discuss_controlPanelButtonUnselectAll').click()
+ );
+ assert.notOk(
+ document.querySelector('.o_Message_checkbox').checked,
+ "the moderation checkbox should become unchecked after click"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector('.o_widget_Discuss_controlPanelButtonSelectAll').click()
+ );
+ assert.ok(
+ document.querySelector('.o_Message_checkbox').checked,
+ "the moderation checkbox should become checked again after click"
+ );
+
+ // 2. go to channel 'general'
+ await afterNextRender(() =>
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.models['mail.thread'].find(thread =>
+ thread.id === 20 &&
+ thread.model === 'mail.channel'
+ ).localId
+ }"]
+ `).click()
+ );
+ // check correct message
+ assert.containsOnce(
+ document.body,
+ '.o_Message',
+ "should be only one message in general channel"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_Message_checkbox',
+ "there should be a moderation checkbox next to the message"
+ );
+ assert.notOk(
+ document.querySelector('.o_Message_checkbox').checked,
+ "the moderation checkbox should not be checked here"
+ );
+ await afterNextRender(() => document.querySelector('.o_Message_checkbox').click());
+ // Don't test moderation actions visibility, since it is similar to moderation box.
+
+ // 3. test discard button
+ await afterNextRender(() =>
+ document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-discard').click()
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ModerationDiscardDialog',
+ "discard dialog should be open"
+ );
+ // the dialog will be tested separately
+ await afterNextRender(() =>
+ document.querySelector('.o_ModerationDiscardDialog .o-cancel').click()
+ );
+ assert.containsNone(
+ document.body,
+ '.o_ModerationDiscardDialog',
+ "discard dialog should be closed"
+ );
+
+ // 4. test reject button
+ await afterNextRender(() =>
+ document.querySelector(`
+ .o_widget_Discuss_controlPanelButtonModeration.o-reject
+ `).click()
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ModerationRejectDialog',
+ "reject dialog should be open"
+ );
+ // the dialog will be tested separately
+ await afterNextRender(() =>
+ document.querySelector('.o_ModerationRejectDialog .o-cancel').click()
+ );
+ assert.containsNone(
+ document.body,
+ '.o_ModerationRejectDialog',
+ "reject dialog should be closed"
+ );
+
+ // 5. test accept button
+ await afterNextRender(() =>
+ document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept').click()
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_Message',
+ "should still be only one message in general channel"
+ );
+ assert.containsNone(
+ document.body,
+ '.o_Message_checkbox',
+ "there should not be a moderation checkbox next to the message"
+ );
+});
+
+QUnit.test('accept pending moderation message', async function (assert) {
+ assert.expect(12);
+
+ Object.assign(this.data.initMessaging, {
+ channel_slots: {
+ channel_channel: [{
+ id: 20,
+ channel_type: "channel",
+ name: "general",
+ moderation: true,
+ }],
+ },
+ is_moderator: true,
+ moderation_counter: 1,
+ moderation_channel_ids: [20],
+ });
+ this.data['mail.message'].records = [{
+ author_id: [2, "Someone"],
+ body: "<p>test</p>",
+ id: 100,
+ model: 'mail.channel',
+ moderation_status: 'pending_moderation',
+ need_moderation: true,
+ res_id: 20,
+ }];
+
+ await this.start({
+ async mockRPC(route, args) {
+ if (args.method === 'moderate') {
+ assert.step('moderate');
+ const messageIDs = args.args[0];
+ const decision = args.args[1];
+ assert.strictEqual(
+ messageIDs.length,
+ 1,
+ "should moderate one message"
+ );
+ assert.strictEqual(
+ messageIDs[0],
+ 100,
+ "should moderate message with ID 100"
+ );
+ assert.strictEqual(
+ decision,
+ 'accept',
+ "should accept the message"
+ );
+ }
+ return this._super(...arguments);
+ },
+ });
+
+ // 1. go to moderation box
+ const moderationBox = document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.messaging.moderation.localId
+ }"]
+ `);
+ assert.ok(
+ moderationBox,
+ "should display the moderation box"
+ );
+
+ await afterNextRender(() => moderationBox.click());
+ assert.ok(
+ document.querySelector(`
+ .o_Message[data-message-local-id="${
+ this.env.models['mail.message'].find(message => message.id === 100).localId
+ }"]
+ `),
+ "should display the message to moderate"
+ );
+ const acceptButton = document.querySelector(`
+ .o_Message[data-message-local-id="${
+ this.env.models['mail.message'].find(message => message.id === 100).localId
+ }"]
+ .o_Message_moderationAction.o-accept
+ `);
+ assert.ok(acceptButton, "should display the accept button");
+
+ await afterNextRender(() => acceptButton.click());
+ assert.verifySteps(['moderate']);
+ assert.containsOnce(
+ document.body,
+ '.o_MessageList_emptyTitle',
+ "should now have no message displayed in moderation box"
+ );
+
+ // 2. go to channel 'general'
+ const channel = document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.models['mail.thread'].find(thread =>
+ thread.id === 20 &&
+ thread.model === 'mail.channel'
+ ).localId
+ }"]
+ `);
+ assert.ok(
+ channel,
+ "should display the general channel"
+ );
+
+ await afterNextRender(() => channel.click());
+ const message = document.querySelector(`
+ .o_Message[data-message-local-id="${
+ this.env.models['mail.message'].find(message => message.id === 100).localId
+ }"]
+ `);
+ assert.ok(
+ message,
+ "should display the accepted message"
+ );
+ assert.containsNone(
+ message,
+ '.o_Message_moderationPending',
+ "the message should not be pending moderation"
+ );
+});
+
+QUnit.test('reject pending moderation message (reject with explanation)', async function (assert) {
+ assert.expect(23);
+
+ Object.assign(this.data.initMessaging, {
+ channel_slots: {
+ channel_channel: [{
+ id: 20,
+ channel_type: "channel",
+ name: "general",
+ moderation: true,
+ }],
+ },
+ is_moderator: true,
+ moderation_counter: 1,
+ moderation_channel_ids: [20],
+ });
+ this.data['mail.message'].records = [{
+ author_id: [2, "Someone"],
+ body: "<p>test</p>",
+ id: 100,
+ model: 'mail.channel',
+ moderation_status: 'pending_moderation',
+ need_moderation: true,
+ res_id: 20,
+ }];
+
+ await this.start({
+ async mockRPC(route, args) {
+ if (args.method === 'moderate') {
+ assert.step('moderate');
+ const messageIDs = args.args[0];
+ const decision = args.args[1];
+ const kwargs = args.kwargs;
+ assert.strictEqual(
+ messageIDs.length,
+ 1,
+ "should moderate one message"
+ );
+ assert.strictEqual(
+ messageIDs[0],
+ 100,
+ "should moderate message with ID 100"
+ );
+ assert.strictEqual(
+ decision,
+ 'reject',
+ "should reject the message"
+ );
+ assert.strictEqual(
+ kwargs.title,
+ "Message Rejected",
+ "should have correct reject message title"
+ );
+ assert.strictEqual(
+ kwargs.comment,
+ "Your message was rejected by moderator.",
+ "should have correct reject message body / comment"
+ );
+ }
+ return this._super(...arguments);
+ },
+ });
+
+ // 1. go to moderation box
+ const moderationBox = document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.messaging.moderation.localId
+ }"]
+ `);
+ assert.ok(
+ moderationBox,
+ "should display the moderation box"
+ );
+
+ await afterNextRender(() => moderationBox.click());
+ const pendingMessage = document.querySelector(`
+ .o_Message[data-message-local-id="${
+ this.env.models['mail.message'].find(message => message.id === 100).localId
+ }"]
+ `);
+ assert.ok(
+ pendingMessage,
+ "should display the message to moderate"
+ );
+ const rejectButton = pendingMessage.querySelector(':scope .o_Message_moderationAction.o-reject');
+ assert.ok(
+ rejectButton,
+ "should display the reject button"
+ );
+
+ await afterNextRender(() => rejectButton.click());
+ const dialog = document.querySelector('.o_ModerationRejectDialog');
+ assert.ok(
+ dialog,
+ "a dialog should be prompt to the moderator on click reject"
+ );
+ assert.strictEqual(
+ dialog.querySelector('.modal-title').textContent,
+ // TODO FIXME, this should be a proper title "Send explanation to author"
+ // see https://github.com/odoo/owl/issues/670
+ "[object Object]",
+ "dialog should have correct title"
+ );
+
+ const messageTitle = dialog.querySelector(':scope .o_ModerationRejectDialog_title');
+ assert.ok(
+ messageTitle,
+ "should have a title for rejecting"
+ );
+ assert.hasAttrValue(
+ messageTitle,
+ 'placeholder',
+ "Subject",
+ "title for reject reason should have correct placeholder"
+ );
+ assert.strictEqual(
+ messageTitle.value,
+ "Message Rejected",
+ "title for reject reason should have correct default value"
+ );
+
+ const messageComment = dialog.querySelector(':scope .o_ModerationRejectDialog_comment');
+ assert.ok(
+ messageComment,
+ "should have a comment for rejecting"
+ );
+ assert.hasAttrValue(
+ messageComment,
+ 'placeholder',
+ "Mail Body",
+ "comment for reject reason should have correct placeholder"
+ );
+ assert.strictEqual(
+ messageComment.value,
+ "Your message was rejected by moderator.",
+ "comment for reject reason should have correct default text content"
+ );
+ const confirmReject = dialog.querySelector(':scope .o-reject');
+ assert.ok(
+ confirmReject,
+ "should have reject button"
+ );
+ assert.strictEqual(
+ confirmReject.textContent,
+ "Reject"
+ );
+
+ await afterNextRender(() => confirmReject.click());
+ assert.verifySteps(['moderate']);
+ assert.containsOnce(
+ document.body,
+ '.o_MessageList_emptyTitle',
+ "should now have no message displayed in moderation box"
+ );
+
+ // 2. go to channel 'general'
+ const channel = document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.models['mail.thread'].find(thread =>
+ thread.id === 20 &&
+ thread.model === 'mail.channel'
+ ).localId
+ }"]
+ `);
+ assert.ok(
+ channel,
+ 'should display the general channel'
+ );
+
+ await afterNextRender(() => channel.click());
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should now have no message in channel"
+ );
+});
+
+QUnit.test('discard pending moderation message (reject without explanation)', async function (assert) {
+ assert.expect(16);
+
+ Object.assign(this.data.initMessaging, {
+ channel_slots: {
+ channel_channel: [{
+ id: 20,
+ channel_type: "channel",
+ name: "general",
+ moderation: true,
+ }],
+ },
+ is_moderator: true,
+ moderation_counter: 1,
+ moderation_channel_ids: [20],
+ });
+ this.data['mail.message'].records = [{
+ author_id: [2, "Someone"],
+ body: "<p>test</p>",
+ id: 100,
+ model: 'mail.channel',
+ moderation_status: 'pending_moderation',
+ need_moderation: true,
+ res_id: 20,
+ }];
+
+ await this.start({
+ async mockRPC(route, args) {
+ if (args.method === 'moderate') {
+ assert.step('moderate');
+ const messageIDs = args.args[0];
+ const decision = args.args[1];
+ assert.strictEqual(messageIDs.length, 1, "should moderate one message");
+ assert.strictEqual(messageIDs[0], 100, "should moderate message with ID 100");
+ assert.strictEqual(decision, 'discard', "should discard the message");
+ }
+ return this._super(...arguments);
+ },
+ });
+
+ // 1. go to moderation box
+ const moderationBox = document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.messaging.moderation.localId
+ }"]
+ `);
+ assert.ok(
+ moderationBox,
+ "should display the moderation box"
+ );
+
+ await afterNextRender(() => moderationBox.click());
+ const pendingMessage = document.querySelector(`
+ .o_Message[data-message-local-id="${
+ this.env.models['mail.message'].find(message => message.id === 100).localId
+ }"]
+ `);
+ assert.ok(
+ pendingMessage,
+ "should display the message to moderate"
+ );
+
+ const discardButton = pendingMessage.querySelector(`
+ :scope .o_Message_moderationAction.o-discard
+ `);
+ assert.ok(
+ discardButton,
+ "should display the discard button"
+ );
+
+ await afterNextRender(() => discardButton.click());
+ const dialog = document.querySelector('.o_ModerationDiscardDialog');
+ assert.ok(
+ dialog,
+ "a dialog should be prompt to the moderator on click discard"
+ );
+ assert.strictEqual(
+ dialog.querySelector('.modal-title').textContent,
+ // TODO FIXME, this should be a proper title "Confirmation"
+ // see https://github.com/odoo/owl/issues/670
+ "[object Object]",
+ "dialog should have correct title"
+ );
+ assert.strictEqual(
+ dialog.textContent,
+ "[object Object]×You are going to discard 1 message.Do you confirm the action?DiscardCancel",
+ "should warn the user on discard action"
+ );
+
+ const confirmDiscard = dialog.querySelector(':scope .o-discard');
+ assert.ok(
+ confirmDiscard,
+ "should have discard button"
+ );
+ assert.strictEqual(
+ confirmDiscard.textContent,
+ "Discard"
+ );
+
+ await afterNextRender(() => confirmDiscard.click());
+ assert.verifySteps(['moderate']);
+ assert.containsOnce(
+ document.body,
+ '.o_MessageList_emptyTitle',
+ "should now have no message displayed in moderation box"
+ );
+
+ // 2. go to channel 'general'
+ const channel = document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.models['mail.thread'].find(thread =>
+ thread.id === 20 &&
+ thread.model === 'mail.channel'
+ ).localId
+ }"]
+ `);
+ assert.ok(
+ channel,
+ "should display the general channel"
+ );
+
+ await afterNextRender(() => channel.click());
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should now have no message in channel"
+ );
+});
+
+QUnit.test('send message in moderated channel', async function (assert) {
+ assert.expect(4);
+
+ const self = this;
+ Object.assign(this.data.initMessaging, {
+ channel_slots: {
+ channel_channel: [{
+ id: 20,
+ channel_type: "channel",
+ name: "general",
+ moderation: true,
+ }],
+ },
+ });
+
+ await this.start({
+ async mockRPC(route, args) {
+ if (args.method === 'message_post') {
+ const message = {
+ id: 100,
+ author_id: [13, 'Someone'],
+ body: args.kwargs.body,
+ channel_ids: [20],
+ message_type: args.kwargs.message_type,
+ model: 'mail.channel',
+ moderation_status: 'pending_moderation',
+ res_id: 20,
+ };
+ const notificationData = {
+ type: 'author',
+ message: message,
+ };
+ const notification = [[false, 'res.partner', 13], notificationData];
+ self.widget.call('bus_service', 'trigger', 'notification', [notification]);
+
+ return message.id;
+ }
+ return this._super(...arguments);
+ },
+ });
+
+ // go to channel 'general'
+ const channel = document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.models['mail.thread'].find(thread =>
+ thread.id === 20 &&
+ thread.model === 'mail.channel'
+ ).localId
+ }"]
+ `);
+ assert.ok(
+ channel,
+ "should display the general channel"
+ );
+
+ await afterNextRender(() => channel.click());
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should have no message in channel"
+ );
+
+ // post a message
+ await afterNextRender(() => {
+ const textInput = document.querySelector('.o_ComposerTextInput_textarea');
+ textInput.focus();
+ document.execCommand('insertText', false, "Some Text");
+ });
+ await afterNextRender(() => document.querySelector('.o_Composer_buttonSend').click());
+ const messagePending = document.querySelector(`
+ .o_Message[data-message-local-id="${
+ this.env.models['mail.message'].find(message => message.id === 100).localId
+ }"]
+ .o_Message_moderationPending
+ `);
+ assert.ok(
+ messagePending,
+ "should display the pending message with pending info"
+ );
+ assert.hasClass(
+ messagePending,
+ 'o-author',
+ "the message should be pending moderation as author"
+ );
+});
+
+QUnit.test('sent message accepted in moderated channel', async function (assert) {
+ assert.expect(5);
+
+ Object.assign(this.data.initMessaging, {
+ channel_slots: {
+ channel_channel: [{
+ id: 20,
+ channel_type: "channel",
+ name: "general",
+ moderation: true,
+ }],
+ },
+ });
+ this.data['mail.message'].records = [{
+ author_id: [13, "Someone"],
+ body: "<p>test</p>",
+ id: 100,
+ model: 'mail.channel',
+ moderation_status: 'pending_moderation',
+ need_moderation: true,
+ res_id: 20,
+ }];
+
+ await this.start();
+
+ const channel = document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.models['mail.thread'].find(thread =>
+ thread.id === 20 &&
+ thread.model === 'mail.channel'
+ ).localId
+ }"]
+ `);
+ assert.ok(
+ channel,
+ "should display the general channel"
+ );
+
+ await afterNextRender(() => channel.click());
+ const messagePending = document.querySelector(`
+ .o_Message[data-message-local-id="${
+ this.env.models['mail.message'].find(message => message.id === 100).localId
+ }"]
+ .o_Message_moderationPending
+ `);
+ assert.ok(
+ messagePending,
+ "should display the pending message with pending info"
+ );
+ assert.hasClass(
+ messagePending,
+ 'o-author',
+ "the message should be pending moderation as author"
+ );
+
+ // simulate accepted message
+ await afterNextRender(() => {
+ const messageData = {
+ author_id: [13, "Someone"],
+ body: "<p>test</p>",
+ channel_ids: [20],
+ id: 100,
+ model: 'mail.channel',
+ moderation_status: 'accepted',
+ res_id: 20,
+ };
+ const notification = [[false, 'mail.channel', 20], messageData];
+ this.widget.call('bus_service', 'trigger', 'notification', [notification]);
+ });
+
+ // check message is accepted
+ const message = document.querySelector(`
+ .o_Message[data-message-local-id="${
+ this.env.models['mail.message'].find(message => message.id === 100).localId
+ }"]
+ `);
+ assert.ok(
+ message,
+ "should still display the message"
+ );
+ assert.containsNone(
+ message,
+ '.o_Message_moderationPending',
+ "the message should not be in pending moderation anymore"
+ );
+});
+
+QUnit.test('sent message rejected in moderated channel', async function (assert) {
+ assert.expect(4);
+
+ Object.assign(this.data.initMessaging, {
+ channel_slots: {
+ channel_channel: [{
+ id: 20,
+ channel_type: "channel",
+ name: "general",
+ moderation: true,
+ }],
+ },
+ });
+ this.data['mail.message'].records = [{
+ author_id: [13, "Someone"],
+ body: "<p>test</p>",
+ id: 100,
+ model: 'mail.channel',
+ moderation_status: 'pending_moderation',
+ need_moderation: true,
+ res_id: 20,
+ }];
+
+ await this.start();
+
+ const channel = document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.models['mail.thread'].find(thread =>
+ thread.id === 20 &&
+ thread.model === 'mail.channel'
+ ).localId
+ }"]
+ `);
+ assert.ok(
+ channel,
+ "should display the general channel"
+ );
+
+ await afterNextRender(() => channel.click());
+ const messagePending = document.querySelector(`
+ .o_Message[data-message-local-id="${
+ this.env.models['mail.message'].find(message => message.id === 100).localId
+ }"]
+ .o_Message_moderationPending
+ `);
+ assert.ok(
+ messagePending,
+ "should display the pending message with pending info"
+ );
+ assert.hasClass(
+ messagePending,
+ 'o-author',
+ "the message should be pending moderation as author"
+ );
+
+ // simulate reject from moderator
+ await afterNextRender(() => {
+ const notifData = {
+ type: 'deletion',
+ message_ids: [100],
+ };
+ const notification = [[false, 'res.partner', 13], notifData];
+ this.widget.call('bus_service', 'trigger', 'notification', [notification]);
+ });
+ // check no message
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "message should be removed from channel after reject"
+ );
+});
+
+QUnit.test('as moderator, pending moderation message accessibility', async function (assert) {
+ // pending moderation message should appear in moderation box and in origin thread
+ assert.expect(3);
+
+ Object.assign(this.data.initMessaging, {
+ channel_slots: {
+ channel_channel: [{
+ id: 20,
+ channel_type: "channel",
+ name: "general",
+ moderation: true,
+ }],
+ },
+ is_moderator: true,
+ moderation_counter: 1,
+ moderation_channel_ids: [20],
+ });
+ this.data['mail.message'].records = [{
+ author_id: [2, "Someone"],
+ body: "<p>test</p>",
+ channel_ids: [20],
+ id: 100,
+ model: 'mail.channel',
+ moderation_status: 'pending_moderation',
+ need_moderation: true,
+ res_id: 20,
+ }];
+
+ await this.start();
+
+ const thread = this.env.models['mail.thread'].find(thread => thread.id === 20 && thread.model === 'mail.channel');
+ assert.ok(
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.messaging.moderation.localId
+ }"]
+ `),
+ "should display the moderation box in the sidebar"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${thread.localId}"]
+ `).click()
+ );
+ const message = this.env.models['mail.message'].find(message => message.id === 100);
+ assert.containsOnce(
+ document.body,
+ `.o_Message[data-message-local-id="${message.localId}"]`,
+ "the pending moderation message should be in the channel"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.messaging.moderation.localId
+ }"]
+ `).click()
+ );
+ assert.containsOnce(
+ document.body,
+ `.o_Message[data-message-local-id="${message.localId}"]`,
+ "the pending moderation message should be in moderation box"
+ );
+});
+
+QUnit.test('as author, pending moderation message should appear in origin thread', async function (assert) {
+ assert.expect(1);
+
+ Object.assign(this.data.initMessaging, {
+ channel_slots: {
+ channel_channel: [{
+ id: 20,
+ channel_type: "channel",
+ name: "general",
+ moderation: true,
+ }],
+ },
+ });
+ await this.start();
+ const thread = this.env.models['mail.thread'].find(thread => thread.id === 20 && thread.model === 'mail.channel');
+ this.data['mail.message'].records = [{
+ author_id: [this.env.session.partner_id, "Me"],
+ body: "<p>test</p>",
+ channel_ids: [], // server do NOT return channel_id of the message
+ id: 100,
+ model: 'mail.channel',
+ moderation_status: 'pending_moderation',
+ need_moderation: true,
+ res_id: 20,
+ }];
+
+ await afterNextRender(() =>
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${thread.localId}"]
+ `).click()
+ );
+ const message = this.env.models['mail.message'].find(message => message.id === 100);
+ assert.containsOnce(
+ document.body,
+ `.o_Message[data-message-local-id="${message.localId}"]`,
+ "the pending moderation message should be in the channel"
+ );
+});
+
+QUnit.test('new pending moderation message posted by me', async function (assert) {
+ // message should appear in origin thread
+ assert.expect(1);
+
+ const self = this;
+ Object.assign(this.data.initMessaging, {
+ channel_slots: {
+ channel_channel: [{
+ id: 20,
+ channel_type: "channel",
+ name: "general",
+ moderation: true,
+ }],
+ },
+ });
+ await this.start({
+ async mockRPC(route, args) {
+ if (args.method === 'message_post') {
+ // simulate receiving the message
+ const message = {
+ author_id: [self.env.session.partner_id, "Me"],
+ body: args.kwargs.body,
+ channel_ids: [], // server do NOT return channel_id of the message if pending moderation
+ moderation_status: 'pending_moderation',
+ id: 2,
+ message_type: args.kwargs.message_type,
+ model: 'mail.channel',
+ subtype_xmlid: args.kwargs.subtype_xmlid,
+ record_name: 'General',
+ res_id: 20,
+ };
+ const notifications = [[
+ ['my-db', 'res.partner', self.env.session.partner_id],
+ { type: 'author', message },
+ ]];
+ self.widget.call('bus_service', 'trigger', 'notification', notifications);
+ }
+ return this._super(...arguments);
+ }
+ });
+ const thread = this.env.models['mail.thread'].find(thread => thread.id === 20 && thread.model === 'mail.channel');
+ await afterNextRender(() =>
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${thread.localId}"]
+ `).click()
+ );
+
+ await afterNextRender(() => {
+ document.querySelector('.o_ComposerTextInput').focus();
+ document.execCommand('insertText', false, "Moderated test");
+ });
+ await afterNextRender(() =>
+ document.querySelector('.o_Composer_buttonSend').click()
+ );
+ const message = this.env.models['mail.message'].find(message => message.id === 2);
+ assert.containsOnce(
+ document.body,
+ `.o_Message[data-message-local-id="${message.localId}"]`,
+ "the pending moderation message should be in the channel"
+ );
+});
+
+QUnit.test('new pending moderation message posted by someone else when moderator', async function (assert) {
+ // the message should appear in origin thread and moderation box if I moderate it
+ assert.expect(2);
+
+ Object.assign(this.data.initMessaging, {
+ channel_slots: {
+ channel_channel: [{
+ id: 20,
+ channel_type: "channel",
+ name: "general",
+ moderation: true,
+ }],
+ },
+ is_moderator: true,
+ moderation_channel_ids: [20],
+ });
+ await this.start();
+ const thread = this.env.models['mail.thread'].find(thread => thread.id === 20 && thread.model === 'mail.channel');
+ await afterNextRender(() =>
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${thread.localId}"]
+ `).click()
+ );
+
+ // simulate receiving the message
+ const messageData = {
+ author_id: [10, "Don Diego de la Vega"],
+ body: "hoy !",
+ channel_ids: [], // server do NOT return channel_id of the message if pending moderation
+ moderation_status: 'pending_moderation',
+ id: 1,
+ model: 'mail.channel',
+ record_name: 'General',
+ res_id: 20,
+ };
+ await afterNextRender(() => {
+ const notifications = [[
+ ['my-db', 'res.partner', this.env.session.partner_id],
+ { type: 'moderator', message: messageData },
+ ]];
+ this.widget.call('bus_service', 'trigger', 'notification', notifications);
+ });
+ const message = this.env.models['mail.message'].find(message => message.id === 1);
+ assert.containsOnce(
+ document.body,
+ `.o_Message[data-message-local-id="${message.localId}"]`,
+ "the pending moderation message should be in the channel"
+ );
+
+ await afterNextRender(() =>
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${
+ this.env.messaging.moderation.localId
+ }"]
+ `).click()
+ );
+ assert.containsOnce(
+ document.body,
+ `.o_Message[data-message-local-id="${message.localId}"]`,
+ "the pending moderation message should be in moderation box"
+ );
+});
+
+});
+});
+});
+
+});
diff --git a/addons/mail/static/src/components/discuss/discuss_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_tests.js
index c2021b18c29..994d75a8935 100644
--- a/addons/mail/static/src/components/discuss/discuss_tests.js
+++ b/addons/mail/static/src/components/discuss/tests/discuss_tests.js
@@ -1,4 +1,4 @@
-odoo.define('mail/static/src/components/discuss/discuss_tests.js', function (require) {
+odoo.define('mail/static/src/components/discuss/tests/discuss_tests.js', function (require) {
'use strict';
const BusService = require('bus.BusService');
@@ -35,10 +35,10 @@ QUnit.module('discuss_tests.js', {
};
},
afterEach() {
- utilsAfterEach(this);
if (this.widget) {
this.widget.destroy();
}
+ utilsAfterEach(this);
},
});
@@ -4566,956 +4566,6 @@ QUnit.test('auto-focus composer on opening thread', async function (assert) {
);
});
-QUnit.test('moderation: moderated channel with pending moderation message', async function (assert) {
- assert.expect(37);
-
- Object.assign(this.data.initMessaging, {
- channel_slots: {
- channel_channel: [{
- id: 20,
- channel_type: "channel",
- name: "general",
- moderation: true,
- }],
- },
- is_moderator: true,
- moderation_counter: 1,
- moderation_channel_ids: [20],
- });
- this.data['mail.message'].records = [{
- author_id: [2, "Someone"],
- body: "<p>test</p>",
- channel_ids: [20],
- id: 100,
- model: 'mail.channel',
- moderation_status: 'pending_moderation',
- need_moderation: true,
- res_id: 20,
- }];
-
- await this.start();
-
- assert.ok(
- document.querySelector(`
- .o_DiscussSidebar_item[data-thread-local-id="${
- this.env.messaging.moderation.localId
- }"]
- `),
- "should display the moderation box"
- );
- const mailboxCounter = document.querySelector(`
- .o_DiscussSidebar_item[data-thread-local-id="${
- this.env.messaging.moderation.localId
- }"]
- .o_DiscussSidebarItem_counter
- `);
- assert.ok(
- mailboxCounter,
- "there should be a counter next to the moderation mailbox in the sidebar"
- );
- assert.strictEqual(
- mailboxCounter.textContent.trim(),
- "1",
- "the mailbox counter of the moderation mailbox should display '1'"
- );
-
- // 1. go to moderation mailbox
- await afterNextRender(() =>
- document.querySelector(`
- .o_DiscussSidebar_item[data-thread-local-id="${
- this.env.messaging.moderation.localId
- }"]
- `).click()
- );
- // check message
- assert.containsOnce(
- document.body,
- '.o_Message',
- "should be only one message in moderation box"
- );
- assert.strictEqual(
- document.querySelector('.o_Message_content').textContent,
- "test",
- "this message pending moderation should have the correct content"
- );
- assert.containsOnce(
- document.body,
- '.o_Message_originThreadLink',
- "thee message should have one origin"
- );
- assert.strictEqual(
- document.querySelector('.o_Message_originThreadLink').textContent,
- "#general",
- "the message pending moderation should have correct origin as its linked document"
- );
- assert.containsOnce(
- document.body,
- '.o_Message_checkbox',
- "there should be a moderation checkbox next to the message"
- );
- assert.notOk(
- document.querySelector('.o_Message_checkbox').checked,
- "the moderation checkbox should be unchecked by default"
- );
- // check select all (enabled) / unselect all (disabled) buttons
- assert.containsOnce(
- document.body,
- '.o_widget_Discuss_controlPanelButtonSelectAll',
- "there should be a 'Select All' button in the control panel"
- );
- assert.doesNotHaveClass(
- document.querySelector('.o_widget_Discuss_controlPanelButtonSelectAll'),
- 'disabled',
- "the 'Select All' button should not be disabled"
- );
- assert.containsOnce(
- document.body,
- '.o_widget_Discuss_controlPanelButtonUnselectAll',
- "there should be a 'Unselect All' button in the control panel"
- );
- assert.hasClass(
- document.querySelector('.o_widget_Discuss_controlPanelButtonUnselectAll'),
- 'disabled',
- "the 'Unselect All' button should be disabled"
- );
- // check moderate all buttons (invisible)
- assert.containsN(
- document.body,
- '.o_widget_Discuss_controlPanelButtonModeration',
- 3,
- "there should be 3 buttons to moderate selected messages in the control panel"
- );
- assert.containsOnce(
- document.body,
- '.o_widget_Discuss_controlPanelButtonModeration.o-accept',
- "there should one moderate button to accept messages pending moderation"
- );
- assert.isNotVisible(
- document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept'),
- "the moderate button 'Accept' should be invisible by default"
- );
- assert.containsOnce(
- document.body,
- '.o_widget_Discuss_controlPanelButtonModeration.o-reject',
- "there should one moderate button to reject messages pending moderation"
- );
- assert.isNotVisible(
- document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-reject'),
- "the moderate button 'Reject' should be invisible by default"
- );
- assert.containsOnce(
- document.body,
- '.o_widget_Discuss_controlPanelButtonModeration.o-discard',
- "there should one moderate button to discard messages pending moderation"
- );
- assert.isNotVisible(
- document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-discard'),
- "the moderate button 'Discard' should be invisible by default"
- );
-
- // click on message moderation checkbox
- await afterNextRender(() => document.querySelector('.o_Message_checkbox').click());
- assert.ok(
- document.querySelector('.o_Message_checkbox').checked,
- "the moderation checkbox should become checked after click"
- );
- // check select all (disabled) / unselect all buttons (enabled)
- assert.hasClass(
- document.querySelector('.o_widget_Discuss_controlPanelButtonSelectAll'),
- 'disabled',
- "the 'Select All' button should be disabled"
- );
- assert.doesNotHaveClass(
- document.querySelector('.o_widget_Discuss_controlPanelButtonUnselectAll'),
- 'disabled',
- "the 'Unselect All' button should not be disabled"
- );
- // check moderate all buttons updated (visible)
- assert.isVisible(
- document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept'),
- "the moderate button 'Accept' should be visible"
- );
- assert.isVisible(
- document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-reject'),
- "the moderate button 'Reject' should be visible"
- );
- assert.isVisible(
- document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-discard'),
- "the moderate button 'Discard' should be visible"
- );
-
- // test select buttons
- await afterNextRender(() =>
- document.querySelector('.o_widget_Discuss_controlPanelButtonUnselectAll').click()
- );
- assert.notOk(
- document.querySelector('.o_Message_checkbox').checked,
- "the moderation checkbox should become unchecked after click"
- );
-
- await afterNextRender(() =>
- document.querySelector('.o_widget_Discuss_controlPanelButtonSelectAll').click()
- );
- assert.ok(
- document.querySelector('.o_Message_checkbox').checked,
- "the moderation checkbox should become checked again after click"
- );
-
- // 2. go to channel 'general'
- await afterNextRender(() =>
- document.querySelector(`
- .o_DiscussSidebar_item[data-thread-local-id="${
- this.env.models['mail.thread'].find(thread =>
- thread.id === 20 &&
- thread.model === 'mail.channel'
- ).localId
- }"]
- `).click()
- );
- // check correct message
- assert.containsOnce(
- document.body,
- '.o_Message',
- "should be only one message in general channel"
- );
- assert.containsOnce(
- document.body,
- '.o_Message_checkbox',
- "there should be a moderation checkbox next to the message"
- );
- assert.notOk(
- document.querySelector('.o_Message_checkbox').checked,
- "the moderation checkbox should not be checked here"
- );
- await afterNextRender(() => document.querySelector('.o_Message_checkbox').click());
- // Don't test moderation actions visibility, since it is similar to moderation box.
-
- // 3. test discard button
- await afterNextRender(() =>
- document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-discard').click()
- );
- assert.containsOnce(
- document.body,
- '.o_ModerationDiscardDialog',
- "discard dialog should be open"
- );
- // the dialog will be tested separately
- await afterNextRender(() =>
- document.querySelector('.o_ModerationDiscardDialog .o-cancel').click()
- );
- assert.containsNone(
- document.body,
- '.o_ModerationDiscardDialog',
- "discard dialog should be closed"
- );
-
- // 4. test reject button
- await afterNextRender(() =>
- document.querySelector(`
- .o_widget_Discuss_controlPanelButtonModeration.o-reject
- `).click()
- );
- assert.containsOnce(
- document.body,
- '.o_ModerationRejectDialog',
- "reject dialog should be open"
- );
- // the dialog will be tested separately
- await afterNextRender(() =>
- document.querySelector('.o_ModerationRejectDialog .o-cancel').click()
- );
- assert.containsNone(
- document.body,
- '.o_ModerationRejectDialog',
- "reject dialog should be closed"
- );
-
- // 5. test accept button
- await afterNextRender(() =>
- document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept').click()
- );
- assert.containsOnce(
- document.body,
- '.o_Message',
- "should still be only one message in general channel"
- );
- assert.containsNone(
- document.body,
- '.o_Message_checkbox',
- "there should not be a moderation checkbox next to the message"
- );
-});
-
-QUnit.test('moderation: accept pending moderation message', async function (assert) {
- assert.expect(12);
-
- Object.assign(this.data.initMessaging, {
- channel_slots: {
- channel_channel: [{
- id: 20,
- channel_type: "channel",
- name: "general",
- moderation: true,
- }],
- },
- is_moderator: true,
- moderation_counter: 1,
- moderation_channel_ids: [20],
- });
- this.data['mail.message'].records = [{
- author_id: [2, "Someone"],
- body: "<p>test</p>",
- id: 100,
- model: 'mail.channel',
- moderation_status: 'pending_moderation',
- need_moderation: true,
- res_id: 20,
- }];
-
- await this.start({
- async mockRPC(route, args) {
- if (args.method === 'moderate') {
- assert.step('moderate');
- const messageIDs = args.args[0];
- const decision = args.args[1];
- assert.strictEqual(
- messageIDs.length,
- 1,
- "should moderate one message"
- );
- assert.strictEqual(
- messageIDs[0],
- 100,
- "should moderate message with ID 100"
- );
- assert.strictEqual(
- decision,
- 'accept',
- "should accept the message"
- );
- }
- return this._super(...arguments);
- },
- });
-
- // 1. go to moderation box
- const moderationBox = document.querySelector(`
- .o_DiscussSidebar_item[data-thread-local-id="${
- this.env.messaging.moderation.localId
- }"]
- `);
- assert.ok(
- moderationBox,
- "should display the moderation box"
- );
-
- await afterNextRender(() => moderationBox.click());
- assert.ok(
- document.querySelector(`
- .o_Message[data-message-local-id="${
- this.env.models['mail.message'].find(message => message.id === 100).localId
- }"]
- `),
- "should display the message to moderate"
- );
- const acceptButton = document.querySelector(`
- .o_Message[data-message-local-id="${
- this.env.models['mail.message'].find(message => message.id === 100).localId
- }"]
- .o_Message_moderationAction.o-accept
- `);
- assert.ok(acceptButton, "should display the accept button");
-
- await afterNextRender(() => acceptButton.click());
- assert.verifySteps(['moderate']);
- assert.containsOnce(
- document.body,
- '.o_MessageList_emptyTitle',
- "should now have no message displayed in moderation box"
- );
-
- // 2. go to channel 'general'
- const channel = document.querySelector(`
- .o_DiscussSidebar_item[data-thread-local-id="${
- this.env.models['mail.thread'].find(thread =>
- thread.id === 20 &&
- thread.model === 'mail.channel'
- ).localId
- }"]
- `);
- assert.ok(
- channel,
- "should display the general channel"
- );
-
- await afterNextRender(() => channel.click());
- const message = document.querySelector(`
- .o_Message[data-message-local-id="${
- this.env.models['mail.message'].find(message => message.id === 100).localId
- }"]
- `);
- assert.ok(
- message,
- "should display the accepted message"
- );
- assert.containsNone(
- message,
- '.o_Message_moderationPending',
- "the message should not be pending moderation"
- );
-});
-
-QUnit.test('moderation: reject pending moderation message (reject with explanation)', async function (assert) {
- assert.expect(23);
-
- Object.assign(this.data.initMessaging, {
- channel_slots: {
- channel_channel: [{
- id: 20,
- channel_type: "channel",
- name: "general",
- moderation: true,
- }],
- },
- is_moderator: true,
- moderation_counter: 1,
- moderation_channel_ids: [20],
- });
- this.data['mail.message'].records = [{
- author_id: [2, "Someone"],
- body: "<p>test</p>",
- id: 100,
- model: 'mail.channel',
- moderation_status: 'pending_moderation',
- need_moderation: true,
- res_id: 20,
- }];
-
- await this.start({
- async mockRPC(route, args) {
- if (args.method === 'moderate') {
- assert.step('moderate');
- const messageIDs = args.args[0];
- const decision = args.args[1];
- const kwargs = args.kwargs;
- assert.strictEqual(
- messageIDs.length,
- 1,
- "should moderate one message"
- );
- assert.strictEqual(
- messageIDs[0],
- 100,
- "should moderate message with ID 100"
- );
- assert.strictEqual(
- decision,
- 'reject',
- "should reject the message"
- );
- assert.strictEqual(
- kwargs.title,
- "Message Rejected",
- "should have correct reject message title"
- );
- assert.strictEqual(
- kwargs.comment,
- "Your message was rejected by moderator.",
- "should have correct reject message body / comment"
- );
- }
- return this._super(...arguments);
- },
- });
-
- // 1. go to moderation box
- const moderationBox = document.querySelector(`
- .o_DiscussSidebar_item[data-thread-local-id="${
- this.env.messaging.moderation.localId
- }"]
- `);
- assert.ok(
- moderationBox,
- "should display the moderation box"
- );
-
- await afterNextRender(() => moderationBox.click());
- const pendingMessage = document.querySelector(`
- .o_Message[data-message-local-id="${
- this.env.models['mail.message'].find(message => message.id === 100).localId
- }"]
- `);
- assert.ok(
- pendingMessage,
- "should display the message to moderate"
- );
- const rejectButton = pendingMessage.querySelector(':scope .o_Message_moderationAction.o-reject');
- assert.ok(
- rejectButton,
- "should display the reject button"
- );
-
- await afterNextRender(() => rejectButton.click());
- const dialog = document.querySelector('.o_ModerationRejectDialog');
- assert.ok(
- dialog,
- "a dialog should be prompt to the moderator on click reject"
- );
- assert.strictEqual(
- dialog.querySelector('.modal-title').textContent,
- // TODO FIXME, this should be a proper title "Send explanation to author"
- // see https://github.com/odoo/owl/issues/670
- "[object Object]",
- "dialog should have correct title"
- );
-
- const messageTitle = dialog.querySelector(':scope .o_ModerationRejectDialog_title');
- assert.ok(
- messageTitle,
- "should have a title for rejecting"
- );
- assert.hasAttrValue(
- messageTitle,
- 'placeholder',
- "Subject",
- "title for reject reason should have correct placeholder"
- );
- assert.strictEqual(
- messageTitle.value,
- "Message Rejected",
- "title for reject reason should have correct default value"
- );
-
- const messageComment = dialog.querySelector(':scope .o_ModerationRejectDialog_comment');
- assert.ok(
- messageComment,
- "should have a comment for rejecting"
- );
- assert.hasAttrValue(
- messageComment,
- 'placeholder',
- "Mail Body",
- "comment for reject reason should have correct placeholder"
- );
- assert.strictEqual(
- messageComment.value,
- "Your message was rejected by moderator.",
- "comment for reject reason should have correct default text content"
- );
- const confirmReject = dialog.querySelector(':scope .o-reject');
- assert.ok(
- confirmReject,
- "should have reject button"
- );
- assert.strictEqual(
- confirmReject.textContent,
- "Reject"
- );
-
- await afterNextRender(() => confirmReject.click());
- assert.verifySteps(['moderate']);
- assert.containsOnce(
- document.body,
- '.o_MessageList_emptyTitle',
- "should now have no message displayed in moderation box"
- );
-
- // 2. go to channel 'general'
- const channel = document.querySelector(`
- .o_DiscussSidebar_item[data-thread-local-id="${
- this.env.models['mail.thread'].find(thread =>
- thread.id === 20 &&
- thread.model === 'mail.channel'
- ).localId
- }"]
- `);
- assert.ok(
- channel,
- 'should display the general channel'
- );
-
- await afterNextRender(() => channel.click());
- assert.containsNone(
- document.body,
- '.o_Message',
- "should now have no message in channel"
- );
-});
-
-QUnit.test('moderation: discard pending moderation message (reject without explanation)', async function (assert) {
- assert.expect(16);
-
- Object.assign(this.data.initMessaging, {
- channel_slots: {
- channel_channel: [{
- id: 20,
- channel_type: "channel",
- name: "general",
- moderation: true,
- }],
- },
- is_moderator: true,
- moderation_counter: 1,
- moderation_channel_ids: [20],
- });
- this.data['mail.message'].records = [{
- author_id: [2, "Someone"],
- body: "<p>test</p>",
- id: 100,
- model: 'mail.channel',
- moderation_status: 'pending_moderation',
- need_moderation: true,
- res_id: 20,
- }];
-
- await this.start({
- async mockRPC(route, args) {
- if (args.method === 'moderate') {
- assert.step('moderate');
- const messageIDs = args.args[0];
- const decision = args.args[1];
- assert.strictEqual(messageIDs.length, 1, "should moderate one message");
- assert.strictEqual(messageIDs[0], 100, "should moderate message with ID 100");
- assert.strictEqual(decision, 'discard', "should discard the message");
- }
- return this._super(...arguments);
- },
- });
-
- // 1. go to moderation box
- const moderationBox = document.querySelector(`
- .o_DiscussSidebar_item[data-thread-local-id="${
- this.env.messaging.moderation.localId
- }"]
- `);
- assert.ok(
- moderationBox,
- "should display the moderation box"
- );
-
- await afterNextRender(() => moderationBox.click());
- const pendingMessage = document.querySelector(`
- .o_Message[data-message-local-id="${
- this.env.models['mail.message'].find(message => message.id === 100).localId
- }"]
- `);
- assert.ok(
- pendingMessage,
- "should display the message to moderate"
- );
-
- const discardButton = pendingMessage.querySelector(`
- :scope .o_Message_moderationAction.o-discard
- `);
- assert.ok(
- discardButton,
- "should display the discard button"
- );
-
- await afterNextRender(() => discardButton.click());
- const dialog = document.querySelector('.o_ModerationDiscardDialog');
- assert.ok(
- dialog,
- "a dialog should be prompt to the moderator on click discard"
- );
- assert.strictEqual(
- dialog.querySelector('.modal-title').textContent,
- // TODO FIXME, this should be a proper title "Confirmation"
- // see https://github.com/odoo/owl/issues/670
- "[object Object]",
- "dialog should have correct title"
- );
- assert.strictEqual(
- dialog.textContent,
- "[object Object]×You are going to discard 1 message.Do you confirm the action?DiscardCancel",
- "should warn the user on discard action"
- );
-
- const confirmDiscard = dialog.querySelector(':scope .o-discard');
- assert.ok(
- confirmDiscard,
- "should have discard button"
- );
- assert.strictEqual(
- confirmDiscard.textContent,
- "Discard"
- );
-
- await afterNextRender(() => confirmDiscard.click());
- assert.verifySteps(['moderate']);
- assert.containsOnce(
- document.body,
- '.o_MessageList_emptyTitle',
- "should now have no message displayed in moderation box"
- );
-
- // 2. go to channel 'general'
- const channel = document.querySelector(`
- .o_DiscussSidebar_item[data-thread-local-id="${
- this.env.models['mail.thread'].find(thread =>
- thread.id === 20 &&
- thread.model === 'mail.channel'
- ).localId
- }"]
- `);
- assert.ok(
- channel,
- "should display the general channel"
- );
-
- await afterNextRender(() => channel.click());
- assert.containsNone(
- document.body,
- '.o_Message',
- "should now have no message in channel"
- );
-});
-
-QUnit.test('moderation: send message in moderated channel', async function (assert) {
- assert.expect(4);
-
- const self = this;
- Object.assign(this.data.initMessaging, {
- channel_slots: {
- channel_channel: [{
- id: 20,
- channel_type: "channel",
- name: "general",
- moderation: true,
- }],
- },
- });
-
- await this.start({
- async mockRPC(route, args) {
- if (args.method === 'message_post') {
- const message = {
- id: 100,
- author_id: [13, 'Someone'],
- body: args.kwargs.body,
- channel_ids: [20],
- message_type: args.kwargs.message_type,
- model: 'mail.channel',
- moderation_status: 'pending_moderation',
- res_id: 20,
- };
- const notificationData = {
- type: 'author',
- message: message,
- };
- const notification = [[false, 'res.partner', 13], notificationData];
- self.widget.call('bus_service', 'trigger', 'notification', [notification]);
-
- return message.id;
- }
- return this._super(...arguments);
- },
- });
-
- // go to channel 'general'
- const channel = document.querySelector(`
- .o_DiscussSidebar_item[data-thread-local-id="${
- this.env.models['mail.thread'].find(thread =>
- thread.id === 20 &&
- thread.model === 'mail.channel'
- ).localId
- }"]
- `);
- assert.ok(
- channel,
- "should display the general channel"
- );
-
- await afterNextRender(() => channel.click());
- assert.containsNone(
- document.body,
- '.o_Message',
- "should have no message in channel"
- );
-
- // post a message
- await afterNextRender(() => {
- const textInput = document.querySelector('.o_ComposerTextInput_textarea');
- textInput.focus();
- document.execCommand('insertText', false, "Some Text");
- });
- await afterNextRender(() => document.querySelector('.o_Composer_buttonSend').click());
- const messagePending = document.querySelector(`
- .o_Message[data-message-local-id="${
- this.env.models['mail.message'].find(message => message.id === 100).localId
- }"]
- .o_Message_moderationPending
- `);
- assert.ok(
- messagePending,
- "should display the pending message with pending info"
- );
- assert.hasClass(
- messagePending,
- 'o-author',
- "the message should be pending moderation as author"
- );
-});
-
-QUnit.test('moderation: sent message accepted in moderated channel', async function (assert) {
- assert.expect(5);
-
- Object.assign(this.data.initMessaging, {
- channel_slots: {
- channel_channel: [{
- id: 20,
- channel_type: "channel",
- name: "general",
- moderation: true,
- }],
- },
- });
- this.data['mail.message'].records = [{
- author_id: [13, "Someone"],
- body: "<p>test</p>",
- id: 100,
- model: 'mail.channel',
- moderation_status: 'pending_moderation',
- need_moderation: true,
- res_id: 20,
- }];
-
- await this.start();
-
- const channel = document.querySelector(`
- .o_DiscussSidebar_item[data-thread-local-id="${
- this.env.models['mail.thread'].find(thread =>
- thread.id === 20 &&
- thread.model === 'mail.channel'
- ).localId
- }"]
- `);
- assert.ok(
- channel,
- "should display the general channel"
- );
-
- await afterNextRender(() => channel.click());
- const messagePending = document.querySelector(`
- .o_Message[data-message-local-id="${
- this.env.models['mail.message'].find(message => message.id === 100).localId
- }"]
- .o_Message_moderationPending
- `);
- assert.ok(
- messagePending,
- "should display the pending message with pending info"
- );
- assert.hasClass(
- messagePending,
- 'o-author',
- "the message should be pending moderation as author"
- );
-
- // simulate accepted message
- await afterNextRender(() => {
- const messageData = {
- author_id: [13, "Someone"],
- body: "<p>test</p>",
- channel_ids: [20],
- id: 100,
- model: 'mail.channel',
- moderation_status: 'accepted',
- res_id: 20,
- };
- const notification = [[false, 'mail.channel', 20], messageData];
- this.widget.call('bus_service', 'trigger', 'notification', [notification]);
- });
-
- // check message is accepted
- const message = document.querySelector(`
- .o_Message[data-message-local-id="${
- this.env.models['mail.message'].find(message => message.id === 100).localId
- }"]
- `);
- assert.ok(
- message,
- "should still display the message"
- );
- assert.containsNone(
- message,
- '.o_Message_moderationPending',
- "the message should not be in pending moderation anymore"
- );
-});
-
-QUnit.test('moderation: sent message rejected in moderated channel', async function (assert) {
- assert.expect(4);
-
- Object.assign(this.data.initMessaging, {
- channel_slots: {
- channel_channel: [{
- id: 20,
- channel_type: "channel",
- name: "general",
- moderation: true,
- }],
- },
- });
- this.data['mail.message'].records = [{
- author_id: [13, "Someone"],
- body: "<p>test</p>",
- id: 100,
- model: 'mail.channel',
- moderation_status: 'pending_moderation',
- need_moderation: true,
- res_id: 20,
- }];
-
- await this.start();
-
- const channel = document.querySelector(`
- .o_DiscussSidebar_item[data-thread-local-id="${
- this.env.models['mail.thread'].find(thread =>
- thread.id === 20 &&
- thread.model === 'mail.channel'
- ).localId
- }"]
- `);
- assert.ok(
- channel,
- "should display the general channel"
- );
-
- await afterNextRender(() => channel.click());
- const messagePending = document.querySelector(`
- .o_Message[data-message-local-id="${
- this.env.models['mail.message'].find(message => message.id === 100).localId
- }"]
- .o_Message_moderationPending
- `);
- assert.ok(
- messagePending,
- "should display the pending message with pending info"
- );
- assert.hasClass(
- messagePending,
- 'o-author',
- "the message should be pending moderation as author"
- );
-
- // simulate reject from moderator
- await afterNextRender(() => {
- const notifData = {
- type: 'deletion',
- message_ids: [100],
- };
- const notification = [[false, 'res.partner', 13], notifData];
- this.widget.call('bus_service', 'trigger', 'notification', [notification]);
- });
- // check no message
- assert.containsNone(
- document.body,
- '.o_Message',
- "message should be removed from channel after reject"
- );
-});
-
});
});
});
diff --git a/addons/mail/views/assets.xml b/addons/mail/views/assets.xml
index 027b4a5acda..6b4623a5416 100644
--- a/addons/mail/views/assets.xml
+++ b/addons/mail/views/assets.xml
@@ -244,7 +244,8 @@
<script type="text/javascript" src="/mail/static/src/components/chatter/chatter_tests.js"></script>
<script type="text/javascript" src="/mail/static/src/components/chatter_topbar/chatter_topbar_tests.js"></script>
<script type="text/javascript" src="/mail/static/src/components/composer/composer_tests.js"></script>
- <script type="text/javascript" src="/mail/static/src/components/discuss/discuss_tests.js"></script>
+ <script type="text/javascript" src="/mail/static/src/components/discuss/tests/discuss_moderation_tests.js"></script>
+ <script type="text/javascript" src="/mail/static/src/components/discuss/tests/discuss_tests.js"></script>
<script type="text/javascript" src="/mail/static/src/components/file_uploader/file_uploader_tests.js"></script>
<script type="text/javascript" src="/mail/static/src/components/follow_button/follow_button_tests.js"></script>
<script type="text/javascript" src="/mail/static/src/components/follower/follower_tests.js"></script>