mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] component: defaultProps application
We re-add the application of defaultProps. Note that the application is done twice in dev mode.
This commit is contained in:
committed by
Aaron Bohy
parent
c16d8ed6de
commit
a0e1af83ac
@@ -9,6 +9,7 @@ import {
|
|||||||
RootFiber,
|
RootFiber,
|
||||||
__internal__destroyed,
|
__internal__destroyed,
|
||||||
} from "./fibers";
|
} from "./fibers";
|
||||||
|
import { applyDefaultProps } from "./props_validation";
|
||||||
import { STATUS } from "./status";
|
import { STATUS } from "./status";
|
||||||
|
|
||||||
export function component(
|
export function component(
|
||||||
@@ -85,6 +86,7 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
|||||||
constructor(C: T, props: any, app: App) {
|
constructor(C: T, props: any, app: App) {
|
||||||
currentNode = this;
|
currentNode = this;
|
||||||
this.app = app;
|
this.app = app;
|
||||||
|
applyDefaultProps(props, C);
|
||||||
this.component = new C(props, app.env, this) as any;
|
this.component = new C(props, app.env, this) as any;
|
||||||
this.renderFn = app.getTemplate(C.template).bind(null, this.component, this);
|
this.renderFn = app.getTemplate(C.template).bind(null, this.component, this);
|
||||||
this.component.setup();
|
this.component.setup();
|
||||||
@@ -185,6 +187,7 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
|||||||
parentFiber.root.patched.push(fiber);
|
parentFiber.root.patched.push(fiber);
|
||||||
}
|
}
|
||||||
const component = this.component;
|
const component = this.component;
|
||||||
|
applyDefaultProps(props, component.constructor as any);
|
||||||
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
||||||
await prom;
|
await prom;
|
||||||
if (fiber !== this.fiber) {
|
if (fiber !== this.fiber) {
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
import { Component } from "./component";
|
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
|
// Prop validation helper
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -17,6 +34,8 @@ export const validateProps = function (name: string | typeof Component, props: a
|
|||||||
const ComponentClass =
|
const ComponentClass =
|
||||||
typeof name !== "string" ? name : parent.constructor.components[name as string];
|
typeof name !== "string" ? name : parent.constructor.components[name as string];
|
||||||
|
|
||||||
|
applyDefaultProps(props, ComponentClass);
|
||||||
|
|
||||||
const propsDef = (<any>ComponentClass).props;
|
const propsDef = (<any>ComponentClass).props;
|
||||||
if (propsDef instanceof Array) {
|
if (propsDef instanceof Array) {
|
||||||
// list of strings (prop names)
|
// list of strings (prop names)
|
||||||
|
|||||||
@@ -1,5 +1,56 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// 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(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
|
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(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
|
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(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
|
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`] = `
|
exports[`props validation can validate a prop with multiple types 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"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(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
|
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`] = `
|
exports[`props validation props are validated in dev mode (code snapshot) 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { makeTestFixture, nextTick, snapshotApp } from "../helpers";
|
import { makeTestFixture, snapshotApp } from "../helpers";
|
||||||
import { Component, useState, xml } from "../../src";
|
import { Component, xml } from "../../src";
|
||||||
import { App, DEV_MSG } from "../../src/app";
|
import { App, DEV_MSG } from "../../src/app";
|
||||||
import { validateProps } from "../../src/component/props_validation";
|
import { validateProps } from "../../src/component/props_validation";
|
||||||
|
|
||||||
@@ -680,7 +680,7 @@ describe("props validation", () => {
|
|||||||
expect(error.message).toBe("Missing props 'p' (component 'SubComp')");
|
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
|
// need to do something about errors catched in render
|
||||||
class SubComp extends Component {
|
class SubComp extends Component {
|
||||||
static props = { p: { type: Number } };
|
static props = { p: { type: Number } };
|
||||||
@@ -690,13 +690,13 @@ describe("props validation", () => {
|
|||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static template = xml`<div><SubComp p="state.p"/></div>`;
|
static template = xml`<div><SubComp p="state.p"/></div>`;
|
||||||
static components = { SubComp };
|
static components = { SubComp };
|
||||||
state: any = useState({ p: 1 });
|
state: any = { p: 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = await mountApp(Parent);
|
const app = await mountApp(Parent);
|
||||||
expect(fixture.innerHTML).toBe("<div><div>1</div></div>");
|
expect(fixture.innerHTML).toBe("<div><div>1</div></div>");
|
||||||
(app as any).root.component.state.p = undefined;
|
(app as any).root.component.state.p = undefined;
|
||||||
await nextTick();
|
await (app as any).root.component.render();
|
||||||
expect(fixture.innerHTML).toBe("<div><div>4</div></div>");
|
expect(fixture.innerHTML).toBe("<div><div>4</div></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -727,51 +727,48 @@ describe("props validation", () => {
|
|||||||
// Default props
|
// Default props
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
describe.skip("default props", () => {
|
describe("default props", () => {
|
||||||
test("can set default values", async () => {
|
test("can set default values", async () => {
|
||||||
// class SubComp extends Component {
|
class SubComp extends Component {
|
||||||
// static defaultProps = { p: 4 };
|
static defaultProps = { p: 4 };
|
||||||
// static template = xml`<div><t t-esc="props.p"/></div>`;
|
static template = xml`<div><t t-esc="props.p"/></div>`;
|
||||||
// }
|
}
|
||||||
// class Parent extends Component {
|
class Parent extends Component {
|
||||||
// static template = xml`<div><SubComp /></div>`;
|
static template = xml`<div><SubComp /></div>`;
|
||||||
// static components = { SubComp };
|
static components = { SubComp };
|
||||||
// }
|
}
|
||||||
// const w = new Parent();
|
await mountApp(Parent);
|
||||||
// await w.mount(fixture);
|
expect(fixture.innerHTML).toBe("<div><div>4</div></div>");
|
||||||
// expect(fixture.innerHTML).toBe("<div><div>4</div></div>");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("default values are also set whenever component is updated", async () => {
|
test("default values are also set whenever component is updated", async () => {
|
||||||
// class SubComp extends Component {
|
class SubComp extends Component {
|
||||||
// static template = xml`<div><t t-esc="props.p"/></div>`;
|
static template = xml`<div><t t-esc="props.p"/></div>`;
|
||||||
// static defaultProps = { p: 4 };
|
static defaultProps = { p: 4 };
|
||||||
// }
|
}
|
||||||
// class Parent extends Component {
|
class Parent extends Component {
|
||||||
// static template = xml`<div><SubComp p="state.p"/></div>`;
|
static template = xml`<div><SubComp p="state.p"/></div>`;
|
||||||
// static components = { SubComp };
|
static components = { SubComp };
|
||||||
// state: any = useState({ p: 1 });
|
state: any = { p: 1 };
|
||||||
// }
|
}
|
||||||
// const w = new Parent();
|
const app = await mountApp(Parent);
|
||||||
// await w.mount(fixture);
|
expect(fixture.innerHTML).toBe("<div><div>1</div></div>");
|
||||||
// expect(fixture.innerHTML).toBe("<div><div>1</div></div>");
|
(app as any).root.component.state.p = undefined;
|
||||||
// w.state.p = undefined;
|
await (app as any).root.component.render();
|
||||||
// await nextTick();
|
expect(fixture.innerHTML).toBe("<div><div>4</div></div>");
|
||||||
// expect(fixture.innerHTML).toBe("<div><div>4</div></div>");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can set default required boolean values", async () => {
|
test("can set default required boolean values", async () => {
|
||||||
// class SubComp extends Component {
|
class SubComp extends Component {
|
||||||
// static props = ["p", "q"];
|
static props = ["p", "q"];
|
||||||
// static defaultProps = { p: true, q: false };
|
static defaultProps = { p: true, q: false };
|
||||||
// static template = xml`<span><t t-if="props.p">hey</t><t t-if="!props.q">hey</t></span>`;
|
static template = xml`<span><t t-if="props.p">hey</t><t t-if="!props.q">hey</t></span>`;
|
||||||
// }
|
}
|
||||||
// class App extends Component {
|
class Parent extends Component {
|
||||||
// static template = xml`<div><SubComp/></div>`;
|
static template = xml`<div><SubComp/></div>`;
|
||||||
// static components = { SubComp };
|
static components = { SubComp };
|
||||||
// }
|
}
|
||||||
// const w = new App(undefined, {});
|
await mountApp(Parent);
|
||||||
// await w.mount(fixture);
|
expect(fixture.innerHTML).toBe("<div><span>heyhey</span></div>");
|
||||||
// expect(fixture.innerHTML).toBe("<div><span>heyhey</span></div>");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user