mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] qweb: t-props directive
We reimplement the directive "t-props" and add some tests for it.
This commit is contained in:
committed by
Aaron Bohy
parent
8c71d99e5f
commit
b6eb4d009e
+10
-1
@@ -905,7 +905,16 @@ export class QWebCompiler {
|
|||||||
for (let p in ast.props) {
|
for (let p in ast.props) {
|
||||||
props.push(`${p}: ${compileExpr(ast.props[p]) || undefined}`);
|
props.push(`${p}: ${compileExpr(ast.props[p]) || undefined}`);
|
||||||
}
|
}
|
||||||
const propString = `{${props.join(",")}}`;
|
const propStr = `{${props.join(",")}}`;
|
||||||
|
|
||||||
|
let propString = propStr;
|
||||||
|
if (ast.dynamicProps) {
|
||||||
|
if (!props.length) {
|
||||||
|
propString = `${compileExpr(ast.dynamicProps)}`;
|
||||||
|
} else {
|
||||||
|
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// cmap key
|
// cmap key
|
||||||
const key = this.generateComponentKey();
|
const key = this.generateComponentKey();
|
||||||
|
|||||||
+5
-1
@@ -105,6 +105,7 @@ export interface ASTComponent {
|
|||||||
type: ASTType.TComponent;
|
type: ASTType.TComponent;
|
||||||
name: string;
|
name: string;
|
||||||
isDynamic: boolean;
|
isDynamic: boolean;
|
||||||
|
dynamicProps: string | null;
|
||||||
props: { [name: string]: string };
|
props: { [name: string]: string };
|
||||||
handlers: { [event: string]: string };
|
handlers: { [event: string]: string };
|
||||||
slots: { [name: string]: AST };
|
slots: { [name: string]: AST };
|
||||||
@@ -657,6 +658,9 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
node.removeAttribute("t-component");
|
node.removeAttribute("t-component");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dynamicProps = node.getAttribute("t-props");
|
||||||
|
node.removeAttribute("t-props");
|
||||||
|
|
||||||
const props: ASTComponent["props"] = {};
|
const props: ASTComponent["props"] = {};
|
||||||
const handlers: ASTComponent["handlers"] = {};
|
const handlers: ASTComponent["handlers"] = {};
|
||||||
for (let name of node.getAttributeNames()) {
|
for (let name of node.getAttributeNames()) {
|
||||||
@@ -706,7 +710,7 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
slots.default = defaultContent;
|
slots.default = defaultContent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { type: ASTType.TComponent, name, isDynamic, props, handlers, slots };
|
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, handlers, slots };
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`t-props t-props and other props 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-text-0/><block-text-1/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let d1 = ctx['props'].a;
|
||||||
|
let d2 = ctx['props'].b;
|
||||||
|
return block1([d1, d2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-props t-props and other props 2`] = `
|
||||||
|
"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><div><block-child-0/></div></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2 = component(\`Comp\`, Object.assign({}, ctx['state1'], {a: ctx['a']}), key + \`__1\`, node, ctx);
|
||||||
|
return block1([], [b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-props t-props only 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-text-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let d1 = ctx['props'].a;
|
||||||
|
return block1([d1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-props t-props only 2`] = `
|
||||||
|
"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><div><block-child-0/></div></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2 = component(\`Comp\`, ctx['state'], key + \`__1\`, node, ctx);
|
||||||
|
return block1([], [b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
@@ -914,8 +914,9 @@ describe.skip("mount special cases", () => {
|
|||||||
// expect(scheduler.tasks.length).toBe(0);
|
// expect(scheduler.tasks.length).toBe(0);
|
||||||
// expect(error).toBeDefined();
|
// expect(error).toBeDefined();
|
||||||
// expect(error.message).toBe("Cannot mount a destroyed component");
|
// expect(error.message).toBe("Cannot mount a destroyed component");
|
||||||
// });
|
});
|
||||||
// test("destroying a sub-component cleans itself from parent's vnode", async () => {
|
|
||||||
|
test("destroying a sub-component cleans itself from parent's vnode", async () => {
|
||||||
// class C1 extends Component {
|
// class C1 extends Component {
|
||||||
// static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
|
// static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { App, Component, mount, onWillStart, onWillUpdateProps, useState } from "../../src";
|
import { App, Component, mount, onWillStart, onWillUpdateProps, useState } from "../../src";
|
||||||
import { Fiber } from "../../src/component/fibers";
|
import { Fiber } from "../../src/component/fibers";
|
||||||
import { onMounted, onPatched, onWillPatch, onWillUnmount } from "../../src/component/lifecycle_hooks";
|
import {
|
||||||
|
onMounted,
|
||||||
|
onPatched,
|
||||||
|
onWillPatch,
|
||||||
|
onWillUnmount,
|
||||||
|
} from "../../src/component/lifecycle_hooks";
|
||||||
import { Scheduler } from "../../src/component/scheduler";
|
import { Scheduler } from "../../src/component/scheduler";
|
||||||
import { status } from "../../src/component/status";
|
import { status } from "../../src/component/status";
|
||||||
import { xml } from "../../src/tags";
|
import { xml } from "../../src/tags";
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { Component, mount, useState, xml } from "../../src";
|
||||||
|
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
||||||
|
|
||||||
|
snapshotEverything();
|
||||||
|
|
||||||
|
let fixture: HTMLElement;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = makeTestFixture();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("t-props", () => {
|
||||||
|
test("t-props only", async () => {
|
||||||
|
class Comp extends Component {
|
||||||
|
static template = xml`<div><t t-esc="props.a"/></div>`;
|
||||||
|
}
|
||||||
|
class Parent extends Component {
|
||||||
|
static components = { Comp };
|
||||||
|
static template = xml`<div><div><Comp t-props="state"/></div></div>`;
|
||||||
|
state = useState({
|
||||||
|
a: "first",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
expect(fixture.textContent).toBe("first");
|
||||||
|
parent.state.a = "";
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.textContent).toBe("");
|
||||||
|
parent.state.a = "fixed";
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.textContent).toBe("fixed");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-props and other props", async () => {
|
||||||
|
class Comp extends Component {
|
||||||
|
static template = xml`<div><t t-esc="props.a"/><t t-esc="props.b"/></div>`;
|
||||||
|
}
|
||||||
|
class Parent extends Component {
|
||||||
|
static components = { Comp };
|
||||||
|
static template = xml`<div><div><Comp t-props="state1" a="a"/></div></div>`;
|
||||||
|
state1 = useState({
|
||||||
|
a: "first",
|
||||||
|
b: "second",
|
||||||
|
});
|
||||||
|
a = "third";
|
||||||
|
}
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
expect(fixture.textContent).toBe("thirdsecond");
|
||||||
|
delete parent.a;
|
||||||
|
parent.render();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.textContent).toBe("second");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -699,6 +699,7 @@ describe("qweb parser", () => {
|
|||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
name: "Comp",
|
name: "Comp",
|
||||||
handlers: {},
|
handlers: {},
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
slots: {},
|
slots: {},
|
||||||
},
|
},
|
||||||
@@ -824,6 +825,7 @@ describe("qweb parser", () => {
|
|||||||
expect(parse(`<MyComponent />`)).toEqual({
|
expect(parse(`<MyComponent />`)).toEqual({
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "MyComponent",
|
name: "MyComponent",
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
handlers: {},
|
handlers: {},
|
||||||
slots: {},
|
slots: {},
|
||||||
@@ -835,6 +837,7 @@ describe("qweb parser", () => {
|
|||||||
expect(parse(`<MyComponent a="1" b="'b'"/>`)).toEqual({
|
expect(parse(`<MyComponent a="1" b="'b'"/>`)).toEqual({
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "MyComponent",
|
name: "MyComponent",
|
||||||
|
dynamicProps: null,
|
||||||
props: { a: "1", b: "'b'" },
|
props: { a: "1", b: "'b'" },
|
||||||
handlers: {},
|
handlers: {},
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
@@ -842,10 +845,23 @@ describe("qweb parser", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("component with t-props", async () => {
|
||||||
|
expect(parse(`<MyComponent t-props="state" a="1"/>`)).toEqual({
|
||||||
|
type: ASTType.TComponent,
|
||||||
|
name: "MyComponent",
|
||||||
|
dynamicProps: "state",
|
||||||
|
props: { a: "1" },
|
||||||
|
handlers: {},
|
||||||
|
isDynamic: false,
|
||||||
|
slots: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("component with event handler", async () => {
|
test("component with event handler", async () => {
|
||||||
expect(parse(`<MyComponent t-on-click="someMethod"/>`)).toEqual({
|
expect(parse(`<MyComponent t-on-click="someMethod"/>`)).toEqual({
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "MyComponent",
|
name: "MyComponent",
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
handlers: { click: "someMethod" },
|
handlers: { click: "someMethod" },
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
@@ -857,6 +873,7 @@ describe("qweb parser", () => {
|
|||||||
expect(parse(`<MyComponent>foo</MyComponent>`)).toEqual({
|
expect(parse(`<MyComponent>foo</MyComponent>`)).toEqual({
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "MyComponent",
|
name: "MyComponent",
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
handlers: {},
|
handlers: {},
|
||||||
@@ -869,6 +886,7 @@ describe("qweb parser", () => {
|
|||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "MyComponent",
|
name: "MyComponent",
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
handlers: {},
|
handlers: {},
|
||||||
slots: {
|
slots: {
|
||||||
@@ -888,6 +906,7 @@ describe("qweb parser", () => {
|
|||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "MyComponent",
|
name: "MyComponent",
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
handlers: {},
|
handlers: {},
|
||||||
slots: { name: { type: ASTType.Text, value: "foo" } },
|
slots: { name: { type: ASTType.Text, value: "foo" } },
|
||||||
@@ -898,6 +917,7 @@ describe("qweb parser", () => {
|
|||||||
expect(parse(`<MyComponent><t t-set-slot="name">foo</t> </MyComponent>`)).toEqual({
|
expect(parse(`<MyComponent><t t-set-slot="name">foo</t> </MyComponent>`)).toEqual({
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "MyComponent",
|
name: "MyComponent",
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
handlers: {},
|
handlers: {},
|
||||||
@@ -917,6 +937,7 @@ describe("qweb parser", () => {
|
|||||||
expect(parse(template)).toEqual({
|
expect(parse(template)).toEqual({
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "MyComponent",
|
name: "MyComponent",
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
handlers: {},
|
handlers: {},
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
@@ -931,6 +952,7 @@ describe("qweb parser", () => {
|
|||||||
expect(parse(`<t t-component="myComponent" />`)).toEqual({
|
expect(parse(`<t t-component="myComponent" />`)).toEqual({
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "myComponent",
|
name: "myComponent",
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
handlers: {},
|
handlers: {},
|
||||||
isDynamic: true,
|
isDynamic: true,
|
||||||
@@ -942,6 +964,7 @@ describe("qweb parser", () => {
|
|||||||
expect(parse(`<t t-component="mycomponent" a="1" b="'b'"/>`)).toEqual({
|
expect(parse(`<t t-component="mycomponent" a="1" b="'b'"/>`)).toEqual({
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "mycomponent",
|
name: "mycomponent",
|
||||||
|
dynamicProps: null,
|
||||||
props: { a: "1", b: "'b'" },
|
props: { a: "1", b: "'b'" },
|
||||||
handlers: {},
|
handlers: {},
|
||||||
isDynamic: true,
|
isDynamic: true,
|
||||||
@@ -949,10 +972,23 @@ describe("qweb parser", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("dynamic component with t-props", async () => {
|
||||||
|
expect(parse(`<t t-component="mycomponent" t-props="state" a="1"/>`)).toEqual({
|
||||||
|
type: ASTType.TComponent,
|
||||||
|
name: "mycomponent",
|
||||||
|
dynamicProps: "state",
|
||||||
|
props: { a: "1" },
|
||||||
|
handlers: {},
|
||||||
|
isDynamic: true,
|
||||||
|
slots: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("component with t-esc", async () => {
|
test("component with t-esc", async () => {
|
||||||
expect(parse(`<MyComponent t-esc="someValue"/>`)).toEqual({
|
expect(parse(`<MyComponent t-esc="someValue"/>`)).toEqual({
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "MyComponent",
|
name: "MyComponent",
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
handlers: {},
|
handlers: {},
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
@@ -964,6 +1000,7 @@ describe("qweb parser", () => {
|
|||||||
expect(parse(`<MyComponent t-call="subTemplate"/>`)).toEqual({
|
expect(parse(`<MyComponent t-call="subTemplate"/>`)).toEqual({
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "MyComponent",
|
name: "MyComponent",
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
handlers: {},
|
handlers: {},
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
@@ -982,6 +1019,7 @@ describe("qweb parser", () => {
|
|||||||
expect(parse(template)).toEqual({
|
expect(parse(template)).toEqual({
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "MyComponent",
|
name: "MyComponent",
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
handlers: {},
|
handlers: {},
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
@@ -991,6 +1029,7 @@ describe("qweb parser", () => {
|
|||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
handlers: {},
|
handlers: {},
|
||||||
name: "Child",
|
name: "Child",
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
slots: { brol: { type: ASTType.Text, value: "coucou" } },
|
slots: { brol: { type: ASTType.Text, value: "coucou" } },
|
||||||
},
|
},
|
||||||
@@ -1009,6 +1048,7 @@ describe("qweb parser", () => {
|
|||||||
expect(parse(template)).toEqual({
|
expect(parse(template)).toEqual({
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
name: "MyComponent",
|
name: "MyComponent",
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
handlers: {},
|
handlers: {},
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
@@ -1018,6 +1058,7 @@ describe("qweb parser", () => {
|
|||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
handlers: {},
|
handlers: {},
|
||||||
name: "Child",
|
name: "Child",
|
||||||
|
dynamicProps: null,
|
||||||
props: {},
|
props: {},
|
||||||
slots: { brol: { type: ASTType.Text, value: "coucou" } },
|
slots: { brol: { type: ASTType.Text, value: "coucou" } },
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user