diff --git a/addons/html_builder/static/src/builder.js b/addons/html_builder/static/src/builder.js
index e87ce67480be1..3c126ee7a2c1b 100644
--- a/addons/html_builder/static/src/builder.js
+++ b/addons/html_builder/static/src/builder.js
@@ -25,6 +25,7 @@ import { CustomizeTab } from "@html_builder/sidebar/customize_tab";
import { CORE_PLUGINS } from "@html_builder/core/core_plugins";
import { EDITOR_COLOR_CSS_VARIABLES, getCSSVariableValue } from "@html_builder/utils/utils_css";
import { withSequence } from "@html_editor/utils/resource";
+import { triggerDomUpdated } from "./core/utils";
export class Builder extends Component {
static template = "html_builder.Builder";
@@ -93,7 +94,7 @@ export class Builder extends Component {
this.state.canRedo = this.editor.shared.history.canRedo();
this.updateInvisibleEls();
editorBus.trigger("UPDATE_EDITING_ELEMENT");
- editorBus.trigger("DOM_UPDATED");
+ triggerDomUpdated(editorBus);
}
},
reloadEditor: (param = {}) => {
@@ -104,10 +105,10 @@ export class Builder extends Component {
},
resources: {
trigger_dom_updated: () => {
- editorBus.trigger("DOM_UPDATED");
+ triggerDomUpdated(editorBus);
},
on_mobile_preview_clicked: withSequence(20, () => {
- editorBus.trigger("DOM_UPDATED");
+ triggerDomUpdated(editorBus);
}),
change_current_options_containers_listeners: (currentOptionsContainers) => {
this.state.currentOptionsContainers = currentOptionsContainers;
diff --git a/addons/html_builder/static/src/builder.xml b/addons/html_builder/static/src/builder.xml
index 344f54c0697ae..2e1427ffdcbe6 100644
--- a/addons/html_builder/static/src/builder.xml
+++ b/addons/html_builder/static/src/builder.xml
@@ -9,10 +9,6 @@
-
-
-
-
Discard
Save
diff --git a/addons/html_builder/static/src/core/building_blocks/basic_many2one.js b/addons/html_builder/static/src/core/building_blocks/basic_many2one.js
deleted file mode 100644
index 9997e6e410986..0000000000000
--- a/addons/html_builder/static/src/core/building_blocks/basic_many2one.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Component, onWillStart, onWillUpdateProps } from "@odoo/owl";
-import { basicContainerBuilderComponentProps } from "../utils";
-import { useCachedModel } from "@html_builder/core/cached_model_utils";
-import { SelectMany2X } from "./select_many2x";
-
-export class BasicMany2One extends Component {
- static template = "html_builder.BasicMany2One";
- static props = {
- ...basicContainerBuilderComponentProps,
- model: String,
- fields: { type: Array, element: String, optional: true },
- domain: { type: Array, optional: true },
- limit: { type: Number, optional: true },
- selected: { type: Object, optional: true },
- select: Function,
- unselect: { type: Function, optional: true },
- defaultMessage: { type: String, optional: true },
- create: { type: Function, optional: true },
- };
- static components = { SelectMany2X };
-
- setup() {
- this.cachedModel = useCachedModel();
- onWillStart(async () => {
- await this.handleProps(this.props);
- });
- onWillUpdateProps(async (newProps) => {
- await this.handleProps(newProps);
- });
- }
- async handleProps(props) {
- if (props.selected && !("display_name" in props.selected && "name" in props.selected)) {
- Object.assign(
- props.selected,
- (
- await this.cachedModel.ormRead(
- this.props.model,
- [props.selected.id],
- ["display_name", "name"]
- )
- )[0]
- );
- }
- }
-}
diff --git a/addons/html_builder/static/src/core/building_blocks/basic_many2one.xml b/addons/html_builder/static/src/core/building_blocks/basic_many2one.xml
deleted file mode 100644
index 84acb5813c155..0000000000000
--- a/addons/html_builder/static/src/core/building_blocks/basic_many2one.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/addons/html_builder/static/src/core/building_blocks/builder_many2one.js b/addons/html_builder/static/src/core/building_blocks/builder_many2one.js
index 1f6b10c63d6ec..c968bf54916b1 100644
--- a/addons/html_builder/static/src/core/building_blocks/builder_many2one.js
+++ b/addons/html_builder/static/src/core/building_blocks/builder_many2one.js
@@ -7,7 +7,8 @@ import {
useDomState,
} from "../utils";
import { BuilderComponent } from "./builder_component";
-import { BasicMany2One } from "./basic_many2one";
+import { SelectMany2X } from "./select_many2x";
+import { useCachedModel } from "../cached_model_utils";
export class BuilderMany2One extends Component {
static template = "html_builder.BuilderMany2One";
@@ -26,11 +27,12 @@ export class BuilderMany2One extends Component {
...BuilderComponent.defaultProps,
allowUnselect: true,
};
- static components = { BuilderComponent, BasicMany2One };
+ static components = { BuilderComponent, SelectMany2X };
setup() {
useBuilderComponent();
const { getAllActions, callOperation } = getAllActionsAndOperations(this);
+ this.cachedModel = useCachedModel();
this.callOperation = callOperation;
this.applyOperation = this.env.editor.shared.history.makePreviewableAsyncOperation(
this.callApply.bind(this)
@@ -40,12 +42,26 @@ export class BuilderMany2One extends Component {
({ actionId }) => getAction(actionId).getValue
);
const { actionId, actionParam } = actionWithGetValue;
- this.domState = useDomState((el) => {
- const actionValue = getAction(actionId).getValue({
+ this.domState = useDomState(async (el) => {
+ const selectedString = getAction(actionId).getValue({
editingElement: el,
params: actionParam,
});
- return { selected: actionValue && JSON.parse(actionValue) };
+ const selected = selectedString && JSON.parse(selectedString);
+ if (selected && !("display_name" in selected && "name" in selected)) {
+ Object.assign(
+ selected,
+ (
+ await this.cachedModel.ormRead(
+ this.props.model,
+ [selected.id],
+ ["display_name", "name"]
+ )
+ )[0]
+ );
+ }
+
+ return { selected };
});
if (this.props.id) {
useDependencyDefinition(this.props.id, {
diff --git a/addons/html_builder/static/src/core/building_blocks/builder_many2one.xml b/addons/html_builder/static/src/core/building_blocks/builder_many2one.xml
index b0274d580bae7..b05897dd7262b 100644
--- a/addons/html_builder/static/src/core/building_blocks/builder_many2one.xml
+++ b/addons/html_builder/static/src/core/building_blocks/builder_many2one.xml
@@ -3,17 +3,18 @@
-
+
diff --git a/addons/html_builder/static/src/core/utils.js b/addons/html_builder/static/src/core/utils.js
index bd1f2ff23daa9..a42b91d637536 100644
--- a/addons/html_builder/static/src/core/utils.js
+++ b/addons/html_builder/static/src/core/utils.js
@@ -25,21 +25,18 @@ function isConnectedElement(el) {
export function useDomState(getState, { checkEditingElement = true, onReady } = {}) {
const env = useEnv();
const isValid = (el) => (!el && !checkEditingElement) || isConnectedElement(el);
- const handler = () => {
+ const handler = async (ev) => {
const editingElement = env.getEditingElement();
if (isValid(editingElement)) {
- Object.assign(state, getState(editingElement));
+ const newStatePromise = getState(editingElement);
+ ev?.detail.getStatePromises.push(newStatePromise);
+ const newState = await newStatePromise;
+ await ev?.detail.updatePromise;
+ Object.assign(state, newState);
}
};
const state = useState({});
- if (onReady) {
- onReady.then(() => {
- handler();
- });
- } else {
- handler();
- }
-
+ onWillStart(() => (onReady ? onReady.then(() => handler()) : handler()));
useBus(env.editorBus, "DOM_UPDATED", handler);
return state;
}
@@ -93,11 +90,11 @@ export function useBuilderComponent() {
};
updateEditingElements();
oldEnv.editorBus.addEventListener("UPDATE_EDITING_ELEMENT", updateEditingElements);
- onWillUpdateProps((nextProps) => {
+ onWillUpdateProps(async (nextProps) => {
if (comp.props.applyTo !== nextProps.applyTo) {
applyTo = nextProps.applyTo;
oldEnv.editorBus.trigger("UPDATE_EDITING_ELEMENT");
- oldEnv.editorBus.trigger("DOM_UPDATED");
+ await triggerDomUpdated(oldEnv.editorBus);
}
});
onWillDestroy(() => {
@@ -937,3 +934,9 @@ export class BaseOptionComponent extends Component {
Object.assign(comp.constructor.components, Components);
}
}
+export function triggerDomUpdated(editorBus) {
+ const getStatePromises = [];
+ const { promise: updatePromise, resolve } = Promise.withResolvers();
+ editorBus.trigger("DOM_UPDATED", { getStatePromises, updatePromise });
+ Promise.all(getStatePromises).then(resolve);
+}
diff --git a/addons/html_builder/static/src/core/utils/update_on_img_changed.js b/addons/html_builder/static/src/core/utils/update_on_img_changed.js
deleted file mode 100644
index 04606f19cf4e1..0000000000000
--- a/addons/html_builder/static/src/core/utils/update_on_img_changed.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import { Component, onWillStart, xml } from "@odoo/owl";
-import { useDomState } from "../utils";
-
-class LoadImgComponent extends Component {
- static template = xml`
-
- `;
- static props = { slots: { type: Object } };
-
- setup() {
- onWillStart(async () => {
- const editingElements = this.env.getEditingElements();
- const promises = [];
- for (const editingEl of editingElements) {
- const imageEls = editingEl.matches("img")
- ? [editingEl]
- : editingEl.querySelectorAll("img");
- for (const imageEl of imageEls) {
- if (!imageEl.complete) {
- promises.push(
- new Promise((resolve) => {
- imageEl.addEventListener("load", () => resolve());
- })
- );
- }
- }
- }
- await Promise.all(promises);
- });
- }
-}
-
-/**
- * In Chrome, when replacing an image on the DOM, some image properties are not
- * available even if the image has been loaded beforehand. This is a problem if
- * an option is using one of those property at each DOM change (useDomState).
- * To solve the problem, this component reloads the option (and waits for the
- * images to be loaded) each time an image has been modified inside its editing
- * element.
- */
-export class UpdateOptionOnImgChanged extends Component {
- // TODO: this is a hack until
is
- // fixed in OWL.
- static template = xml`
-
-
- `;
- static props = { slots: { type: Object } };
- static components = { LoadImgComponent };
-
- setup() {
- let boolean = true;
- this.state = useDomState((editingElement) => {
- const imageEls = editingElement.matches("img")
- ? [editingElement]
- : editingElement.querySelectorAll("img");
- for (const imageEl of imageEls) {
- if (!imageEl.complete) {
- // Rerender the slot if an image is not loaded
- boolean = !boolean;
- break;
- }
- }
- return {
- bool: boolean,
- };
- });
- }
-}
diff --git a/addons/html_editor/static/src/utils/image.js b/addons/html_editor/static/src/utils/image.js
index 16fdbd6fa1e97..400be46efd0f6 100644
--- a/addons/html_editor/static/src/utils/image.js
+++ b/addons/html_editor/static/src/utils/image.js
@@ -35,12 +35,12 @@ export function backgroundImagePartsToCss(parts) {
* @param {HTMLImageElement} image
* @returns {string|null} The mimetype of the image.
*/
-export function getMimetype(image) {
+export function getMimetype(image, dataset = image.dataset) {
const src = getImageSrc(image);
return (
- image.dataset.mimetype ||
- image.dataset.mimetypeBeforeConversion ||
+ dataset.mimetype ||
+ dataset.mimetypeBeforeConversion ||
(src &&
((src.endsWith(".png") && "image/png") ||
(src.endsWith(".webp") && "image/webp") ||
diff --git a/addons/mail/push-to-talk-extension/content.js b/addons/mail/push-to-talk-extension/content.js
index 7b53a74ac0f5b..164a672f1b2df 100644
--- a/addons/mail/push-to-talk-extension/content.js
+++ b/addons/mail/push-to-talk-extension/content.js
@@ -4,7 +4,7 @@
const EXT_ID = "mdiacebcbkmjjlpclnbcgiepgifcnpmg";
chrome.runtime.onMessage.addListener(function (request, sender) {
- if (sender.id === EXT_ID) {
+ if (location.origin !== "null" && sender.id === EXT_ID) {
window.postMessage(request, location.origin);
}
});
diff --git a/addons/mail/static/src/discuss/call/common/ptt_extension_service.js b/addons/mail/static/src/discuss/call/common/ptt_extension_service.js
index c629f14c5ed9d..e22dfb5b01641 100644
--- a/addons/mail/static/src/discuss/call/common/ptt_extension_service.js
+++ b/addons/mail/static/src/discuss/call/common/ptt_extension_service.js
@@ -93,7 +93,7 @@ export const pttExtensionHookService = {
return;
}
const version = parseVersion(await versionPromise);
- if (!location.origin) {
+ if (location.origin === "null") {
return;
}
if (version.isLowerThan("1.0.0.2")) {
diff --git a/addons/mail/tests/discuss/test_ui.py b/addons/mail/tests/discuss/test_ui.py
index c07d93c585fb8..f8c3a47560d0c 100644
--- a/addons/mail/tests/discuss/test_ui.py
+++ b/addons/mail/tests/discuss/test_ui.py
@@ -4,6 +4,7 @@
from odoo import Command
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
+
@odoo.tests.tagged('post_install', '-at_install')
class TestUi(HttpCaseWithUserDemo):
diff --git a/addons/mass_mailing_sms/tests/test_mailing_ui.py b/addons/mass_mailing_sms/tests/test_mailing_ui.py
index 848f34e30d09e..ac952a7867b61 100644
--- a/addons/mass_mailing_sms/tests/test_mailing_ui.py
+++ b/addons/mass_mailing_sms/tests/test_mailing_ui.py
@@ -2,6 +2,7 @@
from odoo.tests import HttpCase, tagged, users
+
@tagged('post_install', '-at_install', 'mail_activity')
class TestMailingUi(HttpCase):
diff --git a/addons/project_todo/tests/test_todo_ui.py b/addons/project_todo/tests/test_todo_ui.py
index 66275e4dcea71..abea68a0c6123 100644
--- a/addons/project_todo/tests/test_todo_ui.py
+++ b/addons/project_todo/tests/test_todo_ui.py
@@ -5,6 +5,7 @@
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
import unittest
+
@tagged('post_install', '-at_install')
class TestTodoUi(HttpCaseWithUserDemo):
diff --git a/addons/survey/tests/test_survey_ui_certification.py b/addons/survey/tests/test_survey_ui_certification.py
index c7bc1520b2bfc..4a5aa5ed3aa39 100644
--- a/addons/survey/tests/test_survey_ui_certification.py
+++ b/addons/survey/tests/test_survey_ui_certification.py
@@ -5,6 +5,7 @@
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
import unittest
+
# TODO master-mysterious-egg fix error
@unittest.skip("prepare mysterious-egg for merging")
@odoo.tests.common.tagged('post_install', '-at_install')
diff --git a/addons/survey/tests/test_survey_ui_feedback.py b/addons/survey/tests/test_survey_ui_feedback.py
index 336f492afacd9..1aaf594892a16 100644
--- a/addons/survey/tests/test_survey_ui_feedback.py
+++ b/addons/survey/tests/test_survey_ui_feedback.py
@@ -5,6 +5,7 @@
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
import unittest
+
# TODO master-mysterious-egg fix error
@unittest.skip("prepare mysterious-egg for merging")
@odoo.tests.common.tagged('post_install', '-at_install')
diff --git a/addons/test_website/tests/test_custom_snippet.py b/addons/test_website/tests/test_custom_snippet.py
index 223efc5622894..27460c1efffc3 100644
--- a/addons/test_website/tests/test_custom_snippet.py
+++ b/addons/test_website/tests/test_custom_snippet.py
@@ -5,6 +5,7 @@
from odoo.tools import mute_logger
import unittest
+
# TODO master-mysterious-egg fix error
@unittest.skip("prepare mysterious-egg for merging")
@odoo.tests.common.tagged('post_install', '-at_install')
diff --git a/addons/test_website/tests/test_form.py b/addons/test_website/tests/test_form.py
index a7a2f682e69ef..3b3adb2376ab1 100644
--- a/addons/test_website/tests/test_form.py
+++ b/addons/test_website/tests/test_form.py
@@ -3,6 +3,7 @@
from odoo.tests import tagged, HttpCase
import unittest
+
# TODO master-mysterious-egg fix error
@unittest.skip("prepare mysterious-egg for merging")
@tagged('-at_install', 'post_install')
diff --git a/addons/test_website/tests/test_image_upload_progress.py b/addons/test_website/tests/test_image_upload_progress.py
index 78b540de739d8..a9d0c5d6bda18 100644
--- a/addons/test_website/tests/test_image_upload_progress.py
+++ b/addons/test_website/tests/test_image_upload_progress.py
@@ -9,6 +9,7 @@
from odoo import http
import unittest
+
# TODO master-mysterious-egg fix error
@unittest.skip("prepare mysterious-egg for merging")
@odoo.tests.common.tagged('post_install', '-at_install')
diff --git a/addons/test_website/tests/test_media.py b/addons/test_website/tests/test_media.py
index 7685960b32138..7676929a44b0d 100644
--- a/addons/test_website/tests/test_media.py
+++ b/addons/test_website/tests/test_media.py
@@ -7,6 +7,7 @@
from odoo.tools import mute_logger
import unittest
+
# TODO master-mysterious-egg fix error
@unittest.skip("prepare mysterious-egg for merging")
@odoo.tests.common.tagged('post_install', '-at_install')
diff --git a/addons/test_website/tests/test_snippet_background_video.py b/addons/test_website/tests/test_snippet_background_video.py
index 71838dd059b51..14891f12a65ca 100644
--- a/addons/test_website/tests/test_snippet_background_video.py
+++ b/addons/test_website/tests/test_snippet_background_video.py
@@ -3,6 +3,7 @@
import odoo.tests
import unittest
+
@odoo.tests.common.tagged('post_install', '-at_install')
class TestSnippetBackgroundVideo(odoo.tests.HttpCase):
# TODO master-mysterious-egg fix error
diff --git a/addons/test_website/tests/test_systray.py b/addons/test_website/tests/test_systray.py
index 0a2b64d448a4c..69922a9d47198 100644
--- a/addons/test_website/tests/test_systray.py
+++ b/addons/test_website/tests/test_systray.py
@@ -6,6 +6,7 @@
from odoo.addons.base.tests.common import HttpCase
+
@tagged('post_install', '-at_install')
class TestSystray(HttpCase):
diff --git a/addons/test_website_modules/tests/test_configurator.py b/addons/test_website_modules/tests/test_configurator.py
index 0a188618d946b..feeb5f0cb3e3e 100644
--- a/addons/test_website_modules/tests/test_configurator.py
+++ b/addons/test_website_modules/tests/test_configurator.py
@@ -5,6 +5,7 @@
from odoo.addons.website.tests.test_configurator import TestConfiguratorCommon
import unittest
+
@odoo.tests.common.tagged('post_install', '-at_install')
class TestConfigurator(TestConfiguratorCommon):
diff --git a/addons/test_website_slides_full/tests/test_ui_wslides.py b/addons/test_website_slides_full/tests/test_ui_wslides.py
index b2b9df0a5de9b..c5b810a179399 100644
--- a/addons/test_website_slides_full/tests/test_ui_wslides.py
+++ b/addons/test_website_slides_full/tests/test_ui_wslides.py
@@ -8,6 +8,7 @@
from odoo.addons.website_slides.tests.test_ui_wslides import TestUICommon
import unittest
+
@tests.common.tagged('post_install', '-at_install')
class TestUi(TestUICommon):
diff --git a/addons/web/tooling/_eslintignore b/addons/web/tooling/_eslintignore
index 7ca3c0556385d..91c2425fdf815 100644
--- a/addons/web/tooling/_eslintignore
+++ b/addons/web/tooling/_eslintignore
@@ -82,6 +82,12 @@ web_gantt/static/tests/legacy/**/*
!addons/html_builder
!addons/html_builder/**/*
+# Whitelist parts of website
+!addons/website/static/src/builder
+!addons/website/static/src/builder/**/*
+!addons/website/static/src/client_actions/website_preview/
+!addons/website/static/src/client_actions/website_preview/**/*
+
# planning
# whitelist new code
!planning
diff --git a/addons/website/static/src/builder/builder_urlpicker.js b/addons/website/static/src/builder/builder_urlpicker.js
index 9a25a474696d5..a90f3512d6e61 100644
--- a/addons/website/static/src/builder/builder_urlpicker.js
+++ b/addons/website/static/src/builder/builder_urlpicker.js
@@ -73,7 +73,6 @@ export class BuilderUrlPicker extends Component {
}
}
-
class UrlPickerPlugin extends Plugin {
static id = "urlPickerPlugin";
diff --git a/addons/website/static/src/builder/plugins/form/form_option.js b/addons/website/static/src/builder/plugins/form/form_option.js
index 2c80e05f18dcb..18938d0345935 100644
--- a/addons/website/static/src/builder/plugins/form/form_option.js
+++ b/addons/website/static/src/builder/plugins/form/form_option.js
@@ -1,6 +1,5 @@
-import { BaseOptionComponent } from "@html_builder/core/utils";
-import { onWillStart, onWillUpdateProps, useState } from "@odoo/owl";
-import { getParsedDataFor } from "./utils";
+import { BaseOptionComponent, useDomState } from "@html_builder/core/utils";
+import { getModelName, getParsedDataFor } from "./utils";
import { FormActionFieldsOption } from "./form_action_fields_option";
import { session } from "@web/session";
@@ -23,9 +22,6 @@ export class FormOption extends BaseOptionComponent {
const el = this.env.getEditingElement();
this.messageEl = el.parentElement.querySelector(".s_website_form_end_message");
this.showEndMessage = false;
- this.state = useState({
- activeForm: {},
- });
// Get the email_to value from the data-for attribute if it exists. We
// use it if there is no value on the email_to input.
const formId = el.id;
@@ -33,25 +29,28 @@ export class FormOption extends BaseOptionComponent {
if (dataForValues) {
this.dataForEmailTo = dataForValues["email_to"];
}
- onWillStart(async () => this.handleProps(this.props));
- onWillUpdateProps(async (props) => this.handleProps(props));
- }
- async handleProps(props) {
- const el = this.env.getEditingElement();
- // Hide change form parameters option for forms
- // e.g. User should not be enable to change existing job application form
- // to opportunity form in 'Apply job' page.
- this.modelCantChange = !!el.getAttribute("hide-change-model");
+ this.state = useDomState(async (el) => {
+ const modelName = getModelName(el);
- // Get list of website_form compatible models.
- this.models = await props.fetchModels(el);
- this.state.activeForm = this.models.find((m) => m.model === props.modelName);
+ // Hide change form parameters option for forms
+ // e.g. User should not be enable to change existing job application form
+ // to opportunity form in 'Apply job' page.
+ this.modelCantChange = !!el.getAttribute("hide-change-model");
- // If the form has no model it means a new snippet has been dropped.
- // Apply the default model selected in willStart on it.
- if (!el.dataset.model_name) {
- const formInfo = await props.prepareFormModel(el, this.state.activeForm);
- props.applyFormModel(el, this.state.activeForm, this.state.activeForm.id, formInfo);
- }
+ // Get list of website_form compatible models.
+ const models = await this.props.fetchModels(el);
+ const activeForm = models.find((m) => m.model === modelName);
+
+ // If the form has no model it means a new snippet has been dropped.
+ // Apply the default model selected in willStart on it.
+ if (!el.dataset.model_name) {
+ const formInfo = await this.props.prepareFormModel(el, activeForm);
+ this.props.applyFormModel(el, activeForm, activeForm.id, formInfo);
+ }
+ return {
+ models,
+ activeForm,
+ };
+ });
}
}
diff --git a/addons/website/static/src/builder/plugins/form/form_option.xml b/addons/website/static/src/builder/plugins/form/form_option.xml
index b23757e37e045..70e636b32e4d0 100644
--- a/addons/website/static/src/builder/plugins/form/form_option.xml
+++ b/addons/website/static/src/builder/plugins/form/form_option.xml
@@ -2,10 +2,10 @@
-
-
+
diff --git a/addons/website/static/src/builder/plugins/form/form_option_plugin.js b/addons/website/static/src/builder/plugins/form/form_option_plugin.js
index 1964ae9f2bb44..95ab3720f15c0 100644
--- a/addons/website/static/src/builder/plugins/form/form_option_plugin.js
+++ b/addons/website/static/src/builder/plugins/form/form_option_plugin.js
@@ -3,7 +3,6 @@ import { Cache } from "@web/core/utils/cache";
import { Plugin } from "@html_editor/plugin";
import { reactive } from "@odoo/owl";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
-import { FormOptionRedraw } from "./form_option_redraw";
import { FormFieldOptionRedraw } from "./form_field_option_redraw";
import { FormOptionAddFieldButton } from "./form_option_add_field_button";
import {
@@ -36,6 +35,7 @@ import {
import { SyncCache } from "@html_builder/utils/sync_cache";
import { _t } from "@web/core/l10n/translation";
import { renderToElement } from "@web/core/utils/render";
+import { FormOption } from "./form_option";
export class FormOptionPlugin extends Plugin {
static id = "websiteFormOption";
@@ -86,7 +86,7 @@ export class FormOptionPlugin extends Plugin {
},
builder_options: [
{
- OptionComponent: FormOptionRedraw,
+ OptionComponent: FormOption,
props: {
fetchModels: this.fetchModels.bind(this),
prepareFormModel: this.prepareFormModel.bind(this),
diff --git a/addons/website/static/src/builder/plugins/form/form_option_redraw.js b/addons/website/static/src/builder/plugins/form/form_option_redraw.js
deleted file mode 100644
index 694de6c8216ed..0000000000000
--- a/addons/website/static/src/builder/plugins/form/form_option_redraw.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import { BaseOptionComponent, useDomState } from "@html_builder/core/utils";
-import { FormOption } from "./form_option";
-import { getModelName } from "./utils";
-import { xml } from "@odoo/owl";
-
-const formOptionRedrawProps = { ...FormOption.props };
-delete formOptionRedrawProps.modelName;
-
-export class FormOptionRedraw extends BaseOptionComponent {
- static template = xml` `;
- static props = formOptionRedrawProps;
- static components = { FormOption };
-
- setup() {
- super.setup();
- this.domState = useDomState((formEl) => {
- const modelName = getModelName(formEl);
- return {
- modelName,
- };
- });
- }
-
- getProps() {
- return {
- ...this.props,
- modelName: this.domState.modelName,
- };
- }
-}
diff --git a/addons/website/static/src/builder/plugins/image/image_filter_option.js b/addons/website/static/src/builder/plugins/image/image_filter_option.js
index b8e863898f2e3..acdcc5f870349 100644
--- a/addons/website/static/src/builder/plugins/image/image_filter_option.js
+++ b/addons/website/static/src/builder/plugins/image/image_filter_option.js
@@ -1,7 +1,6 @@
import { BaseOptionComponent, useDomState } from "@html_builder/core/utils";
import { shouldPreventGifTransformation } from "@html_editor/main/media/image_post_process_plugin";
import { loadImageInfo } from "@html_editor/utils/image_processing";
-import { KeepLast } from "@web/core/utils/concurrency";
export class ImageFilterOption extends BaseOptionComponent {
static template = "html_builder.ImageFilterOption";
@@ -13,22 +12,14 @@ export class ImageFilterOption extends BaseOptionComponent {
};
setup() {
super.setup();
- const keepLast = new KeepLast();
- this.state = useDomState((editingElement) => {
- keepLast
- .add(
- loadImageInfo(editingElement).then((data) => ({
- ...editingElement.dataset,
- ...data,
- }))
- )
- .then((data) => {
- this.state.showFilter =
- data.mimetypeBeforeConversion && !shouldPreventGifTransformation(data);
- });
+ this.state = useDomState(async (editingElement) => {
+ const data = await loadImageInfo(editingElement).then((data) => ({
+ ...editingElement.dataset,
+ ...data,
+ }));
return {
isCustomFilter: editingElement.dataset.glFilter === "custom",
- showFilter: false,
+ showFilter: data.mimetypeBeforeConversion && !shouldPreventGifTransformation(data),
};
});
}
diff --git a/addons/website/static/src/builder/plugins/image/image_format_option.js b/addons/website/static/src/builder/plugins/image/image_format_option.js
index c13f2b6e1e5ac..91ceb79706480 100644
--- a/addons/website/static/src/builder/plugins/image/image_format_option.js
+++ b/addons/website/static/src/builder/plugins/image/image_format_option.js
@@ -1,5 +1,4 @@
import { BaseOptionComponent, useDomState } from "@html_builder/core/utils";
-import { KeepLast } from "@web/core/utils/concurrency";
import { getImageSrc, getMimetype } from "@html_editor/utils/image";
import { clamp } from "@web/core/utils/numbers";
@@ -15,22 +14,15 @@ export class ImageFormatOption extends BaseOptionComponent {
MAX_SUGGESTED_WIDTH = 1920;
setup() {
super.setup();
- const keepLast = new KeepLast();
- this.state = useDomState((editingElement) => {
- keepLast
- .add(
- this.env.editor.shared.imageFormatOption.computeAvailableFormats(
- editingElement,
- this.computeMaxDisplayWidth.bind(this)
- )
- )
- .then((formats) => {
- const hasSrc = !!getImageSrc(editingElement);
- this.state.formats = hasSrc ? formats : [];
- });
+ this.state = useDomState(async (editingElement) => {
+ const formats = await this.env.editor.shared.imageFormatOption.computeAvailableFormats(
+ editingElement,
+ this.computeMaxDisplayWidth.bind(this)
+ );
+ const hasSrc = !!getImageSrc(editingElement);
return {
showQuality: ["image/jpeg", "image/webp"].includes(getMimetype(editingElement)),
- formats: [],
+ formats: hasSrc ? formats : [],
};
});
}
diff --git a/addons/website/static/src/builder/plugins/image/image_tool_option_plugin.js b/addons/website/static/src/builder/plugins/image/image_tool_option_plugin.js
index d70aa6a889987..787512516b06a 100644
--- a/addons/website/static/src/builder/plugins/image/image_tool_option_plugin.js
+++ b/addons/website/static/src/builder/plugins/image/image_tool_option_plugin.js
@@ -1,4 +1,8 @@
-import { cropperDataFieldsWithAspectRatio, isGif } from "@html_editor/utils/image_processing";
+import {
+ cropperDataFieldsWithAspectRatio,
+ isGif,
+ loadImageInfo,
+} from "@html_editor/utils/image_processing";
import { registry } from "@web/core/registry";
import { Plugin } from "@html_editor/plugin";
import { ImageToolOption } from "./image_tool_option";
@@ -192,11 +196,12 @@ class ImageToolOptionPlugin extends Plugin {
};
}
async canHaveHoverEffect(img) {
+ const getDataset = async () => Object.assign({}, img.dataset, await loadImageInfo(img));
return (
img.tagName === "IMG" &&
!this.isDeviceShape(img) &&
!this.isAnimatedShape(img) &&
- this.isImageSupportedForShapes(img) &&
+ this.isImageSupportedForShapes(img, await getDataset()) &&
!(await isImageCorsProtected(img))
);
}
@@ -212,8 +217,8 @@ class ImageToolOptionPlugin extends Plugin {
// todo: to implement while implementing the animated shapes
return false;
}
- isImageSupportedForShapes(img) {
- return img.dataset.originalId && isImageSupportedForProcessing(getMimetype(img));
+ isImageSupportedForShapes(img, dataset = img.dataset) {
+ return dataset.originalId && isImageSupportedForProcessing(getMimetype(img));
}
}
registry.category("website-plugins").add(ImageToolOptionPlugin.id, ImageToolOptionPlugin);
diff --git a/addons/website/static/src/builder/plugins/layout_option/layout_option_plugin.js b/addons/website/static/src/builder/plugins/layout_option/layout_option_plugin.js
index 3b460abab99e4..102763e289e06 100644
--- a/addons/website/static/src/builder/plugins/layout_option/layout_option_plugin.js
+++ b/addons/website/static/src/builder/plugins/layout_option/layout_option_plugin.js
@@ -12,7 +12,6 @@ import { LayoutColumnOption, LayoutGridOption, LayoutOption } from "./layout_opt
import { withSequence } from "@html_editor/utils/resource";
import { LAYOUT, LAYOUT_COLUMN, LAYOUT_GRID } from "@website/builder/option_sequence";
-
class LayoutOptionPlugin extends Plugin {
static id = "LayoutOption";
static dependencies = ["clone", "selection"];
diff --git a/addons/website/static/src/builder/plugins/options/animate_option.js b/addons/website/static/src/builder/plugins/options/animate_option.js
index 35b8ac706d5e5..bc01a1275844c 100644
--- a/addons/website/static/src/builder/plugins/options/animate_option.js
+++ b/addons/website/static/src/builder/plugins/options/animate_option.js
@@ -1,4 +1,3 @@
-import { KeepLast } from "@web/core/utils/concurrency";
import { BaseOptionComponent, useDomState } from "@html_builder/core/utils";
import { isImageSupportedForStyle } from "@website/builder/plugins/image/replace_media_option";
@@ -12,19 +11,15 @@ export class AnimateOption extends BaseOptionComponent {
setup() {
super.setup();
- const keeplast = new KeepLast();
- this.state = useDomState((editingElement) => {
+ this.state = useDomState(async (editingElement) => {
const hasAnimateClass = editingElement.classList.contains("o_animate");
- // todo: maybe add a spinner
- keeplast.add(this.props.canHaveHoverEffect(editingElement)).then((result) => {
- this.state.canHover = result;
- });
+ const canHover = await this.props.canHaveHoverEffect(editingElement);
return {
isOptionActive: this.isOptionActive(editingElement),
hasAnimateClass: hasAnimateClass,
- canHover: false,
+ canHover,
isLimitedEffect: this.limitedEffects.some((className) =>
editingElement.classList.contains(className)
),
diff --git a/addons/website/static/src/builder/plugins/options/card_image_alignment_option.js b/addons/website/static/src/builder/plugins/options/card_image_alignment_option.js
index efae77c2226b0..d2f9c2725f6fa 100644
--- a/addons/website/static/src/builder/plugins/options/card_image_alignment_option.js
+++ b/addons/website/static/src/builder/plugins/options/card_image_alignment_option.js
@@ -12,7 +12,8 @@ export class CardImageAlignmentOption extends BaseOptionComponent {
setup() {
super.setup();
- this.state = useDomState((editingElement) => {
+ this.state = useDomState(async (editingElement) => {
+ await this.waitForAllImageloaded(this.env.getEditingElements());
const imageToWrapperRatio = this.getImageToWrapperRatio(editingElement);
const hasCoverImage = !!editingElement.querySelector(".o_card_img_wrapper");
// Sometimes the imageToWrapperRatio is very close to but not
@@ -47,4 +48,23 @@ export class CardImageAlignmentOption extends BaseOptionComponent {
const wrapperRatio = imageWrapperEl.offsetWidth / imageWrapperEl.offsetHeight;
return imgRatio / wrapperRatio;
}
+
+ async waitForAllImageloaded(editingElements) {
+ const promises = [];
+ for (const editingEl of editingElements) {
+ const imageEls = editingEl.matches("img")
+ ? [editingEl]
+ : editingEl.querySelectorAll("img");
+ for (const imageEl of imageEls) {
+ if (!imageEl.complete) {
+ promises.push(
+ new Promise((resolve) => {
+ imageEl.addEventListener("load", () => resolve());
+ })
+ );
+ }
+ }
+ }
+ await Promise.all(promises);
+ }
}
diff --git a/addons/website/static/src/builder/plugins/options/card_option.js b/addons/website/static/src/builder/plugins/options/card_option.js
index e2945c22a2da7..931049ade2214 100644
--- a/addons/website/static/src/builder/plugins/options/card_option.js
+++ b/addons/website/static/src/builder/plugins/options/card_option.js
@@ -3,7 +3,6 @@ import { WebsiteBackgroundOption } from "@website/builder/plugins/options/backgr
import { CardImageOption } from "./card_image_option";
import { BorderConfigurator } from "@html_builder/plugins/border_configurator_option";
import { ShadowOption } from "@html_builder/plugins/shadow_option";
-import { UpdateOptionOnImgChanged } from "@html_builder/core/utils/update_on_img_changed";
export class CardOption extends BaseOptionComponent {
static template = "website.CardOption";
@@ -12,7 +11,6 @@ export class CardOption extends BaseOptionComponent {
WebsiteBackgroundOption,
BorderConfigurator,
ShadowOption,
- UpdateOptionOnImgChanged,
};
static props = {
disableWidth: { type: Boolean, optional: true },
diff --git a/addons/website/static/src/builder/plugins/options/card_option.xml b/addons/website/static/src/builder/plugins/options/card_option.xml
index 37f702adb8b1e..4332aa361e8aa 100644
--- a/addons/website/static/src/builder/plugins/options/card_option.xml
+++ b/addons/website/static/src/builder/plugins/options/card_option.xml
@@ -11,9 +11,7 @@
-
-
-
+
diff --git a/addons/website/static/src/builder/plugins/options/carousel_cards_item_option.js b/addons/website/static/src/builder/plugins/options/carousel_cards_item_option.js
index 3063e377efe8d..e9d07aa7f44b9 100644
--- a/addons/website/static/src/builder/plugins/options/carousel_cards_item_option.js
+++ b/addons/website/static/src/builder/plugins/options/carousel_cards_item_option.js
@@ -1,12 +1,10 @@
import { BaseOptionComponent } from "@html_builder/core/utils";
-import { UpdateOptionOnImgChanged } from "@html_builder/core/utils/update_on_img_changed";
import { CardImageAlignmentOption } from "./card_image_alignment_option";
export class CarouselCardsItemOption extends BaseOptionComponent {
static template = "html_builder.CarouselCardsItemOption";
static components = {
CardImageAlignmentOption,
- UpdateOptionOnImgChanged,
};
static props = {};
}
diff --git a/addons/website/static/src/builder/plugins/options/carousel_cards_item_option.xml b/addons/website/static/src/builder/plugins/options/carousel_cards_item_option.xml
index c957056a85fdb..dfce6f2328044 100644
--- a/addons/website/static/src/builder/plugins/options/carousel_cards_item_option.xml
+++ b/addons/website/static/src/builder/plugins/options/carousel_cards_item_option.xml
@@ -2,9 +2,7 @@
-
-
-
+
diff --git a/addons/website/static/src/builder/plugins/options/visibility_option_plugin.js b/addons/website/static/src/builder/plugins/options/visibility_option_plugin.js
index f7dcf08c2967b..316c2ccf14e46 100644
--- a/addons/website/static/src/builder/plugins/options/visibility_option_plugin.js
+++ b/addons/website/static/src/builder/plugins/options/visibility_option_plugin.js
@@ -5,10 +5,7 @@ import { pyToJsLocale } from "@web/core/l10n/utils";
import { getElementsWithOption } from "@html_builder/utils/utils";
import { VisibilityOption } from "./visibility_option";
import { withSequence } from "@html_editor/utils/resource";
-import {
- CONDITIONAL_VISIBILITY,
- DEVICE_VISIBILITY,
-} from "@website/builder/option_sequence";
+import { CONDITIONAL_VISIBILITY, DEVICE_VISIBILITY } from "@website/builder/option_sequence";
export const VISIBILITY_OPTION_SELECTOR = "section, .s_hr";
export const DEVICE_VISIBILITY_OPTION_SELECTOR = "section .row > div";
diff --git a/addons/website/static/src/client_actions/website_preview/new_content_modal.js b/addons/website/static/src/client_actions/website_preview/new_content_modal.js
index a55cc3ac719de..efc2197be7670 100644
--- a/addons/website/static/src/client_actions/website_preview/new_content_modal.js
+++ b/addons/website/static/src/client_actions/website_preview/new_content_modal.js
@@ -1,8 +1,5 @@
import { InstallModuleDialog } from "./install_module_dialog";
-import {
- MODULE_STATUS,
- NewContentElement,
-} from "./new_content_element";
+import { MODULE_STATUS, NewContentElement } from "./new_content_element";
import { Component, onWillStart, useState, xml } from "@odoo/owl";
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
import { _t } from "@web/core/l10n/translation";
diff --git a/addons/website/static/src/client_actions/website_preview/website_builder_action.js b/addons/website/static/src/client_actions/website_preview/website_builder_action.js
index 7bb5ce17705fe..3b889e0e304a7 100644
--- a/addons/website/static/src/client_actions/website_preview/website_builder_action.js
+++ b/addons/website/static/src/client_actions/website_preview/website_builder_action.js
@@ -168,7 +168,8 @@ export class WebsiteBuilder extends Component {
isMobile: this.websiteContext.isMobile,
Plugins: websitePlugins,
config: { initialTarget: this.target, initialTab: this.initialTab },
- getThemeTab: () => odoo.loader.modules.get("@website/builder/plugins/theme/theme_tab").ThemeTab,
+ getThemeTab: () =>
+ odoo.loader.modules.get("@website/builder/plugins/theme/theme_tab").ThemeTab,
};
}
diff --git a/addons/website/static/tests/builder/custom_tab/container_buttons.test.js b/addons/website/static/tests/builder/custom_tab/container_buttons.test.js
index 80bb1ef010b3d..8568c6e798297 100644
--- a/addons/website/static/tests/builder/custom_tab/container_buttons.test.js
+++ b/addons/website/static/tests/builder/custom_tab/container_buttons.test.js
@@ -37,7 +37,7 @@ const dummySnippet = `
`;
test("Use the sidebar 'remove' buttons", async () => {
- await setupWebsiteBuilder(dummySnippet);
+ const { waitDomUpdated } = await setupWebsiteBuilder(dummySnippet);
const removeSectionSelector =
".o_customize_tab .options-container > div:contains('Dummy Section') button.oe_snippet_remove";
@@ -47,7 +47,7 @@ test("Use the sidebar 'remove' buttons", async () => {
".o_customize_tab .options-container > div:contains('Image') button.oe_snippet_remove";
await contains(":iframe .col-lg-7 img").click();
- await animationFrame();
+ await waitDomUpdated();
expect(removeSectionSelector).toHaveCount(1);
expect(removeColumnSelector).toHaveCount(1);
expect(removeImageSelector).toHaveCount(1);
diff --git a/addons/website/static/tests/builder/image_shape.test.js b/addons/website/static/tests/builder/image_shape.test.js
index 9044c7880a684..d198314cef97d 100644
--- a/addons/website/static/tests/builder/image_shape.test.js
+++ b/addons/website/static/tests/builder/image_shape.test.js
@@ -105,7 +105,7 @@ test("Should change the shape color of an image", async () => {
);
});
test("Should change the shape color of an image with a class color", async () => {
- const { getEditor } = await setupWebsiteBuilder(`
+ const { getEditor, waitDomUpdated } = await setupWebsiteBuilder(`
${testImg}
@@ -154,7 +154,7 @@ test("Should change the shape color of an image with a class color", async () =>
// ensure the shape action has been applied
await editor.shared.operation.next(() => {});
// wait for owl to update the dom
- await animationFrame();
+ await waitDomUpdated();
expect(`[data-label="Colors"] .o_we_color_preview:nth-child(1)`).toHaveAttribute(
"style",
diff --git a/addons/website/static/tests/builder/website_builder/animate_option.test.js b/addons/website/static/tests/builder/website_builder/animate_option.test.js
index b46d66487230e..7e6355632ddef 100644
--- a/addons/website/static/tests/builder/website_builder/animate_option.test.js
+++ b/addons/website/static/tests/builder/website_builder/animate_option.test.js
@@ -35,7 +35,7 @@ test("visibility of animation animation=none", async () => {
});
describe("onAppearance", () => {
test("visibility of animation animation=onAppearance", async () => {
- await setupWebsiteBuilder(
+ const { waitDomUpdated } = await setupWebsiteBuilder(
`
${testImg}
@@ -47,6 +47,7 @@ describe("onAppearance", () => {
await contains(".options-container [data-label='Animation'] .dropdown-toggle").click();
await contains(".o-dropdown--menu [data-action-value='onAppearance']").click();
+ await waitDomUpdated();
expect(".options-container [data-label='Animation'] .o-dropdown").toHaveText(
"On Appearance"
);
@@ -62,7 +63,7 @@ describe("onAppearance", () => {
expect(".options-container [data-label='Duration'] input").toHaveValue("1");
});
test("visibility of animation animation=onAppearance effect=slide", async () => {
- await setupWebsiteBuilder(
+ const { waitDomUpdated } = await setupWebsiteBuilder(
`
${testImg}
@@ -77,6 +78,7 @@ describe("onAppearance", () => {
await contains(".options-container [data-label='Effect'] .dropdown-toggle").click();
await contains(".o-dropdown--menu [data-action-value='o_anim_slide_in']").click();
+ await waitDomUpdated();
expect(".options-container [data-label='Effect'] .o-dropdown").toHaveText("Slide");
expect(".options-container [data-label='Direction'] .o-dropdown").toHaveText("From right");
@@ -89,7 +91,7 @@ describe("onAppearance", () => {
expect(".options-container [data-label='Duration'] input").toHaveValue("1");
});
test("visibility of animation animation=onAppearance effect=bounce", async () => {
- await setupWebsiteBuilder(
+ const { waitDomUpdated } = await setupWebsiteBuilder(
`
${testImg}
@@ -104,6 +106,7 @@ describe("onAppearance", () => {
await contains(".options-container [data-label='Effect'] .dropdown-toggle").click();
await contains(".o-dropdown--menu [data-action-value='o_anim_bounce_in']").click();
+ await waitDomUpdated();
expect(".options-container [data-label='Effect'] .o-dropdown").toHaveText("Bounce");
expect(".options-container [data-label='Direction'] .o-dropdown").toHaveText("In place");
@@ -116,7 +119,7 @@ describe("onAppearance", () => {
expect(".options-container [data-label='Duration'] input").toHaveValue("1");
});
test("visibility of animation animation=onAppearance effect=flash", async () => {
- await setupWebsiteBuilder(
+ const { waitDomUpdated } = await setupWebsiteBuilder(
`
${testImg}
@@ -131,6 +134,7 @@ describe("onAppearance", () => {
await contains(".options-container [data-label='Effect'] .dropdown-toggle").click();
await contains(".o-dropdown--menu [data-action-value='o_anim_flash']").click();
+ await waitDomUpdated();
expect(".options-container [data-label='Effect'] .o-dropdown").toHaveText("Flash");
expect(".options-container [data-label='Direction']").not.toBeVisible();
@@ -144,7 +148,7 @@ describe("onAppearance", () => {
});
});
test("visibility of animation animation=onScroll", async () => {
- await setupWebsiteBuilder(`
+ const { waitDomUpdated } = await setupWebsiteBuilder(`
${testImg}
@@ -153,6 +157,7 @@ test("visibility of animation animation=onScroll", async () => {
await contains(".options-container [data-label='Animation'] .dropdown-toggle").click();
await contains(".o-dropdown--menu [data-action-value='onScroll']").click();
+ await waitDomUpdated();
expect(".options-container [data-label='Animation'] .o-dropdown").toHaveText("On Scroll");
expect(".options-container [data-label='Effect'] .o-dropdown").toHaveText("Fade");
@@ -166,7 +171,7 @@ test("visibility of animation animation=onScroll", async () => {
expect(".options-container [data-label='Scroll Zone']").toBeVisible();
});
test("animation=onScroll should not be visible when the animation is limited", async () => {
- await setupWebsiteBuilder(
+ const { waitDomUpdated } = await setupWebsiteBuilder(
`
${testImg}
@@ -181,6 +186,7 @@ test("animation=onScroll should not be visible when the animation is limited", a
await contains(".options-container [data-label='Effect'] .dropdown-toggle").click();
await contains(".o-dropdown--menu [data-action-value='o_anim_flash']").click();
+ await waitDomUpdated();
expect(".options-container [data-label='Effect'] .o-dropdown").toHaveText("Flash");
await contains(".options-container [data-label='Animation'] .dropdown-toggle").click();
@@ -285,7 +291,7 @@ test("image should not be lazy onAppearance", async () => {
});
test("should not show the animation options if the image has a parent [data-oe-type='image']", async () => {
- const { getEditor } = await setupWebsiteBuilder(`
+ const { getEditor, waitDomUpdated } = await setupWebsiteBuilder(`
${testImg}
@@ -293,17 +299,17 @@ test("should not show the animation options if the image has a parent [data-oe-t
const editor = getEditor();
await contains(":iframe .test-options-target img").click();
- await animationFrame();
+ await waitDomUpdated();
expect(".options-container [data-label='Animation'] .dropdown-toggle").toBeVisible();
const optionTarget = queryFirst(":iframe .test-options-target");
optionTarget.setAttribute("data-oe-type", "image");
editor.shared.history.addStep();
- await animationFrame();
+ await waitDomUpdated();
expect(".options-container [data-label='Animation'] .dropdown-toggle").not.toBeVisible();
});
test("should not show the animation options if the image has is [data-oe-xpath]", async () => {
- const { getEditor } = await setupWebsiteBuilder(`
+ const { getEditor, waitDomUpdated } = await setupWebsiteBuilder(`
${testImg}
@@ -311,12 +317,12 @@ test("should not show the animation options if the image has is [data-oe-xpath]"
const editor = getEditor();
await contains(":iframe .test-options-target img").click();
- await animationFrame();
+ await waitDomUpdated();
expect(".options-container [data-label='Animation'] .dropdown-toggle").toBeVisible();
const optionTarget = queryFirst(":iframe .test-options-target img");
optionTarget.setAttribute("data-oe-xpath", "/foo/bar");
editor.shared.history.addStep();
- await animationFrame();
+ await waitDomUpdated();
expect(".options-container [data-label='Animation'] .dropdown-toggle").not.toBeVisible();
});
diff --git a/addons/website/static/tests/builder/website_builder/image_gallery.test.js b/addons/website/static/tests/builder/website_builder/image_gallery.test.js
index 9aa9bfc43140e..43ea7de1ab5fa 100644
--- a/addons/website/static/tests/builder/website_builder/image_gallery.test.js
+++ b/addons/website/static/tests/builder/website_builder/image_gallery.test.js
@@ -31,7 +31,7 @@ test("Add image in gallery", async () => {
{ pure: true }
);
- await setupWebsiteBuilder(
+ const { waitDomUpdated } = await setupWebsiteBuilder(
`
@@ -68,7 +68,7 @@ test("Add image in gallery", async () => {
// We use "click" instead of contains.click because contains wait for the image to be visible.
// In this test we don't want to wait ~800ms for the image to be visible but we can still click on it
await click("img.o_we_attachment_highlight");
- await animationFrame();
+ await waitDomUpdated();
await contains(".modal-footer button").click();
await waitFor(":iframe .o_masonry_col img[data-index='6']");
@@ -84,6 +84,7 @@ test("Add image in gallery", async () => {
"get_image_info",
"get_image_info",
"get_image_info",
+ "get_image_info",
]);
expect(":iframe .o_masonry_col img[data-index='6']").toHaveAttribute(
"data-mimetype",
diff --git a/addons/website/static/tests/builder/website_helpers.js b/addons/website/static/tests/builder/website_helpers.js
index 1b7b56d860893..4c5662e17663d 100644
--- a/addons/website/static/tests/builder/website_helpers.js
+++ b/addons/website/static/tests/builder/website_helpers.js
@@ -38,6 +38,7 @@ import { WebClient } from "@web/webclient/webclient";
import { patchWithCleanupImg } from "@html_builder/../tests/helpers";
import { getWebsiteSnippets } from "./snippets_getter.hoot";
import { mockImageRequests } from "./image_test_helpers";
+import { Mutex } from "@web/core/utils/concurrency";
class Website extends models.Model {
_name = "website";
@@ -170,9 +171,23 @@ export async function setupWebsiteBuilder(
},
});
+ const mutex = new Mutex();
+ const waitDomUpdated = async () => {
+ await animationFrame();
+ await mutex.exec(() => {});
+ await animationFrame();
+ }
patchWithCleanup(Builder.prototype, {
setup() {
super.setup();
+ patchWithCleanup(this.env.editorBus, {
+ trigger(eventName, detail) {
+ if (eventName === 'DOM_UPDATED') {
+ mutex.exec(() => detail.updatePromise);
+ }
+ return super.trigger(eventName, detail);
+ }
+ });
editor = this.editor;
},
});
@@ -228,6 +243,7 @@ export async function setupWebsiteBuilder(
getEditableContent: () => editableContent,
openBuilderSidebar: async () => await openBuilderSidebar(editAssetsLoaded),
getIframeEl: () => iframe,
+ waitDomUpdated,
};
}
diff --git a/addons/website/tests/test_grid_layout.py b/addons/website/tests/test_grid_layout.py
index 85765bfb2ca66..3f011787c9b66 100644
--- a/addons/website/tests/test_grid_layout.py
+++ b/addons/website/tests/test_grid_layout.py
@@ -3,6 +3,7 @@
import odoo.tests
from odoo.addons.website.tools import create_image_attachment
+
@odoo.tests.common.tagged('post_install', '-at_install')
class TestWebsiteGridLayout(odoo.tests.HttpCase):
@@ -11,7 +12,6 @@ def test_01_replace_grid_image(self):
create_image_attachment(self.env, '/web/image/website.s_banner_default_image', 's_banner_default_image2.jpg')
self.start_tour(self.env['website'].get_client_action_url('/'), 'website_replace_grid_image', login="admin")
-
def test_02_scroll_to_new_grid_item(self):
create_image_attachment(self.env, '/web/image/website.s_banner_default_image', 's_banner_default_image.jpg')
self.start_tour(self.env['website'].get_client_action_url('/'), 'scroll_to_new_grid_item', login='admin')
diff --git a/addons/website_blog/tests/test_ui.py b/addons/website_blog/tests/test_ui.py
index ef92bc974cd53..514ce29a1dd51 100644
--- a/addons/website_blog/tests/test_ui.py
+++ b/addons/website_blog/tests/test_ui.py
@@ -4,6 +4,7 @@
import odoo.tests
from odoo.addons.website_blog.tests.common import TestWebsiteBlogCommon
+
@odoo.tests.tagged('post_install', '-at_install')
class TestWebsiteBlogUi(odoo.tests.HttpCase, TestWebsiteBlogCommon):
@classmethod
diff --git a/addons/website_crm/tests/test_website_crm.py b/addons/website_crm/tests/test_website_crm.py
index ce77251a4f1ff..38e21c626de92 100644
--- a/addons/website_crm/tests/test_website_crm.py
+++ b/addons/website_crm/tests/test_website_crm.py
@@ -4,6 +4,7 @@
import odoo.tests
import unittest
+
# TODO master-mysterious-egg fix error
@unittest.skip("prepare mysterious-egg for merging")
@odoo.tests.tagged('post_install', '-at_install')
diff --git a/addons/website_event/tests/test_website_event.py b/addons/website_event/tests/test_website_event.py
index 5d3829573be8c..9b22926420377 100644
--- a/addons/website_event/tests/test_website_event.py
+++ b/addons/website_event/tests/test_website_event.py
@@ -14,6 +14,7 @@
from odoo.tests.common import users
import unittest
+
# TODO master-mysterious-egg fix error
@unittest.skip("prepare mysterious-egg for merging")
class TestEventRegisterUTM(HttpCase, TestEventOnlineCommon):
@@ -53,6 +54,7 @@ def test_event_registration_utm_values(self):
self.assertEqual(new_registration.utm_source_id, self.env.ref('utm.utm_source_newsletter'))
self.assertEqual(new_registration.utm_medium_id, self.env.ref('utm.utm_medium_email'))
+
# TODO master-mysterious-egg fix error
@unittest.skip("prepare mysterious-egg for merging")
@tagged('post_install', '-at_install')
diff --git a/addons/website_mass_mailing/tests/test_snippets.py b/addons/website_mass_mailing/tests/test_snippets.py
index a8e8f250be3d8..986e18cd1e3fd 100644
--- a/addons/website_mass_mailing/tests/test_snippets.py
+++ b/addons/website_mass_mailing/tests/test_snippets.py
@@ -4,6 +4,7 @@
from odoo.tests import HttpCase, tagged
import unittest
+
@tagged('post_install', '-at_install')
class TestSnippets(HttpCase):
diff --git a/addons/website_sale/tests/test_customize.py b/addons/website_sale/tests/test_customize.py
index 1619ca72079f2..2f909ea41ef9b 100644
--- a/addons/website_sale/tests/test_customize.py
+++ b/addons/website_sale/tests/test_customize.py
@@ -8,6 +8,7 @@
from odoo.addons.website.tests.common import HttpCaseWithWebsiteUser
import unittest
+
# TODO master-mysterious-egg fix error
@unittest.skip("prepare mysterious-egg for merging")
@tagged('post_install', '-at_install')
diff --git a/addons/website_sale/tests/test_delivery_ui.py b/addons/website_sale/tests/test_delivery_ui.py
index e8e8d7744133c..71704270c5ddb 100644
--- a/addons/website_sale/tests/test_delivery_ui.py
+++ b/addons/website_sale/tests/test_delivery_ui.py
@@ -3,6 +3,7 @@
import odoo.tests
from odoo.fields import Command
+
@odoo.tests.tagged('post_install', '-at_install')
class TestUi(odoo.tests.HttpCase):
def test_01_free_delivery_when_exceed_threshold(self):
diff --git a/addons/website_sale/tests/test_sale_process.py b/addons/website_sale/tests/test_sale_process.py
index 258ef95c4752c..207f097929321 100644
--- a/addons/website_sale/tests/test_sale_process.py
+++ b/addons/website_sale/tests/test_sale_process.py
@@ -12,6 +12,7 @@
_logger = logging.getLogger(__name__)
+
# TODO master-mysterious-egg fix error
@unittest.skip("prepare mysterious-egg for merging")
@tagged('post_install', '-at_install')
diff --git a/addons/website_sale/tests/test_website_sale_combo_configurator.py b/addons/website_sale/tests/test_website_sale_combo_configurator.py
index eadfafa5f91da..5a006e2f01afe 100644
--- a/addons/website_sale/tests/test_website_sale_combo_configurator.py
+++ b/addons/website_sale/tests/test_website_sale_combo_configurator.py
@@ -5,6 +5,7 @@
from odoo.addons.website_sale.tests.common import WebsiteSaleCommon
+
@tagged('post_install', '-at_install')
class TestWebsiteSaleComboConfigurator(HttpCase, WebsiteSaleCommon):
def test_website_sale_combo_configurator(self):
diff --git a/addons/website_sale/tests/test_website_sale_reorder_from_portal.py b/addons/website_sale/tests/test_website_sale_reorder_from_portal.py
index 15c6794486d87..7a4f83d2fca5b 100644
--- a/addons/website_sale/tests/test_website_sale_reorder_from_portal.py
+++ b/addons/website_sale/tests/test_website_sale_reorder_from_portal.py
@@ -2,7 +2,6 @@
from odoo.fields import Command
from odoo.tests import tagged
-import unittest
from odoo.addons.base.tests.common import HttpCaseWithUserPortal
from odoo.addons.website.tools import MockRequest
diff --git a/addons/website_sale/tests/test_website_sale_show_compare_list_price.py b/addons/website_sale/tests/test_website_sale_show_compare_list_price.py
index 10a53fa5823ce..9a56150864737 100644
--- a/addons/website_sale/tests/test_website_sale_show_compare_list_price.py
+++ b/addons/website_sale/tests/test_website_sale_show_compare_list_price.py
@@ -2,7 +2,6 @@
from odoo.fields import Command
from odoo.tests import tagged
-import unittest
from odoo.addons.account.tests.common import AccountTestInvoicingHttpCommon
diff --git a/addons/website_sale/tests/test_website_sale_snippets.py b/addons/website_sale/tests/test_website_sale_snippets.py
index 71c8e20f6dce1..d780472272c76 100644
--- a/addons/website_sale/tests/test_website_sale_snippets.py
+++ b/addons/website_sale/tests/test_website_sale_snippets.py
@@ -3,7 +3,6 @@
import logging
from odoo.tests import HttpCase, tagged
-import unittest
from odoo.addons.website.tools import MockRequest
diff --git a/addons/website_sale_stock/tests/test_website_sale_stock_stock_notification.py b/addons/website_sale_stock/tests/test_website_sale_stock_stock_notification.py
index f4ef41fe7c90a..3d667a18ef9f7 100644
--- a/addons/website_sale_stock/tests/test_website_sale_stock_stock_notification.py
+++ b/addons/website_sale_stock/tests/test_website_sale_stock_stock_notification.py
@@ -4,6 +4,7 @@
from odoo.tests import tagged
from odoo.tests.common import HttpCase
+
@tagged('post_install', '-at_install')
class TestStockNotificationProduct(HttpCase):
@classmethod
diff --git a/addons/website_sale_wishlist/tests/test_wishlist_process.py b/addons/website_sale_wishlist/tests/test_wishlist_process.py
index 6ad592fae3542..0d8332cae4f48 100644
--- a/addons/website_sale_wishlist/tests/test_wishlist_process.py
+++ b/addons/website_sale_wishlist/tests/test_wishlist_process.py
@@ -4,6 +4,7 @@
from odoo.tests import HttpCase, tagged
import unittest
+
@tagged('-at_install', 'post_install')
class TestWishlistProcess(HttpCase):
diff --git a/odoo/addons/test_main_flows/tests/test_flow.py b/odoo/addons/test_main_flows/tests/test_flow.py
index 4a9a0cad83213..e9deed6877250 100644
--- a/odoo/addons/test_main_flows/tests/test_flow.py
+++ b/odoo/addons/test_main_flows/tests/test_flow.py
@@ -191,6 +191,7 @@ def test_company_switch_access_error_debug(self):
with mute_logger("odoo.http"):
self.start_tour(f"/odoo/action-{act_window.id}?debug=assets", "test_company_switch_access_error", login="admin", cookies={"cids": current_companies})
+
# TODO master-mysterious-egg fix error
@unittest.skip("prepare mysterious-egg for merging")
@odoo.tests.tagged('post_install', '-at_install')