[IMP] qweb: t-props directive

We reimplement the directive "t-props" and add some tests for it.
This commit is contained in:
Mathieu Duckerts-Antoine
2021-10-20 15:25:32 +02:00
committed by Géry Debongnie
parent 700d574314
commit ba2fe3ff55
7 changed files with 181 additions and 5 deletions
+10 -1
View File
@@ -905,7 +905,16 @@ export class QWebCompiler {
for (let p in ast.props) {
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
const key = this.generateComponentKey();
+5 -1
View File
@@ -105,6 +105,7 @@ export interface ASTComponent {
type: ASTType.TComponent;
name: string;
isDynamic: boolean;
dynamicProps: string | null;
props: { [name: string]: string };
handlers: { [event: string]: string };
slots: { [name: string]: AST };
@@ -657,6 +658,9 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
node.removeAttribute("t-component");
}
const dynamicProps = node.getAttribute("t-props");
node.removeAttribute("t-props");
const props: ASTComponent["props"] = {};
const handlers: ASTComponent["handlers"] = {};
for (let name of node.getAttributeNames()) {
@@ -706,7 +710,7 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
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]);
}
}"
`;
+3 -2
View File
@@ -914,8 +914,9 @@ describe.skip("mount special cases", () => {
// expect(scheduler.tasks.length).toBe(0);
// expect(error).toBeDefined();
// 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 {
// static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
// }
+6 -1
View File
@@ -1,6 +1,11 @@
import { App, Component, mount, onWillStart, onWillUpdateProps, useState } from "../../src";
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 { status } from "../../src/component/status";
import { xml } from "../../src/tags";
+54
View File
@@ -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");
});
});
+41
View File
@@ -699,6 +699,7 @@ describe("qweb parser", () => {
isDynamic: false,
name: "Comp",
handlers: {},
dynamicProps: null,
props: {},
slots: {},
},
@@ -824,6 +825,7 @@ describe("qweb parser", () => {
expect(parse(`<MyComponent />`)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: {},
handlers: {},
slots: {},
@@ -835,6 +837,7 @@ describe("qweb parser", () => {
expect(parse(`<MyComponent a="1" b="'b'"/>`)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: { a: "1", b: "'b'" },
handlers: {},
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 () => {
expect(parse(`<MyComponent t-on-click="someMethod"/>`)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: {},
handlers: { click: "someMethod" },
isDynamic: false,
@@ -857,6 +873,7 @@ describe("qweb parser", () => {
expect(parse(`<MyComponent>foo</MyComponent>`)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: {},
isDynamic: false,
handlers: {},
@@ -869,6 +886,7 @@ describe("qweb parser", () => {
type: ASTType.TComponent,
name: "MyComponent",
isDynamic: false,
dynamicProps: null,
props: {},
handlers: {},
slots: {
@@ -888,6 +906,7 @@ describe("qweb parser", () => {
type: ASTType.TComponent,
name: "MyComponent",
isDynamic: false,
dynamicProps: null,
props: {},
handlers: {},
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({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: {},
isDynamic: false,
handlers: {},
@@ -917,6 +937,7 @@ describe("qweb parser", () => {
expect(parse(template)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: {},
handlers: {},
isDynamic: false,
@@ -931,6 +952,7 @@ describe("qweb parser", () => {
expect(parse(`<t t-component="myComponent" />`)).toEqual({
type: ASTType.TComponent,
name: "myComponent",
dynamicProps: null,
props: {},
handlers: {},
isDynamic: true,
@@ -942,6 +964,7 @@ describe("qweb parser", () => {
expect(parse(`<t t-component="mycomponent" a="1" b="'b'"/>`)).toEqual({
type: ASTType.TComponent,
name: "mycomponent",
dynamicProps: null,
props: { a: "1", b: "'b'" },
handlers: {},
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 () => {
expect(parse(`<MyComponent t-esc="someValue"/>`)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: {},
handlers: {},
isDynamic: false,
@@ -964,6 +1000,7 @@ describe("qweb parser", () => {
expect(parse(`<MyComponent t-call="subTemplate"/>`)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: {},
handlers: {},
isDynamic: false,
@@ -982,6 +1019,7 @@ describe("qweb parser", () => {
expect(parse(template)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: {},
handlers: {},
isDynamic: false,
@@ -991,6 +1029,7 @@ describe("qweb parser", () => {
isDynamic: false,
handlers: {},
name: "Child",
dynamicProps: null,
props: {},
slots: { brol: { type: ASTType.Text, value: "coucou" } },
},
@@ -1009,6 +1048,7 @@ describe("qweb parser", () => {
expect(parse(template)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: {},
handlers: {},
isDynamic: false,
@@ -1018,6 +1058,7 @@ describe("qweb parser", () => {
isDynamic: false,
handlers: {},
name: "Child",
dynamicProps: null,
props: {},
slots: { brol: { type: ASTType.Text, value: "coucou" } },
},