Compare commits

...

9 Commits

Author SHA1 Message Date
Samuel Degueldre 73f94fba3f [REL] v1.4.9
# v1.4.9

- fix: correctly capture the scope of arrow functions passed as props
2021-12-07 10:09:12 +01:00
Samuel Degueldre 7a16449724 [IMP] CI: make formatting check mandatory for ci check 2021-12-03 14:08:05 +01:00
Samuel Degueldre 718c765e3b [FIX] qweb: correctly capture the scope of arrow functions in props 2021-12-03 14:08:05 +01:00
Samuel Degueldre 150d620b8e [REF] run prettier 2021-12-03 14:08:05 +01:00
Géry Debongnie 307b936d01 [REL] v1.4.8
# v1.4.8

- fix: prevent crash in some rare cases
2021-11-03 13:44:46 +01:00
Achraf (abz) 6950f8e628 [FIX] components/fiber: Call patch only if target is valid
Currently in some cases, adding an attachment via lognote creates a traceback.
Error : shouldPatch is true while `vnode` is not defined, so `patch()` failed
This is a hotfix correcting this problem by calling `patch()` only if `shouldPatch` is true **and** the `vnode` is set.

opw-2645203
2021-11-03 11:37:04 +01:00
Géry Debongnie 4e3b7c74da [REL] v1.4.7
v1.4.7

fix: memory leak in some templates
2021-10-19 16:47:20 +02:00
Géry Debongnie 98bb2842d8 [FIX] run prettier on codebase 2021-10-19 16:45:04 +02:00
Géry Debongnie 6e185f987b [FIX] qweb: do not leak values in global context in rare cases
In some very rare cases (such as the use of the t-foreach directive),
Owl did leak the values in the render context in the global context.
This was due to the fact that the compiled template looked like this:

let _3 = _4 = _5;

instead of

