diff --git a/addons/mail/static/src/components/discuss_public_view_container/discuss_public_view_container.js b/addons/mail/static/src/components/discuss_public_view_container/discuss_public_view_container.js
index 9373574c03f..3c232df6b3f 100644
--- a/addons/mail/static/src/components/discuss_public_view_container/discuss_public_view_container.js
+++ b/addons/mail/static/src/components/discuss_public_view_container/discuss_public_view_container.js
@@ -5,7 +5,7 @@ import { useModels } from '@mail/component_hooks/use_models';
import '@mail/components/discuss_public_view/discuss_public_view';
import { getMessagingComponent } from "@mail/utils/messaging_component";
-const { Component, useSubEnv } = owl;
+const { Component } = owl;
export class DiscussPublicViewContainer extends Component {
@@ -13,9 +13,6 @@ export class DiscussPublicViewContainer extends Component {
* @override
*/
setup() {
- // for now, the legacy env is needed for internal functions such as
- // `useModels` to work
- useSubEnv(Component.env);
useModels();
super.setup();
}
diff --git a/addons/mail/static/src/components/messaging_menu_container/messaging_menu_container.js b/addons/mail/static/src/components/messaging_menu_container/messaging_menu_container.js
index 98f80fe47e8..efa9ab2064d 100644
--- a/addons/mail/static/src/components/messaging_menu_container/messaging_menu_container.js
+++ b/addons/mail/static/src/components/messaging_menu_container/messaging_menu_container.js
@@ -5,7 +5,7 @@ import { useModels } from '@mail/component_hooks/use_models';
import '@mail/components/messaging_menu/messaging_menu';
import { getMessagingComponent } from "@mail/utils/messaging_component";
-const { Component, useSubEnv } = owl;
+const { Component } = owl;
export class MessagingMenuContainer extends Component {
@@ -13,9 +13,6 @@ export class MessagingMenuContainer extends Component {
* @override
*/
setup() {
- // for now, the legacy env is needed for internal functions such as
- // `useModels` to work
- useSubEnv(Component.env);
useModels();
super.setup();
}
diff --git a/addons/mail/static/src/components/notification_request/notification_request.js b/addons/mail/static/src/components/notification_request/notification_request.js
index f4ba5154762..1730860a0e1 100644
--- a/addons/mail/static/src/components/notification_request/notification_request.js
+++ b/addons/mail/static/src/components/notification_request/notification_request.js
@@ -31,7 +31,7 @@ export class NotificationRequest extends Component {
_handleResponseNotificationPermission(value) {
this.messaging.refreshIsNotificationPermissionDefault();
if (value !== 'granted') {
- this.env.services['bus_service'].sendNotification({
+ this.env.services['legacy_bus_service'].sendNotification({
message: this.env._t("Odoo will not have the permission to send native notifications on this device."),
title: this.env._t("Permission denied"),
});
diff --git a/addons/mail/static/src/components/rtc_activity_notice_container/rtc_activity_notice_container.js b/addons/mail/static/src/components/rtc_activity_notice_container/rtc_activity_notice_container.js
index c0ed9eda5da..facc5e6de85 100644
--- a/addons/mail/static/src/components/rtc_activity_notice_container/rtc_activity_notice_container.js
+++ b/addons/mail/static/src/components/rtc_activity_notice_container/rtc_activity_notice_container.js
@@ -5,7 +5,7 @@ import { useModels } from '@mail/component_hooks/use_models';
import '@mail/components/rtc_activity_notice/rtc_activity_notice';
import { getMessagingComponent } from "@mail/utils/messaging_component";
-const { Component, useSubEnv } = owl;
+const { Component } = owl;
export class RtcActivityNoticeContainer extends Component {
@@ -13,9 +13,6 @@ export class RtcActivityNoticeContainer extends Component {
* @override
*/
setup() {
- // for now, the legacy env is needed for internal functions such as
- // `useModels` to work
- useSubEnv(Component.env);
useModels();
super.setup();
}
diff --git a/addons/mail/static/src/js/tours/mail.js b/addons/mail/static/src/js/tours/mail.js
index ddda18795f7..a70637fef47 100644
--- a/addons/mail/static/src/js/tours/mail.js
+++ b/addons/mail/static/src/js/tours/mail.js
@@ -5,7 +5,7 @@ import { Markup } from 'web.utils';
import tour from 'web_tour.tour';
tour.register('mail_tour', {
- url: "/web#action=mail.widgets.discuss",
+ url: "/web#action=mail.action_discuss",
sequence: 80,
}, [{
trigger: '.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_commandAdd',
diff --git a/addons/mail/static/src/main.js b/addons/mail/static/src/main.js
index 04e5c66b4a2..d6dba4a81d6 100644
--- a/addons/mail/static/src/main.js
+++ b/addons/mail/static/src/main.js
@@ -2,17 +2,26 @@
import { ChatWindowManagerContainer } from '@mail/components/chat_window_manager_container/chat_window_manager_container';
import { DialogManagerContainer } from '@mail/components/dialog_manager_container/dialog_manager_container';
-import { MessagingService } from '@mail/services/messaging/messaging';
-import { SystrayService } from '@mail/services/systray_service/systray_service';
-import { DiscussWidget } from '@mail/widgets/discuss/discuss';
+import { DiscussContainer } from '@mail/components/discuss_container/discuss_container';
+import { messagingService } from '@mail/services/messaging_service';
+import { systrayService } from '@mail/services/systray_service';
+import { makeMessagingToLegacyEnv } from '@mail/utils/make_messaging_to_legacy_env';
-import { serviceRegistry, action_registry } from 'web.core';
import { registry } from '@web/core/registry';
-serviceRegistry.add('messaging', MessagingService);
-serviceRegistry.add('systray_service', SystrayService);
+const messagingValuesService = {
+ start() {
+ return {};
+ }
+};
-action_registry.add('mail.widgets.discuss', DiscussWidget);
+const serviceRegistry = registry.category('services');
+serviceRegistry.add('messaging', messagingService);
+serviceRegistry.add('messagingValues', messagingValuesService);
+serviceRegistry.add('systray_service', systrayService);
+serviceRegistry.add('messaging_to_legacy_env', makeMessagingToLegacyEnv(owl.Component.env));
+
+registry.category('actions').add('mail.action_discuss', DiscussContainer);
registry.category('main_components').add('DialogManagerContainer', { Component: DialogManagerContainer });
registry.category('main_components').add('ChatWindowManagerContainer', { Component: ChatWindowManagerContainer });
diff --git a/addons/mail/static/src/model/model_manager.js b/addons/mail/static/src/model/model_manager.js
index 0145856a232..19d90ea1a64 100644
--- a/addons/mail/static/src/model/model_manager.js
+++ b/addons/mail/static/src/model/model_manager.js
@@ -134,11 +134,6 @@ export class ModelManager {
* "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;
this._generateModels();
/**
* Create the messaging singleton record.
diff --git a/addons/mail/static/src/models/activity.js b/addons/mail/static/src/models/activity.js
index 2a3fbc77a73..a28723cea9b 100644
--- a/addons/mail/static/src/models/activity.js
+++ b/addons/mail/static/src/models/activity.js
@@ -134,10 +134,10 @@ registerModel({
},
res_id: this.id,
};
- this.env.bus.trigger('do-action', {
+ this.env.services.action.doAction(
action,
- options: { on_close: () => this.fetchAndUpdate() },
- });
+ { onClose: () => this.fetchAndUpdate() },
+ );
},
async fetchAndUpdate() {
const [data] = await this.messaging.rpc({
@@ -210,17 +210,17 @@ registerModel({
if (!action) {
return;
}
- this.env.bus.trigger('do-action', {
+ this.env.services.action.doAction(
action,
- options: {
- on_close: () => {
+ {
+ onClose: () => {
if (!thread.exists()) {
return;
}
thread.fetchData(['activities']);
},
},
- });
+ );
},
/**
* @private
diff --git a/addons/mail/static/src/models/chatter.js b/addons/mail/static/src/models/chatter.js
index 2a7c56d0f1f..84529f8ad2a 100644
--- a/addons/mail/static/src/models/chatter.js
+++ b/addons/mail/static/src/models/chatter.js
@@ -82,12 +82,12 @@ registerModel({
},
res_id: false,
};
- return this.env.bus.trigger('do-action', {
+ return this.env.services.action.doAction(
action,
- options: {
- on_close: () => this.reloadParentView(),
- },
- });
+ {
+ onClose: () => this.reloadParentView(),
+ }
+ );
},
/**
* Handles click on "send message" button.
diff --git a/addons/mail/static/src/models/composer_view.js b/addons/mail/static/src/models/composer_view.js
index 3657b221150..6d7897aa422 100644
--- a/addons/mail/static/src/models/composer_view.js
+++ b/addons/mail/static/src/models/composer_view.js
@@ -533,7 +533,7 @@ registerModel({
};
const composer = this.composer;
const options = {
- on_close: () => {
+ onClose: () => {
if (!composer.exists()) {
return;
}
@@ -543,7 +543,7 @@ registerModel({
}
},
};
- await this.env.bus.trigger('do-action', { action, options });
+ await this.env.services.action.doAction(action, options);
},
/**
* Post a message in provided composer's thread based on current composer fields values.
diff --git a/addons/mail/static/src/models/device.js b/addons/mail/static/src/models/device.js
index 921a424f2d7..d6b20684098 100644
--- a/addons/mail/static/src/models/device.js
+++ b/addons/mail/static/src/models/device.js
@@ -3,6 +3,8 @@
import { registerModel } from '@mail/model/model_core';
import { attr } from '@mail/model/model_field';
import { browser } from "@web/core/browser/browser";
+import { isMobileOS } from "@web/core/browser/feature_detection";
+import { SIZES } from '@web/core/ui/ui_service';
registerModel({
name: 'Device',
@@ -30,9 +32,9 @@ registerModel({
this.update({
globalWindowInnerHeight: this.messaging.browser.innerHeight,
globalWindowInnerWidth: this.messaging.browser.innerWidth,
- isMobileDevice: this.env.device.isMobileDevice,
- isSmall: this.env.device.isMobile,
- sizeClass: this.env.device.size_class,
+ isMobileDevice: isMobileOS(),
+ isSmall: this.env.isSmall,
+ sizeClass: this.env.services.ui.size,
});
},
},
@@ -56,5 +58,8 @@ registerModel({
* attribute.
*/
sizeClass: attr(),
+ sizeClasses: attr({
+ default: SIZES,
+ }),
},
});
diff --git a/addons/mail/static/src/models/discuss.js b/addons/mail/static/src/models/discuss.js
index 4bec19bc910..bdb5620861a 100644
--- a/addons/mail/static/src/models/discuss.js
+++ b/addons/mail/static/src/models/discuss.js
@@ -162,14 +162,14 @@ registerModel({
this.focus();
}
if (!this.discussView) {
- this.env.bus.trigger('do-action', {
- action: 'mail.action_discuss',
- options: {
+ this.env.services.action.doAction(
+ 'mail.action_discuss',
+ {
active_id: this.threadToActiveId(this),
clear_breadcrumbs: false,
on_reverse_breadcrumb: () => this.close(), // this is useless, close is called by destroy anyway
},
- });
+ );
}
},
/**
diff --git a/addons/mail/static/src/models/discuss_sidebar_category.js b/addons/mail/static/src/models/discuss_sidebar_category.js
index 65d6df20a6c..55b1c624795 100644
--- a/addons/mail/static/src/models/discuss_sidebar_category.js
+++ b/addons/mail/static/src/models/discuss_sidebar_category.js
@@ -176,14 +176,12 @@ registerModel({
*/
onClickCommandView(ev) {
ev.stopPropagation();
- return this.env.bus.trigger('do-action', {
- action: {
- name: this.env._t("Public Channels"),
- type: 'ir.actions.act_window',
- res_model: 'mail.channel',
- views: [[false, 'kanban'], [false, 'form']],
- domain: [['public', '!=', 'private']],
- },
+ return this.env.services.action.doAction({
+ name: this.env._t("Public Channels"),
+ type: 'ir.actions.act_window',
+ res_model: 'mail.channel',
+ views: [[false, 'kanban'], [false, 'form']],
+ domain: [['public', '!=', 'private']],
});
},
/**
diff --git a/addons/mail/static/src/models/discuss_sidebar_category_item.js b/addons/mail/static/src/models/discuss_sidebar_category_item.js
index 4ca911b23d5..6bbd0c3c0e2 100644
--- a/addons/mail/static/src/models/discuss_sidebar_category_item.js
+++ b/addons/mail/static/src/models/discuss_sidebar_category_item.js
@@ -134,14 +134,12 @@ registerModel({
*/
onClickCommandSettings(ev) {
ev.stopPropagation();
- return this.env.bus.trigger('do-action', {
- action: {
- type: 'ir.actions.act_window',
- res_model: this.channel.model,
- res_id: this.channel.id,
- views: [[false, 'form']],
- target: 'current',
- },
+ return this.env.services.action.doAction({
+ type: 'ir.actions.act_window',
+ res_model: this.channel.model,
+ res_id: this.channel.id,
+ views: [[false, 'form']],
+ target: 'current',
});
},
/**
diff --git a/addons/mail/static/src/models/discuss_view.js b/addons/mail/static/src/models/discuss_view.js
index 7d91175a0f2..5ff4b97a61e 100644
--- a/addons/mail/static/src/models/discuss_view.js
+++ b/addons/mail/static/src/models/discuss_view.js
@@ -45,9 +45,9 @@ registerModel({
* Handles OWL update on the discuss component.
*/
onComponentUpdate() {
- if (this.discuss.thread && this.actionManager) {
+ if (this.discuss.thread) {
if (this.lastPushStateActiveThread !== this.discuss.thread) {
- this.actionManager.do_push_state({
+ this.env.services.router.pushState({
action: this.actionId,
active_id: this.discuss.activeId,
});
@@ -63,7 +63,7 @@ registerModel({
this.lastThreadCache && this.lastThreadCache.localId === this.discuss.threadView.threadCache.localId &&
this.lastThreadCounter > 0 && this.discuss.thread.counter === 0
) {
- this.env.bus.trigger('show-effect', {
+ this.env.services.effect.add({
message: this.env._t("Congratulations, your inbox is empty!"),
type: 'rainbow_man',
});
@@ -156,11 +156,6 @@ registerModel({
* The id of the action which opened discuss.
*/
actionId: attr(),
- /**
- * Used to push state when changing active thread.
- * The actionManager passed to the discuss widget.
- */
- actionManager: attr(),
discuss: one('Discuss', {
inverse: 'discussView',
readonly: true,
diff --git a/addons/mail/static/src/models/locale.js b/addons/mail/static/src/models/locale.js
index 7e8f39466af..98cc402a36f 100644
--- a/addons/mail/static/src/models/locale.js
+++ b/addons/mail/static/src/models/locale.js
@@ -3,6 +3,8 @@
import { registerModel } from '@mail/model/model_core';
import { attr } from '@mail/model/model_field';
+import { localization } from '@web/core/l10n/localization';
+
registerModel({
name: 'Locale',
identifyingFields: ['messaging'],
@@ -12,14 +14,14 @@ registerModel({
* @returns {string}
*/
_computeLanguage() {
- return this.env._t.database.parameters.code;
+ return this.env.services.user.lang;
},
/**
* @private
* @returns {string}
*/
_computeTextDirection() {
- return this.env._t.database.parameters.direction;
+ return localization.direction;
},
},
fields: {
diff --git a/addons/mail/static/src/models/mail_template.js b/addons/mail/static/src/models/mail_template.js
index a44d6936126..0c28e1d0d4e 100644
--- a/addons/mail/static/src/models/mail_template.js
+++ b/addons/mail/static/src/models/mail_template.js
@@ -25,14 +25,10 @@ registerModel({
force_email: true,
},
};
- this.env.bus.trigger('do-action', {
+ this.env.services.action.doAction(
action,
- options: {
- on_close: () => {
- activity.thread.fetchData(['attachments', 'messages']);
- },
- },
- });
+ { onClose: () => activity.thread.fetchData(['attachments', 'messages']) }
+ );
},
/**
* @param {Activity} activity
diff --git a/addons/mail/static/src/models/message.js b/addons/mail/static/src/models/message.js
index 9fc77200fdb..fab793a800f 100644
--- a/addons/mail/static/src/models/message.js
+++ b/addons/mail/static/src/models/message.js
@@ -231,14 +231,14 @@ registerModel({
* Opens the view that allows to resend the message in case of failure.
*/
openResendAction() {
- this.env.bus.trigger('do-action', {
- action: 'mail.mail_resend_message_action',
- options: {
+ this.env.services.action.doAction(
+ 'mail.mail_resend_message_action',
+ {
additional_context: {
mail_message_to_resend: this.id,
},
- },
- });
+ }
+ );
},
/**
* Removes the given reaction from this message.
diff --git a/addons/mail/static/src/models/messaging.js b/addons/mail/static/src/models/messaging.js
index 250409d6b65..7d906ec08b2 100644
--- a/addons/mail/static/src/models/messaging.js
+++ b/addons/mail/static/src/models/messaging.js
@@ -15,8 +15,8 @@ registerModel({
identifyingFields: [],
lifecycleHooks: {
_willDelete() {
- if (this.env.services['bus_service']) {
- this.env.services['bus_service'].off('window_focus', null, this._handleGlobalWindowFocus);
+ if (this.env.services['legacy_bus_service']) {
+ this.env.services['legacy_bus_service'].off('window_focus', null, this._handleGlobalWindowFocus);
}
},
},
@@ -25,7 +25,7 @@ registerModel({
* Starts messaging and related records.
*/
async start() {
- this.env.services['bus_service'].on('window_focus', null, this._handleGlobalWindowFocus);
+ this.env.services['legacy_bus_service'].on('window_focus', null, this._handleGlobalWindowFocus);
await this.initializer.start();
if (!this.exists()) {
return;
@@ -70,7 +70,8 @@ registerModel({
* @return {number} the id of the notification.
*/
notify(params) {
- return this.env.services.notification.notify(params);
+ const { message, ...options } = params;
+ return this.env.services.notification.add(message, options);
},
/**
* Opens a chat with the provided person and returns it.
@@ -100,13 +101,11 @@ registerModel({
* @param {string} param0.model
*/
async openDocument({ id, model }) {
- this.env.bus.trigger('do-action', {
- action: {
- type: 'ir.actions.act_window',
- res_model: model,
- views: [[false, 'form']],
- res_id: id,
- },
+ this.env.services.action.doAction({
+ type: 'ir.actions.act_window',
+ res_model: model,
+ views: [[false, 'form']],
+ res_id: id,
});
if (this.messaging.device.isSmall) {
// messaging menu has a higher z-index than views so it must
@@ -157,8 +156,35 @@ registerModel({
* @param {Object} params
* @return {any}
*/
- async rpc(params, options) {
- return this.env.services.rpc(params, options);
+ async rpc(params, options = {}) {
+ if (params.route) {
+ const { route, params: rpcParameters } = params;
+ const { shadow: silent, ...rpcSettings } = options;
+ return this.env.services.rpc(route, rpcParameters, { silent, ...rpcSettings });
+ } else {
+ const { args, method, model, kwargs = {} } = params;
+ const { context, domain, fields, groupBy, ...ormOptions } = kwargs;
+
+ const ormService = 'shadow' in options ? this.env.services.orm.silent : this.env.services.orm;
+ switch (method) {
+ case 'create':
+ return ormService.create(model, args[0], context);
+ case 'read':
+ return ormService.read(model, args[0], args.length > 1 ? args[1] : undefined, context);
+ case 'read_group':
+ return ormService.readGroup(model, domain, fields, groupBy, ormOptions, context);
+ case 'search':
+ return ormService.search(model, args[0], ormOptions, context);
+ case 'search_read':
+ return ormService.searchRead(model, domain, fields, ormOptions, context);
+ case 'unlink':
+ return ormService.unlink(model, args[0], context);
+ case 'write':
+ return ormService.write(model, args[0], args[1], context);
+ default:
+ return ormService.call(model, method, args, kwargs);
+ }
+ }
},
/**
* Refreshes the value of `isNotificationPermissionDefault`.
diff --git a/addons/mail/static/src/models/messaging_notification_handler.js b/addons/mail/static/src/models/messaging_notification_handler.js
index ad8cceaf9f3..c850e8919e7 100644
--- a/addons/mail/static/src/models/messaging_notification_handler.js
+++ b/addons/mail/static/src/models/messaging_notification_handler.js
@@ -16,9 +16,9 @@ registerModel({
identifyingFields: ['messaging'],
lifecycleHooks: {
_willDelete() {
- if (this.env.services['bus_service']) {
- this.env.services['bus_service'].off('notification');
- this.env.services['bus_service'].stopPolling();
+ if (this.env.services['legacy_bus_service']) {
+ this.env.services['legacy_bus_service'].off('notification');
+ this.env.services['legacy_bus_service'].stopPolling();
}
},
},
@@ -28,8 +28,8 @@ registerModel({
* the current users. This includes pinned channels for instance.
*/
start() {
- this.env.services.bus_service.onNotification(null, notifs => this._handleNotifications(notifs));
- this.env.services.bus_service.startPolling();
+ this.env.services.legacy_bus_service.onNotification(null, notifs => this._handleNotifications(notifs));
+ this.env.services.legacy_bus_service.startPolling();
},
/**
* @private
@@ -269,7 +269,7 @@ registerModel({
channel.correspondent === this.messaging.partnerRoot
);
if (!isChatWithOdooBot) {
- const isOdooFocused = this.env.services['bus_service'].isOdooFocused();
+ const isOdooFocused = this.env.services['legacy_bus_service'].isOdooFocused();
// Notify if out of focus
if (!isOdooFocused && channel.isChatChannel) {
this._notifyNewChannelMessageWhileOutOfFocus({
@@ -657,7 +657,7 @@ registerModel({
// then open a chat for the current user with the new user.
const message = sprintf(this.env._t('%s connected'), username);
const title = this.env._t("This is their first connection. Wish them luck.");
- this.env.services['bus_service'].sendNotification({ message, title, type: 'info' });
+ this.env.services['legacy_bus_service'].sendNotification({ message, title, type: 'info' });
const chat = await this.messaging.getChat({ partnerId });
if (!this.exists() || !chat || this.messaging.device.isSmall) {
return;
@@ -699,7 +699,7 @@ registerModel({
const notificationContent = escape(
htmlToTextContentInline(message.body).substr(0, PREVIEW_MSG_MAX_SIZE)
);
- this.env.services['bus_service'].sendNotification({
+ this.env.services['legacy_bus_service'].sendNotification({
message: notificationContent,
title: notificationTitle,
type: 'info',
diff --git a/addons/mail/static/src/models/notification_group.js b/addons/mail/static/src/models/notification_group.js
index 2f710566f14..fd8f2600927 100644
--- a/addons/mail/static/src/models/notification_group.js
+++ b/addons/mail/static/src/models/notification_group.js
@@ -88,17 +88,15 @@ registerModel({
if (this.notification_type !== 'email') {
return;
}
- this.env.bus.trigger('do-action', {
- action: {
- name: this.env._t("Mail Failures"),
- type: 'ir.actions.act_window',
- view_mode: 'kanban,list,form',
- views: [[false, 'kanban'], [false, 'list'], [false, 'form']],
- target: 'current',
- res_model: this.res_model,
- domain: [['message_has_error', '=', true]],
- context: { create: false },
- },
+ this.env.services.action.doAction({
+ name: this.env._t("Mail Failures"),
+ type: 'ir.actions.act_window',
+ view_mode: 'kanban,list,form',
+ views: [[false, 'kanban'], [false, 'list'], [false, 'form']],
+ target: 'current',
+ res_model: this.res_model,
+ domain: [['message_has_error', '=', true]],
+ context: { create: false },
});
if (this.messaging.device.isSmall) {
// messaging menu has a higher z-index than views so it must
diff --git a/addons/mail/static/src/models/rtc_call_participant_card.js b/addons/mail/static/src/models/rtc_call_participant_card.js
index 9989c96d8f6..d7069e9e386 100644
--- a/addons/mail/static/src/models/rtc_call_participant_card.js
+++ b/addons/mail/static/src/models/rtc_call_participant_card.js
@@ -88,7 +88,7 @@ registerModel({
* @returns {boolean}
*/
_computeHasConnectionInfo() {
- return Boolean(this.rtcSession && this.env.isDebug() && this.channel.rtc);
+ return Boolean(this.rtcSession && this.env.debug && this.channel.rtc);
},
/**
* @private
diff --git a/addons/mail/static/src/models/thread.js b/addons/mail/static/src/models/thread.js
index 8b0c6c483af..5d8fcbdb7f2 100644
--- a/addons/mail/static/src/models/thread.js
+++ b/addons/mail/static/src/models/thread.js
@@ -1716,18 +1716,18 @@ registerModel({
default_res_id: this.id,
},
};
- this.env.bus.trigger('do-action', {
+ this.env.services.action.doAction(
action,
- options: {
- on_close: async () => {
+ {
+ onClose: async () => {
if (!this.exists()) {
return;
- }
+ }
await this.fetchData(['followers']);
this.env.bus.trigger('Thread:promptAddFollower-closed');
},
- },
- });
+ }
+ );
},
/**
* @private
diff --git a/addons/mail/static/src/models/tracking_value_item.js b/addons/mail/static/src/models/tracking_value_item.js
index a8e164d5173..28fe783ee35 100644
--- a/addons/mail/static/src/models/tracking_value_item.js
+++ b/addons/mail/static/src/models/tracking_value_item.js
@@ -4,6 +4,7 @@ import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { format } from 'web.field_utils';
+import { session } from '@web/session';
registerModel({
name: 'TrackingValueItem',
@@ -53,7 +54,7 @@ registerModel({
case 'monetary':
return format.monetary(this.value, undefined, {
currency: this.currencyId
- ? this.env.session.currencies[this.currencyId]
+ ? session.currencies[this.currencyId]
: undefined,
forceString: true,
});
diff --git a/addons/mail/static/src/public/discuss_public_boot.js b/addons/mail/static/src/public/discuss_public_boot.js
index 88c3fa69614..2bbf404ce74 100644
--- a/addons/mail/static/src/public/discuss_public_boot.js
+++ b/addons/mail/static/src/public/discuss_public_boot.js
@@ -1,20 +1,16 @@
/** @odoo-module **/
import { data } from 'mail.discuss_public_channel_template';
+import { makeMessagingToLegacyEnv } from '@mail/utils/make_messaging_to_legacy_env';
import { DialogManagerContainer } from '@mail/components/dialog_manager_container/dialog_manager_container';
import { DiscussPublicViewContainer } from '@mail/components/discuss_public_view_container/discuss_public_view_container';
-import { MessagingService } from '@mail/services/messaging/messaging';
+import { messagingService } from '@mail/services/messaging_service';
import { processTemplates } from '@web/core/assets';
import { MainComponentsContainer } from '@web/core/main_components_container';
import { registry } from '@web/core/registry';
import { makeEnv, startServices } from '@web/env';
-import { session } from '@web/session';
-
-import * as AbstractService from 'web.AbstractService';
-import { serviceRegistry as legacyServiceRegistry } from 'web.core';
-import * as legacyEnv from 'web.env';
import {
makeLegacyCrashManagerService,
makeLegacyDialogMappingService,
@@ -23,6 +19,10 @@ import {
makeLegacySessionService,
mapLegacyEnvToWowlEnv,
} from '@web/legacy/utils';
+import { session } from '@web/session';
+
+import * as AbstractService from 'web.AbstractService';
+import * as legacyEnv from 'web.env';
import * as legacySession from 'web.session';
const { Component, mount, whenReady } = owl;
@@ -31,6 +31,15 @@ Component.env = legacyEnv;
(async function boot() {
await whenReady();
+
+ const messagingValuesService = {
+ start() {
+ return {
+ autofetchPartnerImStatus: false,
+ };
+ }
+ };
+
AbstractService.prototype.deployServices(Component.env);
const serviceRegistry = registry.category('services');
serviceRegistry.add('legacy_rpc', makeLegacyRpcService(Component.env));
@@ -38,6 +47,10 @@ Component.env = legacyEnv;
serviceRegistry.add('legacy_notification', makeLegacyNotificationService(Component.env));
serviceRegistry.add('legacy_crash_manager', makeLegacyCrashManagerService(Component.env));
serviceRegistry.add('legacy_dialog_mapping', makeLegacyDialogMappingService(Component.env));
+ serviceRegistry.add('messaging_service_to_legacy_env', makeMessagingToLegacyEnv(Component.env));
+
+ serviceRegistry.add('messaging', messagingService);
+ serviceRegistry.add('messagingValues', messagingValuesService);
const mainComponentsRegistry = registry.category('main_components');
mainComponentsRegistry.add('DiscussPublicViewContainer', { Component: DiscussPublicViewContainer });
@@ -61,11 +74,6 @@ Component.env = legacyEnv;
]);
mapLegacyEnvToWowlEnv(Component.env, env);
odoo.isReady = true;
- legacyServiceRegistry.add('messaging', MessagingService.extend({
- messagingValues: {
- autofetchPartnerImStatus: false,
- },
- }));
await mount(MainComponentsContainer, document.body, { env, templates, dev: env.debug });
createDiscussPublicView();
})();
diff --git a/addons/mail/static/src/services/messaging/messaging.js b/addons/mail/static/src/services/messaging/messaging.js
deleted file mode 100644
index 31d6f8fb0f6..00000000000
--- a/addons/mail/static/src/services/messaging/messaging.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/** @odoo-module **/
-
-import { ModelManager } from '@mail/model/model_manager';
-
-import AbstractService from 'web.AbstractService';
-
-export const MessagingService = AbstractService.extend({
- dependencies: ['bus_service'],
- messagingValues: {},
- /**
- * @override
- */
- init(env) {
- this._super(env);
- this.modelManager = new ModelManager(env);
- },
- /**
- * @override
- */
- start() {
- this._super();
- this.modelManager.start(this.messagingValues);
- },
- /**
- * Returns the messaging record once it is initialized. This method should
- * be considered the main entry point to the messaging system for outside
- * code.
- *
- * @returns {Messaging}
- **/
- async get() {
- return this.modelManager.getMessaging();
- }
-});
diff --git a/addons/mail/static/src/services/messaging_service.js b/addons/mail/static/src/services/messaging_service.js
new file mode 100644
index 00000000000..499d7a3906b
--- /dev/null
+++ b/addons/mail/static/src/services/messaging_service.js
@@ -0,0 +1,44 @@
+/** @odoo-module **/
+
+import { ModelManager } from '@mail/model/model_manager';
+
+export const messagingService = {
+ dependencies: [
+ 'effect',
+ 'legacy_bus_service',
+ 'localization',
+ 'messagingValues',
+ 'orm',
+ 'router',
+ 'rpc',
+ 'ui',
+ 'user',
+ ],
+
+ start(env, { messagingValues }) {
+ const modelManager = new ModelManager(env);
+ this._startModelManager(modelManager, messagingValues);
+
+ return {
+ /**
+ * Returns the messaging record once it is initialized. This method
+ * should be considered the main entry point to the messaging system
+ * for outside code.
+ *
+ * @returns {mail.messaging}
+ **/
+ async get() {
+ return modelManager.getMessaging();
+ },
+ modelManager,
+ };
+ },
+ /**
+ * Separate method to control creation delay in tests.
+ *
+ * @private
+ */
+ _startModelManager(modelManager, messagingValues) {
+ modelManager.start(messagingValues);
+ },
+};
diff --git a/addons/mail/static/src/services/systray_service/systray_service.js b/addons/mail/static/src/services/systray_service.js
similarity index 77%
rename from addons/mail/static/src/services/systray_service/systray_service.js
rename to addons/mail/static/src/services/systray_service.js
index 41e25e7cf83..9553c9cba09 100644
--- a/addons/mail/static/src/services/systray_service/systray_service.js
+++ b/addons/mail/static/src/services/systray_service.js
@@ -3,18 +3,14 @@
import { MessagingMenuContainer } from '@mail/components/messaging_menu_container/messaging_menu_container';
import { RtcActivityNoticeContainer } from '@mail/components/rtc_activity_notice_container/rtc_activity_notice_container';
-import AbstractService from 'web.AbstractService';
import { registry } from '@web/core/registry';
const systrayRegistry = registry.category('systray');
-export const SystrayService = AbstractService.extend({
+export const systrayService = {
dependencies: ['messaging'],
- /**
- * @override {web.AbstractService}
- */
- async start() {
+ start() {
systrayRegistry.add('mail.MessagingMenuContainer', { Component: MessagingMenuContainer });
systrayRegistry.add('mail.RtcActivityNoticeContainer', { Component: RtcActivityNoticeContainer }, { sequence: 100 });
},
-});
+};
diff --git a/addons/mail/static/src/utils/make_messaging_to_legacy_env.js b/addons/mail/static/src/utils/make_messaging_to_legacy_env.js
new file mode 100644
index 00000000000..a9a32563dd3
--- /dev/null
+++ b/addons/mail/static/src/utils/make_messaging_to_legacy_env.js
@@ -0,0 +1,11 @@
+
+/** @odoo-module **/
+
+export function makeMessagingToLegacyEnv(legacyEnv) {
+ return {
+ dependencies: ['messaging'],
+ start(_, { messaging }) {
+ legacyEnv.services.messaging = messaging;
+ },
+ };
+}
diff --git a/addons/mail/static/src/webclient/commands/mail_providers.js b/addons/mail/static/src/webclient/commands/mail_providers.js
index 4cd0ab17f26..81664f16e94 100644
--- a/addons/mail/static/src/webclient/commands/mail_providers.js
+++ b/addons/mail/static/src/webclient/commands/mail_providers.js
@@ -31,7 +31,7 @@ const commandProviderRegistry = registry.category("command_provider");
commandProviderRegistry.add("partner", {
namespace: "@",
async provide(newEnv, options) {
- const messaging = await Component.env.services.messaging.get();
+ const messaging = await newEnv.services.messaging.get();
const suggestions = [];
await messaging.models['Partner'].imSearch({
callback(partners) {
@@ -58,7 +58,7 @@ commandProviderRegistry.add("partner", {
commandProviderRegistry.add("channel", {
namespace: "#",
async provide(newEnv, options) {
- const messaging = await Component.env.services.messaging.get();
+ const messaging = await newEnv.services.messaging.get();
const channels = await messaging.models['Thread'].searchChannelsToOpen({
limit: 10,
searchTerm: options.searchValue,
diff --git a/addons/mail/static/src/widgets/discuss/discuss.js b/addons/mail/static/src/widgets/discuss/discuss.js
deleted file mode 100644
index 3da180743bb..00000000000
--- a/addons/mail/static/src/widgets/discuss/discuss.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/** @odoo-module **/
-
-import { DiscussContainer } from "@mail/components/discuss_container/discuss_container";
-
-import AbstractAction from 'web.AbstractAction';
-
-const { App } = owl;
-
-export const DiscussWidget = AbstractAction.extend({
- template: 'mail.widgets.Discuss',
- /**
- * @override {web.AbstractAction}
- * @param {web.ActionManager} parent
- * @param {Object} action
- * @param {Object} [action.context]
- * @param {string} [action.context.active_id]
- * @param {Object} [action.params]
- * @param {string} [action.params.default_active_id]
- * @param {Object} [options={}]
- */
- init(parent, action, options={}) {
- this._super(...arguments);
-
- this.app = undefined;
- this.action = action;
- this.actionManager = parent;
- },
- /**
- * @override {web.AbstractAction}
- */
- destroy() {
- if (this.app) {
- this.app.destroy();
- this.app = undefined;
- }
- if (this.$buttons) {
- this.$buttons.off().remove();
- }
- this._super(...arguments);
- },
- /**
- * @override {web.AbstractAction}
- */
- async on_attach_callback() {
- this._super(...arguments);
- if (this.app) {
- // prevent twice call to on_attach_callback (FIXME)
- return;
- }
-
- this.app = new App(DiscussContainer, {
- templates: window.__OWL_TEMPLATES__,
- env: owl.Component.env,
- dev: owl.Component.env.isDebug(),
- translateFn: owl.Component.env._t,
- translatableAttributes: ["data-tooltip"],
- props: {
- action: this.action,
- actionManager: this.actionManager,
- },
- });
- await this.app.mount(this.el);
- },
- /**
- * @override {web.AbstractAction}
- */
- on_detach_callback() {
- this._super(...arguments);
- if (this.app) {
- this.app.destroy();
- }
- this.app = undefined;
- },
-});
diff --git a/addons/mail/static/src/widgets/discuss/discuss.scss b/addons/mail/static/src/widgets/discuss/discuss.scss
deleted file mode 100644
index e7a19f7ec2c..00000000000
--- a/addons/mail/static/src/widgets/discuss/discuss.scss
+++ /dev/null
@@ -1,10 +0,0 @@
-// ------------------------------------------------------------------
-// Layout
-// ------------------------------------------------------------------
-
-.o_widget_Discuss {
- display: flex;
- flex: 0 0 100%;
- flex-flow: column;
- height: 100%;
-}
diff --git a/addons/mail/static/src/widgets/discuss/discuss.xml b/addons/mail/static/src/widgets/discuss/discuss.xml
deleted file mode 100644
index 5826f157aff..00000000000
--- a/addons/mail/static/src/widgets/discuss/discuss.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/addons/mail/static/tests/helpers/mock_server.js b/addons/mail/static/tests/helpers/mock_server.js
index 2df186cc7f6..07f98e63d11 100644
--- a/addons/mail/static/tests/helpers/mock_server.js
+++ b/addons/mail/static/tests/helpers/mock_server.js
@@ -1,57 +1,25 @@
/** @odoo-module **/
-import { getPyEnv, nextAnimationFrame } from '@mail/../tests/helpers/test_utils';
+import { getPyEnv, TEST_USER_IDS } from '@mail/../tests/helpers/test_utils';
+
+import { patch } from "@web/core/utils/patch";
+import { MockServer } from "@web/../tests/helpers/mock_server";
-import MockServer from 'web.MockServer';
import { date_to_str, datetime_to_str } from 'web.time';
-MockServer.include({
- /**
- * Param 'data' may have keys for the different magic partners/users.
- *
- * Note: we must delete these keys, so that this is not
- * handled as a model definition.
- *
- * @override
- * @param {Object} [data.currentPartnerId]
- * @param {Object} [data.currentUserId]
- * @param {Object} [data.partnerRootId]
- * @param {Object} [data.publicPartnerId]
- * @param {Object} [data.publicUserId]
- * @param {Widget} [options.widget] mocked widget (use to call services)
- */
- init(data, options) {
- if (data && data.currentPartnerId) {
- this.currentPartnerId = data.currentPartnerId;
- delete data.currentPartnerId;
- }
- if (data && data.currentUserId) {
- this.currentUserId = data.currentUserId;
- delete data.currentUserId;
- }
- if (data && data.partnerRootId) {
- this.partnerRootId = data.partnerRootId;
- delete data.partnerRootId;
- }
- if (data && data.publicPartnerId) {
- this.publicPartnerId = data.publicPartnerId;
- delete data.publicPartnerId;
- }
- if (data && data.publicUserId) {
- this.publicUserId = data.publicUserId;
- delete data.publicUserId;
- }
- this._widget = options.widget;
+patch(MockServer.prototype, 'mail', {
+ init({ models }) {
this._super(...arguments);
+ Object.assign(this, TEST_USER_IDS);
- if (this.currentPartnerId) {
+ if (this.currentPartnerId && models && 'res.partner' in models) {
this.currentPartner = this.getRecords('res.partner', [['id', '=', this.currentPartnerId]])[0];
}
MockServer.currentMockServer = this;
// creation of the ir.model.fields records, required for tracked fields
- for (const modelName in data) {
- const fieldNamesToFields = data[modelName].fields;
+ for (const modelName in models) {
+ const fieldNamesToFields = models[modelName].fields;
for (const fname in fieldNamesToFields) {
if (fieldNamesToFields[fname].tracking) {
this.mockCreate('ir.model.fields', { model: modelName, name: fname });
@@ -65,6 +33,32 @@ MockServer.include({
async setup() {
this.pyEnv = await getPyEnv();
},
+ /**
+ * @override
+ */
+ async performRPC(route, args) {
+ if (route === '/mail/attachment/upload') {
+ const ufile = args.body.get('ufile');
+ const is_pending = args.body.get('is_pending') === 'true';
+ const model = is_pending ? 'mail.compose.message' : args.body.get('thread_model');
+ const id = is_pending ? 0 : parseInt(args.body.get('thread_id'));
+ const attachmentId = this.mockCreate('ir.attachment', {
+ // datas,
+ mimetype: ufile.type,
+ name: ufile.name,
+ res_id: id,
+ res_model: model,
+ });
+ const attachment = this.getRecords('ir.attachment', [['id', '=', attachmentId]])[0];
+ return {
+ 'filename': attachment.name,
+ 'id': attachment.id,
+ 'mimetype': attachment.mimetype,
+ 'size': attachment.file_size
+ };
+ }
+ return this._super(...arguments);
+ },
//--------------------------------------------------------------------------
// Private
@@ -73,34 +67,11 @@ MockServer.include({
/**
* @override
*/
- async _performFetch(resource, init) {
- if (resource === '/mail/attachment/upload') {
- const ufile = init.body.get('ufile');
- const is_pending = init.body.get('is_pending') === 'true';
- const model = is_pending ? 'mail.compose.message' : init.body.get('thread_model');
- const id = is_pending ? 0 : parseInt(init.body.get('thread_id'));
- const attachmentId = this.pyEnv['ir.attachment'].create({
- // datas,
- mimetype: ufile.type,
- name: ufile.name,
- res_id: id,
- res_model: model,
- });
- const attachment = this.getRecords('ir.attachment', [['id', '=', attachmentId]])[0];
- return new window.Response(JSON.stringify({
- 'filename': attachment.name,
- 'id': attachment.id,
- 'mimetype': attachment.mimetype,
- 'size': attachment.file_size
- }));
- }
- return this._super(...arguments);
- },
- /**
- * @override
- */
- async _performRpc(route, args) {
+ async _performRPC(route, args) {
// routes
+ if (route === '/longpolling/im_status') {
+ return [];
+ }
if (route === '/mail/message/post') {
if (args.thread_model === 'mail.channel') {
return this._mockMailChannelMessagePost(args.thread_id, args.post_data, args.context);
@@ -327,7 +298,7 @@ MockServer.include({
/**
* @override
*/
- _mockWrite(model) {
+ mockWrite(model) {
const initialTrackedFieldValuesByRecordId = this._mockMailThread_TrackPrepare(model);
const mockWriteResult = this._super(...arguments);
if (initialTrackedFieldValuesByRecordId) {
@@ -652,7 +623,7 @@ MockServer.include({
*/
_mockMailActivityActionDone(ids) {
const activities = this.getRecords('mail.activity', [['id', 'in', ids]]);
- this._mockUnlink('mail.activity', [activities.map(activity => activity.id)]);
+ this.mockUnlink('mail.activity', [activities.map(activity => activity.id)]);
},
/**
* Simulates `action_feedback` on `mail.activity`.
@@ -1249,7 +1220,7 @@ MockServer.include({
return matchingChannels;
};
- const mentionSuggestions = mentionSuggestionsFilter(this.data['mail.channel'].records, search, limit);
+ const mentionSuggestions = mentionSuggestionsFilter(this.models['mail.channel'].records, search, limit);
return mentionSuggestions;
},
@@ -1442,9 +1413,6 @@ MockServer.include({
* @returns {Object[]}
*/
async _mockMailMessage_MessageFetch(domain, max_id, min_id, limit = 30) {
- // TODO FIXME delay RPC until next potential render as a workaround
- // to OWL issue (possibly https://github.com/odoo/owl/issues/904)
- await nextAnimationFrame();
if (max_id) {
domain.push(['id', '<', max_id]);
}
@@ -1782,7 +1750,7 @@ MockServer.include({
if (record.user_id) {
const user = this.getRecords('res.users', [['id', '=', record.user_id]]);
if (user.partner_id) {
- const reason = this.data[model].fields['user_id'].string;
+ const reason = this.models[model].fields['user_id'].string;
this._mockMailThread_MessageAddSuggestedRecipient(result, user.partner_id, reason);
}
}
@@ -1967,9 +1935,9 @@ MockServer.include({
* Simulates `_message_track` on `mail.thread`
*/
_mockMailThread_MessageTrack(modelName, trackedFieldNames, initialTrackedFieldValuesByRecordId) {
- const trackFieldNamesToField = this._mockFieldsGet(modelName, [trackedFieldNames]);
+ const trackFieldNamesToField = this.mockFieldsGet(modelName, [trackedFieldNames]);
const tracking = {};
- const records = this.data[modelName].records;
+ const records = this.models[modelName].records;
for (const record of records) {
tracking[record.id] = this._mockMailBaseModel_MailTrack(modelName, trackFieldNamesToField, initialTrackedFieldValuesByRecordId[record.id], record);
}
@@ -2005,7 +1973,7 @@ MockServer.include({
* Simulates `_track_get_fields` on `mail.thread`
*/
_mockMailThread_TrackGetFields(model) {
- return Object.entries(this.data[model].fields).reduce((prev, next) => {
+ return Object.entries(this.models[model].fields).reduce((prev, next) => {
if (next[1].tracking) {
prev.push(next[0]);
}
@@ -2021,7 +1989,7 @@ MockServer.include({
return;
}
const initialTrackedFieldValuesByRecordId = {};
- for (const record of this.data[model].records) {
+ for (const record of this.models[model].records) {
const values = {};
initialTrackedFieldValuesByRecordId[record.id] = values;
for (const fname of trackedFieldNames) {
@@ -2041,7 +2009,7 @@ MockServer.include({
*/
_mockMailTrackingValue_CreateTrackingValues(initialValue, newValue, fieldName, field, modelName) {
let isTracked = true;
- const irField = this.data['ir.model.fields'].records.find(field => field.model === modelName && field.name === fieldName);
+ const irField = this.models['ir.model.fields'].records.find(field => field.model === modelName && field.name === fieldName);
if (!irField) {
return;
@@ -2504,36 +2472,4 @@ MockServer.include({
),
});
},
-
- //--------------------------------------------------------------------------
- // New mock server compatibility
- //--------------------------------------------------------------------------
-
- getRecords() {
- return this._getRecords(...arguments);
- },
- mockCreate() {
- return this._mockCreate(...arguments);
- },
- mockRead() {
- return this._mockRead(...arguments);
- },
- mockReadGroup() {
- return this._mockReadGroup(...arguments);
- },
- mockSearch() {
- return this._mockSearch(...arguments);
- },
- mockSearchCount() {
- return this._mockSearchCount(...arguments);
- },
- mockSearchRead() {
- return this._mockSearchRead(...arguments);
- },
- mockUnlink() {
- return this._mockUnlink(...arguments);
- },
- mockWrite() {
- return this._mockWrite(...arguments);
- },
});
diff --git a/addons/mail/static/tests/helpers/test_utils.js b/addons/mail/static/tests/helpers/test_utils.js
index c354fcf3647..a807e615cb6 100644
--- a/addons/mail/static/tests/helpers/test_utils.js
+++ b/addons/mail/static/tests/helpers/test_utils.js
@@ -1,39 +1,22 @@
/** @odoo-module **/
-import BusService from 'bus.BusService';
-
-import { DialogManagerContainer } from '@mail/components/dialog_manager_container/dialog_manager_container';
-import { ChatWindowManagerContainer } from '@mail/components/chat_window_manager_container/chat_window_manager_container';
import { MessagingMenuContainer } from '@mail/components/messaging_menu_container/messaging_menu_container';
import { insertAndReplace, replace } from '@mail/model/model_field_command';
-import { MessagingService } from '@mail/services/messaging/messaging';
-import { makeDeferred } from '@mail/utils/deferred';
import { getMessagingComponent } from '@mail/utils/messaging_component';
import { nextTick } from '@mail/utils/utils';
-import { DiscussWidget } from '@mail/widgets/discuss/discuss';
-import { addTimeControlToEnv } from '@mail/../tests/helpers/time_control';
+import { getAdvanceTime } from '@mail/../tests/helpers/time_control';
+import { getWebClientReady } from '@mail/../tests/helpers/webclient_setup';
-import core from 'web.core';
-import AbstractStorageService from 'web.AbstractStorageService';
-import RamStorage from 'web.RamStorage';
-import {
- createView,
- mock,
-} from 'web.test_utils';
-import Widget from 'web.Widget';
-import { browser } from '@web/core/browser/browser';
import { registry } from '@web/core/registry';
import { registerCleanup } from "@web/../tests/helpers/cleanup";
-import { getFixture, patchWithCleanup } from "@web/../tests/helpers/utils";
-import { createWebClient, getActionManagerServerData } from "@web/../tests/webclient/helpers";
+import { MockServer } from "@web/../tests/helpers/mock_server";
+import { getFixture, makeDeferred, patchWithCleanup } from "@web/../tests/helpers/utils";
+import { doAction, getActionManagerServerData } from "@web/../tests/webclient/helpers";
-import LegacyRegistry from "web.Registry";
-import MockServer from 'web.MockServer';
+import core from 'web.core';
-const { App, Component, EventBus } = owl;
+const { App, EventBus } = owl;
const { afterNextRender } = App;
-const { addMockEnvironment } = mock;
-
const modelDefinitionsPromise = new Promise(resolve => {
QUnit.begin(() => resolve(getModelDefinitions()));
});
@@ -59,44 +42,6 @@ function _createFakeDataTransfer(files) {
};
}
-/**
- * @private
- * @param {HTMLElement} target
- */
-async function mountChatWindowManagerContainer(target) {
- const app = new App(ChatWindowManagerContainer, {
- templates: window.__OWL_TEMPLATES__,
- env: owl.Component.env,
- dev: owl.Component.env.isDebug(),
- translateFn: owl.Component.env._t,
- translatableAttributes: ["data-tooltip"],
- });
- await app.mount(target);
-
- registerCleanup(() => {
- app.destroy();
- });
-}
-
-/**
- * @private
- * @param {HTMLElement} target
- */
-async function mountDialogManagerContainer(target) {
- const app = new App(DialogManagerContainer, {
- templates: window.__OWL_TEMPLATES__,
- env: owl.Component.env,
- dev: owl.Component.env.isDebug(),
- translateFn: owl.Component.env._t,
- translatableAttributes: ["data-tooltip"],
- });
- await app.mount(target);
-
- registerCleanup(() => {
- app.destroy();
- });
-}
-
//------------------------------------------------------------------------------
// Public: rendering timers
//------------------------------------------------------------------------------
@@ -198,7 +143,7 @@ const VIEW_TYPE_TO_ARCH = {
* server (creation, deletion, update of records...)
*/
export async function startServer() {
- const data = { ...TEST_USER_IDS };
+ const models = {};
const views = {};
const modelDefinitions = await modelDefinitionsPromise;
const recordsToInsertRegistry = registry.category('mail.model.definitions').category('recordsToInsert');
@@ -208,7 +153,7 @@ const VIEW_TYPE_TO_ARCH = {
// prevent tests from mutating the records.
records.push(...JSON.parse(JSON.stringify(recordsToInsertRegistry.get(modelName))));
}
- data[modelName] = { fields: { ...fields }, records };
+ models[modelName] = { fields: { ...fields }, records };
// generate default views for this model.
for (const [viewType, viewArch] of Object.entries(VIEW_TYPE_TO_ARCH)) {
@@ -217,13 +162,16 @@ const VIEW_TYPE_TO_ARCH = {
}
pyEnv = new Proxy(
{
+ getData() {
+ return this.mockServer.models;
+ },
+ getViews() {
+ return views;
+ },
...TEST_USER_IDS,
get currentPartner() {
return this.mockServer.currentPartner;
},
- getViews() {
- return views;
- }
},
{
get(target, name) {
@@ -305,7 +253,7 @@ const VIEW_TYPE_TO_ARCH = {
},
},
);
- pyEnv['mockServer'] = new MockServer(data, {});
+ pyEnv['mockServer'] = new MockServer({ models }, {});
await pyEnv['mockServer'].setup();
registerCleanup(() => pyEnv = undefined);
return pyEnv;
@@ -366,7 +314,7 @@ function getAfterEvent({ messagingBus }) {
clearTimeout(timeoutNoEvent);
// If the event is triggered before the end of the async function,
// ensure the function finishes its job before returning.
- await funcRes;
+ return await funcRes;
};
}
@@ -499,7 +447,7 @@ function getCreateMessageComponent({ env, modelManager, target }) {
function getCreateMessagingMenuComponent({ env, target }) {
return async function createMessagingMenuComponent() {
- return await createRootComponent({ env }, MessagingMenuContainer, { target });
+ return await createRootComponent(env, MessagingMenuContainer, { target });
};
}
@@ -527,7 +475,7 @@ function getCreateThreadViewComponent({ afterEvent, env, target }) {
'flex-flow': 'column',
height: '300px',
});
- target.append(div);
+ target.appendChild(div);
actualTarget = div;
} else {
actualTarget = target;
@@ -553,67 +501,41 @@ function getCreateThreadViewComponent({ afterEvent, env, target }) {
};
}
-function getOpenDiscuss({ afterNextRender, discuss, selector, widget }) {
+function getOpenDiscuss(webClient, { context, params, ...props } = {}) {
return async function openDiscuss() {
- widget.do_push_state = () => {};
- const discussWidget = new DiscussWidget(widget, discuss);
- await discussWidget.appendTo($(selector));
- await afterNextRender(() => discussWidget.on_attach_callback());
- // Some changes in the models are made on mount, but these changes don't
- // cause a rerender directly, they cause the model to fetch more data
- // but we cannot wait for that data to come back as the model manager
- // doesn't expose it. This means that in the following microticks, the
- // data will come back from the server and cause a render. The following
- // is a way for us to catch the render cascade caused by the data coming
- // back and wait for it.
- await afterNextRender(() => {
- discussWidget.app.root.render();
- });
- return discussWidget;
+ const actionOpenDiscuss = {
+ // hardcoded actionId, required for discuss_container props validation.
+ id: 104,
+ context,
+ params,
+ tag: 'mail.action_discuss',
+ type: 'ir.actions.client',
+ };
+ await afterNextRender(() => doAction(webClient, actionOpenDiscuss, { props }));
};
}
+//------------------------------------------------------------------------------
+// Public: start function helpers
+//------------------------------------------------------------------------------
+
/**
* Main function used to make a mocked environment with mocked messaging env.
*
* @param {Object} [param0={}]
- * @param {string} [param0.arch] makes only sense when `param0.hasView` is set:
- * the arch to use in createView.
- * @param {Object} [param0.archs]
- * @param {boolean} [param0.autoOpenDiscuss=false] determine whether mounted discuss should be
+ * @param {boolean} [param0.autoOpenDiscuss=false] determine if discuss should be
* open initially. Deprecated, call openDiscuss() instead.
- * @param {boolean} [param0.debug=false]
- * @param {Object} [param0.data] makes only sense when `param0.hasView` is set:
- * the data to use in createView.
- * @param {Object} [param0.discuss={}] provide data that is passed to discuss widget
- * (= client action) as 2nd positional argument.
- * @param {Object} [param0.env={}]
- * @param {function} [param0.mockFetch]
+ * @param {Object} [param0.serverData] The data to pass to the webClient
+ * @param {Object} [param0.discuss={}] provide data that is passed to the discuss action.
+ * @param {Object} [param0.legacyServices]
+ * @param {Object} [param0.services]
* @param {function} [param0.mockRPC]
- * @param {boolean} [param0.hasWebClient=false] if set, use
- * createWebClient
* @param {boolean} [param0.hasTimeControl=false] if set, all flow of time
- * with `env.browser.setTimeout` are fully controlled by test itself.
- * @see addTimeControlToEnv that adds `advanceTime` function in
- * `env.testUtils`.
- * @param {boolean} [param0.hasView=false] if set, use createView to create a
- * view instead of a generic widget.
+ * with `messaging.browser.setTimeout` are fully controlled by test itself.
* @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.
- * @param {string} [param0.model] makes only sense when `param0.hasView` is set:
- * the model to use in createView.
- * @param {integer} [param0.res_id] makes only sense when `param0.hasView` is set:
- * the res_id to use in createView.
- * @param {Object} [param0.services]
- * @param {Object} [param0.session]
- * @param {Element} [param0.target] if provided, the component will be mounted inside
- * that element (only used if `params0.hasWebClient` is true)
- * @param {Object} [param0.View] makes only sense when `param0.hasView` is set:
- * the View class to use in createView.
- * @param {Object} [param0.viewOptions] makes only sense when `param0.hasView`
- * is set: the view options to use in createView.
* @param {Object} [param0.waitUntilEvent]
* @param {String} [param0.waitUntilEvent.eventName]
* @param {String} [param0.waitUntilEvent.message]
@@ -631,7 +553,6 @@ function getOpenDiscuss({ afterNextRender, discuss, selector, widget }) {
* as param of `messagingBeforeCreationDeferred`. To make sure messaging is
* not initialized, test should mock RPC `mail/init_messaging` and block its
* resolution.
- * @param {...Object} [param0.kwargs]
* @throws {Error} in case some provided parameters are wrong, such as
* `waitUntilMessagingCondition`.
* @returns {Object}
@@ -643,189 +564,19 @@ async function start(param0 = {}) {
throttle: func => func,
});
const {
- autoOpenDiscuss = false,
+ autoOpenDiscuss,
discuss = {},
- env: providedEnv = {},
- hasWebClient = false,
- hasTimeControl = false,
- hasView = false,
- loadingBaseDelayDuration = 0,
- messagingBeforeCreationDeferred = Promise.resolve(),
- target: webclientTarget = getFixture(),
+ hasTimeControl,
waitUntilEvent,
waitUntilMessagingCondition = 'initialized',
} = param0;
+ const target = param0['target'] || getFixture();
+ param0['target'] = target;
if (!['none', 'created', 'initialized'].includes(waitUntilMessagingCondition)) {
throw Error(`Unknown parameter value ${waitUntilMessagingCondition} for 'waitUntilMessaging'.`);
}
- delete param0.autoOpenDiscuss;
- delete param0.env;
- delete param0.hasWebClient;
- delete param0.hasTimeControl;
- delete param0.hasView;
- delete param0.target;
const messagingBus = new EventBus();
- const { debug = false } = param0;
const testSetupDoneDeferred = makeDeferred();
- let env = {
- ...providedEnv,
- browser: {
- ...providedEnv.browser,
- innerWidth: browser.innerWidth,
- innerHeight: browser.innerHeight,
- },
- device: {
- ...providedEnv.device,
- isMobile: owl.Component.env.device.isMobile,
- },
- };
- env.session = Object.assign(
- {
- is_bound: Promise.resolve(),
- url: s => s,
- },
- env.session
- );
- env.isDebug = env.isDebug || (() => true);
- if (hasTimeControl) {
- env = addTimeControlToEnv(env);
- }
-
- const services = Object.assign({}, {
- bus_service: BusService.extend({
- _beep() {}, // Do nothing
- _poll() {}, // Do nothing
- _registerWindowUnload() {}, // Do nothing
- isOdooFocused() {
- return true;
- },
- updateOption() {},
- }),
- local_storage: AbstractStorageService.extend({ storage: new RamStorage() }),
- messaging: MessagingService.extend({
- // test specific values
- messagingValues: {
- autofetchPartnerImStatus: false,
- disableAnimation: true,
- loadingBaseDelayDuration,
- messagingBus,
- },
- /**
- * Override to ensure tests run in debug mode to catch all potential
- * programming errors and provide better message when they happen.
- */
- init(...args) {
- this._super(...args);
- this.modelManager.isDebug = true;
- },
- /**
- * Override:
- * - to ensure the test setup is complete before starting otherwise
- * for example the mock server might not be ready yet at init
- * messaging,
- * - to add control on when messaging is created, useful to test
- * spinners and race conditions.
- *
- * @override
- */
- async start() {
- const _super = this._super.bind(this);
- await testSetupDoneDeferred;
- await messagingBeforeCreationDeferred;
- _super();
- },
- }),
- }, param0.services);
-
- if (!param0.data && (!param0.serverData || !param0.serverData.models)) {
- pyEnv = pyEnv || await startServer();
- const data = pyEnv.mockServer.data;
- Object.assign(data, TEST_USER_IDS);
- if (hasWebClient) {
- param0.serverData = param0.serverData || getActionManagerServerData();
- param0.serverData.models = data;
- param0.serverData.views = { ...pyEnv.getViews(), ...param0.serverData.views };
- } else {
- param0.data = data;
- }
- }
-
- const kwargs = Object.assign({}, param0, {
- archs: Object.assign({}, {
- 'mail.message,false,search': '
'
- }, param0.archs),
- debug: param0.debug || false,
- services: Object.assign({}, services, param0.services),
- }, { env });
- let widget;
- let mockServer;
- let testEnv;
- const selector = debug ? 'body' : '#qunit-fixture';
- if (hasView) {
- widget = await createView(kwargs);
- } else if (hasWebClient) {
- let serverData;
- if (!kwargs.serverData) {
- serverData = getActionManagerServerData();
- } else {
- serverData = kwargs.serverData;
- delete kwargs.serverData;
- }
-
- if (kwargs.actions) {
- const actions = {};
- kwargs.actions.forEach((act) => {
- actions[act.xml_id || act.id] = act;
- });
- Object.assign(serverData.actions, actions);
- delete kwargs.actions;
- }
-
- Object.assign(serverData.views, kwargs.archs);
- delete kwargs.archs;
-
- Object.assign(serverData.models, kwargs.data);
- delete kwargs.data;
-
- const mockRPC = kwargs.mockRPC;
- delete kwargs.mockRPC;
-
- if (kwargs.services) {
- const serviceRegistry = kwargs.serviceRegistry = new LegacyRegistry();
- for (const sname in kwargs.services) {
- serviceRegistry.add(sname, kwargs.services[sname]);
- }
- delete kwargs.services;
- }
-
- const legacyParams = kwargs;
- legacyParams.withLegacyMockServer = true;
- legacyParams.env = env;
-
- widget = await createWebClient({ target: webclientTarget, serverData, mockRPC, legacyParams });
- } else {
- const Parent = Widget.extend({ do_push_state() {} });
- const parent = new Parent();
- mockServer = await addMockEnvironment(parent, kwargs);
- widget = new Widget(parent);
- await widget.appendTo($(selector));
- Object.assign(widget, {
- destroy() {
- delete widget.destroy;
- parent.destroy();
- },
- });
- }
- // get the final test env after execution of createView/createWebClient/addMockEnvironment
- testEnv = Component.env;
- patchWithCleanup(browser, {
- fetch: testEnv.browser.fetch,
- });
- // link the pyEnv to the actual mockServer after execution of createView/createWebClient/addMockEnvironment
- if (pyEnv) {
- pyEnv.mockServer = MockServer.currentMockServer;
- mockServer = pyEnv.mockServer;
- }
const afterEvent = getAfterEvent({ messagingBus });
let waitUntilEventPromise;
if (waitUntilEvent) {
@@ -834,22 +585,15 @@ async function start(param0 = {}) {
testSetupDoneDeferred.resolve();
waitUntilEventPromise = Promise.resolve();
}
- const result = {
- afterEvent,
- env: testEnv,
- mockServer,
- pyEnv,
- widget,
- wowlEnv: hasWebClient ? widget.env : undefined,
- };
- if (hasTimeControl) {
- result['advanceTime'] = env.testUtils.advanceTime;
- }
- const { modelManager } = testEnv.services.messaging;
- registerCleanup(async() => {
- if (!hasWebClient) {
- widget.destroy();
- }
+
+ pyEnv = await getPyEnv();
+ param0.serverData = param0.serverData || getActionManagerServerData();
+ param0.serverData.models = { ...pyEnv.getData(), ...param0.serverData.models };
+ param0.serverData.views = { ...pyEnv.getViews(), ...param0.serverData.views };
+ const webClient = await getWebClientReady({ ...param0, messagingBus, testSetupDoneDeferred });
+
+ const { modelManager } = webClient.env.services.messaging;
+ registerCleanup(async () => {
await modelManager.messagingInitializedPromise;
modelManager.messaging.delete();
});
@@ -860,30 +604,37 @@ async function start(param0 = {}) {
await modelManager.messagingCreatedPromise;
await modelManager.messagingInitializedPromise;
}
- const openDiscuss = getOpenDiscuss({ afterNextRender, discuss, selector, widget });
+ const openDiscuss = getOpenDiscuss(webClient, discuss);
if (autoOpenDiscuss) {
- const discussWidget = await openDiscuss();
- result['discussWidget'] = discussWidget;
+ await openDiscuss();
}
- const target = hasWebClient ? webclientTarget : widget.el;
- await mountChatWindowManagerContainer(target);
- await mountDialogManagerContainer(target);
+ // link the pyEnv to the actual mockServer after execution of createWebClient.
+ pyEnv.mockServer = MockServer.currentMockServer;
+ const openView = async (action, options) => {
+ action['type'] = action['type'] || 'ir.actions.act_window';
+ return doAction(webClient, action, { props: options });
+ };
await waitUntilEventPromise;
return {
- ...result,
+ advanceTime: hasTimeControl ? getAdvanceTime() : undefined,
+ afterEvent,
afterNextRender,
click: getClick({ afterNextRender }),
- createChatterContainerComponent: getCreateChatterContainerComponent({ afterEvent, env: testEnv, target }),
- createComposerComponent: getCreateComposerComponent({ env: testEnv, modelManager, target }),
- createComposerSuggestionComponent: getCreateComposerSuggestionComponent({ env: testEnv, modelManager, target }),
- createMessageComponent: getCreateMessageComponent({ env: testEnv, modelManager, target }),
- createMessagingMenuComponent: getCreateMessagingMenuComponent({ env: testEnv, target }),
- createNotificationListComponent: getCreateNotificationListComponent({ env: testEnv, modelManager, target }),
- createRootMessagingComponent: (componentName, props) => createRootMessagingComponent(testEnv, componentName, { props, target }),
- createThreadViewComponent: getCreateThreadViewComponent({ afterEvent, env: testEnv, target }),
+ createChatterContainerComponent: getCreateChatterContainerComponent({ afterEvent, env: webClient.env, target }),
+ createComposerComponent: getCreateComposerComponent({ env: webClient.env, modelManager, target }),
+ createComposerSuggestionComponent: getCreateComposerSuggestionComponent({ env: webClient.env, modelManager, target }),
+ createMessageComponent: getCreateMessageComponent({ env: webClient.env, modelManager, target }),
+ createMessagingMenuComponent: getCreateMessagingMenuComponent({ env: webClient.env, target }),
+ createNotificationListComponent: getCreateNotificationListComponent({ env: webClient.env, modelManager, target }),
+ createRootMessagingComponent: (componentName, props) => createRootMessagingComponent(webClient.env, componentName, { props, target }),
+ createThreadViewComponent: getCreateThreadViewComponent({ afterEvent, env: webClient.env, target }),
+ env: webClient.env,
insertText,
messaging: modelManager.messaging,
openDiscuss,
+ openView,
+ pyEnv,
+ webClient,
};
}
diff --git a/addons/mail/static/tests/helpers/time_control.js b/addons/mail/static/tests/helpers/time_control.js
index 78ca6a14a61..600735b5aa8 100644
--- a/addons/mail/static/tests/helpers/time_control.js
+++ b/addons/mail/static/tests/helpers/time_control.js
@@ -3,17 +3,9 @@
import { nextTick } from '@mail/utils/utils';
import { browser } from '@web/core/browser/browser';
-import { patchWithCleanup } from '@web/../tests/helpers/utils';
+import { patchWithCleanup } from "@web/../tests/helpers/utils";
-/**
- * @param {Object} [providedEnv={}]
- * @returns {Object}
- */
- export function addTimeControlToEnv(providedEnv = {}) {
- const env = { ...providedEnv };
- if (!env.browser) {
- env.browser = {};
- }
+export function getAdvanceTime() {
// list of timeout ids that have timed out.
let timedOutIds = [];
// key: timeoutId, value: func + remaining duration
@@ -39,31 +31,24 @@ import { patchWithCleanup } from '@web/../tests/helpers/utils';
return timeoutId;
},
});
- if (!env.testUtils) {
- env.testUtils = {};
- }
- Object.assign(env.testUtils, {
- advanceTime: async duration => {
- await nextTick();
- for (const id of timeouts.keys()) {
- const timeout = timeouts.get(id);
- if (timeout.isTimedOut) {
- continue;
- }
- timeout.duration = Math.max(timeout.duration - duration, 0);
- if (timeout.duration === 0) {
- timedOutIds.push(id);
- }
+ return async function (duration) {
+ await nextTick();
+ for (const id of timeouts.keys()) {
+ const timeout = timeouts.get(id);
+ if (timeout.isTimedOut) {
+ continue;
}
- while (timedOutIds.length > 0) {
- const id = timedOutIds.shift();
- const timeout = timeouts.get(id);
- timeouts.delete(id);
- timeout.func();
- await nextTick();
+ timeout.duration = Math.max(timeout.duration - duration, 0);
+ if (timeout.duration === 0) {
+ timedOutIds.push(id);
}
+ }
+ while (timedOutIds.length > 0) {
+ const id = timedOutIds.shift();
+ const timeout = timeouts.get(id);
+ timeouts.delete(id);
+ timeout.func();
await nextTick();
- },
- });
- return env;
+ }
+ };
}
diff --git a/addons/mail/static/tests/helpers/webclient_setup.js b/addons/mail/static/tests/helpers/webclient_setup.js
new file mode 100644
index 00000000000..6c10d2cca2b
--- /dev/null
+++ b/addons/mail/static/tests/helpers/webclient_setup.js
@@ -0,0 +1,174 @@
+/** @odoo-module **/
+
+import { legacyBusService } from '@bus/js/services/legacy/legacy_bus_service';
+
+import BusService from 'bus.BusService';
+
+import { ChatWindowManagerContainer } from '@mail/components/chat_window_manager_container/chat_window_manager_container';
+import { DialogManagerContainer } from '@mail/components/dialog_manager_container/dialog_manager_container';
+import { DiscussContainer } from '@mail/components/discuss_container/discuss_container';
+import { messagingService } from '@mail/services/messaging_service';
+import { makeMessagingToLegacyEnv } from '@mail/utils/make_messaging_to_legacy_env';
+
+import { registry } from '@web/core/registry';
+import { patchWithCleanup } from "@web/../tests/helpers/utils";
+import { createWebClient } from "@web/../tests/webclient/helpers";
+
+import AbstractStorageService from 'web.AbstractStorageService';
+import RamStorage from 'web.RamStorage';
+import LegacyRegistry from 'web.Registry';
+
+const WEBCLIENT_LOAD_ROUTES = [
+ '/web/webclient/load_menus',
+ '/web/dataset/call_kw/res.users/load_views',
+];
+const WEBCLIENT_PARAMETER_NAMES = new Set(['legacyParams', 'mockRPC', 'serverData', 'target', 'webClientClass']);
+const SERVICES_PARAMETER_NAMES = new Set([
+ 'legacyServices', 'loadingBaseDelayDuration', 'messagingBeforeCreationDeferred',
+ 'messagingBus', 'services', 'testSetupDoneDeferred',
+]);
+
+/**
+ * Add required components to the main component registry.
+ */
+ function setupMainComponentRegistry() {
+ const mainComponentRegistry = registry.category('main_components');
+ mainComponentRegistry.add('ChatWindowManagerContainer', { Component: ChatWindowManagerContainer });
+ mainComponentRegistry.add('DialogManagerContainer', { Component: DialogManagerContainer });
+ registry.category('actions').add('mail.action_discuss', DiscussContainer);
+}
+
+/**
+ * Setup both legacy and new service registries.
+ *
+ * @param {Object} param0
+ * @param {Object} [param0.services]
+ * @param {Object} [param0.legacyServices]
+ * @param {number} [param0.loadingBaseDelayDuration=0]
+ * @param {Promise} [param0.messagingBeforeCreationDeferred=Promise.resolve()]
+ * Deferred that let tests block messaging creation and simulate resolution.
+ * Useful for testing components behavior when messaging is not yet created.
+ * @param {EventBus} [param0.messagingBus]
+ * @param {Promise} [param0.testSetupDoneDeferred] The Promise to whose revolution has to be
+ * waited before starting the messaging service.
+ * @returns {LegacyRegistry} The registry containing all the legacy services that will be passed
+ * to the webClient as a legacy parameter.
+ */
+function setupMessagingServiceRegistries({
+ legacyServices,
+ loadingBaseDelayDuration = 0,
+ messagingBeforeCreationDeferred = Promise.resolve(),
+ messagingBus,
+ services,
+ testSetupDoneDeferred,
+ }) {
+ const serviceRegistry = registry.category('services');
+ const legacyServiceRegistry = new LegacyRegistry();
+ legacyServices = {
+ bus_service: BusService.extend({
+ _beep() {}, // Do nothing
+ _poll() {}, // Do nothing
+ _registerWindowUnload() {}, // Do nothing
+ isOdooFocused() {
+ return true;
+ },
+ updateOption() {},
+ }),
+ local_storage: AbstractStorageService.extend({ storage: new RamStorage() }),
+ ...legacyServices,
+ };
+
+ patchWithCleanup(messagingService, {
+ async _startModelManager() {
+ const _super = this._super.bind(this);
+ await testSetupDoneDeferred;
+ await messagingBeforeCreationDeferred;
+ return _super(...arguments);
+ },
+ });
+
+ const messagingValues = {
+ start() {
+ return {
+ autofetchPartnerImStatus: false,
+ disableAnimation: true,
+ loadingBaseDelayDuration,
+ messagingBus,
+ };
+ }
+ };
+
+ services = {
+ messagingValues,
+ ...services,
+ };
+ // during tests, we need to wait for the legacyBusService to be added before adding the messaging
+ // service to the registry or it will result in an error during the webClient creation.
+ const addMessagingToRegistryFn = ev => {
+ const { key, operation } = ev.detail;
+ if (key === 'legacy_bus_service' && operation === 'add') {
+ serviceRegistry.add('messaging', messagingService);
+ serviceRegistry.add('messaging_service_to_legacy_env', makeMessagingToLegacyEnv(owl.Component.env));
+ serviceRegistry.removeEventListener('UPDATE', addMessagingToRegistryFn);
+ }
+ };
+ serviceRegistry.addEventListener('UPDATE', addMessagingToRegistryFn);
+
+ Object.entries(services).forEach(([serviceName, service]) => {
+ serviceRegistry.add(serviceName, service);
+ });
+ Object.entries(legacyServices).forEach(([serviceName, service]) => {
+ legacyServiceRegistry.add(serviceName, service);
+ });
+
+ return legacyServiceRegistry;
+}
+
+/**
+ * Creates a properly configured instance of WebClient, with the messaging service and all it's
+ * dependencies initialized.
+ *
+ * @param {Object} param0
+ * @param {Object} [param0.serverData]
+ * @param {Object} [param0.services]
+ * @param {Object} [param0.legacyServices]
+ * @param {Object} [param0.legacyParams={}]
+ * @param {Object} [param0.loadingBaseDelayDuration]
+ * @param {Object} [param0.messagingBeforeCreationDeferred]
+ * @param {Promise} [param0.testSetupDoneDeferred] The Promise to whose revolution has to be
+ * waited before starting the messaging service.
+ * @param {EventBus} [param0.messagingBus] The event bus to be used by messaging.
+ * @returns {WebClient}
+ */
+ async function getWebClientReady(param0) {
+ setupMainComponentRegistry();
+
+ const servicesParameters = {};
+ const param0Entries = Object.entries(param0);
+ for (const [parameterName, value] of param0Entries) {
+ if (SERVICES_PARAMETER_NAMES.has(parameterName)) {
+ servicesParameters[parameterName] = value;
+ }
+ }
+ const legacyServiceRegistry = setupMessagingServiceRegistries(servicesParameters);
+
+ const webClientParameters = {};
+ for (const [parameterName, value] of param0Entries) {
+ if (WEBCLIENT_PARAMETER_NAMES.has(parameterName)) {
+ webClientParameters[parameterName] = value;
+ }
+ }
+ webClientParameters.legacyParams = {
+ legacyServicesToDeployInWowlEnv: {
+ legacy_bus_service: legacyBusService,
+ },
+ serviceRegistry: legacyServiceRegistry,
+ ...webClientParameters.legacyParams,
+ };
+ return createWebClient(webClientParameters);
+}
+
+export {
+ getWebClientReady,
+ WEBCLIENT_LOAD_ROUTES,
+};
diff --git a/addons/mail/static/tests/qunit_suite_tests/chatter_tests.js b/addons/mail/static/tests/qunit_suite_tests/chatter_tests.js
index 1f96a727c1b..0c66213ebb4 100644
--- a/addons/mail/static/tests/qunit_suite_tests/chatter_tests.js
+++ b/addons/mail/static/tests/qunit_suite_tests/chatter_tests.js
@@ -1,10 +1,10 @@
/** @odoo-module **/
import { start, startServer } from '@mail/../tests/helpers/test_utils';
+import { WEBCLIENT_LOAD_ROUTES } from '@mail/../tests/helpers/webclient_setup';
-import FormView from 'web.FormView';
-import ListView from 'web.ListView';
import testUtils from 'web.test_utils';
+import { patchWithCleanup } from '@web/../tests/helpers/utils';
QUnit.module('mail', {}, function () {
QUnit.module('Chatter');
@@ -13,19 +13,25 @@ QUnit.test('list activity widget with no activity', async function (assert) {
assert.expect(4);
const pyEnv = await startServer();
- await start({
- hasView: true,
- View: ListView,
- model: 'res.users',
- arch: '
',
- mockRPC: function (route) {
- if (!['/mail/init_messaging', '/mail/load_message_failures'].includes(route)) {
+ const views = {
+ 'res.users,false,list': '
',
+ };
+ const { openView } = await start({
+ mockRPC: function (route, args) {
+ if (
+ args.method !== 'get_views' &&
+ !['/mail/init_messaging', '/mail/load_message_failures', ...WEBCLIENT_LOAD_ROUTES].includes(route)
+ ) {
assert.step(route);
}
- return this._super(...arguments);
},
+ serverData: { views },
session: { uid: pyEnv.currentUserId },
});
+ await openView({
+ res_model: 'res.users',
+ views: [[false, 'list']],
+ });
assert.containsOnce(document.body, '.o_mail_activity .o_activity_color_default');
assert.strictEqual(document.querySelector('.o_activity_summary').innerText, '');
@@ -55,18 +61,24 @@ QUnit.test('list activity widget with activities', async function (assert) {
activity_summary: false,
activity_type_id: mailActivityTypeId2,
});
+ const views = {
+ 'res.users,false,list': '
',
+ };
- await start({
- hasView: true,
- View: ListView,
- model: 'res.users',
- arch: '
',
- mockRPC: function (route) {
- if (!['/mail/init_messaging', '/mail/load_message_failures'].includes(route)) {
+ const { openView } = await start({
+ mockRPC: function (route, args) {
+ if (
+ args.method !== 'get_views' &&
+ !['/mail/init_messaging', '/mail/load_message_failures', ...WEBCLIENT_LOAD_ROUTES].includes(route)
+ ) {
assert.step(route);
}
- return this._super(...arguments);
},
+ serverData: { views },
+ });
+ await openView({
+ res_model: 'res.users',
+ views: [[false, 'list']],
});
const firstRow = document.querySelector('.o_data_row');
@@ -95,17 +107,23 @@ QUnit.test('list activity widget with exception', async function (assert) {
activity_exception_icon: 'fa-warning',
});
- await start({
- hasView: true,
- View: ListView,
- model: 'res.users',
- arch: '
',
- mockRPC: function (route) {
- if (!['/mail/init_messaging', '/mail/load_message_failures'].includes(route)) {
+ const views = {
+ 'res.users,false,list': '
',
+ };
+ const { openView } = await start({
+ mockRPC: function (route, args) {
+ if (
+ args.method !== 'get_views' &&
+ !['/mail/init_messaging', '/mail/load_message_failures', ...WEBCLIENT_LOAD_ROUTES].includes(route)
+ ) {
assert.step(route);
}
- return this._super(...arguments);
},
+ serverData: { views },
+ });
+ await openView({
+ res_model: 'res.users',
+ views: [[false, 'list']],
});
assert.containsOnce(document.body, '.o_activity_color_today.text-warning.fa-warning');
@@ -146,16 +164,15 @@ QUnit.test('list activity widget: open dropdown', async function (assert) {
activity_type_id: mailActivityTypeId2,
});
- await start({
- hasView: true,
- View: ListView,
- model: 'res.users',
- arch: `
-
-
-
`,
+ const views = {
+ 'res.users,false,list': '
',
+ };
+ const { env, openView } = await start({
mockRPC: function (route, args) {
- if (!['/mail/init_messaging', '/mail/load_message_failures'].includes(route)) {
+ if (
+ args.method !== 'get_views' &&
+ !['/mail/init_messaging', '/mail/load_message_failures', ...WEBCLIENT_LOAD_ROUTES].includes(route)
+ ) {
assert.step(args.method || route);
}
if (args.method === 'action_feedback') {
@@ -165,14 +182,21 @@ QUnit.test('list activity widget: open dropdown', async function (assert) {
activity_summary: 'Meet FP',
activity_type_id: mailActivityTypeId1,
});
- return Promise.resolve();
+ // random value returned in order for the mock server to know that this route is implemented.
+ return true;
}
- return this._super(route, args);
},
- intercepts: {
- switch_view: () => assert.step('switch_view'),
+ serverData: { views },
+ });
+ patchWithCleanup(env.services.action, {
+ switchView() {
+ assert.step('switch_view');
},
});
+ await openView({
+ res_model: 'res.users',
+ views: [[false, 'list']],
+ });
assert.strictEqual(document.querySelector('.o_activity_summary').innerText, 'Call with Al');
@@ -236,13 +260,18 @@ QUnit.test('list activity exception widget with activity', async function (asser
activity_exception_decoration: 'warning',
activity_exception_icon: 'fa-warning',
});
- await start({
- hasView: true,
- View: ListView,
- model: 'res.users',
- arch: '
' +
- ' ' +
- '',
+ const views = {
+ 'res.users,false,list':
+ `
+
+ `,
+ };
+ const { openView } = await start({
+ serverData: { views },
+ });
+ await openView({
+ res_model: 'res.users',
+ views: [[false, 'list']],
});
assert.containsN(document.body, '.o_data_row', 2, "should have two records");
@@ -274,15 +303,18 @@ QUnit.test('list activity widget: done the activity with "ENTER" keyboard shortc
activity_summary: 'Call with Al',
activity_type_id: mailActivityTypeId1,
});
-
- await start({
- hasView: true,
- View: ListView,
- model: 'res.users',
- arch: `
-
+ const views = {
+ 'res.users,false,list':
+ `
`,
+ };
+ const { openView } = await start({
+ serverData: { views },
+ });
+ await openView({
+ res_model: 'res.users',
+ views: [[false, 'list']],
});
await testUtils.dom.click(document.querySelector('.o_activity_btn span'));
@@ -322,19 +354,24 @@ QUnit.test('list activity widget: done and schedule the next activity with "ENTE
activity_type_id: mailActivityTypeId1,
});
- await start({
- hasView: true,
- View: ListView,
- model: 'res.users',
- arch: `
-
+ const views = {
+ 'res.users,false,list':
+ `
`,
- intercepts: {
- do_action: function (ev) {
- assert.strictEqual(ev.data.action.name, "Schedule an Activity", 'should do a do_action with correct parameters');
- },
- },
+ };
+ const { env, openView } = await start({
+ serverData: { views },
+ });
+ await openView({
+ res_model: 'res.users',
+ views: [[false, 'list']],
+ });
+ patchWithCleanup(env.services.action, {
+ doAction(action) {
+ assert.strictEqual(action.name, "Schedule an Activity", 'should do a do_action with correct parameters');
+ return this._super(...arguments);
+ }
});
await testUtils.dom.click(document.querySelector('.o_activity_btn span'));
@@ -359,32 +396,35 @@ QUnit.test('fieldmany2many tags email (edition)', async function (assert) {
const mailMessageId1 = pyEnv['mail.message'].create({
partner_ids: [resPartnerId1],
});
-
- await start({
- hasView: true,
- View: FormView,
- model: 'mail.message',
- res_id: mailMessageId1,
- arch: '',
- viewOptions: {
- mode: 'edit',
- },
+ 'res.partner,false,form': '',
+ };
+ var { openView } = await start({
+ serverData: { views },
mockRPC: function (route, args) {
if (args.method === 'read' && args.model === 'res.partner') {
assert.step(JSON.stringify(args.args[0]));
assert.ok(args.args[1].includes('email'), "should read the email");
}
- return this._super.apply(this, arguments);
- },
- archs: {
- 'res.partner,false,form': '',
},
});
+ await openView(
+ {
+ res_id: mailMessageId1,
+ res_model: 'mail.message',
+ views: [[false, 'form']],
+ },
+ {
+ mode: 'edit',
+ },
+ );
assert.verifySteps([`[${resPartnerId1}]`]);
assert.containsOnce(document.body, '.o_field_many2manytags[name="partner_ids"] .badge.o_tag_color_0',
@@ -428,13 +468,16 @@ QUnit.test('many2many_tags_email widget can load more than 40 records', async fu
const mailMessageId1 = pyEnv['mail.message'].create({
partner_ids: messagePartnerIds,
});
-
- const { click } = await start({
- hasView: true,
- View: FormView,
- model: 'mail.message',
- arch: '',
+ const views = {
+ 'mail.message,false,form': '',
+ };
+ var { click, openView } = await start({
+ serverData: { views },
+ });
+ await openView({
res_id: mailMessageId1,
+ res_model: 'mail.message',
+ views: [[false, 'form']],
});
assert.strictEqual(document.querySelectorAll('.o_field_widget[name="partner_ids"] .badge').length, 100);
diff --git a/addons/mail/static/tests/qunit_suite_tests/components/activity_mark_done_popover_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/activity_mark_done_popover_tests.js
index 2fb59a60b7b..c462fdd84ed 100644
--- a/addons/mail/static/tests/qunit_suite_tests/components/activity_mark_done_popover_tests.js
+++ b/addons/mail/static/tests/qunit_suite_tests/components/activity_mark_done_popover_tests.js
@@ -2,7 +2,7 @@
import { start, startServer } from '@mail/../tests/helpers/test_utils';
-import Bus from 'web.Bus';
+import { patchWithCleanup } from '@web/../tests/helpers/utils';
QUnit.module('mail', {}, function () {
QUnit.module('components', {}, function () {
@@ -129,13 +129,13 @@ QUnit.test('activity mark done popover mark done without feedback', async functi
assert.strictEqual(args.args[0][0], mailActivityId1);
assert.strictEqual(args.kwargs.attachment_ids.length, 0);
assert.notOk(args.kwargs.feedback);
- return;
+ // random value returned in order for the mock server to know that this route is implemented.
+ return true;
}
if (route === '/web/dataset/call_kw/mail.activity/unlink') {
// 'unlink' on non-existing record raises a server crash
throw new Error("'unlink' RPC on activity must not be called (already unlinked from mark as done)");
}
- return this._super(...arguments);
},
});
await createChatterContainerComponent({
@@ -170,13 +170,13 @@ QUnit.test('activity mark done popover mark done with feedback', async function
assert.strictEqual(args.args[0][0], mailActivityId1);
assert.strictEqual(args.kwargs.attachment_ids.length, 0);
assert.strictEqual(args.kwargs.feedback, 'This task is done');
- return;
+ // random value returned in order for the mock server to know that this route is implemented.
+ return true;
}
if (route === '/web/dataset/call_kw/mail.activity/unlink') {
// 'unlink' on non-existing record raises a server crash
throw new Error("'unlink' RPC on activity must not be called (already unlinked from mark as done)");
}
- return this._super(...arguments);
},
});
await createChatterContainerComponent({
@@ -198,11 +198,6 @@ QUnit.test('activity mark done popover mark done with feedback', async function
QUnit.test('activity mark done popover mark done and schedule next', async function (assert) {
assert.expect(6);
- const bus = new Bus();
- bus.on('do-action', null, () => {
- assert.step('activity_action');
- throw new Error("The do-action event should not be triggered when the route doesn't return an action");
- });
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create();
const mailActivityId1 = pyEnv['mail.activity'].create({
@@ -211,7 +206,7 @@ QUnit.test('activity mark done popover mark done and schedule next', async funct
res_id: resPartnerId1,
res_model: 'res.partner',
});
- const { click, createChatterContainerComponent } = await start({
+ const { click, env, createChatterContainerComponent } = await start({
async mockRPC(route, args) {
if (route === '/web/dataset/call_kw/mail.activity/action_feedback_schedule_next') {
assert.step('action_feedback_schedule_next');
@@ -225,9 +220,13 @@ QUnit.test('activity mark done popover mark done and schedule next', async funct
// 'unlink' on non-existing record raises a server crash
throw new Error("'unlink' RPC on activity must not be called (already unlinked from mark as done)");
}
- return this._super(...arguments);
},
- env: { bus },
+ });
+ patchWithCleanup(env.services.action, {
+ doAction() {
+ assert.step('activity_action');
+ throw new Error("The do-action event should not be triggered when the route doesn't return an action");
+ },
});
await createChatterContainerComponent({
threadId: resPartnerId1,
@@ -248,15 +247,6 @@ QUnit.test('activity mark done popover mark done and schedule next', async funct
QUnit.test('[technical] activity mark done & schedule next with new action', async function (assert) {
assert.expect(3);
- const bus = new Bus();
- bus.on('do-action', null, ({ action }) => {
- assert.step('activity_action');
- assert.deepEqual(
- action,
- { type: 'ir.actions.act_window' },
- "The content of the action should be correct"
- );
- });
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create();
pyEnv['mail.activity'].create({
@@ -265,13 +255,21 @@ QUnit.test('[technical] activity mark done & schedule next with new action', asy
res_id: resPartnerId1,
res_model: 'res.partner',
});
- const { click, createChatterContainerComponent } = await start({
- env: { bus },
+ const { click, createChatterContainerComponent, env } = await start({
async mockRPC(route, args) {
if (route === '/web/dataset/call_kw/mail.activity/action_feedback_schedule_next') {
return { type: 'ir.actions.act_window' };
}
- return this._super(...arguments);
+ },
+ });
+ patchWithCleanup(env.services.action, {
+ doAction(action) {
+ assert.step('activity_action');
+ assert.deepEqual(
+ action,
+ { type: 'ir.actions.act_window' },
+ "The content of the action should be correct"
+ );
},
});
await createChatterContainerComponent({
diff --git a/addons/mail/static/tests/qunit_suite_tests/components/activity_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/activity_tests.js
index 03a3164064e..370b95a8fe3 100644
--- a/addons/mail/static/tests/qunit_suite_tests/components/activity_tests.js
+++ b/addons/mail/static/tests/qunit_suite_tests/components/activity_tests.js
@@ -2,8 +2,8 @@
import { afterNextRender, start, startServer } from '@mail/../tests/helpers/test_utils';
-import Bus from 'web.Bus';
import { date_to_str } from 'web.time';
+import { patchWithCleanup } from '@web/../tests/helpers/utils';
QUnit.module('mail', {}, function () {
QUnit.module('components', {}, function () {
@@ -602,8 +602,9 @@ QUnit.test('activity with mail template: preview mail', async function (assert)
res_id: resPartnerId1,
res_model: 'res.partner',
});
- const bus = new Bus();
- bus.on('do-action', null, ({ action }) => {
+ const { createChatterContainerComponent, env } = await start();
+ patchWithCleanup(env.services.action, {
+ doAction(action) {
assert.step('do_action');
assert.strictEqual(
action.context.default_res_id,
@@ -634,8 +635,8 @@ QUnit.test('activity with mail template: preview mail', async function (assert)
"mail.compose.message",
'Action should have "mail.compose.message" as res_model'
);
+ },
});
- const { createChatterContainerComponent } = await start({ env: { bus } });
await createChatterContainerComponent({
threadId: resPartnerId1,
threadModel: 'res.partner',
@@ -678,9 +679,8 @@ QUnit.test('activity with mail template: send mail', async function (assert) {
assert.strictEqual(args.args[0].length, 1);
assert.strictEqual(args.args[0][0], resPartnerId1);
assert.strictEqual(args.args[1], mailTemplateId1);
- return;
- } else {
- return this._super(...arguments);
+ // random value returned in order for the mock server to know that this route is implemented.
+ return true;
}
},
});
@@ -835,8 +835,9 @@ QUnit.test('activity click on edit', async function (assert) {
res_id: resPartnerId1,
res_model: 'res.partner',
});
- const bus = new Bus();
- bus.on('do-action', null, ({ action }) => {
+ const { click, createChatterContainerComponent, env } = await start();
+ patchWithCleanup(env.services.action, {
+ doAction(action) {
assert.step('do_action');
assert.strictEqual(
action.context.default_res_id,
@@ -863,8 +864,9 @@ QUnit.test('activity click on edit', async function (assert) {
mailActivityId1,
'Action should have activity id as res_id'
);
+ return this._super(...arguments);
+ },
});
- const { createChatterContainerComponent } = await start({ env: { bus } });
await createChatterContainerComponent({
threadId: resPartnerId1,
threadModel: 'res.partner',
@@ -880,7 +882,7 @@ QUnit.test('activity click on edit', async function (assert) {
"should have activity edit button"
);
- document.querySelector('.o_Activity_editButton').click();
+ await click('.o_Activity_editButton');
assert.verifySteps(
['do_action'],
"should have called 'schedule activity' action correctly"
@@ -898,8 +900,9 @@ QUnit.test('activity edition', async function (assert) {
res_id: resPartnerId1,
res_model: 'res.partner',
});
- const bus = new Bus();
- bus.on('do-action', null, ({ action, options }) => {
+ const { click, createChatterContainerComponent, env } = await start();
+ patchWithCleanup(env.services.action, {
+ doAction(action, options) {
assert.step('do_action');
assert.strictEqual(
action.context.default_res_id,
@@ -927,9 +930,9 @@ QUnit.test('activity edition', async function (assert) {
'Action should have activity id as res_id'
);
pyEnv['mail.activity'].write([mailActivityId1], { icon: 'fa-check' });
- options.on_close();
+ options.onClose();
+ },
});
- const { click, createChatterContainerComponent } = await start({ env: { bus } });
await createChatterContainerComponent({
threadId: resPartnerId1,
threadModel: 'res.partner',
@@ -995,9 +998,6 @@ QUnit.test('activity click on cancel', async function (assert) {
assert.step('unlink');
assert.strictEqual(args.args[0].length, 1);
assert.strictEqual(args.args[0][0], mailActivityId1);
- return;
- } else {
- return this._super(...arguments);
}
},
});
@@ -1109,8 +1109,20 @@ QUnit.test('activity mark done popover click on discard', async function (assert
QUnit.test('data-oe-id & data-oe-model link redirection on click', async function (assert) {
assert.expect(7);
- const bus = new Bus();
- bus.on('do-action', null, ({ action }) => {
+ const pyEnv = await startServer();
+ const resPartnerId1 = pyEnv['res.partner'].create();
+ const emailActivityTypeId = pyEnv['mail.activity.type'].search([['name', '=', 'Email']])[0];
+ pyEnv['mail.activity'].create({
+ activity_category: 'default',
+ activity_type_id: emailActivityTypeId,
+ can_write: true,
+ note: `some.model_250
`,
+ res_id: resPartnerId1,
+ res_model: 'res.partner',
+ });
+ const { createChatterContainerComponent, env } = await start();
+ patchWithCleanup(env.services.action, {
+ doAction(action) {
assert.strictEqual(
action.type,
'ir.actions.act_window',
@@ -1127,19 +1139,8 @@ QUnit.test('data-oe-id & data-oe-model link redirection on click', async functio
"action should open view on 250"
);
assert.step('do-action:openFormView_some.model_250');
+ },
});
- const pyEnv = await startServer();
- const resPartnerId1 = pyEnv['res.partner'].create();
- const emailActivityTypeId = pyEnv['mail.activity.type'].search([['name', '=', 'Email']])[0];
- pyEnv['mail.activity'].create({
- activity_category: 'default',
- activity_type_id: emailActivityTypeId,
- can_write: true,
- note: `some.model_250
`,
- res_id: resPartnerId1,
- res_model: 'res.partner',
- });
- const { createChatterContainerComponent } = await start({ data: this.data, env: { bus } });
await createChatterContainerComponent({
threadId: resPartnerId1,
threadModel: 'res.partner',
diff --git a/addons/mail/static/tests/qunit_suite_tests/components/attachment_list_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/attachment_list_tests.js
index 3fa20c72f99..6cf7bbeaea8 100644
--- a/addons/mail/static/tests/qunit_suite_tests/components/attachment_list_tests.js
+++ b/addons/mail/static/tests/qunit_suite_tests/components/attachment_list_tests.js
@@ -81,7 +81,6 @@ QUnit.test('simplest layout + editable', async function (assert) {
"should fetch image with 200x200 pixels ratio");
assert.step('fetch_image');
}
- return this._super(...arguments);
},
});
const attachment = messaging.models['Attachment'].create({
@@ -248,9 +247,7 @@ QUnit.test('clicking on the delete attachment button multiple times should do th
async mockRPC(route, args) {
if (route === '/mail/attachment/delete') {
assert.step('attachment_unlink');
- return;
}
- return this._super(...arguments);
},
});
const attachment = messaging.models['Attachment'].create({
diff --git a/addons/mail/static/tests/qunit_suite_tests/components/chat_window_manager_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/chat_window_manager_tests.js
index fc249484b7b..476e27b6f63 100644
--- a/addons/mail/static/tests/qunit_suite_tests/components/chat_window_manager_tests.js
+++ b/addons/mail/static/tests/qunit_suite_tests/components/chat_window_manager_tests.js
@@ -10,6 +10,7 @@ import {
startServer,
} from '@mail/../tests/helpers/test_utils';
+import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
import { file, dom } from 'web.test_utils';
const { createFile, inputFiles } = file;
const { triggerEvent } = dom;
@@ -231,12 +232,10 @@ QUnit.test('open chat from "new message" chat window should open chat in place o
const imSearchDef = makeDeferred();
patchUiSize({ width: 1920 });
const { click, createMessagingMenuComponent } = await start({
- async mockRPC(route, args) {
- const res = await this._super(...arguments);
+ mockRPC(route, args) {
if (args.method === 'im_search') {
imSearchDef.resolve();
}
- return res;
}
});
await createMessagingMenuComponent();
@@ -381,12 +380,10 @@ QUnit.test('new message autocomplete should automatically select first result',
pyEnv['res.users'].create({ partner_id: resPartnerId1 });
const imSearchDef = makeDeferred();
const { click, createMessagingMenuComponent } = await start({
- async mockRPC(route, args) {
- const res = await this._super(...arguments);
+ mockRPC(route, args) {
if (args.method === 'im_search') {
imSearchDef.resolve();
}
- return res;
},
});
await createMessagingMenuComponent();
@@ -509,7 +506,6 @@ QUnit.test('chat window: fold', async function (assert) {
if (args.method === 'channel_fold') {
assert.step(`rpc:${args.method}/${args.kwargs.state}`);
}
- return this._super(...arguments);
},
});
await createMessagingMenuComponent();
@@ -561,7 +557,6 @@ QUnit.test('chat window: open / close', async function (assert) {
if (args.method === 'channel_fold') {
assert.step(`rpc:channel_fold/${args.kwargs.state}`);
}
- return this._super(...arguments);
},
});
await createMessagingMenuComponent();
@@ -732,7 +727,6 @@ QUnit.test('chat window: close on ESCAPE', async function (assert) {
if (args.method === 'channel_fold') {
assert.step(`rpc:channel_fold/${args.kwargs.state}`);
}
- return this._super(...arguments);
},
});
assert.containsOnce(
@@ -2013,7 +2007,6 @@ QUnit.test('chat window does not fetch messages if hidden', async function (asse
const { channel_id } = args;
assert.step(`rpc:/mail/channel/messages:${channel_id}`);
}
- return this._super(...arguments);
},
});
@@ -2280,21 +2273,16 @@ QUnit.test('Textual representations of shift previous/next operations are correc
},
]);
await start({
- env: {
- _t: Object.assign((s => s), {
- database: {
- parameters: {
- code: "en_US",
- date_format: '%m/%d/%Y',
- decimal_point: ".",
- direction: 'rtl',
- grouping: [],
- thousands_sep: ",",
- time_format: '%H:%M:%S',
- },
- },
+ services: {
+ localization: makeFakeLocalizationService({
+ dateFormat: '%m/%d/%Y',
+ timeFormat: '%H:%M:%S',
+ direction: 'rtl',
+ grouping: [],
+ thousandsSep: ',',
+ decimalPoint: '.',
}),
- }
+ },
});
assert.strictEqual(
diff --git a/addons/mail/static/tests/qunit_suite_tests/components/chatter_suggested_recipient_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/chatter_suggested_recipient_tests.js
index 5840b8cbf50..7dcde918118 100644
--- a/addons/mail/static/tests/qunit_suite_tests/components/chatter_suggested_recipient_tests.js
+++ b/addons/mail/static/tests/qunit_suite_tests/components/chatter_suggested_recipient_tests.js
@@ -235,7 +235,6 @@ QUnit.test("suggested recipients should not be notified when posting an internal
"post data should not contain suggested recipients when posting an internal note"
);
}
- return this._super(...arguments);
},
});
await createChatterContainerComponent({
diff --git a/addons/mail/static/tests/qunit_suite_tests/components/chatter_topbar_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/chatter_topbar_tests.js
index ef1cb38dd0c..c3682aa0731 100644
--- a/addons/mail/static/tests/qunit_suite_tests/components/chatter_topbar_tests.js
+++ b/addons/mail/static/tests/qunit_suite_tests/components/chatter_topbar_tests.js
@@ -118,7 +118,6 @@ QUnit.test('attachment loading is delayed', async function (assert) {
if (route.includes('/mail/thread/data')) {
await makeTestPromise(); // simulate long loading
}
- return this._super(...arguments);
}
});
await createChatterContainerComponent({
@@ -160,7 +159,6 @@ QUnit.test('attachment counter while loading attachments', async function (asser
if (route.includes('/mail/thread/data')) {
await makeTestPromise(); // simulate long loading
}
- return this._super(...arguments);
}
});
await createChatterContainerComponent({
@@ -198,11 +196,9 @@ QUnit.test('attachment counter transition when attachments become loaded)', asyn
const attachmentPromise = makeTestPromise();
const { createChatterContainerComponent } = await start({
async mockRPC(route) {
- const _super = this._super.bind(this, ...arguments); // limitation of class.js
if (route.includes('/mail/thread/data')) {
await attachmentPromise;
}
- return _super();
},
});
await createChatterContainerComponent({
diff --git a/addons/mail/static/tests/qunit_suite_tests/components/composer_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/composer_tests.js
index 6f66c5208d2..de68ab2b36e 100644
--- a/addons/mail/static/tests/qunit_suite_tests/components/composer_tests.js
+++ b/addons/mail/static/tests/qunit_suite_tests/components/composer_tests.js
@@ -11,7 +11,6 @@ import {
startServer,
} from '@mail/../tests/helpers/test_utils';
-import FormView from 'web.FormView';
import {
file,
makeTestPromise,
@@ -21,32 +20,7 @@ const { createFile, inputFiles } = file;
QUnit.module('mail', {}, function () {
QUnit.module('components', {}, function () {
-QUnit.module('composer_tests.js', {
- beforeEach() {
- // FIXME archs could be removed once task-2248306 is done
- // The mockServer will try to get the list view
- // of every relational fields present in the main view.
- // In the case of mail fields, we don't really need them,
- // but they still need to be defined.
- this.createView = async (viewParams, ...args) => {
- const startResult = makeTestPromise();
- await afterNextRender(async () => {
- const viewArgs = Object.assign(
- {
- archs: {
- 'mail.activity,false,list': '',
- 'mail.followers,false,list': '',
- 'mail.message,false,list': '',
- },
- },
- viewParams,
- );
- startResult.resolve(await start(viewArgs, ...args));
- });
- return startResult;
- };
- },
-});
+QUnit.module('composer_tests.js');
QUnit.test('composer text input: basic rendering when posting a message', async function (assert) {
assert.expect(5);
@@ -613,7 +587,6 @@ QUnit.test('do not send typing notification on typing "/" command', async functi
if (args.method === 'notify_typing') {
assert.step(`notify_typing:${args.kwargs.is_typing}`);
}
- return this._super(...arguments);
},
});
@@ -637,7 +610,6 @@ QUnit.test('do not send typing notification on typing after selecting suggestion
if (args.method === 'notify_typing') {
assert.step(`notify_typing:${args.kwargs.is_typing}`);
}
- return this._super(...arguments);
},
});
@@ -1050,7 +1022,6 @@ QUnit.test('composer text input cleared on message post', async function (assert
if (route === '/mail/message/post') {
assert.step('message_post');
}
- return this._super(...arguments);
},
});
const thread = messaging.models['Thread'].findFromIdentifyingData({
@@ -1122,7 +1093,6 @@ QUnit.test('current partner notify is typing to other thread members', async fun
if (args.method === 'notify_typing') {
assert.step(`notify_typing:${args.kwargs.is_typing}`);
}
- return this._super(...arguments);
},
});
@@ -1152,7 +1122,6 @@ QUnit.test('current partner is typing should not translate on textual typing sta
if (args.method === 'notify_typing') {
assert.step(`notify_typing:${args.kwargs.is_typing}`);
}
- return this._super(...arguments);
},
});
@@ -1190,7 +1159,6 @@ QUnit.test('current partner notify no longer is typing to thread members after 5
if (args.method === 'notify_typing') {
assert.step(`notify_typing:${args.kwargs.is_typing}`);
}
- return this._super(...arguments);
},
});
@@ -1227,7 +1195,6 @@ QUnit.test('current partner notify is typing again to other members every 50s of
if (args.method === 'notify_typing') {
assert.step(`notify_typing:${args.kwargs.is_typing}`);
}
- return this._super(...arguments);
},
});
@@ -1259,12 +1226,10 @@ QUnit.test('composer: send button is disabled if attachment upload is not finish
const attachmentUploadedPromise = makeTestPromise();
const mailChannelId1 = pyEnv['mail.channel'].create();
const { createComposerComponent, messaging } = await start({
- async mockFetch(resource, init) {
- const res = this._super(...arguments);
- if (resource === '/mail/attachment/upload') {
+ async mockRPC(route) {
+ if (route === '/mail/attachment/upload') {
await attachmentUploadedPromise;
}
- return res;
}
});
const thread = messaging.models['Thread'].findFromIdentifyingData({
@@ -1373,13 +1338,11 @@ QUnit.test('remove an uploading attachment', async function (assert) {
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create();
const { click, createComposerComponent, messaging } = await start({
- async mockFetch(resource, init) {
- const res = this._super(...arguments);
- if (resource === '/mail/attachment/upload') {
+ async mockRPC(route) {
+ if (route === '/mail/attachment/upload') {
// simulates uploading indefinitely
await new Promise(() => {});
}
- return res;
}
});
const thread = messaging.models['Thread'].findFromIdentifyingData({
@@ -1428,13 +1391,11 @@ QUnit.test('remove an uploading attachment aborts upload', async function (asser
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create();
const { afterEvent, createComposerComponent, messaging } = await start({
- async mockFetch(resource, init) {
- const res = this._super(...arguments);
- if (resource === '/mail/attachment/upload') {
+ async mockRPC(route) {
+ if (route === '/mail/attachment/upload') {
// simulates uploading indefinitely
await new Promise(() => {});
}
- return res;
}
});
const thread = messaging.models['Thread'].findFromIdentifyingData({
@@ -1477,13 +1438,9 @@ QUnit.test("Show a default status in the recipient status text when the thread d
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create();
- const { click } = await this.createView({
- hasView: true,
- // View params
- View: FormView,
- model: 'res.partner',
- arch: `
-
-
- `,
+ `,
+ };
+ const { click, openView } = await start({
+ serverData: { views },
+ });
+ await openView({
+ res_model: 'res.partner',
res_id: resPartnerId1,
+ views: [[false, 'form']],
});
await click('.o_ChatterTopbar_buttonSendMessage');
assert.strictEqual(
@@ -1508,13 +1471,9 @@ QUnit.test("Show a thread name in the recipient status text.", async function (a
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({ name: "test name" });
- const { click, messaging } = await this.createView({
- hasView: true,
- // View params
- View: FormView,
- model: 'res.partner',
- arch: `
-