diff --git a/tests/components/error_handling.test.ts b/tests/components/error_handling.test.ts index 67254427..151287c1 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("
heyfalse
"); - 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("
heyfalse
"); - app.state.flag = true; - await nextTick(); - expect(fixture.innerHTML).toBe("
Error handled
"); - 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("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``; - 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("
Error handled
"); - 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("
Error handled
"); - 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("
Error handled
"); - 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("
Error handled
"); - 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("
Error handled
"); - 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("
Error handled
"); - 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("
Error handled
"); - 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("
Error handled
"); - 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("
OK
Error handled
"); - 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("
abc
abc
"); - app.state.message = "def"; - await nextTick(); - await nextTick(); - await nextTick(); - expect(fixture.innerHTML).toBe("
def
Error handled
"); - 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("1
abc
"); - 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("
heyfalse
"); +// 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("
heyfalse
"); +// app.state.flag = true; +// await nextTick(); +// expect(fixture.innerHTML).toBe("
Error handled
"); +// 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("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``; +// 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("
Error handled
"); +// 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("
Error handled
"); +// 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("
Error handled
"); +// 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("
Error handled
"); +// 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("
Error handled
"); +// 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("
Error handled
"); +// 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("
Error handled
"); +// 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("
Error handled
"); +// 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("
OK
Error handled
"); +// 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("
abc
abc
"); +// app.state.message = "def"; +// await nextTick(); +// await nextTick(); +// await nextTick(); +// expect(fixture.innerHTML).toBe("
def
Error handled
"); +// 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("1
abc
"); +// 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", +// ] +// `); +// }); +// });