diff --git a/addons/mail/static/src/env/test_env.js b/addons/mail/static/src/env/test_env.js index 41c60ba3b16..8b565c05a75 100644 --- a/addons/mail/static/src/env/test_env.js +++ b/addons/mail/static/src/env/test_env.js @@ -9,10 +9,6 @@ import { nextTick } from '@mail/utils/utils'; */ export function addMessagingToEnv(providedEnv = {}) { const env = { ...providedEnv }; - /** - * Registry of models. - */ - env.models = {}; /** * Environment keys used in messaging. */ @@ -34,7 +30,6 @@ export function addMessagingToEnv(providedEnv = {}) { return this.messaging.isInitialized; }, messagingCreatedPromise: makeDeferred(), - messagingInitializedDeferred: makeDeferred(), }); return env; } diff --git a/addons/mail/static/src/js/main.js b/addons/mail/static/src/js/main.js index 6383abd22c2..e94277fb1df 100644 --- a/addons/mail/static/src/js/main.js +++ b/addons/mail/static/src/js/main.js @@ -1,45 +1,17 @@ /** @odoo-module **/ -import { ModelManager } from '@mail/model/model_manager'; import MessagingService from '@mail/services/messaging/messaging'; +import { makeDeferred } from '@mail/utils/deferred/deferred'; import env from 'web.commonEnv'; import { serviceRegistry } from 'web.core'; - -async function createMessaging() { - await new Promise(resolve => { - /** - * Called when all JS resources are loaded. This is useful in order - * to do some processing after other JS files have been parsed, for - * example new models or patched models that are coming from - * other modules, because some of those patches might need to be - * applied before messaging initialization. - */ - window.addEventListener('load', resolve); - }); - /** - * All JS resources are loaded, but not necessarily processed. - * We assume no messaging-related modules return any Promise, - * therefore they should be processed *at most* asynchronously at - * "Promise time". - */ - await new Promise(resolve => setTimeout(resolve)); - /** - * Some models require session data, like locale text direction (depends on - * fully loaded translation). - */ - await env.session.is_bound; - - env.modelManager.start(); -} - /** * Environment keys used in messaging. */ Object.assign(env, { isMessagingInitialized() { - if (!this.modelManager.messaging) { + if (!this.modelManager || !this.modelManager.messaging) { return false; } return this.modelManager.messaging.isInitialized; @@ -50,17 +22,16 @@ Object.assign(env, { * Useful for discuss widget to know when messaging is created, because this * is an essential condition to make it work. */ - messagingCreatedPromise: createMessaging(), - modelManager: new ModelManager(env), + messagingCreatedPromise: makeDeferred(), }); Object.defineProperty(env, 'messaging', { get() { - return this.modelManager.messaging; + return this.modelManager && this.modelManager.messaging; }, }); Object.defineProperty(env, 'models', { get() { - return this.modelManager.models; + return this.modelManager && this.modelManager.models; }, }); diff --git a/addons/mail/static/src/services/chat_window_service/chat_window_service.js b/addons/mail/static/src/services/chat_window_service/chat_window_service.js index 4ea7adc9926..bccbca42d88 100644 --- a/addons/mail/static/src/services/chat_window_service/chat_window_service.js +++ b/addons/mail/static/src/services/chat_window_service/chat_window_service.js @@ -6,6 +6,7 @@ import AbstractService from 'web.AbstractService'; import { bus, serviceRegistry } from 'web.core'; const ChatWindowService = AbstractService.extend({ + dependencies: ['messaging'], /** * @override {web.AbstractService} */ diff --git a/addons/mail/static/src/services/dialog_service/dialog_service.js b/addons/mail/static/src/services/dialog_service/dialog_service.js index 64f3b2d0436..488b85f21cf 100644 --- a/addons/mail/static/src/services/dialog_service/dialog_service.js +++ b/addons/mail/static/src/services/dialog_service/dialog_service.js @@ -6,6 +6,7 @@ import AbstractService from 'web.AbstractService'; import { bus, serviceRegistry } from 'web.core'; const DialogService = AbstractService.extend({ + dependencies: ['messaging'], /** * @override {web.AbstractService} */ diff --git a/addons/mail/static/src/services/messaging/messaging.js b/addons/mail/static/src/services/messaging/messaging.js index 579f905fb9c..82b6a0d5800 100644 --- a/addons/mail/static/src/services/messaging/messaging.js +++ b/addons/mail/static/src/services/messaging/messaging.js @@ -1,14 +1,56 @@ /** @odoo-module **/ +import { ModelManager } from '@mail/model/model_manager'; + import AbstractService from 'web.AbstractService'; export default AbstractService.extend({ dependencies: ['bus_service'], /** - * @override {web.AbstractService} + * @override */ - start() { + init() { this._super(...arguments); - this.env.messagingCreatedPromise.then(() => this.env.messaging.start()); + this._modelManager = new ModelManager(this.env); + this.env.modelManager = this._modelManager; + }, + /** + * @override + */ + async start() { + this._super(...arguments); + if (document.readyState === 'loading') { + await new Promise(resolve => { + /** + * Called when all JS resources are loaded. This is useful in order + * to do some processing after other JS files have been parsed, for + * example new models or patched models that are coming from + * other modules, because some of those patches might need to be + * applied before messaging initialization. + */ + window.addEventListener('load', resolve); + }); + } + /** + * All JS resources are loaded, but not necessarily processed. + * We assume no messaging-related modules return any Promise, + * therefore they should be processed *at most* asynchronously at + * "Promise time". + */ + await new Promise(resolve => setTimeout(resolve)); + /** + * Some models require session data, like locale text direction (depends on + * fully loaded translation). + */ + await this.env.session.is_bound; + await this.startModelManager(); + this.env.messagingCreatedPromise.resolve(); + await this.env.messaging.start(); + }, + /** + * Starts the model manager. + */ + async startModelManager() { + await this._modelManager.start(); }, }); diff --git a/addons/mail/static/src/utils/test_utils.js b/addons/mail/static/src/utils/test_utils.js index c94f2d1b389..150e6fefb44 100644 --- a/addons/mail/static/src/utils/test_utils.js +++ b/addons/mail/static/src/utils/test_utils.js @@ -6,8 +6,9 @@ import { addMessagingToEnv, addTimeControlToEnv, } from '@mail/env/test_env'; -import { ModelManager } from '@mail/model/model_manager'; import ChatWindowService from '@mail/services/chat_window_service/chat_window_service'; +import MessagingService from '@mail/services/messaging/messaging'; +import { makeDeferred } from '@mail/utils/deferred/deferred'; import DialogService from '@mail/services/dialog_service/dialog_service'; import { getMessagingComponent } from '@mail/utils/messaging_component'; import { nextTick } from '@mail/utils/utils'; @@ -483,7 +484,7 @@ async function start(param0 = {}) { } = callbacks; const { debug = false } = param0; initCallbacks.forEach(callback => callback(param0)); - + const testSetupDoneDeferred = makeDeferred(); let env = Object.assign(providedEnv || {}); env.session = Object.assign( { @@ -520,6 +521,56 @@ async function start(param0 = {}) { _listenHomeMenu: () => {}, }), local_storage: AbstractStorageService.extend({ storage: new RamStorage() }), + messaging: MessagingService.extend({ + /** + * Override to bind the getters on the actual env that is given, + * because the env in the scope of this test might only serve as + * template and not be the one actually used. + * + * @override + */ + init() { + this._super(...arguments); + Object.defineProperty(this.env, 'messaging', { + get() { + return this.modelManager.messaging; + }, + }); + Object.defineProperty(this.env, 'models', { + get() { + return this.modelManager.models; + }, + }); + }, + /** + * Override to control when messaging is created, useful to test + * spinners and race conditions. + * + * @override + */ + async start() { + const _super = this._super.bind(this); + await messagingBeforeCreationDeferred; + await _super(); + }, + /** + * Override to ensure the test setup is complete before starting + * otherwise for example the mock server might not be ready yet. + * And also override to give test values to messaging. + * + * @override + */ + async startModelManager() { + await testSetupDoneDeferred; + await this._modelManager.start({ + autofetchPartnerImStatus: false, + disableAnimation: true, + isQUnitTest: true, + loadingBaseDelayDuration, + messagingBus, + }); + }, + }), }, param0.services); const kwargs = Object.assign({}, param0, { @@ -615,18 +666,6 @@ async function start(param0 = {}) { } // get the final test env after execution of createView/createWebClient/addMockEnvironment testEnv = Component.env; - const modelManager = new ModelManager(testEnv); - Object.assign(testEnv, { modelManager }); - Object.defineProperty(testEnv, 'messaging', { - get() { - return this.modelManager.messaging; - }, - }); - Object.defineProperty(testEnv, 'models', { - get() { - return this.modelManager.models; - }, - }); /** * Returns a promise resolved after the expected event is received. * @@ -671,51 +710,34 @@ async function start(param0 = {}) { await funcRes; }); + let waitUntilEventPromise; + if (waitUntilEvent) { + waitUntilEventPromise = afterEvent({ func: () => testSetupDoneDeferred.resolve(), ...waitUntilEvent, }); + } else { + testSetupDoneDeferred.resolve(); + waitUntilEventPromise = Promise.resolve(); + } + const result = { afterEvent, env: testEnv, mockServer, widget, }; - const start = async () => { - messagingBeforeCreationDeferred.then(async () => { - /** - * Some models require session data, like locale text direction - * (depends on fully loaded translation). - */ - await env.session.is_bound; - - testEnv.modelManager.start({ - autofetchPartnerImStatus: false, - disableAnimation: true, - isQUnitTest: true, - loadingBaseDelayDuration, - messagingBus, - }); - modelManager.messaging.start().then(() => - testEnv.messagingInitializedDeferred.resolve() - ); - testEnv.messagingCreatedPromise.resolve(); - }); - if (waitUntilMessagingCondition === 'created') { - await testEnv.messagingCreatedPromise; - } - if (waitUntilMessagingCondition === 'initialized') { - await testEnv.messagingInitializedDeferred; - } - - if (mountCallbacks.length > 0) { - await afterNextRender(async () => { - await Promise.all(mountCallbacks.map(callback => callback({ selector, widget }))); - }); - } - returnCallbacks.forEach(callback => callback(result)); - }; - if (waitUntilEvent) { - await afterEvent({ func: start, ...waitUntilEvent }); - } else { - await start(); + if (waitUntilMessagingCondition === 'created') { + await testEnv.messagingCreatedPromise; } + if (waitUntilMessagingCondition === 'initialized') { + await testEnv.messagingCreatedPromise; + await testEnv.modelManager.messaging.initializedPromise; + } + if (mountCallbacks.length > 0) { + await afterNextRender(async () => { + await Promise.all(mountCallbacks.map(callback => callback({ selector, widget }))); + }); + } + returnCallbacks.forEach(callback => callback(result)); + await waitUntilEventPromise; return result; } diff --git a/addons/mail/static/src/widgets/discuss/discuss.js b/addons/mail/static/src/widgets/discuss/discuss.js index 65d469072da..ff87bcff37d 100644 --- a/addons/mail/static/src/widgets/discuss/discuss.js +++ b/addons/mail/static/src/widgets/discuss/discuss.js @@ -31,20 +31,15 @@ const DiscussWidget = AbstractAction.extend({ this.component = undefined; this._lastPushStateActiveThread = null; - }, - /** - * @override - */ - async willStart() { - await this._super(...arguments); this.env = Component.env; - await this.env.messagingCreatedPromise; - const initActiveId = this.options.active_id || - (this.action.context && this.action.context.active_id) || - (this.action.params && this.action.params.default_active_id) || - 'mail.box_inbox'; - this.discuss = this.env.messaging.discuss; - this.discuss.update({ initActiveId }); + this.env.messagingCreatedPromise.then(() => { + const initActiveId = this.options.active_id || + (this.action.context && this.action.context.active_id) || + (this.action.params && this.action.params.default_active_id) || + 'mail.box_inbox'; + this.discuss = this.env.messaging.discuss; + this.discuss.update({ initActiveId }); + }); }, /** * @override {web.AbstractAction}