diff options
| author | Sébastien Theys <seb@odoo.com> | 2020-10-09 10:55:21 +0000 |
|---|---|---|
| committer | Sébastien Theys <seb@odoo.com> | 2020-10-09 12:04:01 +0000 |
| commit | aa006658ea25abaa2b8d03003f1d4f9214fb6468 (patch) | |
| tree | 06ba9046da4f2c4d6382c88a3075f3ec912b7ff9 /addons/mail/static/src/components | |
| parent | de62d7bae2c466b74b0d9a83398c5f031233afdb (diff) | |
[FIX] mail: fix discuss search filters and related issues
- don't reset `stringifiedDomain` when changing thread (this led to the
control panel displaying obsolete filters)
- refresh non-main cache that are currently displayed when there is a known
change of messages in the same thread's main cache
- refresh non-main-cache on displaying them, to fetch potentially new messages
- fix discuss "select all" conditions
task-2171873
task-2282374
closes odoo/odoo#59631
X-original-commit: b89dc5f949b3ad062fc4197a4152066c207e1bf9
Related: odoo/enterprise#13958
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
Diffstat (limited to 'addons/mail/static/src/components')
3 files changed, 420 insertions, 6 deletions
diff --git a/addons/mail/static/src/components/discuss/tests/discuss_domain_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_domain_tests.js new file mode 100644 index 00000000000..21146652d28 --- /dev/null +++ b/addons/mail/static/src/components/discuss/tests/discuss_domain_tests.js @@ -0,0 +1,407 @@ +odoo.define('mail/static/src/components/discuss/tests/discuss_domain_tests.js', function (require) { +'use strict'; + +const { + afterEach, + afterNextRender, + beforeEach, + start, +} = require('mail/static/src/utils/test_utils.js'); + +QUnit.module('mail', {}, function () { +QUnit.module('components', {}, function () { +QUnit.module('discuss', {}, function () { +QUnit.module('discuss_domain_tests.js', { + beforeEach() { + beforeEach(this); + + this.start = async params => { + const { afterEvent, env, widget } = await start(Object.assign({}, params, { + autoOpenDiscuss: true, + data: this.data, + hasDiscuss: true, + })); + this.afterEvent = afterEvent; + this.env = env; + this.widget = widget; + }; + }, + afterEach() { + afterEach(this); + }, +}); + +QUnit.test('discuss should filter messages based on given domain', async function (assert) { + assert.expect(2); + + this.data['mail.message'].records.push({ + body: "test", + needaction: true, + needaction_partner_ids: [this.data.currentPartnerId], + }, { + body: "not empty", + needaction: true, + needaction_partner_ids: [this.data.currentPartnerId], + }); + await this.start(); + assert.containsN( + document.body, + '.o_Message', + 2, + "should have 2 messages in Inbox initially" + ); + + await this.afterEvent({ + eventName: 'o-thread-view-hint-processed', + func: () => { + // simulate control panel search + this.env.messaging.discuss.update({ + stringifiedDomain: JSON.stringify([['body', 'ilike', 'test']]), + }); + }, + message: "should wait until search filter is applied", + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'mail.box' && + threadViewer.thread.id === 'inbox' + ); + }, + }); + assert.containsOnce( + document.body, + '.o_Message', + "should only have the 1 message containing 'test' remaining after doing a search" + ); +}); + +QUnit.test('discuss should keep filter domain on changing thread', async function (assert) { + assert.expect(3); + + this.data['mail.channel'].records.push({ id: 20 }); + this.data['mail.message'].records.push({ + body: "test", + channel_ids: [20], + }, { + body: "not empty", + channel_ids: [20], + }); + await this.start(); + const channel = this.env.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }); + assert.containsNone( + document.body, + '.o_Message', + "should have no message in Inbox initially" + ); + + await this.afterEvent({ + eventName: 'o-thread-view-hint-processed', + func: () => { + // simulate control panel search + this.env.messaging.discuss.update({ + stringifiedDomain: JSON.stringify([['body', 'ilike', 'test']]), + }); + }, + message: "should wait until search filter is applied", + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'mail.box' && + threadViewer.thread.id === 'inbox' + ); + }, + }); + assert.containsNone( + document.body, + '.o_Message', + "should have still no message in Inbox after doing a search" + ); + + await this.afterEvent({ + eventName: 'o-thread-view-hint-processed', + func: () => { + document.querySelector(` + .o_DiscussSidebar_item[data-thread-local-id="${channel.localId}"] + `).click(); + }, + message: "should wait until channel 20 is loaded after clicking on it", + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'mail.channel' && + threadViewer.thread.id === 20 + ); + }, + }); + assert.containsOnce( + document.body, + '.o_Message', + "should only have the 1 message containing 'test' in channel 20 (due to the domain still applied on changing thread)" + ); +}); + +QUnit.test('discuss should refresh filtered thread on receiving new message', async function (assert) { + assert.expect(2); + + this.data['mail.channel'].records.push({ id: 20 }); + await this.start(); + const channel = this.env.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }); + await this.afterEvent({ + eventName: 'o-thread-view-hint-processed', + func: () => { + document.querySelector(` + .o_DiscussSidebar_item[data-thread-local-id="${channel.localId}"] + `).click(); + }, + message: "should wait until channel 20 is loaded after clicking on it", + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'mail.channel' && + threadViewer.thread.id === 20 + ); + }, + }); + await this.afterEvent({ + eventName: 'o-thread-view-hint-processed', + func: () => { + // simulate control panel search + this.env.messaging.discuss.update({ + stringifiedDomain: JSON.stringify([['body', 'ilike', 'test']]), + }); + }, + message: "should wait until search filter is applied", + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'mail.channel' && + threadViewer.thread.id === 20 + ); + }, + }); + assert.containsNone( + document.body, + '.o_Message', + "should have initially no message in channel 20 matching the search 'test'" + ); + + // simulate receiving a message + await this.afterEvent({ + eventName: 'o-thread-view-hint-processed', + func: () => this.env.services.rpc({ + route: '/mail/chat_post', + params: { + uuid: channel.uuid, + message_content: "test", + }, + }), + message: "should wait until channel 20 refreshed its filtered message list", + predicate: data => { + return ( + data.threadViewer.thread.model === 'mail.channel' && + data.threadViewer.thread.id === 20 && + data.hint.type === 'messages-loaded' + ); + }, + }); + assert.containsOnce( + document.body, + '.o_Message', + "should only have the 1 message containing 'test' in channel 20 after just receiving it" + ); +}); + +QUnit.test('discuss should refresh filtered thread on changing thread', async function (assert) { + assert.expect(4); + + this.data['mail.channel'].records.push({ id: 20 }, { id: 21 }); + await this.start(); + const channel20 = this.env.models['mail.thread'].findFromIdentifyingData({ + id: 20, + model: 'mail.channel', + }); + const channel21 = this.env.models['mail.thread'].findFromIdentifyingData({ + id: 21, + model: 'mail.channel', + }); + await this.afterEvent({ + eventName: 'o-thread-view-hint-processed', + func: () => { + document.querySelector(` + .o_DiscussSidebar_item[data-thread-local-id="${channel20.localId}"] + `).click(); + }, + message: "should wait until channel 20 is loaded after clicking on it", + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'mail.channel' && + threadViewer.thread.id === 20 + ); + }, + }); + await this.afterEvent({ + eventName: 'o-thread-view-hint-processed', + func: () => { + // simulate control panel search + this.env.messaging.discuss.update({ + stringifiedDomain: JSON.stringify([['body', 'ilike', 'test']]), + }); + }, + message: "should wait until search filter is applied", + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'mail.channel' && + threadViewer.thread.id === 20 + ); + }, + }); + assert.containsNone( + document.body, + '.o_Message', + "should have initially no message in channel 20 matching the search 'test'" + ); + + await this.afterEvent({ + eventName: 'o-thread-view-hint-processed', + func: () => { + document.querySelector(` + .o_DiscussSidebar_item[data-thread-local-id="${channel21.localId}"] + `).click(); + }, + message: "should wait until channel 21 is loaded after clicking on it", + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'mail.channel' && + threadViewer.thread.id === 21 + ); + }, + }); + assert.containsNone( + document.body, + '.o_Message', + "should have no message in channel 21 matching the search 'test'" + ); + // simulate receiving a message on channel 20 while channel 21 is displayed + await this.env.services.rpc({ + route: '/mail/chat_post', + params: { + uuid: channel20.uuid, + message_content: "test", + }, + }); + assert.containsNone( + document.body, + '.o_Message', + "should still have no message in channel 21 matching the search 'test' after receiving a message on channel 20" + ); + + await this.afterEvent({ + eventName: 'o-thread-view-hint-processed', + func: () => { + document.querySelector(` + .o_DiscussSidebar_item[data-thread-local-id="${channel20.localId}"] + `).click(); + }, + message: "should wait until channel 20 is loaded with the new message after clicking on it", + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'mail.channel' && + threadViewer.thread.id === 20 && + threadViewer.threadCache.fetchedMessages.length === 1 + ); + }, + }); + assert.containsOnce( + document.body, + '.o_Message', + "should now have the 1 message containing 'test' in channel 20 when displaying it, after having received the message while the channel was not visible" + ); +}); + +QUnit.test('select all and unselect all buttons should work on filtered thread', async function (assert) { + assert.expect(4); + + this.data['mail.channel'].records.push({ + id: 20, + is_moderator: true, + moderation: true, + name: "general", + }); + this.data['mail.message'].records.push({ + body: "<p>test</p>", + model: 'mail.channel', + moderation_status: 'pending_moderation', + res_id: 20, + }); + await this.start(); + await this.afterEvent({ + eventName: 'o-thread-view-hint-processed', + func: () => { + document.querySelector(` + .o_DiscussSidebar_item[data-thread-local-id="${this.env.messaging.moderation.localId}"] + `).click(); + }, + message: "should wait until moderation box is loaded after clicking on it", + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'mail.box' && + threadViewer.thread.id === 'moderation' + ); + }, + }); + await this.afterEvent({ + eventName: 'o-thread-view-hint-processed', + func: () => { + // simulate control panel search + this.env.messaging.discuss.update({ + stringifiedDomain: JSON.stringify([['body', 'ilike', 'test']]), + }); + }, + message: "should wait until search filter is applied", + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'mail.box' && + threadViewer.thread.id === 'moderation' + ); + }, + }); + assert.containsOnce( + document.body, + '.o_Message', + "should only have the 1 message containing 'test' in moderation box" + ); + assert.notOk( + document.querySelector('.o_Message_checkbox').checked, + "the moderation checkbox should not be checked initially" + ); + + await afterNextRender(() => document.querySelector('.o_widget_Discuss_controlPanelButtonSelectAll').click()); + assert.ok( + document.querySelector('.o_Message_checkbox').checked, + "the moderation checkbox should be checked after clicking on 'select all'" + ); + + await afterNextRender(() => document.querySelector('.o_widget_Discuss_controlPanelButtonUnselectAll').click()); + assert.notOk( + document.querySelector('.o_Message_checkbox').checked, + "the moderation checkbox should be unchecked after clicking on 'unselect all'" + ); +}); + +}); +}); +}); + +}); diff --git a/addons/mail/static/src/components/message_list/message_list.js b/addons/mail/static/src/components/message_list/message_list.js index 72368ec3689..b7332514141 100644 --- a/addons/mail/static/src/components/message_list/message_list.js +++ b/addons/mail/static/src/components/message_list/message_list.js @@ -124,12 +124,18 @@ class MessageList extends Component { case 'home-menu-shown': this._adjustFromHomeMenuShown(hint); break; + case 'messages-loaded': + this.threadView.markComponentHintProcessed(hint); + break; case 'message-received': this._adjustFromMessageReceived(hint); break; case 'more-messages-loaded': this._adjustFromMoreMessagesLoaded(hint); break; + case 'new-messages-loaded': + this.threadView.markComponentHintProcessed(hint); + break; } } this._willPatchSnapshot = undefined; @@ -333,9 +339,6 @@ class MessageList extends Component { isProcessed = true; } if (isProcessed) { - this.env.messagingBus.trigger('o-component-message-list-thread-cache-changed', { - threadViewer: this.threadView.threadViewer, - }); this.threadView.markComponentHintProcessed(hint); } } diff --git a/addons/mail/static/src/components/thread_view/thread_view_tests.js b/addons/mail/static/src/components/thread_view/thread_view_tests.js index 320512a0f8c..2bf4c08a7a8 100644 --- a/addons/mail/static/src/components/thread_view/thread_view_tests.js +++ b/addons/mail/static/src/components/thread_view/thread_view_tests.js @@ -741,13 +741,17 @@ QUnit.test('basic rendering of canceled notification', async function (assert) { }]], }); await this.afterEvent({ - eventName: 'o-component-message-list-thread-cache-changed', + eventName: 'o-thread-view-hint-processed', func: () => { this.createThreadViewComponent(threadViewer.threadView); }, message: "thread become loaded with messages", - predicate: ({ threadViewer }) => { - return threadViewer.thread.model === 'mail.channel' && threadViewer.thread.id === 11; + predicate: ({ hint, threadViewer }) => { + return ( + hint.type === 'messages-loaded' && + threadViewer.thread.model === 'mail.channel' && + threadViewer.thread.id === 11 + ); }, }); |
