From 0b1c4dd4efb658af339fbb5f8b061693c99bd7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 20 Dec 2021 17:32:32 +0100 Subject: [PATCH] [IMP] app: improve API, small refactoring --- src/app/app.ts | 45 +++++++------------ src/app/template_set.ts | 28 ++++++++---- src/component/component_node.ts | 16 ++++--- src/index.ts | 25 ++--------- .../__snapshots__/app.test.ts.snap | 14 ++++++ tests/{components => app}/app.test.ts | 13 +++++- .../__snapshots__/basics.test.ts.snap | 13 ++++++ tests/components/basics.test.ts | 14 +++++- tests/components/concurrency.test.ts | 2 +- tests/components/env.test.ts | 4 +- .../components/higher_order_component.test.ts | 4 +- tests/components/hooks.test.ts | 8 ++-- tests/misc/portal.test.ts | 13 ++---- 13 files changed, 112 insertions(+), 87 deletions(-) rename tests/{components => app}/__snapshots__/app.test.ts.snap (68%) rename tests/{components => app}/app.test.ts (79%) diff --git a/src/app/app.ts b/src/app/app.ts index 9a5aa638..eebf26ed 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -2,7 +2,7 @@ import { Component } from "../component/component"; import { ComponentNode } from "../component/component_node"; import { MountOptions } from "../component/fibers"; import { Scheduler } from "../component/scheduler"; -import { TemplateSet } from "./template_set"; +import { TemplateSet, TemplateSetConfig } from "./template_set"; import { nodeErrorHandlers } from "../component/error_handling"; // reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f @@ -11,12 +11,9 @@ export interface Env { [key: string]: any; } -export interface AppConfig { - dev?: boolean; +export interface AppConfig extends TemplateSetConfig { env?: Env; - translatableAttributes?: string[]; - translateFn?: (s: string) => string; - templates?: string | Document; + props?: any; } export const DEV_MSG = `Owl is running in 'dev' mode. @@ -27,35 +24,19 @@ See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for mor export class App extends TemplateSet { Root: T; props: any; - env: Env = Object.freeze({}); + env: Env; scheduler = new Scheduler(window.requestAnimationFrame.bind(window)); root: ComponentNode | null = null; - constructor(Root: T, props?: any) { - super(); + constructor(Root: T, config: AppConfig = {}) { + super(config); this.Root = Root; - this.props = props; - } - - configure(config: AppConfig): App { if (config.dev) { - this.dev = config.dev; console.info(DEV_MSG); } - if (config.env) { - const descrs = Object.getOwnPropertyDescriptors(config.env); - this.env = Object.freeze(Object.defineProperties({}, descrs)); - } - if (config.translateFn) { - this.translateFn = config.translateFn; - } - if (config.translatableAttributes) { - this.translatableAttributes = config.translatableAttributes; - } - if (config.templates) { - this.addTemplates(config.templates); - } - return this; + const descrs = Object.getOwnPropertyDescriptors(config.env || {}); + this.env = Object.freeze(Object.defineProperties({}, descrs)); + this.props = config.props || {}; } mount(target: HTMLElement, options?: MountOptions): Promise> { @@ -114,3 +95,11 @@ export class App extends TemplateSet { } } } + +export async function mount( + C: T, + target: HTMLElement, + config: AppConfig & MountOptions = {} +): Promise> { + return new App(C, config).mount(target, config); +} diff --git a/src/app/template_set.ts b/src/app/template_set.ts index 4a9874a7..a7f50766 100644 --- a/src/app/template_set.ts +++ b/src/app/template_set.ts @@ -37,22 +37,34 @@ function parseXML(xml: string): Document { return doc; } +export interface TemplateSetConfig { + dev?: boolean; + translatableAttributes?: string[]; + translateFn?: (s: string) => string; + templates?: string | Document; +} + export class TemplateSet { + dev: boolean; rawTemplates: typeof globalTemplates = Object.create(globalTemplates); templates: { [name: string]: Template } = {}; translateFn?: (s: string) => string; translatableAttributes?: string[]; - utils: typeof UTILS; - dev?: boolean; - - constructor() { - const call = (owner: any, subTemplate: string, ctx: any, parent: any) => { + utils: typeof UTILS = Object.assign({}, UTILS, { + call: (owner: any, subTemplate: string, ctx: any, parent: any) => { const template = this.getTemplate(subTemplate); return toggler(subTemplate, template.call(owner, ctx, parent)); - }; + }, + getTemplate: (name: string) => this.getTemplate(name), + }); - const getTemplate = (name: string) => this.getTemplate(name); - this.utils = Object.assign({}, UTILS, { getTemplate, call }); + constructor(config: TemplateSetConfig = {}) { + this.dev = config.dev || false; + this.translateFn = config.translateFn; + this.translatableAttributes = config.translatableAttributes; + if (config.templates) { + this.addTemplates(config.templates); + } } addTemplate(name: string, template: string | Node, options: { allowDuplicate?: boolean } = {}) { diff --git a/src/component/component_node.ts b/src/component/component_node.ts index bd96adad..22481b68 100644 --- a/src/component/component_node.ts +++ b/src/component/component_node.ts @@ -13,6 +13,16 @@ import { handleError, fibersInError } from "./error_handling"; import { applyDefaultProps } from "./props_validation"; import { STATUS } from "./status"; +let currentNode: ComponentNode | null = null; + +export function getCurrent(): ComponentNode | null { + return currentNode; +} + +export function useComponent(): Component { + return currentNode!.component; +} + export function component( name: string | typeof Component, props: any, @@ -62,12 +72,6 @@ export function component( // Component VNode // ----------------------------------------------------------------------------- -let currentNode: ComponentNode | null = null; - -export function getCurrent(): ComponentNode | null { - return currentNode; -} - type LifecycleHook = Function; export class ComponentNode diff --git a/src/index.ts b/src/index.ts index 760a34da..d61c7371 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,27 +32,9 @@ export const blockDom = { comment, }; -import type { AppConfig } from "./app/app"; -import { App } from "./app/app"; -import { Component } from "./component/component"; -import { getCurrent } from "./component/component_node"; - -export { App, Component }; - -export async function mount( - C: T, - target: HTMLElement, - config: AppConfig = {} -): Promise> { - const app = new App(C); - return app.configure(config).mount(target); -} - -export function useComponent(): Component { - const current = getCurrent(); - return current!.component; -} - +export { App, mount } from "./app/app"; +export { Component } from "./component/component"; +export { useComponent } from "./component/component_node"; export { status } from "./component/status"; export { Portal } from "./portal"; export { Memo } from "./memo"; @@ -60,7 +42,6 @@ export { xml } from "./app/template_set"; export { useState, reactive } from "./reactivity"; export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks"; export { EventBus, whenReady, loadFile, markup } from "./utils"; - export { onWillStart, onMounted, diff --git a/tests/components/__snapshots__/app.test.ts.snap b/tests/app/__snapshots__/app.test.ts.snap similarity index 68% rename from tests/components/__snapshots__/app.test.ts.snap rename to tests/app/__snapshots__/app.test.ts.snap index b48f82a9..aa92154d 100644 --- a/tests/components/__snapshots__/app.test.ts.snap +++ b/tests/app/__snapshots__/app.test.ts.snap @@ -15,6 +15,20 @@ exports[`app App supports env with getters/setters 1`] = ` }" `; +exports[`app can configure an app with props 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = ctx['props'].value; + return block1([d1]); + } +}" +`; + exports[`app destroy remove the widget from the DOM 1`] = ` "function anonymous(bdom, helpers ) { diff --git a/tests/components/app.test.ts b/tests/app/app.test.ts similarity index 79% rename from tests/components/app.test.ts rename to tests/app/app.test.ts index 146428ce..70c69314 100644 --- a/tests/components/app.test.ts +++ b/tests/app/app.test.ts @@ -40,8 +40,7 @@ describe("app", () => { static template = xml`
`; } - const app = new App(SomeComponent); - app.configure({ env }); + const app = new App(SomeComponent, { env }); const comp = await app.mount(fixture); expect(fixture.innerHTML).toBe("
maggot serv1
"); someVal = "brain"; @@ -50,4 +49,14 @@ describe("app", () => { await nextTick(); expect(fixture.innerHTML).toBe("
brain serv1,serv2
"); }); + + test("can configure an app with props", async () => { + class SomeComponent extends Component { + static template = xml`
`; + } + + const app = new App(SomeComponent, { props: { value: 333 } }); + await app.mount(fixture); + expect(fixture.innerHTML).toBe("
333
"); + }); }); diff --git a/tests/components/__snapshots__/basics.test.ts.snap b/tests/components/__snapshots__/basics.test.ts.snap index ecd092ba..b087241f 100644 --- a/tests/components/__snapshots__/basics.test.ts.snap +++ b/tests/components/__snapshots__/basics.test.ts.snap @@ -1060,6 +1060,19 @@ exports[`mount targets default mount option is 'last-child' 1`] = ` }" `; +exports[`mount targets mount function: can mount a component (with default position='last-child') 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; + + let block1 = createBlock(\`
app
\`); + + return function template(ctx, node, key = \\"\\") { + return block1(); + } +}" +`; + exports[`support svg components add proper namespace to svg 1`] = ` "function anonymous(bdom, helpers ) { diff --git a/tests/components/basics.test.ts b/tests/components/basics.test.ts index a65bc3ff..b590384c 100644 --- a/tests/components/basics.test.ts +++ b/tests/components/basics.test.ts @@ -41,7 +41,7 @@ describe("basics", () => { static template = xml``; } - const app = new App(Test, { value: 3 }); + const app = new App(Test, { props: { value: 3 } }); const component = await app.mount(fixture); expect(fixture.innerHTML).toBe("3"); @@ -125,7 +125,7 @@ describe("basics", () => { } } - const app = new App(Test, p); + const app = new App(Test, { props: p }); await app.mount(fixture); }); @@ -819,6 +819,16 @@ describe("mount targets", () => { expect(fixture.innerHTML).toBe("
app
"); }); + test("mount function: can mount a component (with default position='last-child')", async () => { + class Root extends Component { + static template = xml`
app
`; + } + const span = document.createElement("span"); + fixture.appendChild(span); + await mount(Root, fixture, { position: "last-child" }); + expect(fixture.innerHTML).toBe("
app
"); + }); + test("default mount option is 'last-child'", async () => { class Root extends Component { static template = xml`
app
`; diff --git a/tests/components/concurrency.test.ts b/tests/components/concurrency.test.ts index 6a5a0f3b..bdb78b0d 100644 --- a/tests/components/concurrency.test.ts +++ b/tests/components/concurrency.test.ts @@ -623,7 +623,7 @@ test("rendering component again in next microtick", async () => { } const env = { config: { flag: false } }; - await new App(Parent).configure({ env }).mount(fixture); + await mount(Parent, fixture, { env }); expect(fixture.innerHTML).toBe("
"); expect([ "Parent:setup", diff --git a/tests/components/env.test.ts b/tests/components/env.test.ts index a014b29e..d1a1bf87 100644 --- a/tests/components/env.test.ts +++ b/tests/components/env.test.ts @@ -21,7 +21,7 @@ describe("env handling", () => { class Test extends Component { static template = xml`
`; } - const component = await new App(Test).configure({ env }).mount(fixture); + const component = await new App(Test, { env }).mount(fixture); expect(Object.isFrozen(component.env)).toBeTruthy(); expect(component.env).toEqual({ foo: 42, bar: { value: 42 } }); expect(() => { @@ -47,7 +47,7 @@ describe("env handling", () => { static components = { Child }; } - await new App(Test).configure({ env }).mount(fixture); + await new App(Test, { env }).mount(fixture); expect(child.env).toEqual(env); }); }); diff --git a/tests/components/higher_order_component.test.ts b/tests/components/higher_order_component.test.ts index dae55926..8ea4ce6e 100644 --- a/tests/components/higher_order_component.test.ts +++ b/tests/components/higher_order_component.test.ts @@ -1,4 +1,4 @@ -import { App, Component, mount, useState, xml } from "../../src"; +import { Component, mount, useState, xml } from "../../src"; import { makeTestFixture, nextTick, snapshotEverything } from "../helpers"; let fixture: HTMLElement; @@ -64,7 +64,7 @@ describe("basics", () => { static components = { Child, OtherChild }; } const env = { options: { flag: true } }; - const parent = await new App(Parent).configure({ env }).mount(fixture); + const parent = await mount(Parent, fixture, { env }); expect(fixture.innerHTML).toBe("CHILD 1"); env.options.flag = false; diff --git a/tests/components/hooks.test.ts b/tests/components/hooks.test.ts index e1e6c275..cc15d92a 100644 --- a/tests/components/hooks.test.ts +++ b/tests/components/hooks.test.ts @@ -176,7 +176,7 @@ describe("hooks", () => { } } const env = { val: 1 }; - await new App(Test).configure({ env }).mount(fixture); + await mount(Test, fixture, { env }); expect(fixture.innerHTML).toBe("
1
"); }); @@ -188,7 +188,7 @@ describe("hooks", () => { } } const env = { val: 3 }; - const component = await new App(Test).configure({ env }).mount(fixture); + const component = await mount(Test, fixture, { env }); expect(fixture.innerHTML).toBe("
3
"); expect(component.env).not.toHaveProperty("val2"); expect(component.env).toHaveProperty("val"); @@ -219,7 +219,7 @@ describe("hooks", () => { return someVal; }, }; - const component = await new App(Test).configure({ env }).mount(fixture); + const component = await mount(Test, fixture, { env }); expect(fixture.innerHTML).toBe("
maggot brain
"); someVal = "brain"; someVal2 = "maggot"; @@ -252,7 +252,7 @@ describe("hooks", () => { } } const env = { val: 3 }; - await new App(Parent).configure({ env }).mount(fixture); + await mount(Parent, fixture, { env }); expect(fixture.innerHTML).toBe("3
5
"); }); diff --git a/tests/misc/portal.test.ts b/tests/misc/portal.test.ts index ac7aff67..e7ba2140 100644 --- a/tests/misc/portal.test.ts +++ b/tests/misc/portal.test.ts @@ -1,5 +1,4 @@ import { - App, Component, mount, onError, @@ -432,10 +431,8 @@ describe("Portal", () => {
`; } const env = {}; - const app = new App(Parent); - app.configure({ env }); addOutsideDiv(fixture); - const parent = await mount(Parent, fixture); + const parent = await mount(Parent, fixture, { env }); expect(parent.env).toStrictEqual({}); }); @@ -529,10 +526,8 @@ describe("Portal: Props validation", () => {
`; } let error: Error; - let app = new App(Parent); - app.configure({ dev: true }); try { - await app.mount(fixture); + await mount(Parent, fixture, { dev: true }); } catch (e) { error = e as Error; } @@ -555,10 +550,8 @@ describe("Portal: Props validation", () => { `; } let error: Error; - let app = new App(Parent); - app.configure({ dev: true }); try { - await app.mount(fixture); + await mount(Parent, fixture, { dev: true }); } catch (e) { error = e as Error; }