Compare commits

..

3 Commits

Author SHA1 Message Date
Géry Debongnie 64bad25762 [REL] v2.0.0-beta-22
# v2.0.0-beta-22

- fix: t-call: nested t-call with magic variable 0
- fix: prevent crash in case with t-foreach, t-out and components
2022-09-29 09:17:06 +02:00
Géry Debongnie 7ab34c5ca5 [FIX] prevent crash in case with t-foreach and t-out with components
The t-out directive is compiled internally into a LazyValue, which
represents a value that may or may not be created sometimes in the
future.  It can also be reused more than once, and this is where there
may be an issue: if a component is contained in the lazyvalue, it needs
a unique key (coming from the t-foreach) to be properly indexed in the
parent children map.  However, the LazyValue does not keep the key
information, so it is not able to provide it to its content.

The fix is then quite clear: the LazyValue class should store the key
information, and provides it to its content.  This allows the LazyValue
to be used multiple times, in any place in a template.

closes #1270
2022-09-29 08:28:38 +02:00
Géry Debongnie 669fd622ec [FIX] t-call: nested t-call with magic variable 0
Before this commit, the template compiler would guess the next block id
that will be generated when compiling the body of a tcall.  This is
correct IF there are not nested t-call, but otherwise wrong, because the
next block id could be mixed up: the first t-call would save the next
block id (let's say n), then the inner t-call would also save the same
block id (so, n), will then generate its own block (n+1), then the outer
t-call would use the block n index instead of n+1

The best fix, in my opinion, is to make sure we get the next block var
name, so we do not have to guess. To do that, each compile block type
function needs to properly return the information.

closes #1267
2022-09-28 09:31:41 +02:00
12 changed files with 247 additions and 92 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@odoo/owl", "name": "@odoo/owl",
"version": "2.0.0-beta-21", "version": "2.0.0-beta-22",
"description": "Odoo Web Library (OWL)", "description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js", "main": "dist/owl.cjs.js",
"module": "dist/owl.es.js", "module": "dist/owl.es.js",
+82 -73
View File
@@ -214,6 +214,14 @@ class CodeTarget {
result.push(`}`); result.push(`}`);
return result.join("\n "); return result.join("\n ");
} }
currentKey(ctx: Context) {
let key = this.loopLevel ? `key${this.loopLevel}` : "key";
if (ctx.tKeyExpr) {
key = `${ctx.tKeyExpr} + ${key}`;
}
return key;
}
} }
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"]; const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
@@ -365,19 +373,15 @@ export class CodeGenerator {
insertBlock(expression: string, block: BlockDescription, ctx: Context): void { insertBlock(expression: string, block: BlockDescription, ctx: Context): void {
let blockExpr = block.generateExpr(expression); let blockExpr = block.generateExpr(expression);
const tKeyExpr = ctx.tKeyExpr;
if (block.parentVar) { if (block.parentVar) {
let keyArg = `key${this.target.loopLevel}`; let key = this.target.currentKey(ctx);
if (tKeyExpr) {
keyArg = `${tKeyExpr} + ${keyArg}`;
}
this.helpers.add("withKey"); this.helpers.add("withKey");
this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${keyArg});`); this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${key});`);
return; return;
} }
if (tKeyExpr) { if (ctx.tKeyExpr) {
blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`; blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`;
} }
if (block.isRoot && !ctx.preventRoot) { if (block.isRoot && !ctx.preventRoot) {
@@ -424,78 +428,66 @@ export class CodeGenerator {
.join(""); .join("");
} }
compileAST(ast: AST, ctx: Context) { /**
* @returns the newly created block name, if any
*/
compileAST(ast: AST, ctx: Context): string | null {
switch (ast.type) { switch (ast.type) {
case ASTType.Comment: case ASTType.Comment:
this.compileComment(ast, ctx); return this.compileComment(ast, ctx);
break;
case ASTType.Text: case ASTType.Text:
this.compileText(ast, ctx); return this.compileText(ast, ctx);
break;
case ASTType.DomNode: case ASTType.DomNode:
this.compileTDomNode(ast, ctx); return this.compileTDomNode(ast, ctx);
break;
case ASTType.TEsc: case ASTType.TEsc:
this.compileTEsc(ast, ctx); return this.compileTEsc(ast, ctx);
break;
case ASTType.TOut: case ASTType.TOut:
this.compileTOut(ast, ctx); return this.compileTOut(ast, ctx);
break;
case ASTType.TIf: case ASTType.TIf:
this.compileTIf(ast, ctx); return this.compileTIf(ast, ctx);
break;
case ASTType.TForEach: case ASTType.TForEach:
this.compileTForeach(ast, ctx); return this.compileTForeach(ast, ctx);
break;
case ASTType.TKey: case ASTType.TKey:
this.compileTKey(ast, ctx); return this.compileTKey(ast, ctx);
break;
case ASTType.Multi: case ASTType.Multi:
this.compileMulti(ast, ctx); return this.compileMulti(ast, ctx);
break;
case ASTType.TCall: case ASTType.TCall:
this.compileTCall(ast, ctx); return this.compileTCall(ast, ctx);
break;
case ASTType.TCallBlock: case ASTType.TCallBlock:
this.compileTCallBlock(ast, ctx); return this.compileTCallBlock(ast, ctx);
break;
case ASTType.TSet: case ASTType.TSet:
this.compileTSet(ast, ctx); return this.compileTSet(ast, ctx);
break;
case ASTType.TComponent: case ASTType.TComponent:
this.compileComponent(ast, ctx); return this.compileComponent(ast, ctx);
break;
case ASTType.TDebug: case ASTType.TDebug:
this.compileDebug(ast, ctx); return this.compileDebug(ast, ctx);
break;
case ASTType.TLog: case ASTType.TLog:
this.compileLog(ast, ctx); return this.compileLog(ast, ctx);
break;
case ASTType.TSlot: case ASTType.TSlot:
this.compileTSlot(ast, ctx); return this.compileTSlot(ast, ctx);
break;
case ASTType.TTranslation: case ASTType.TTranslation:
this.compileTTranslation(ast, ctx); return this.compileTTranslation(ast, ctx);
break;
case ASTType.TPortal: case ASTType.TPortal:
this.compileTPortal(ast, ctx); return this.compileTPortal(ast, ctx);
} }
} }
compileDebug(ast: ASTDebug, ctx: Context) { compileDebug(ast: ASTDebug, ctx: Context): string | null {
this.addLine(`debugger;`); this.addLine(`debugger;`);
if (ast.content) { if (ast.content) {
this.compileAST(ast.content, ctx); return this.compileAST(ast.content, ctx);
} }
return null;
} }
compileLog(ast: ASTLog, ctx: Context) { compileLog(ast: ASTLog, ctx: Context): string | null {
this.addLine(`console.log(${compileExpr(ast.expr)});`); this.addLine(`console.log(${compileExpr(ast.expr)});`);
if (ast.content) { if (ast.content) {
this.compileAST(ast.content, ctx); return this.compileAST(ast.content, ctx);
} }
return null;
} }
compileComment(ast: ASTComment, ctx: Context) { compileComment(ast: ASTComment, ctx: Context): string {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock; const isNewBlock = !block || forceNewBlock;
if (isNewBlock) { if (isNewBlock) {
@@ -508,9 +500,10 @@ export class CodeGenerator {
const text = xmlDoc.createComment(ast.value); const text = xmlDoc.createComment(ast.value);
block!.insert(text); block!.insert(text);
} }
return block!.varName;
} }
compileText(ast: ASTText, ctx: Context) { compileText(ast: ASTText, ctx: Context): string {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
let value = ast.value; let value = ast.value;
@@ -529,6 +522,7 @@ export class CodeGenerator {
const createFn = ast.type === ASTType.Text ? xmlDoc.createTextNode : xmlDoc.createComment; const createFn = ast.type === ASTType.Text ? xmlDoc.createTextNode : xmlDoc.createComment;
block.insert(createFn.call(xmlDoc, value)); block.insert(createFn.call(xmlDoc, value));
} }
return block.varName;
} }
generateHandlerCode(rawEvent: string, handler: string): string { generateHandlerCode(rawEvent: string, handler: string): string {
@@ -548,7 +542,7 @@ export class CodeGenerator {
return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`; return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`;
} }
compileTDomNode(ast: ASTDomNode, ctx: Context) { compileTDomNode(ast: ASTDomNode, ctx: Context): string {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns; const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns;
let codeIdx = this.target.code.length; let codeIdx = this.target.code.length;
@@ -735,9 +729,10 @@ export class CodeGenerator {
this.addLine(`let ${block!.children.map((c) => c.varName)};`, codeIdx); this.addLine(`let ${block!.children.map((c) => c.varName)};`, codeIdx);
} }
} }
return block!.varName;
} }
compileTEsc(ast: ASTTEsc, ctx: Context) { compileTEsc(ast: ASTTEsc, ctx: Context): string {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
let expr: string; let expr: string;
if (ast.expr === "0") { if (ast.expr === "0") {
@@ -758,9 +753,10 @@ export class CodeGenerator {
const text = xmlDoc.createElement(`block-text-${idx}`); const text = xmlDoc.createElement(`block-text-${idx}`);
block.insert(text); block.insert(text);
} }
return block.varName;
} }
compileTOut(ast: ASTTOut, ctx: Context) { compileTOut(ast: ASTTOut, ctx: Context): string {
let { block } = ctx; let { block } = ctx;
if (block) { if (block) {
this.insertAnchor(block); this.insertAnchor(block);
@@ -782,6 +778,7 @@ export class CodeGenerator {
blockStr = `safeOutput(${compileExpr(ast.expr)})`; blockStr = `safeOutput(${compileExpr(ast.expr)})`;
} }
this.insertBlock(blockStr, block, ctx); this.insertBlock(blockStr, block, ctx);
return block.varName;
} }
compileTIfBranch(content: AST, block: BlockDescription, ctx: Context) { compileTIfBranch(content: AST, block: BlockDescription, ctx: Context) {
@@ -795,7 +792,7 @@ export class CodeGenerator {
this.target.indentLevel--; this.target.indentLevel--;
} }
compileTIf(ast: ASTTif, ctx: Context, nextNode?: ASTDomNode) { compileTIf(ast: ASTTif, ctx: Context, nextNode?: ASTDomNode): string {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
const codeIdx = this.target.code.length; const codeIdx = this.target.code.length;
const isNewBlock = !block || (block.type !== "multi" && forceNewBlock); const isNewBlock = !block || (block.type !== "multi" && forceNewBlock);
@@ -838,9 +835,10 @@ export class CodeGenerator {
const args = block!.children.map((c) => c.varName).join(", "); const args = block!.children.map((c) => c.varName).join(", ");
this.insertBlock(`multi([${args}])`, block!, ctx)!; this.insertBlock(`multi([${args}])`, block!, ctx)!;
} }
return block.varName;
} }
compileTForeach(ast: ASTTForEach, ctx: Context) { compileTForeach(ast: ASTTForEach, ctx: Context): string {
let { block } = ctx; let { block } = ctx;
if (block) { if (block) {
this.insertAnchor(block); this.insertAnchor(block);
@@ -918,9 +916,10 @@ export class CodeGenerator {
this.addLine(`ctx = ctx.__proto__;`); this.addLine(`ctx = ctx.__proto__;`);
} }
this.insertBlock("l", block, ctx); this.insertBlock("l", block, ctx);
return block.varName;
} }
compileTKey(ast: ASTTKey, ctx: Context) { compileTKey(ast: ASTTKey, ctx: Context): string | null {
const tKeyExpr = generateId("tKey_"); const tKeyExpr = generateId("tKey_");
this.define(tKeyExpr, compileExpr(ast.expr)); this.define(tKeyExpr, compileExpr(ast.expr));
ctx = createContext(ctx, { ctx = createContext(ctx, {
@@ -928,20 +927,22 @@ export class CodeGenerator {
block: ctx.block, block: ctx.block,
index: ctx.index, index: ctx.index,
}); });
this.compileAST(ast.content, ctx); return this.compileAST(ast.content, ctx);
} }
compileMulti(ast: ASTMulti, ctx: Context) { compileMulti(ast: ASTMulti, ctx: Context): string | null {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock; const isNewBlock = !block || forceNewBlock;
let codeIdx = this.target.code.length; let codeIdx = this.target.code.length;
if (isNewBlock) { if (isNewBlock) {
const n = ast.content.filter((c) => c.type !== ASTType.TSet).length; const n = ast.content.filter((c) => c.type !== ASTType.TSet).length;
let result: string | null = null;
if (n <= 1) { if (n <= 1) {
for (let child of ast.content) { for (let child of ast.content) {
this.compileAST(child, ctx); const blockName = this.compileAST(child, ctx);
result = result || blockName;
} }
return; return result;
} }
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
} }
@@ -981,9 +982,10 @@ export class CodeGenerator {
const args = block!.children.map((c) => c.varName).join(", "); const args = block!.children.map((c) => c.varName).join(", ");
this.insertBlock(`multi([${args}])`, block!, ctx)!; this.insertBlock(`multi([${args}])`, block!, ctx)!;
} }
return block!.varName;
} }
compileTCall(ast: ASTTCall, ctx: Context) { compileTCall(ast: ASTTCall, ctx: Context): string {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
let ctxVar = ctx.ctxVar || "ctx"; let ctxVar = ctx.ctxVar || "ctx";
if (ast.context) { if (ast.context) {
@@ -994,12 +996,11 @@ export class CodeGenerator {
this.addLine(`${ctxVar} = Object.create(${ctxVar});`); this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
this.addLine(`${ctxVar}[isBoundary] = 1;`); this.addLine(`${ctxVar}[isBoundary] = 1;`);
this.helpers.add("isBoundary"); this.helpers.add("isBoundary");
const nextId = BlockDescription.nextBlockId;
const subCtx = createContext(ctx, { preventRoot: true, ctxVar }); const subCtx = createContext(ctx, { preventRoot: true, ctxVar });
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx); const bl = this.compileMulti({ type: ASTType.Multi, content: ast.body }, subCtx);
if (nextId !== BlockDescription.nextBlockId) { if (bl) {
this.helpers.add("zero"); this.helpers.add("zero");
this.addLine(`${ctxVar}[zero] = b${nextId};`); this.addLine(`${ctxVar}[zero] = ${bl};`);
} }
} }
const isDynamic = INTERP_REGEXP.test(ast.name); const isDynamic = INTERP_REGEXP.test(ast.name);
@@ -1033,9 +1034,10 @@ export class CodeGenerator {
if (ast.body && !ctx.isLast) { if (ast.body && !ctx.isLast) {
this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`); this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
} }
return block.varName;
} }
compileTCallBlock(ast: ASTTCallBlock, ctx: Context) { compileTCallBlock(ast: ASTTCallBlock, ctx: Context): string {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
if (block) { if (block) {
if (!forceNewBlock) { if (!forceNewBlock) {
@@ -1044,9 +1046,10 @@ export class CodeGenerator {
} }
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
this.insertBlock(compileExpr(ast.name), block, { ...ctx, forceNewBlock: !block }); this.insertBlock(compileExpr(ast.name), block, { ...ctx, forceNewBlock: !block });
return block.varName;
} }
compileTSet(ast: ASTTSet, ctx: Context) { compileTSet(ast: ASTTSet, ctx: Context): null {
this.target.shouldProtectScope = true; this.target.shouldProtectScope = true;
this.helpers.add("isBoundary").add("withDefault"); this.helpers.add("isBoundary").add("withDefault");
const expr = ast.value ? compileExpr(ast.value || "") : "null"; const expr = ast.value ? compileExpr(ast.value || "") : "null";
@@ -1054,7 +1057,8 @@ export class CodeGenerator {
this.helpers.add("LazyValue"); this.helpers.add("LazyValue");
const bodyAst: AST = { type: ASTType.Multi, content: ast.body }; const bodyAst: AST = { type: ASTType.Multi, content: ast.body };
const name = this.compileInNewTarget("value", bodyAst, ctx); const name = this.compileInNewTarget("value", bodyAst, ctx);
let value = `new LazyValue(${name}, ctx, this, node)`; let key = this.target.currentKey(ctx);
let value = `new LazyValue(${name}, ctx, this, node, ${key})`;
value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value; value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
this.addLine(`ctx[\`${ast.name}\`] = ${value};`); this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
} else { } else {
@@ -1071,6 +1075,7 @@ export class CodeGenerator {
this.helpers.add("setContextValue"); this.helpers.add("setContextValue");
this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`); this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
} }
return null;
} }
generateComponentKey() { generateComponentKey() {
@@ -1122,7 +1127,7 @@ export class CodeGenerator {
return propString; return propString;
} }
compileComponent(ast: ASTComponent, ctx: Context) { compileComponent(ast: ASTComponent, ctx: Context): string {
let { block } = ctx; let { block } = ctx;
// props // props
const hasSlotsProp = "slots" in (ast.props || {}); const hasSlotsProp = "slots" in (ast.props || {});
@@ -1222,6 +1227,7 @@ export class CodeGenerator {
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockExpr, block, ctx); this.insertBlock(blockExpr, block, ctx);
return block.varName;
} }
wrapWithEventCatcher(expr: string, on: EventHandlers): string { wrapWithEventCatcher(expr: string, on: EventHandlers): string {
@@ -1240,7 +1246,7 @@ export class CodeGenerator {
return `${name}(${expr}, [${handlers.join(",")}])`; return `${name}(${expr}, [${handlers.join(",")}])`;
} }
compileTSlot(ast: ASTSlot, ctx: Context) { compileTSlot(ast: ASTSlot, ctx: Context): string {
this.helpers.add("callSlot"); this.helpers.add("callSlot");
let { block } = ctx; let { block } = ctx;
let blockString: string; let blockString: string;
@@ -1289,14 +1295,16 @@ export class CodeGenerator {
} }
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false }); this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
return block.varName;
} }
compileTTranslation(ast: ASTTranslation, ctx: Context) { compileTTranslation(ast: ASTTranslation, ctx: Context): string | null {
if (ast.content) { if (ast.content) {
this.compileAST(ast.content, Object.assign({}, ctx, { translate: false })); return this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
} }
return null;
} }
compileTPortal(ast: ASTTPortal, ctx: Context) { compileTPortal(ast: ASTTPortal, ctx: Context): string {
if (!this.staticDefs.find((d) => d.id === "Portal")) { if (!this.staticDefs.find((d) => d.id === "Portal")) {
this.staticDefs.push({ id: "Portal", expr: `app.Portal` }); this.staticDefs.push({ id: "Portal", expr: `app.Portal` });
} }
@@ -1323,5 +1331,6 @@ export class CodeGenerator {
} }
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false }); this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
return block.varName;
} }
} }
+5 -2
View File
@@ -111,15 +111,18 @@ class LazyValue {
ctx: any; ctx: any;
component: any; component: any;
node: any; node: any;
constructor(fn: any, ctx: any, component: any, node: any) { key: any;
constructor(fn: any, ctx: any, component: any, node: any, key: any) {
this.fn = fn; this.fn = fn;
this.ctx = capture(ctx); this.ctx = capture(ctx);
this.component = component; this.component = component;
this.node = node; this.node = node;
this.key = key;
} }
evaluate(): any { evaluate(): any {
return this.fn.call(this.component, this.ctx, this.node); return this.fn.call(this.component, this.ctx, this.node, this.key);
} }
toString() { toString() {
@@ -334,6 +334,57 @@ exports[`t-call (template calling) inherit context 2`] = `
}" }"
`; `;
exports[`t-call (template calling) nested t-calls with magic variable 0 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { isBoundary, zero } = helpers;
const callTemplate_1 = app.getTemplate(\`grandchild\`);
const callTemplate_2 = app.getTemplate(\`child\`);
let block1 = createBlock(\`<p>Some content...</p>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b1 = block1();
ctx[zero] = b1;
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
ctx = ctx.__proto__;
ctx[zero] = b2;
return callTemplate_2.call(this, ctx, node, key + \`__2\`);
}
}"
`;
exports[`t-call (template calling) nested t-calls with magic variable 0 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { zero } = helpers;
return function template(ctx, node, key = \\"\\") {
const b2 = text(\`grandchild\`);
const b3 = ctx[zero];
return multi([b2, b3]);
}
}"
`;
exports[`t-call (template calling) nested t-calls with magic variable 0 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { zero } = helpers;
return function template(ctx, node, key = \\"\\") {
return ctx[zero];
}
}"
`;
exports[`t-call (template calling) recursive template, part 1 1`] = ` exports[`t-call (template calling) recursive template, part 1 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -126,7 +126,7 @@ exports[`t-esc t-esc is escaped 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`var\`] = new LazyValue(value1, ctx, this, node); ctx[\`var\`] = new LazyValue(value1, ctx, this, node, key);
let txt1 = ctx['var']; let txt1 = ctx['var'];
return block1([txt1]); return block1([txt1]);
} }
@@ -161,7 +161,7 @@ exports[`t-out t-out bdom 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`var\`] = new LazyValue(value1, ctx, this, node); ctx[\`var\`] = new LazyValue(value1, ctx, this, node, key);
const b3 = safeOutput(ctx['var']); const b3 = safeOutput(ctx['var']);
return block1([], [b3]); return block1([], [b3]);
} }
@@ -310,7 +310,7 @@ exports[`t-out t-out switch markup on bdom 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
let b3,b5; let b3,b5;
ctx[\`bdom\`] = new LazyValue(value1, ctx, this, node); ctx[\`bdom\`] = new LazyValue(value1, ctx, this, node, key);
if (ctx['hasBdom']) { if (ctx['hasBdom']) {
const b4 = safeOutput(ctx['bdom']); const b4 = safeOutput(ctx['bdom']);
b3 = block3([], [b4]); b3 = block3([], [b4]);
@@ -106,7 +106,7 @@ exports[`t-set set from body literal (with t-if/t-else 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`value\`] = new LazyValue(value1, ctx, this, node); ctx[\`value\`] = new LazyValue(value1, ctx, this, node, key);
return text(ctx['value']); return text(ctx['value']);
} }
}" }"
@@ -142,7 +142,7 @@ exports[`t-set set from body lookup 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`stuff\`] = new LazyValue(value1, ctx, this, node); ctx[\`stuff\`] = new LazyValue(value1, ctx, this, node, key);
let txt1 = ctx['stuff']; let txt1 = ctx['stuff'];
return block1([txt1]); return block1([txt1]);
} }
@@ -206,7 +206,7 @@ exports[`t-set t-set body is evaluated immediately 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"v1\\", 'before'); setContextValue(ctx, \\"v1\\", 'before');
ctx[\`v2\`] = new LazyValue(value1, ctx, this, node); ctx[\`v2\`] = new LazyValue(value1, ctx, this, node, key);
setContextValue(ctx, \\"v1\\", 'after'); setContextValue(ctx, \\"v1\\", 'after');
const b3 = safeOutput(ctx['v2']); const b3 = safeOutput(ctx['v2']);
return block1([], [b3]); return block1([], [b3]);
@@ -471,7 +471,7 @@ exports[`t-set t-set with content and sub t-esc 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`setvar\`] = new LazyValue(value1, ctx, this, node); ctx[\`setvar\`] = new LazyValue(value1, ctx, this, node, key);
let txt1 = ctx['setvar']; let txt1 = ctx['setvar'];
return block1([txt1]); return block1([txt1]);
} }
@@ -497,7 +497,7 @@ exports[`t-set t-set with t-value (falsy) and body 1`] = `
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"v3\\", false); setContextValue(ctx, \\"v3\\", false);
setContextValue(ctx, \\"v1\\", 'before'); setContextValue(ctx, \\"v1\\", 'before');
ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node)); ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node, key));
setContextValue(ctx, \\"v1\\", 'after'); setContextValue(ctx, \\"v1\\", 'after');
setContextValue(ctx, \\"v3\\", true); setContextValue(ctx, \\"v3\\", true);
const b3 = safeOutput(ctx['v2']); const b3 = safeOutput(ctx['v2']);
@@ -525,7 +525,7 @@ exports[`t-set t-set with t-value (truthy) and body 1`] = `
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"v3\\", 'Truthy'); setContextValue(ctx, \\"v3\\", 'Truthy');
setContextValue(ctx, \\"v1\\", 'before'); setContextValue(ctx, \\"v1\\", 'before');
ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node)); ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node, key));
setContextValue(ctx, \\"v1\\", 'after'); setContextValue(ctx, \\"v1\\", 'after');
setContextValue(ctx, \\"v3\\", false); setContextValue(ctx, \\"v3\\", false);
const b3 = safeOutput(ctx['v2']); const b3 = safeOutput(ctx['v2']);
@@ -638,7 +638,7 @@ exports[`t-set value priority (with non text body 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`value\`] = withDefault(1, new LazyValue(value1, ctx, this, node)); ctx[\`value\`] = withDefault(1, new LazyValue(value1, ctx, this, node, key));
let txt1 = ctx['value']; let txt1 = ctx['value'];
return block1([txt1]); return block1([txt1]);
} }
+17
View File
@@ -479,4 +479,21 @@ describe("t-call (template calling)", () => {
"<span>123lucas</span>" "<span>123lucas</span>"
); );
}); });
test("nested t-calls with magic variable 0", () => {
const context = new TestContext();
context.addTemplate("grandchild", `grandchild<t t-out="0"/>`);
context.addTemplate("child", `<t t-out="0"/>`);
context.addTemplate(
"main",
`
<t t-call="child">
<t t-call="grandchild">
<p>Some content...</p>
</t>
</t>`
);
expect(context.renderToString("main")).toBe("grandchild<p>Some content...</p>");
});
}); });
@@ -163,7 +163,7 @@ exports[`basics t-set with a body expression can be passed in props, and then t-
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`abc\`] = new LazyValue(value1, ctx, this, node); ctx[\`abc\`] = new LazyValue(value1, ctx, this, node, key);
const b3 = comp1({val: ctx['abc']}, key + \`__1\`, node, this, null); const b3 = comp1({val: ctx['abc']}, key + \`__1\`, node, this, null);
return block1([], [b3]); return block1([], [b3]);
} }
@@ -0,0 +1,39 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components in t-out simple list 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { prepareList, isBoundary, withDefault, LazyValue, safeOutput, withKey } = helpers;
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
function value1(ctx, node, key = \\"\\") {
return comp1({}, key + \`__1\`, node, this, null);
}
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx = Object.create(ctx);
const [k_block1, v_block1, l_block1, c_block1] = prepareList([1,2]);;
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`n\`] = v_block1[i1];
const key1 = ctx['n'];
ctx[\`blabla\`] = new LazyValue(value1, ctx, this, node, key1);
c_block1[i1] = withKey(safeOutput(ctx['blabla']), key1);
}
return list(c_block1);
}
}"
`;
exports[`components in t-out simple list 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`child\`);
}
}"
`;
@@ -59,7 +59,7 @@ exports[`t-set slots with a t-set with a component in body 1`] = `
function slot1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node); ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key);
const b3 = text(\` in slot \`); const b3 = text(\` in slot \`);
const b4 = safeOutput(ctx['v']); const b4 = safeOutput(ctx['v']);
return multi([b3, b4]); return multi([b3, b4]);
@@ -114,7 +114,7 @@ exports[`t-set slots with an t-set with a component in body 1`] = `
function slot1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node); ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key);
const b5 = text(\` tea \`); const b5 = text(\` tea \`);
const b6 = safeOutput(ctx['v']); const b6 = safeOutput(ctx['v']);
return multi([b5, b6]); return multi([b5, b6]);
@@ -169,7 +169,7 @@ exports[`t-set slots with an unused t-set with a component in body 1`] = `
function slot1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node); ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key);
return text(\` in slot \`); return text(\` in slot \`);
} }
@@ -343,7 +343,7 @@ exports[`t-set t-set with a component in body 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node); ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key);
const b3 = safeOutput(ctx['v']); const b3 = safeOutput(ctx['v']);
return block1([], [b3]); return block1([], [b3]);
} }
@@ -377,7 +377,7 @@ exports[`t-set t-set with something in body 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node); ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key);
const b3 = safeOutput(ctx['v']); const b3 = safeOutput(ctx['v']);
return block1([], [b3]); return block1([], [b3]);
} }
+36
View File
@@ -0,0 +1,36 @@
import { Component, mount, xml } from "../../src/index";
import { makeTestFixture, snapshotEverything } from "../helpers";
snapshotEverything();
// -----------------------------------------------------------------------------
// t-out
// -----------------------------------------------------------------------------
describe("components in t-out", () => {
let fixture: HTMLElement;
beforeEach(() => {
fixture = makeTestFixture();
});
test("simple list", async () => {
class Child extends Component {
static template = xml`child`;
}
class Parent extends Component {
static template = xml`
<t t-foreach="[1,2]" t-as="n" t-key="n">
<t t-set="blabla">
<Child />
</t>
<t t-out="blabla"/>
</t>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("childchild");
});
});