From edd79ff560cf321ebd0488e4917abb7424fea1bb Mon Sep 17 00:00:00 2001 From: Samuel Degueldre Date: Wed, 2 Feb 2022 10:36:49 +0000 Subject: [PATCH] [REF] mail,*: adapt code to owl 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *im_livechat,sms,snailmail Owl 2 changelog: https://github.com/odoo/owl/blob/a9f29c4caad4f32d06be1ec4780572825781cd9b/CHANGELOG.md Part-of: odoo/odoo#80156 Co-authored-by: Aaron Bohy Co-authored-by: Bruno Boi Co-authored-by: Géry Debongnie Co-authored-by: Samuel Degueldre Co-authored-by: Mathieu Duckerts-Antoine Co-authored-by: Simon Genin (ges) Co-authored-by: Francois (fge) Co-authored-by: Michael Mattiello (mcm) Co-authored-by: Julien Mougenot Co-authored-by: luvi Co-authored-by: Lucas Perais (lpe) Co-authored-by: Jorge Pinna Puissant --- addons/im_livechat/__manifest__.py | 3 +- .../use_component_to_model.js | 8 +- .../component_hooks/use_models/use_models.js | 16 +- .../use_ref_to_model/use_ref_to_model.js | 8 +- .../use_rendered_values.js | 14 +- .../component_hooks/use_update/use_update.js | 8 +- .../src/components/activity/activity.js | 9 +- .../src/components/activity/activity.xml | 6 +- .../activity/tests/activity_tests.js | 4 +- .../tests/activity_mark_done_popover_tests.js | 4 +- .../attachment_delete_confirm.js | 1 - .../tests/attachment_image_tests.js | 4 +- .../tests/attachment_list_tests.js | 4 +- .../autocomplete_input/autocomplete_input.js | 35 ++-- .../channel_invitation_form.xml | 6 +- .../channel_member_list.xml | 4 +- .../src/components/chat_window/chat_window.js | 12 +- .../components/chat_window/chat_window.xml | 2 +- .../chat_window_header/chat_window_header.js | 10 +- .../chatter_container/chatter_container.js | 6 +- .../src/components/composer/composer.js | 40 ++++- .../composer/tests/composer_tests.js | 4 +- .../composer_suggested_recipient.js | 24 ++- .../composer_suggested_recipient.xml | 2 +- .../composer_suggestion.js | 2 +- .../composer_suggestion_list.js | 2 +- .../composer_text_input.js | 5 +- .../static/src/components/discuss/discuss.js | 1 - .../components/discuss/tests/discuss_tests.js | 4 +- .../discuss_sidebar_category.js | 2 +- .../src/components/emoji_list/emoji_list.xml | 2 +- .../tests/file_uploader_tests.js | 6 +- .../components/follow_button/follow_button.js | 2 +- .../src/components/follower/follower.js | 3 + .../follower_list_menu/follower_list_menu.js | 2 +- .../static/src/components/message/message.js | 2 + .../static/src/components/message/message.xml | 4 +- .../components/message/tests/message_tests.js | 4 +- .../message_action_list.xml | 2 +- .../message_in_reply_to_view.js | 7 +- .../message_in_reply_to_view.xml | 2 +- .../components/message_list/message_list.js | 8 +- .../components/message_list/message_list.scss | 7 +- .../components/message_list/message_list.xml | 6 +- .../tests/messaging_menu_tests.js | 1 - .../messaging_menu_container.js | 4 +- .../mobile_messaging_navbar.xml | 2 +- .../partner_im_status_icon.js | 4 +- .../rtc_activity_notice_container.js | 4 +- .../rtc_call_participant_card.js | 3 + .../rtc_controller/rtc_controller.js | 3 + .../thread_typing_icon/thread_typing_icon.js | 2 + .../thread_view/tests/thread_view_tests.js | 4 +- .../src/components/thread_view/thread_view.js | 11 +- addons/mail/static/src/js/utils.js | 3 +- .../js/views/activity/activity_renderer.js | 20 ++- .../static/src/models/activity/activity.js | 2 + .../channel_invitation_form.js | 6 +- .../src/models/composer_view/composer_view.js | 2 +- .../mail/static/src/models/discuss/discuss.js | 3 +- .../messaging_notification_handler.js | 5 +- .../static/src/public/discuss_public_boot.js | 28 ++-- addons/mail/static/src/public/root_widget.js | 9 +- .../chat_window_service.js | 24 ++- .../services/dialog_service/dialog_service.js | 24 ++- addons/mail/static/src/utils/test_utils.js | 152 +++++++----------- .../static/src/widgets/discuss/discuss.js | 31 ++-- .../widgets/form_renderer/form_renderer.js | 5 +- addons/mail/static/src/xml/activity_view.xml | 4 +- addons/mail/static/tests/activity_tests.js | 55 ++++--- .../static/tests/m2x_avatar_user_tests.js | 57 +++---- .../static/tests/tools/debug_manager_tests.js | 9 +- .../commands/mail_providers_tests.js | 14 +- .../components/message/tests/message_tests.js | 4 +- .../components/message/tests/message_tests.js | 4 +- .../snailmail_error/snailmail_error.js | 1 - 76 files changed, 445 insertions(+), 362 deletions(-) diff --git a/addons/im_livechat/__manifest__.py b/addons/im_livechat/__manifest__.py index bdd717fa82f..8274cbdd588 100644 --- a/addons/im_livechat/__manifest__.py +++ b/addons/im_livechat/__manifest__.py @@ -80,7 +80,7 @@ Help your customers with this chat, and analyse their feedback. 'web/static/lib/qweb/qweb2.js', # Odoo JS Framework 'web/static/lib/owl/owl.js', - 'web/static/src/owl_next_adapter.js', + 'web/static/src/owl2_compatibility/*.js', 'web/static/src/legacy/js/promise_extension.js', 'web/static/src/boot.js', 'web/static/src/core/assets.js', @@ -101,6 +101,7 @@ Help your customers with this chat, and analyse their feedback. 'web/static/src/core/notifications/notification_container.js', 'web/static/src/core/notifications/notification_service.js', 'web/static/src/core/registry.js', + 'web/static/src/core/transition.js', 'web/static/src/core/ui/block_ui.js', 'web/static/src/core/ui/ui_service.js', 'web/static/src/core/user_service.js', diff --git a/addons/mail/static/src/component_hooks/use_component_to_model/use_component_to_model.js b/addons/mail/static/src/component_hooks/use_component_to_model/use_component_to_model.js index b337cc782e2..2b9eee17fee 100644 --- a/addons/mail/static/src/component_hooks/use_component_to_model/use_component_to_model.js +++ b/addons/mail/static/src/component_hooks/use_component_to_model/use_component_to_model.js @@ -2,7 +2,7 @@ import { clear } from '@mail/model/model_field_command'; -const { onWillUpdateProps, useComponent } = owl; +const { onWillDestroy, onWillUpdateProps, useComponent } = owl; /** * This hook provides support for saving the reference of the component directly @@ -30,12 +30,10 @@ export function useComponentToModel({ fieldName, modelName }) { nextRecord.update({ [fieldName]: component }); } }); - const __destroy = component.__destroy; - component.__destroy = parent => { + onWillDestroy(() => { const record = modelManager.models[modelName].get(component.props.localId); if (record) { record.update({ [fieldName]: clear() }); } - __destroy.call(component, parent); - }; + }); } diff --git a/addons/mail/static/src/component_hooks/use_models/use_models.js b/addons/mail/static/src/component_hooks/use_models/use_models.js index 94fa51f0752..ca2e48c2845 100644 --- a/addons/mail/static/src/component_hooks/use_models/use_models.js +++ b/addons/mail/static/src/component_hooks/use_models/use_models.js @@ -2,7 +2,7 @@ import { Listener } from '@mail/model/model_listener'; -const { useComponent } = owl; +const { onRendered, onWillDestroy, onWillRender, useComponent } = owl; /** * This hook provides support for automatically re-rendering when used records @@ -24,23 +24,21 @@ export function useModels() { name: `useModels() of ${component}`, onChange: () => component.render(), }); - const __render = component.__render; - component.__render = fiber => { + onWillRender(() => { if (modelManager) { modelManager.startListening(listener); } - __render.call(component, fiber); + }); + onRendered(() => { if (modelManager) { modelManager.stopListening(listener); } - }; - const __destroy = component.__destroy; - component.__destroy = parent => { + }); + onWillDestroy(() => { if (modelManager) { modelManager.removeListener(listener); } - __destroy.call(component, parent); - }; + }); modelManager.messagingCreatedPromise.then(() => { component.render(); }); diff --git a/addons/mail/static/src/component_hooks/use_ref_to_model/use_ref_to_model.js b/addons/mail/static/src/component_hooks/use_ref_to_model/use_ref_to_model.js index a9e613b3125..b841c243007 100644 --- a/addons/mail/static/src/component_hooks/use_ref_to_model/use_ref_to_model.js +++ b/addons/mail/static/src/component_hooks/use_ref_to_model/use_ref_to_model.js @@ -2,7 +2,7 @@ import { clear } from '@mail/model/model_field_command'; -const { onWillUpdateProps, useComponent, useRef } = owl; +const { onWillDestroy, onWillUpdateProps, useComponent, useRef } = owl; /** * This hook provides support for saving the result of useRef directly into the @@ -32,12 +32,10 @@ export function useRefToModel({ fieldName, modelName, refName }) { nextRecord.update({ [fieldName]: ref }); } }); - const __destroy = component.__destroy; - component.__destroy = parent => { + onWillDestroy(() => { const record = modelManager.models[modelName].get(component.props.localId); if (record) { record.update({ [fieldName]: clear() }); } - __destroy.call(component, parent); - }; + }); } diff --git a/addons/mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js b/addons/mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js index b32a8556427..c3ae3b2c621 100644 --- a/addons/mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js +++ b/addons/mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js @@ -2,7 +2,7 @@ import { Listener } from '@mail/model/model_listener'; -const { onMounted, onPatched, useComponent } = owl; +const { onMounted, onPatched, onWillDestroy, onWillRender, useComponent } = owl; /** * This hooks provides support for accessing the values returned by the given @@ -22,22 +22,16 @@ export function useRenderedValues(selector) { name: `useRenderedValues() of ${component}`, onChange: () => component.render(), }); - const __render = component.__render.bind(component); - component.__render = function () { + onWillRender(() => { modelManager.startListening(listener); renderedValues = selector(); modelManager.stopListening(listener); - return __render(...arguments); - }; + }) onMounted(onUpdate); onPatched(onUpdate); function onUpdate() { patchedValues = renderedValues; } - const __destroy = component.__destroy; - component.__destroy = parent => { - modelManager.removeListener(listener); - __destroy.call(component, parent); - }; + onWillDestroy(() => modelManager.removeListener(listener)); return () => patchedValues; } diff --git a/addons/mail/static/src/component_hooks/use_update/use_update.js b/addons/mail/static/src/component_hooks/use_update/use_update.js index 60de8ac40ce..5a06ee1b26f 100644 --- a/addons/mail/static/src/component_hooks/use_update/use_update.js +++ b/addons/mail/static/src/component_hooks/use_update/use_update.js @@ -2,7 +2,7 @@ import { Listener } from '@mail/model/model_listener'; -const { onMounted, onPatched, useComponent } = owl; +const { onMounted, onPatched, onWillDestroy, useComponent } = owl; /** * This hook provides support for executing code after update (render or patch). @@ -29,11 +29,9 @@ export function useUpdate({ func }) { } onMounted(onUpdate); onPatched(onUpdate); - const __destroy = component.__destroy; - component.__destroy = parent => { + onWillDestroy(() => { if (modelManager) { modelManager.removeListener(listener); } - __destroy.call(component, parent); - }; + }); } diff --git a/addons/mail/static/src/components/activity/activity.js b/addons/mail/static/src/components/activity/activity.js index bee292814c1..4996297bb7d 100644 --- a/addons/mail/static/src/components/activity/activity.js +++ b/addons/mail/static/src/components/activity/activity.js @@ -2,10 +2,16 @@ import { registerMessagingComponent } from '@mail/utils/messaging_component'; -const { Component } = owl; +import Popover from "web.Popover"; + +const { Component, markup } = owl; export class Activity extends Component { + get noteAsMarkup() { + return markup(this.activityView.activity.note); + } + /** * @returns {ActivityView} */ @@ -18,6 +24,7 @@ export class Activity extends Component { Object.assign(Activity, { props: { localId: String }, template: 'mail.Activity', + components: { Popover }, }); registerMessagingComponent(Activity); diff --git a/addons/mail/static/src/components/activity/activity.xml b/addons/mail/static/src/components/activity/activity.xml index eefc308f687..63819e0a55b 100644 --- a/addons/mail/static/src/components/activity/activity.xml +++ b/addons/mail/static/src/components/activity/activity.xml @@ -2,7 +2,7 @@ -
+
@@ -102,7 +102,7 @@
- +
@@ -125,7 +125,7 @@ - + diff --git a/addons/mail/static/src/components/activity/tests/activity_tests.js b/addons/mail/static/src/components/activity/tests/activity_tests.js index 4dca664b4d3..2bceec8569e 100644 --- a/addons/mail/static/src/components/activity/tests/activity_tests.js +++ b/addons/mail/static/src/components/activity/tests/activity_tests.js @@ -14,8 +14,8 @@ QUnit.module('activity_tests.js', { this.start = async params => { const res = await start({ ...params, data: this.data }); - const { components, env, widget } = res; - this.components = components; + const { apps, env, widget } = res; + this.apps = apps; this.env = env; this.widget = widget; return res; diff --git a/addons/mail/static/src/components/activity_mark_done_popover/tests/activity_mark_done_popover_tests.js b/addons/mail/static/src/components/activity_mark_done_popover/tests/activity_mark_done_popover_tests.js index 001f08b12ee..441316059f3 100644 --- a/addons/mail/static/src/components/activity_mark_done_popover/tests/activity_mark_done_popover_tests.js +++ b/addons/mail/static/src/components/activity_mark_done_popover/tests/activity_mark_done_popover_tests.js @@ -13,8 +13,8 @@ QUnit.module('activity_mark_done_popover_tests.js', { this.start = async params => { const res = await start({ ...params, data: this.data }); - const { components, env, widget } = res; - this.components = components; + const { apps, env, widget } = res; + this.apps = apps; this.env = env; this.widget = widget; return res; diff --git a/addons/mail/static/src/components/attachment_delete_confirm/attachment_delete_confirm.js b/addons/mail/static/src/components/attachment_delete_confirm/attachment_delete_confirm.js index 139e82ce24f..d11f97dd462 100644 --- a/addons/mail/static/src/components/attachment_delete_confirm/attachment_delete_confirm.js +++ b/addons/mail/static/src/components/attachment_delete_confirm/attachment_delete_confirm.js @@ -11,7 +11,6 @@ export class AttachmentDeleteConfirm extends Component { * @override */ setup() { - super.setup(); useComponentToModel({ fieldName: 'component', modelName: 'AttachmentDeleteConfirmView' }); } diff --git a/addons/mail/static/src/components/attachment_image/tests/attachment_image_tests.js b/addons/mail/static/src/components/attachment_image/tests/attachment_image_tests.js index bb71961210e..cf4589a64e8 100644 --- a/addons/mail/static/src/components/attachment_image/tests/attachment_image_tests.js +++ b/addons/mail/static/src/components/attachment_image/tests/attachment_image_tests.js @@ -16,9 +16,9 @@ QUnit.module('attachment_image_tests.js', { this.start = async params => { const res = await start({ ...params, data: this.data }); - const { afterEvent, components, env, widget } = res; + const { afterEvent, apps, env, widget } = res; this.afterEvent = afterEvent; - this.components = components; + this.apps = apps; this.env = env; this.widget = widget; return res; diff --git a/addons/mail/static/src/components/attachment_list/tests/attachment_list_tests.js b/addons/mail/static/src/components/attachment_list/tests/attachment_list_tests.js index 20dcebf0ffb..3d1155dc856 100644 --- a/addons/mail/static/src/components/attachment_list/tests/attachment_list_tests.js +++ b/addons/mail/static/src/components/attachment_list/tests/attachment_list_tests.js @@ -18,9 +18,9 @@ QUnit.module('attachment_list_tests.js', { this.start = async params => { const res = await start({ ...params, data: this.data }); - const { afterEvent, components, env, widget } = res; + const { afterEvent, apps, env, widget } = res; this.afterEvent = afterEvent; - this.components = components; + this.apps = apps; this.env = env; this.widget = widget; return res; diff --git a/addons/mail/static/src/components/autocomplete_input/autocomplete_input.js b/addons/mail/static/src/components/autocomplete_input/autocomplete_input.js index 8fe13e45aff..c7cb07b2694 100644 --- a/addons/mail/static/src/components/autocomplete_input/autocomplete_input.js +++ b/addons/mail/static/src/components/autocomplete_input/autocomplete_input.js @@ -2,7 +2,7 @@ import { registerMessagingComponent } from '@mail/utils/messaging_component'; -const { Component, onMounted, onWillUnmount } = owl; +const { Component, onMounted, onWillUnmount, useEffect } = owl; export class AutocompleteInput extends Component { @@ -13,6 +13,12 @@ export class AutocompleteInput extends Component { super.setup(); onMounted(() => this._mounted()); onWillUnmount(() => this._willUnmount()); + useEffect(() => { + if (this.props.inputRef) { + this.props.inputRef.el = this.root.el; + return () => this.props.inputRef.el = null; + } + }) } _mounted() { @@ -73,13 +79,6 @@ export class AutocompleteInput extends Component { return element.contains(node); } - focus() { - if (!this.root.el) { - return; - } - this.root.el.focus(); - } - //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- @@ -147,6 +146,7 @@ Object.assign(AutocompleteInput, { isFocusOnMount: false, isHtml: false, placeholder: '', + onFocusin: () => {}, }, props: { customClass: { @@ -157,8 +157,14 @@ Object.assign(AutocompleteInput, { type: Function, optional: true, }, - isFocusOnMount: Boolean, - isHtml: Boolean, + isFocusOnMount: { + type: Boolean, + optional: true, + }, + isHtml: { + type: Boolean, + optional: true, + }, onFocusin: { type: Function, optional: true, @@ -167,7 +173,10 @@ Object.assign(AutocompleteInput, { type: Function, optional: true, }, - placeholder: String, + placeholder: { + type: String, + optional: true, + }, select: { type: Function, optional: true, @@ -176,6 +185,10 @@ Object.assign(AutocompleteInput, { type: Function, optional: true, }, + inputRef: { + type: { el: Object }, + optional: true, + }, }, template: 'mail.AutocompleteInput', }); diff --git a/addons/mail/static/src/components/channel_invitation_form/channel_invitation_form.xml b/addons/mail/static/src/components/channel_invitation_form/channel_invitation_form.xml index 5a03086aa23..ec9a8d07963 100644 --- a/addons/mail/static/src/components/channel_invitation_form/channel_invitation_form.xml +++ b/addons/mail/static/src/components/channel_invitation_form/channel_invitation_form.xml @@ -11,7 +11,7 @@
-
+
Avatar @@ -29,7 +29,7 @@
- +
@@ -46,7 +46,7 @@

Selected users:

- diff --git a/addons/mail/static/src/components/channel_member_list/channel_member_list.xml b/addons/mail/static/src/components/channel_member_list/channel_member_list.xml index b9e74cbda93..3bdbf7b96bb 100644 --- a/addons/mail/static/src/components/channel_member_list/channel_member_list.xml +++ b/addons/mail/static/src/components/channel_member_list/channel_member_list.xml @@ -33,7 +33,7 @@
-
-
+
Avatar @@ -46,7 +46,7 @@ />
- +
diff --git a/addons/mail/static/src/components/chat_window/chat_window.js b/addons/mail/static/src/components/chat_window/chat_window.js index 13ab1d50bb9..74cf9bfc005 100644 --- a/addons/mail/static/src/components/chat_window/chat_window.js +++ b/addons/mail/static/src/components/chat_window/chat_window.js @@ -19,7 +19,7 @@ export class ChatWindow extends Component { * Useful when focusing this chat window, which consists of focusing * this input. */ - this._inputRef = useRef('input'); + this._inputRef = { el: null }; // the following are passed as props to children this._onAutocompleteSelect = this._onAutocompleteSelect.bind(this); this._onAutocompleteSource = this._onAutocompleteSource.bind(this); @@ -109,8 +109,8 @@ export class ChatWindow extends Component { } if (this.chatWindow.isDoFocus) { this.chatWindow.update({ isDoFocus: false }); - if (this._inputRef.comp) { - this._inputRef.comp.focus(); + if (this._inputRef.el) { + this._inputRef.el.focus(); } } this._applyVisibleOffset(); @@ -254,9 +254,9 @@ Object.assign(ChatWindow, { }, props: { localId: String, - hasCloseAsBackButton: Boolean, - isExpandable: Boolean, - isFullscreen: Boolean, + hasCloseAsBackButton: { type: Boolean, optional: true }, + isExpandable: { type: Boolean, optional: true }, + isFullscreen: { type: Boolean, optional: true }, }, template: 'mail.ChatWindow', }); diff --git a/addons/mail/static/src/components/chat_window/chat_window.xml b/addons/mail/static/src/components/chat_window/chat_window.xml index 0d00cca3998..5870ff77532 100644 --- a/addons/mail/static/src/components/chat_window/chat_window.xml +++ b/addons/mail/static/src/components/chat_window/chat_window.xml @@ -45,7 +45,7 @@ select="_onAutocompleteSelect" source="_onAutocompleteSource" onFocusin="chatWindow.onFocusInNewMessageFormInput" - t-ref="input" + inputRef="_inputRef" />
diff --git a/addons/mail/static/src/components/chat_window_header/chat_window_header.js b/addons/mail/static/src/components/chat_window_header/chat_window_header.js index bec8197d227..27ec8907e8b 100644 --- a/addons/mail/static/src/components/chat_window_header/chat_window_header.js +++ b/addons/mail/static/src/components/chat_window_header/chat_window_header.js @@ -137,8 +137,14 @@ Object.assign(ChatWindowHeader, { }, props: { chatWindowLocalId: String, - hasCloseAsBackButton: Boolean, - isExpandable: Boolean, + hasCloseAsBackButton: { + type: Boolean, + optional: true, + }, + isExpandable: { + type: Boolean, + optional: true, + }, onClicked: { type: Function, optional: true, diff --git a/addons/mail/static/src/components/chatter_container/chatter_container.js b/addons/mail/static/src/components/chatter_container/chatter_container.js index 806a72b1d66..d04cf4c4978 100644 --- a/addons/mail/static/src/components/chatter_container/chatter_container.js +++ b/addons/mail/static/src/components/chatter_container/chatter_container.js @@ -3,7 +3,7 @@ import { registerMessagingComponent } from '@mail/utils/messaging_component'; import { clear } from '@mail/model/model_field_command'; -const { Component, onWillUpdateProps } = owl; +const { Component, onWillDestroy, onWillUpdateProps } = owl; const getChatterNextTemporaryId = (function () { let tmpId = 0; @@ -32,6 +32,7 @@ export class ChatterContainer extends Component { this.chatterId = getChatterNextTemporaryId(); this._insertFromProps(this.props); onWillUpdateProps(nextProps => this._willUpdateProps(nextProps)); + onWillDestroy(this._onWillDestroy); } _willUpdateProps(nextProps) { @@ -41,8 +42,7 @@ export class ChatterContainer extends Component { /** * @override */ - destroy() { - super.destroy(); + _onWillDestroy() { if (this.chatter) { this.chatter.delete(); } diff --git a/addons/mail/static/src/components/composer/composer.js b/addons/mail/static/src/components/composer/composer.js index 4ec3460e6b5..44b2a49056e 100644 --- a/addons/mail/static/src/components/composer/composer.js +++ b/addons/mail/static/src/components/composer/composer.js @@ -219,16 +219,34 @@ Object.assign(Composer, { }, props: { localId: String, - hasCurrentPartnerAvatar: Boolean, - hasDiscardButton: Boolean, - hasFollowers: Boolean, + hasCurrentPartnerAvatar: { + type: Boolean, + optional: true, + }, + hasDiscardButton: { + type: Boolean, + optional: true, + }, + hasFollowers: { + type: Boolean, + optional: true, + }, hasMentionSuggestionsBelowPosition: { type: Boolean, optional: true, }, - hasSendButton: Boolean, - hasThreadName: Boolean, - hasThreadTyping: Boolean, + hasSendButton: { + type: Boolean, + optional: true, + }, + hasThreadName: { + type: Boolean, + optional: true, + }, + hasThreadTyping: { + type: Boolean, + optional: true, + }, showAttachmentsExtensions: { type: Boolean, optional: true, @@ -237,8 +255,14 @@ Object.assign(Composer, { type: Boolean, optional: true, }, - isCompact: Boolean, - isExpandable: Boolean, + isCompact: { + type: Boolean, + optional: true, + }, + isExpandable: { + type: Boolean, + optional: true, + }, }, template: 'mail.Composer', }); diff --git a/addons/mail/static/src/components/composer/tests/composer_tests.js b/addons/mail/static/src/components/composer/tests/composer_tests.js index ad3772d7a3a..54eb7a5d0e5 100644 --- a/addons/mail/static/src/components/composer/tests/composer_tests.js +++ b/addons/mail/static/src/components/composer/tests/composer_tests.js @@ -28,9 +28,9 @@ QUnit.module('composer_tests.js', { this.start = async params => { const res = await start({ ...params, data: this.data }); - const { afterEvent, components, env, widget } = res; + const { afterEvent, apps, env, widget } = res; this.afterEvent = afterEvent; - this.components = components; + this.apps = apps; this.env = env; this.widget = widget; return res; diff --git a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js index bb53ee72f7b..5df8ab778a1 100644 --- a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js +++ b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js @@ -10,13 +10,23 @@ const { Component, useRef } = owl; class FormViewDialogComponentAdapter extends ComponentAdapter { - renderWidget() { + async renderWidget() { // Ensure the dialog is properly reconstructed. Without this line, it is // impossible to open the dialog again after having it closed a first // time, because the DOM of the dialog has disappeared. - return this.willStart(); + await this.onWillStart(); + this.props.setFormViewDialogWidget(this.widget); } + updateWidget() { + // This component should never be re-rendered but because shouldUpdate was removed, + // when the Composer is rerendered, so is the ComposerSuggestedRecipients even + // though its props haven't changed and there is nothing to do. + } + + get widgetArgs() { + return [this.props.params]; + } } export class ComposerSuggestedRecipient extends Component { @@ -42,7 +52,9 @@ export class ComposerSuggestedRecipient extends Component { * Reference of the partner creation dialog. Useful to open it, for * compatibility with old code. */ - this._dialogRef = useRef('dialog'); + this.setFormViewDialogWidget = (widget) => { + this._dialogWidget = widget; + }; /** * Whether the dialog is currently open. `_dialogRef` cannot be trusted * to know if the dialog is open due to manually calling `open` and @@ -110,12 +122,12 @@ export class ComposerSuggestedRecipient extends Component { // Recipients must always be partners. On selecting a suggested // recipient that does not have a partner, the partner creation form // should be opened. - if (isChecked && this._dialogRef && !this._isDialogOpen) { + if (isChecked && this._dialogWidget && !this._isDialogOpen) { this._isDialogOpen = true; - this._dialogRef.comp.widget.on('closed', this, () => { + this._dialogWidget.on('closed', this, () => { this._isDialogOpen = false; }); - this._dialogRef.comp.widget.open(); + this._dialogWidget.open(); } } } diff --git a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.xml b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.xml index 11b02c44afa..c65ebca0b08 100644 --- a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.xml +++ b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.xml @@ -17,6 +17,7 @@ diff --git a/addons/mail/static/src/components/composer_suggestion/composer_suggestion.js b/addons/mail/static/src/components/composer_suggestion/composer_suggestion.js index 0adf15885e1..c3de7705d4b 100644 --- a/addons/mail/static/src/components/composer_suggestion/composer_suggestion.js +++ b/addons/mail/static/src/components/composer_suggestion/composer_suggestion.js @@ -116,7 +116,7 @@ Object.assign(ComposerSuggestion, { }, props: { composerViewLocalId: String, - isActive: Boolean, + isActive: { type: Boolean, optional: true }, modelName: String, recordLocalId: String, }, diff --git a/addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.js b/addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.js index 5519d07bdfe..2e2d406eb4c 100644 --- a/addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.js +++ b/addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.js @@ -25,7 +25,7 @@ Object.assign(ComposerSuggestionList, { }, props: { composerViewLocalId: String, - isBelow: Boolean, + isBelow: { type: Boolean, optional: true }, }, template: 'mail.ComposerSuggestionList', }); diff --git a/addons/mail/static/src/components/composer_text_input/composer_text_input.js b/addons/mail/static/src/components/composer_text_input/composer_text_input.js index 7926774b6f7..16774d06ebb 100644 --- a/addons/mail/static/src/components/composer_text_input/composer_text_input.js +++ b/addons/mail/static/src/components/composer_text_input/composer_text_input.js @@ -404,7 +404,10 @@ Object.assign(ComposerTextInput, { }, props: { localId: String, - hasMentionSuggestionsBelowPosition: Boolean, + hasMentionSuggestionsBelowPosition: { + type: Boolean, + optional: true, + }, isCompact: Boolean, onComposerTextInputSendShortcut: { type: Function, diff --git a/addons/mail/static/src/components/discuss/discuss.js b/addons/mail/static/src/components/discuss/discuss.js index 0c573f0d304..3003ce45edc 100644 --- a/addons/mail/static/src/components/discuss/discuss.js +++ b/addons/mail/static/src/components/discuss/discuss.js @@ -11,7 +11,6 @@ export class Discuss extends Component { * @override */ setup() { - super.setup(); this._updateLocalStoreProps(); // bind since passed as props this._onMobileAddItemHeaderInputSelect = this._onMobileAddItemHeaderInputSelect.bind(this); diff --git a/addons/mail/static/src/components/discuss/tests/discuss_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_tests.js index 2683869af26..c45c1ad6260 100644 --- a/addons/mail/static/src/components/discuss/tests/discuss_tests.js +++ b/addons/mail/static/src/components/discuss/tests/discuss_tests.js @@ -28,9 +28,9 @@ QUnit.module('discuss_tests.js', { data: this.data, hasDiscuss: true, })); - const { afterEvent, components, env, widget } = res; + const { afterEvent, apps, env, widget } = res; this.afterEvent = afterEvent; - this.components = components; + this.apps = apps; this.env = env; this.widget = widget; return res; diff --git a/addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.js b/addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.js index 3347cec4fdc..52bb59200da 100644 --- a/addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.js +++ b/addons/mail/static/src/components/discuss_sidebar_category/discuss_sidebar_category.js @@ -10,7 +10,7 @@ export class DiscussSidebarCategory extends Component { * @returns {DiscussSidebarCategory} */ get category() { - return this.messaging.models['DiscussSidebarCategory'].get(this.props.localId); + return this.messaging && this.messaging.models['DiscussSidebarCategory'].get(this.props.localId); } } diff --git a/addons/mail/static/src/components/emoji_list/emoji_list.xml b/addons/mail/static/src/components/emoji_list/emoji_list.xml index cef13a0bb61..a7909c2efc5 100644 --- a/addons/mail/static/src/components/emoji_list/emoji_list.xml +++ b/addons/mail/static/src/components/emoji_list/emoji_list.xml @@ -4,7 +4,7 @@
- + diff --git a/addons/mail/static/src/components/file_uploader/tests/file_uploader_tests.js b/addons/mail/static/src/components/file_uploader/tests/file_uploader_tests.js index 66bc8f2c543..0030ac26e53 100644 --- a/addons/mail/static/src/components/file_uploader/tests/file_uploader_tests.js +++ b/addons/mail/static/src/components/file_uploader/tests/file_uploader_tests.js @@ -11,13 +11,13 @@ QUnit.module('file_uploader', {}, function () { QUnit.module('file_uploader_tests.js', { beforeEach() { beforeEach(this); - this.components = []; + this.apps = []; this.start = async params => { const res = await start({ ...params, data: this.data }); - const { components, env, widget } = res; + const { apps, env, widget } = res; this.env = env; - this.components = components; + this.apps = apps; this.widget = widget; return res; }; diff --git a/addons/mail/static/src/components/follow_button/follow_button.js b/addons/mail/static/src/components/follow_button/follow_button.js index 4c059fcaead..d658fdb8b8a 100644 --- a/addons/mail/static/src/components/follow_button/follow_button.js +++ b/addons/mail/static/src/components/follow_button/follow_button.js @@ -73,7 +73,7 @@ Object.assign(FollowButton, { isDisabled: false, }, props: { - isDisabled: Boolean, + isDisabled: { type: Boolean, optional: true }, threadLocalId: String, }, template: 'mail.FollowButton', diff --git a/addons/mail/static/src/components/follower/follower.js b/addons/mail/static/src/components/follower/follower.js index 17005dd81ba..c342986a629 100644 --- a/addons/mail/static/src/components/follower/follower.js +++ b/addons/mail/static/src/components/follower/follower.js @@ -51,6 +51,9 @@ export class Follower extends Component { } Object.assign(Follower, { + defaultProps: { + onClick: () => {}, + }, props: { followerLocalId: String, onClick: { diff --git a/addons/mail/static/src/components/follower_list_menu/follower_list_menu.js b/addons/mail/static/src/components/follower_list_menu/follower_list_menu.js index 7ed398b7a7b..ae1ac03772f 100644 --- a/addons/mail/static/src/components/follower_list_menu/follower_list_menu.js +++ b/addons/mail/static/src/components/follower_list_menu/follower_list_menu.js @@ -117,7 +117,7 @@ Object.assign(FollowerListMenu, { isDisabled: false, }, props: { - isDisabled: Boolean, + isDisabled: { type: Boolean, optional: true }, threadLocalId: String, }, template: 'mail.FollowerListMenu', diff --git a/addons/mail/static/src/components/message/message.js b/addons/mail/static/src/components/message/message.js index 55db73b73b1..7df1282420c 100644 --- a/addons/mail/static/src/components/message/message.js +++ b/addons/mail/static/src/components/message/message.js @@ -8,6 +8,7 @@ import { isEventHandled, markEventHandled } from '@mail/utils/utils'; import { _lt } from 'web.core'; import { format } from 'web.field_utils'; +import Popover from "web.Popover"; import { getLangDatetimeFormat } from 'web.time'; const { Component, onWillUnmount, useRef, useState } = owl; @@ -557,6 +558,7 @@ export class Message extends Component { Object.assign(Message, { props: { localId: String }, template: 'mail.Message', + components: { Popover }, }); registerMessagingComponent(Message); diff --git a/addons/mail/static/src/components/message/message.xml b/addons/mail/static/src/components/message/message.xml index 9676e705d3f..a0b6eeebe34 100644 --- a/addons/mail/static/src/components/message/message.xml +++ b/addons/mail/static/src/components/message/message.xml @@ -16,7 +16,7 @@ 'o-squashed': messageView and messageView.isSquashed, 'o-starred': messageView and messageView.message.isStarred, 'mt-3': messageView and !messageView.isSquashed and messageView.threadView, - }" t-attf-class="{{ messageView and messageView.extraClass }} {{ className }}" t-on-click="_onClick" t-on-mouseenter="state.isHovered = true" t-on-mouseleave="state.isHovered = false" t-att-data-message-local-id="messageView and messageView.message.localId" + }" t-attf-class="{{ messageView and messageView.extraClass }} {{ className }}" t-on-click="_onClick" t-on-mouseenter="() => state.isHovered = true" t-on-mouseleave="() => state.isHovered = false" t-att-data-message-local-id="messageView and messageView.message.localId" t-ref="root" > @@ -111,7 +111,7 @@ - + diff --git a/addons/mail/static/src/components/message/tests/message_tests.js b/addons/mail/static/src/components/message/tests/message_tests.js index c99a3751416..04f812cc708 100644 --- a/addons/mail/static/src/components/message/tests/message_tests.js +++ b/addons/mail/static/src/components/message/tests/message_tests.js @@ -22,9 +22,9 @@ QUnit.module('message_tests.js', { this.start = async params => { const res = await start({ ...params, data: this.data }); - const { afterEvent, components, env, widget } = res; + const { afterEvent, apps, env, widget } = res; this.afterEvent = afterEvent; - this.components = components; + this.apps = apps; this.env = env; this.widget = widget; return res; diff --git a/addons/mail/static/src/components/message_action_list/message_action_list.xml b/addons/mail/static/src/components/message_action_list/message_action_list.xml index 62c0f1118a7..d83f911952b 100644 --- a/addons/mail/static/src/components/message_action_list/message_action_list.xml +++ b/addons/mail/static/src/components/message_action_list/message_action_list.xml @@ -1,7 +1,7 @@ -
+
diff --git a/addons/mail/static/src/components/message_in_reply_to_view/message_in_reply_to_view.js b/addons/mail/static/src/components/message_in_reply_to_view/message_in_reply_to_view.js index dfb72ad204a..0a009aaf1b4 100644 --- a/addons/mail/static/src/components/message_in_reply_to_view/message_in_reply_to_view.js +++ b/addons/mail/static/src/components/message_in_reply_to_view/message_in_reply_to_view.js @@ -2,9 +2,14 @@ import { registerMessagingComponent } from '@mail/utils/messaging_component'; -const { Component } = owl; +const { Component, markup } = owl; export class MessageInReplyToView extends Component { + + get contentAsMarkup() { + return markup(this.messageInReplyToView.messageView.message.parentMessage.prettyBody); + } + /** * @returns {MessageInReplyToView} */ diff --git a/addons/mail/static/src/components/message_in_reply_to_view/message_in_reply_to_view.xml b/addons/mail/static/src/components/message_in_reply_to_view/message_in_reply_to_view.xml index df427432222..30773f5a43b 100644 --- a/addons/mail/static/src/components/message_in_reply_to_view/message_in_reply_to_view.xml +++ b/addons/mail/static/src/components/message_in_reply_to_view/message_in_reply_to_view.xml @@ -7,7 +7,7 @@ @ - + Click to see the attachments diff --git a/addons/mail/static/src/components/message_list/message_list.js b/addons/mail/static/src/components/message_list/message_list.js index 82bfeb4a982..213642aeb8b 100644 --- a/addons/mail/static/src/components/message_list/message_list.js +++ b/addons/mail/static/src/components/message_list/message_list.js @@ -5,6 +5,8 @@ import { useComponentToModel } from '@mail/component_hooks/use_component_to_mode import { useRenderedValues } from '@mail/component_hooks/use_rendered_values/use_rendered_values'; import { useUpdate } from '@mail/component_hooks/use_update/use_update'; +import { Transition } from "@web/core/transition"; + const { Component, onWillPatch, useRef } = owl; export class MessageList extends Component { @@ -385,6 +387,7 @@ export class MessageList extends Component { } Object.assign(MessageList, { + components: { Transition }, defaultProps: { hasScrollAdjust: true, }, @@ -397,7 +400,10 @@ Object.assign(MessageList, { type: Function, optional: true, }, - hasScrollAdjust: Boolean, + hasScrollAdjust: { + type: Boolean, + optional: true, + }, localId: String, }, template: 'mail.MessageList', diff --git a/addons/mail/static/src/components/message_list/message_list.scss b/addons/mail/static/src/components/message_list/message_list.scss index 22452de273b..27a38fd878b 100644 --- a/addons/mail/static/src/components/message_list/message_list.scss +++ b/addons/mail/static/src/components/message_list/message_list.scss @@ -123,11 +123,8 @@ // ------------------------------------------------------------------ .o_MessageList_separatorNewMessages:not(.o-disable-animation) { - &.fade-leave-active { - transition: opacity 0.5s; - } - - &.fade-leave-to { + transition: opacity 0.5s; + &.o-fade-leave { opacity: 0; } } diff --git a/addons/mail/static/src/components/message_list/message_list.xml b/addons/mail/static/src/components/message_list/message_list.xml index 7f902e8d091..4dd2feb087e 100644 --- a/addons/mail/static/src/components/message_list/message_list.xml +++ b/addons/mail/static/src/components/message_list/message_list.xml @@ -48,11 +48,11 @@ - -
+ +

New messages
- +
diff --git a/addons/mail/static/src/components/messaging_menu/tests/messaging_menu_tests.js b/addons/mail/static/src/components/messaging_menu/tests/messaging_menu_tests.js index b8e65de9bad..25c848958af 100644 --- a/addons/mail/static/src/components/messaging_menu/tests/messaging_menu_tests.js +++ b/addons/mail/static/src/components/messaging_menu/tests/messaging_menu_tests.js @@ -397,7 +397,6 @@ QUnit.test('new message', async function (assert) { hasChatWindow: true, }); await createMessagingMenuComponent(); - await afterNextRender(() => document.querySelector(`.o_MessagingMenu_toggler`).click() ); 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 435789220b6..09d56191fee 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 @@ -3,7 +3,7 @@ import { useModels } from "@mail/component_hooks/use_models/use_models"; import { getMessagingComponent } from "@mail/utils/messaging_component"; -const { Component } = owl; +const { Component, useSubEnv } = owl; export class MessagingMenuContainer extends Component { @@ -13,7 +13,7 @@ export class MessagingMenuContainer extends Component { setup() { // for now, the legacy env is needed for internal functions such as // `useModels` to work - this.env = Component.env; + useSubEnv(Component.env); useModels(); super.setup(); } diff --git a/addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.xml b/addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.xml index 7b5d16cade5..783e31787dd 100644 --- a/addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.xml +++ b/addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.xml @@ -5,7 +5,7 @@
-
+
diff --git a/addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js b/addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js index 58170ecfb15..bf0c8783364 100644 --- a/addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js +++ b/addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js @@ -41,12 +41,12 @@ Object.assign(PartnerImStatusIcon, { }, props: { partnerLocalId: String, - hasBackground: Boolean, + hasBackground: { type: Boolean, optional: true }, /** * Determines whether a click on `this` should open a chat with * `this.partner`. */ - hasOpenChat: Boolean, + hasOpenChat: { type: Boolean, optional: true }, }, template: 'mail.PartnerImStatusIcon', }); 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 af016b30531..84bee737e4c 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 @@ -3,7 +3,7 @@ import { useModels } from "@mail/component_hooks/use_models/use_models"; import { getMessagingComponent } from "@mail/utils/messaging_component"; -const { Component } = owl; +const { Component, useSubEnv } = owl; export class RtcActivityNoticeContainer extends Component { @@ -13,7 +13,7 @@ export class RtcActivityNoticeContainer extends Component { setup() { // for now, the legacy env is needed for internal functions such as // `useModels` to work - this.env = Component.env; + useSubEnv(Component.env); useModels(); super.setup(); } diff --git a/addons/mail/static/src/components/rtc_call_participant_card/rtc_call_participant_card.js b/addons/mail/static/src/components/rtc_call_participant_card/rtc_call_participant_card.js index ad06e98cb98..c53626f4425 100644 --- a/addons/mail/static/src/components/rtc_call_participant_card/rtc_call_participant_card.js +++ b/addons/mail/static/src/components/rtc_call_participant_card/rtc_call_participant_card.js @@ -2,6 +2,8 @@ import { registerMessagingComponent } from '@mail/utils/messaging_component'; +import Popover from "web.Popover"; + const { Component, useRef } = owl; export class RtcCallParticipantCard extends Component { @@ -48,6 +50,7 @@ export class RtcCallParticipantCard extends Component { Object.assign(RtcCallParticipantCard, { props: { localId: String }, template: 'mail.RtcCallParticipantCard', + components: { Popover }, }); registerMessagingComponent(RtcCallParticipantCard); diff --git a/addons/mail/static/src/components/rtc_controller/rtc_controller.js b/addons/mail/static/src/components/rtc_controller/rtc_controller.js index 0d1e70a95ef..cddb079bc44 100644 --- a/addons/mail/static/src/components/rtc_controller/rtc_controller.js +++ b/addons/mail/static/src/components/rtc_controller/rtc_controller.js @@ -2,6 +2,8 @@ import { registerMessagingComponent } from '@mail/utils/messaging_component'; +import Popover from "web.Popover"; + const { Component } = owl; export class RtcController extends Component { @@ -18,6 +20,7 @@ export class RtcController extends Component { Object.assign(RtcController, { props: { localId: String }, template: 'mail.RtcController', + components: { Popover }, }); registerMessagingComponent(RtcController); diff --git a/addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.js b/addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.js index 6c4b60687de..ca3f329127d 100644 --- a/addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.js +++ b/addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.js @@ -15,10 +15,12 @@ Object.assign(ThreadTypingIcon, { animation: { type: String, validate: prop => ['bounce', 'none', 'pulse'].includes(prop), + optional: true, }, size: { type: String, validate: prop => ['small', 'medium'].includes(prop), + optional: true, }, title: { type: String, diff --git a/addons/mail/static/src/components/thread_view/tests/thread_view_tests.js b/addons/mail/static/src/components/thread_view/tests/thread_view_tests.js index 5ce814ca89f..83e803ccac4 100644 --- a/addons/mail/static/src/components/thread_view/tests/thread_view_tests.js +++ b/addons/mail/static/src/components/thread_view/tests/thread_view_tests.js @@ -18,9 +18,9 @@ QUnit.module('thread_view_tests.js', { this.start = async params => { const res = await start({ ...params, data: this.data }); - const { afterEvent, components, env, widget } = res; + const { afterEvent, apps, env, widget } = res; this.afterEvent = afterEvent; - this.components = components; + this.apps = apps; this.env = env; this.widget = widget; return res; diff --git a/addons/mail/static/src/components/thread_view/thread_view.js b/addons/mail/static/src/components/thread_view/thread_view.js index 9c9fb5c2061..4eece5e83ad 100644 --- a/addons/mail/static/src/components/thread_view/thread_view.js +++ b/addons/mail/static/src/components/thread_view/thread_view.js @@ -42,6 +42,7 @@ Object.assign(ThreadView, { hasComposerThreadName: false, showComposerAttachmentsExtensions: true, showComposerAttachmentsFilenames: true, + onFocusin: () => {}, }, props: { /** @@ -86,8 +87,14 @@ Object.assign(ThreadView, { type: Function, optional: true, }, - showComposerAttachmentsExtensions: Boolean, - showComposerAttachmentsFilenames: Boolean, + showComposerAttachmentsExtensions: { + type: Boolean, + optional: true, + }, + showComposerAttachmentsFilenames: { + type: Boolean, + optional: true, + }, }, template: 'mail.ThreadView', }); diff --git a/addons/mail/static/src/js/utils.js b/addons/mail/static/src/js/utils.js index da8b7eeb7f3..955a3fdb0b3 100644 --- a/addons/mail/static/src/js/utils.js +++ b/addons/mail/static/src/js/utils.js @@ -1,8 +1,7 @@ /** @odoo-module **/ import core from 'web.core'; - -const { escape } = owl; +import { escape } from '@web/core/utils/strings'; var _t = core._t; diff --git a/addons/mail/static/src/js/views/activity/activity_renderer.js b/addons/mail/static/src/js/views/activity/activity_renderer.js index 94b19a75baa..e5e7098c627 100644 --- a/addons/mail/static/src/js/views/activity/activity_renderer.js +++ b/addons/mail/static/src/js/views/activity/activity_renderer.js @@ -14,7 +14,6 @@ import utils from 'web.utils'; const { useState } = owl; const _t = core._t; - /** * Owl Component Adapter for ActivityRecord which is KanbanRecord (Odoo Widget) * TODO: Remove this adapter when ActivityRecord is a Component @@ -70,16 +69,23 @@ class KanbanColumnProgressBarAdapter extends ComponentAdapter { // KanbanColumnProgressBar triggers 3 events before being mounted // but we don't need to listen to them in our case. if (this.el) { + if (ev.name === "set_progress_bar_state") { + this.props.onSetProgressBarState(new CustomEvent("set-progress-bar-state", { + bubbles: true, + cancelable: true, + detail: ev.data, + })); + } super._trigger_up(ev); } } } class ActivityRenderer extends AbstractRendererOwl { - constructor(parent, props) { - super(...arguments); + setup() { + super.setup(...arguments); this.qweb = new QWeb(this.env.isDebug(), {_s: session.origin}); - this.qweb.add_template(utils.json_node_to_xml(props.templates)); + this.qweb.add_template(utils.json_node_to_xml(this.props.templates)); this.activeFilter = useState({ state: null, activityTypeId: null, @@ -192,7 +198,11 @@ class ActivityRenderer extends AbstractRendererOwl { } else { this.activeFilter.state = null; this.activeFilter.activityTypeId = null; - this.activeFilter.resIds = []; + if (this.activeFilter.resIds.length > 0) { + // writing a new array is a state mutation which triggers a rerender + // only replace resIds with empty array if it's not already empty + this.activeFilter.resIds = []; + } } } } diff --git a/addons/mail/static/src/models/activity/activity.js b/addons/mail/static/src/models/activity/activity.js index 468d427cd1e..cbd7ec95079 100644 --- a/addons/mail/static/src/models/activity/activity.js +++ b/addons/mail/static/src/models/activity/activity.js @@ -4,6 +4,8 @@ import { registerModel } from '@mail/model/model_core'; import { attr, many, one } from '@mail/model/model_field'; import { clear, insert, unlink, unlinkAll } from '@mail/model/model_field_command'; +const { markup } = owl; + registerModel({ name: 'Activity', identifyingFields: ['id'], diff --git a/addons/mail/static/src/models/channel_invitation_form/channel_invitation_form.js b/addons/mail/static/src/models/channel_invitation_form/channel_invitation_form.js index 24b188d295e..f879669119a 100644 --- a/addons/mail/static/src/models/channel_invitation_form/channel_invitation_form.js +++ b/addons/mail/static/src/models/channel_invitation_form/channel_invitation_form.js @@ -53,9 +53,8 @@ registerModel({ }, /** * @param {Partner} partner - * @param {MouseEvent} ev */ - onClickSelectablePartner(partner, ev) { + onClickSelectablePartner(partner) { if (this.selectedPartners.includes(partner)) { this.update({ selectedPartners: unlink(partner) }); return; @@ -64,9 +63,8 @@ registerModel({ }, /** * @param {Partner} partner - * @param {MouseEvent} ev */ - onClickSelectedPartner(partner, ev) { + onClickSelectedPartner(partner) { this.update({ selectedPartners: unlink(partner) }); }, /** diff --git a/addons/mail/static/src/models/composer_view/composer_view.js b/addons/mail/static/src/models/composer_view/composer_view.js index 4b21c6243ab..e3cb1c04972 100644 --- a/addons/mail/static/src/models/composer_view/composer_view.js +++ b/addons/mail/static/src/models/composer_view/composer_view.js @@ -8,7 +8,7 @@ import { OnChange } from '@mail/model/model_onchange'; import { addLink, escapeAndCompactTextContent, parseAndTransform } from '@mail/js/utils'; import { isEventHandled, markEventHandled } from '@mail/utils/utils'; -const { escape } = owl; +import { escape } from '@web/core/utils/strings'; registerModel({ name: 'ComposerView', diff --git a/addons/mail/static/src/models/discuss/discuss.js b/addons/mail/static/src/models/discuss/discuss.js index 2a20347d890..3fc90ca5592 100644 --- a/addons/mail/static/src/models/discuss/discuss.js +++ b/addons/mail/static/src/models/discuss/discuss.js @@ -3,8 +3,7 @@ import { registerModel } from '@mail/model/model_core'; import { attr, one } from '@mail/model/model_field'; import { clear, insertAndReplace, link, unlink } from '@mail/model/model_field_command'; - -const { escape } = owl; +import { escape } from '@web/core/utils/strings'; registerModel({ name: 'Discuss', diff --git a/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js b/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js index 17b479fc87e..c05963dd875 100644 --- a/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js +++ b/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js @@ -4,10 +4,11 @@ import { registerModel } from '@mail/model/model_core'; import { decrement, increment, insert, insertAndReplace, link, replace, unlink } from '@mail/model/model_field_command'; import { htmlToTextContentInline } from '@mail/js/utils'; +import { escape } from '@web/core/utils/strings'; import { str_to_datetime } from 'web.time'; import { Markup } from 'web.utils'; +import { renderToString } from "@web/core/utils/render"; -const { escape } = owl; const PREVIEW_MSG_MAX_SIZE = 350; // optimal for native English speakers registerModel({ @@ -678,7 +679,7 @@ registerModel({ // hack: notification template does not support OWL components, // so we simply use their template to make HTML as if it comes // from component - const channelIcon = this.env.qweb.renderToString('mail.ThreadIcon', { + const channelIcon = renderToString('mail.ThreadIcon', { env: this.env, thread: channel, }); diff --git a/addons/mail/static/src/public/discuss_public_boot.js b/addons/mail/static/src/public/discuss_public_boot.js index 6f5f1f78156..2a10f2ee1aa 100644 --- a/addons/mail/static/src/public/discuss_public_boot.js +++ b/addons/mail/static/src/public/discuss_public_boot.js @@ -24,13 +24,12 @@ import { } from '@web/legacy/utils'; import * as legacySession from 'web.session'; -const { Component, config, mount, whenReady } = owl; +const { Component, mount, whenReady } = owl; Component.env = legacyEnv; (async function boot() { await whenReady(); - config.mode = Component.env.isDebug() ? 'dev' : 'prod'; AbstractService.prototype.deployServices(Component.env); const serviceRegistry = registry.category('services'); serviceRegistry.add('legacy_rpc', makeLegacyRpcService(Component.env)); @@ -39,7 +38,6 @@ Component.env = legacyEnv; serviceRegistry.add('legacy_crash_manager', makeLegacyCrashManagerService(Component.env)); serviceRegistry.add('legacy_dialog_mapping', makeLegacyDialogMappingService(Component.env)); await legacySession.is_bound; - Component.env.qweb.addTemplates(legacySession.owlTemplates); Object.assign(odoo, { info: { db: session.db, @@ -54,7 +52,6 @@ Component.env = legacyEnv; startServices(env), odoo.loadTemplatesPromise.then(processTemplates), ]); - env.qweb.addTemplates(templates); mapLegacyEnvToWowlEnv(Component.env, env); odoo.isReady = true; legacyServiceRegistry.add('messaging', MessagingService.extend({ @@ -62,16 +59,19 @@ Component.env = legacyEnv; autofetchPartnerImStatus: false, }, })); - await mount(MainComponentsContainer, { env, target: document.body }); - createAndMountDiscussPublicView(); + await mount(MainComponentsContainer, document.body, { env, templates, dev: env.debug }); + createAndMountDiscussPublicView(templates); })(); -async function createAndMountDiscussPublicView() { +async function createAndMountDiscussPublicView(templates) { const messaging = await Component.env.services.messaging.get(); // needed by the attachment viewer const DialogManager = getMessagingComponent('DialogManager'); - const dialogManagerComponent = new DialogManager(null, {}); - await dialogManagerComponent.mount(document.body); + await mount(DialogManager, document.body, { + templates, + env: Component.env, + dev: Component.env.isDebug(), + }); messaging.models['Thread'].insert(messaging.models['Thread'].convertData(data.channelData)); const discussPublicView = messaging.models['DiscussPublicView'].create(data.discussPublicViewData); if (discussPublicView.shouldDisplayWelcomeViewInitially) { @@ -80,8 +80,12 @@ async function createAndMountDiscussPublicView() { discussPublicView.switchToThreadView(); } const DiscussPublicView = getMessagingComponent('DiscussPublicView'); - const discussPublicViewComponent = new DiscussPublicView(null, { - localId: discussPublicView.localId, + await mount(DiscussPublicView, document.body, { + templates, + env: Component.env, + dev: Component.env.isDebug(), + props: { + localId: discussPublicView.localId, + }, }); - await discussPublicViewComponent.mount(document.body); } diff --git a/addons/mail/static/src/public/root_widget.js b/addons/mail/static/src/public/root_widget.js index 9ba6785d6a6..f254bdb18fb 100644 --- a/addons/mail/static/src/public/root_widget.js +++ b/addons/mail/static/src/public/root_widget.js @@ -3,9 +3,10 @@ * TourManager so it can get access to _trigger_up. */ odoo.define("root.widget", function (require) { - const { ComponentAdapter } = require("web.OwlCompatibility"); - + // need to wait for owl.Component.env to be set by discuss_public_boot before + // we spawn the component adapter + require("@mail/public/discuss_public_boot"); + const { standaloneAdapter } = require("web.OwlCompatibility"); const { Component } = owl; - - return new ComponentAdapter(null, { Component }); + return standaloneAdapter({ Component }); }); diff --git a/addons/mail/static/src/services/chat_window_service/chat_window_service.js b/addons/mail/static/src/services/chat_window_service/chat_window_service.js index 4501af03d08..34b17002158 100644 --- a/addons/mail/static/src/services/chat_window_service/chat_window_service.js +++ b/addons/mail/static/src/services/chat_window_service/chat_window_service.js @@ -5,6 +5,8 @@ import { getMessagingComponent } from "@mail/utils/messaging_component"; import AbstractService from 'web.AbstractService'; import { bus } from 'web.core'; +const { App } = owl; + export const ChatWindowService = AbstractService.extend({ dependencies: ['messaging'], /** @@ -18,9 +20,9 @@ export const ChatWindowService = AbstractService.extend({ * @private */ destroy() { - if (this.component) { - this.component.destroy(); - this.component = undefined; + if (this.app) { + this.app.destroy(); + this.app = undefined; } }, @@ -45,14 +47,20 @@ export const ChatWindowService = AbstractService.extend({ * @private */ async _mount() { - if (this.component) { - this.component.destroy(); - this.component = undefined; + if (this.app) { + this.app.destroy(); + this.app = undefined; } const ChatWindowManagerComponent = getMessagingComponent("ChatWindowManager"); - this.component = new ChatWindowManagerComponent(null); + this.app = new App(ChatWindowManagerComponent, { + templates: window.__OWL_TEMPLATES__, + env: owl.Component.env, + dev: owl.Component.env.isDebug(), + translateFn: owl.Component.env._t, + translatableAttributes: ["data-tooltip"], + }); const parentNode = this._getParentNode(); - await this.component.mount(parentNode); + await this.app.mount(parentNode); }, //-------------------------------------------------------------------------- diff --git a/addons/mail/static/src/services/dialog_service/dialog_service.js b/addons/mail/static/src/services/dialog_service/dialog_service.js index ed53b0aa77c..2e95ab7c0e3 100644 --- a/addons/mail/static/src/services/dialog_service/dialog_service.js +++ b/addons/mail/static/src/services/dialog_service/dialog_service.js @@ -5,6 +5,8 @@ import { getMessagingComponent } from "@mail/utils/messaging_component"; import AbstractService from 'web.AbstractService'; import { bus } from 'web.core'; +const { App } = owl; + export const DialogService = AbstractService.extend({ dependencies: ['messaging'], /** @@ -18,9 +20,9 @@ export const DialogService = AbstractService.extend({ * @private */ destroy() { - if (this.component) { - this.component.destroy(); - this.component = undefined; + if (this.app) { + this.app.destroy(); + this.app = undefined; } }, @@ -45,14 +47,20 @@ export const DialogService = AbstractService.extend({ * @private */ async _mount() { - if (this.component) { - this.component.destroy(); - this.component = undefined; + if (this.app) { + this.app.destroy(); + this.app = undefined; } const DialogManagerComponent = getMessagingComponent("DialogManager"); - this.component = new DialogManagerComponent(null); + this.app = new App(DialogManagerComponent, { + templates: window.__OWL_TEMPLATES__, + env: owl.Component.env, + dev: owl.Component.env.isDebug(), + translateFn: owl.Component.env._t, + translatableAttributes: ["data-tooltip"], + }); const parentNode = this._getParentNode(); - await this.component.mount(parentNode); + await this.app.mount(parentNode); }, //-------------------------------------------------------------------------- diff --git a/addons/mail/static/src/utils/test_utils.js b/addons/mail/static/src/utils/test_utils.js index c3840994704..861a7aef03d 100644 --- a/addons/mail/static/src/utils/test_utils.js +++ b/addons/mail/static/src/utils/test_utils.js @@ -25,11 +25,13 @@ import { mock, } from 'web.test_utils'; import Widget from 'web.Widget'; +import { getFixture } from "@web/../tests/helpers/utils"; import { createWebClient, getActionManagerServerData } from "@web/../tests/webclient/helpers"; import LegacyRegistry from "web.Registry"; -const { Component, EventBus } = owl; +const { App, Component, EventBus } = owl; +const { afterNextRender } = App; const { addMockEnvironment, patch: legacyPatch, @@ -162,74 +164,11 @@ function _useDiscuss(callbacks, { afterNextRender }) { * @returns {Promise} */ function nextAnimationFrame() { - const requestAnimationFrame = Component.scheduler.requestAnimationFrame; return new Promise(function (resolve) { setTimeout(() => requestAnimationFrame(() => resolve())); }); } -/** - * Returns a promise resolved the next time OWL stops rendering. - * - * @param {function} func function which, when called, is - * expected to trigger OWL render(s). - * @param {number} [timeoutDelay=5000] in ms - * @returns {Promise} - */ -const afterNextRender = (function () { - const stop = Component.scheduler.stop; - const stopPromises = []; - - Component.scheduler.stop = function () { - const wasRunning = this.isRunning; - stop.call(this); - if (wasRunning) { - while (stopPromises.length) { - stopPromises.pop().resolve(); - } - } - }; - - async function afterNextRender(func, timeoutDelay = 5000) { - // Define the potential errors outside of the promise to get a proper - // trace if they happen. - const startError = new Error("Timeout: the render didn't start."); - const stopError = new Error("Timeout: the render didn't stop."); - // Set up the timeout to reject if no render happens. - let timeoutNoRender; - const timeoutProm = new Promise((resolve, reject) => { - timeoutNoRender = setTimeout(() => { - let error = startError; - if (Component.scheduler.isRunning) { - error = stopError; - } - console.error(error); - reject(error); - }, timeoutDelay); - }); - // Set up the promise to resolve if a render happens. - const prom = makeTestPromise(); - stopPromises.push(prom); - // Start the function expected to trigger a render after the promise - // has been registered to not miss any potential render. - const funcRes = func(); - // Make them race (first to resolve/reject wins). - await Promise.race([prom, timeoutProm]); - clearTimeout(timeoutNoRender); - // Wait the end of the function to ensure all potential effects are - // taken into account during the following verification step. - await funcRes; - // Wait one more frame to make sure no new render has been queued. - await nextAnimationFrame(); - if (Component.scheduler.isRunning) { - await afterNextRender(() => {}, timeoutDelay); - } - } - - return afterNextRender; -})(); - - //------------------------------------------------------------------------------ // Public: test lifecycle //------------------------------------------------------------------------------ @@ -290,7 +229,7 @@ function beforeEach(self) { } Object.assign(self, { - components: [], + apps: [], data, unpatch, widget: undefined @@ -310,9 +249,8 @@ function afterEach(self) { // The components must be destroyed before the widget, because the // widget might destroy the models before destroying the components, // and the components might still rely on messaging (or other) record(s). - while (self.components.length > 0) { - const component = self.components.pop(); - component.destroy(); + while (self.apps.length > 0) { + self.apps.pop().destroy(); } if (self.widget) { self.widget.destroy(); @@ -386,11 +324,17 @@ function getAfterEvent({ messagingBus }) { * @returns {Component} */ async function createRootComponent(self, Component, { props = {}, target }) { - Component.env = self.env; - const component = new Component(null, props); - delete Component.env; - self.components.push(component); - await afterNextRender(() => component.mount(target)); + const app = new App(Component, { + props, + templates: window.__OWL_TEMPLATES__, + env: self.env, + test: true, + }); + self.apps.push(app); + let component; + await afterNextRender(() => { + component = app.mount(target); + }); return component; } @@ -418,11 +362,11 @@ function getClick({ afterNextRender }) { }; } -function getCreateChatterContainerComponent({ afterEvent, components, env, widget }) { +function getCreateChatterContainerComponent({ afterEvent, apps, env, widget }) { return async function createChatterContainerComponent(props, { waitUntilMessagesLoaded = true } = {}) { let chatterContainerComponent; async function func() { - chatterContainerComponent = await createRootMessagingComponent({ components, env }, "ChatterContainer", { + chatterContainerComponent = await createRootMessagingComponent({ apps, env }, "ChatterContainer", { props, target: widget.el, }); @@ -449,67 +393,67 @@ function getCreateChatterContainerComponent({ afterEvent, components, env, widge }; } -function getCreateComposerComponent({ components, env, modelManager, widget }) { +function getCreateComposerComponent({ apps, env, modelManager, widget }) { return async function createComposerComponent(composer, props) { const composerView = modelManager.messaging.models['ComposerView'].create({ qunitTest: insertAndReplace({ composer: replace(composer), }), }); - return await createRootMessagingComponent({ components, env }, "Composer", { + return await createRootMessagingComponent({ apps, env }, "Composer", { props: { localId: composerView.localId, ...props }, target: widget.el, }); }; } -function getCreateComposerSuggestionComponent({ components, env, modelManager, widget }) { +function getCreateComposerSuggestionComponent({ apps, env, modelManager, widget }) { return async function createComposerSuggestionComponent(composer, props) { const composerView = modelManager.messaging.models['ComposerView'].create({ qunitTest: insertAndReplace({ composer: replace(composer), }), }); - await createRootMessagingComponent({ components, env }, "ComposerSuggestion", { + await createRootMessagingComponent({ apps, env }, "ComposerSuggestion", { props: { ...props, composerViewLocalId: composerView.localId }, target: widget.el, }); }; } -function getCreateMessageComponent({ components, env, modelManager, widget }) { +function getCreateMessageComponent({ apps, env, modelManager, widget }) { return async function createMessageComponent(message) { const messageView = modelManager.messaging.models['MessageView'].create({ message: replace(message), qunitTest: insertAndReplace(), }); - await createRootMessagingComponent({ components, env }, "Message", { + await createRootMessagingComponent({ apps, env }, "Message", { props: { localId: messageView.localId }, target: widget.el, }); }; } -function getCreateMessagingMenuComponent({ components, env, widget }) { +function getCreateMessagingMenuComponent({ apps, env, widget }) { return async function createMessagingMenuComponent() { - return await createRootComponent({ components, env }, MessagingMenuContainer, { target: widget.el }); + return await createRootComponent({ apps, env }, MessagingMenuContainer, { target: widget.el }); }; } -function getCreateNotificationListComponent({ components, env, modelManager, widget }) { +function getCreateNotificationListComponent({ apps, env, modelManager, widget }) { return async function createNotificationListComponent({ filter = 'all' } = {}) { const notificationListView = modelManager.messaging.models['NotificationListView'].create({ filter, qunitTestOwner: insertAndReplace(), }); - await createRootMessagingComponent({ components, env }, "NotificationList", { + await createRootMessagingComponent({ apps, env }, "NotificationList", { props: { localId: notificationListView.localId }, target: widget.el, }); }; } -function getCreateThreadViewComponent({ afterEvent, components, env, widget }) { +function getCreateThreadViewComponent({ afterEvent, apps, env, widget }) { return async function createThreadViewComponent(threadView, otherProps = {}, { isFixedSize = false, waitUntilMessagesLoaded = true } = {}) { let target; if (isFixedSize) { @@ -526,7 +470,7 @@ function getCreateThreadViewComponent({ afterEvent, components, env, widget }) { target = widget.el; } async function func() { - return createRootMessagingComponent({ components, env }, "ThreadView", { props: { localId: threadView.localId, ...otherProps }, target }); + return createRootMessagingComponent({ apps, env }, "ThreadView", { props: { localId: threadView.localId, ...otherProps }, target }); } if (waitUntilMessagesLoaded) { await afterNextRender(() => afterEvent({ @@ -549,6 +493,16 @@ function getCreateThreadViewComponent({ afterEvent, components, env, widget }) { function getOpenDiscuss({ afterNextRender, discussWidget }) { return async function openDiscuss() { 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(); + }); }; } @@ -592,6 +546,8 @@ function getOpenDiscuss({ afterNextRender, discussWidget }) { * 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` @@ -636,6 +592,7 @@ async function start(param0 = {}) { hasView = false, loadingBaseDelayDuration = 0, messagingBeforeCreationDeferred = Promise.resolve(), + target = getFixture(), waitUntilEvent, waitUntilMessagingCondition = 'initialized', } = param0; @@ -649,6 +606,7 @@ async function start(param0 = {}) { delete param0.hasDiscuss; delete param0.hasTimeControl; delete param0.hasView; + delete param0.target; if (hasChatWindow) { callbacks = _useChatWindow(callbacks, { afterNextRender }); } @@ -800,7 +758,7 @@ async function start(param0 = {}) { legacyParams.withLegacyMockServer = true; legacyParams.env = env; - widget = await createWebClient({ serverData, mockRPC, legacyParams }); + widget = await createWebClient({ target, serverData, mockRPC, legacyParams }); legacyPatch(widget, { destroy() { @@ -833,10 +791,10 @@ async function start(param0 = {}) { testSetupDoneDeferred.resolve(); waitUntilEventPromise = Promise.resolve(); } - const components = []; + const apps = []; const result = { afterEvent, - components, + apps, env: testEnv, mockServer, widget, @@ -862,13 +820,13 @@ async function start(param0 = {}) { ...result, afterNextRender, click: getClick({ afterNextRender }), - createChatterContainerComponent: getCreateChatterContainerComponent({ afterEvent, components, env: testEnv, widget }), - createComposerComponent: getCreateComposerComponent({ components, env: testEnv, modelManager, widget }), - createComposerSuggestionComponent: getCreateComposerSuggestionComponent({ components, env: testEnv, modelManager, widget }), - createMessageComponent: getCreateMessageComponent({ components, env: testEnv, modelManager, widget }), - createMessagingMenuComponent: getCreateMessagingMenuComponent({ components, env: testEnv, widget }), - createNotificationListComponent: getCreateNotificationListComponent({ components, env: testEnv, modelManager, widget }), - createThreadViewComponent: getCreateThreadViewComponent({ afterEvent, components, env: testEnv, widget }), + createChatterContainerComponent: getCreateChatterContainerComponent({ afterEvent, apps, env: testEnv, widget }), + createComposerComponent: getCreateComposerComponent({ apps, env: testEnv, modelManager, widget }), + createComposerSuggestionComponent: getCreateComposerSuggestionComponent({ apps, env: testEnv, modelManager, widget }), + createMessageComponent: getCreateMessageComponent({ apps, env: testEnv, modelManager, widget }), + createMessagingMenuComponent: getCreateMessagingMenuComponent({ apps, env: testEnv, widget }), + createNotificationListComponent: getCreateNotificationListComponent({ apps, env: testEnv, modelManager, widget }), + createThreadViewComponent: getCreateThreadViewComponent({ afterEvent, apps, env: testEnv, widget }), messaging: modelManager.messaging, openDiscuss, }; diff --git a/addons/mail/static/src/widgets/discuss/discuss.js b/addons/mail/static/src/widgets/discuss/discuss.js index f9068bb6673..09d87808544 100644 --- a/addons/mail/static/src/widgets/discuss/discuss.js +++ b/addons/mail/static/src/widgets/discuss/discuss.js @@ -4,7 +4,7 @@ import { DiscussContainer } from "@mail/components/discuss_container/discuss_con import AbstractAction from 'web.AbstractAction'; -const { Component } = owl; +const { App, Component } = owl; export const DiscussWidget = AbstractAction.extend({ template: 'mail.widgets.Discuss', @@ -27,7 +27,7 @@ export const DiscussWidget = AbstractAction.extend({ this.discuss = undefined; this.options = options; - this.component = undefined; + this.app = undefined; this._lastPushStateActiveThread = null; this.env = Component.env; @@ -45,9 +45,9 @@ export const DiscussWidget = AbstractAction.extend({ * @override {web.AbstractAction} */ destroy() { - if (this.component) { - this.component.destroy(); - this.component = undefined; + if (this.app) { + this.app.destroy(); + this.app = undefined; } if (this.$buttons) { this.$buttons.off().remove(); @@ -57,13 +57,12 @@ export const DiscussWidget = AbstractAction.extend({ /** * @override {web.AbstractAction} */ - on_attach_callback() { + async on_attach_callback() { this._super(...arguments); - if (this.component) { + if (this.app) { // prevent twice call to on_attach_callback (FIXME) return; } - this.component = new DiscussContainer(); this._pushStateActionManagerEventListener = ev => { ev.stopPropagation(); if (this._lastPushStateActiveThread === this.discuss.thread) { @@ -84,17 +83,25 @@ export const DiscussWidget = AbstractAction.extend({ 'o-show-rainbow-man', this._showRainbowManEventListener ); - return this.component.mount(this.el); + + 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"], + }); + await this.app.mount(this.el); }, /** * @override {web.AbstractAction} */ on_detach_callback() { this._super(...arguments); - if (this.component) { - this.component.destroy(); + if (this.app) { + this.app.destroy(); } - this.component = undefined; + this.app = undefined; this.el.removeEventListener( 'o-push-state-action-manager', this._pushStateActionManagerEventListener diff --git a/addons/mail/static/src/widgets/form_renderer/form_renderer.js b/addons/mail/static/src/widgets/form_renderer/form_renderer.js index 78abe007e6e..86fef09c235 100644 --- a/addons/mail/static/src/widgets/form_renderer/form_renderer.js +++ b/addons/mail/static/src/widgets/form_renderer/form_renderer.js @@ -105,6 +105,9 @@ FormRenderer.include({ * @returns {jQuery.Element} */ _makeChatterContainerTarget() { + if (this._chatterContainerTarget) { + return $(this._chatterContainerTarget); + } const $el = $('
'); this._chatterContainerTarget = $el[0]; return $el; @@ -151,7 +154,7 @@ FormRenderer.include({ if (!this._chatterContainerComponent) { this._makeChatterContainerComponent(); } else { - await this._updateChatterContainerComponent(); + return this._updateChatterContainerComponent(); } await this._mountChatterContainerComponent(); } diff --git a/addons/mail/static/src/xml/activity_view.xml b/addons/mail/static/src/xml/activity_view.xml index 096b6095839..12bbd1c9a25 100644 --- a/addons/mail/static/src/xml/activity_view.xml +++ b/addons/mail/static/src/xml/activity_view.xml @@ -28,7 +28,7 @@ + onSetProgressBarState.bind="_onSetProgressBarState"/>
@@ -75,7 +75,7 @@ - + Schedule activity diff --git a/addons/mail/static/tests/activity_tests.js b/addons/mail/static/tests/activity_tests.js index 064e962a9b2..82517112598 100644 --- a/addons/mail/static/tests/activity_tests.js +++ b/addons/mail/static/tests/activity_tests.js @@ -5,12 +5,13 @@ import ActivityView from '@mail/js/views/activity/activity_view'; import testUtils from 'web.test_utils'; import domUtils from 'web.dom'; -import { legacyExtraNextTick, patchWithCleanup } from "@web/../tests/helpers/utils"; +import { getFixture, legacyExtraNextTick, patchWithCleanup } from "@web/../tests/helpers/utils"; import { createWebClient, doAction } from "@web/../tests/webclient/helpers"; import { session } from '@web/session'; import { click } from "@web/../tests/helpers/utils"; let serverData; +let target; var createView = testUtils.createView; @@ -116,6 +117,7 @@ QUnit.module('activity view', { }, }; serverData = { models: this.data }; + target = getFixture(); } }); @@ -402,17 +404,17 @@ QUnit.test("activity view: no group_by_menu and no comparison_menu", async funct await doAction(webClient, 1); assert.containsN( - webClient, + target, ".o_search_options .dropdown button:visible", 2, "only two elements should be available in view search" ); assert.isVisible( - $(webClient.el).find(".o_search_options .dropdown.o_filter_menu > button"), + target.querySelector(".o_search_options .dropdown.o_filter_menu > button"), "filter should be available in view search" ); assert.isVisible( - $(webClient.el).find(".o_search_options .dropdown.o_favorite_menu > button"), + target.querySelector(".o_search_options .dropdown.o_favorite_menu > button"), "favorites should be available in view search" ); }); @@ -522,7 +524,7 @@ QUnit.test("Activity view: discard an activity creation dialog", async function await doAction(webClient, 1); await testUtils.dom.click( - $(webClient.el).find(".o_activity_view .o_data_row .o_activity_empty_cell")[0] + target.querySelector(".o_activity_view .o_data_row .o_activity_empty_cell") ); await legacyExtraNextTick(); assert.containsOnce($, ".modal.o_technical_modal", "Activity Modal should be opened"); @@ -578,10 +580,10 @@ QUnit.test('Activity view: many2one_avatar_user widget in activity view', async await doAction(webClient, 1); await legacyExtraNextTick(); - assert.containsN(webClient, '.o_m2o_avatar', 2); - assert.containsOnce(webClient, 'tr[data-res-id=13] .o_m2o_avatar > img[src="/web/image/res.users/1/avatar_128"]', + assert.containsN(target, '.o_m2o_avatar', 2); + assert.containsOnce(target, 'tr[data-res-id=13] .o_m2o_avatar > img[src="/web/image/res.users/1/avatar_128"]', "should have m2o avatar image"); - assert.containsNone(webClient, '.o_m2o_avatar > span', + assert.containsNone(target, '.o_m2o_avatar > span', "should not have text on many2one_avatar_user if onlyImage node option is passed"); }); @@ -602,11 +604,14 @@ QUnit.test("Activity view: on_destroy_callback doesn't crash", async function (a }; patchWithCleanup(ActivityRenderer.prototype, { - mounted() { - assert.step('mounted'); - }, - willUnmount() { - assert.step('willUnmount'); + setup() { + this._super(); + owl.onMounted(() => { + assert.step('mounted'); + }); + owl.onWillUnmount(() => { + assert.step('willUnmount'); + }); } }); @@ -660,7 +665,7 @@ QUnit.test("Schedule activity dialog uses the same search view as activity view" ]) // click on "Schedule activity" - await click(webClient.el.querySelector(".o_activity_view .o_record_selector")); + await click(target.querySelector(".o_activity_view .o_record_selector")); assert.verifySteps([ '[[false,"list"],[false,"search"]]', @@ -680,7 +685,7 @@ QUnit.test("Schedule activity dialog uses the same search view as activity view" ]) // click on "Schedule activity" - await click(webClient.el.querySelector(".o_activity_view .o_record_selector")); + await click(target.querySelector(".o_activity_view .o_record_selector")); assert.verifySteps([ '[[false,"list"],[1,"search"]]', @@ -715,28 +720,28 @@ QUnit.test('Activity view: apply progressbar filter', async function (assert) { await doAction(webClient, 1); - assert.containsNone(webClient.el.querySelector('.o_activity_view thead'), + assert.containsNone(target.querySelector('.o_activity_view thead'), '.o_activity_filter_planned,.o_activity_filter_today,.o_activity_filter_overdue,.o_activity_filter___false', "should not have active filter"); - assert.containsNone(webClient.el.querySelector('.o_activity_view tbody'), + assert.containsNone(target.querySelector('.o_activity_view tbody'), '.o_activity_filter_planned,.o_activity_filter_today,.o_activity_filter_overdue,.o_activity_filter___false', "should not have active filter"); - assert.strictEqual(webClient.el.querySelector('.o_activity_view tbody .o_activity_record').textContent, + assert.strictEqual(target.querySelector('.o_activity_view tbody .o_activity_record').textContent, 'Office planning', "'Office planning' should be first record"); - assert.containsOnce(webClient.el.querySelector('.o_activity_view tbody'), '.planned', + assert.containsOnce(target.querySelector('.o_activity_view tbody'), '.planned', "other records should be available"); - await testUtils.dom.click(webClient.el.querySelector('.o_kanban_counter_progress .progress-bar[data-filter="planned"]')); - assert.containsOnce(webClient.el.querySelector('.o_activity_view thead'), '.o_activity_filter_planned', + await testUtils.dom.click(target.querySelector('.o_kanban_counter_progress .progress-bar[data-filter="planned"]')); + assert.containsOnce(target.querySelector('.o_activity_view thead'), '.o_activity_filter_planned', "planned should be active filter"); - assert.containsN(webClient.el.querySelector('.o_activity_view tbody'), '.o_activity_filter_planned', 5, + assert.containsN(target.querySelector('.o_activity_view tbody'), '.o_activity_filter_planned', 5, "planned should be active filter"); - assert.strictEqual(webClient.el.querySelector('.o_activity_view tbody .o_activity_record').textContent, + assert.strictEqual(target.querySelector('.o_activity_view tbody .o_activity_record').textContent, 'Meeting Room Furnitures', "'Office planning' should be first record"); - const tr = webClient.el.querySelectorAll('.o_activity_view tbody tr')[1]; + const tr = target.querySelectorAll('.o_activity_view tbody tr')[1]; assert.hasClass(tr.querySelectorAll('td')[1], 'o_activity_empty_cell', "other records should be hidden"); - assert.containsNone(webClient.el.querySelector('.o_activity_view tbody'), 'planned', + assert.containsNone(target.querySelector('.o_activity_view tbody'), 'planned', "other records should be hidden"); }); diff --git a/addons/mail/static/tests/m2x_avatar_user_tests.js b/addons/mail/static/tests/m2x_avatar_user_tests.js index 6c045b7107e..78829683bbb 100644 --- a/addons/mail/static/tests/m2x_avatar_user_tests.js +++ b/addons/mail/static/tests/m2x_avatar_user_tests.js @@ -2,7 +2,7 @@ import { Many2OneAvatarUser } from '@mail/js/m2x_avatar_user'; import { afterEach, beforeEach, start } from '@mail/utils/test_utils'; -import { click, legacyExtraNextTick, patchWithCleanup, triggerHotkey } from "@web/../tests/helpers/utils"; +import { click, getFixture, legacyExtraNextTick, patchWithCleanup, triggerHotkey } from "@web/../tests/helpers/utils"; import { createWebClient, doAction } from '@web/../tests/webclient/helpers'; import { registry } from "@web/core/registry"; import { makeLegacyCommandService } from "@web/legacy/utils"; @@ -14,6 +14,7 @@ import session from 'web.session'; import makeTestEnvironment from "web.test_env"; import { dom, mock, nextTick } from 'web.test_utils'; +let target; QUnit.module('mail', {}, function () { QUnit.module('M2XAvatarUser', { @@ -48,6 +49,8 @@ QUnit.module('mail', {}, function () { { id: 7, name: "Luigi", partner_id: 12 }, { id: 23, name: "Yoshi", partner_id: 13 } ); + + target = getFixture(); }, afterEach() { afterEach(this); @@ -431,7 +434,7 @@ QUnit.module('mail', {}, function () { 'res.users': this.data['res.users'], } const serverData = { models, views} - const webClient = await createWebClient({serverData}); + const webClient = await createWebClient({ serverData }); await doAction(webClient, { res_id: 1, type: 'ir.actions.act_window', @@ -440,25 +443,25 @@ QUnit.module('mail', {}, function () { 'view_mode': 'form', 'views': [[false, 'form']], }); - assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "Mario") + assert.strictEqual(target.querySelector(".o_m2o_avatar > span").textContent, "Mario") triggerHotkey("control+k") await nextTick(); - const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign to ...ALT + I") + const idx = [...target.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign to ...ALT + I") assert.ok(idx >= 0); - await click([...webClient.el.querySelectorAll(".o_command")][idx]) + await click([...target.querySelectorAll(".o_command")][idx]) await nextTick(); - assert.deepEqual([...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent), [ + assert.deepEqual([...target.querySelectorAll(".o_command")].map(el => el.textContent), [ "Your Company, Mitchell Admin", "Public user", "Mario", "Luigi", "Yoshi", ]) - await click(webClient.el, "#o_command_3") + await click(target, "#o_command_3") await legacyExtraNextTick(); - assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "Luigi") + assert.strictEqual(target.querySelector(".o_m2o_avatar > span").textContent, "Luigi") }); QUnit.test('many2one_avatar_user widget edited by the smart action "Assign to me"', async function (assert) { @@ -479,7 +482,7 @@ QUnit.module('mail', {}, function () { 'res.users': this.data['res.users'], } const serverData = { models, views} - const webClient = await createWebClient({serverData}); + const webClient = await createWebClient({ serverData }); await doAction(webClient, { res_id: 1, type: 'ir.actions.act_window', @@ -488,23 +491,23 @@ QUnit.module('mail', {}, function () { 'view_mode': 'form', 'views': [[false, 'form']], }); - assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "Mario") + assert.strictEqual(target.querySelector(".o_m2o_avatar > span").textContent, "Mario") triggerHotkey("control+k") await nextTick(); - const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign/unassign to meALT + SHIFT + I") + const idx = [...target.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign/unassign to meALT + SHIFT + I") assert.ok(idx >= 0); // Assign me (Luigi) triggerHotkey("alt+shift+i") await legacyExtraNextTick(); - assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "Luigi") + assert.strictEqual(target.querySelector(".o_m2o_avatar > span").textContent, "Luigi") // Unassign me triggerHotkey("control+k"); await nextTick(); - await click([...webClient.el.querySelectorAll(".o_command")][idx]) + await click([...target.querySelectorAll(".o_command")][idx]) await legacyExtraNextTick(); - assert.strictEqual(webClient.el.querySelector(".o_m2o_avatar > span").textContent, "") + assert.strictEqual(target.querySelector(".o_m2o_avatar > span").textContent, "") }); QUnit.test('many2many_avatar_user widget edited by the smart action "Assign to..."', async function (assert) { @@ -524,7 +527,7 @@ QUnit.module('mail', {}, function () { 'res.users': this.data['res.users'], } const serverData = { models, views} - const webClient = await createWebClient({serverData}); + const webClient = await createWebClient({ serverData }); await doAction(webClient, { res_id: 1, type: 'ir.actions.act_window', @@ -533,25 +536,25 @@ QUnit.module('mail', {}, function () { 'view_mode': 'form', 'views': [[false, 'form']], }); - let userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent)); + let userNames = [...target.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent)); assert.deepEqual(userNames, ["Mario", "Yoshi"]); triggerHotkey("control+k") await nextTick(); - const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign to ...ALT + I") + const idx = [...target.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign to ...ALT + I") assert.ok(idx >= 0); - await click([...webClient.el.querySelectorAll(".o_command")][idx]) + await click([...target.querySelectorAll(".o_command")][idx]) await nextTick(); - assert.deepEqual([...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent), [ + assert.deepEqual([...target.querySelectorAll(".o_command")].map(el => el.textContent), [ "Your Company, Mitchell Admin", "Public user", "Luigi" ]); - await click(webClient.el, "#o_command_2"); + await click(target, "#o_command_2"); await legacyExtraNextTick(); - userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map(el => el.textContent); + userNames = [...target.querySelectorAll(".o_tag_badge_text")].map(el => el.textContent); assert.deepEqual(userNames, ["Mario", "Yoshi", "Luigi"]); }); @@ -573,7 +576,7 @@ QUnit.module('mail', {}, function () { 'res.users': this.data['res.users'], } const serverData = { models, views} - const webClient = await createWebClient({serverData}); + const webClient = await createWebClient({ serverData }); await doAction(webClient, { res_id: 1, type: 'ir.actions.act_window', @@ -582,26 +585,26 @@ QUnit.module('mail', {}, function () { 'view_mode': 'form', 'views': [[false, 'form']], }); - let userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent)); + let userNames = [...target.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent)); assert.deepEqual(userNames, ["Mario", "Yoshi"]); triggerHotkey("control+k"); await nextTick(); - const idx = [...webClient.el.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign/unassign to meALT + SHIFT + I"); + const idx = [...target.querySelectorAll(".o_command")].map(el => el.textContent).indexOf("Assign/unassign to meALT + SHIFT + I"); assert.ok(idx >= 0); // Assign me (Luigi) triggerHotkey("alt+shift+i"); await legacyExtraNextTick(); - userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent)); + userNames = [...target.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent)); assert.deepEqual(userNames, ["Mario", "Yoshi", "Luigi"]); // Unassign me triggerHotkey("control+k"); await nextTick(); - await click([...webClient.el.querySelectorAll(".o_command")][idx]); + await click([...target.querySelectorAll(".o_command")][idx]); await legacyExtraNextTick(); - userNames = [...webClient.el.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent)); + userNames = [...target.querySelectorAll(".o_tag_badge_text")].map((el => el.textContent)); assert.deepEqual(userNames, ["Mario", "Yoshi"]); }); diff --git a/addons/mail/static/tests/tools/debug_manager_tests.js b/addons/mail/static/tests/tools/debug_manager_tests.js index f3a7e667ebf..7dcf7d155c4 100644 --- a/addons/mail/static/tests/tools/debug_manager_tests.js +++ b/addons/mail/static/tests/tools/debug_manager_tests.js @@ -1,7 +1,7 @@ /** @odoo-module **/ import { manageMessages } from "@mail/js/tools/debug_manager"; -import { click, legacyExtraNextTick, patchWithCleanup } from "@web/../tests/helpers/utils"; +import { click, getFixture, legacyExtraNextTick, patchWithCleanup } from "@web/../tests/helpers/utils"; import { createWebClient, doAction, getActionManagerServerData } from "@web/../tests/webclient/helpers"; import { registry } from "@web/core/registry"; @@ -37,12 +37,13 @@ QUnit.test("Manage Messages", async function (assert) { } } + const target = getFixture(); const wc = await createWebClient({ serverData, mockRPC }); await doAction(wc, 3, { viewType: "form", props: { resId: 5 } }); await legacyExtraNextTick(); - await click(wc.el, ".o_debug_manager .dropdown-toggle"); + await click(target, ".o_debug_manager .dropdown-toggle"); - const dropdownItems = wc.el.querySelectorAll( + const dropdownItems = target.querySelectorAll( ".o_debug_manager .dropdown-menu .dropdown-item" ); assert.strictEqual(dropdownItems.length, 1); @@ -56,7 +57,7 @@ QUnit.test("Manage Messages", async function (assert) { await legacyExtraNextTick(); assert.strictEqual( - wc.el.querySelector(".breadcrumb-item.active").innerText.trim(), + target.querySelector(".breadcrumb-item.active").innerText.trim(), "Manage Messages" ); }); diff --git a/addons/mail/static/tests/webclient/commands/mail_providers_tests.js b/addons/mail/static/tests/webclient/commands/mail_providers_tests.js index 024f176e7b9..db4f9de493f 100644 --- a/addons/mail/static/tests/webclient/commands/mail_providers_tests.js +++ b/addons/mail/static/tests/webclient/commands/mail_providers_tests.js @@ -2,7 +2,7 @@ import { afterEach, afterNextRender, beforeEach, start } from '@mail/utils/test_utils'; import { editSearchBar } from '@web/../tests/core/commands/command_service_tests'; -import { click, nextTick, patchWithCleanup, triggerHotkey } from "@web/../tests/helpers/utils"; +import { click, getFixture, nextTick, patchWithCleanup, triggerHotkey } from "@web/../tests/helpers/utils"; import { browser } from '@web/core/browser/browser'; import { commandService } from "@web/core/commands/command_service"; import { registry } from "@web/core/registry"; @@ -41,10 +41,12 @@ QUnit.module('mail', {}, function () { { id: 23, name: "User 3", partner_id: 13 }, ); + const target = getFixture(); const { widget: webClient } = await start({ data: this.data, hasChatWindow: true, hasWebClient: true, + target, }); triggerHotkey("control+k"); await nextTick(); @@ -52,7 +54,7 @@ QUnit.module('mail', {}, function () { // Switch to partners await editSearchBar("@"); assert.deepEqual( - [...webClient.el.querySelectorAll(".o_command_palette .o_command")].map((el) => el.textContent), + [...target.querySelectorAll(".o_command_palette .o_command")].map((el) => el.textContent), [ "Partner 1p1@odoo.com", "Partner 2p2@odoo.com", @@ -63,8 +65,6 @@ QUnit.module('mail', {}, function () { await afterNextRender(() => click(document.body, ".o_command.focused")); assert.containsOnce(document.body, ".o_ChatWindow"); assert.strictEqual(document.querySelector(".o_ChatWindow .o_ChatWindowHeader_name").textContent, "Partner 1"); - - webClient.destroy(); }); QUnit.test('open the chatWindow of a channel from the command palette', async function (assert) { @@ -80,10 +80,12 @@ QUnit.module('mail', {}, function () { name: "project", members: [this.data.currentPartnerId], }); + const target = getFixture(); const { widget: webClient } = await start({ data: this.data, hasChatWindow: true, hasWebClient: true, + target, }); triggerHotkey("control+k"); await nextTick(); @@ -91,7 +93,7 @@ QUnit.module('mail', {}, function () { // Switch to channels await editSearchBar("#"); assert.deepEqual( - [...webClient.el.querySelectorAll(".o_command_palette .o_command")].map((el) => el.textContent), + [...target.querySelectorAll(".o_command_palette .o_command")].map((el) => el.textContent), [ "general", "project" @@ -101,7 +103,5 @@ QUnit.module('mail', {}, function () { await afterNextRender(() => click(document.body, ".o_command.focused")); assert.containsOnce(document.body, ".o_ChatWindow"); assert.strictEqual(document.querySelector(".o_ChatWindow .o_ChatWindowHeader_name").textContent, "general"); - - webClient.destroy(); }); }); diff --git a/addons/sms/static/src/components/message/tests/message_tests.js b/addons/sms/static/src/components/message/tests/message_tests.js index 379e907a4d7..dabb9778d1f 100644 --- a/addons/sms/static/src/components/message/tests/message_tests.js +++ b/addons/sms/static/src/components/message/tests/message_tests.js @@ -20,9 +20,9 @@ QUnit.module('message_tests.js', { this.start = async params => { const res = await start({ ...params, data: this.data }); - const { afterEvent, components, env, widget } = res; + const { afterEvent, apps, env, widget } = res; this.afterEvent = afterEvent; - this.components = components; + this.apps = apps; this.env = env; this.widget = widget; return res; diff --git a/addons/snailmail/static/src/components/message/tests/message_tests.js b/addons/snailmail/static/src/components/message/tests/message_tests.js index 6134cb9d9d7..522046bf4eb 100644 --- a/addons/snailmail/static/src/components/message/tests/message_tests.js +++ b/addons/snailmail/static/src/components/message/tests/message_tests.js @@ -19,9 +19,9 @@ QUnit.module('message_tests.js', { this.start = async params => { const res = await start({ ...params, data: this.data }); - const { afterEvent, components, env, widget } = res; + const { afterEvent, apps, env, widget } = res; this.afterEvent = afterEvent; - this.components = components; + this.apps = apps; this.env = env; this.widget = widget; return res; diff --git a/addons/snailmail/static/src/components/snailmail_error/snailmail_error.js b/addons/snailmail/static/src/components/snailmail_error/snailmail_error.js index b37c8f32d50..9c19a6c3a55 100644 --- a/addons/snailmail/static/src/components/snailmail_error/snailmail_error.js +++ b/addons/snailmail/static/src/components/snailmail_error/snailmail_error.js @@ -11,7 +11,6 @@ export class SnailmailError extends Component { * @override */ setup() { - super.setup(); useComponentToModel({ fieldName: 'component', modelName: 'SnailmailErrorView' }); }