diff --git a/addons/web/static/src/core/main_components_container.js b/addons/web/static/src/core/main_components_container.js
index 8a8b4a4a7d0..477f3f5e250 100644
--- a/addons/web/static/src/core/main_components_container.js
+++ b/addons/web/static/src/core/main_components_container.js
@@ -10,8 +10,9 @@ export class MainComponentsContainer extends Component {
}
handleComponentError(error, C) {
- // remove the faulty component
+ // remove the faulty component and rerender without it
this.Components.splice(this.Components.indexOf(C), 1);
+ this.render();
/**
* we rethrow the error to notify the user something bad happened.
* We do it after a tick to make sure owl can properly finish its
diff --git a/addons/web/static/tests/core/main_components_container_tests.js b/addons/web/static/tests/core/main_components_container_tests.js
new file mode 100644
index 00000000000..3f853d01ca9
--- /dev/null
+++ b/addons/web/static/tests/core/main_components_container_tests.js
@@ -0,0 +1,136 @@
+/** @odoo-module **/
+import { MainComponentsContainer } from "@web/core/main_components_container";
+import { registry } from "@web/core/registry";
+import { clearRegistryWithCleanup, makeTestEnv } from "../helpers/mock_env";
+import { patch, unpatch } from "@web/core/utils/patch";
+import { getFixture, nextTick } from "../helpers/utils";
+
+const { mount, Component } = owl;
+const { useState } = owl.hooks;
+const { xml } = owl.tags;
+const mainComponentsRegistry = registry.category("main_components");
+let container;
+let target;
+
+QUnit.module("Components", (hooks) => {
+ hooks.beforeEach(async () => {
+ target = getFixture();
+ clearRegistryWithCleanup(mainComponentsRegistry);
+ });
+ hooks.afterEach(() => {
+ if (container) {
+ container.unmount();
+ container = undefined;
+ }
+ });
+
+ QUnit.module("MainComponentsContainer");
+
+ QUnit.test("simple rendering", async function (assert) {
+ const env = await makeTestEnv();
+
+ class MainComponentA extends Component {}
+ MainComponentA.template = xml`MainComponentA`;
+
+ class MainComponentB extends Component {}
+ MainComponentB.template = xml`MainComponentB`;
+
+ mainComponentsRegistry.add("MainComponentA", { Component: MainComponentA, props: {} });
+ mainComponentsRegistry.add("MainComponentB", { Component: MainComponentB, props: {} });
+ container = await mount(MainComponentsContainer, { env, target, props: {} });
+ assert.equal(
+ container.el.outerHTML,
+ "
MainComponentAMainComponentB
"
+ );
+ });
+
+ QUnit.test("unmounts erroring main component", async function (assert) {
+ const env = await makeTestEnv();
+
+ let compA;
+ class MainComponentA extends Component {
+ setup() {
+ compA = this;
+ this.state = useState({ shouldThrow: false });
+ }
+ get error() {
+ throw new Error("BOOM");
+ }
+ }
+ MainComponentA.template = xml`MainComponentA`;
+
+ class MainComponentB extends Component {}
+ MainComponentB.template = xml`MainComponentB`;
+
+ mainComponentsRegistry.add("MainComponentA", { Component: MainComponentA, props: {} });
+ mainComponentsRegistry.add("MainComponentB", { Component: MainComponentB, props: {} });
+ container = await mount(MainComponentsContainer, { env, target, props: {} });
+ assert.equal(
+ container.el.outerHTML,
+ "MainComponentAMainComponentB
"
+ );
+
+ const handler = (ev) => {
+ assert.step(ev.reason.message);
+ // need to preventDefault to remove error from console (so python test pass)
+ ev.preventDefault();
+ };
+ window.addEventListener("unhandledrejection", handler);
+ patch(QUnit, "MainComponentsContainer QUnit patch", {
+ onUnhandledRejection: () => {},
+ });
+ compA.state.shouldThrow = true;
+ await nextTick();
+ window.removeEventListener("unhandledrejection", handler);
+ // unpatch QUnit asap so any other errors can be caught by it
+ unpatch(QUnit, "MainComponentsContainer QUnit patch");
+ assert.verifySteps(["BOOM"]);
+
+ assert.equal(container.el.outerHTML, "MainComponentB
");
+ });
+
+ QUnit.test("unmounts erroring main component: variation", async function (assert) {
+ const env = await makeTestEnv();
+
+ class MainComponentA extends Component {}
+ MainComponentA.template = xml`MainComponentA`;
+
+ let compB;
+ class MainComponentB extends Component {
+ setup() {
+ compB = this;
+ this.state = useState({ shouldThrow: false });
+ }
+ get error() {
+ throw new Error("BOOM");
+ }
+ }
+ MainComponentB.template = xml`MainComponentB`;
+
+ mainComponentsRegistry.add("MainComponentA", { Component: MainComponentA, props: {} });
+ mainComponentsRegistry.add("MainComponentB", { Component: MainComponentB, props: {} });
+ container = await mount(MainComponentsContainer, { env, target, props: {} });
+ assert.equal(
+ container.el.outerHTML,
+ "MainComponentAMainComponentB
"
+ );
+
+ const handler = (ev) => {
+ assert.step(ev.reason.message);
+ // need to preventDefault to remove error from console (so python test pass)
+ ev.preventDefault();
+ };
+ window.addEventListener("unhandledrejection", handler);
+ patch(QUnit, "MainComponentsContainer QUnit patch", {
+ onUnhandledRejection: () => {},
+ });
+ compB.state.shouldThrow = true;
+ await nextTick();
+ window.removeEventListener("unhandledrejection", handler);
+ // unpatch QUnit asap so any other errors can be caught by it
+ unpatch(QUnit, "MainComponentsContainer QUnit patch");
+ assert.verifySteps(["BOOM"]);
+
+ assert.equal(container.el.outerHTML, "MainComponentA
");
+ });
+});