diff --git a/tests/components/error_handling.test.ts b/tests/components/error_handling.test.ts
index 151287c1..67254427 100644
--- a/tests/components/error_handling.test.ts
+++ b/tests/components/error_handling.test.ts
@@ -1,1875 +1,1875 @@
-// import { App, Component, mount, onWillDestroy } from "../../src";
-// import {
-// onError,
-// onMounted,
-// onPatched,
-// onWillPatch,
-// onWillStart,
-// onWillRender,
-// onRendered,
-// onWillUnmount,
-// useState,
-// xml,
-// } from "../../src/index";
-// import {
-// logStep,
-// makeTestFixture,
-// nextTick,
-// nextMicroTick,
-// snapshotEverything,
-// useLogLifecycle,
-// nextAppError,
-// steps,
-// } from "../helpers";
-// import { OwlError } from "../../src/common/owl_error";
-
-// let fixture: HTMLElement;
-
-// snapshotEverything();
-
-// let originalconsoleError = console.error;
-// let mockConsoleError: any;
-// let originalconsoleWarn = console.warn;
-// let mockConsoleWarn: any;
-
-// beforeEach(() => {
-// fixture = makeTestFixture();
-// mockConsoleError = jest.fn(() => {});
-// mockConsoleWarn = jest.fn(() => {});
-// console.error = mockConsoleError;
-// console.warn = mockConsoleWarn;
-// });
-
-// afterEach(() => {
-// console.error = originalconsoleError;
-// console.warn = originalconsoleWarn;
-// });
-
-// describe("basics", () => {
-// test("no component catching error lead to full app destruction", async () => {
-// class ErrorComponent extends Component {
-// static template = xml`
hey
`;
-// }
-
-// class Parent extends Component {
-// static template = xml`
`;
-// static components = { ErrorComponent };
-// state = { flag: false };
-// }
-// const parent = await mount(Parent, fixture);
-// expect(fixture.innerHTML).toBe("");
-// parent.state.flag = true;
-
-// parent.render();
-// await expect(nextAppError(parent.__owl__.app)).resolves.toThrow(
-// "An error occured in the owl lifecycle"
-// );
-// expect(fixture.innerHTML).toBe("");
-// expect(mockConsoleWarn).toBeCalledTimes(1);
-// });
-
-// test("display a nice error if it cannot find component", async () => {
-// class SomeComponent extends Component {}
-// class Parent extends Component {
-// static template = xml``;
-// static components = { SomeComponent };
-// }
-// const app = new App(Parent);
-// let error: Error;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow(
-// 'Cannot find the definition of component "SomeMispelledComponent"'
-// );
-// await mountProm;
-// expect(error!).toBeDefined();
-// expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
-// expect(console.error).toBeCalledTimes(0);
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(1);
-// });
-
-// test("display a nice error if it cannot find component (in dev mode)", async () => {
-// class SomeComponent extends Component {}
-// class Parent extends Component {
-// static template = xml``;
-// static components = { SomeComponent };
-// }
-// const app = new App(Parent, { test: true });
-// let error: Error;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow(
-// 'Cannot find the definition of component "SomeMispelledComponent"'
-// );
-// await mountProm;
-// expect(error!).toBeDefined();
-// expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
-// expect(console.error).toBeCalledTimes(0);
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(1);
-// });
-
-// test("display a nice error if a component is not a component", async () => {
-// function notAComponentConstructor() {}
-// class Parent extends Component {
-// static template = xml``;
-// static components = { SomeComponent: notAComponentConstructor };
-// }
-// const app = new App(Parent as typeof Component);
-// let error: Error;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow(
-// '"SomeComponent" is not a Component. It must inherit from the Component class'
-// );
-// await mountProm;
-// expect(error!).toBeDefined();
-// expect(error!.message).toBe(
-// '"SomeComponent" is not a Component. It must inherit from the Component class'
-// );
-// });
-
-// test("display a nice error if the components key is missing with subcomponents", async () => {
-// class Parent extends Component {
-// static template = xml`
`;
-// }
-// const app = new App(Parent as typeof Component);
-// let error: Error;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow(
-// 'Cannot find the definition of component "MissingChild", missing static components key in parent'
-// );
-// await mountProm;
-// expect(error!).toBeDefined();
-// expect(error!.message).toBe(
-// 'Cannot find the definition of component "MissingChild", missing static components key in parent'
-// );
-// });
-
-// test("display a nice error if the root component template fails to compile", async () => {
-// // This is a special case: mount throws synchronously and we don't have any
-// // node which can handle the error, hence the different structure of this test
-// class Comp extends Component {
-// static template = xml`test
`;
-// }
-// const app = new App(Comp);
-// let error: Error;
-// try {
-// await app.mount(fixture);
-// } catch (e) {
-// error = e as Error;
-// }
-// const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier 'ctx'
-
-// generated code:
-// function(app, bdom, helpers) {
-// let { text, createBlock, list, multi, html, toggler, comment } = bdom;
-
-// let block1 = createBlock(\`test
\`);
-
-// return function template(ctx, node, key = "") {
-// let attr1 = ctx['a']ctx['b'];
-// return block1([attr1]);
-// }
-// }`;
-// expect(error!).toBeDefined();
-// expect(error!.message).toBe(expectedErrorMessage);
-// });
-
-// test("display a nice error if a non-root component template fails to compile", async () => {
-// class Child extends Component {
-// static template = xml`test
`;
-// }
-// class Parent extends Component {
-// static components = { Child };
-// static template = xml``;
-// }
-// const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier 'ctx'
-
-// generated code:
-// function(app, bdom, helpers) {
-// let { text, createBlock, list, multi, html, toggler, comment } = bdom;
-
-// let block1 = createBlock(\`test
\`);
-
-// return function template(ctx, node, key = "") {
-// let attr1 = ctx['a']ctx['b'];
-// return block1([attr1]);
-// }
-// }`;
-// const app = new App(Parent as typeof Component);
-// let error: Error;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow(expectedErrorMessage);
-// await mountProm;
-// expect(error!).toBeDefined();
-// expect(error!.message).toBe(expectedErrorMessage);
-// });
-
-// test("simple catchError", async () => {
-// class Boom extends Component {
-// static template = xml``;
-// }
-
-// class Parent extends Component {
-// static template = xml`
-//
-// Error
-//
-//
-//
-//
`;
-// static components = { Boom };
-
-// error: any = false;
-
-// setup() {
-// onError((err) => {
-// this.error = err;
-// this.render();
-// });
-// }
-// }
-// await mount(Parent, fixture);
-// expect(fixture.innerHTML).toBe("Error
");
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-// });
-
-// describe("errors and promises", () => {
-// test("a rendering error will reject the mount promise", async () => {
-// // we do not catch error in willPatch anymore
-// class Root extends Component {
-// static template = xml`
`;
-// }
-
-// const app = new App(Root);
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
-// await mountProm;
-// expect(error!).toBeDefined();
-// expect(error!.cause).toBeDefined();
-// const regexp =
-// /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
-// expect(error!.cause.message).toMatch(regexp);
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleError).toBeCalledTimes(0);
-// });
-
-// test("an error in mounted call will reject the mount promise", async () => {
-// class Root extends Component {
-// static template = xml`abc
`;
-// setup() {
-// onMounted(() => {
-// throw new Error("boom");
-// });
-// }
-// }
-
-// const app = new App(Root);
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
-// await mountProm;
-// expect(error!).toBeDefined();
-// expect(error!.cause).toBeDefined();
-// expect(error!.cause.message).toBe("boom");
-// expect(fixture.innerHTML).toBe("");
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(1);
-// });
-
-// test("an error in onMounted callback will have the component's setup in its stack trace", async () => {
-// class Root extends Component {
-// static template = xml`abc
`;
-// setup() {
-// onMounted(() => {
-// throw new Error("boom");
-// });
-// }
-// }
-
-// const app = new App(Root, { test: true });
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted");
-// await mountProm;
-// expect(error!).toBeDefined();
-// expect(error!.stack).toContain("Root.setup");
-// expect(error!.stack).toContain("error_handling.test.ts");
-// expect(fixture.innerHTML).toBe("");
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(1);
-// });
-
-// test("errors in onWillRender/onRender aren't wrapped more than once", async () => {
-// class Root extends Component {
-// static template = xml`abc
`;
-// setup() {
-// onWillRender(() => {
-// throw new Error("boom in onWillRender");
-// });
-// onRendered(() => {
-// throw new Error("boom in onRendered");
-// });
-// }
-// }
-
-// const app = new App(Root, { test: true });
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillRender");
-// await mountProm;
-// expect(error!).toBeDefined();
-// expect(error!.message).toBe(
-// `The following error occurred in onWillRender: "boom in onWillRender"`
-// );
-// });
-
-// test("error while rendering component isn't wrapped by onWillRender/onRendered", async () => {
-// class App extends Component {
-// static template = xml`abc
`;
-// setup() {
-// onWillRender(() => {});
-// onRendered(() => {});
-// }
-// }
-
-// let error: Error;
-// try {
-// await mount(App, fixture, { test: true });
-// } catch (e) {
-// error = e as Error;
-// }
-// expect(error!).toBeDefined();
-// expect(error!.message).toBe("Tokenizer error: could not tokenize `{ 'invalid: 5 }`");
-// });
-
-// test("wrapped errors in async code are correctly caught", async () => {
-// class Root extends Component {
-// static template = xml`abc
`;
-// setup() {
-// onWillStart(async () => {
-// await Promise.resolve();
-// throw new Error("boom in onWillStart");
-// });
-// }
-// }
-
-// const app = new App(Root, { test: true });
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart");
-// await mountProm;
-// expect(error!).toBeDefined();
-// expect(error!.message).toBe(
-// `The following error occurred in onWillStart: "boom in onWillStart"`
-// );
-// await new Promise((r) => setTimeout(r, 0)); // wait for the rejection event to bubble
-// });
-
-// test("an error in willPatch call will reject the render promise", async () => {
-// class Root extends Component {
-// static template = xml`
`;
-// val = 3;
-// setup() {
-// onWillPatch(() => {
-// throw new Error("boom");
-// });
-// onError((e) => (error = e));
-// }
-// }
-
-// const root = await mount(Root, fixture, { test: true });
-// root.val = 4;
-// let error: Error;
-// root.render();
-// await nextTick();
-// expect(error!).toBeDefined();
-// expect(error!.message).toBe(`The following error occurred in onWillPatch: "boom"`);
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("an error in patched call will reject the render promise", async () => {
-// class Root extends Component {
-// static template = xml`
`;
-// val = 3;
-// setup() {
-// onPatched(() => {
-// throw new Error("boom");
-// });
-// onError((e) => (error = e));
-// }
-// }
-
-// const root = await mount(Root, fixture, { test: true });
-// root.val = 4;
-// let error: Error;
-// root.render();
-// await nextTick();
-// expect(error!).toBeDefined();
-// expect(error!.message).toBe(`The following error occurred in onPatched: "boom"`);
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("a rendering error in a sub component will reject the mount promise", async () => {
-// // we do not catch error in willPatch anymore
-// class Child extends Component {
-// static template = xml`
`;
-// }
-// class Parent extends Component {
-// static template = xml`
`;
-// static components = { Child };
-// }
-
-// const app = new App(Parent);
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
-// await mountProm;
-// expect(error!).toBeDefined();
-// expect(error!.cause).toBeDefined();
-// const regexp =
-// /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
-// expect(error!.cause.message).toMatch(regexp);
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(1);
-// });
-
-// test("a rendering error will reject the render promise", async () => {
-// class Root extends Component {
-// static template = xml`
`;
-// flag = false;
-// setup() {
-// onError(({ cause }) => (error = cause));
-// }
-// }
-
-// const root = await mount(Root, fixture);
-// expect(fixture.innerHTML).toBe("");
-// root.flag = true;
-// let error: Error;
-// root.render();
-// await nextTick();
-// expect(error!).toBeDefined();
-// const regexp =
-// /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
-// expect(error!.message).toMatch(regexp);
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("a rendering error will reject the render promise (with sub components)", async () => {
-// class Child extends Component {
-// static template = xml``;
-// }
-// class Parent extends Component {
-// static template = xml`
`;
-// static components = { Child };
-// }
-
-// const app = new App(Parent);
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
-// await mountProm;
-// expect(error!).toBeDefined();
-// expect(error!.cause).toBeDefined();
-// const regexp =
-// /Cannot read properties of undefined \(reading 'y'\)|Cannot read property 'y' of undefined/g;
-// expect(error!.cause.message).toMatch(regexp);
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(1);
-// });
-
-// test("errors in mounted and in willUnmount", async () => {
-// class Example extends Component {
-// static template = xml``;
-// val: any;
-// setup() {
-// onMounted(() => {
-// throw new Error("Error in mounted");
-// this.val = { foo: "bar" };
-// });
-
-// onWillUnmount(() => {
-// console.log(this.val.foo);
-// });
-// }
-// }
-
-// const app = new App(Example, { test: true });
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted");
-// await mountProm;
-// expect(error!.message).toBe(`The following error occurred in onMounted: "Error in mounted"`);
-// // 1 additional error is logged because the destruction of the app causes
-// // the onWillUnmount hook to be called and to fail
-// expect(mockConsoleError).toBeCalledTimes(1);
-// expect(mockConsoleWarn).toBeCalledTimes(1);
-// });
-
-// test("errors in rerender", async () => {
-// class Example extends Component {
-// static template = xml``;
-// state: any = { a: { b: 1 } };
-// }
-// const root = await mount(Example, fixture);
-// expect(fixture.innerHTML).toBe("1
");
-
-// root.state = "boom";
-// root.render();
-// await expect(nextAppError(root.__owl__.app)).resolves.toThrow(
-// "error occured in the owl lifecycle"
-// );
-// expect(fixture.innerHTML).toBe("");
-// expect(mockConsoleWarn).toBeCalledTimes(1);
-// });
-// });
-
-// describe("can catch errors", () => {
-// test("can catch an error in a component render function", async () => {
-// class ErrorComponent extends Component {
-// static template = xml`hey
`;
-// }
-// class ErrorBoundary extends Component {
-// static template = xml`
-//
-// Error handled
-//
-//
`;
-// state = useState({ error: false });
-
-// setup() {
-// onError(() => (this.state.error = true));
-// }
-// }
-// class App extends Component {
-// static template = xml`
-//
-//
-//
`;
-// state = useState({ flag: false });
-// static components = { ErrorBoundary, ErrorComponent };
-// }
-// const app = await mount(App, fixture);
-// expect(fixture.innerHTML).toBe("");
-// app.state.flag = true;
-// await nextTick();
-// expect(fixture.innerHTML).toBe("");
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("can catch an error in onmounted", async () => {
-// class ErrorComponent extends Component {
-// static template = xml`Error!!!
`;
-// setup() {
-// useLogLifecycle();
-// onMounted(() => {
-// throw new Error("error");
-// });
-// }
-// }
-// class PerfectComponent extends Component {
-// static template = xml`perfect
`;
-// setup() {
-// useLogLifecycle();
-// }
-// }
-// class Main extends Component {
-// static template = xml`Main`;
-// component: any;
-// state: any;
-// setup() {
-// this.state = useState({ ok: false });
-// useLogLifecycle();
-// this.component = ErrorComponent;
-// onError(() => {
-// this.component = PerfectComponent;
-// this.render();
-// });
-// }
-// }
-
-// const app = await mount(Main, fixture);
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Main:setup",
-// "Main:willStart",
-// "Main:willRender",
-// "Main:rendered",
-// "Main:mounted",
-// ]
-// `);
-// expect(fixture.innerHTML).toBe("Main");
-// (app as any).state.ok = true;
-// await nextTick();
-// expect(fixture.innerHTML).toBe("MainError!!!
");
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Main:willRender",
-// "ErrorComponent:setup",
-// "ErrorComponent:willStart",
-// "Main:rendered",
-// "ErrorComponent:willRender",
-// "ErrorComponent:rendered",
-// "Main:willPatch",
-// "ErrorComponent:mounted",
-// "Main:willRender",
-// "PerfectComponent:setup",
-// "PerfectComponent:willStart",
-// "Main:rendered",
-// ]
-// `);
-// await nextTick();
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "PerfectComponent:willRender",
-// "PerfectComponent:rendered",
-// "Main:willPatch",
-// "ErrorComponent:willUnmount",
-// "ErrorComponent:willDestroy",
-// "PerfectComponent:mounted",
-// "Main:patched",
-// ]
-// `);
-// expect(fixture.innerHTML).toBe("Mainperfect
");
-// });
-
-// test("calling a hook outside setup should crash", async () => {
-// class Root extends Component {
-// static template = xml``;
-// state = useState({ value: 1 });
-
-// setup() {
-// onWillStart(() => {
-// this.state = useState({ value: 2 });
-// });
-// }
-// }
-// const app = new App(Root, { test: true });
-// let error: OwlError;
-// const crashProm = expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart");
-// await app.mount(fixture).catch((e: Error) => (error = e));
-// await crashProm;
-// expect(error!.message).toBe(
-// `The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"`
-// );
-// });
-
-// test("Errors have the right cause", async () => {
-// const err = new Error("test error");
-// class Root extends Component {
-// static template = xml``;
-// state = useState({ value: 1 });
-
-// setup() {
-// onMounted(() => {
-// throw err;
-// });
-// }
-// }
-// const app = new App(Root, { test: true });
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted");
-// await mountProm;
-// expect(error!.message).toBe(`The following error occurred in onMounted: "test error"`);
-// expect(error!.cause).toBe(err);
-// });
-
-// test("Errors in owl lifecycle are wrapped in dev mode: async hook", async () => {
-// const err = new Error("test error");
-// class Root extends Component {
-// static template = xml``;
-// state = useState({ value: 1 });
-
-// setup() {
-// onWillStart(async () => {
-// await nextMicroTick();
-// throw err;
-// });
-// }
-// }
-// const app = new App(Root, { test: true });
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart");
-// await mountProm;
-// expect(error!.message).toBe(`The following error occurred in onWillStart: "test error"`);
-// expect(error!.cause).toBe(err);
-// });
-
-// test("Errors in owl lifecycle are wrapped outside dev mode: sync hook", async () => {
-// const err = new Error("test error");
-// class Root extends Component {
-// static template = xml``;
-// state = useState({ value: 1 });
-
-// setup() {
-// onMounted(() => {
-// throw err;
-// });
-// }
-// }
-// const app = new App(Root);
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
-// await mountProm;
-// expect(error!.message).toBe(
-// `An error occured in the owl lifecycle (see this Error's "cause" property)`
-// );
-// expect(error!.cause).toBe(err);
-// });
-
-// test("Errors in owl lifecycle are wrapped out of dev mode: async hook", async () => {
-// const err = new Error("test error");
-// class Root extends Component {
-// static template = xml``;
-// state = useState({ value: 1 });
-
-// setup() {
-// onWillStart(async () => {
-// await nextMicroTick();
-// throw err;
-// });
-// }
-// }
-// const app = new App(Root);
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
-// await mountProm;
-// expect(error!.message).toBe(
-// `An error occured in the owl lifecycle (see this Error's "cause" property)`
-// );
-// expect(error!.cause).toBe(err);
-// });
-
-// test("Thrown values that are not errors are wrapped in dev mode", async () => {
-// class Root extends Component {
-// static template = xml``;
-// state = useState({ value: 1 });
-
-// setup() {
-// onMounted(() => {
-// throw "This is not an error";
-// });
-// }
-// }
-// const app = new App(Root, { test: true });
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("not an Error was thrown in onMounted");
-// await mountProm;
-// expect(error!.message).toBe(
-// `Something that is not an Error was thrown in onMounted (see this Error's "cause" property)`
-// );
-// expect(error!.cause).toBe("This is not an error");
-// });
-
-// test("Thrown values that are not errors are wrapped outside dev mode", async () => {
-// class Root extends Component {
-// static template = xml``;
-// state = useState({ value: 1 });
-
-// setup() {
-// onMounted(() => {
-// throw "This is not an error";
-// });
-// }
-// }
-// const app = new App(Root);
-// let error: OwlError;
-// const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
-// await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
-// await mountProm;
-// expect(error!.message).toBe(
-// `An error occured in the owl lifecycle (see this Error's "cause" property)`
-// );
-// expect(error!.cause).toBe("This is not an error");
-// });
-
-// test("can catch an error in the initial call of a component render function (parent mounted)", async () => {
-// class ErrorComponent extends Component {
-// static template = xml`hey
`;
-// }
-// class ErrorBoundary extends Component {
-// static template = xml`
-//
-// Error handled
-//
-//
`;
-// state = useState({ error: false });
-
-// setup() {
-// onError(() => {
-// this.state.error = true;
-// });
-// }
-// }
-// class App extends Component {
-// static template = xml`
-//
-//
-//
`;
-// static components = { ErrorBoundary, ErrorComponent };
-// }
-// await mount(App, fixture);
-// expect(fixture.innerHTML).toBe("");
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("can catch an error in the initial call of a component render function (parent updated)", async () => {
-// class ErrorComponent extends Component {
-// static template = xml`hey
`;
-// }
-// class ErrorBoundary extends Component {
-// static template = xml`
-//
-// Error handled
-//
-//
`;
-// state = useState({ error: false });
-
-// setup() {
-// onError(() => (this.state.error = true));
-// }
-// }
-// class App extends Component {
-// static template = xml`
-//
-//
-//
`;
-// state = useState({ flag: false });
-// static components = { ErrorBoundary, ErrorComponent };
-// }
-// const app = await mount(App, fixture);
-// app.state.flag = true;
-// await nextTick();
-// expect(fixture.innerHTML).toBe("");
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("can catch an error in the constructor call of a component render function", async () => {
-// class ErrorComponent extends Component {
-// static template = xml`Some text
`;
-// setup() {
-// throw new Error("NOOOOO");
-// }
-// }
-// class ErrorBoundary extends Component {
-// static template = xml`
-// Error handled
-//
-//
`;
-// state = useState({ error: false });
-
-// setup() {
-// onError(() => (this.state.error = true));
-// }
-// }
-// class App extends Component {
-// static template = xml`
-//
-//
`;
-// static components = { ErrorBoundary, ErrorComponent };
-// }
-// await mount(App, fixture);
-// expect(fixture.innerHTML).toBe("");
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("can catch an error in the constructor call of a component render function 2", async () => {
-// class ClassicCompoent extends Component {
-// static template = xml`classic
`;
-// }
-
-// class ErrorComponent extends Component {
-// static template = xml`Some text
`;
-// setup() {
-// throw new Error("NOOOOO");
-// }
-// }
-// class ErrorBoundary extends Component {
-// static template = xml`
-// Error handled
-//
-//
`;
-// state = useState({ error: false });
-
-// setup() {
-// onError(() => (this.state.error = true));
-// }
-// }
-// class App extends Component {
-// static template = xml`
-//
-//
`;
-// static components = { ErrorBoundary, ErrorComponent, ClassicCompoent };
-// }
-// await mount(App, fixture);
-// expect(fixture.innerHTML).toBe("");
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("can catch an error in the willStart call", async () => {
-// class ErrorComponent extends Component {
-// static template = xml`Some text
`;
-// setup() {
-// onWillStart(async () => {
-// // we wait a little bit to be in a different stack frame
-// await nextTick();
-// throw new Error("NOOOOO");
-// });
-// }
-// }
-// class ErrorBoundary extends Component {
-// static template = xml`
-//
-// Error handled
-//
-//
`;
-// state = useState({ error: false });
-
-// setup() {
-// onError(() => (this.state.error = true));
-// }
-// }
-// class App extends Component {
-// static template = xml`
`;
-// static components = { ErrorBoundary, ErrorComponent };
-// }
-// await mount(App, fixture);
-// expect(fixture.innerHTML).toBe("");
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("can catch an error origination from a child's willStart function", async () => {
-// class ClassicCompoent extends Component {
-// static template = xml`classic
`;
-// }
-
-// class ErrorComponent extends Component {
-// static template = xml`Some text
`;
-// setup() {
-// onWillStart(() => {
-// throw new Error("NOOOOO");
-// });
-// }
-// }
-// class ErrorBoundary extends Component {
-// static template = xml`
-// Error handled
-//
-//
`;
-// state = useState({ error: false });
-
-// setup() {
-// onError(() => (this.state.error = true));
-// }
-// }
-// class App extends Component {
-// static template = xml`
-//
-//
`;
-// static components = { ErrorBoundary, ErrorComponent, ClassicCompoent };
-// }
-// await mount(App, fixture);
-// expect(fixture.innerHTML).toBe("");
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("can catch an error in the mounted call", async () => {
-// class ErrorComponent extends Component {
-// static template = xml`Some text
`;
-// setup() {
-// useLogLifecycle();
-// onMounted(() => {
-// logStep("boom");
-// throw new Error("NOOOOO");
-// });
-// }
-// }
-// class ErrorBoundary extends Component {
-// static template = xml`
-// Error handled
-//
-//
`;
-// state = useState({ error: false });
-
-// setup() {
-// useLogLifecycle();
-// onError(() => (this.state.error = true));
-// }
-// }
-// class Root extends Component {
-// static template = xml`
-//
-//
`;
-// static components = { ErrorBoundary, ErrorComponent };
-// setup() {
-// useLogLifecycle();
-// }
-// }
-// await mount(Root, fixture);
-// expect(fixture.innerHTML).toBe("");
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Root:setup",
-// "Root:willStart",
-// "Root:willRender",
-// "ErrorBoundary:setup",
-// "ErrorBoundary:willStart",
-// "Root:rendered",
-// "ErrorBoundary:willRender",
-// "ErrorComponent:setup",
-// "ErrorComponent:willStart",
-// "ErrorBoundary:rendered",
-// "ErrorComponent:willRender",
-// "ErrorComponent:rendered",
-// "ErrorComponent:mounted",
-// "boom",
-// "ErrorBoundary:willRender",
-// "ErrorBoundary:rendered",
-// "ErrorBoundary:mounted",
-// "Root:mounted",
-// ]
-// `);
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("can catch an error in the mounted call (in root component)", async () => {
-// class ErrorComponent extends Component {
-// static template = xml`Some text
`;
-// setup() {
-// useLogLifecycle();
-// onMounted(() => {
-// logStep("boom");
-// throw new Error("NOOOOO");
-// });
-// }
-// }
-// class Root extends Component {
-// static template = xml`
-// Error handled
-//
-//
`;
-// static components = { ErrorComponent };
-// state = useState({ error: false });
-
-// setup() {
-// useLogLifecycle();
-// onError(() => (this.state.error = true));
-// }
-// }
-// await mount(Root, fixture);
-// expect(fixture.innerHTML).toBe("Error handled
");
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Root:setup",
-// "Root:willStart",
-// "Root:willRender",
-// "ErrorComponent:setup",
-// "ErrorComponent:willStart",
-// "Root:rendered",
-// "ErrorComponent:willRender",
-// "ErrorComponent:rendered",
-// "ErrorComponent:mounted",
-// "boom",
-// "Root:willRender",
-// "Root:rendered",
-// "Root:mounted",
-// ]
-// `);
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("can catch an error in the mounted call (in child of child)", async () => {
-// class Boom extends Component {
-// static template = xml`Some text
`;
-// setup() {
-// useLogLifecycle();
-// onMounted(() => {
-// logStep("boom");
-// throw new Error("NOOOOO");
-// });
-// }
-// }
-
-// class C extends Component {
-// static template = xml`
-// Error handled
-//
-//
`;
-// static components = { Boom };
-// state = useState({ error: false });
-
-// setup() {
-// useLogLifecycle();
-// onError(() => (this.state.error = true));
-// }
-// }
-
-// class B extends Component {
-// static template = xml`
`;
-// static components = { C };
-// setup() {
-// useLogLifecycle();
-// }
-// }
-// class A extends Component {
-// static template = xml``;
-// static components = { B };
-// setup() {
-// useLogLifecycle();
-// }
-// }
-// await mount(A, fixture);
-// expect(fixture.innerHTML).toBe("");
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "A:setup",
-// "A:willStart",
-// "A:willRender",
-// "B:setup",
-// "B:willStart",
-// "A:rendered",
-// "B:willRender",
-// "C:setup",
-// "C:willStart",
-// "B:rendered",
-// "C:willRender",
-// "Boom:setup",
-// "Boom:willStart",
-// "C:rendered",
-// "Boom:willRender",
-// "Boom:rendered",
-// "Boom:mounted",
-// "boom",
-// "C:willRender",
-// "C:rendered",
-// "C:mounted",
-// "B:mounted",
-// "A:mounted",
-// ]
-// `);
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("error in mounted on a component with a sibling (properly mounted)", async () => {
-// class ErrorComponent extends Component {
-// static template = xml`Some text
`;
-// setup() {
-// useLogLifecycle();
-// onMounted(() => {
-// logStep("boom");
-// throw new Error("NOOOOO");
-// });
-// }
-// }
-// class ErrorBoundary extends Component {
-// static template = xml`
-// Error handled
-//
-//
`;
-// state = useState({ error: false });
-
-// setup() {
-// useLogLifecycle();
-// onError(() => (this.state.error = true));
-// }
-// }
-// class OK extends Component {
-// static template = xml`OK`;
-// setup() {
-// useLogLifecycle();
-// }
-// }
-
-// class Root extends Component {
-// static template = xml`
-//
-//
-//
`;
-// static components = { ErrorBoundary, ErrorComponent, OK };
-// setup() {
-// useLogLifecycle();
-// }
-// }
-// await mount(Root, fixture);
-// expect(fixture.innerHTML).toBe("");
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Root:setup",
-// "Root:willStart",
-// "Root:willRender",
-// "OK:setup",
-// "OK:willStart",
-// "ErrorBoundary:setup",
-// "ErrorBoundary:willStart",
-// "Root:rendered",
-// "OK:willRender",
-// "OK:rendered",
-// "ErrorBoundary:willRender",
-// "ErrorComponent:setup",
-// "ErrorComponent:willStart",
-// "ErrorBoundary:rendered",
-// "ErrorComponent:willRender",
-// "ErrorComponent:rendered",
-// "ErrorComponent:mounted",
-// "boom",
-// "ErrorBoundary:willRender",
-// "ErrorBoundary:rendered",
-// "ErrorBoundary:mounted",
-// "OK:mounted",
-// "Root:mounted",
-// ]
-// `);
-// expect(mockConsoleError).toBeCalledTimes(0);
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("can catch an error in the willPatch call", async () => {
-// class ErrorComponent extends Component {
-// static template = xml`
`;
-// setup() {
-// onWillPatch(() => {
-// throw new Error("NOOOOO");
-// });
-// }
-// }
-// class ErrorBoundary extends Component {
-// static template = xml`
-//
-// Error handled
-//
-//
`;
-// state = useState({ error: false });
-
-// setup() {
-// onError(() => (this.state.error = true));
-// }
-// }
-// class App extends Component {
-// static template = xml`
-//
-//
-//
-//
`;
-// state = useState({ message: "abc" });
-// static components = { ErrorBoundary, ErrorComponent };
-// }
-// const app = await mount(App, fixture);
-// expect(fixture.innerHTML).toBe("");
-// app.state.message = "def";
-// await nextTick();
-// await nextTick();
-// await nextTick();
-// expect(fixture.innerHTML).toBe("");
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("catchError in catchError", async () => {
-// class Boom extends Component {
-// static template = xml``;
-// }
-
-// class Child extends Component {
-// static template = xml`
-//
-//
-//
`;
-// static components = { Boom };
-
-// setup() {
-// onError((error) => {
-// throw error;
-// });
-// }
-// }
-
-// class Parent extends Component {
-// static template = xml`
-//
-// Error
-//
-//
-//
-//
`;
-// static components = { Child };
-
-// error: any = false;
-
-// setup() {
-// onError((error) => {
-// this.error = error;
-// this.render();
-// });
-// }
-// }
-
-// await mount(Parent, fixture);
-// expect(fixture.innerHTML).toBe("Error
");
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("onError in class inheritance is not called if no rethrown", async () => {
-// const steps: string[] = [];
-
-// class Abstract extends Component {
-// static template = xml`
-//
-//
-//
-//
-//
-//
-//
`;
-// state: any;
-// setup() {
-// this.state = useState({});
-// onError(() => {
-// steps.push("Abstract onError");
-// this.state.error = "Abstract";
-// });
-// }
-// }
-
-// class Concrete extends Abstract {
-// setup() {
-// super.setup();
-// onError(() => {
-// steps.push("Concrete onError");
-// this.state.error = "Concrete";
-// });
-// }
-// }
-
-// class Parent extends Component {
-// static components = { Concrete };
-// static template = xml``;
-// }
-
-// await mount(Parent, fixture);
-
-// expect(steps).toStrictEqual(["Concrete onError"]);
-// expect(fixture.innerHTML).toBe("Concrete
");
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("onError in class inheritance is called if rethrown", async () => {
-// const steps: string[] = [];
-
-// class Abstract extends Component {
-// static template = xml`
-//
-//
-//
-//
-//
-//
-//
`;
-// state: any;
-// setup() {
-// this.state = useState({});
-// onError(() => {
-// steps.push("Abstract onError");
-// this.state.error = "Abstract";
-// });
-// }
-// }
-
-// class Concrete extends Abstract {
-// setup() {
-// super.setup();
-// onError((error) => {
-// steps.push("Concrete onError");
-// this.state.error = "Concrete";
-// throw error;
-// });
-// }
-// }
-
-// class Parent extends Component {
-// static components = { Concrete };
-// static template = xml``;
-// }
-
-// await mount(Parent, fixture);
-
-// expect(steps).toStrictEqual(["Concrete onError", "Abstract onError"]);
-// expect(fixture.innerHTML).toBe("Abstract
");
-// expect(mockConsoleWarn).toBeCalledTimes(0);
-// });
-
-// test("catching error, rethrow, render parent -- a main component loop implementation", async () => {
-// let parentState: any;
-
-// class ErrorComponent extends Component {
-// static template = xml``;
-// setup() {
-// throw new Error("My Error");
-// }
-// }
-
-// class Child extends Component {
-// static template = xml``;
-// static components = { ErrorComponent };
-// setup() {
-// onError((error) => {
-// throw error;
-// });
-// }
-// }
-
-// class Sibling extends Component {
-// static template = xml`Sibling
`;
-// }
-
-// class ErrorHandler extends Component {
-// static template = xml``;
-// setup() {
-// onError(() => {
-// this.props.onError();
-// Promise.resolve().then(() => {
-// parentState.cps[2] = {
-// id: 2,
-// Comp: Sibling,
-// };
-// });
-// });
-// }
-// }
-
-// class Parent extends Component {
-// static template = xml`
-//
-//
-//
-//
-// `;
-
-// static components = { ErrorHandler };
-// state: any = useState({
-// cps: {},
-// });
-
-// setup() {
-// parentState = this.state;
-// }
-
-// cleanUp(id: number) {
-// delete this.state.cps[id];
-// }
-// }
-
-// await mount(Parent, fixture);
-// parentState.cps[1] = { id: 1, Comp: Child };
-// await nextMicroTick();
-// expect(fixture.innerHTML).toBe("");
-// await nextTick();
-// expect(fixture.innerHTML).toBe("Sibling
");
-// });
-
-// test("catching in child makes parent render", async () => {
-// class Child extends Component {
-// static template = xml``;
-// }
-
-// class ErrorComp extends Component {
-// static template = xml``;
-// setup() {
-// throw new Error("Error Component");
-// }
-// }
-
-// class Catch extends Component {
-// static template = xml``;
-// setup() {
-// onError(({ cause }) => {
-// this.props.onError(cause);
-// });
-// }
-// }
-
-// const steps: any[] = [];
-// class Parent extends Component {
-// static components = { Catch };
-// static template = xml`
-//
-//
-//
-//
-//
-// `;
-
-// elements: any = {};
-
-// onError(id: any, error: Error) {
-// steps.push(error.message);
-// delete this.elements[id];
-// this.elements[2] = Child;
-// this.render();
-// }
-// }
-
-// const parent = await mount(Parent, fixture);
-// expect(fixture.innerHTML).toBe("");
-
-// parent.elements[1] = ErrorComp;
-// parent.render();
-// await nextTick();
-// expect(fixture.innerHTML).toBe("Child 2
");
-// expect(steps).toEqual(["Error Component"]);
-// });
-
-// test("an error in onWillDestroy", async () => {
-// class Child extends Component {
-// static template = xml`abc
`;
-// setup() {
-// useLogLifecycle();
-// onWillDestroy(() => {
-// throw new Error("boom");
-// });
-// }
-// }
-
-// class Parent extends Component {
-// static template = xml`
-//
-// `;
-// static components = { Child };
-
-// state = useState({ value: 1, hasChild: true });
-// setup() {
-// useLogLifecycle();
-// onError(() => {
-// this.state.value++;
-// });
-// }
-// }
-
-// const parent = await mount(Parent, fixture);
-// expect(fixture.innerHTML).toBe("1abc
");
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Parent:setup",
-// "Parent:willStart",
-// "Parent:willRender",
-// "Child:setup",
-// "Child:willStart",
-// "Parent:rendered",
-// "Child:willRender",
-// "Child:rendered",
-// "Child:mounted",
-// "Parent:mounted",
-// ]
-// `);
-// parent.state.hasChild = false;
-// await nextTick();
-// await nextTick();
-// await nextTick();
-// await nextTick();
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Parent:willRender",
-// "Parent:rendered",
-// "Parent:willPatch",
-// "Child:willUnmount",
-// "Child:willDestroy",
-// "Parent:patched",
-// "Parent:willRender",
-// "Parent:rendered",
-// "Parent:willPatch",
-// "Parent:patched",
-// ]
-// `);
-// expect(fixture.innerHTML).toBe("2");
-// });
-
-// test("an error in onWillDestroy, variation", async () => {
-// class Child extends Component {
-// static template = xml`abc
`;
-// setup() {
-// useLogLifecycle();
-// onWillDestroy(() => {
-// throw new Error("boom");
-// });
-// }
-// }
-
-// class Parent extends Component {
-// static template = xml`
-//
-// `;
-// static components = { Child };
-
-// state = useState({ value: 1, hasChild: false });
-// setup() {
-// useLogLifecycle();
-// onError(() => {
-// this.state.value++;
-// });
-// }
-// }
-
-// const parent = await mount(Parent, fixture);
-// expect(fixture.innerHTML).toBe("1");
-
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Parent:setup",
-// "Parent:willStart",
-// "Parent:willRender",
-// "Parent:rendered",
-// "Parent:mounted",
-// ]
-// `);
-
-// parent.state.hasChild = true;
-// await nextMicroTick();
-// await nextMicroTick();
-// await nextMicroTick();
-// await nextMicroTick();
-// await nextMicroTick();
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Parent:willRender",
-// "Child:setup",
-// "Child:willStart",
-// "Parent:rendered",
-// "Child:willRender",
-// "Child:rendered",
-// ]
-// `);
-// parent.state.hasChild = false;
-// await nextTick();
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Parent:willRender",
-// "Parent:rendered",
-// "Child:willDestroy",
-// "Parent:willRender",
-// "Parent:rendered",
-// ]
-// `);
-// expect(fixture.innerHTML).toBe("1");
-// await nextTick();
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Parent:willPatch",
-// "Parent:patched",
-// ]
-// `);
-// expect(fixture.innerHTML).toBe("2");
-// });
-
-// test("error in onMounted, graceful recovery", async () => {
-// class Child extends Component {
-// static template = xml`abc`;
-// setup() {
-// useLogLifecycle();
-// }
-// }
-
-// class OtherChild extends Component {
-// static template = xml`def`;
-// setup() {
-// useLogLifecycle();
-// }
-// }
-
-// class Boom extends Component {
-// static template = xml`boom`;
-// setup() {
-// useLogLifecycle();
-// onMounted(() => {
-// throw new Error("boom");
-// });
-// }
-// }
-
-// class Parent extends Component {
-// static template = xml`parent`;
-// static components = { Child, Boom };
-// setup() {
-// useLogLifecycle();
-// }
-// }
-
-// class Root extends Component {
-// static template = xml``;
-
-// component: any = Parent;
-// setup() {
-// useLogLifecycle();
-// onError(() => {
-// logStep("error");
-// this.component = OtherChild;
-// this.render();
-// });
-// }
-// }
-
-// await mount(Root, fixture);
-// expect(fixture.innerHTML).toBe("def");
-
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Root:setup",
-// "Root:willStart",
-// "Root:willRender",
-// "Parent:setup",
-// "Parent:willStart",
-// "Root:rendered",
-// "Parent:willRender",
-// "Child:setup",
-// "Child:willStart",
-// "Boom:setup",
-// "Boom:willStart",
-// "Parent:rendered",
-// "Child:willRender",
-// "Child:rendered",
-// "Boom:willRender",
-// "Boom:rendered",
-// "Boom:mounted",
-// "error",
-// "Root:willRender",
-// "OtherChild:setup",
-// "OtherChild:willStart",
-// "Root:rendered",
-// "OtherChild:willRender",
-// "OtherChild:rendered",
-// "OtherChild:mounted",
-// "Root:mounted",
-// ]
-// `);
-// });
-
-// test("error in onMounted, graceful recovery, variation", async () => {
-// class Child extends Component {
-// static template = xml`abc`;
-// setup() {
-// useLogLifecycle();
-// }
-// }
-
-// class OtherChild extends Component {
-// static template = xml`def`;
-// setup() {
-// useLogLifecycle();
-// }
-// }
-
-// class Boom extends Component {
-// static template = xml`boom`;
-// setup() {
-// useLogLifecycle();
-// onMounted(() => {
-// throw new Error("boom");
-// });
-// }
-// }
-
-// class Parent extends Component {
-// static template = xml`parent`;
-// static components = { Child, Boom };
-// setup() {
-// useLogLifecycle();
-// }
-// }
-
-// class Root extends Component {
-// static template = xml`R`;
-
-// component: any = Parent;
-// state = useState({ gogogo: false });
-
-// setup() {
-// useLogLifecycle();
-// onError(() => {
-// logStep("error");
-// this.component = OtherChild;
-// this.render();
-// });
-// }
-// }
-
-// const root = await mount(Root, fixture);
-// expect(fixture.innerHTML).toBe("R");
-
-// // standard mounting process
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Root:setup",
-// "Root:willStart",
-// "Root:willRender",
-// "Root:rendered",
-// "Root:mounted",
-// ]
-// `);
-
-// root.state.gogogo = true;
-// await nextTick();
-
-// expect(fixture.innerHTML).toBe("Rparentabcboom");
-// // rerender, root creates sub components, it crashes, tries to recover
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "Root:willRender",
-// "Parent:setup",
-// "Parent:willStart",
-// "Root:rendered",
-// "Parent:willRender",
-// "Child:setup",
-// "Child:willStart",
-// "Boom:setup",
-// "Boom:willStart",
-// "Parent:rendered",
-// "Child:willRender",
-// "Child:rendered",
-// "Boom:willRender",
-// "Boom:rendered",
-// "Root:willPatch",
-// "Boom:mounted",
-// "error",
-// "Root:willRender",
-// "OtherChild:setup",
-// "OtherChild:willStart",
-// "Root:rendered",
-// ]
-// `);
-
-// await nextTick();
-// expect(fixture.innerHTML).toBe("Rdef");
-
-// expect(steps.splice(0)).toMatchInlineSnapshot(`
-// Array [
-// "OtherChild:willRender",
-// "OtherChild:rendered",
-// "Root:willPatch",
-// "Child:willDestroy",
-// "Boom:willUnmount",
-// "Boom:willDestroy",
-// "Parent:willDestroy",
-// "OtherChild:mounted",
-// "Root:patched",
-// ]
-// `);
-// });
-// });
+import { App, Component, mount, onWillDestroy } from "../../src";
+import {
+ onError,
+ onMounted,
+ onPatched,
+ onWillPatch,
+ onWillStart,
+ onWillRender,
+ onRendered,
+ onWillUnmount,
+ useState,
+ xml,
+} from "../../src/index";
+import {
+ logStep,
+ makeTestFixture,
+ nextTick,
+ nextMicroTick,
+ snapshotEverything,
+ useLogLifecycle,
+ nextAppError,
+ steps,
+} from "../helpers";
+import { OwlError } from "../../src/common/owl_error";
+
+let fixture: HTMLElement;
+
+snapshotEverything();
+
+let originalconsoleError = console.error;
+let mockConsoleError: any;
+let originalconsoleWarn = console.warn;
+let mockConsoleWarn: any;
+
+beforeEach(() => {
+ fixture = makeTestFixture();
+ mockConsoleError = jest.fn(() => {});
+ mockConsoleWarn = jest.fn(() => {});
+ console.error = mockConsoleError;
+ console.warn = mockConsoleWarn;
+});
+
+afterEach(() => {
+ console.error = originalconsoleError;
+ console.warn = originalconsoleWarn;
+});
+
+describe("basics", () => {
+ test("no component catching error lead to full app destruction", async () => {
+ class ErrorComponent extends Component {
+ static template = xml`hey
`;
+ }
+
+ class Parent extends Component {
+ static template = xml`
`;
+ static components = { ErrorComponent };
+ state = { flag: false };
+ }
+ const parent = await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe("");
+ parent.state.flag = true;
+
+ parent.render();
+ await expect(nextAppError(parent.__owl__.app)).resolves.toThrow(
+ "An error occured in the owl lifecycle"
+ );
+ expect(fixture.innerHTML).toBe("");
+ expect(mockConsoleWarn).toBeCalledTimes(1);
+ });
+
+ test("display a nice error if it cannot find component", async () => {
+ class SomeComponent extends Component {}
+ class Parent extends Component {
+ static template = xml``;
+ static components = { SomeComponent };
+ }
+ const app = new App(Parent);
+ let error: Error;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow(
+ 'Cannot find the definition of component "SomeMispelledComponent"'
+ );
+ await mountProm;
+ expect(error!).toBeDefined();
+ expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
+ expect(console.error).toBeCalledTimes(0);
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(1);
+ });
+
+ test("display a nice error if it cannot find component (in dev mode)", async () => {
+ class SomeComponent extends Component {}
+ class Parent extends Component {
+ static template = xml``;
+ static components = { SomeComponent };
+ }
+ const app = new App(Parent, { test: true });
+ let error: Error;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow(
+ 'Cannot find the definition of component "SomeMispelledComponent"'
+ );
+ await mountProm;
+ expect(error!).toBeDefined();
+ expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
+ expect(console.error).toBeCalledTimes(0);
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(1);
+ });
+
+ test("display a nice error if a component is not a component", async () => {
+ function notAComponentConstructor() {}
+ class Parent extends Component {
+ static template = xml``;
+ static components = { SomeComponent: notAComponentConstructor };
+ }
+ const app = new App(Parent as typeof Component);
+ let error: Error;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow(
+ '"SomeComponent" is not a Component. It must inherit from the Component class'
+ );
+ await mountProm;
+ expect(error!).toBeDefined();
+ expect(error!.message).toBe(
+ '"SomeComponent" is not a Component. It must inherit from the Component class'
+ );
+ });
+
+ test("display a nice error if the components key is missing with subcomponents", async () => {
+ class Parent extends Component {
+ static template = xml`
`;
+ }
+ const app = new App(Parent as typeof Component);
+ let error: Error;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow(
+ 'Cannot find the definition of component "MissingChild", missing static components key in parent'
+ );
+ await mountProm;
+ expect(error!).toBeDefined();
+ expect(error!.message).toBe(
+ 'Cannot find the definition of component "MissingChild", missing static components key in parent'
+ );
+ });
+
+ test("display a nice error if the root component template fails to compile", async () => {
+ // This is a special case: mount throws synchronously and we don't have any
+ // node which can handle the error, hence the different structure of this test
+ class Comp extends Component {
+ static template = xml`test
`;
+ }
+ const app = new App(Comp);
+ let error: Error;
+ try {
+ await app.mount(fixture);
+ } catch (e) {
+ error = e as Error;
+ }
+ const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier 'ctx'
+
+generated code:
+function(app, bdom, helpers) {
+ let { text, createBlock, list, multi, html, toggler, comment } = bdom;
+
+ let block1 = createBlock(\`test
\`);
+
+ return function template(ctx, node, key = "") {
+ let attr1 = ctx['a']ctx['b'];
+ return block1([attr1]);
+ }
+}`;
+ expect(error!).toBeDefined();
+ expect(error!.message).toBe(expectedErrorMessage);
+ });
+
+ test("display a nice error if a non-root component template fails to compile", async () => {
+ class Child extends Component {
+ static template = xml`test
`;
+ }
+ class Parent extends Component {
+ static components = { Child };
+ static template = xml``;
+ }
+ const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier 'ctx'
+
+generated code:
+function(app, bdom, helpers) {
+ let { text, createBlock, list, multi, html, toggler, comment } = bdom;
+
+ let block1 = createBlock(\`test
\`);
+
+ return function template(ctx, node, key = "") {
+ let attr1 = ctx['a']ctx['b'];
+ return block1([attr1]);
+ }
+}`;
+ const app = new App(Parent as typeof Component);
+ let error: Error;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow(expectedErrorMessage);
+ await mountProm;
+ expect(error!).toBeDefined();
+ expect(error!.message).toBe(expectedErrorMessage);
+ });
+
+ test("simple catchError", async () => {
+ class Boom extends Component {
+ static template = xml``;
+ }
+
+ class Parent extends Component {
+ static template = xml`
+
+ Error
+
+
+
+
`;
+ static components = { Boom };
+
+ error: any = false;
+
+ setup() {
+ onError((err) => {
+ this.error = err;
+ this.render();
+ });
+ }
+ }
+ await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe("Error
");
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+});
+
+describe("errors and promises", () => {
+ test("a rendering error will reject the mount promise", async () => {
+ // we do not catch error in willPatch anymore
+ class Root extends Component {
+ static template = xml`
`;
+ }
+
+ const app = new App(Root);
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
+ await mountProm;
+ expect(error!).toBeDefined();
+ expect(error!.cause).toBeDefined();
+ const regexp =
+ /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
+ expect(error!.cause.message).toMatch(regexp);
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleError).toBeCalledTimes(0);
+ });
+
+ test("an error in mounted call will reject the mount promise", async () => {
+ class Root extends Component {
+ static template = xml`abc
`;
+ setup() {
+ onMounted(() => {
+ throw new Error("boom");
+ });
+ }
+ }
+
+ const app = new App(Root);
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
+ await mountProm;
+ expect(error!).toBeDefined();
+ expect(error!.cause).toBeDefined();
+ expect(error!.cause.message).toBe("boom");
+ expect(fixture.innerHTML).toBe("");
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(1);
+ });
+
+ test("an error in onMounted callback will have the component's setup in its stack trace", async () => {
+ class Root extends Component {
+ static template = xml`abc
`;
+ setup() {
+ onMounted(() => {
+ throw new Error("boom");
+ });
+ }
+ }
+
+ const app = new App(Root, { test: true });
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted");
+ await mountProm;
+ expect(error!).toBeDefined();
+ expect(error!.stack).toContain("Root.setup");
+ expect(error!.stack).toContain("error_handling.test.ts");
+ expect(fixture.innerHTML).toBe("");
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(1);
+ });
+
+ test("errors in onWillRender/onRender aren't wrapped more than once", async () => {
+ class Root extends Component {
+ static template = xml`abc
`;
+ setup() {
+ onWillRender(() => {
+ throw new Error("boom in onWillRender");
+ });
+ onRendered(() => {
+ throw new Error("boom in onRendered");
+ });
+ }
+ }
+
+ const app = new App(Root, { test: true });
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillRender");
+ await mountProm;
+ expect(error!).toBeDefined();
+ expect(error!.message).toBe(
+ `The following error occurred in onWillRender: "boom in onWillRender"`
+ );
+ });
+
+ test("error while rendering component isn't wrapped by onWillRender/onRendered", async () => {
+ class App extends Component {
+ static template = xml`abc
`;
+ setup() {
+ onWillRender(() => {});
+ onRendered(() => {});
+ }
+ }
+
+ let error: Error;
+ try {
+ await mount(App, fixture, { test: true });
+ } catch (e) {
+ error = e as Error;
+ }
+ expect(error!).toBeDefined();
+ expect(error!.message).toBe("Tokenizer error: could not tokenize `{ 'invalid: 5 }`");
+ });
+
+ test("wrapped errors in async code are correctly caught", async () => {
+ class Root extends Component {
+ static template = xml`abc
`;
+ setup() {
+ onWillStart(async () => {
+ await Promise.resolve();
+ throw new Error("boom in onWillStart");
+ });
+ }
+ }
+
+ const app = new App(Root, { test: true });
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart");
+ await mountProm;
+ expect(error!).toBeDefined();
+ expect(error!.message).toBe(
+ `The following error occurred in onWillStart: "boom in onWillStart"`
+ );
+ await new Promise((r) => setTimeout(r, 0)); // wait for the rejection event to bubble
+ });
+
+ test("an error in willPatch call will reject the render promise", async () => {
+ class Root extends Component {
+ static template = xml`
`;
+ val = 3;
+ setup() {
+ onWillPatch(() => {
+ throw new Error("boom");
+ });
+ onError((e) => (error = e));
+ }
+ }
+
+ const root = await mount(Root, fixture, { test: true });
+ root.val = 4;
+ let error: Error;
+ root.render();
+ await nextTick();
+ expect(error!).toBeDefined();
+ expect(error!.message).toBe(`The following error occurred in onWillPatch: "boom"`);
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("an error in patched call will reject the render promise", async () => {
+ class Root extends Component {
+ static template = xml`
`;
+ val = 3;
+ setup() {
+ onPatched(() => {
+ throw new Error("boom");
+ });
+ onError((e) => (error = e));
+ }
+ }
+
+ const root = await mount(Root, fixture, { test: true });
+ root.val = 4;
+ let error: Error;
+ root.render();
+ await nextTick();
+ expect(error!).toBeDefined();
+ expect(error!.message).toBe(`The following error occurred in onPatched: "boom"`);
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("a rendering error in a sub component will reject the mount promise", async () => {
+ // we do not catch error in willPatch anymore
+ class Child extends Component {
+ static template = xml`
`;
+ }
+ class Parent extends Component {
+ static template = xml`
`;
+ static components = { Child };
+ }
+
+ const app = new App(Parent);
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
+ await mountProm;
+ expect(error!).toBeDefined();
+ expect(error!.cause).toBeDefined();
+ const regexp =
+ /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
+ expect(error!.cause.message).toMatch(regexp);
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(1);
+ });
+
+ test("a rendering error will reject the render promise", async () => {
+ class Root extends Component {
+ static template = xml`
`;
+ flag = false;
+ setup() {
+ onError(({ cause }) => (error = cause));
+ }
+ }
+
+ const root = await mount(Root, fixture);
+ expect(fixture.innerHTML).toBe("");
+ root.flag = true;
+ let error: Error;
+ root.render();
+ await nextTick();
+ expect(error!).toBeDefined();
+ const regexp =
+ /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
+ expect(error!.message).toMatch(regexp);
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("a rendering error will reject the render promise (with sub components)", async () => {
+ class Child extends Component {
+ static template = xml``;
+ }
+ class Parent extends Component {
+ static template = xml`
`;
+ static components = { Child };
+ }
+
+ const app = new App(Parent);
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
+ await mountProm;
+ expect(error!).toBeDefined();
+ expect(error!.cause).toBeDefined();
+ const regexp =
+ /Cannot read properties of undefined \(reading 'y'\)|Cannot read property 'y' of undefined/g;
+ expect(error!.cause.message).toMatch(regexp);
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(1);
+ });
+
+ test("errors in mounted and in willUnmount", async () => {
+ class Example extends Component {
+ static template = xml``;
+ val: any;
+ setup() {
+ onMounted(() => {
+ throw new Error("Error in mounted");
+ this.val = { foo: "bar" };
+ });
+
+ onWillUnmount(() => {
+ console.log(this.val.foo);
+ });
+ }
+ }
+
+ const app = new App(Example, { test: true });
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted");
+ await mountProm;
+ expect(error!.message).toBe(`The following error occurred in onMounted: "Error in mounted"`);
+ // 1 additional error is logged because the destruction of the app causes
+ // the onWillUnmount hook to be called and to fail
+ expect(mockConsoleError).toBeCalledTimes(1);
+ expect(mockConsoleWarn).toBeCalledTimes(1);
+ });
+
+ test("errors in rerender", async () => {
+ class Example extends Component {
+ static template = xml``;
+ state: any = { a: { b: 1 } };
+ }
+ const root = await mount(Example, fixture);
+ expect(fixture.innerHTML).toBe("1
");
+
+ root.state = "boom";
+ root.render();
+ await expect(nextAppError(root.__owl__.app)).resolves.toThrow(
+ "error occured in the owl lifecycle"
+ );
+ expect(fixture.innerHTML).toBe("");
+ expect(mockConsoleWarn).toBeCalledTimes(1);
+ });
+});
+
+describe("can catch errors", () => {
+ test("can catch an error in a component render function", async () => {
+ class ErrorComponent extends Component {
+ static template = xml`hey
`;
+ }
+ class ErrorBoundary extends Component {
+ static template = xml`
+
+ Error handled
+
+
`;
+ state = useState({ error: false });
+
+ setup() {
+ onError(() => (this.state.error = true));
+ }
+ }
+ class App extends Component {
+ static template = xml`
+
+
+
`;
+ state = useState({ flag: false });
+ static components = { ErrorBoundary, ErrorComponent };
+ }
+ const app = await mount(App, fixture);
+ expect(fixture.innerHTML).toBe("");
+ app.state.flag = true;
+ await nextTick();
+ expect(fixture.innerHTML).toBe("");
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("can catch an error in onmounted", async () => {
+ class ErrorComponent extends Component {
+ static template = xml`Error!!!
`;
+ setup() {
+ useLogLifecycle();
+ onMounted(() => {
+ throw new Error("error");
+ });
+ }
+ }
+ class PerfectComponent extends Component {
+ static template = xml`perfect
`;
+ setup() {
+ useLogLifecycle();
+ }
+ }
+ class Main extends Component {
+ static template = xml`Main`;
+ component: any;
+ state: any;
+ setup() {
+ this.state = useState({ ok: false });
+ useLogLifecycle();
+ this.component = ErrorComponent;
+ onError(() => {
+ this.component = PerfectComponent;
+ this.render();
+ });
+ }
+ }
+
+ const app = await mount(Main, fixture);
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Main:setup",
+ "Main:willStart",
+ "Main:willRender",
+ "Main:rendered",
+ "Main:mounted",
+ ]
+ `);
+ expect(fixture.innerHTML).toBe("Main");
+ (app as any).state.ok = true;
+ await nextTick();
+ expect(fixture.innerHTML).toBe("MainError!!!
");
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Main:willRender",
+ "ErrorComponent:setup",
+ "ErrorComponent:willStart",
+ "Main:rendered",
+ "ErrorComponent:willRender",
+ "ErrorComponent:rendered",
+ "Main:willPatch",
+ "ErrorComponent:mounted",
+ "Main:willRender",
+ "PerfectComponent:setup",
+ "PerfectComponent:willStart",
+ "Main:rendered",
+ ]
+ `);
+ await nextTick();
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "PerfectComponent:willRender",
+ "PerfectComponent:rendered",
+ "Main:willPatch",
+ "ErrorComponent:willUnmount",
+ "ErrorComponent:willDestroy",
+ "PerfectComponent:mounted",
+ "Main:patched",
+ ]
+ `);
+ expect(fixture.innerHTML).toBe("Mainperfect
");
+ });
+
+ test("calling a hook outside setup should crash", async () => {
+ class Root extends Component {
+ static template = xml``;
+ state = useState({ value: 1 });
+
+ setup() {
+ onWillStart(() => {
+ this.state = useState({ value: 2 });
+ });
+ }
+ }
+ const app = new App(Root, { test: true });
+ let error: OwlError;
+ const crashProm = expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart");
+ await app.mount(fixture).catch((e: Error) => (error = e));
+ await crashProm;
+ expect(error!.message).toBe(
+ `The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"`
+ );
+ });
+
+ test("Errors have the right cause", async () => {
+ const err = new Error("test error");
+ class Root extends Component {
+ static template = xml``;
+ state = useState({ value: 1 });
+
+ setup() {
+ onMounted(() => {
+ throw err;
+ });
+ }
+ }
+ const app = new App(Root, { test: true });
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted");
+ await mountProm;
+ expect(error!.message).toBe(`The following error occurred in onMounted: "test error"`);
+ expect(error!.cause).toBe(err);
+ });
+
+ test("Errors in owl lifecycle are wrapped in dev mode: async hook", async () => {
+ const err = new Error("test error");
+ class Root extends Component {
+ static template = xml``;
+ state = useState({ value: 1 });
+
+ setup() {
+ onWillStart(async () => {
+ await nextMicroTick();
+ throw err;
+ });
+ }
+ }
+ const app = new App(Root, { test: true });
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart");
+ await mountProm;
+ expect(error!.message).toBe(`The following error occurred in onWillStart: "test error"`);
+ expect(error!.cause).toBe(err);
+ });
+
+ test("Errors in owl lifecycle are wrapped outside dev mode: sync hook", async () => {
+ const err = new Error("test error");
+ class Root extends Component {
+ static template = xml``;
+ state = useState({ value: 1 });
+
+ setup() {
+ onMounted(() => {
+ throw err;
+ });
+ }
+ }
+ const app = new App(Root);
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
+ await mountProm;
+ expect(error!.message).toBe(
+ `An error occured in the owl lifecycle (see this Error's "cause" property)`
+ );
+ expect(error!.cause).toBe(err);
+ });
+
+ test("Errors in owl lifecycle are wrapped out of dev mode: async hook", async () => {
+ const err = new Error("test error");
+ class Root extends Component {
+ static template = xml``;
+ state = useState({ value: 1 });
+
+ setup() {
+ onWillStart(async () => {
+ await nextMicroTick();
+ throw err;
+ });
+ }
+ }
+ const app = new App(Root);
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
+ await mountProm;
+ expect(error!.message).toBe(
+ `An error occured in the owl lifecycle (see this Error's "cause" property)`
+ );
+ expect(error!.cause).toBe(err);
+ });
+
+ test("Thrown values that are not errors are wrapped in dev mode", async () => {
+ class Root extends Component {
+ static template = xml``;
+ state = useState({ value: 1 });
+
+ setup() {
+ onMounted(() => {
+ throw "This is not an error";
+ });
+ }
+ }
+ const app = new App(Root, { test: true });
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("not an Error was thrown in onMounted");
+ await mountProm;
+ expect(error!.message).toBe(
+ `Something that is not an Error was thrown in onMounted (see this Error's "cause" property)`
+ );
+ expect(error!.cause).toBe("This is not an error");
+ });
+
+ test("Thrown values that are not errors are wrapped outside dev mode", async () => {
+ class Root extends Component {
+ static template = xml``;
+ state = useState({ value: 1 });
+
+ setup() {
+ onMounted(() => {
+ throw "This is not an error";
+ });
+ }
+ }
+ const app = new App(Root);
+ let error: OwlError;
+ const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
+ await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
+ await mountProm;
+ expect(error!.message).toBe(
+ `An error occured in the owl lifecycle (see this Error's "cause" property)`
+ );
+ expect(error!.cause).toBe("This is not an error");
+ });
+
+ test("can catch an error in the initial call of a component render function (parent mounted)", async () => {
+ class ErrorComponent extends Component {
+ static template = xml`hey
`;
+ }
+ class ErrorBoundary extends Component {
+ static template = xml`
+
+ Error handled
+
+
`;
+ state = useState({ error: false });
+
+ setup() {
+ onError(() => {
+ this.state.error = true;
+ });
+ }
+ }
+ class App extends Component {
+ static template = xml`
+
+
+
`;
+ static components = { ErrorBoundary, ErrorComponent };
+ }
+ await mount(App, fixture);
+ expect(fixture.innerHTML).toBe("");
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("can catch an error in the initial call of a component render function (parent updated)", async () => {
+ class ErrorComponent extends Component {
+ static template = xml`hey
`;
+ }
+ class ErrorBoundary extends Component {
+ static template = xml`
+
+ Error handled
+
+
`;
+ state = useState({ error: false });
+
+ setup() {
+ onError(() => (this.state.error = true));
+ }
+ }
+ class App extends Component {
+ static template = xml`
+
+
+
`;
+ state = useState({ flag: false });
+ static components = { ErrorBoundary, ErrorComponent };
+ }
+ const app = await mount(App, fixture);
+ app.state.flag = true;
+ await nextTick();
+ expect(fixture.innerHTML).toBe("");
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("can catch an error in the constructor call of a component render function", async () => {
+ class ErrorComponent extends Component {
+ static template = xml`Some text
`;
+ setup() {
+ throw new Error("NOOOOO");
+ }
+ }
+ class ErrorBoundary extends Component {
+ static template = xml`
+ Error handled
+
+
`;
+ state = useState({ error: false });
+
+ setup() {
+ onError(() => (this.state.error = true));
+ }
+ }
+ class App extends Component {
+ static template = xml`
+
+
`;
+ static components = { ErrorBoundary, ErrorComponent };
+ }
+ await mount(App, fixture);
+ expect(fixture.innerHTML).toBe("");
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("can catch an error in the constructor call of a component render function 2", async () => {
+ class ClassicCompoent extends Component {
+ static template = xml`classic
`;
+ }
+
+ class ErrorComponent extends Component {
+ static template = xml`Some text
`;
+ setup() {
+ throw new Error("NOOOOO");
+ }
+ }
+ class ErrorBoundary extends Component {
+ static template = xml`
+ Error handled
+
+
`;
+ state = useState({ error: false });
+
+ setup() {
+ onError(() => (this.state.error = true));
+ }
+ }
+ class App extends Component {
+ static template = xml`
+
+
`;
+ static components = { ErrorBoundary, ErrorComponent, ClassicCompoent };
+ }
+ await mount(App, fixture);
+ expect(fixture.innerHTML).toBe("");
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("can catch an error in the willStart call", async () => {
+ class ErrorComponent extends Component {
+ static template = xml`Some text
`;
+ setup() {
+ onWillStart(async () => {
+ // we wait a little bit to be in a different stack frame
+ await nextTick();
+ throw new Error("NOOOOO");
+ });
+ }
+ }
+ class ErrorBoundary extends Component {
+ static template = xml`
+
+ Error handled
+
+
`;
+ state = useState({ error: false });
+
+ setup() {
+ onError(() => (this.state.error = true));
+ }
+ }
+ class App extends Component {
+ static template = xml`
`;
+ static components = { ErrorBoundary, ErrorComponent };
+ }
+ await mount(App, fixture);
+ expect(fixture.innerHTML).toBe("");
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("can catch an error origination from a child's willStart function", async () => {
+ class ClassicCompoent extends Component {
+ static template = xml`classic
`;
+ }
+
+ class ErrorComponent extends Component {
+ static template = xml`Some text
`;
+ setup() {
+ onWillStart(() => {
+ throw new Error("NOOOOO");
+ });
+ }
+ }
+ class ErrorBoundary extends Component {
+ static template = xml`
+ Error handled
+
+
`;
+ state = useState({ error: false });
+
+ setup() {
+ onError(() => (this.state.error = true));
+ }
+ }
+ class App extends Component {
+ static template = xml`
+
+
`;
+ static components = { ErrorBoundary, ErrorComponent, ClassicCompoent };
+ }
+ await mount(App, fixture);
+ expect(fixture.innerHTML).toBe("");
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("can catch an error in the mounted call", async () => {
+ class ErrorComponent extends Component {
+ static template = xml`Some text
`;
+ setup() {
+ useLogLifecycle();
+ onMounted(() => {
+ logStep("boom");
+ throw new Error("NOOOOO");
+ });
+ }
+ }
+ class ErrorBoundary extends Component {
+ static template = xml`
+ Error handled
+
+
`;
+ state = useState({ error: false });
+
+ setup() {
+ useLogLifecycle();
+ onError(() => (this.state.error = true));
+ }
+ }
+ class Root extends Component {
+ static template = xml`
+
+
`;
+ static components = { ErrorBoundary, ErrorComponent };
+ setup() {
+ useLogLifecycle();
+ }
+ }
+ await mount(Root, fixture);
+ expect(fixture.innerHTML).toBe("");
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Root:setup",
+ "Root:willStart",
+ "Root:willRender",
+ "ErrorBoundary:setup",
+ "ErrorBoundary:willStart",
+ "Root:rendered",
+ "ErrorBoundary:willRender",
+ "ErrorComponent:setup",
+ "ErrorComponent:willStart",
+ "ErrorBoundary:rendered",
+ "ErrorComponent:willRender",
+ "ErrorComponent:rendered",
+ "ErrorComponent:mounted",
+ "boom",
+ "ErrorBoundary:willRender",
+ "ErrorBoundary:rendered",
+ "ErrorBoundary:mounted",
+ "Root:mounted",
+ ]
+ `);
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("can catch an error in the mounted call (in root component)", async () => {
+ class ErrorComponent extends Component {
+ static template = xml`Some text
`;
+ setup() {
+ useLogLifecycle();
+ onMounted(() => {
+ logStep("boom");
+ throw new Error("NOOOOO");
+ });
+ }
+ }
+ class Root extends Component {
+ static template = xml`
+ Error handled
+
+
`;
+ static components = { ErrorComponent };
+ state = useState({ error: false });
+
+ setup() {
+ useLogLifecycle();
+ onError(() => (this.state.error = true));
+ }
+ }
+ await mount(Root, fixture);
+ expect(fixture.innerHTML).toBe("Error handled
");
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Root:setup",
+ "Root:willStart",
+ "Root:willRender",
+ "ErrorComponent:setup",
+ "ErrorComponent:willStart",
+ "Root:rendered",
+ "ErrorComponent:willRender",
+ "ErrorComponent:rendered",
+ "ErrorComponent:mounted",
+ "boom",
+ "Root:willRender",
+ "Root:rendered",
+ "Root:mounted",
+ ]
+ `);
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("can catch an error in the mounted call (in child of child)", async () => {
+ class Boom extends Component {
+ static template = xml`Some text
`;
+ setup() {
+ useLogLifecycle();
+ onMounted(() => {
+ logStep("boom");
+ throw new Error("NOOOOO");
+ });
+ }
+ }
+
+ class C extends Component {
+ static template = xml`
+ Error handled
+
+
`;
+ static components = { Boom };
+ state = useState({ error: false });
+
+ setup() {
+ useLogLifecycle();
+ onError(() => (this.state.error = true));
+ }
+ }
+
+ class B extends Component {
+ static template = xml`
`;
+ static components = { C };
+ setup() {
+ useLogLifecycle();
+ }
+ }
+ class A extends Component {
+ static template = xml``;
+ static components = { B };
+ setup() {
+ useLogLifecycle();
+ }
+ }
+ await mount(A, fixture);
+ expect(fixture.innerHTML).toBe("");
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "A:setup",
+ "A:willStart",
+ "A:willRender",
+ "B:setup",
+ "B:willStart",
+ "A:rendered",
+ "B:willRender",
+ "C:setup",
+ "C:willStart",
+ "B:rendered",
+ "C:willRender",
+ "Boom:setup",
+ "Boom:willStart",
+ "C:rendered",
+ "Boom:willRender",
+ "Boom:rendered",
+ "Boom:mounted",
+ "boom",
+ "C:willRender",
+ "C:rendered",
+ "C:mounted",
+ "B:mounted",
+ "A:mounted",
+ ]
+ `);
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("error in mounted on a component with a sibling (properly mounted)", async () => {
+ class ErrorComponent extends Component {
+ static template = xml`Some text
`;
+ setup() {
+ useLogLifecycle();
+ onMounted(() => {
+ logStep("boom");
+ throw new Error("NOOOOO");
+ });
+ }
+ }
+ class ErrorBoundary extends Component {
+ static template = xml`
+ Error handled
+
+
`;
+ state = useState({ error: false });
+
+ setup() {
+ useLogLifecycle();
+ onError(() => (this.state.error = true));
+ }
+ }
+ class OK extends Component {
+ static template = xml`OK`;
+ setup() {
+ useLogLifecycle();
+ }
+ }
+
+ class Root extends Component {
+ static template = xml`
+
+
+
`;
+ static components = { ErrorBoundary, ErrorComponent, OK };
+ setup() {
+ useLogLifecycle();
+ }
+ }
+ await mount(Root, fixture);
+ expect(fixture.innerHTML).toBe("");
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Root:setup",
+ "Root:willStart",
+ "Root:willRender",
+ "OK:setup",
+ "OK:willStart",
+ "ErrorBoundary:setup",
+ "ErrorBoundary:willStart",
+ "Root:rendered",
+ "OK:willRender",
+ "OK:rendered",
+ "ErrorBoundary:willRender",
+ "ErrorComponent:setup",
+ "ErrorComponent:willStart",
+ "ErrorBoundary:rendered",
+ "ErrorComponent:willRender",
+ "ErrorComponent:rendered",
+ "ErrorComponent:mounted",
+ "boom",
+ "ErrorBoundary:willRender",
+ "ErrorBoundary:rendered",
+ "ErrorBoundary:mounted",
+ "OK:mounted",
+ "Root:mounted",
+ ]
+ `);
+ expect(mockConsoleError).toBeCalledTimes(0);
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("can catch an error in the willPatch call", async () => {
+ class ErrorComponent extends Component {
+ static template = xml`
`;
+ setup() {
+ onWillPatch(() => {
+ throw new Error("NOOOOO");
+ });
+ }
+ }
+ class ErrorBoundary extends Component {
+ static template = xml`
+
+ Error handled
+
+
`;
+ state = useState({ error: false });
+
+ setup() {
+ onError(() => (this.state.error = true));
+ }
+ }
+ class App extends Component {
+ static template = xml`
+
+
+
+
`;
+ state = useState({ message: "abc" });
+ static components = { ErrorBoundary, ErrorComponent };
+ }
+ const app = await mount(App, fixture);
+ expect(fixture.innerHTML).toBe("");
+ app.state.message = "def";
+ await nextTick();
+ await nextTick();
+ await nextTick();
+ expect(fixture.innerHTML).toBe("");
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("catchError in catchError", async () => {
+ class Boom extends Component {
+ static template = xml``;
+ }
+
+ class Child extends Component {
+ static template = xml`
+
+
+
`;
+ static components = { Boom };
+
+ setup() {
+ onError((error) => {
+ throw error;
+ });
+ }
+ }
+
+ class Parent extends Component {
+ static template = xml`
+
+ Error
+
+
+
+
`;
+ static components = { Child };
+
+ error: any = false;
+
+ setup() {
+ onError((error) => {
+ this.error = error;
+ this.render();
+ });
+ }
+ }
+
+ await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe("Error
");
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("onError in class inheritance is not called if no rethrown", async () => {
+ const steps: string[] = [];
+
+ class Abstract extends Component {
+ static template = xml`
+
+
+
+
+
+
+
`;
+ state: any;
+ setup() {
+ this.state = useState({});
+ onError(() => {
+ steps.push("Abstract onError");
+ this.state.error = "Abstract";
+ });
+ }
+ }
+
+ class Concrete extends Abstract {
+ setup() {
+ super.setup();
+ onError(() => {
+ steps.push("Concrete onError");
+ this.state.error = "Concrete";
+ });
+ }
+ }
+
+ class Parent extends Component {
+ static components = { Concrete };
+ static template = xml``;
+ }
+
+ await mount(Parent, fixture);
+
+ expect(steps).toStrictEqual(["Concrete onError"]);
+ expect(fixture.innerHTML).toBe("Concrete
");
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("onError in class inheritance is called if rethrown", async () => {
+ const steps: string[] = [];
+
+ class Abstract extends Component {
+ static template = xml`
+
+
+
+
+
+
+
`;
+ state: any;
+ setup() {
+ this.state = useState({});
+ onError(() => {
+ steps.push("Abstract onError");
+ this.state.error = "Abstract";
+ });
+ }
+ }
+
+ class Concrete extends Abstract {
+ setup() {
+ super.setup();
+ onError((error) => {
+ steps.push("Concrete onError");
+ this.state.error = "Concrete";
+ throw error;
+ });
+ }
+ }
+
+ class Parent extends Component {
+ static components = { Concrete };
+ static template = xml``;
+ }
+
+ await mount(Parent, fixture);
+
+ expect(steps).toStrictEqual(["Concrete onError", "Abstract onError"]);
+ expect(fixture.innerHTML).toBe("Abstract
");
+ expect(mockConsoleWarn).toBeCalledTimes(0);
+ });
+
+ test("catching error, rethrow, render parent -- a main component loop implementation", async () => {
+ let parentState: any;
+
+ class ErrorComponent extends Component {
+ static template = xml``;
+ setup() {
+ throw new Error("My Error");
+ }
+ }
+
+ class Child extends Component {
+ static template = xml``;
+ static components = { ErrorComponent };
+ setup() {
+ onError((error) => {
+ throw error;
+ });
+ }
+ }
+
+ class Sibling extends Component {
+ static template = xml`Sibling
`;
+ }
+
+ class ErrorHandler extends Component {
+ static template = xml``;
+ setup() {
+ onError(() => {
+ this.props.onError();
+ Promise.resolve().then(() => {
+ parentState.cps[2] = {
+ id: 2,
+ Comp: Sibling,
+ };
+ });
+ });
+ }
+ }
+
+ class Parent extends Component {
+ static template = xml`
+
+
+
+
+ `;
+
+ static components = { ErrorHandler };
+ state: any = useState({
+ cps: {},
+ });
+
+ setup() {
+ parentState = this.state;
+ }
+
+ cleanUp(id: number) {
+ delete this.state.cps[id];
+ }
+ }
+
+ await mount(Parent, fixture);
+ parentState.cps[1] = { id: 1, Comp: Child };
+ await nextMicroTick();
+ expect(fixture.innerHTML).toBe("");
+ await nextTick();
+ expect(fixture.innerHTML).toBe("Sibling
");
+ });
+
+ test("catching in child makes parent render", async () => {
+ class Child extends Component {
+ static template = xml``;
+ }
+
+ class ErrorComp extends Component {
+ static template = xml``;
+ setup() {
+ throw new Error("Error Component");
+ }
+ }
+
+ class Catch extends Component {
+ static template = xml``;
+ setup() {
+ onError(({ cause }) => {
+ this.props.onError(cause);
+ });
+ }
+ }
+
+ const steps: any[] = [];
+ class Parent extends Component {
+ static components = { Catch };
+ static template = xml`
+
+
+
+
+
+ `;
+
+ elements: any = {};
+
+ onError(id: any, error: Error) {
+ steps.push(error.message);
+ delete this.elements[id];
+ this.elements[2] = Child;
+ this.render();
+ }
+ }
+
+ const parent = await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe("");
+
+ parent.elements[1] = ErrorComp;
+ parent.render();
+ await nextTick();
+ expect(fixture.innerHTML).toBe("Child 2
");
+ expect(steps).toEqual(["Error Component"]);
+ });
+
+ test("an error in onWillDestroy", async () => {
+ class Child extends Component {
+ static template = xml`abc
`;
+ setup() {
+ useLogLifecycle();
+ onWillDestroy(() => {
+ throw new Error("boom");
+ });
+ }
+ }
+
+ class Parent extends Component {
+ static template = xml`
+
+ `;
+ static components = { Child };
+
+ state = useState({ value: 1, hasChild: true });
+ setup() {
+ useLogLifecycle();
+ onError(() => {
+ this.state.value++;
+ });
+ }
+ }
+
+ const parent = await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe("1abc
");
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Parent:setup",
+ "Parent:willStart",
+ "Parent:willRender",
+ "Child:setup",
+ "Child:willStart",
+ "Parent:rendered",
+ "Child:willRender",
+ "Child:rendered",
+ "Child:mounted",
+ "Parent:mounted",
+ ]
+ `);
+ parent.state.hasChild = false;
+ await nextTick();
+ await nextTick();
+ await nextTick();
+ await nextTick();
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Parent:willRender",
+ "Parent:rendered",
+ "Parent:willPatch",
+ "Child:willUnmount",
+ "Child:willDestroy",
+ "Parent:patched",
+ "Parent:willRender",
+ "Parent:rendered",
+ "Parent:willPatch",
+ "Parent:patched",
+ ]
+ `);
+ expect(fixture.innerHTML).toBe("2");
+ });
+
+ test("an error in onWillDestroy, variation", async () => {
+ class Child extends Component {
+ static template = xml`abc
`;
+ setup() {
+ useLogLifecycle();
+ onWillDestroy(() => {
+ throw new Error("boom");
+ });
+ }
+ }
+
+ class Parent extends Component {
+ static template = xml`
+
+ `;
+ static components = { Child };
+
+ state = useState({ value: 1, hasChild: false });
+ setup() {
+ useLogLifecycle();
+ onError(() => {
+ this.state.value++;
+ });
+ }
+ }
+
+ const parent = await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe("1");
+
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Parent:setup",
+ "Parent:willStart",
+ "Parent:willRender",
+ "Parent:rendered",
+ "Parent:mounted",
+ ]
+ `);
+
+ parent.state.hasChild = true;
+ await nextMicroTick();
+ await nextMicroTick();
+ await nextMicroTick();
+ await nextMicroTick();
+ await nextMicroTick();
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Parent:willRender",
+ "Child:setup",
+ "Child:willStart",
+ "Parent:rendered",
+ "Child:willRender",
+ "Child:rendered",
+ ]
+ `);
+ parent.state.hasChild = false;
+ await nextTick();
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Parent:willRender",
+ "Parent:rendered",
+ "Child:willDestroy",
+ "Parent:willRender",
+ "Parent:rendered",
+ ]
+ `);
+ expect(fixture.innerHTML).toBe("1");
+ await nextTick();
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Parent:willPatch",
+ "Parent:patched",
+ ]
+ `);
+ expect(fixture.innerHTML).toBe("2");
+ });
+
+ test("error in onMounted, graceful recovery", async () => {
+ class Child extends Component {
+ static template = xml`abc`;
+ setup() {
+ useLogLifecycle();
+ }
+ }
+
+ class OtherChild extends Component {
+ static template = xml`def`;
+ setup() {
+ useLogLifecycle();
+ }
+ }
+
+ class Boom extends Component {
+ static template = xml`boom`;
+ setup() {
+ useLogLifecycle();
+ onMounted(() => {
+ throw new Error("boom");
+ });
+ }
+ }
+
+ class Parent extends Component {
+ static template = xml`parent`;
+ static components = { Child, Boom };
+ setup() {
+ useLogLifecycle();
+ }
+ }
+
+ class Root extends Component {
+ static template = xml``;
+
+ component: any = Parent;
+ setup() {
+ useLogLifecycle();
+ onError(() => {
+ logStep("error");
+ this.component = OtherChild;
+ this.render();
+ });
+ }
+ }
+
+ await mount(Root, fixture);
+ expect(fixture.innerHTML).toBe("def");
+
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Root:setup",
+ "Root:willStart",
+ "Root:willRender",
+ "Parent:setup",
+ "Parent:willStart",
+ "Root:rendered",
+ "Parent:willRender",
+ "Child:setup",
+ "Child:willStart",
+ "Boom:setup",
+ "Boom:willStart",
+ "Parent:rendered",
+ "Child:willRender",
+ "Child:rendered",
+ "Boom:willRender",
+ "Boom:rendered",
+ "Boom:mounted",
+ "error",
+ "Root:willRender",
+ "OtherChild:setup",
+ "OtherChild:willStart",
+ "Root:rendered",
+ "OtherChild:willRender",
+ "OtherChild:rendered",
+ "OtherChild:mounted",
+ "Root:mounted",
+ ]
+ `);
+ });
+
+ test("error in onMounted, graceful recovery, variation", async () => {
+ class Child extends Component {
+ static template = xml`abc`;
+ setup() {
+ useLogLifecycle();
+ }
+ }
+
+ class OtherChild extends Component {
+ static template = xml`def`;
+ setup() {
+ useLogLifecycle();
+ }
+ }
+
+ class Boom extends Component {
+ static template = xml`boom`;
+ setup() {
+ useLogLifecycle();
+ onMounted(() => {
+ throw new Error("boom");
+ });
+ }
+ }
+
+ class Parent extends Component {
+ static template = xml`parent`;
+ static components = { Child, Boom };
+ setup() {
+ useLogLifecycle();
+ }
+ }
+
+ class Root extends Component {
+ static template = xml`R`;
+
+ component: any = Parent;
+ state = useState({ gogogo: false });
+
+ setup() {
+ useLogLifecycle();
+ onError(() => {
+ logStep("error");
+ this.component = OtherChild;
+ this.render();
+ });
+ }
+ }
+
+ const root = await mount(Root, fixture);
+ expect(fixture.innerHTML).toBe("R");
+
+ // standard mounting process
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Root:setup",
+ "Root:willStart",
+ "Root:willRender",
+ "Root:rendered",
+ "Root:mounted",
+ ]
+ `);
+
+ root.state.gogogo = true;
+ await nextTick();
+
+ expect(fixture.innerHTML).toBe("Rparentabcboom");
+ // rerender, root creates sub components, it crashes, tries to recover
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "Root:willRender",
+ "Parent:setup",
+ "Parent:willStart",
+ "Root:rendered",
+ "Parent:willRender",
+ "Child:setup",
+ "Child:willStart",
+ "Boom:setup",
+ "Boom:willStart",
+ "Parent:rendered",
+ "Child:willRender",
+ "Child:rendered",
+ "Boom:willRender",
+ "Boom:rendered",
+ "Root:willPatch",
+ "Boom:mounted",
+ "error",
+ "Root:willRender",
+ "OtherChild:setup",
+ "OtherChild:willStart",
+ "Root:rendered",
+ ]
+ `);
+
+ await nextTick();
+ expect(fixture.innerHTML).toBe("Rdef");
+
+ expect(steps.splice(0)).toMatchInlineSnapshot(`
+ Array [
+ "OtherChild:willRender",
+ "OtherChild:rendered",
+ "Root:willPatch",
+ "Child:willDestroy",
+ "Boom:willUnmount",
+ "Boom:willDestroy",
+ "Parent:willDestroy",
+ "OtherChild:mounted",
+ "Root:patched",
+ ]
+ `);
+ });
+});