mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] component: improve errors when thrown from lifecycle hooks
Previously, a crash in a lifecycle hook for any reason would throw an error whose stack trace started from the scheduler and contained only the place where the hook was called by owl, but not the place where the hook was registered by the user. This proved very difficult for users to debug as they cannot really tell which component registered that hook. This commit alleviates the issue by creating a new Error when the hook is originally called, and wrapping the registered callback in a try catch, throwing an error with the correct stack trace instead of the error in the hook, and setting the error in the hook as the cause of this synthetic error.
This commit is contained in:
committed by
Géry Debongnie
parent
4a922ed82d
commit
804ad3c35e
@@ -1092,6 +1092,19 @@ exports[`errors and promises an error in mounted call will reject the mount prom
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`errors and promises an error in onMounted callback will have the component's setup in its stack trace 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>abc</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`errors and promises an error in patched call will reject the render promise 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -82,8 +82,6 @@ describe("basics", () => {
|
||||
});
|
||||
|
||||
test("display a nice error if it cannot find component (in dev mode)", async () => {
|
||||
const info = console.info;
|
||||
console.info = jest.fn(() => {}); // dev mode message
|
||||
class SomeComponent extends Component {}
|
||||
class Parent extends Component {
|
||||
static template = xml`<SomeMispelledComponent />`;
|
||||
@@ -91,7 +89,7 @@ describe("basics", () => {
|
||||
}
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Parent, fixture, { dev: true });
|
||||
await mount(Parent, fixture, { test: true });
|
||||
} catch (e) {
|
||||
error = e as Error;
|
||||
}
|
||||
@@ -100,8 +98,6 @@ describe("basics", () => {
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
expect(mockConsoleError).toBeCalledTimes(0);
|
||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
||||
expect(console.info).toBeCalledTimes(1);
|
||||
console.info = info;
|
||||
});
|
||||
|
||||
test("simple catchError", async () => {
|
||||
@@ -179,6 +175,30 @@ describe("errors and promises", () => {
|
||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test("an error in onMounted callback will have the component's setup in its stack trace", async () => {
|
||||
class App extends Component {
|
||||
static template = xml`<div>abc</div>`;
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
throw new Error("boom");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(App, fixture, { test: true });
|
||||
} catch (e) {
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.stack).toContain("App.setup");
|
||||
expect(error!.stack).toContain("error_handling.test.ts");
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
expect(mockConsoleError).toBeCalledTimes(0);
|
||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test("an error in willPatch call will reject the render promise", async () => {
|
||||
class Root extends Component {
|
||||
static template = xml`<div><t t-esc="val"/></div>`;
|
||||
@@ -191,13 +211,13 @@ describe("errors and promises", () => {
|
||||
}
|
||||
}
|
||||
|
||||
const root = await mount(Root, fixture);
|
||||
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("boom");
|
||||
expect(error!.message).toBe(`The following error occurred in onWillPatch: "boom"`);
|
||||
expect(mockConsoleError).toBeCalledTimes(0);
|
||||
expect(mockConsoleWarn).toBeCalledTimes(0);
|
||||
});
|
||||
@@ -214,13 +234,13 @@ describe("errors and promises", () => {
|
||||
}
|
||||
}
|
||||
|
||||
const root = await mount(Root, fixture);
|
||||
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("boom");
|
||||
expect(error!.message).toBe(`The following error occurred in onPatched: "boom"`);
|
||||
expect(mockConsoleError).toBeCalledTimes(0);
|
||||
expect(mockConsoleWarn).toBeCalledTimes(0);
|
||||
});
|
||||
@@ -296,7 +316,6 @@ describe("errors and promises", () => {
|
||||
});
|
||||
|
||||
test("errors in mounted and in willUnmount", async () => {
|
||||
expect.assertions(4);
|
||||
class Example extends Component {
|
||||
static template = xml`<div/>`;
|
||||
val: any;
|
||||
@@ -313,9 +332,11 @@ describe("errors and promises", () => {
|
||||
}
|
||||
|
||||
try {
|
||||
await mount(Example, fixture);
|
||||
await mount(Example, fixture, { test: true });
|
||||
} catch (e) {
|
||||
expect((e as Error).message).toBe("Error in mounted");
|
||||
expect((e as 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
|
||||
@@ -385,14 +406,14 @@ describe("can catch errors", () => {
|
||||
});
|
||||
}
|
||||
}
|
||||
let e: any = null;
|
||||
let e: Error;
|
||||
try {
|
||||
await mount(Root, fixture);
|
||||
await mount(Root, fixture, { test: true });
|
||||
} catch (error) {
|
||||
e = error;
|
||||
e = error as Error;
|
||||
}
|
||||
expect(e.message).toBe(
|
||||
"No active component (a hook function should only be called in 'setup')"
|
||||
expect(e!.message).toBe(
|
||||
`The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"`
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user