diff --git a/src/app/app.ts b/src/app/app.ts index 49add59b..b05143fd 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -4,6 +4,7 @@ import { MountOptions } from "../component/fibers"; import { Scheduler } from "../component/scheduler"; import { TemplateSet, TemplateSetConfig } from "./template_set"; import { nodeErrorHandlers } from "../component/error_handling"; +import { validateTarget } from "../utils"; // reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f @@ -48,22 +49,13 @@ export class App< } mount(target: HTMLElement, options?: MountOptions): Promise & InstanceType> { - this.checkTarget(target); + validateTarget(target); const node = this.makeNode(this.Root, this.props); const prom = this.mountNode(node, target, options); this.root = node; return prom; } - checkTarget(target: HTMLElement) { - if (!(target instanceof HTMLElement)) { - throw new Error("Cannot mount component: the target is not a valid DOM element"); - } - if (!document.body.contains(target)) { - throw new Error("Cannot mount a component on a detached dom node"); - } - } - makeNode(Component: ComponentConstructor, props: any): ComponentNode { return new ComponentNode(Component, props, this); } diff --git a/src/component/fibers.ts b/src/component/fibers.ts index 1da13453..8c9ce42c 100644 --- a/src/component/fibers.ts +++ b/src/component/fibers.ts @@ -1,4 +1,5 @@ import { BDom, mount } from "../blockdom"; +import { validateTarget } from "../utils"; import type { ComponentNode } from "./component_node"; import { fibersInError, handleError } from "./error_handling"; import { STATUS } from "./status"; @@ -158,6 +159,7 @@ export class MountFiber extends RootFiber { complete() { let current: Fiber | undefined = this; try { + validateTarget(this.target); const node = this.node; if (node.bdom) { // this is a complicated situation: if we mount a fiber with an existing diff --git a/src/utils.ts b/src/utils.ts index 58f8b027..e7f52357 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -24,6 +24,15 @@ export function batched(callback: Callback): Callback { }; } +export function validateTarget(target: HTMLElement) { + if (!(target instanceof HTMLElement)) { + throw new Error("Cannot mount component: the target is not a valid DOM element"); + } + if (!document.body.contains(target)) { + throw new Error("Cannot mount a component on a detached dom node"); + } +} + export class EventBus extends EventTarget { trigger(name: string, payload?: any) { this.dispatchEvent(new CustomEvent(name, { detail: payload })); diff --git a/tests/components/__snapshots__/basics.test.ts.snap b/tests/components/__snapshots__/basics.test.ts.snap index 1a81e4fe..07c1c560 100644 --- a/tests/components/__snapshots__/basics.test.ts.snap +++ b/tests/components/__snapshots__/basics.test.ts.snap @@ -81,6 +81,19 @@ exports[`basics a class component inside a class component, no external dom 2`] }" `; +exports[`basics a component cannot be mounted in a detached node (even if node is detached later 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + exports[`basics a component inside a component 1`] = ` "function anonymous(bdom, helpers ) { diff --git a/tests/components/basics.test.ts b/tests/components/basics.test.ts index 40ec74e8..2b9db3bf 100644 --- a/tests/components/basics.test.ts +++ b/tests/components/basics.test.ts @@ -171,6 +171,27 @@ describe("basics", () => { expect(error!.message).toBe("Cannot mount a component on a detached dom node"); }); + test("a component cannot be mounted in a detached node (even if node is detached later)", async () => { + const warn = console.warn; + console.warn = jest.fn(); + class Test extends Component { + static template = xml`
`; + } + let error: Error; + const prom = mount(Test, fixture); + await Promise.resolve(); + fixture.remove(); + try { + await prom; + } catch (e) { + error = e as Error; + } + expect(error!).toBeDefined(); + expect(error!.message).toBe("Cannot mount a component on a detached dom node"); + expect(console.warn).toBeCalledTimes(1); + console.warn = warn; + }); + test("crashes if it cannot find a template", async () => { class Test extends Component { static template = "wrongtemplate";