diff --git a/src/qweb/base_directives.ts b/src/qweb/base_directives.ts index d0ce4984..f95bfb1b 100644 --- a/src/qweb/base_directives.ts +++ b/src/qweb/base_directives.ts @@ -28,17 +28,14 @@ function compileValueNode(value: any, node: Element, qweb: QWeb, ctx: Compilatio // the 'zero' magical symbol is where we can find the result of the rendering // of the body of the t-call. ctx.rootContext.shouldDefineUtils = true; - ctx.addLine(`c${ctx.parentNode}.push(...scope[utils.zero]);`); + const zeroArgs = ctx.escaping + ? `{text: utils.vDomToString(scope[utils.zero])}` + : `...scope[utils.zero]`; + ctx.addLine(`c${ctx.parentNode}.push(${zeroArgs});`); } return; } - if (value.xml instanceof NodeList && !value.id) { - for (let node of Array.from(value.xml)) { - qweb._compileNode(node, ctx); - } - return; - } let exprID: string; if (typeof value === "string") { exprID = `_${ctx.generateID()}`; @@ -47,7 +44,15 @@ function compileValueNode(value: any, node: Element, qweb: QWeb, ctx: Compilatio exprID = `scope.${value.id}`; } ctx.addIf(`${exprID} || ${exprID} === 0`); + if (ctx.escaping) { + let protectID; + if (value.hasBody) { + protectID = ctx.startProtectScope(); + ctx.addLine( + `${exprID} = ${exprID} instanceof utils.VDomArray ? utils.vDomToString(${exprID}) : ${exprID};` + ); + } if (ctx.parentTextNode) { ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`); } else if (ctx.parentNode) { @@ -61,21 +66,25 @@ function compileValueNode(value: any, node: Element, qweb: QWeb, ctx: Compilatio ctx.addLine(`result = vn${nodeID}`); } } + if (value.hasBody) { + ctx.stopProtectScope(protectID); + } } else { ctx.rootContext.shouldDefineUtils = true; - ctx.addLine(`c${ctx.parentNode}.push(...utils.htmlToVDOM(${exprID}));`); + if (value.hasBody) { + ctx.addLine( + `const vnodeArray = ${exprID} instanceof utils.VDomArray ? ${exprID} : utils.htmlToVDOM(${exprID});` + ); + ctx.addLine(`c${ctx.parentNode}.push(...vnodeArray);`); + } else { + ctx.addLine(`c${ctx.parentNode}.push(...utils.htmlToVDOM(${exprID}));`); + } } if (node.childNodes.length) { ctx.addElse(); qweb._compileChildren(node, ctx); } - if (value.xml instanceof NodeList && value.id) { - ctx.addElse(); - for (let node of Array.from(value.xml)) { - qweb._compileNode(node, ctx); - } - } ctx.closeIf(); } @@ -106,25 +115,46 @@ QWeb.addDirective({ name: "set", extraNames: ["value"], priority: 60, - atNodeEncounter({ node, ctx }): boolean { + atNodeEncounter({ node, qweb, ctx }): boolean { ctx.rootContext.shouldDefineScope = true; const variable = node.getAttribute("t-set")!; let value = node.getAttribute("t-value")!; ctx.variables[variable] = ctx.variables[variable] || {}; let qwebvar = ctx.variables[variable]; + const hasBody = node.hasChildNodes(); + qwebvar.id = variable; + qwebvar.expr = `scope.${variable}`; if (value) { const formattedValue = ctx.formatExpression(value); - if (ctx.variables.hasOwnProperty(variable) && qwebvar.id) { - ctx.addLine(`${qwebvar.expr} = ${formattedValue}`); - } else { - ctx.addLine(`scope.${variable} = ${formattedValue};`); - qwebvar.id = variable; - qwebvar.expr = `scope.${variable}`; - qwebvar.value = formattedValue; + ctx.addLine(`${qwebvar.expr} = ${formattedValue};`); + qwebvar.value = formattedValue; + } + + if (hasBody) { + ctx.rootContext.shouldDefineUtils = true; + if (value) { + ctx.addIf(`!(${qwebvar.expr})`); + } + const tempParentNodeID = ctx.generateID(); + const _parentNode = ctx.parentNode; + ctx.parentNode = tempParentNodeID; + + ctx.addLine(`const c${tempParentNodeID} = new utils.VDomArray();`); + const nodeCopy = node.cloneNode(true) as Element; + for (let attr of ["t-set", "t-value", "t-if", "t-else", "t-elif"]) { + nodeCopy.removeAttribute(attr); + } + qweb._compileNode(nodeCopy, ctx); + + ctx.addLine(`${qwebvar.expr} = c${tempParentNodeID}`); + qwebvar.value = `c${tempParentNodeID}`; + qwebvar.hasBody = true; + + ctx.parentNode = _parentNode; + if (value) { + ctx.closeIf(); } - } else { - qwebvar.xml = node.childNodes; } return true; } @@ -205,12 +235,12 @@ QWeb.addDirective({ // Step 3: compile t-call body if necessary // ------------------------------------------------ let hasBody = node.hasChildNodes(); + let protectID; if (hasBody) { // we add a sub scope to protect the ambient scope ctx.addLine(`{`); ctx.indent(); - ctx.addLine(`let origScope = scope;`); - ctx.addLine(`scope = Object.assign(Object.create(context), scope);`); + protectID = ctx.startProtectScope(); const nodeCopy = node.cloneNode(true) as Element; nodeCopy.removeAttribute("t-call"); const parentNode = ctx.parentNode; @@ -229,7 +259,7 @@ QWeb.addDirective({ // Step 4: add the appropriate function call to current component // ------------------------------------------------ - const callingScope = hasBody ? 'scope' : 'Object.assign(Object.create(context), scope)'; + const callingScope = hasBody ? "scope" : "Object.assign(Object.create(context), scope)"; if (ctx.parentNode) { ctx.addLine( `this.subTemplates['${subTemplate}'].call(this, ${callingScope}, Object.assign({}, extra, {parentNode: c${ctx.parentNode}}));` @@ -247,7 +277,7 @@ QWeb.addDirective({ // Step 5: restore previous scope // ------------------------------------------------ if (hasBody) { - ctx.addLine(`scope = origScope;`); + ctx.stopProtectScope(protectID); ctx.dedent(); ctx.addLine(`}`); } @@ -279,9 +309,7 @@ QWeb.addDirective({ ctx.addLine(`_${valuesID} = Object.values(_${arrayID});`); ctx.closeIf(); ctx.addLine(`var _length${keysID} = _${keysID}.length;`); - let varsID = ctx.generateID(); - ctx.addLine(`let _scope${varsID} = scope;`); - ctx.addLine(`scope = Object.assign(Object.create(context), _scope${varsID});`); + let varsID = ctx.startProtectScope(); const loopVar = `i${ctx.loopNumber}`; ctx.addLine(`for (let ${loopVar} = 0; ${loopVar} < _length${keysID}; ${loopVar}++) {`); ctx.indent(); @@ -307,7 +335,7 @@ QWeb.addDirective({ qweb._compileNode(nodeCopy, ctx); ctx.dedent(); ctx.addLine("}"); - ctx.addLine(`scope = _scope${varsID};`); + ctx.stopProtectScope(varsID); return true; } }); diff --git a/src/qweb/compilation_context.ts b/src/qweb/compilation_context.ts index 9d9877eb..19cd1551 100644 --- a/src/qweb/compilation_context.ts +++ b/src/qweb/compilation_context.ts @@ -174,4 +174,14 @@ export class CompilationContext { let r = s.replace(/\{\{.*?\}\}/g, s => "${" + this.formatExpression(s.slice(2, -2)) + "}"); return "`" + r + "`"; } + startProtectScope(): number { + const protectID = this.generateID(); + this.rootContext.shouldDefineScope = true; + this.addLine(`const _origScope${protectID} = scope;`); + this.addLine(`scope = Object.assign(Object.create(context), scope);`); + return protectID; + } + stopProtectScope(protectID: number) { + this.addLine(`scope = _origScope${protectID};`); + } } diff --git a/src/qweb/expression_parser.ts b/src/qweb/expression_parser.ts index ef07a1da..3a02dfd7 100644 --- a/src/qweb/expression_parser.ts +++ b/src/qweb/expression_parser.ts @@ -42,6 +42,7 @@ export interface QWebVar { id: string; // foo expr: string; // scope.foo (local variables => only foo) value?: string; // 1 + 3 + hasBody?: boolean; } //------------------------------------------------------------------------------ diff --git a/src/qweb/qweb.ts b/src/qweb/qweb.ts index 0e3a527f..b808828c 100644 --- a/src/qweb/qweb.ts +++ b/src/qweb/qweb.ts @@ -87,7 +87,7 @@ interface Utils { } const UTILS: Utils = { - zero: Symbol('zero'), + zero: Symbol("zero"), toObj(expr) { if (typeof expr === "string") { expr = expr.trim(); @@ -106,6 +106,20 @@ const UTILS: Utils = { shallowEqual, addNameSpace(vnode) { addNS(vnode.data, vnode.children, vnode.sel); + }, + VDomArray: class VDomArray extends Array {}, + vDomToString: function(vdom: VNode[]): string { + return vdom + .map(vnode => { + if (vnode.sel) { + const node = document.createElement(vnode.sel); + const result = patch(node, vnode); + return (result.elm).outerHTML; + } else { + return vnode.text; + } + }) + .join(); } }; @@ -383,7 +397,7 @@ export class QWeb extends EventBus { let code = ctx.generateCode(); const templateName = ctx.templateName.replace(/`/g, "'").slice(0, 200); - code.unshift(` // Template name: "${templateName}"`) + code.unshift(` // Template name: "${templateName}"`); let template; try { diff --git a/tests/component/__snapshots__/component.test.ts.snap b/tests/component/__snapshots__/component.test.ts.snap index f9e53c5e..e2cd9375 100644 --- a/tests/component/__snapshots__/component.test.ts.snap +++ b/tests/component/__snapshots__/component.test.ts.snap @@ -19,8 +19,8 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.section_first = i1 === 0 scope.section_last = i1 === _length3 - 1 @@ -35,8 +35,8 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for _8 = Object.values(_6); } var _length7 = _7.length; - let _scope9 = scope; - scope = Object.assign(Object.create(context), _scope9); + const _origScope9 = scope; + scope = Object.assign(Object.create(context), scope); for (let i2 = 0; i2 < _length7; i2++) { scope.blip_first = i2 === 0 scope.blip_last = i2 === _length7 - 1 @@ -68,9 +68,9 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for } w10.__owl__.parentLastFiberId = extra.fiber.id; } - scope = _scope9; + scope = _origScope9; } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -318,8 +318,8 @@ exports[`composition sub components with some state rendered in a loop 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.number_first = i1 === 0 scope.number_last = i1 === _length3 - 1 @@ -352,7 +352,7 @@ exports[`composition sub components with some state rendered in a loop 1`] = ` } w7.__owl__.parentLastFiberId = extra.fiber.id; } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -983,8 +983,8 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.item_first = i1 === 0 scope.item_last = i1 === _length3 - 1 @@ -1018,7 +1018,7 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument } w7.__owl__.parentLastFiberId = extra.fiber.id; } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -1120,8 +1120,8 @@ exports[`t-model directive in a t-foreach 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.thing_first = i1 === 0 scope.thing_last = i1 === _length3 - 1 @@ -1139,7 +1139,7 @@ exports[`t-model directive in a t-foreach 1`] = ` extra.handlers[k9] = extra.handlers[k9] || ((ev) => {expr8.f = ev.target.checked}); p8.on['input'] = extra.handlers[k9]; } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -1518,8 +1518,8 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = ` _5 = Object.values(_3); } var _length4 = _4.length; - let _scope6 = scope; - scope = Object.assign(Object.create(context), _scope6); + const _origScope6 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length4; i1++) { scope.user_first = i1 === 0 scope.user_last = i1 === _length4 - 1 @@ -1556,7 +1556,7 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = ` } w9.__owl__.parentLastFiberId = extra.fiber.id; } - scope = _scope6; + scope = _origScope6; return vn1; }" `; @@ -1615,8 +1615,8 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = ` _5 = Object.values(_3); } var _length4 = _4.length; - let _scope6 = scope; - scope = Object.assign(Object.create(context), _scope6); + const _origScope6 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length4; i1++) { scope.user_first = i1 === 0 scope.user_last = i1 === _length4 - 1 @@ -1654,7 +1654,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = ` } w9.__owl__.parentLastFiberId = extra.fiber.id; } - scope = _scope6; + scope = _origScope6; return vn1; }" `; diff --git a/tests/qweb/__snapshots__/qweb.test.ts.snap b/tests/qweb/__snapshots__/qweb.test.ts.snap index 8058ef09..e02a0312 100644 --- a/tests/qweb/__snapshots__/qweb.test.ts.snap +++ b/tests/qweb/__snapshots__/qweb.test.ts.snap @@ -397,8 +397,8 @@ exports[`foreach does not pollute the rendering context 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.item_first = i1 === 0 scope.item_last = i1 === _length3 - 1 @@ -410,7 +410,7 @@ exports[`foreach does not pollute the rendering context 1`] = ` c1.push({text: _6}); } } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -431,8 +431,8 @@ exports[`foreach iterate on items (on a element node) 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.item_first = i1 === 0 scope.item_last = i1 === _length3 - 1 @@ -448,7 +448,7 @@ exports[`foreach iterate on items (on a element node) 1`] = ` c7.push({text: _8}); } } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -469,8 +469,8 @@ exports[`foreach iterate on items 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.item_first = i1 === 0 scope.item_last = i1 === _length3 - 1 @@ -494,7 +494,7 @@ exports[`foreach iterate on items 1`] = ` } c1.push({text: \`] \`}); } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -515,8 +515,8 @@ exports[`foreach iterate, dict param 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.item_first = i1 === 0 scope.item_last = i1 === _length3 - 1 @@ -540,7 +540,7 @@ exports[`foreach iterate, dict param 1`] = ` } c1.push({text: \`] \`}); } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -561,8 +561,8 @@ exports[`foreach iterate, position 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.elem_first = i1 === 0 scope.elem_last = i1 === _length3 - 1 @@ -583,7 +583,7 @@ exports[`foreach iterate, position 1`] = ` } c1.push({text: \`) \`}); } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -604,8 +604,8 @@ exports[`foreach t-foreach in t-forach 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.number_first = i1 === 0 scope.number_last = i1 === _length3 - 1 @@ -620,8 +620,8 @@ exports[`foreach t-foreach in t-forach 1`] = ` _8 = Object.values(_6); } var _length7 = _7.length; - let _scope9 = scope; - scope = Object.assign(Object.create(context), _scope9); + const _origScope9 = scope; + scope = Object.assign(Object.create(context), scope); for (let i2 = 0; i2 < _length7; i2++) { scope.letter_first = i2 === 0 scope.letter_last = i2 === _length7 - 1 @@ -639,9 +639,9 @@ exports[`foreach t-foreach in t-forach 1`] = ` } c1.push({text: \`] \`}); } - scope = _scope9; + scope = _origScope9; } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -662,8 +662,8 @@ exports[`foreach warn if no key in some case 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.item_first = i1 === 0 scope.item_last = i1 === _length3 - 1 @@ -678,7 +678,7 @@ exports[`foreach warn if no key in some case 1`] = ` c6.push({text: _7}); } } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -725,8 +725,8 @@ exports[`misc global 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.value_first = i1 === 0 scope.value_last = i1 === _length3 - 1 @@ -741,12 +741,12 @@ exports[`misc global 1`] = ` c6.push({text: _7}); } { - let origScope = scope; + const _origScope10 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; { - let origScope = scope; + const _origScope13 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; @@ -754,18 +754,18 @@ exports[`misc global 1`] = ` scope[utils.zero] = c__0; } this.subTemplates['_callee-uses-foo'].call(this, scope, Object.assign({}, extra, {parentNode: c__0})); - scope = origScope; + scope = _origScope13; } this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0})); - scope.foo = 'bbb' + scope.foo = 'bbb'; this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0})); scope[utils.zero] = c__0; } this.subTemplates['_callee-asc'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); - scope = origScope; + scope = _origScope10; } } - scope = _scope5; + scope = _origScope5; this.subTemplates['_callee-asc-toto'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1})); return vn1; }" @@ -1059,23 +1059,23 @@ exports[`t-call (template calling call with several sub nodes on same line 1`] = let c1 = [], p1 = {key:1}; var vn1 = h('div', p1, c1); { - let origScope = scope; + const _origScope3 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; - let c3 = [], p3 = {key:3}; - var vn3 = h('span', p3, c3); - c__0.push(vn3); - c3.push({text: \`hey\`}); - c__0.push({text: \` \`}); let c4 = [], p4 = {key:4}; var vn4 = h('span', p4, c4); c__0.push(vn4); - c4.push({text: \`yay\`}); + c4.push({text: \`hey\`}); + c__0.push({text: \` \`}); + let c5 = [], p5 = {key:5}; + var vn5 = h('span', p5, c5); + c__0.push(vn5); + c5.push({text: \`yay\`}); scope[utils.zero] = c__0; } this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); - scope = origScope; + scope = _origScope3; } return vn1; }" @@ -1106,23 +1106,23 @@ exports[`t-call (template calling cascading t-call t-raw='0' 1`] = ` let c1 = [], p1 = {key:1}; var vn1 = h('div', p1, c1); { - let origScope = scope; + const _origScope10 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; - let c8 = [], p8 = {key:8}; - var vn8 = h('span', p8, c8); - c__0.push(vn8); - c8.push({text: \`hey\`}); + let c11 = [], p11 = {key:11}; + var vn11 = h('span', p11, c11); + c__0.push(vn11); + c11.push({text: \`hey\`}); c__0.push({text: \` \`}); - let c9 = [], p9 = {key:9}; - var vn9 = h('span', p9, c9); - c__0.push(vn9); - c9.push({text: \`yay\`}); + let c12 = [], p12 = {key:12}; + var vn12 = h('span', p12, c12); + c__0.push(vn12); + c12.push({text: \`yay\`}); scope[utils.zero] = c__0; } this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); - scope = origScope; + scope = _origScope10; } return vn1; }" @@ -1191,7 +1191,7 @@ exports[`t-call (template calling recursive template, part 2 1`] = ` let c1 = [], p1 = {key:1}; var vn1 = h('div', p1, c1); { - let origScope = scope; + const _origScope10 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; @@ -1199,7 +1199,7 @@ exports[`t-call (template calling recursive template, part 2 1`] = ` scope[utils.zero] = c__0; } this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); - scope = origScope; + scope = _origScope10; } return vn1; }" @@ -1231,8 +1231,8 @@ exports[`t-call (template calling recursive template, part 2 2`] = ` _7 = Object.values(_5); } var _length6 = _6.length; - let _scope8 = scope; - scope = Object.assign(Object.create(context), _scope8); + const _origScope8 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length6; i1++) { scope.subtree_first = i1 === 0 scope.subtree_last = i1 === _length6 - 1 @@ -1240,7 +1240,7 @@ exports[`t-call (template calling recursive template, part 2 2`] = ` scope.subtree = _6[i1] scope.subtree_value = _7[i1] { - let origScope = scope; + const _origScope9 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; @@ -1248,10 +1248,10 @@ exports[`t-call (template calling recursive template, part 2 2`] = ` scope[utils.zero] = c__0; } this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c2})); - scope = origScope; + scope = _origScope9; } } - scope = _scope8; + scope = _origScope8; }" `; @@ -1265,7 +1265,7 @@ exports[`t-call (template calling recursive template, part 3 1`] = ` let c1 = [], p1 = {key:1}; var vn1 = h('div', p1, c1); { - let origScope = scope; + const _origScope10 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; @@ -1273,7 +1273,7 @@ exports[`t-call (template calling recursive template, part 3 1`] = ` scope[utils.zero] = c__0; } this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); - scope = origScope; + scope = _origScope10; } return vn1; }" @@ -1305,8 +1305,8 @@ exports[`t-call (template calling recursive template, part 3 2`] = ` _7 = Object.values(_5); } var _length6 = _6.length; - let _scope8 = scope; - scope = Object.assign(Object.create(context), _scope8); + const _origScope8 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length6; i1++) { scope.subtree_first = i1 === 0 scope.subtree_last = i1 === _length6 - 1 @@ -1314,7 +1314,7 @@ exports[`t-call (template calling recursive template, part 3 2`] = ` scope.subtree = _6[i1] scope.subtree_value = _7[i1] { - let origScope = scope; + const _origScope9 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; @@ -1322,10 +1322,10 @@ exports[`t-call (template calling recursive template, part 3 2`] = ` scope[utils.zero] = c__0; } this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c2})); - scope = origScope; + scope = _origScope9; } } - scope = _scope8; + scope = _origScope8; }" `; @@ -1339,7 +1339,7 @@ exports[`t-call (template calling scoped parameters 1`] = ` let c1 = [], p1 = {key:1}; var vn1 = h('div', p1, c1); { - let origScope = scope; + const _origScope2 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; @@ -1347,7 +1347,7 @@ exports[`t-call (template calling scoped parameters 1`] = ` scope[utils.zero] = c__0; } this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); - scope = origScope; + scope = _origScope2; } if (scope.foo || scope.foo === 0) { c1.push({text: scope.foo}); @@ -1401,8 +1401,8 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.v_first = i1 === 0 scope.v_last = i1 === _length3 - 1 @@ -1411,7 +1411,7 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = ` scope.v_value = _4[i1] scope.val = scope['v'].val; { - let origScope = scope; + const _origScope8 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; @@ -1419,10 +1419,10 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = ` scope[utils.zero] = c__0; } this.subTemplates['sub'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); - scope = origScope; + scope = _origScope8; } } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -1463,7 +1463,7 @@ exports[`t-call (template calling with unused body 1`] = ` let result; var h = this.h; { - let origScope = scope; + const _origScope3 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; @@ -1473,7 +1473,7 @@ exports[`t-call (template calling with unused body 1`] = ` result = [] this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result})); result = result[0] - scope = origScope; + scope = _origScope3; } return result; }" @@ -1488,7 +1488,7 @@ exports[`t-call (template calling with unused setbody 1`] = ` let result; var h = this.h; { - let origScope = scope; + const _origScope3 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; @@ -1498,7 +1498,7 @@ exports[`t-call (template calling with unused setbody 1`] = ` result = [] this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result})); result = result[0] - scope = origScope; + scope = _origScope3; } return result; }" @@ -1513,7 +1513,7 @@ exports[`t-call (template calling with used body 1`] = ` let result; var h = this.h; { - let origScope = scope; + const _origScope3 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; @@ -1523,7 +1523,7 @@ exports[`t-call (template calling with used body 1`] = ` result = [] this.subTemplates['_callee-printsbody'].call(this, scope, Object.assign({}, extra, {parentNode: result})); result = result[0] - scope = origScope; + scope = _origScope3; } return result; }" @@ -1539,7 +1539,7 @@ exports[`t-call (template calling with used set body 1`] = ` let c1 = [], p1 = {key:1}; var vn1 = h('span', p1, c1); { - let origScope = scope; + const _origScope3 = scope; scope = Object.assign(Object.create(context), scope); { let c__0 = []; @@ -1547,7 +1547,7 @@ exports[`t-call (template calling with used set body 1`] = ` scope[utils.zero] = c__0; } this.subTemplates['_callee-uses-foo'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); - scope = origScope; + scope = _origScope3; } return vn1; }" @@ -1621,6 +1621,59 @@ exports[`t-esc literal 1`] = ` }" `; +exports[`t-esc t-esc is escaped 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"test\\" + let utils = this.constructor.utils; + let scope = Object.create(context); + var h = this.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + const c2 = new utils.VDomArray(); + let c3 = [], p3 = {key:3}; + var vn3 = h('p', p3, c3); + c2.push(vn3); + c3.push({text: \`escaped\`}); + scope.var = c2 + if (scope.var || scope.var === 0) { + const _origScope4 = scope; + scope = Object.assign(Object.create(context), scope); + scope.var = scope.var instanceof utils.VDomArray ? utils.vDomToString(scope.var) : scope.var; + c1.push({text: scope.var}); + scope = _origScope4; + } + return vn1; +}" +`; + +exports[`t-esc t-esc=0 is escaped 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"testCaller\\" + let utils = this.constructor.utils; + let scope = Object.create(context); + var h = this.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + { + const _origScope3 = scope; + scope = Object.assign(Object.create(context), scope); + { + let c__0 = []; + let c4 = [], p4 = {key:4}; + var vn4 = h('p', p4, c4); + c__0.push(vn4); + c4.push({text: \`escaped\`}); + scope[utils.zero] = c__0; + } + this.subTemplates['test'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); + scope = _origScope3; + } + return vn1; +}" +`; + exports[`t-esc variable 1`] = ` "function anonymous(context, extra ) { @@ -1930,8 +1983,8 @@ exports[`t-key t-key directive in a list 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.beer_first = i1 === 0 scope.beer_last = i1 === _length3 - 1 @@ -1947,7 +2000,7 @@ exports[`t-key t-key directive in a list 1`] = ` c7.push({text: _8}); } } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -2029,8 +2082,8 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.action_first = i1 === 0 scope.action_last = i1 === _length3 - 1 @@ -2048,7 +2101,7 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = ` p8.on['click'] = function (e) {if (!context.__owl__.isMounted){return}if (handler9) { handler9(e); } else { context.activate(action); }}; c8.push({text: \`link\`}); } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -2251,8 +2304,8 @@ exports[`t-on t-on with prevent modifier in t-foreach 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.project_first = i1 === 0 scope.project_last = i1 === _length3 - 1 @@ -2272,7 +2325,7 @@ exports[`t-on t-on with prevent modifier in t-foreach 1`] = ` c8.push({text: _10}); } } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -2464,8 +2517,8 @@ exports[`t-ref refs in a loop 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.item_first = i1 === 0 scope.item_last = i1 === _length3 - 1 @@ -2490,7 +2543,7 @@ exports[`t-ref refs in a loop 1`] = ` c7.push({text: _9}); } } - scope = _scope5; + scope = _origScope5; return vn1; }" `; @@ -2563,11 +2616,21 @@ exports[`t-set set from body literal 1`] = ` "function anonymous(context, extra ) { // Template name: \\"test\\" + let utils = this.constructor.utils; let scope = Object.create(context); let result; var h = this.h; - var vn1 = {text: \`ok\`}; - result = vn1; + const c1 = new utils.VDomArray(); + c1.push({text: \`ok\`}); + scope.value = c1 + if (scope.value || scope.value === 0) { + const _origScope2 = scope; + scope = Object.assign(Object.create(context), scope); + scope.value = scope.value instanceof utils.VDomArray ? utils.vDomToString(scope.value) : scope.value; + var vn3 = {text: scope.value}; + result = vn3 + scope = _origScope2; + } return result; }" `; @@ -2576,13 +2639,23 @@ exports[`t-set set from body lookup 1`] = ` "function anonymous(context, extra ) { // Template name: \\"test\\" + let utils = this.constructor.utils; let scope = Object.create(context); var h = this.h; let c1 = [], p1 = {key:1}; var vn1 = h('div', p1, c1); - var _2 = scope['value']; - if (_2 || _2 === 0) { - c1.push({text: _2}); + const c2 = new utils.VDomArray(); + var _3 = scope['value']; + if (_3 || _3 === 0) { + c2.push({text: _3}); + } + scope.stuff = c2 + if (scope.stuff || scope.stuff === 0) { + const _origScope4 = scope; + scope = Object.assign(Object.create(context), scope); + scope.stuff = scope.stuff instanceof utils.VDomArray ? utils.vDomToString(scope.stuff) : scope.stuff; + c1.push({text: scope.stuff}); + scope = _origScope4; } return vn1; }" @@ -2596,6 +2669,9 @@ exports[`t-set set from empty body 1`] = ` var h = this.h; let c1 = [], p1 = {key:1}; var vn1 = h('div', p1, c1); + if (scope.stuff || scope.stuff === 0) { + c1.push({text: scope.stuff}); + } return vn1; }" `; @@ -2617,19 +2693,27 @@ exports[`t-set t-set and t-if 1`] = ` `; exports[`t-set t-set body is evaluated immediately 1`] = ` -"function anonymous(context,extra +"function anonymous(context, extra ) { + // Template name: \\"test\\" + let utils = this.constructor.utils; let scope = Object.create(context); var h = this.h; let c1 = [], p1 = {key:1}; var vn1 = h('div', p1, c1); scope.v1 = 'before'; - scope.v1 = 'after' - let c2 = [], p2 = {key:2}; - var vn2 = h('span', p2, c2); - c1.push(vn2); + const c2 = new utils.VDomArray(); + let c3 = [], p3 = {key:3}; + var vn3 = h('span', p3, c3); + c2.push(vn3); if (scope.v1 || scope.v1 === 0) { - c2.push({text: scope.v1}); + c3.push({text: scope.v1}); + } + scope.v2 = c2 + scope.v1 = 'after'; + if (scope.v2 || scope.v2 === 0) { + const vnodeArray = scope.v2 instanceof utils.VDomArray ? scope.v2 : utils.htmlToVDOM(scope.v2); + c1.push(...vnodeArray); } return vn1; }" @@ -2671,8 +2755,8 @@ exports[`t-set t-set should reuse variable if possible 1`] = ` _4 = Object.values(_2); } var _length3 = _3.length; - let _scope5 = scope; - scope = Object.assign(Object.create(context), _scope5); + const _origScope5 = scope; + scope = Object.assign(Object.create(context), scope); for (let i1 = 0; i1 < _length3; i1++) { scope.elem_first = i1 === 0 scope.elem_last = i1 === _length3 - 1 @@ -2690,9 +2774,73 @@ exports[`t-set t-set should reuse variable if possible 1`] = ` if (scope.v || scope.v === 0) { c8.push({text: scope.v}); } - scope.v = scope['elem'] + scope.v = scope['elem']; + } + scope = _origScope5; + return vn1; +}" +`; + +exports[`t-set t-set with t-value (falsy) and body 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"test\\" + let utils = this.constructor.utils; + let scope = Object.create(context); + var h = this.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + scope.v3 = false; + scope.v1 = 'before'; + scope.v2 = scope.v3; + if (!(scope.v2)) { + const c2 = new utils.VDomArray(); + let c3 = [], p3 = {key:3}; + var vn3 = h('span', p3, c3); + c2.push(vn3); + if (scope.v1 || scope.v1 === 0) { + c3.push({text: scope.v1}); + } + scope.v2 = c2 + } + scope.v1 = 'after'; + scope.v3 = true; + if (scope.v2 || scope.v2 === 0) { + const vnodeArray = scope.v2 instanceof utils.VDomArray ? scope.v2 : utils.htmlToVDOM(scope.v2); + c1.push(...vnodeArray); + } + return vn1; +}" +`; + +exports[`t-set t-set with t-value (truthy) and body 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"test\\" + let utils = this.constructor.utils; + let scope = Object.create(context); + var h = this.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + scope.v3 = 'Truthy'; + scope.v1 = 'before'; + scope.v2 = scope.v3; + if (!(scope.v2)) { + const c2 = new utils.VDomArray(); + let c3 = [], p3 = {key:3}; + var vn3 = h('span', p3, c3); + c2.push(vn3); + if (scope.v1 || scope.v1 === 0) { + c3.push({text: scope.v1}); + } + scope.v2 = c2 + } + scope.v1 = 'after'; + scope.v3 = false; + if (scope.v2 || scope.v2 === 0) { + const vnodeArray = scope.v2 instanceof utils.VDomArray ? scope.v2 : utils.htmlToVDOM(scope.v2); + c1.push(...vnodeArray); } - scope = _scope5; return vn1; }" `; @@ -2701,19 +2849,25 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 1`] = ` "function anonymous(context, extra ) { // Template name: \\"test\\" + let utils = this.constructor.utils; let scope = Object.create(context); var h = this.h; let c1 = [], p1 = {key:1}; var vn1 = h('div', p1, c1); if (scope['flag']) { + const c2 = new utils.VDomArray(); + c2.push({text: \`1\`}); + scope.ourvar = c2 } else { scope.ourvar = 0; } if (scope.ourvar || scope.ourvar === 0) { + const _origScope3 = scope; + scope = Object.assign(Object.create(context), scope); + scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar; c1.push({text: scope.ourvar}); - } else { - c1.push({text: \`1\`}); + scope = _origScope3; } return vn1; }" @@ -2723,19 +2877,25 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 2`] = ` "function anonymous(context, extra ) { // Template name: \\"test\\" + let utils = this.constructor.utils; let scope = Object.create(context); var h = this.h; let c1 = [], p1 = {key:1}; var vn1 = h('div', p1, c1); if (scope['flag']) { + const c2 = new utils.VDomArray(); + c2.push({text: \`1\`}); + scope.ourvar = c2 } else { scope.ourvar = 0; } if (scope.ourvar || scope.ourvar === 0) { + const _origScope3 = scope; + scope = Object.assign(Object.create(context), scope); + scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar; c1.push({text: scope.ourvar}); - } else { - c1.push({text: \`1\`}); + scope = _origScope3; } return vn1; }" @@ -2745,6 +2905,7 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 1`] = ` "function anonymous(context, extra ) { // Template name: \\"test\\" + let utils = this.constructor.utils; let scope = Object.create(context); var h = this.h; let c1 = [], p1 = {key:1}; @@ -2753,11 +2914,16 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 1`] = ` scope.ourvar = 1; } else { + const c2 = new utils.VDomArray(); + c2.push({text: \`0\`}); + scope.ourvar = c2 } if (scope.ourvar || scope.ourvar === 0) { + const _origScope3 = scope; + scope = Object.assign(Object.create(context), scope); + scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar; c1.push({text: scope.ourvar}); - } else { - c1.push({text: \`0\`}); + scope = _origScope3; } return vn1; }" @@ -2767,6 +2933,7 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 2`] = ` "function anonymous(context, extra ) { // Template name: \\"test\\" + let utils = this.constructor.utils; let scope = Object.create(context); var h = this.h; let c1 = [], p1 = {key:1}; @@ -2775,11 +2942,16 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 2`] = ` scope.ourvar = 1; } else { + const c2 = new utils.VDomArray(); + c2.push({text: \`0\`}); + scope.ourvar = c2 } if (scope.ourvar || scope.ourvar === 0) { + const _origScope3 = scope; + scope = Object.assign(Object.create(context), scope); + scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar; c1.push({text: scope.ourvar}); - } else { - c1.push({text: \`0\`}); + scope = _origScope3; } return vn1; }" @@ -2789,13 +2961,23 @@ exports[`t-set value priority 1`] = ` "function anonymous(context, extra ) { // Template name: \\"test\\" + let utils = this.constructor.utils; let scope = Object.create(context); var h = this.h; let c1 = [], p1 = {key:1}; var vn1 = h('div', p1, c1); scope.value = 1; + if (!(scope.value)) { + const c2 = new utils.VDomArray(); + c2.push({text: \`2\`}); + scope.value = c2 + } if (scope.value || scope.value === 0) { + const _origScope3 = scope; + scope = Object.assign(Object.create(context), scope); + scope.value = scope.value instanceof utils.VDomArray ? utils.vDomToString(scope.value) : scope.value; c1.push({text: scope.value}); + scope = _origScope3; } return vn1; }" diff --git a/tests/qweb/qweb.test.ts b/tests/qweb/qweb.test.ts index b59212ef..e950c403 100644 --- a/tests/qweb/qweb.test.ts +++ b/tests/qweb/qweb.test.ts @@ -139,6 +139,17 @@ describe("t-esc", () => { qweb.addTemplate("test", `nope`); expect(renderToString(qweb, "test")).toBe("nope"); }); + test("t-esc is escaped", () => { + qweb.addTemplate("test", `

escaped

`); + const domRendered = renderToDOM(qweb, "test"); + expect(domRendered.textContent).toBe("

escaped

"); + }); + test("t-esc=0 is escaped", () => { + qweb.addTemplate("test", ``); + qweb.addTemplate("testCaller", `

escaped

`); + const domRendered = renderToDOM(qweb, "testCaller") as HTMLElement; + expect(domRendered.querySelector("span")!.textContent).toBe("

escaped

"); + }); }); describe("t-raw", () => { @@ -289,10 +300,45 @@ describe("t-set", () => { + ` + ); + + expect(renderToString(qweb, "test")).toBe("
before
"); + }); + + test("t-set with t-value (falsy) and body", () => { + qweb.addTemplate( + "test", + `
+ + + + + + + +
`); expect(renderToString(qweb, "test")).toBe("
before
"); }); + + test("t-set with t-value (truthy) and body", () => { + qweb.addTemplate( + "test", + `
+ + + + + + + + +
`); + + expect(renderToString(qweb, "test")).toBe("
Truthy
"); + }); }); describe("t-if", () => { @@ -1611,11 +1657,12 @@ describe("debugging", () => { const consoleLog = console.log; console.log = jest.fn(); qweb.addTemplates(` +

coucou

-
` - ); + +
`); qweb.render("test"); expect(console.log).toHaveBeenCalledTimes(1);