diff --git a/src/blockdom/toggler.ts b/src/blockdom/toggler.ts
index 367be569..d31c84ad 100644
--- a/src/blockdom/toggler.ts
+++ b/src/blockdom/toggler.ts
@@ -43,7 +43,9 @@ class VToggler {
}
}
- beforeRemove() {}
+ beforeRemove() {
+ this.child.beforeRemove();
+ }
remove() {
this.child.remove();
diff --git a/src/qweb/compiler.ts b/src/qweb/compiler.ts
index b445bae7..15e92dd0 100644
--- a/src/qweb/compiler.ts
+++ b/src/qweb/compiler.ts
@@ -123,6 +123,7 @@ interface Context {
preventRoot?: boolean;
isLast?: boolean;
translate: boolean;
+ tKeyExpr: string | null;
}
function createContext(parentCtx: Context, params?: Partial) {
@@ -132,6 +133,7 @@ function createContext(parentCtx: Context, params?: Partial) {
index: 0,
forceNewBlock: true,
translate: parentCtx.translate,
+ tKeyExpr: null,
},
params
);
@@ -169,7 +171,6 @@ export class QWebCompiler {
nextBlockId = 1;
shouldProtectScope: boolean = false;
shouldDefineAssign: boolean = false;
- shouldDefineKey0: boolean = false;
hasSafeContext: boolean | null = null;
hasRef: boolean = false;
// hasTCall: boolean = false;
@@ -212,6 +213,7 @@ export class QWebCompiler {
forceNewBlock: false,
isLast: true,
translate: true,
+ tKeyExpr: null,
});
const code = this.generateCode();
return new Function("bdom, helpers", code) as TemplateFunction;
@@ -257,12 +259,22 @@ export class QWebCompiler {
insertBlock(expression: string, block: BlockDescription, ctx: Context): string | null {
let id: string | null = null;
- const blockExpr = block.generateExpr(expression);
+ let blockExpr = block.generateExpr(expression);
+ const tKeyExpr = ctx.tKeyExpr;
if (block.parentVar) {
- this.addLine(
- `${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, key${this.target.loopLevel});`
- );
- } else if (block.isRoot && !ctx.preventRoot) {
+ let keyArg = `key${this.target.loopLevel}`;
+ if (tKeyExpr) {
+ keyArg = `${tKeyExpr} + ${keyArg}`;
+ }
+ this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${keyArg});`);
+ return id;
+ }
+
+ if (tKeyExpr) {
+ blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`;
+ }
+
+ if (block.isRoot && !ctx.preventRoot) {
this.addLine(`return ${blockExpr};`);
} else {
this.addLine(`let ${block.varName} = ${blockExpr};`);
@@ -796,6 +808,13 @@ export class QWebCompiler {
}
compileTKey(ast: ASTTKey, ctx: Context) {
+ const tKeyExpr = this.generateId("tKey_");
+ this.addLine(`const ${tKeyExpr} = ${compileExpr(ast.expr)};`);
+ ctx = createContext(ctx, {
+ tKeyExpr,
+ block: ctx.block,
+ index: ctx.index,
+ });
this.compileAST(ast.content, ctx);
}
@@ -970,7 +989,11 @@ export class QWebCompiler {
propString = propVar;
}
- let blockArgs = `${expr}, ${propString}, key + \`${key}\`, node, ctx`;
+ let keyArg = `key + \`${key}\``;
+ if (ctx.tKeyExpr) {
+ keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
+ }
+ let blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
// slots
const hasSlot = !!Object.keys(ast.slots).length;
@@ -1007,7 +1030,7 @@ export class QWebCompiler {
extraArgs.slots = slotDef;
}
- if (block && ctx.forceNewBlock === false) {
+ if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
// todo: check the forcenewblock condition
this.insertAnchor(block);
}
diff --git a/src/qweb/inline_expressions.ts b/src/qweb/inline_expressions.ts
index 24a533d8..2474c414 100644
--- a/src/qweb/inline_expressions.ts
+++ b/src/qweb/inline_expressions.ts
@@ -25,10 +25,9 @@
// Misc types, constants and helpers
//------------------------------------------------------------------------------
-const RESERVED_WORDS =
- "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,eval,void,Math,RegExp,Array,Object,Date".split(
- ","
- );
+const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,eval,void,Math,RegExp,Array,Object,Date".split(
+ ","
+);
const WORD_REPLACEMENT: { [key: string]: string } = Object.assign(Object.create(null), {
and: "&&",
diff --git a/tests/blockdom/toggler.test.ts b/tests/blockdom/toggler.test.ts
index efa114e6..be411a14 100644
--- a/tests/blockdom/toggler.test.ts
+++ b/tests/blockdom/toggler.test.ts
@@ -54,4 +54,61 @@ describe("togglers", () => {
expect(fixture.innerHTML).toBe("hey
");
expect(fixture.firstChild).not.toBe(textnode);
});
+
+ test("beforeRemove is called", () => {
+ const block = createBlock("hey
");
+
+ function blockOverriden() {
+ const vn = block();
+ vn.beforeRemove = () => {
+ steps.push("beforeRemove");
+ };
+ return vn;
+ }
+
+ const steps: string[] = [];
+ const tree = toggler("key1", blockOverriden());
+ mount(tree, fixture);
+ expect(fixture.innerHTML).toBe("hey
");
+ patch(tree, toggler("key2", text("foo")));
+ expect(fixture.innerHTML).toBe("foo");
+
+ patch(tree, toggler("key3", blockOverriden()), true);
+ expect(fixture.innerHTML).toBe("hey
");
+
+ patch(tree, toggler("key2", text("foo")), true);
+ expect(fixture.innerHTML).toBe("foo");
+
+ expect(steps).toEqual(["beforeRemove"]);
+ });
+
+ test("beforeRemove is called when within a block", () => {
+ const block = createBlock("
");
+ const conditionalBlock = createBlock("hey
");
+
+ const steps: string[] = [];
+
+ function blockOverriden() {
+ const vn = conditionalBlock();
+ vn.beforeRemove = function () {
+ steps.push("beforeRemove");
+ };
+ return vn;
+ }
+ const mainBlock = block([], [toggler("key1", blockOverriden())]);
+
+ mount(mainBlock, fixture);
+ expect(fixture.innerHTML).toBe("hey
");
+
+ patch(mainBlock, block([], []));
+ expect(fixture.innerHTML).toBe("");
+
+ patch(mainBlock, block([], [toggler("key1", blockOverriden())]), true);
+ expect(fixture.innerHTML).toBe("hey
");
+
+ patch(mainBlock, block([], []), true);
+ expect(fixture.innerHTML).toBe("");
+
+ expect(steps).toEqual(["beforeRemove"]);
+ });
});
diff --git a/tests/components/__snapshots__/basics.test.ts.snap b/tests/components/__snapshots__/basics.test.ts.snap
index a42818e9..19d66627 100644
--- a/tests/components/__snapshots__/basics.test.ts.snap
+++ b/tests/components/__snapshots__/basics.test.ts.snap
@@ -581,7 +581,8 @@ exports[`basics reconciliation alg is not confused in some specific situation 2`
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
- let b3 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
+ const tKey_2 = 4;
+ let b3 = toggler(tKey_2, component(\`Child\`, {}, tKey_2 + key + \`__3\`, node, ctx));
return block1([], [b2, b3]);
}
}"
@@ -640,8 +641,10 @@ exports[`basics same t-keys in two different places 2`] = `
let block1 = createBlock(\`\`);
return function template(ctx, node, key = \\"\\") {
- let b2 = component(\`Child\`, {blip: '1'}, key + \`__1\`, node, ctx);
- let b3 = component(\`Child\`, {blip: '2'}, key + \`__2\`, node, ctx);
+ const tKey_1 = 1;
+ let b2 = toggler(tKey_1, component(\`Child\`, {blip: '1'}, tKey_1 + key + \`__2\`, node, ctx));
+ const tKey_3 = 1;
+ let b3 = toggler(tKey_3, component(\`Child\`, {blip: '2'}, tKey_3 + key + \`__4\`, node, ctx));
return block1([], [b2, b3]);
}
}"
@@ -889,14 +892,15 @@ exports[`basics t-key on a component with t-if, and a sibling component 2`] = `
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
- let block1 = createBlock(\`
\`);
+ let block1 = createBlock(\`
\`);
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (false) {
- b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
+ const tKey_1 = 'str';
+ b2 = toggler(tKey_1, component(\`Child\`, {}, tKey_1 + key + \`__2\`, node, ctx));
}
- b3 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
+ b3 = component(\`Child\`, {}, key + \`__3\`, node, ctx);
return block1([], [b2, b3]);
}
}"
diff --git a/tests/components/__snapshots__/concurrency.test.ts.snap b/tests/components/__snapshots__/concurrency.test.ts.snap
index 5d746d91..8e839c8a 100644
--- a/tests/components/__snapshots__/concurrency.test.ts.snap
+++ b/tests/components/__snapshots__/concurrency.test.ts.snap
@@ -18,7 +18,7 @@ exports[`calling render in destroy 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
- let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, inTCall, shallowEqual, setContextValue } = helpers;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`
\`);
@@ -33,7 +33,7 @@ exports[`calling render in destroy 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
- let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
return function template(ctx, node, key = \\"\\") {
return component(\`C\`, {fromA: ctx['props'].fromA}, key + \`__1\`, node, ctx);
@@ -45,10 +45,11 @@ exports[`calling render in destroy 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
- let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
return function template(ctx, node, key = \\"\\") {
- return component(\`B\`, {fromA: ctx['state']}, key + \`__1\`, node, ctx);
+ const tKey_1 = ctx['key'];
+ return toggler(tKey_1, component(\`B\`, {fromA: ctx['state']}, tKey_1 + key + \`__2\`, node, ctx));
}
}"
`;
@@ -1159,12 +1160,13 @@ exports[`two renderings initiated between willPatch and patched 2`] = `
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
- let block1 = createBlock(\`
\`);
+ let block1 = createBlock(\`
\`);
return function template(ctx, node, key = \\"\\") {
let b2;
if (ctx['state'].flag) {
- b2 = component(\`Panel\`, {val: ctx['state'].panel}, key + \`__1\`, node, ctx);
+ const tKey_1 = 'panel_'+ctx['state'].panel;
+ b2 = toggler(tKey_1, component(\`Panel\`, {val: ctx['state'].panel}, tKey_1 + key + \`__2\`, node, ctx));
}
return block1([], [b2]);
}
diff --git a/tests/components/__snapshots__/t_component.test.ts.snap b/tests/components/__snapshots__/t_component.test.ts.snap
index 247a85ce..90fa231e 100644
--- a/tests/components/__snapshots__/t_component.test.ts.snap
+++ b/tests/components/__snapshots__/t_component.test.ts.snap
@@ -79,8 +79,9 @@ exports[`t-component can use dynamic components (the class) if given (with diffe
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
return function template(ctx, node, key = \\"\\") {
- let Comp2 = ctx['myComponent'];
- return toggler(Comp2, component(Comp2, {}, key + \`__1\`, node, ctx));
+ const tKey_1 = ctx['state'].child;
+ let Comp3 = ctx['myComponent'];
+ return toggler(tKey_1, toggler(Comp3, component(Comp3, {}, tKey_1 + key + \`__2\`, node, ctx)));
}
}"
`;
@@ -120,8 +121,9 @@ exports[`t-component can use dynamic components (the class) if given 3`] = `
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
return function template(ctx, node, key = \\"\\") {
- let Comp2 = ctx['myComponent'];
- return toggler(Comp2, component(Comp2, {}, key + \`__1\`, node, ctx));
+ const tKey_1 = ctx['state'].child;
+ let Comp3 = ctx['myComponent'];
+ return toggler(tKey_1, toggler(Comp3, component(Comp3, {}, tKey_1 + key + \`__2\`, node, ctx)));
}
}"
`;
diff --git a/tests/components/__snapshots__/t_foreach.test.ts.snap b/tests/components/__snapshots__/t_foreach.test.ts.snap
index 60fa7592..8492ed7d 100644
--- a/tests/components/__snapshots__/t_foreach.test.ts.snap
+++ b/tests/components/__snapshots__/t_foreach.test.ts.snap
@@ -281,6 +281,43 @@ exports[`list of components sub components with some state rendered in a loop 2`
}"
`;
+exports[`list of components switch component position 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let d1 = ctx['props'].key;
+ return block1([d1]);
+ }
+}"
+`;
+
+exports[`list of components switch component position 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ ctx = Object.create(ctx);
+ const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['clist']);
+ for (let i1 = 0; i1 < l_block2; i1++) {
+ ctx[\`c\`] = v_block2[i1];
+ let key1 = ctx['c'];
+ c_block2[i1] = withKey(component(\`Child\`, {key: ctx['c']}, key + \`__1__\${key1}\`, node, ctx), key1);
+ }
+ let b2 = list(c_block2);
+ return block1([], [b2]);
+ }
+}"
+`;
+
exports[`list of components t-foreach with t-component, and update 1`] = `
"function anonymous(bdom, helpers
) {
diff --git a/tests/components/__snapshots__/t_key.test.ts.snap b/tests/components/__snapshots__/t_key.test.ts.snap
new file mode 100644
index 00000000..0202ed22
--- /dev/null
+++ b/tests/components/__snapshots__/t_key.test.ts.snap
@@ -0,0 +1,236 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`t-key t-foreach with t-key switch component position 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let d1 = ctx['props'].key;
+ return block1([d1]);
+ }
+}"
+`;
+
+exports[`t-key t-foreach with t-key switch component position 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ ctx = Object.create(ctx);
+ const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['clist']);
+ for (let i1 = 0; i1 < l_block2; i1++) {
+ ctx[\`c\`] = v_block2[i1];
+ let key1 = ctx['c'];
+ const tKey_1 = ctx['key1'];
+ c_block2[i1] = withKey(component(\`Child\`, {key: ctx['c']+ctx['key1']}, tKey_1 + key + \`__2__\${key1}\`, node, ctx), tKey_1 + key1);
+ }
+ let b2 = list(c_block2);
+ return block1([], [b2]);
+ }
+}"
+`;
+
+exports[`t-key t-key on Component 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let d1 = ctx['props'].key;
+ return block1([d1]);
+ }
+}"
+`;
+
+exports[`t-key t-key on Component 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ const tKey_1 = ctx['key'];
+ let b2 = toggler(tKey_1, component(\`Child\`, {key: ctx['key']}, tKey_1 + key + \`__2\`, node, ctx));
+ return block1([], [b2]);
+ }
+}"
+`;
+
+exports[`t-key t-key on Component as a function 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let d1 = ctx['props'].key;
+ return block1([d1]);
+ }
+}"
+`;
+
+exports[`t-key t-key on Component as a function 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ const tKey_1 = ctx['key'];
+ let b2 = toggler(tKey_1, component(\`Child\`, {key: ctx['key']}, tKey_1 + key + \`__2\`, node, ctx));
+ return block1([], [b2]);
+ }
+}"
+`;
+
+exports[`t-key t-key on multiple Components 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let d1 = ctx['props'].key;
+ return block1([d1]);
+ }
+}"
+`;
+
+exports[`t-key t-key on multiple Components 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ const tKey_1 = ctx['key1'];
+ let b2 = toggler(tKey_1, component(\`Child\`, {key: ctx['key1']}, tKey_1 + key + \`__2\`, node, ctx));
+ const tKey_3 = ctx['key2'];
+ let b3 = toggler(tKey_3, component(\`Child\`, {key: ctx['key2']}, tKey_3 + key + \`__4\`, node, ctx));
+ return block1([], [b2, b3]);
+ }
+}"
+`;
+
+exports[`t-key t-key on multiple Components with t-call 1 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let d1 = ctx['props'].key;
+ return block1([d1]);
+ }
+}"
+`;
+
+exports[`t-key t-key on multiple Components with t-call 1 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, toNumber } = helpers;
+
+ return function template(ctx, node, key = \\"\\") {
+ const tKey_1 = ctx['key'];
+ return toggler(tKey_1, component(\`Child\`, {key: ctx['key']}, tKey_1 + key + \`__2\`, node, ctx));
+ }
+}"
+`;
+
+exports[`t-key t-key on multiple Components with t-call 1 3`] = `
+"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, toNumber } = helpers;
+ const callTemplate_2 = getTemplate(\`calledTemplate\`);
+ const callTemplate_4 = getTemplate(\`calledTemplate\`);
+
+ let block1 = createBlock(\`\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ ctx = Object.create(ctx);
+ ctx[isBoundary] = 1
+ ctx = Object.create(ctx);
+ ctx[isBoundary] = 1;
+ setContextValue(ctx, \\"key\\", ctx['key1']);
+ let b2 = callTemplate_2(ctx, node, key + \`__1\`);
+ ctx = ctx.__proto__;
+ ctx = Object.create(ctx);
+ ctx[isBoundary] = 1;
+ setContextValue(ctx, \\"key\\", ctx['key2']);
+ let b3 = callTemplate_4(ctx, node, key + \`__3\`);
+ return block1([], [b2, b3]);
+ }
+}"
+`;
+
+exports[`t-key t-key on multiple Components with t-call 2 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let d1 = ctx['props'].key;
+ return block1([d1]);
+ }
+}"
+`;
+
+exports[`t-key t-key on multiple Components with t-call 2 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, toNumber } = helpers;
+
+ return function template(ctx, node, key = \\"\\") {
+ const tKey_1 = ctx['key1'];
+ let b2 = toggler(tKey_1, component(\`Child\`, {key: ctx['key1']}, tKey_1 + key + \`__2\`, node, ctx));
+ const tKey_3 = ctx['key2'];
+ let b3 = toggler(tKey_3, component(\`Child\`, {key: ctx['key2']}, tKey_3 + key + \`__4\`, node, ctx));
+ return multi([b2, b3]);
+ }
+}"
+`;
+
+exports[`t-key t-key on multiple Components with t-call 2 3`] = `
+"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, toNumber } = helpers;
+ const callTemplate_2 = getTemplate(\`calledTemplate\`);
+
+ let block1 = createBlock(\`\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let b2 = callTemplate_2(ctx, node, key + \`__1\`);
+ return block1([], [b2]);
+ }
+}"
+`;
diff --git a/tests/components/concurrency.test.ts b/tests/components/concurrency.test.ts
index 12a19841..396a65b3 100644
--- a/tests/components/concurrency.test.ts
+++ b/tests/components/concurrency.test.ts
@@ -2108,7 +2108,7 @@ test("concurrent renderings scenario 16", async () => {
// });
// TODO: unskip when t-key is reimplemented properly
-test.skip("calling render in destroy", async () => {
+test("calling render in destroy", async () => {
const steps: any[] = [];
let a: any = null;
@@ -2165,7 +2165,7 @@ test.skip("calling render in destroy", async () => {
// this nextTick is critical, otherwise jest may silently swallow errors
await nextTick();
- expect(steps).toBe(["B:mounted", "B:willUnmount", "B:mounted"]);
+ expect(steps).toStrictEqual(["B:mounted", "B:willUnmount", "B:mounted"]);
expect(fixture.innerHTML).toBe("A
");
});
@@ -2302,7 +2302,7 @@ test("render method wait until rendering is done", async () => {
test("two renderings initiated between willPatch and patched", async () => {
let parent: any = null;
- let steps: string[] = [];
+ const steps: string[] = [];
class Panel extends Component {
static template = xml``;
@@ -2325,17 +2325,8 @@ test("two renderings initiated between willPatch and patched", async () => {
}
await mount(Parent, fixture);
-
expect(fixture.innerHTML).toBe("");
- parent.state.panel = "Panel2";
- await nextTick();
- expect(fixture.innerHTML).toBe("");
-
- parent.state.flag = false;
- await nextTick();
- expect(fixture.innerHTML).toBe("");
-
expect(steps).toEqual([
"Parent:setup",
"Parent:willStart",
@@ -2345,13 +2336,31 @@ test("two renderings initiated between willPatch and patched", async () => {
"Panel:render",
"Panel:mounted",
"Parent:mounted",
+ ]);
+ steps.length = 0;
+
+ parent.state.panel = "Panel2";
+ await nextTick();
+ expect(fixture.innerHTML).toBe("");
+
+ expect(steps).toEqual([
"Parent:render",
- "Panel:willUpdateProps",
+ "Panel:setup",
+ "Panel:willStart",
"Panel:render",
"Parent:willPatch",
- "Panel:willPatch",
- "Panel:patched",
+ "Panel:willUnmount",
+ "Panel:destroyed",
+ "Panel:mounted",
"Parent:patched",
+ ]);
+ steps.length = 0;
+
+ parent.state.flag = false;
+ await nextTick();
+ expect(fixture.innerHTML).toBe("");
+
+ expect(steps).toEqual([
"Parent:render",
"Parent:willPatch",
"Panel:willUnmount",
diff --git a/tests/components/error_handling.test.ts b/tests/components/error_handling.test.ts
index 963f5676..67ef11ef 100644
--- a/tests/components/error_handling.test.ts
+++ b/tests/components/error_handling.test.ts
@@ -43,8 +43,7 @@ describe("basics", () => {
expect(fixture.innerHTML).toBe("");
expect(status(parent)).toBe("destroyed");
expect(error).toBeDefined();
- const regexp =
- /Cannot read properties of undefined \(reading 'this'\)|Cannot read property 'this' of undefined/g;
+ const regexp = /Cannot read properties of undefined \(reading 'this'\)|Cannot read property 'this' of undefined/g;
expect(error.message).toMatch(regexp);
});
@@ -113,8 +112,7 @@ describe.skip("errors and promises", () => {
error = e;
}
expect(error).toBeDefined();
- const regexp =
- /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
+ const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error.message).toMatch(regexp);
expect(console.error).toBeCalledTimes(0);
@@ -227,8 +225,7 @@ describe.skip("errors and promises", () => {
error = e;
}
expect(error).toBeDefined();
- const regexp =
- /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
+ const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error.message).toMatch(regexp);
expect(console.error).toBeCalledTimes(0);
@@ -254,8 +251,7 @@ describe.skip("errors and promises", () => {
error = e;
}
expect(error).toBeDefined();
- const regexp =
- /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
+ const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error.message).toMatch(regexp);
expect(console.error).toBeCalledTimes(0);
@@ -278,8 +274,7 @@ describe.skip("errors and promises", () => {
error = e;
}
expect(error).toBeDefined();
- const regexp =
- /Cannot read properties of undefined \(reading 'y'\)|Cannot read property 'y' of undefined/g;
+ const regexp = /Cannot read properties of undefined \(reading 'y'\)|Cannot read property 'y' of undefined/g;
expect(error.message).toMatch(regexp);
});
diff --git a/tests/components/style_class.test.ts b/tests/components/style_class.test.ts
index 2f75798f..e44a1262 100644
--- a/tests/components/style_class.test.ts
+++ b/tests/components/style_class.test.ts
@@ -341,8 +341,7 @@ describe("style and class handling", () => {
error = e;
}
expect(error).toBeDefined();
- const regexp =
- /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
+ const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error.message).toMatch(regexp);
expect(fixture.innerHTML).toBe("");
});
diff --git a/tests/components/t_foreach.test.ts b/tests/components/t_foreach.test.ts
index aa722108..2a76a885 100644
--- a/tests/components/t_foreach.test.ts
+++ b/tests/components/t_foreach.test.ts
@@ -260,4 +260,32 @@ describe("list of components", () => {
await nextTick(); // wait for changes triggered in mounted to be applied
expect(fixture.innerHTML).toBe("B0B1
");
});
+
+ test("switch component position", async () => {
+ const childInstances = [];
+ class Child extends Component {
+ static template = xml``;
+ setup() {
+ childInstances.push(this);
+ }
+ }
+
+ class Parent extends Component {
+ static components = { Child };
+ static template = xml`
+
+
+
+ `;
+
+ clist = [1, 2];
+ }
+
+ const parent = await mount(Parent, fixture);
+ expect((parent.el as HTMLElement).innerHTML).toBe("1
2
");
+ parent.clist = [2, 1];
+ await parent.render();
+ expect((parent.el as HTMLElement).innerHTML).toBe("2
1
");
+ expect(childInstances.length).toBe(2);
+ });
});
diff --git a/tests/components/t_key.test.ts b/tests/components/t_key.test.ts
new file mode 100644
index 00000000..8fb69f45
--- /dev/null
+++ b/tests/components/t_key.test.ts
@@ -0,0 +1,208 @@
+import { snapshotEverything, makeTestFixture, addTemplate } from "../helpers";
+import { Component, mount, xml } from "../../src/index";
+
+snapshotEverything();
+
+let fixture: HTMLElement;
+
+beforeEach(() => {
+ fixture = makeTestFixture();
+});
+
+describe("t-key", () => {
+ test("t-key on Component", async () => {
+ let childInstance = null;
+ class Child extends Component {
+ static template = xml``;
+
+ setup() {
+ childInstance = this;
+ }
+ }
+
+ class Parent extends Component {
+ static components = { Child };
+ static template = xml``;
+
+ key = 1;
+ }
+
+ const parent = await mount(Parent, fixture);
+ expect((parent.el as HTMLElement).innerHTML).toBe("1
");
+
+ const oldChild = childInstance;
+ parent.key = 2;
+ await parent.render();
+ expect((parent.el as HTMLElement).innerHTML).toBe("2
");
+ expect(oldChild === childInstance).toBeFalsy();
+ });
+
+ test("t-key on Component as a function", async () => {
+ let childInstance = null;
+ class Child extends Component {
+ static template = xml``;
+
+ setup() {
+ childInstance = this;
+ }
+ }
+
+ let keyCalls = 0;
+ let __key = 1;
+ class Parent extends Component {
+ static components = { Child };
+ static template = xml``;
+
+ get key() {
+ keyCalls++;
+ return __key;
+ }
+ }
+
+ const parent = await mount(Parent, fixture);
+ expect((parent.el as HTMLElement).innerHTML).toBe("1
");
+ expect(keyCalls).toBe(2); // one for t-key, the other for the props
+
+ const oldChild = childInstance;
+ __key = 2;
+ await parent.render();
+ expect((parent.el as HTMLElement).innerHTML).toBe("2
");
+ expect(oldChild === childInstance).toBeFalsy();
+ expect(keyCalls).toBe(4);
+ });
+
+ test("t-key on multiple Components", async () => {
+ const childInstances = [];
+ class Child extends Component {
+ static template = xml``;
+
+ setup() {
+ childInstances.push(this);
+ }
+ }
+
+ class Parent extends Component {
+ static components = { Child };
+ static template = xml`
+
+
+ `;
+
+ key1 = 1;
+ key2 = 2;
+ }
+
+ const parent = await mount(Parent, fixture);
+ expect((parent.el as HTMLElement).innerHTML).toBe("1
2
");
+
+ parent.key1 = 2;
+ parent.key2 = 1;
+ await parent.render();
+ expect((parent.el as HTMLElement).innerHTML).toBe("2
1
");
+ expect(childInstances.length).toBe(4);
+ });
+
+ test("t-key on multiple Components with t-call 1", async () => {
+ const childInstances = [];
+ class Child extends Component {
+ static template = xml``;
+
+ setup() {
+ childInstances.push(this);
+ }
+ }
+
+ addTemplate("calledTemplate", ``);
+
+ class Parent extends Component {
+ static components = { Child };
+ static template = xml`
+
+
+ `;
+
+ key1 = 1;
+ key2 = 2;
+ }
+
+ const parent = await mount(Parent, fixture);
+ expect((parent.el as HTMLElement).innerHTML).toBe("1
2
");
+
+ parent.key1 = 2;
+ parent.key2 = 1;
+ await parent.render();
+ expect((parent.el as HTMLElement).innerHTML).toBe("2
1
");
+ expect(childInstances.length).toBe(4);
+ });
+
+ test("t-key on multiple Components with t-call 2", async () => {
+ const childInstances = [];
+ class Child extends Component {
+ static template = xml``;
+
+ setup() {
+ childInstances.push(this);
+ }
+ }
+
+ addTemplate(
+ "calledTemplate",
+ ``
+ );
+
+ class Parent extends Component {
+ static components = { Child };
+ static template = xml`
+
+ `;
+
+ key1 = 1;
+ key2 = 2;
+ }
+
+ const parent = await mount(Parent, fixture);
+ expect((parent.el as HTMLElement).innerHTML).toBe("1
2
");
+
+ parent.key1 = 2;
+ parent.key2 = 1;
+ await parent.render();
+ expect((parent.el as HTMLElement).innerHTML).toBe("2
1
");
+ expect(childInstances.length).toBe(4);
+ });
+
+ test("t-foreach with t-key switch component position", async () => {
+ const childInstances = [];
+ class Child extends Component {
+ static template = xml``;
+ setup() {
+ childInstances.push(this);
+ }
+ }
+
+ class Parent extends Component {
+ static components = { Child };
+ static template = xml`
+
+
+
+ `;
+
+ key1 = "key1";
+ clist = [1, 2];
+ }
+
+ const parent = await mount(Parent, fixture);
+ expect((parent.el as HTMLElement).innerHTML).toBe("1key1
2key1
");
+ parent.clist = [2, 1];
+ await parent.render();
+ expect((parent.el as HTMLElement).innerHTML).toBe("2key1
1key1
");
+ expect(childInstances.length).toBe(2);
+ childInstances.length = 0;
+
+ parent.clist = [1, 2];
+ parent.key1 = "key2";
+ await parent.render();
+ expect((parent.el as HTMLElement).innerHTML).toBe("1key2
2key2
");
+ expect(childInstances.length).toBe(2);
+ });
+});
diff --git a/tests/misc/portal.test.ts b/tests/misc/portal.test.ts
index 2ee53eba..6e7bee56 100644
--- a/tests/misc/portal.test.ts
+++ b/tests/misc/portal.test.ts
@@ -405,8 +405,7 @@ describe("Portal", () => {
error = e;
}
expect(error).toBeDefined();
- const regexp =
- /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
+ const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error.message).toMatch(regexp);
});
diff --git a/tests/qweb/__snapshots__/t_foreach.test.ts.snap b/tests/qweb/__snapshots__/t_foreach.test.ts.snap
index 2b9356fc..de42c63f 100644
--- a/tests/qweb/__snapshots__/t_foreach.test.ts.snap
+++ b/tests/qweb/__snapshots__/t_foreach.test.ts.snap
@@ -479,7 +479,8 @@ exports[`t-foreach throws error if invalid loop expression 1`] = `
ctx[\`item\`] = v_block2[i1];
ctx[\`item_index\`] = i1;
let key1 = ctx['item'];
- c_block2[i1] = withKey(block3(), key1);
+ const tKey_1 = ctx['item_index'];
+ c_block2[i1] = withKey(block3(), tKey_1 + key1);
}
let b2 = list(c_block2);
return block1([], [b2]);
diff --git a/tests/qweb/__snapshots__/t_key.test.ts.snap b/tests/qweb/__snapshots__/t_key.test.ts.snap
index a51dfa98..578a716b 100644
--- a/tests/qweb/__snapshots__/t_key.test.ts.snap
+++ b/tests/qweb/__snapshots__/t_key.test.ts.snap
@@ -9,8 +9,41 @@ exports[`t-key can use t-key directive on a node 1`] = `
let block1 = createBlock(\`
\`);
return function template(ctx, node, key = \\"\\") {
+ const tKey_1 = ctx['beer'].id;
let d1 = ctx['beer'].name;
- return block1([d1]);
+ return toggler(tKey_1, block1([d1]));
+ }
+}"
+`;
+
+exports[`t-key can use t-key directive on a node 2 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ const tKey_1 = ctx['beer'].id;
+ let d1 = ctx['beer'].name;
+ return toggler(tKey_1, block1([d1]));
+ }
+}"
+`;
+
+exports[`t-key can use t-key directive on a node as a function 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, toNumber } = helpers;
+
+ let block1 = createBlock(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ const tKey_1 = ctx['getKey'](ctx['beer']);
+ let d1 = ctx['beer'].name;
+ return toggler(tKey_1, block1([d1]));
}
}"
`;
diff --git a/tests/qweb/__snapshots__/t_ref.test.ts.snap b/tests/qweb/__snapshots__/t_ref.test.ts.snap
index d03bbd62..e26efe58 100644
--- a/tests/qweb/__snapshots__/t_ref.test.ts.snap
+++ b/tests/qweb/__snapshots__/t_ref.test.ts.snap
@@ -102,10 +102,11 @@ exports[`t-ref refs in a loop 1`] = `
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`item\`] = v_block2[i1];
let key1 = ctx['item'];
- const v1 = ctx['item'];
- let d1 = (el) => refs[\`\${v1}\`] = el;
+ const tKey_1 = ctx['item'];
+ const v2 = ctx['item'];
+ let d1 = (el) => refs[\`\${v2}\`] = el;
let d2 = ctx['item'];
- c_block2[i1] = withKey(block3([d1, d2]), key1);
+ c_block2[i1] = withKey(block3([d1, d2]), tKey_1 + key1);
}
let b2 = list(c_block2);
return block1([], [b2]);
diff --git a/tests/qweb/t_key.test.ts b/tests/qweb/t_key.test.ts
index 08ec93b5..20ad0643 100644
--- a/tests/qweb/t_key.test.ts
+++ b/tests/qweb/t_key.test.ts
@@ -1,4 +1,5 @@
-import { renderToString, snapshotEverything } from "../helpers";
+import { renderToString, renderToBdom, snapshotEverything, makeTestFixture } from "../helpers";
+import { mount, patch } from "../../src/blockdom/index";
snapshotEverything();
@@ -10,6 +11,28 @@ describe("t-key", () => {
);
});
+ test("can use t-key directive on a node as a function", () => {
+ const template = `
`;
+ const getKey = (arg: any) => arg.id;
+ expect(renderToString(template, { getKey, beer: { id: 12, name: "Chimay Rouge" } })).toBe(
+ "Chimay Rouge
"
+ );
+ });
+
+ test("can use t-key directive on a node 2", async () => {
+ const template = `
`;
+ const bd = renderToBdom(template, { beer: { id: 12, name: "Chimay Rouge" } });
+ const fixture = makeTestFixture();
+ await mount(bd, fixture);
+ const div = fixture.firstChild;
+
+ expect((div as HTMLElement).outerHTML).toBe("Chimay Rouge
");
+ const bd2 = renderToBdom(template, { beer: { id: 13, name: "Chimay Rouge" } });
+ await patch(bd, bd2);
+ expect(div !== fixture.firstChild).toBeTruthy();
+ expect((div as HTMLElement).outerHTML).toBe("Chimay Rouge
");
+ });
+
test("t-key directive in a list", () => {
const template = `