diff --git a/src/qweb/compiler.ts b/src/qweb/compiler.ts
index 411d0742..89a8d3c4 100644
--- a/src/qweb/compiler.ts
+++ b/src/qweb/compiler.ts
@@ -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();
diff --git a/src/qweb/parser.ts b/src/qweb/parser.ts
index bd5b3fcb..018a12a8 100644
--- a/src/qweb/parser.ts
+++ b/src/qweb/parser.ts
@@ -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 };
}
// -----------------------------------------------------------------------------
diff --git a/tests/components/__snapshots__/t_props.test.ts.snap b/tests/components/__snapshots__/t_props.test.ts.snap
new file mode 100644
index 00000000..6edb0a9a
--- /dev/null
+++ b/tests/components/__snapshots__/t_props.test.ts.snap
@@ -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(\`
\`);
+
+ 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(\`\`);
+
+ 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(\`
\`);
+
+ 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(\`\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let b2 = component(\`Comp\`, ctx['state'], key + \`__1\`, node, ctx);
+ return block1([], [b2]);
+ }
+}"
+`;
diff --git a/tests/components/basics.test.ts b/tests/components/basics.test.ts
index 90d63321..38593863 100644
--- a/tests/components/basics.test.ts
+++ b/tests/components/basics.test.ts
@@ -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``;
// }
diff --git a/tests/components/concurrency.test.ts b/tests/components/concurrency.test.ts
index 47258ea2..12a19841 100644
--- a/tests/components/concurrency.test.ts
+++ b/tests/components/concurrency.test.ts
@@ -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";
diff --git a/tests/components/t_props.test.ts b/tests/components/t_props.test.ts
new file mode 100644
index 00000000..f8d8d47a
--- /dev/null
+++ b/tests/components/t_props.test.ts
@@ -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`
`;
+ }
+ class Parent extends Component {
+ static components = { Comp };
+ static template = xml``;
+ 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`
`;
+ }
+ class Parent extends Component {
+ static components = { Comp };
+ static template = xml``;
+ 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");
+ });
+});
diff --git a/tests/qweb/parser.test.ts b/tests/qweb/parser.test.ts
index 93817400..c63d881d 100644
--- a/tests/qweb/parser.test.ts
+++ b/tests/qweb/parser.test.ts
@@ -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(``)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
+ dynamicProps: null,
props: {},
handlers: {},
slots: {},
@@ -835,6 +837,7 @@ describe("qweb parser", () => {
expect(parse(``)).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(``)).toEqual({
+ type: ASTType.TComponent,
+ name: "MyComponent",
+ dynamicProps: "state",
+ props: { a: "1" },
+ handlers: {},
+ isDynamic: false,
+ slots: {},
+ });
+ });
+
test("component with event handler", async () => {
expect(parse(``)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
+ dynamicProps: null,
props: {},
handlers: { click: "someMethod" },
isDynamic: false,
@@ -857,6 +873,7 @@ describe("qweb parser", () => {
expect(parse(`foo`)).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(`foo `)).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(``)).toEqual({
type: ASTType.TComponent,
name: "myComponent",
+ dynamicProps: null,
props: {},
handlers: {},
isDynamic: true,
@@ -942,6 +964,7 @@ describe("qweb parser", () => {
expect(parse(``)).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(``)).toEqual({
+ type: ASTType.TComponent,
+ name: "mycomponent",
+ dynamicProps: "state",
+ props: { a: "1" },
+ handlers: {},
+ isDynamic: true,
+ slots: {},
+ });
+ });
+
test("component with t-esc", async () => {
expect(parse(``)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
+ dynamicProps: null,
props: {},
handlers: {},
isDynamic: false,
@@ -964,6 +1000,7 @@ describe("qweb parser", () => {
expect(parse(``)).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" } },
},