let _3 = _5;
let _4 = _5;
2021-10-19 16:41:45 +02:00
20 changed files with 697 additions and 60 deletions
+1 -1
View File
@@ -24,4 +24,4 @@ jobs:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run test
- run: npm run prettier
- run: npm run check-formatting
+1 -1
View File
@@ -124,7 +124,7 @@ npm install @odoo/owl
If you want to use a simple `<script>` tag, the last release can be downloaded here:
- [owl-1.4.6](https://github.com/odoo/owl/releases/tag/v1.4.6)
- [owl-1.4.9](https://github.com/odoo/owl/releases/tag/v1.4.9)
## License
+4 -4
View File
@@ -176,9 +176,9 @@ const uuid = generateUUID();
class MyComponent extends Component {
static template = xml`<div data-o-${uuid}="">...</div>`;
static style = css`
[data-o-${uuid}] {
color: red;
}
`;
[data-o-${uuid}] {
color: red;
}
`;
}
```
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "1.4.6",
"version": "1.4.9",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"browser": "dist/owl.iife.js",
+1 -1
View File
@@ -1,6 +1,6 @@
# 🦉 OWL Roadmap 🦉
- Current version: 1.4.6
- Current version: 1.4.9
- Status: stable
This roadmap is only an attempt at predicting Owl's future. Everything may
+1 -1
View File
@@ -767,7 +767,7 @@ export async function mount<T extends Type<Component>>(
const { env, props, target } = params;
let origEnv = C.hasOwnProperty("env") ? (C as any).env : null;
if (env) {
((C as any) as typeof Component).env = env;
(C as any as typeof Component).env = env;
}
const component: Component = new C(null, props);
if (origEnv) {
+5 -1
View File
@@ -234,7 +234,11 @@ QWeb.addDirective({
} else if (!name.startsWith("t-")) {
if (name !== "class" && name !== "style") {
// this is a prop!
props[name] = ctx.formatExpression(value) || "undefined";
if (value.includes("=>")) {
props[name] = ctx.captureExpression(value);
} else {
props[name] = ctx.formatExpression(value) || "undefined";
}
}
}
}
+3 -2
View File
@@ -243,8 +243,9 @@ export class Fiber {
}
component.__patch(target!, fiber.vnode!);
} else {
if (fiber.shouldPatch) {
component.__patch(component.__owl__.vnode!, fiber.vnode!);
const vnode = component.__owl__.vnode;
if (fiber.shouldPatch && vnode) {
component.__patch(vnode, fiber.vnode!);
// When updating a Component's props (in directive),
// the component has a pvnode AND should be patched.
// However, its pvnode.elm may have changed if it is a High Order Component
+2 -1
View File
@@ -326,7 +326,8 @@ QWeb.addDirective({
ctx.addLine(`if (!_${arrayID}) { throw new Error('QWeb error: Invalid loop expression')}`);
let keysID = ctx.generateID();
let valuesID = ctx.generateID();
ctx.addLine(`let _${keysID} = _${valuesID} = _${arrayID};`);
ctx.addLine(`let _${keysID} = _${arrayID};`);
ctx.addLine(`let _${valuesID} = _${arrayID};`);
ctx.addIf(`!(_${arrayID} instanceof Array)`);
ctx.addLine(`_${keysID} = Object.keys(_${arrayID});`);
ctx.addLine(`_${valuesID} = Object.values(_${arrayID});`);
+12 -1
View File
@@ -163,7 +163,18 @@ export class CompilationContext {
const done = new Set();
return tokens
.map((tok) => {
if (tok.varName) {
// "this" in captured expressions should be the current component
if (tok.value === "this") {
if (!done.has("this")) {
done.add("this");
this.addLine(`const this_${argId} = utils.getComponent(context);`);
}
tok.value = `this_${argId}`;
}
// Variables that should be looked up in the scope. isLocal is for arrow
// function arguments that should stay untouched (eg "ev => ev" should
// not become "const ev_1 = scope['ev']; ev_1 => ev_1")
if (tok.varName && !tok.isLocal) {
if (!done.has(tok.varName)) {
done.add(tok.varName);
this.addLine(`const ${tok.varName}_${argId} = ${tok.value};`);
+15 -3
View File
@@ -25,9 +25,10 @@
// 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 = Object.assign(Object.create(null), {
and: "&&",
@@ -69,6 +70,7 @@ interface Token {
size?: number;
varName?: string;
replace?: Function;
isLocal?: boolean;
}
const STATIC_TOKEN_MAP: { [key: string]: TKind } = Object.assign(Object.create(null), {
@@ -253,6 +255,7 @@ const isRightSeparator = (token) =>
* the list of variables so it does not get replaced by a lookup in the context
*/
export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar }): Token[] {
const localVars = new Set<string>();
scope = Object.create(scope);
const tokens = tokenize(expr);
@@ -307,11 +310,13 @@ export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar
if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) {
tokens[j].value = tokens[j].originalValue!;
scope[tokens[j].value] = { id: tokens[j].value, expr: tokens[j].value };
localVars.add(tokens[j].value);
}
j--;
}
} else {
scope[token.value] = { id: token.value, expr: token.value };
localVars.add(token.value);
}
}
@@ -326,6 +331,13 @@ export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar
}
i++;
}
// Mark all variables that have been used locally.
// This assumes the expression has only one scope (incorrect but "good enough for now")
for (const token of tokens) {
if (token.type === "SYMBOL" && localVars.has(token.value)) {
token.isLocal = true;
}
}
return tokens;
}
@@ -51,7 +51,8 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
let vn1 = h('div', p1, c1);
let _2 = scope['state'].s;
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -68,7 +69,8 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
let key1 = i1;
let _6 = scope['section'].blips;
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
let _7 = _8 = _6;
let _7 = _6;
let _8 = _6;
if (!(_6 instanceof Array)) {
_7 = Object.keys(_6);
_8 = Object.values(_6);
@@ -309,7 +311,8 @@ exports[`composition sub components with some state rendered in a loop 1`] = `
let vn1 = h('div', p1, c1);
let _2 = scope['state'].numbers;
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -597,7 +600,8 @@ exports[`other directives with t-component t-on expression captured in t-foreach
scope.iter = 0;
let _2 = scope['arr'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -639,7 +643,8 @@ exports[`other directives with t-component t-on expression in t-foreach 1`] = `
let vn1 = h('div', p1, c1);
let _2 = scope['state'].values;
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -691,7 +696,8 @@ exports[`other directives with t-component t-on expression in t-foreach with t-s
scope.bossa = 'nova';
let _2 = scope['state'].values;
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -744,7 +750,8 @@ exports[`other directives with t-component t-on method call in t-foreach 1`] = `
let vn1 = h('div', p1, c1);
let _2 = scope['state'].values;
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -1410,7 +1417,8 @@ exports[`other directives with t-component t-set outside modified in t-foreach 1
scope.iter = 0;
let _2 = scope['state'].values;
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -1446,6 +1454,350 @@ exports[`other directives with t-component t-set outside modified in t-foreach 1
}"
`;
exports[`props evaluation arrow function prop captures component instance as 'this' 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"Parent\\"
let utils = this.constructor.utils;
let QWeb = this.constructor;
let parent = context;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
// Component 'Child'
const this_2 = utils.getComponent(context);
let w3 = '__4__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__4__']] : false;
let props3 = {callback:value=>this_2.setValue(value),value:scope['state'].val};
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
w3.destroy();
w3 = false;
}
if (w3) {
w3.__updateProps(props3, extra.fiber, undefined);
let pvnode = w3.__owl__.pvnode;
c1.push(pvnode);
} else {
let componentKey3 = \`Child\`;
let W3 = scope['Child'] || context.constructor.components[componentKey3] || QWeb.components[componentKey3];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3);
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
c1.push(pvnode);
w3.__owl__.pvnode = pvnode;
}
w3.__owl__.parentLastFiberId = extra.fiber.id;
return vn1;
}"
`;
exports[`props evaluation arrow function prop captures component instance as 'this' 2`] = `
"function anonymous(context, extra
) {
// Template name: \\"Child\\"
let scope = Object.create(context);
let h = this.h;
let c5 = [], p5 = {key:5};
let vn5 = h('span', p5, c5);
let _6 = scope['props'].value;
if (_6 != null) {
c5.push({text: _6});
}
return vn5;
}"
`;
exports[`props evaluation arrow function prop captures context component instance as 'this' inside slot 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"Parent\\"
let utils = this.constructor.utils;
let QWeb = this.constructor;
let parent = context;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
// Component 'Wrapper'
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
let props2 = {};
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
w2.destroy();
w2 = false;
}
if (w2) {
w2.__updateProps(props2, extra.fiber, utils.combine(context, scope));
let pvnode = w2.__owl__.pvnode;
c1.push(pvnode);
} else {
let componentKey2 = \`Wrapper\`;
let W2 = scope['Wrapper'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
w2.__owl__.slotId = 1;
let fiber = w2.__prepare(extra.fiber, utils.combine(context, scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
}
w2.__owl__.parentLastFiberId = extra.fiber.id;
return vn1;
}"
`;
exports[`props evaluation arrow function prop captures context component instance as 'this' inside slot 2`] = `
"function anonymous(context, extra
) {
// Template name: \\"Wrapper\\"
let utils = this.constructor.utils;
let result;
let h = this.h;
const slot8 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
if (slot8) {
let children9= []
result = {}
slot8.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: children9, parent: extra.parent || context}));
utils.defineProxy(result, children9[0]);
}
return result;
}"
`;
exports[`props evaluation arrow function prop captures context component instance as 'this' inside slot 3`] = `
"function anonymous(context, extra
) {
// Template name: \\"slot_default_template\\"
let utils = this.constructor.utils;
let QWeb = this.constructor;
let parent = extra.parent;
let scope = Object.create(context);
let h = this.h;
let c4 = extra.parentNode;
// Component 'Child'
const this_5 = utils.getComponent(context);
let w6 = '__7__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__7__']] : false;
let props6 = {callback:value=>this_5.setValue(value),value:scope['state'].val};
if (w6 && w6.__owl__.currentFiber && !w6.__owl__.vnode) {
w6.destroy();
w6 = false;
}
if (w6) {
w6.__updateProps(props6, extra.fiber, undefined);
let pvnode = w6.__owl__.pvnode;
c4.push(pvnode);
} else {
let componentKey6 = \`Child\`;
let W6 = scope['Child'] || context.constructor.components[componentKey6] || QWeb.components[componentKey6];
if (!W6) {throw new Error('Cannot find the definition of component \\"' + componentKey6 + '\\"')}
w6 = new W6(parent, props6);
parent.__owl__.cmap['__7__'] = w6.__owl__.id;
let fiber = w6.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: '__7__', hook: {remove() {},destroy(vn) {w6.destroy();}}});
c4.push(pvnode);
w6.__owl__.pvnode = pvnode;
}
w6.__owl__.parentLastFiberId = extra.fiber.id;
}"
`;
exports[`props evaluation arrow function prop captures context component instance as 'this' inside slot 4`] = `
"function anonymous(context, extra
) {
// Template name: \\"Child\\"
let scope = Object.create(context);
let h = this.h;
let c10 = [], p10 = {key:10};
let vn10 = h('span', p10, c10);
let _11 = scope['props'].value;
if (_11 != null) {
c10.push({text: _11});
}
return vn10;
}"
`;
exports[`props evaluation arrow function prop captures context component instance as 'this' inside slot default content 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"Parent\\"
let utils = this.constructor.utils;
let QWeb = this.constructor;
let parent = context;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
// Component 'Wrapper'
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
let props2 = {};
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
w2.destroy();
w2 = false;
}
if (w2) {
w2.__updateProps(props2, extra.fiber, undefined);
let pvnode = w2.__owl__.pvnode;
c1.push(pvnode);
} else {
let componentKey2 = \`Wrapper\`;
let W2 = scope['Wrapper'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
}
w2.__owl__.parentLastFiberId = extra.fiber.id;
return vn1;
}"
`;
exports[`props evaluation arrow function prop captures context component instance as 'this' inside slot default content 2`] = `
"function anonymous(context, extra
) {
// Template name: \\"Wrapper\\"
let utils = this.constructor.utils;
let QWeb = this.constructor;
let parent = context;
let scope = Object.create(context);
let result;
let h = this.h;
const slot4 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
if (slot4) {
let children5= []
result = {}
slot4.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: children5, parent: extra.parent || context}));
utils.defineProxy(result, children5[0]);
} else {
// Component 'Child'
const this_6 = utils.getComponent(context);
let w7 = '__8__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__8__']] : false;
let vn9 = {};
result = vn9;
let props7 = {callback:value=>this_6.setValue(value),value:scope['state'].val};
if (w7 && w7.__owl__.currentFiber && !w7.__owl__.vnode) {
w7.destroy();
w7 = false;
}
if (w7) {
w7.__updateProps(props7, extra.fiber, undefined);
let pvnode = w7.__owl__.pvnode;
utils.defineProxy(vn9, pvnode);
} else {
let componentKey7 = \`Child\`;
let W7 = scope['Child'] || context.constructor.components[componentKey7] || QWeb.components[componentKey7];
if (!W7) {throw new Error('Cannot find the definition of component \\"' + componentKey7 + '\\"')}
w7 = new W7(parent, props7);
parent.__owl__.cmap['__8__'] = w7.__owl__.id;
let fiber = w7.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: '__8__', hook: {remove() {},destroy(vn) {w7.destroy();}}});
utils.defineProxy(vn9, pvnode);
w7.__owl__.pvnode = pvnode;
}
w7.__owl__.parentLastFiberId = extra.fiber.id;
}
return result;
}"
`;
exports[`props evaluation arrow function prop captures context component instance as 'this' inside slot default content 3`] = `
"function anonymous(context, extra
) {
// Template name: \\"Child\\"
let scope = Object.create(context);
let h = this.h;
let c10 = [], p10 = {key:10};
let vn10 = h('span', p10, c10);
let _11 = scope['props'].value;
if (_11 != null) {
c10.push({text: _11});
}
return vn10;
}"
`;
exports[`props evaluation arrow function prop captures loop variables 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"Parent\\"
let utils = this.constructor.utils;
let QWeb = this.constructor;
let parent = context;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _2 = [0,1];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.loopVar_first = i1 === 0
scope.loopVar_last = i1 === _length3 - 1
scope.loopVar_index = i1
scope.loopVar = _3[i1]
scope.loopVar_value = _4[i1]
let key1 = scope['loopVar'];
// Component 'Child'
const this_6 = utils.getComponent(context);
const loopVar_6 = scope['loopVar'];
let k8 = \`__8__\${key1}__\`;
let w7 = k8 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k8]] : false;
let props7 = {callback:()=>this_6.setValue(loopVar_6),value:scope['state'].val};
if (w7 && w7.__owl__.currentFiber && !w7.__owl__.vnode) {
w7.destroy();
w7 = false;
}
if (w7) {
w7.__updateProps(props7, extra.fiber, undefined);
let pvnode = w7.__owl__.pvnode;
c1.push(pvnode);
} else {
let componentKey7 = \`Child\`;
let W7 = scope['Child'] || context.constructor.components[componentKey7] || QWeb.components[componentKey7];
if (!W7) {throw new Error('Cannot find the definition of component \\"' + componentKey7 + '\\"')}
w7 = new W7(parent, props7);
parent.__owl__.cmap[k8] = w7.__owl__.id;
let fiber = w7.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: k8, hook: {remove() {},destroy(vn) {w7.destroy();}}});
c1.push(pvnode);
w7.__owl__.pvnode = pvnode;
}
w7.__owl__.parentLastFiberId = extra.fiber.id;
}
scope = _origScope5;
return vn1;
}"
`;
exports[`props evaluation arrow function prop captures loop variables 2`] = `
"function anonymous(context, extra
) {
// Template name: \\"Child\\"
let scope = Object.create(context);
let h = this.h;
let c9 = [], p9 = {key:9};
let vn9 = h('span', p9, c9);
let _10 = scope['props'].value;
if (_10 != null) {
c9.push({text: _10});
}
return vn9;
}"
`;
exports[`props evaluation t-set with a body expression can be used as textual prop 1`] = `
"function anonymous(context, extra
) {
@@ -1560,7 +1912,8 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument
let vn1 = h('div', p1, c1);
let _2 = scope['items'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -1838,7 +2191,8 @@ exports[`t-model directive in a t-foreach 1`] = `
let vn1 = h('div', p1, c1);
let _2 = scope['state'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -1878,7 +2232,8 @@ exports[`t-model directive in a t-foreach, part 2 1`] = `
let vn1 = h('div', p1, c1);
let _2 = scope['state'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -414,7 +414,8 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
c1.push(vn2);
let _3 = scope['state'].users;
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
let _4 = _5 = _3;
let _4 = _3;
let _5 = _3;
if (!(_3 instanceof Array)) {
_4 = Object.keys(_3);
_5 = Object.values(_3);
@@ -512,7 +513,8 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
c1.push(vn2);
let _3 = scope['state'].users;
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
let _4 = _5 = _3;
let _4 = _3;
let _5 = _3;
if (!(_3 instanceof Array)) {
_4 = Object.keys(_3);
_5 = Object.values(_3);
@@ -645,7 +647,8 @@ exports[`t-slot directive slots in t-foreach in t-foreach 1`] = `
let vn1 = h('div', p1, c1);
let _2 = scope['tree'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -672,7 +675,8 @@ exports[`t-slot directive slots in t-foreach in t-foreach 1`] = `
c1.push(vn8);
let _9 = scope['node1'].nodes;
if (!_9) { throw new Error('QWeb error: Invalid loop expression')}
let _10 = _11 = _9;
let _10 = _9;
let _11 = _9;
if (!(_9 instanceof Array)) {
_10 = Object.keys(_9);
_11 = Object.values(_9);
+2 -1
View File
@@ -265,7 +265,8 @@ describe("class and style attributes with t-component", () => {
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("");
});
+170
View File
@@ -1881,6 +1881,176 @@ describe("props evaluation ", () => {
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>&lt;p&gt;4343&lt;/p&gt;<p>43</p></span></div>");
});
test("arrow function prop captures component instance as 'this'", async () => {
expect.assertions(5);
let child, parent;
class Child extends Component {
setup() {
child = this;
}
}
env.qweb.addTemplate("Child", `<span><t t-esc="props.value"/></span>`);
class Parent extends Component {
static components = { Child };
state = useState({ val: 42 });
setup() {
parent = this;
}
setValue(value) {
expect(this).toBe(parent);
this.state.val = value;
}
}
env.qweb.addTemplate(
"Parent",
`<div>
<Child callback="value => this.setValue(value)" value="state.val"/>
</div>`
);
const widget = new Parent();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
child.props.callback(123);
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>123</span></div>");
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
expect(env.qweb.templates.Child.fn.toString()).toMatchSnapshot();
});
test("arrow function prop captures context component instance as 'this' inside slot", async () => {
expect.assertions(7);
let child, parent;
class Child extends Component {
setup() {
child = this;
}
}
env.qweb.addTemplate("Child", `<span><t t-esc="props.value"/></span>`);
class Wrapper extends Component {}
env.qweb.addTemplate("Wrapper", `<t t-slot="default"/>`);
class Parent extends Component {
static components = { Child, Wrapper };
state = useState({ val: 42 });
setup() {
parent = this;
}
setValue(value) {
expect(this).toBe(parent);
this.state.val = value;
}
}
env.qweb.addTemplate(
"Parent",
`<div>
<Wrapper>
<Child callback="value => this.setValue(value)" value="state.val"/>
</Wrapper>
</div>`
);
const widget = new Parent();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
child.props.callback(123);
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>123</span></div>");
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
expect(env.qweb.templates.Wrapper.fn.toString()).toMatchSnapshot();
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
expect(env.qweb.templates.Child.fn.toString()).toMatchSnapshot();
});
test("arrow function prop captures context component instance as 'this' inside slot default content", async () => {
expect.assertions(6);
let child, wrapper;
class Child extends Component {
setup() {
child = this;
}
}
env.qweb.addTemplate("Child", `<span><t t-esc="props.value"/></span>`);
class Wrapper extends Component {
static components = { Child };
state = useState({ val: 42 });
setup() {
wrapper = this;
}
setValue(value) {
expect(this).toBe(wrapper);
this.state.val = value;
}
}
env.qweb.addTemplate(
"Wrapper",
`<t t-slot="default">
<Child callback="value => this.setValue(value)" value="state.val"/>
</t>`
);
class Parent extends Component {
static components = { Wrapper };
}
env.qweb.addTemplate(
"Parent",
`<div>
<Wrapper/>
</div>`
);
const widget = new Parent();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
child.props.callback(123);
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>123</span></div>");
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
expect(env.qweb.templates.Wrapper.fn.toString()).toMatchSnapshot();
expect(env.qweb.templates.Child.fn.toString()).toMatchSnapshot();
});
test("arrow function prop captures loop variables", async () => {
let children = [];
class Child extends Component {
setup() {
children.push(this);
}
}
env.qweb.addTemplate("Child", `<span><t t-esc="props.value"/></span>`);
class Parent extends Component {
static components = { Child };
state = useState({ val: 42 });
setValue(value) {
this.state.val = value;
}
}
env.qweb.addTemplate(
"Parent",
`<div>
<t t-foreach="[0, 1]" t-as="loopVar" t-key="loopVar">
<Child callback="() => this.setValue(loopVar)" value="state.val"/>
</t>
</div>`
);
const widget = new Parent();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>42</span><span>42</span></div>");
children[0].props.callback();
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>0</span><span>0</span></div>");
children[1].props.callback();
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>1</span><span>1</span></div>");
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
expect(env.qweb.templates.Child.fn.toString()).toMatchSnapshot();
});
});
describe("other directives with t-component", () => {
+8 -4
View File
@@ -361,7 +361,8 @@ describe("component error handling (catchError)", () => {
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);
@@ -472,7 +473,8 @@ describe("component error handling (catchError)", () => {
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);
@@ -499,7 +501,8 @@ describe("component error handling (catchError)", () => {
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);
@@ -523,7 +526,8 @@ describe("component error handling (catchError)", () => {
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);
});
+2 -1
View File
@@ -546,7 +546,8 @@ describe("Portal: Basic use and DOM placement", () => {
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);
});
+44 -22
View File
@@ -429,7 +429,8 @@ exports[`foreach does not pollute the rendering context 1`] = `
let vn1 = h('div', p1, c1);
let _2 = [1];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -464,7 +465,8 @@ exports[`foreach iterate on items (on a element node) 1`] = `
let vn1 = h('div', p1, c1);
let _2 = [1,2];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -502,7 +504,8 @@ exports[`foreach iterate on items 1`] = `
let vn1 = h('div', p1, c1);
let _2 = [3,2,1];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -549,7 +552,8 @@ exports[`foreach iterate, dict param 1`] = `
let vn1 = h('div', p1, c1);
let _2 = scope['value'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -596,7 +600,8 @@ exports[`foreach iterate, position 1`] = `
let vn1 = h('div', p1, c1);
let _2 = Array(5);
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -641,7 +646,8 @@ exports[`foreach t-call with body in t-foreach in t-foreach 1`] = `
let vn1 = h('div', p1, c1);
let _2 = scope['numbers'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -658,7 +664,8 @@ exports[`foreach t-call with body in t-foreach in t-foreach 1`] = `
let key1 = i1;
let _6 = scope['letters'];
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
let _7 = _8 = _6;
let _7 = _6;
let _8 = _6;
if (!(_6 instanceof Array)) {
_7 = Object.keys(_6);
_8 = Object.values(_6);
@@ -729,7 +736,8 @@ exports[`foreach t-call without body in t-foreach in t-foreach 1`] = `
let vn1 = h('div', p1, c1);
let _2 = scope['numbers'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -746,7 +754,8 @@ exports[`foreach t-call without body in t-foreach in t-foreach 1`] = `
let key1 = i1;
let _6 = scope['letters'];
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
let _7 = _8 = _6;
let _7 = _6;
let _8 = _6;
if (!(_6 instanceof Array)) {
_7 = Object.keys(_6);
_8 = Object.values(_6);
@@ -811,7 +820,8 @@ exports[`foreach t-foreach in t-forach 1`] = `
let vn1 = h('div', p1, c1);
let _2 = scope['numbers'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -828,7 +838,8 @@ exports[`foreach t-foreach in t-forach 1`] = `
let key1 = i1;
let _6 = scope['letters'];
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
let _7 = _8 = _6;
let _7 = _6;
let _8 = _6;
if (!(_6 instanceof Array)) {
_7 = Object.keys(_6);
_8 = Object.values(_6);
@@ -871,7 +882,8 @@ exports[`foreach warn if no key in some case 1`] = `
let vn1 = h('div', p1, c1);
let _2 = [1,2];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -940,7 +952,8 @@ exports[`misc global 1`] = `
let vn1 = h('div', p1, c1);
let _2 = [4,5,6];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -1666,7 +1679,8 @@ exports[`t-call (template calling recursive template, part 2 2`] = `
}
let _6 = scope['node'].children||[];
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
let _7 = _8 = _6;
let _7 = _6;
let _8 = _6;
if (!(_6 instanceof Array)) {
_7 = Object.keys(_6);
_8 = Object.values(_6);
@@ -1746,7 +1760,8 @@ exports[`t-call (template calling recursive template, part 3 2`] = `
}
let _6 = scope['node'].children||[];
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
let _7 = _8 = _6;
let _7 = _6;
let _8 = _6;
if (!(_6 instanceof Array)) {
_7 = Object.keys(_6);
_8 = Object.values(_6);
@@ -1832,7 +1847,8 @@ exports[`t-call (template calling recursive template, part 4: with t-set recursi
}
let _6 = scope['node'].children||[];
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
let _7 = _8 = _6;
let _7 = _6;
let _8 = _6;
if (!(_6 instanceof Array)) {
_7 = Object.keys(_6);
_8 = Object.values(_6);
@@ -1998,7 +2014,8 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = `
let vn1 = h('div', p1, c1);
let _2 = scope['list'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -2775,7 +2792,8 @@ exports[`t-key t-key directive in a list 1`] = `
let vn1 = h('ul', p1, c1);
let _2 = scope['beers'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -2879,7 +2897,8 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = `
let vn1 = h('ul', p1, c1);
let _2 = ['someval'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -3161,7 +3180,8 @@ exports[`t-on t-on with prevent modifier in t-foreach 1`] = `
let vn1 = h('div', p1, c1);
let _2 = scope['projects'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -3432,7 +3452,8 @@ exports[`t-ref refs in a loop 1`] = `
let vn1 = h('div', p1, c1);
let _2 = scope['items'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -3676,7 +3697,8 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
scope.v = 1;
let _2 = scope['list'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
@@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`memory t-foreach does not leak stuff in global scope 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('p', p1, c1);
let _2 = [3,2,1];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _2;
let _4 = _2;
if (!(_2 instanceof Array)) {
_3 = Object.keys(_2);
_4 = Object.values(_2);
}
let _length3 = _3.length;
let _origScope5 = scope;
scope = Object.create(scope);
for (let i1 = 0; i1 < _length3; i1++) {
scope.item_first = i1 === 0
scope.item_last = i1 === _length3 - 1
scope.item_index = i1
scope.item = _3[i1]
scope.item_value = _4[i1]
let key1 = i1;
let _6 = scope['item'];
if (_6 != null) {
c1.push({text: _6});
}
}
scope = _origScope5;
return vn1;
}"
`;
+14
View File
@@ -0,0 +1,14 @@
import { QWeb } from "../../src/qweb/index";
import { renderToString } from "../helpers";
describe("memory", () => {
test("t-foreach does not leak stuff in global scope", () => {
let qweb = new QWeb();
const initialNumberOfGlobals = Object.keys(window).length;
qweb.addTemplate("test", `<p><t t-foreach="[3, 2, 1]" t-as="item"><t t-esc="item"/></t></p>`);
const result = renderToString(qweb, "test");
const expected = `<p>321</p>`;
expect(result).toBe(expected);
expect(Object.keys(window).length).toBe(initialNumberOfGlobals);
});
});