[FIX] web: rerender MainComponentsContainer on main component error
Previously, when a main component errored during rendering, we would remove it from the list of main components that should be rendered by the MainComponentsContainer to avoid it erroring again on a subsequent rendering (which can cause infinite error loops). Unfortunately, since we did not rerender the MainComponentsContainer after this removal, the erroring main component would remain, and could cause such error loops anyway. This commit fixes that. closes odoo/odoo#80822 X-original-commit: 0a2fa98086dfdaa36b01ca8ef3e22b8b8ffef60c Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com> Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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`<span>MainComponentA</span>`;
|
||||
|
||||
class MainComponentB extends Component {}
|
||||
MainComponentB.template = xml`<span>MainComponentB</span>`;
|
||||
|
||||
mainComponentsRegistry.add("MainComponentA", { Component: MainComponentA, props: {} });
|
||||
mainComponentsRegistry.add("MainComponentB", { Component: MainComponentB, props: {} });
|
||||
container = await mount(MainComponentsContainer, { env, target, props: {} });
|
||||
assert.equal(
|
||||
container.el.outerHTML,
|
||||
"<div><span>MainComponentA</span><span>MainComponentB</span></div>"
|
||||
);
|
||||
});
|
||||
|
||||
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`<span><t t-if="state.shouldThrow" t-esc="error"/>MainComponentA</span>`;
|
||||
|
||||
class MainComponentB extends Component {}
|
||||
MainComponentB.template = xml`<span>MainComponentB</span>`;
|
||||
|
||||
mainComponentsRegistry.add("MainComponentA", { Component: MainComponentA, props: {} });
|
||||
mainComponentsRegistry.add("MainComponentB", { Component: MainComponentB, props: {} });
|
||||
container = await mount(MainComponentsContainer, { env, target, props: {} });
|
||||
assert.equal(
|
||||
container.el.outerHTML,
|
||||
"<div><span>MainComponentA</span><span>MainComponentB</span></div>"
|
||||
);
|
||||
|
||||
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, "<div><span>MainComponentB</span></div>");
|
||||
});
|
||||
|
||||
QUnit.test("unmounts erroring main component: variation", async function (assert) {
|
||||
const env = await makeTestEnv();
|
||||
|
||||
class MainComponentA extends Component {}
|
||||
MainComponentA.template = xml`<span>MainComponentA</span>`;
|
||||
|
||||
let compB;
|
||||
class MainComponentB extends Component {
|
||||
setup() {
|
||||
compB = this;
|
||||
this.state = useState({ shouldThrow: false });
|
||||
}
|
||||
get error() {
|
||||
throw new Error("BOOM");
|
||||
}
|
||||
}
|
||||
MainComponentB.template = xml`<span><t t-if="state.shouldThrow" t-esc="error"/>MainComponentB</span>`;
|
||||
|
||||
mainComponentsRegistry.add("MainComponentA", { Component: MainComponentA, props: {} });
|
||||
mainComponentsRegistry.add("MainComponentB", { Component: MainComponentB, props: {} });
|
||||
container = await mount(MainComponentsContainer, { env, target, props: {} });
|
||||
assert.equal(
|
||||
container.el.outerHTML,
|
||||
"<div><span>MainComponentA</span><span>MainComponentB</span></div>"
|
||||
);
|
||||
|
||||
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, "<div><span>MainComponentA</span></div>");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user