diff --git a/src/runtime/scheduler.ts b/src/runtime/scheduler.ts index 72079dfed..ed1d14c00 100644 --- a/src/runtime/scheduler.ts +++ b/src/runtime/scheduler.ts @@ -93,7 +93,12 @@ export class Scheduler { if (!hasError) { fiber.complete(); } - this.tasks.delete(fiber); + if (fiber.appliedToDom) { + // in this case, the fiber has been recycled by some rendering triggered + // in a error handler. so we actually want to keep the fiber around, + // otherwise the new render will just never be applied + this.tasks.delete(fiber); + } } } } diff --git a/tests/components/__snapshots__/error_handling.test.ts.snap b/tests/components/__snapshots__/error_handling.test.ts.snap index d674b9750..45f1bf43d 100644 --- a/tests/components/__snapshots__/error_handling.test.ts.snap +++ b/tests/components/__snapshots__/error_handling.test.ts.snap @@ -320,6 +320,50 @@ exports[`can catch errors can catch an error in a component render function 3`] }" `; +exports[`can catch errors can catch an error in onmounted 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + const comp1 = app.createComponent(null, false, false, false, []); + + return function template(ctx, node, key = \\"\\") { + let b2, b3; + b2 = text(\`Main\`); + if (ctx['state'].ok) { + const Comp1 = ctx['component']; + b3 = toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1)); + } + return multi([b2, b3]); + } +}" +`; + +exports[`can catch errors can catch an error in onmounted 3`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + let block1 = createBlock(\`
Error!!!
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`can catch errors can catch an error in onmounted 4`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + let block1 = createBlock(\`
perfect
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + exports[`can catch errors can catch an error in the constructor call of a component render function 1`] = ` "function anonymous(app, bdom, helpers ) { diff --git a/tests/components/error_handling.test.ts b/tests/components/error_handling.test.ts index 9b45ea78c..703b29183 100644 --- a/tests/components/error_handling.test.ts +++ b/tests/components/error_handling.test.ts @@ -564,6 +564,82 @@ describe("can catch errors", () => { 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("Main
Error!!!
"); + 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("Main
perfect
"); + }); + test("calling a hook outside setup should crash", async () => { class Root extends Component { static template = xml``;