diff --git a/addons/mail/static/src/components/attachment/attachment.js b/addons/mail/static/src/components/attachment/attachment.js index c437b891ab7..3ef672467f3 100644 --- a/addons/mail/static/src/components/attachment/attachment.js +++ b/addons/mail/static/src/components/attachment/attachment.js @@ -66,7 +66,7 @@ export class Attachment extends Component { if (this.attachment.fileType !== 'image') { return ''; } - if (this.env.isQUnitTest) { + if (this.attachment.messaging.isQUnitTest) { // background-image:url is hardly mockable, and attachments in // QUnit tests do not actually exist in DB, so style should not // be fetched at all. diff --git a/addons/mail/static/src/components/message/message.js b/addons/mail/static/src/components/message/message.js index 7b52862bdd5..cde3c18e2d4 100644 --- a/addons/mail/static/src/components/message/message.js +++ b/addons/mail/static/src/components/message/message.js @@ -406,7 +406,7 @@ export class Message extends Component { el.remove(); } this._insertReadMoreLess($(this._contentRef.el)); - this.env.messagingBus.trigger('o-component-message-read-more-less-inserted', { + this.message.messaging.messagingBus.trigger('o-component-message-read-more-less-inserted', { message: this.message, }); } 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 acd696a5241..60fcdf16c7b 100644 --- a/addons/mail/static/src/components/message_list/message_list.js +++ b/addons/mail/static/src/components/message_list/message_list.js @@ -505,7 +505,7 @@ export class MessageList extends Component { return; } const scrollTop = this._getScrollableElement().scrollTop; - this.env.messagingBus.trigger('o-component-message-list-scrolled', { + this.threadView.messaging.messagingBus.trigger('o-component-message-list-scrolled', { orderedMessages, scrollTop, thread, diff --git a/addons/mail/static/src/components/message_list/message_list.xml b/addons/mail/static/src/components/message_list/message_list.xml index 6d8c7051ea7..b9cc092ead8 100644 --- a/addons/mail/static/src/components/message_list/message_list.xml +++ b/addons/mail/static/src/components/message_list/message_list.xml @@ -49,7 +49,7 @@ -
+

New messages
diff --git a/addons/mail/static/src/env/test_env.js b/addons/mail/static/src/env/test_env.js index b8d69aecfae..41c60ba3b16 100644 --- a/addons/mail/static/src/env/test_env.js +++ b/addons/mail/static/src/env/test_env.js @@ -3,8 +3,6 @@ import { makeDeferred } from '@mail/utils/deferred/deferred'; import { nextTick } from '@mail/utils/utils'; -const { EventBus } = owl.core; - /** * @param {Object} [providedEnv={}] * @returns {Object} @@ -19,7 +17,6 @@ export function addMessagingToEnv(providedEnv = {}) { * Environment keys used in messaging. */ Object.assign(env, { - autofetchPartnerImStatus: false, browser: Object.assign({ innerHeight: 1080, innerWidth: 1920, @@ -30,24 +27,14 @@ export function addMessagingToEnv(providedEnv = {}) { }, }, (env.browser && env.browser.Notification) || {}), }, env.browser), - disableAnimation: true, isMessagingInitialized() { if (!this.messaging) { return false; } return this.messaging.isInitialized; }, - /** - * States whether the environment is in QUnit test or not. - * - * Useful to prevent some behaviour in QUnit tests, like applying - * style of attachment that uses url. - */ - isQUnitTest: true, - loadingBaseDelayDuration: providedEnv.loadingBaseDelayDuration || 0, messagingCreatedPromise: makeDeferred(), messagingInitializedDeferred: makeDeferred(), - messagingBus: new EventBus(), }); return env; } diff --git a/addons/mail/static/src/js/main.js b/addons/mail/static/src/js/main.js index dd6e35cb125..6383abd22c2 100644 --- a/addons/mail/static/src/js/main.js +++ b/addons/mail/static/src/js/main.js @@ -6,7 +6,6 @@ import MessagingService from '@mail/services/messaging/messaging'; import env from 'web.commonEnv'; import { serviceRegistry } from 'web.core'; -const { EventBus } = owl.core; async function createMessaging() { await new Promise(resolve => { @@ -39,23 +38,12 @@ async function createMessaging() { * Environment keys used in messaging. */ Object.assign(env, { - autofetchPartnerImStatus: true, - disableAnimation: false, isMessagingInitialized() { if (!this.modelManager.messaging) { return false; } return this.modelManager.messaging.isInitialized; }, - /** - * States whether the environment is in QUnit test or not. - * - * Useful to prevent some behaviour in QUnit tests, like applying - * style of attachment that uses url. - */ - isQUnitTest: false, - loadingBaseDelayDuration: 400, - messagingBus: new EventBus(), /** * Promise which becomes resolved when messaging is created. * diff --git a/addons/mail/static/src/model/model_manager.js b/addons/mail/static/src/model/model_manager.js index 709b95b8ef4..ff3cb7e4ba9 100644 --- a/addons/mail/static/src/model/model_manager.js +++ b/addons/mail/static/src/model/model_manager.js @@ -101,8 +101,10 @@ export class ModelManager { /** * Called when all JS modules that register or patch models have been * done. This launches generation of models. + * + * @param {Object} values field name/value pairs to give at messaging create */ - start() { + start(values) { /** * Generate the models. */ @@ -110,7 +112,7 @@ export class ModelManager { /** * Create the messaging singleton record. */ - this.models['mail.messaging'].create(); + this.models['mail.messaging'].create(values); } //-------------------------------------------------------------------------- diff --git a/addons/mail/static/src/models/attachment/attachment.js b/addons/mail/static/src/models/attachment/attachment.js index 4aa63bf888e..d353547f816 100644 --- a/addons/mail/static/src/models/attachment/attachment.js +++ b/addons/mail/static/src/models/attachment/attachment.js @@ -315,7 +315,7 @@ function factory(dependencies) { if (!this.uploadingAbortController) { const abortController = new AbortController(); abortController.signal.onabort = () => { - this.env.messagingBus.trigger('o-attachment-upload-abort', { + this.messaging.messagingBus.trigger('o-attachment-upload-abort', { attachment: this }); }; diff --git a/addons/mail/static/src/models/chatter/chatter.js b/addons/mail/static/src/models/chatter/chatter.js index ef6ff2805f6..b5a8122fa63 100644 --- a/addons/mail/static/src/models/chatter/chatter.js +++ b/addons/mail/static/src/models/chatter/chatter.js @@ -178,7 +178,7 @@ function factory(dependencies) { this._attachmentsLoaderTimeout = this.env.browser.setTimeout(() => { this.update({ isShowingAttachmentsLoading: true }); this._isPreparingAttachmentsLoading = false; - }, this.env.loadingBaseDelayDuration); + }, this.messaging.loadingBaseDelayDuration); } /** diff --git a/addons/mail/static/src/models/messaging/messaging.js b/addons/mail/static/src/models/messaging/messaging.js index 39b1ef2f6e9..e4d778879e6 100644 --- a/addons/mail/static/src/models/messaging/messaging.js +++ b/addons/mail/static/src/models/messaging/messaging.js @@ -5,6 +5,8 @@ import { attr, many2many, many2one, one2many, one2one } from '@mail/model/model_ import { create } from '@mail/model/model_field_command'; import { makeDeferred } from '@mail/utils/deferred/deferred'; +const { EventBus } = owl.core; + function factory(dependencies) { class Messaging extends dependencies['mail.model'] { @@ -157,6 +159,17 @@ function factory(dependencies) { return makeDeferred(); } + /** + * @private + * @returns {owl.EventBus} + */ + _computeMessagingBus() { + if (this.messagingBus) { + return; + } + return new EventBus(); + } + /** * @private * @returns {boolean} @@ -187,6 +200,13 @@ function factory(dependencies) { inverse: 'messaging', isCausal: true, }), + /** + * Determines whether a loop should be started at initialization to + * periodically fetch the im_status of all users. + */ + autofetchPartnerImStatus: attr({ + default: true, + }), cannedResponses: one2many('mail.canned_response'), chatWindowManager: one2one('mail.chat_window_manager', { default: create(), @@ -206,6 +226,12 @@ function factory(dependencies) { isCausal: true, readonly: true, }), + /** + * Determines whether animations should be disabled. + */ + disableAnimation: attr({ + default: false, + }), discuss: one2one('mail.discuss', { default: create(), isCausal: true, @@ -243,11 +269,34 @@ function factory(dependencies) { isNotificationPermissionDefault: attr({ compute: '_computeIsNotificationPermissionDefault', }), + /** + * States whether the current environment is QUnit test. Useful to + * disable some features that are not possible to test due to + * technical limitations. + */ + isQUnitTest: attr({ + default: false, + }), locale: one2one('mail.locale', { default: create(), isCausal: true, readonly: true, }), + /** + * Determines after how much time in ms a "loading" indicator should be + * shown. Useful to avoid flicker for almost instant loading. + */ + loadingBaseDelayDuration: attr({ + default: 400, + }), + /** + * Determines the bus that is used to communicate messaging events. + */ + messagingBus: attr({ + compute: '_computeMessagingBus', + readonly: true, + required: true, + }), messagingMenu: one2one('mail.messaging_menu', { default: create(), isCausal: true, diff --git a/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js b/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js index b3e07be88eb..8e3e456b694 100644 --- a/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js +++ b/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js @@ -52,7 +52,7 @@ function factory(dependencies) { if (discuss.isOpen) { discuss.openInitThread(); } - if (this.env.autofetchPartnerImStatus) { + if (this.messaging.autofetchPartnerImStatus) { this.env.models['mail.partner'].startLoopFetchImStatus(); } } diff --git a/addons/mail/static/src/models/thread/thread.js b/addons/mail/static/src/models/thread/thread.js index 7fc1afea756..82de476e324 100644 --- a/addons/mail/static/src/models/thread/thread.js +++ b/addons/mail/static/src/models/thread/thread.js @@ -1540,7 +1540,7 @@ function factory(dependencies) { * @private */ _onChangeLastSeenByCurrentPartnerMessageId() { - this.env.messagingBus.trigger('o-thread-last-seen-by-current-partner-message-id-changed', { + this.messaging.messagingBus.trigger('o-thread-last-seen-by-current-partner-message-id-changed', { thread: this, }); } diff --git a/addons/mail/static/src/models/thread_cache/thread_cache.js b/addons/mail/static/src/models/thread_cache/thread_cache.js index cbb61b130ed..53675e819a8 100644 --- a/addons/mail/static/src/models/thread_cache/thread_cache.js +++ b/addons/mail/static/src/models/thread_cache/thread_cache.js @@ -283,7 +283,7 @@ function factory(dependencies) { if (!extraDomain && messages.length < limit) { this.update({ isAllHistoryLoaded: true }); } - this.env.messagingBus.trigger('o-thread-cache-loaded-messages', { + this.messaging.messagingBus.trigger('o-thread-cache-loaded-messages', { fetchedMessages: messages, threadCache: this, }); diff --git a/addons/mail/static/src/models/thread_view/thread_view.js b/addons/mail/static/src/models/thread_view/thread_view.js index fd84ee4e840..49a68403fb6 100644 --- a/addons/mail/static/src/models/thread_view/thread_view.js +++ b/addons/mail/static/src/models/thread_view/thread_view.js @@ -48,7 +48,7 @@ function factory(dependencies) { this.update({ componentHintList: this.componentHintList.filter(h => h !== hint), }); - this.env.messagingBus.trigger('o-thread-view-hint-processed', { + this.messaging.messagingBus.trigger('o-thread-view-hint-processed', { hint, threadViewer: this.threadViewer, }); diff --git a/addons/mail/static/src/services/new_message_service/tests/new_message_service_tests.js b/addons/mail/static/src/services/new_message_service/tests/new_message_service_tests.js index 35f4ef54fec..bcd8bbf6326 100644 --- a/addons/mail/static/src/services/new_message_service/tests/new_message_service_tests.js +++ b/addons/mail/static/src/services/new_message_service/tests/new_message_service_tests.js @@ -54,12 +54,14 @@ QUnit.test('Open "New Message" chat window with a command', async function (asse await afterNextRender(() => click(document.body, ".o_command.focused")); assert.containsOnce(document.body, ".o_ChatWindow"); assert.strictEqual(document.querySelector(".o_ChatWindow .o_ChatWindowHeader_name").textContent, "New message"); + + webClient.destroy(); }); QUnit.test('open "New Message" chat window with a hotkey', async function (assert) { assert.expect(2); - await start({ + const { widget: webClient } = await start({ data: this.data, hasChatWindow: true, hasWebClient: true, @@ -67,6 +69,8 @@ QUnit.test('open "New Message" chat window with a hotkey', async function (asser await afterNextRender(() => triggerHotkey("alt+shift+w")); assert.containsOnce(document.body, ".o_ChatWindow"); assert.strictEqual(document.querySelector(".o_ChatWindow .o_ChatWindowHeader_name").textContent, "New message"); + + webClient.destroy(); }); QUnit.test('Open "New Message" in discuss with a command', async function (assert) { @@ -88,12 +92,14 @@ QUnit.test('Open "New Message" in discuss with a command', async function (asser await afterNextRender(() => click(document.body, ".o_command.focused")); assert.containsOnce(document.body, ".o_DiscussSidebar_itemNewInput"); + + webClient.destroy(); }); QUnit.test('Open "New Message" in discuss with a hotkey', async function (assert) { assert.expect(1); - await start({ + const { widget: webClient } = await start({ autoOpenDiscuss: true, data: this.data, hasDiscuss: true, @@ -101,6 +107,8 @@ QUnit.test('Open "New Message" in discuss with a hotkey', async function (assert }); await afterNextRender(() => triggerHotkey("alt+shift+w")); assert.containsOnce(document.body, ".o_DiscussSidebar_itemNewInput"); + + webClient.destroy(); }); }); diff --git a/addons/mail/static/src/utils/test_utils.js b/addons/mail/static/src/utils/test_utils.js index cfe33f18ac8..c94f2d1b389 100644 --- a/addons/mail/static/src/utils/test_utils.js +++ b/addons/mail/static/src/utils/test_utils.js @@ -33,6 +33,7 @@ const { unpatch: legacyUnpatch, } = mock; const { Component } = owl; +const { EventBus } = owl.core; //------------------------------------------------------------------------------ // Private @@ -394,6 +395,7 @@ async function createRootMessagingComponent(self, componentName, { props = {}, t * `env.testUtils`. * @param {boolean} [param0.hasView=false] if set, use createView to create a * view instead of a generic widget. + * @param {integer} [param0.loadingBaseDelayDuration=0] * @param {Deferred|Promise} [param0.messagingBeforeCreationDeferred=Promise.resolve()] * Deferred that let tests block messaging creation and simulate resolution. * Useful for testing working components when messaging is not yet created. @@ -445,6 +447,7 @@ async function start(param0 = {}) { hasMessagingMenu = false, hasTimeControl = false, hasView = false, + loadingBaseDelayDuration = 0, messagingBeforeCreationDeferred = Promise.resolve(), waitUntilEvent, waitUntilMessagingCondition = 'initialized', @@ -471,6 +474,7 @@ async function start(param0 = {}) { if (hasMessagingMenu) { callbacks = _useMessagingMenu(callbacks); } + const messagingBus = new EventBus(); const { init: initCallbacks, mount: mountCallbacks, @@ -650,7 +654,7 @@ async function start(param0 = {}) { }); // Set up the promise to resolve if the event is triggered. const eventProm = new Promise(resolve => { - testEnv.messagingBus.on(eventName, null, data => { + messagingBus.on(eventName, null, data => { if (!predicate || predicate(data)) { resolve(); } @@ -681,8 +685,14 @@ async function start(param0 = {}) { */ await env.session.is_bound; - testEnv.modelManager.start(); - testEnv.messaging.start().then(() => + testEnv.modelManager.start({ + autofetchPartnerImStatus: false, + disableAnimation: true, + isQUnitTest: true, + loadingBaseDelayDuration, + messagingBus, + }); + modelManager.messaging.start().then(() => testEnv.messagingInitializedDeferred.resolve() ); testEnv.messagingCreatedPromise.resolve(); @@ -702,7 +712,7 @@ async function start(param0 = {}) { returnCallbacks.forEach(callback => callback(result)); }; if (waitUntilEvent) { - await afterEvent(Object.assign({ func: start }, waitUntilEvent)); + await afterEvent({ func: start, ...waitUntilEvent }); } else { await start(); } diff --git a/addons/mail/static/tests/webclients/commands/mail_providers_tests.js b/addons/mail/static/tests/webclients/commands/mail_providers_tests.js index 72420fa9975..da97c8748ea 100644 --- a/addons/mail/static/tests/webclients/commands/mail_providers_tests.js +++ b/addons/mail/static/tests/webclients/commands/mail_providers_tests.js @@ -65,6 +65,8 @@ QUnit.module('mail', {}, function () { await afterNextRender(() => click(document.body, ".o_command.focused")); assert.containsOnce(document.body, ".o_ChatWindow"); assert.strictEqual(document.querySelector(".o_ChatWindow .o_ChatWindowHeader_name").textContent, "Partner 1"); + + webClient.destroy(); }); QUnit.test('open the chatWindow of a channel from the command palette', async function (assert) { @@ -104,5 +106,7 @@ QUnit.module('mail', {}, function () { await afterNextRender(() => click(document.body, ".o_command.focused")); assert.containsOnce(document.body, ".o_ChatWindow"); assert.strictEqual(document.querySelector(".o_ChatWindow .o_ChatWindowHeader_name").textContent, "general"); + + webClient.destroy(); }); });