diff --git a/src/runtime/app.ts b/src/runtime/app.ts index df6064aef..c6f5a4569 100644 --- a/src/runtime/app.ts +++ b/src/runtime/app.ts @@ -1,6 +1,6 @@ import { OwlError } from "../common/owl_error"; import { version } from "../version"; -import { Component, ComponentConstructor, Props } from "./component"; +import { Component, ComponentConstructor } from "./component"; import { ComponentNode, saveCurrent } from "./component_node"; import { handleError, nodeErrorHandlers } from "./rendering/error_handling"; import { Fiber, MountOptions, RootFiber } from "./rendering/fibers"; @@ -9,21 +9,20 @@ import { proxy, toRaw } from "./reactivity/proxy"; import { Scheduler } from "./rendering/scheduler"; import { TemplateSet, TemplateSetConfig } from "./template_set"; import { validateTarget } from "./utils"; +import { GetProps } from "./props"; // reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f -export interface Env { - [key: string]: any; -} +type ComponentInstance = C extends new (...args: any) => infer T + ? T + : never; -interface RootConfig { - env?: E; +interface RootConfig

{ pluginManager?: PluginManager; props?: P; } -export interface AppConfig extends TemplateSetConfig { - env?: E; +export interface AppConfig extends TemplateSetConfig { name?: string; pluginManager?: PluginManager; test?: boolean; @@ -47,27 +46,26 @@ declare global { type MountTarget = HTMLElement | ShadowRoot; -interface Root

any = any> { - node: ComponentNode; - promise: Promise; - mount(target: MountTarget, options?: MountOptions): Promise & InstanceType>; +interface Root { + node: ComponentNode; + promise: Promise>; + mount(target: MountTarget, options?: MountOptions): Promise>; destroy(): void; } window.__OWL_DEVTOOLS__ ||= { apps, Fiber, RootFiber, toRaw, proxy }; -export class App extends TemplateSet { +export class App extends TemplateSet { static validateTarget = validateTarget; static apps = apps; static version = version; name: string; - env: E; scheduler = new Scheduler(); - roots: Set> = new Set(); + roots: Set> = new Set(); pluginManager: PluginManager; - constructor(config: AppConfig = {}) { + constructor(config: AppConfig = {}) { super(config); this.name = config.name || ""; apps.add(this); @@ -79,31 +77,18 @@ export class App extends TemplateSet { console.info(`Owl is running in 'dev' mode.`); hasBeenLogged = true; } - const env = config.env || {}; - const descrs = Object.getOwnPropertyDescriptors(env); - this.env = Object.freeze(Object.create(Object.getPrototypeOf(env), descrs)); } - createRoot( - Root: ComponentConstructor, - config: RootConfig = {} - ): Root { - const props = config.props || ({} as Props); - // hack to make sure the sub root get the sub env if necessary. for owl 3, - // would be nice to rethink the initialization process to make sure that - // we can create a ComponentNode and give it explicitely the env, instead - // of looking it up in the app - const env = this.env; - if (config.env) { - this.env = config.env as any; - } + createRoot( + Root: T, + config: RootConfig>> = {} + ): Root { + const props = config.props || ({} as any); const restore = saveCurrent(); const node = this.makeNode(Root, props); restore(); - if (config.env) { - this.env = env; - } + let resolve!: (value: any) => void; let reject!: (reason?: any) => void; const promise = new Promise((res, rej) => { @@ -128,7 +113,10 @@ export class App extends TemplateSet { return root; } - makeNode(Component: ComponentConstructor, props: any): ComponentNode { + makeNode( + Component: T, + props: GetProps> + ): ComponentNode { return new ComponentNode(Component, props, this, null, null); } @@ -169,7 +157,7 @@ export class App extends TemplateSet { apps.delete(this); } - createComponent

( + createComponent

>( name: string | null, isStatic: boolean, hasSlotsProp: boolean, @@ -177,12 +165,12 @@ export class App extends TemplateSet { propList: string[] ) { const isDynamic = !isStatic; - let arePropsDifferent: (p1: Object, p2: Object) => boolean; + let arePropsDifferent: (p1: P, p2: P) => boolean; const hasNoProp = propList.length === 0; if (hasSlotsProp) { arePropsDifferent = (_1, _2) => true; } else if (hasDynamicPropList) { - arePropsDifferent = function (props1: Props, props2: Props) { + arePropsDifferent = function (props1: P, props2: P) { for (let k in props1) { if (props1[k] !== props2[k]) { return true; @@ -193,7 +181,7 @@ export class App extends TemplateSet { } else if (hasNoProp) { arePropsDifferent = (_1: any, _2: any) => false; } else { - arePropsDifferent = function (props1: Props, props2: Props) { + arePropsDifferent = function (props1: P, props2: P) { for (let p of propList) { if (props1[p] !== props2[p]) { return true; @@ -250,20 +238,10 @@ export class App extends TemplateSet { } } -type ComponentInstance> = C extends new ( - ...args: any -) => infer T - ? T - : never; - -export async function mount< - T extends ComponentConstructor, - P extends object = any, - E = any ->( - C: T & ComponentConstructor, +export async function mount( + C: T, target: MountTarget, - config: AppConfig & RootConfig & MountOptions = {} + config: AppConfig & RootConfig>> & MountOptions = {} ): Promise> { const app = new App(config); const root = app.createRoot(C, config); diff --git a/src/runtime/component.ts b/src/runtime/component.ts index 4b51741c4..7a7fb979f 100644 --- a/src/runtime/component.ts +++ b/src/runtime/component.ts @@ -4,29 +4,23 @@ import type { ComponentNode } from "./component_node"; // Component Class // ----------------------------------------------------------------------------- -export type Props = { [key: string]: any }; - interface StaticComponentProperties { template: string; defaultProps?: any; components?: { [componentName: string]: ComponentConstructor }; } -export interface ComponentConstructor

- extends StaticComponentProperties { - new (env: E, node: ComponentNode): Component; +export interface ComponentConstructor extends StaticComponentProperties { + new (node: ComponentNode): Component; } -export class Component { +export class Component { static template: string = ""; static defaultProps?: any; - __props: Props = {} as any; // TODO: remove. it's just to keep types for now - env: Env; __owl__: ComponentNode; - constructor(env: Env, node: ComponentNode) { - this.env = env; + constructor(node: ComponentNode) { this.__owl__ = node; } diff --git a/src/runtime/component_node.ts b/src/runtime/component_node.ts index aba3111c2..e1ca4f986 100644 --- a/src/runtime/component_node.ts +++ b/src/runtime/component_node.ts @@ -1,7 +1,7 @@ import { OwlError } from "../common/owl_error"; -import type { App, Env } from "./app"; +import type { App } from "./app"; import { BDom, VNode } from "./blockdom"; -import { Component, ComponentConstructor, Props } from "./component"; +import { Component, ComponentConstructor } from "./component"; import { PluginManager } from "./plugins"; import { Atom, @@ -53,21 +53,20 @@ function applyDefaultProps

(props: P, defaultProps: Partial

) type LifecycleHook = Function; -export class ComponentNode

implements VNode> { +export class ComponentNode implements VNode { el?: HTMLElement | Text | undefined; app: App; fiber: Fiber | null = null; - component: Component; + component: Component; bdom: BDom | null = null; status: STATUS = STATUS.NEW; forceNextRender: boolean = false; parentKey: string | null; name: string; // TODO: remove - props: P; + props: Record; renderFn: Function; parent: ComponentNode | null; - childEnv: Env; children: { [key: string]: ComponentNode } = Object.create(null); refs: any = {}; @@ -83,8 +82,8 @@ export class ComponentNode

implements VNode, - props: P, + C: ComponentConstructor, + props: Record, app: App, parent: ComponentNode | null, parentKey: string | null @@ -107,11 +106,9 @@ export class ComponentNode

implements VNode implements VNode, parentFiber: Fiber) { props = Object.assign({}, props); // update const fiber = makeChildFiber(this, parentFiber); @@ -330,7 +327,7 @@ export class ComponentNode

implements VNode | null, afterNode: Node | null) { + moveBeforeVNode(other: ComponentNode | null, afterNode: Node | null) { this.bdom!.moveBeforeVNode(other ? other.bdom : null, afterNode); } diff --git a/src/runtime/hooks.ts b/src/runtime/hooks.ts index a3f5028c3..0c3939912 100644 --- a/src/runtime/hooks.ts +++ b/src/runtime/hooks.ts @@ -1,4 +1,3 @@ -import type { Env } from "./app"; import { getCurrent } from "./component_node"; import { onMounted, onPatched, onWillDestroy, onWillUnmount } from "./lifecycle_hooks"; import { PluginConstructor, PluginManager } from "./plugins"; @@ -24,39 +23,6 @@ export function useRef(name: string): { el: }; } -// ----------------------------------------------------------------------------- -// useEnv and useSubEnv -// ----------------------------------------------------------------------------- - -/** - * This hook is useful as a building block for some customized hooks, that may - * need a reference to the env of the component calling them. - */ -export function useEnv(): E { - return getCurrent().component.env as any; -} - -function extendEnv(currentEnv: Object, extension: Object): Object { - const env = Object.create(currentEnv); - const descrs = Object.getOwnPropertyDescriptors(extension); - return Object.freeze(Object.defineProperties(env, descrs)); -} - -/** - * This hook is a simple way to let components use a sub environment. Note that - * like for all hooks, it is important that this is only called in the - * constructor method. - */ -export function useSubEnv(envExtension: Env) { - const node = getCurrent(); - node.component.env = extendEnv(node.component.env as any, envExtension); - useChildSubEnv(envExtension); -} - -export function useChildSubEnv(envExtension: Env) { - const node = getCurrent(); - node.childEnv = extendEnv(node.childEnv, envExtension); -} // ----------------------------------------------------------------------------- // useEffect // ----------------------------------------------------------------------------- diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 040baae45..f3d055083 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -40,22 +40,14 @@ export { Component } from "./component"; export type { ComponentConstructor } from "./component"; export { useComponent } from "./component_node"; export { props } from "./props"; -export type { PropsValidation } from "./props"; +export type { GetProps, PropsValidation } from "./props"; export { status } from "./status"; export { proxy, markRaw, toRaw } from "./reactivity/proxy"; export { untrack } from "./reactivity/computations"; export { signal } from "./reactivity/signal"; export { derived } from "./reactivity/derived"; export { effect } from "./reactivity/effect"; -export { - useEffect, - useEnv, - useListener, - useRef, - useChildSubEnv, - useSubEnv, - usePlugins, -} from "./hooks"; +export { useEffect, useListener, useRef, usePlugins } from "./hooks"; export { batched, EventBus, htmlEscape, whenReady, markup } from "./utils"; export { onWillStart, diff --git a/src/runtime/props.ts b/src/runtime/props.ts index bfe3a0d1b..4967a37b6 100644 --- a/src/runtime/props.ts +++ b/src/runtime/props.ts @@ -1,35 +1,47 @@ import { OwlError } from "../common/owl_error"; +import type { Component } from "./component"; import { getCurrent } from "./component_node"; import { validateSchema } from "./validation"; -type ConstructorTypedPropsValidation = new (...args: any[]) => T; +type ConstructorTypedPropsValidation = new (...args: any) => T; type UnionTypedPropsValidation = ReadonlyArray; -interface SchemaTypedPropsValidation { - optional?: O; - validate?(value: any): boolean; -} +type OptionalSchemaTypedPropsValidation = { + optional: O; +}; -interface TypeSchemaTypedPropsValidation - extends SchemaTypedPropsValidation { - type: new (...args: any[]) => T; -} +type ValidableSchemaTypedPropsValidation = { + validate(value: any): boolean; +}; -interface MapSchemaTypedPropsValidation - extends TypeSchemaTypedPropsValidation { +type TypeSchemaTypedPropsValidation = { + type: new (...args: any) => T; +}; + +type MapSchemaTypedPropsValidation = { + type: ObjectConstructor; shape: PropsValidation; -} +}; -interface RecordSchemaTypedPropsValidation - extends TypeSchemaTypedPropsValidation { +type RecordSchemaTypedPropsValidation = { + type: ObjectConstructor; values: TypedPropsValidation; -} +}; -interface ArraySchemaTypedPropsValidation - extends TypeSchemaTypedPropsValidation { +type ArraySchemaTypedPropsValidation = { + type: ArrayConstructor; element: TypedPropsValidation; -} +}; + +type SchemaTypedPropsValidation = { + type?: new (...args: any) => T; + optional?: O; + validate?(value: T): boolean; + shape?: PropsValidation; + values?: TypedPropsValidation; + element?: TypedPropsValidation; +}; type ValueTypedPropsValidation = { value: T; @@ -39,90 +51,68 @@ type TypedPropsValidation = | true | ConstructorTypedPropsValidation | UnionTypedPropsValidation - | SchemaTypedPropsValidation - | TypeSchemaTypedPropsValidation - | MapSchemaTypedPropsValidation - | RecordSchemaTypedPropsValidation - | ArraySchemaTypedPropsValidation + | SchemaTypedPropsValidation | ValueTypedPropsValidation; export type RecordPropsValidation = Record; - export type KeysPropsValidation = readonly string[]; export type PropsValidation = RecordPropsValidation | KeysPropsValidation; //----------------------------------------------------------------------------- -type Optional = O extends true ? T | undefined : T; - -type ConvertConstructorTypedPropsValidation = - V extends ConstructorTypedPropsValidation ? I : never; - -type ConvertUnionTypedPropsValidation = V[number]; - -type ConvertMapSchemaTypedPropsValidation = - V extends MapSchemaTypedPropsValidation - ? Optional, O> - : never; - -type ConvertRecordSchemaTypedPropsValidation = - V extends RecordSchemaTypedPropsValidation - ? Optional<{ [K: string]: ConvertTypedPropsValidation }, O> - : never; - -type ConvertArraySchemaTypedPropsValidation = - V extends ArraySchemaTypedPropsValidation - ? Optional[], O> - : never; - -type ConvertTypeSchemaTypedPropsValidation = - V extends TypeSchemaTypedPropsValidation ? Optional : never; - -type ConvertSchemaTypedPropsValidation = - V extends SchemaTypedPropsValidation ? any : never; - -type ConvertValueTypedPropsValidation = - V extends ValueTypedPropsValidation ? T : never; - type ConvertTypedPropsValidation = V extends true ? any - : V extends ConstructorTypedPropsValidation - ? ConvertConstructorTypedPropsValidation + : V extends ConstructorTypedPropsValidation + ? I : V extends UnionTypedPropsValidation - ? ConvertUnionTypedPropsValidation + ? V[number] : V extends MapSchemaTypedPropsValidation - ? ConvertMapSchemaTypedPropsValidation + ? ConvertPropsValidation : V extends RecordSchemaTypedPropsValidation - ? ConvertRecordSchemaTypedPropsValidation + ? Record> : V extends ArraySchemaTypedPropsValidation - ? ConvertArraySchemaTypedPropsValidation - : V extends TypeSchemaTypedPropsValidation - ? ConvertTypeSchemaTypedPropsValidation - : V extends SchemaTypedPropsValidation - ? ConvertSchemaTypedPropsValidation - : V extends ValueTypedPropsValidation - ? ConvertValueTypedPropsValidation + ? ConvertTypedPropsValidation[] + : V extends TypeSchemaTypedPropsValidation + ? I + : V extends ValueTypedPropsValidation + ? T + : V extends OptionalSchemaTypedPropsValidation + ? any + : V extends ValidableSchemaTypedPropsValidation + ? any : never; -type ConvertRecordPropsValidation = { - [K in keyof V]: ConvertTypedPropsValidation; -}; - -type ConvertKeysPropsValidation = { [K in V[number]]: any }; - type ConvertPropsValidation = V extends KeysPropsValidation - ? ConvertKeysPropsValidation + ? { [K in V[number] as K extends `${infer N}?` ? N : never]?: any } & { + [K in V[number] as K extends `${string}?` ? never : K]: any; + } : V extends RecordPropsValidation - ? ConvertRecordPropsValidation + ? { + [K in keyof V as V[K] extends OptionalSchemaTypedPropsValidation + ? K + : never]?: ConvertTypedPropsValidation; + } & { + [K in keyof V as V[K] extends OptionalSchemaTypedPropsValidation + ? never + : K]: ConvertTypedPropsValidation; + } : never; //----------------------------------------------------------------------------- -type Props, V extends PropsValidation> = T & - ConvertPropsValidation; +declare const isProps: unique symbol; +type IsPropsObj = { [isProps]: true }; +export type Props = IsPropsObj & + (unknown extends T ? ConvertPropsValidation : T); + +export type GetProps = { + [K in keyof T]: T[K] extends IsPropsObj ? (x: Omit) => void : never; +}[keyof T] extends (x: infer I) => void + ? { [K in keyof I]: I[K] } + : never; -export function props, V extends PropsValidation = PropsValidation>( +export function props( validation?: V ): Props { const node = getCurrent(); diff --git a/tests/app/__snapshots__/app.test.ts.snap b/tests/app/__snapshots__/app.test.ts.snap index 407440771..877c01f83 100644 --- a/tests/app/__snapshots__/app.test.ts.snap +++ b/tests/app/__snapshots__/app.test.ts.snap @@ -1,20 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`app App supports env with getters/setters 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - - let block1 = createBlock(\`

\`); - - return function template(ctx, node, key = "") { - let txt1 = ctx['env'].someVal; - let txt2 = Object.keys(ctx['env'].services); - return block1([txt1, txt2]); - } -}" -`; - exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately on destroy 1`] = ` "function anonymous(app, bdom, helpers ) { diff --git a/tests/app/__snapshots__/sub_root.test.ts.snap b/tests/app/__snapshots__/sub_root.test.ts.snap index 3cf7d1457..163182ded 100644 --- a/tests/app/__snapshots__/sub_root.test.ts.snap +++ b/tests/app/__snapshots__/sub_root.test.ts.snap @@ -57,32 +57,6 @@ exports[`destroy a subroot while another component is mounted in main app 4`] = }" `; -exports[`subroot by default, 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 by default, 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(); - } -}" -`; - exports[`subroot can create a root in a setup function, then use a hook 1`] = ` "function anonymous(app, bdom, helpers ) { @@ -157,32 +131,6 @@ exports[`subroot can mount subroot inside own dom 2`] = ` }" `; -exports[`subroot env can be specified for sub roots 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 can be specified for sub roots 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 subcomponents can be destroyed, and it properly cleanup the subroots 1`] = ` "function anonymous(app, bdom, helpers ) { diff --git a/tests/app/app.test.ts b/tests/app/app.test.ts index 569d9ac84..e9aa79344 100644 --- a/tests/app/app.test.ts +++ b/tests/app/app.test.ts @@ -34,31 +34,6 @@ describe("app", () => { expect(status(comp)).toBe("destroyed"); }); - test("App supports env with getters/setters", async () => { - let someVal = "maggot"; - - const services: any = { serv1: "" }; - const env = { - get someVal() { - return someVal; - }, - services, - }; - - class SomeComponent extends Component { - static template = xml`
`; - } - - const app = new App({ env }); - const comp = await app.createRoot(SomeComponent).mount(fixture); - expect(fixture.innerHTML).toBe("
maggot serv1
"); - someVal = "brain"; - services.serv2 = ""; - comp.render(); - await nextTick(); - expect(fixture.innerHTML).toBe("
brain serv1,serv2
"); - }); - test("can configure an app with props", async () => { class SomeComponent extends Component { static template = xml`
`; diff --git a/tests/app/sub_root.test.ts b/tests/app/sub_root.test.ts index a6e36e851..e69612306 100644 --- a/tests/app/sub_root.test.ts +++ b/tests/app/sub_root.test.ts @@ -47,58 +47,6 @@ describe("subroot", () => { expect(status(subcomp)).toBe("destroyed"); }); - test("by default, 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(); - await app.createRoot(SC).mount(fixture); - const subRoot = app.createRoot(Sub); - await subRoot.mount(fixture); - - expect(env).toBeDefined(); - expect(subenv).toBeDefined(); - expect(env).toBe(subenv); - }); - - test("env can be specified for sub roots", async () => { - const env1 = { env1: true }; - const env2 = {}; - let someComponentEnv: any, subComponentEnv: any; - class SC extends SomeComponent { - setup() { - someComponentEnv = this.env; - } - } - class Sub extends SubComponent { - setup() { - subComponentEnv = this.env; - } - } - - const app = new App({ env: env1 }); - await app.createRoot(SC).mount(fixture); - const subRoot = app.createRoot(Sub, { env: env2 }); - await subRoot.mount(fixture); - - // because env is different in app => it is given a sub object, frozen and all - // not sure it is a good idea, but it's the way owl 2 works. maybe we should - // avoid doing anything with the main env and let user code do it if they - // want. in that case, we can change the test here to assert that they are equal - expect(someComponentEnv).not.toBe(env1); - expect(someComponentEnv!.env1).toBe(true); - expect(subComponentEnv).toBe(env2); - }); - test("subcomponents can be destroyed, and it properly cleanup the subroots", async () => { const app = new App(); const comp = await app.createRoot(SomeComponent).mount(fixture); diff --git a/tests/components/__snapshots__/concurrency.test.ts.snap b/tests/components/__snapshots__/concurrency.test.ts.snap index 11e1a5438..7544fd241 100644 --- a/tests/components/__snapshots__/concurrency.test.ts.snap +++ b/tests/components/__snapshots__/concurrency.test.ts.snap @@ -1779,7 +1779,7 @@ exports[`rendering component again in next microtick 1`] = ` return function template(ctx, node, key = "") { let b2; let hdlr1 = [ctx['onClick'], ctx]; - if (ctx['env'].config.flag) { + if (ctx['this'].state.config.flag) { b2 = comp1({}, key + \`__1\`, node, this, null); } return block1([hdlr1], [b2]); diff --git a/tests/components/__snapshots__/higher_order_component.test.ts.snap b/tests/components/__snapshots__/higher_order_component.test.ts.snap index 992756a23..61051d22b 100644 --- a/tests/components/__snapshots__/higher_order_component.test.ts.snap +++ b/tests/components/__snapshots__/higher_order_component.test.ts.snap @@ -35,10 +35,10 @@ exports[`basics can select a sub widget 1`] = ` return function template(ctx, node, key = "") { let b2, b3; - if (ctx['env'].options.flag) { + if (ctx['this'].state.options.flag) { b2 = comp1({}, key + \`__1\`, node, this, null); } - if (!ctx['env'].options.flag) { + if (!ctx['this'].state.options.flag) { b3 = comp2({}, key + \`__2\`, node, this, null); } return multi([b2, b3]); diff --git a/tests/components/__snapshots__/hooks.test.ts.snap b/tests/components/__snapshots__/hooks.test.ts.snap index 4d90b4ae9..a340bc3c1 100644 --- a/tests/components/__snapshots__/hooks.test.ts.snap +++ b/tests/components/__snapshots__/hooks.test.ts.snap @@ -74,20 +74,6 @@ exports[`hooks can use useComponent 1`] = ` }" `; -exports[`hooks can use useEnv 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - - let block1 = createBlock(\`
\`); - - return function template(ctx, node, key = "") { - let txt1 = ctx['env'].val; - return block1([txt1]); - } -}" -`; - exports[`hooks mounted callbacks should be called in reverse order from willUnmount callbacks 1`] = ` "function anonymous(app, bdom, helpers ) { @@ -102,94 +88,6 @@ exports[`hooks mounted callbacks should be called in reverse order from willUnmo }" `; -exports[`hooks parent and child env (with useChildSubEnv then useSubEnv) 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - const comp1 = app.createComponent(\`Child\`, true, false, false, []); - - return function template(ctx, node, key = "") { - const b2 = text(ctx['env'].val); - const b3 = comp1({}, key + \`__1\`, node, this, null); - return multi([b2, b3]); - } -}" -`; - -exports[`hooks parent and child env (with useChildSubEnv then useSubEnv) 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - - let block2 = createBlock(\`
\`); - - return function template(ctx, node, key = "") { - let b2; - if (ctx['env'].hasParent) { - let txt1 = ctx['env'].val; - b2 = block2([txt1]); - } - return multi([b2]); - } -}" -`; - -exports[`hooks parent and child env (with useChildSubEnv) 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - const comp1 = app.createComponent(\`Child\`, true, false, false, []); - - return function template(ctx, node, key = "") { - const b2 = text(ctx['env'].val); - const b3 = comp1({}, key + \`__1\`, node, this, null); - return multi([b2, b3]); - } -}" -`; - -exports[`hooks parent and child env (with useChildSubEnv) 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - - let block1 = createBlock(\`
\`); - - return function template(ctx, node, key = "") { - let txt1 = ctx['env'].val; - return block1([txt1]); - } -}" -`; - -exports[`hooks parent and child env (with useSubEnv) 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - const comp1 = app.createComponent(\`Child\`, true, false, false, []); - - return function template(ctx, node, key = "") { - const b2 = text(ctx['env'].val); - const b3 = comp1({}, key + \`__1\`, node, this, null); - return multi([b2, b3]); - } -}" -`; - -exports[`hooks parent and child env (with useSubEnv) 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - - let block1 = createBlock(\`
\`); - - return function template(ctx, node, key = "") { - let txt1 = ctx['env'].val; - return block1([txt1]); - } -}" -`; - exports[`hooks two different call to willPatch/patched should work 1`] = ` "function anonymous(app, bdom, helpers ) { @@ -204,47 +102,6 @@ exports[`hooks two different call to willPatch/patched should work 1`] = ` }" `; -exports[`hooks useChildSubEnv does not pollute user env 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - - let block1 = createBlock(\`
\`); - - return function template(ctx, node, key = "") { - let txt1 = ctx['env'].val; - return block1([txt1]); - } -}" -`; - -exports[`hooks useChildSubEnv supports arbitrary descriptor 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - const comp1 = app.createComponent(\`Child\`, true, false, false, []); - - return function template(ctx, node, key = "") { - return comp1({}, key + \`__1\`, node, this, null); - } -}" -`; - -exports[`hooks useChildSubEnv supports arbitrary descriptor 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - - let block1 = createBlock(\`
\`); - - return function template(ctx, node, key = "") { - let txt1 = ctx['env'].someVal; - let txt2 = ctx['env'].someVal2; - return block1([txt1, txt2]); - } -}" -`; - exports[`hooks useEffect hook dependencies prevent effects from rerunning when unchanged 1`] = ` "function anonymous(app, bdom, helpers ) { @@ -360,44 +217,3 @@ exports[`hooks useRef hook: basic use 1`] = ` } }" `; - -exports[`hooks useSubEnv modifies user env 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - - let block1 = createBlock(\`
\`); - - return function template(ctx, node, key = "") { - let txt1 = ctx['env'].val; - return block1([txt1]); - } -}" -`; - -exports[`hooks useSubEnv supports arbitrary descriptor 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - const comp1 = app.createComponent(\`Child\`, true, false, false, []); - - return function template(ctx, node, key = "") { - return comp1({}, key + \`__1\`, node, this, null); - } -}" -`; - -exports[`hooks useSubEnv supports arbitrary descriptor 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - - let block1 = createBlock(\`
\`); - - return function template(ctx, node, key = "") { - let txt1 = ctx['env'].someVal; - let txt2 = ctx['env'].someVal2; - return block1([txt1, txt2]); - } -}" -`; diff --git a/tests/components/__snapshots__/rendering.test.ts.snap b/tests/components/__snapshots__/rendering.test.ts.snap index 0cbe5912a..46ce35776 100644 --- a/tests/components/__snapshots__/rendering.test.ts.snap +++ b/tests/components/__snapshots__/rendering.test.ts.snap @@ -205,7 +205,7 @@ exports[`rendering semantics render with deep=true followed by render with deep= return function template(ctx, node, key = "") { const b2 = text(\`child\`); - const b3 = text(ctx['env'].getValue()); + const b3 = text(ctx['this'].state.getValue()); return multi([b2, b3]); } }" diff --git a/tests/components/concurrency.test.ts b/tests/components/concurrency.test.ts index 173e59e31..13a030670 100644 --- a/tests/components/concurrency.test.ts +++ b/tests/components/concurrency.test.ts @@ -807,23 +807,24 @@ test("rendering component again in next microtick", async () => { static template = xml`
- +
`; static components = { Child }; + state = state; setup() { useLogLifecycle(); } async onClick() { - this.env.config.flag = true; + this.state.config.flag = true; this.render(); await Promise.resolve(); this.render(); } } - const env = { config: { flag: false } }; - await mount(Parent, fixture, { env }); + const state = { config: { flag: false } }; + await mount(Parent, fixture); expect(fixture.innerHTML).toBe("
"); expect(steps.splice(0)).toMatchInlineSnapshot(` [ diff --git a/tests/components/env.test.ts b/tests/components/env.test.ts deleted file mode 100644 index 7c52240df..000000000 --- a/tests/components/env.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Component, mount, xml } from "../../src"; -import { makeTestFixture } from "../helpers"; - -let fixture: HTMLElement; - -beforeEach(() => { - fixture = makeTestFixture(); -}); - -describe("env handling", () => { - test("has an env by default", async () => { - class Test extends Component { - static template = xml`
`; - } - const component = await mount(Test, fixture); - expect(component.env).toEqual({}); - }); - - test.skip("env is shallow frozen", async () => { - const env = { foo: 42, bar: { value: 42 } }; - class Test extends Component { - static template = xml`
`; - } - const component = await mount(Test, fixture, { env }); - expect(Object.isFrozen(component.env)).toBeTruthy(); - expect(component.env).toEqual({ foo: 42, bar: { value: 42 } }); - expect(() => { - component.env.foo = 23; - }).toThrow(/Cannot assign to read only property 'foo' of object/); - component.env.bar.value = 23; - expect(component.env).toEqual({ foo: 42, bar: { value: 23 } }); - }); - - test("parent env is propagated to child components", async () => { - const env = { foo: 42, bar: { value: 42 } }; - let child: any = null; - - class Child extends Component { - static template = xml`
`; - setup() { - child = this; - } - } - - class Test extends Component { - static template = xml``; - static components = { Child }; - } - - await mount(Test, fixture, { env }); - expect(child.env).toEqual(env); - // we check that the frozen env maintain the same prototype chain - expect(Object.getPrototypeOf(child.env)).toBe(Object.getPrototypeOf(env)); - }); -}); diff --git a/tests/components/higher_order_component.test.ts b/tests/components/higher_order_component.test.ts index 90857de9b..396349c22 100644 --- a/tests/components/higher_order_component.test.ts +++ b/tests/components/higher_order_component.test.ts @@ -58,17 +58,18 @@ describe("basics", () => { class Parent extends Component { static template = xml` - - + + `; static components = { Child, OtherChild }; + state = state; } - const env = { options: { flag: true } }; - const parent = await mount(Parent, fixture, { env }); + const state = { options: { flag: true } }; + const parent = await mount(Parent, fixture); expect(fixture.innerHTML).toBe("CHILD 1"); - env.options.flag = false; + state.options.flag = false; parent.render(); await nextTick(); expect(fixture.innerHTML).toBe("
CHILD 2
"); diff --git a/tests/components/hooks.test.ts b/tests/components/hooks.test.ts index d80a25fcc..2bd9b2641 100644 --- a/tests/components/hooks.test.ts +++ b/tests/components/hooks.test.ts @@ -10,12 +10,9 @@ import { onWillUpdateProps, useComponent, useEffect, - useEnv, useListener, useRef, proxy, - useChildSubEnv, - useSubEnv, xml, OwlError, props, @@ -177,115 +174,6 @@ describe("hooks", () => { }); }); - test("can use useEnv", async () => { - expect.assertions(3); - class Test extends Component { - static template = xml`
`; - setup() { - expect(useEnv()).toBe(this.env); - } - } - const env = { val: 1 }; - await mount(Test, fixture, { env }); - expect(fixture.innerHTML).toBe("
1
"); - }); - - test("useSubEnv modifies user env", async () => { - class Test extends Component { - static template = xml`
`; - setup() { - useSubEnv({ val2: 1 }); - } - } - const env = { val: 3 }; - const component = await mount(Test, fixture, { env }); - expect(fixture.innerHTML).toBe("
3
"); - expect(component.env).toHaveProperty("val2"); - expect(component.env).toHaveProperty("val"); - }); - - test("useChildSubEnv does not pollute user env", async () => { - class Test extends Component { - static template = xml`
`; - setup() { - useChildSubEnv({ val2: 1 }); - } - } - const env = { val: 3 }; - const component = await mount(Test, fixture, { env }); - expect(fixture.innerHTML).toBe("
3
"); - expect(component.env).not.toHaveProperty("val2"); - expect(component.env).toHaveProperty("val"); - }); - - test("useSubEnv supports arbitrary descriptor", async () => { - let someVal = "maggot"; - let someVal2 = "brain"; - - class Child extends Component { - static template = xml`
`; - } - - class Test extends Component { - static template = xml``; - static components = { Child }; - setup() { - useSubEnv({ - get someVal2() { - return someVal2; - }, - }); - } - } - - const env = { - get someVal() { - return someVal; - }, - }; - const component = await mount(Test, fixture, { env }); - expect(fixture.innerHTML).toBe("
maggot brain
"); - someVal = "brain"; - someVal2 = "maggot"; - component.render(true); - await nextTick(); - expect(fixture.innerHTML).toBe("
brain maggot
"); - }); - - test("useChildSubEnv supports arbitrary descriptor", async () => { - let someVal = "maggot"; - let someVal2 = "brain"; - - class Child extends Component { - static template = xml`
`; - } - - class Test extends Component { - static template = xml``; - static components = { Child }; - setup() { - useChildSubEnv({ - get someVal2() { - return someVal2; - }, - }); - } - } - someVal = "maggot"; - const env = { - get someVal() { - return someVal; - }, - }; - const component = await mount(Test, fixture, { env }); - expect(fixture.innerHTML).toBe("
maggot brain
"); - someVal = "brain"; - someVal2 = "maggot"; - component.render(true); - await nextTick(); - expect(fixture.innerHTML).toBe("
brain maggot
"); - }); - test("can use useComponent", async () => { expect.assertions(2); class Test extends Component { @@ -297,58 +185,6 @@ describe("hooks", () => { await mount(Test, fixture); }); - test("parent and child env (with useSubEnv)", async () => { - class Child extends Component { - static template = xml`
`; - } - - class Parent extends Component { - static template = xml``; - static components = { Child }; - setup() { - useSubEnv({ val: 5 }); - } - } - const env = { val: 3 }; - await mount(Parent, fixture, { env }); - expect(fixture.innerHTML).toBe("5
5
"); - }); - - test("parent and child env (with useChildSubEnv)", async () => { - class Child extends Component { - static template = xml`
`; - } - - class Parent extends Component { - static template = xml``; - static components = { Child }; - setup() { - useChildSubEnv({ val: 5 }); - } - } - const env = { val: 3 }; - await mount(Parent, fixture, { env }); - expect(fixture.innerHTML).toBe("3
5
"); - }); - - test("parent and child env (with useChildSubEnv then useSubEnv)", async () => { - class Child extends Component { - static template = xml`
`; - } - - class Parent extends Component { - static template = xml``; - static components = { Child }; - setup() { - useChildSubEnv({ hasParent: true }); - useSubEnv({ val: 5 }); - } - } - const env = { val: 3 }; - await mount(Parent, fixture, { env }); - expect(fixture.innerHTML).toBe("5
5
"); - }); - test("can use onWillStart, onWillUpdateProps", async () => { const steps: string[] = []; async function slow(): Promise { diff --git a/tests/components/props_validation.test.ts b/tests/components/props_validation.test.ts index 1c1d17154..9b137ddf3 100644 --- a/tests/components/props_validation.test.ts +++ b/tests/components/props_validation.test.ts @@ -545,7 +545,7 @@ describe("props validation", () => { try { await mount(TestComponent, fixture, { dev: true, - props: { myprop: [{ a: 1 }] }, + props: { myprop: [{ a: 1 }] } as any, }); } catch (e) { error = e as Error; @@ -612,7 +612,7 @@ describe("props validation", () => { try { await mount(TestComponent, fixture, { dev: true, - props: { n: "str" }, + props: { n: "str" } as any, }); } catch (e) { error = e as Error; @@ -697,7 +697,7 @@ describe("props validation", () => { await expect( mount(SubComp, fixture, { dev: true, - props: { myprop: 1 }, + props: { myprop: 1 } as any, }) ).rejects.toThrow("Invalid props for component 'SubComp': 'myprop' is not a array"); }); @@ -746,7 +746,7 @@ describe("props validation", () => { await expect( mount(SubComp, fixture, { dev: true, - props: { message: 1, flag: true }, + props: { message: 1, flag: true } as any, }) ).rejects.toThrow(expect.anything()); }); @@ -781,7 +781,7 @@ describe("props validation", () => { await expect( mount(SubComp, fixture, { dev: true, - props: { message: null }, + props: { message: null } as any, }) ).rejects.toThrow(expect.anything()); }); diff --git a/tests/components/rendering.test.ts b/tests/components/rendering.test.ts index 43eb87fa1..83b417f41 100644 --- a/tests/components/rendering.test.ts +++ b/tests/components/rendering.test.ts @@ -146,7 +146,8 @@ describe("rendering semantics", () => { test("render with deep=true followed by render with deep=false work as expected", async () => { class Child extends Component { - static template = xml`child`; + static template = xml`child`; + state = state; setup() { useLogLifecycle(); } @@ -163,13 +164,13 @@ describe("rendering semantics", () => { } } let value = 3; - const env = { + const state = { getValue() { return value; }, }; - const parent = await mount(Parent, fixture, { env }); + const parent = await mount(Parent, fixture); expect(fixture.innerHTML).toBe("parentAchild3"); expect(steps.splice(0)).toMatchInlineSnapshot(` diff --git a/tests/misc/__snapshots__/portal.test.ts.snap b/tests/misc/__snapshots__/portal.test.ts.snap index 71945a66c..f5756bc0a 100644 --- a/tests/misc/__snapshots__/portal.test.ts.snap +++ b/tests/misc/__snapshots__/portal.test.ts.snap @@ -805,40 +805,6 @@ exports[`Portal portal with target not in dom 1`] = ` }" `; -exports[`Portal portal's parent's env is not polluted 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - const Portal = app.Portal; - const comp1 = app.createComponent(\`Child\`, true, false, false, []); - const comp2 = app.createComponent(null, false, true, false, false); - - let block1 = createBlock(\`
\`); - - function slot1(ctx, node, key = "") { - return comp1({}, key + \`__1\`, node, this, null); - } - - return function template(ctx, node, key = "") { - const b3 = comp2({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__2\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal portal's parent's env is not polluted 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - - let block1 = createBlock(\`\`); - - return function template(ctx, node, key = "") { - return block1(); - } -}" -`; - exports[`Portal simple catchError with portal 1`] = ` "function anonymous(app, bdom, helpers ) { diff --git a/tests/misc/portal.test.ts b/tests/misc/portal.test.ts index 46e0f63ef..132423708 100644 --- a/tests/misc/portal.test.ts +++ b/tests/misc/portal.test.ts @@ -518,26 +518,6 @@ describe("Portal", () => { expect(error!.message).toMatch(regexp); }); - test("portal's parent's env is not polluted", async () => { - class Child extends Component { - static template = xml` - `; - } - class Parent extends Component { - static components = { Child }; - static template = xml` -
- - - -
`; - } - const env = {}; - addOutsideDiv(fixture); - const parent = await mount(Parent, fixture, { env }); - expect(parent.env).toStrictEqual({}); - }); - test("Portal composed with t-slot", async () => { const steps: Array = []; let childInst: Component | null = null;