diff --git a/src/component/component_node.ts b/src/component/component_node.ts index 6c3ba1ac..f9f69139 100644 --- a/src/component/component_node.ts +++ b/src/component/component_node.ts @@ -9,6 +9,7 @@ import { RootFiber, __internal__destroyed, } from "./fibers"; +import { applyDefaultProps } from "./props_validation"; import { STATUS } from "./status"; export function component( @@ -85,6 +86,7 @@ export class ComponentNode implements VNode implements VNode f.call(component, props))); await prom; if (fiber !== this.fiber) { diff --git a/src/component/props_validation.ts b/src/component/props_validation.ts index 3de1322a..a7be4e35 100644 --- a/src/component/props_validation.ts +++ b/src/component/props_validation.ts @@ -1,5 +1,22 @@ import { Component } from "./component"; +/** + * Apply default props (only top level). + * + * Note that this method does modify in place the props + */ +export function applyDefaultProps(props: { [key: string]: any }, ComponentClass: typeof Component) { + const defaultProps = (ComponentClass as any).defaultProps + if (defaultProps) { + for (let propName in defaultProps) { + if (props![propName] === undefined) { + props![propName] = defaultProps[propName]; + } + } + } +} + + //------------------------------------------------------------------------------ // Prop validation helper //------------------------------------------------------------------------------ @@ -17,6 +34,8 @@ export const validateProps = function (name: string | typeof Component, props: a const ComponentClass = typeof name !== "string" ? name : parent.constructor.components[name as string]; + applyDefaultProps(props, ComponentClass); + const propsDef = (ComponentClass).props; if (propsDef instanceof Array) { // list of strings (prop names) diff --git a/tests/components/__snapshots__/props_validation.test.ts.snap b/tests/components/__snapshots__/props_validation.test.ts.snap index 96fc3aae..10276f26 100644 --- a/tests/components/__snapshots__/props_validation.test.ts.snap +++ b/tests/components/__snapshots__/props_validation.test.ts.snap @@ -1,5 +1,56 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`default props can set default required boolean values 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + const props2 = {} + helpers.validateProps(\`SubComp\`, props2, ctx) + let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`default props can set default values 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + const props2 = {} + helpers.validateProps(\`SubComp\`, props2, ctx) + let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + +exports[`default props default values are also set whenever component is updated 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + const props2 = {p: ctx['state'].p} + helpers.validateProps(\`SubComp\`, props2, ctx) + let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + exports[`props validation can validate a prop with multiple types 1`] = ` "function anonymous(bdom, helpers ) { @@ -204,6 +255,23 @@ exports[`props validation can validate recursively complicated prop def 2`] = ` }" `; +exports[`props validation default values are applied before validating props at update 1`] = ` +"function anonymous(bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, component } = bdom; + let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers; + + let block1 = createBlock(\`
\`); + + return function template(ctx, node, key = \\"\\") { + const props2 = {p: ctx['state'].p} + helpers.validateProps(\`SubComp\`, props2, ctx) + let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); + return block1([], [b2]); + } +}" +`; + exports[`props validation props are validated in dev mode (code snapshot) 1`] = ` "function anonymous(bdom, helpers ) { diff --git a/tests/components/props_validation.test.ts b/tests/components/props_validation.test.ts index f6e0a21b..fb77debd 100644 --- a/tests/components/props_validation.test.ts +++ b/tests/components/props_validation.test.ts @@ -1,5 +1,5 @@ -import { makeTestFixture, nextTick, snapshotApp } from "../helpers"; -import { Component, useState, xml } from "../../src"; +import { makeTestFixture, snapshotApp } from "../helpers"; +import { Component, xml } from "../../src"; import { App, DEV_MSG } from "../../src/app"; import { validateProps } from "../../src/component/props_validation"; @@ -680,7 +680,7 @@ describe("props validation", () => { expect(error.message).toBe("Missing props 'p' (component 'SubComp')"); }); - test.skip("default values are applied before validating props at update", async () => { + test("default values are applied before validating props at update", async () => { // need to do something about errors catched in render class SubComp extends Component { static props = { p: { type: Number } }; @@ -690,13 +690,13 @@ describe("props validation", () => { class Parent extends Component { static template = xml`
`; static components = { SubComp }; - state: any = useState({ p: 1 }); + state: any = { p: 1 }; } const app = await mountApp(Parent); expect(fixture.innerHTML).toBe("
1
"); (app as any).root.component.state.p = undefined; - await nextTick(); + await (app as any).root.component.render(); expect(fixture.innerHTML).toBe("
4
"); }); @@ -727,51 +727,48 @@ describe("props validation", () => { // Default props //------------------------------------------------------------------------------ -describe.skip("default props", () => { +describe("default props", () => { test("can set default values", async () => { - // class SubComp extends Component { - // static defaultProps = { p: 4 }; - // static template = xml`
`; - // } - // class Parent extends Component { - // static template = xml`
`; - // static components = { SubComp }; - // } - // const w = new Parent(); - // await w.mount(fixture); - // expect(fixture.innerHTML).toBe("
4
"); + class SubComp extends Component { + static defaultProps = { p: 4 }; + static template = xml`
`; + } + class Parent extends Component { + static template = xml`
`; + static components = { SubComp }; + } + await mountApp(Parent); + expect(fixture.innerHTML).toBe("
4
"); }); test("default values are also set whenever component is updated", async () => { - // class SubComp extends Component { - // static template = xml`
`; - // static defaultProps = { p: 4 }; - // } - // class Parent extends Component { - // static template = xml`
`; - // static components = { SubComp }; - // state: any = useState({ p: 1 }); - // } - // const w = new Parent(); - // await w.mount(fixture); - // expect(fixture.innerHTML).toBe("
1
"); - // w.state.p = undefined; - // await nextTick(); - // expect(fixture.innerHTML).toBe("
4
"); + class SubComp extends Component { + static template = xml`
`; + static defaultProps = { p: 4 }; + } + class Parent extends Component { + static template = xml`
`; + static components = { SubComp }; + state: any = { p: 1 }; + } + const app = await mountApp(Parent); + expect(fixture.innerHTML).toBe("
1
"); + (app as any).root.component.state.p = undefined; + await (app as any).root.component.render(); + expect(fixture.innerHTML).toBe("
4
"); }); test("can set default required boolean values", async () => { - // class SubComp extends Component { - // static props = ["p", "q"]; - // static defaultProps = { p: true, q: false }; - // static template = xml`heyhey`; - // } - // class App extends Component { - // static template = xml`
`; - // static components = { SubComp }; - // } - // const w = new App(undefined, {}); - // await w.mount(fixture); - // expect(fixture.innerHTML).toBe("
heyhey
"); + class SubComp extends Component { + static props = ["p", "q"]; + static defaultProps = { p: true, q: false }; + static template = xml`heyhey`; + } + class Parent extends Component { + static template = xml`
`; + static components = { SubComp }; + } + await mountApp(Parent); + expect(fixture.innerHTML).toBe("
heyhey
"); }); });