diff --git a/src/runtime/app.ts b/src/runtime/app.ts index ace8fd1c3..901d83930 100644 --- a/src/runtime/app.ts +++ b/src/runtime/app.ts @@ -65,6 +65,7 @@ export class App< props: P; env: E; scheduler = new Scheduler(); + subRoots: Set = new Set(); root: ComponentNode | null = null; warnIfNoStaticProps: boolean; @@ -101,6 +102,21 @@ export class App< return prom; } + mountSubRoot any = any>( + SubRoot: ComponentConstructor, + subProps: SP, + target: HTMLElement | ShadowRoot + ): Promise & InstanceType> { + App.validateTarget(target); + if (this.dev) { + validateProps(SubRoot, subProps, { __owl__: { app: this } }); + } + const node = this.makeNode(SubRoot, subProps); + const prom = this.mountNode(node, target); + this.subRoots.add(node); + return prom; + } + makeNode(Component: ComponentConstructor, props: any): ComponentNode { return new ComponentNode(Component, props, this, null, null); } @@ -134,6 +150,9 @@ export class App< destroy() { if (this.root) { + for (let subroot of this.subRoots) { + subroot.destroy(); + } this.root.destroy(); this.scheduler.processTasks(); } diff --git a/tests/app/__snapshots__/sub_root.test.ts.snap b/tests/app/__snapshots__/sub_root.test.ts.snap new file mode 100644 index 000000000..6b9fe0630 --- /dev/null +++ b/tests/app/__snapshots__/sub_root.test.ts.snap @@ -0,0 +1,79 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`subroot can mount subroot 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + let block1 = createBlock(\`
main app
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`subroot can mount subroot 2`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + let block1 = createBlock(\`
sub root
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`subroot can mount subroot inside own dom 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + let block1 = createBlock(\`
main app
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`subroot can mount subroot inside own dom 2`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + let block1 = createBlock(\`
sub root
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`subroot env is the same in sub root 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + let block1 = createBlock(\`
main app
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + +exports[`subroot env is the same in sub root 2`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + let block1 = createBlock(\`
sub root
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; diff --git a/tests/app/sub_root.test.ts b/tests/app/sub_root.test.ts new file mode 100644 index 000000000..00e08d356 --- /dev/null +++ b/tests/app/sub_root.test.ts @@ -0,0 +1,72 @@ +import { App, Component, xml } from "../../src"; +import { status } from "../../src/runtime/status"; +import { + makeTestFixture, + snapshotEverything, +} from "../helpers"; + +let fixture: HTMLElement; + +snapshotEverything(); + +beforeEach(() => { + fixture = makeTestFixture(); +}); + +class SomeComponent extends Component { + static template = xml`
main app
`; +} + +class SubComponent extends Component { + static template = xml`
sub root
`; +} + +describe("subroot", () => { + test("can mount subroot", async () => { + const app = new App(SomeComponent); + const comp = await app.mount(fixture); + expect(fixture.innerHTML).toBe("
main app
"); + const subcomp = await app.mountSubRoot(SubComponent, null, fixture); + expect(fixture.innerHTML).toBe("
main app
sub root
"); + + app.destroy(); + expect(fixture.innerHTML).toBe(""); + expect(status(comp)).toBe("destroyed"); + expect(status(subcomp)).toBe("destroyed"); + }); + + test("can mount subroot inside own dom", async () => { + const app = new App(SomeComponent); + const comp = await app.mount(fixture); + expect(fixture.innerHTML).toBe("
main app
"); + const subcomp = await app.mountSubRoot(SubComponent, null, fixture.querySelector("div")!); + expect(fixture.innerHTML).toBe("
main app
sub root
"); + + app.destroy(); + expect(fixture.innerHTML).toBe(""); + expect(status(comp)).toBe("destroyed"); + expect(status(subcomp)).toBe("destroyed"); + }); + + test("env is the same in sub root", async () => { + let env, subenv; + class SC extends SomeComponent { + setup() { + env = this.env; + } + } + class Sub extends SubComponent { + setup() { + subenv = this.env; + } + } + + const app = new App(SC); + await app.mount(fixture); + await app.mountSubRoot(Sub, null, fixture); + + expect(env).toBeDefined(); + expect(subenv).toBeDefined(); + expect(env).toBe(subenv); + }); +});