Compare commits

..

20 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
Géry Debongnie c0f495661d [REL] v1.4.6
# v1.4.6

- fix: crash in component render (in rare cases)
- fix: build system now target ES2017 instead of ESNext
- fix: remove useless log in prod mode
2021-10-04 15:16:01 +02:00
Géry Debongnie 0f7a8289a6 [FIX] config: do not log anything if in prod mode
It is worse having a dev mode in a production environment than the
opposite. The purpose of the warning was to avoid that situation.

However, in prod mode, it is not really necessary to log the config key.

closes #915
2021-10-04 14:57:30 +02:00
Géry Debongnie 1c3b04f6a8 [FIX] build: fix typescript target to ES2017
This makes sure that we do not leak modern code in odoo.
2021-10-04 14:57:30 +02:00
Géry Debongnie 64db7777dd [FIX] component: async issue
This is a tricky commit. The key point is that the Fiber.complete
method, which commits a rendering to the DOM works like this: it
traverses the component tree, patch the corresponding DOM for each
component, calls the mounted/destroy hooks, and reset the currentfiber
of components to null, all synchronously.

However, this means that it is possible for components to initiate a
rendering (which create a new currentFiber) before the currentFiber is
reset to null, so the internal state of owl is corrupted. This can
occurs in a crash, as in the test that accompanies this commit.

To fix this, we take care of resetting the currentFiber first, while we
walk the component tree. Then, the internal state is always consistent
(i.e. a currentFiber to null means that there is no pending rendering)

closes #904
2021-10-04 14:57:30 +02:00
Géry Debongnie 37313c47a3 [REL] v1.4.5
# v1.4.5

- qweb: expose translatable attributes
- ci/tests: update jest/rollup
- doc: clarify tutorial
- doc: update readme, add nice badges
2021-09-22 16:34:02 +02:00
Géry Debongnie 3f563de9c0 [CLEANUP] run prettier on codebase 2021-09-22 16:29:59 +02:00
Géry Debongnie 97564a7612 [FIX] tests: update tests error messages
Updating my node version to v16, I noticed many tests breaking, because
the way errors are formatted did change.  This commit ensures that the
test suite keeps working for all node versions.
2021-09-22 16:00:37 +02:00
Géry Debongnie f1abf7f2ea [DOC] update readme, to add nice badges 2021-09-21 09:36:15 +02:00
Géry Debongnie 8464a1b04e [IMP] qweb: expose translatable attributes
This commit allows owl users to add any attribute to the list of
translatable attributes.

closes #903
2021-09-21 09:36:15 +02:00
Géry Debongnie c0a62dfd05 [DOC] clarify tutorial
The part about setting up the various files did not express clearly that
we need the iife file, not the cjs.
2021-09-21 09:36:15 +02:00
Lucas Lefèvre 600f1e35d4 [IMP] package.json: update jest and rollup
If we want to upgrade Typescript to version >4 (in this repo or another using
owl), we also need jest >25. Jest <26 do not support Typescript >4.

However, jest >26 has a few breaking changes that completely breaks owl in
tests.

This commit updates jest to the current latest version (27) and adapts the
code accordingly.

A few words on what changed
---------------------------

1. the default test environment is no longer `jsdom`. It is now manually
   configured to restore the previous env.

2. the jsdom version has been upgraded. This brings a few breaking changes,
   detailed later.

3. there's a bug in jsdom >16.4. Manually created `<t/>` (by qweb compilation)
   are recognized as "T" and not "t". Qweb thinks it's a component (since the
   first letter is capitalized), but it's not a component: boom, everything
   breaks.
   A fix has been proposed here jsdom/jsdom#3240. But it's not likely to land
   in jest in the short term (jest would need to update its dependency to the
   next major jsdom version). This commit works around the problem for now by
   creating `<t/>` slighty differently such that they are in an XML document
   from the start (and not HTML document).

4. the xml parser implementation changed to increase the strictness and
   correctness of XML parsing, according to specifications. A few tests needed
   to be adapted.
   https://github.com/jsdom/jsdom/commit/c96decf837ece54bdc550dfb7dca7e5d6c97bc2d

5. jest matcher `toHaveProperty` now check inherited properties. This breaks a
   few tests. Since the breaking assertions didn't bring a lot of value from a
   behavior point of view (it was more "white box" technical tests), they are
   removed in this commit
   https://github.com/facebook/jest/commit/1256f76a5a83034b51c7524142b60b099f69a7ab

6. jsdom now implements the behavior of links (`<a/>`). One test was `click`ing
   on such a link, with the right click. Jsdom now tries to navigate to the
   pointed URl...but crashes because navigation is not implemented :(
   It turns out the test is probably not a valid/realistic scenario. On every
   tested browser (chrome - chromium - brave - edge -firefox - safari), a
   right click with the mouse triggers a `contextmenu` event and no `click`
   event.
   https://github.com/jsdom/jsdom/commit/cc95abc576f596ff7f3eaf8245f376e1f21aa485
2021-09-08 11:47:50 +02:00
28 changed files with 830 additions and 93 deletions
+2 -2
View File
@@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
node-version: [12.x, 14.x]
node-version: [12.x, 14.x, 16.x]
steps:
- uses: actions/checkout@v2
@@ -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
+5 -1
View File
@@ -1,5 +1,9 @@
<h1 align="center">🦉 <a href="https://odoo.github.io/owl/">OWL Framework</a> 🦉</h1>
[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
[![npm version](https://badge.fury.io/js/@odoo%2Fowl.svg)](https://badge.fury.io/js/@odoo%2Fowl)
[![Downloads](https://img.shields.io/npm/dm/@odoo%2Fowl.svg)](https://www.npmjs.com/package/@odoo/owl)
_Class based components with hooks, reactive state and concurrent mode_
## Project Overview
@@ -120,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.4](https://github.com/odoo/owl/releases/tag/v1.4.4)
- [owl-1.4.9](https://github.com/odoo/owl/releases/tag/v1.4.9)
## License
+3 -1
View File
@@ -70,7 +70,9 @@ just put the following code:
Note that we put everything inside an immediately executed function to avoid leaking
anything to the global scope.
Finally, `owl.js` should be the last version downloaded from the Owl repository (you can use `owl.min.js` if you prefer).
Finally, `owl.js` should be the last version downloaded from the Owl repository (you can use `owl.min.js` if you prefer). Be aware that you should download the `owl.iife.js` or `owl.iife.min.js`, because these files
are built to run directly on the browser (other files such as `owl.cjs.js` are
built to be bundled by other tools).
Now, the project should be ready. Loading the `index.html` file into a browser
should show an empty page, with the title `Owl Todo App`, and it should log a
+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;
}
`;
}
```
+8 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "1.4.4",
"version": "1.4.9",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"browser": "dist/owl.iife.js",
@@ -37,28 +37,29 @@
},
"homepage": "https://github.com/odoo/owl#readme",
"devDependencies": {
"@types/jest": "^23.3.14",
"@types/jest": "^27.0.1",
"@types/node": "^14.11.8",
"chalk": "^3.0.0",
"cpx": "^1.5.0",
"current-git-branch": "^1.1.0",
"git-rev-sync": "^1.12.0",
"github-api": "^3.3.0",
"jest": "^23.6.0",
"jest-environment-jsdom": "^24.7.1",
"jest": "^27.1.0",
"jest-environment-jsdom": "^27.1.0",
"live-server": "^1.2.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.4",
"rollup": "^1.6.0",
"rollup": "^2.56.3",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.27.3",
"rollup-plugin-typescript2": "^0.30.0",
"sass": "^1.16.1",
"source-map-support": "^0.5.10",
"ts-jest": "^23.10.5",
"ts-jest": "^27.0.5",
"typescript": "^3.7.2",
"uglify-es": "^3.3.9"
},
"jest": {
"testEnvironment": "jsdom",
"roots": [
"<rootDir>/src",
"<rootDir>/tests"
+1 -1
View File
@@ -1,6 +1,6 @@
# 🦉 OWL Roadmap 🦉
- Current version: 1.4.4
- Current version: 1.4.9
- Status: stable
This roadmap is only an attempt at predicting Owl's future. Everything may
+2 -2
View File
@@ -400,6 +400,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
if (currentFiber && !currentFiber.isRendered && !currentFiber.isCompleted) {
return scheduler.addFiber(currentFiber.root);
}
// if we aren't mounted at this point, it implies that there is a
// currentFiber that is already rendered (isRendered is true), so we are
// about to be mounted
@@ -505,7 +506,6 @@ export class Component<Props extends {} = any, T extends Env = Env> {
const __owl__ = this.__owl__;
__owl__.status = STATUS.MOUNTED;
__owl__.currentFiber = null;
this.mounted();
if (__owl__.mountedCB) {
__owl__.mountedCB();
@@ -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";
}
}
}
}
+4 -6
View File
@@ -198,6 +198,7 @@ export class Fiber {
// build patchQueue
const patchQueue: Fiber[] = [];
const doWork: (Fiber) => Fiber | null = function (f) {
f.component.__owl__.currentFiber = null;
patchQueue.push(f);
return f.child;
};
@@ -242,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
@@ -255,10 +257,6 @@ export class Fiber {
component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm;
}
}
const compOwl = component.__owl__;
if (fiber === compOwl.currentFiber) {
compOwl.currentFiber = null;
}
}
// insert into the DOM (mount case)
+5 -3
View File
@@ -1,4 +1,5 @@
import { QWeb } from "./qweb/index";
import { TRANSLATABLE_ATTRS } from "./qweb/qweb";
/**
* This file creates and exports the OWL 'config' object, with keys:
@@ -9,9 +10,12 @@ import { QWeb } from "./qweb/index";
interface Config {
mode: string;
enableTransitions: boolean;
translatableAttributes: string[];
}
export const config = {} as Config;
export const config = {
translatableAttributes: TRANSLATABLE_ATTRS,
} as Config;
Object.defineProperty(config, "mode", {
get() {
@@ -24,8 +28,6 @@ Object.defineProperty(config, "mode", {
This is not suitable for production use.
See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`);
} else {
console.log(`Owl is now running in 'prod' mode.`);
}
},
});
+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;
}
+14 -3
View File
@@ -66,7 +66,7 @@ interface QWebConfig {
// Const/global stuff/helpers
//------------------------------------------------------------------------------
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
export const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
const lineBreakRE = /[\r\n]/;
const whitespaceRE = /\s+/g;
@@ -329,6 +329,9 @@ export class QWeb extends EventBus {
* template, with the name given by the t-name attribute.
*/
addTemplates(xmlstr: string | Document) {
if (!xmlstr) {
return;
}
const doc = typeof xmlstr === "string" ? parseXML(xmlstr) : xmlstr;
const templates = doc.getElementsByTagName("templates")[0];
if (!templates) {
@@ -557,7 +560,11 @@ export class QWeb extends EventBus {
}
if (node.tagName !== "t" && node.hasAttribute("t-call")) {
const tCallNode = document.createElement("t");
const tCallNode = document.implementation.createDocument(
"http://www.w3.org/1999/xhtml",
"t",
null
).documentElement;
tCallNode.setAttribute("t-call", node.getAttribute("t-call")!);
node.removeAttribute("t-call");
node.prepend(tCallNode);
@@ -593,7 +600,11 @@ export class QWeb extends EventBus {
throw new Error(`Unknown QWeb directive: '${attrName}'`);
}
if (node.tagName !== "t" && (attrName === "t-esc" || attrName === "t-raw")) {
const tNode = document.createElement("t");
const tNode = document.implementation.createDocument(
"http://www.w3.org/1999/xhtml",
"t",
null
).documentElement;
tNode.setAttribute(attrName, node.getAttribute(attrName)!);
for (let child of Array.from(node.childNodes)) {
tNode.appendChild(child);
@@ -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);
+56
View File
@@ -1388,6 +1388,62 @@ describe("async rendering", () => {
expect(fixture.innerHTML).toBe("<span>4</span>");
});
test("calling render in destroy", async () => {
let a: any = null;
let c: any = null;
class C extends Component {
static template = xml`
<div>
<t t-esc="props.fromA"/>
</div>`;
}
let flag = false;
class B extends Component {
static template = xml`<C fromA="props.fromA"/>`;
static components = { C };
setup() {
c = this;
}
mounted() {
if (flag) {
this.render();
} else {
flag = true;
}
}
willUnmount() {
c.render();
}
}
class A extends Component {
static template = xml`<B t-key="key" fromA="state"/>`;
static components = { B };
state = "a";
key = 1;
setup() {
a = this;
}
}
const parent = new A();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div>a</div>");
a.state = "A";
a.key = 2;
await a.render();
// this nextTick is critical, otherwise jest may silently swallow errors
await nextTick();
expect(fixture.innerHTML).toBe("<div>A</div>");
});
test("change state and call manually render: no unnecessary rendering", async () => {
class Widget extends Component {
static template = xml`<div><t t-esc="state.val"/></div>`;
+3 -1
View File
@@ -265,7 +265,9 @@ describe("class and style attributes with t-component", () => {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe("Cannot read property 'crash' of undefined");
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", () => {
+15 -5
View File
@@ -95,7 +95,9 @@ describe("component error handling (catchError)", () => {
try {
await super.render();
} catch (e) {
expect(e.message).toBe("Cannot read property 'this' of undefined");
expect(e.message).toMatch(
/Cannot read properties of undefined \(reading 'this'\)|Cannot read property 'this' of undefined/
);
}
}
}
@@ -359,7 +361,9 @@ describe("component error handling (catchError)", () => {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe("Cannot read property 'crash' of undefined");
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);
console.error = consoleError;
@@ -469,7 +473,9 @@ describe("component error handling (catchError)", () => {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe("Cannot read property 'crash' of undefined");
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);
console.error = consoleError;
@@ -495,7 +501,9 @@ describe("component error handling (catchError)", () => {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe("Cannot read property 'crash' of undefined");
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);
console.error = consoleError;
@@ -518,7 +526,9 @@ describe("component error handling (catchError)", () => {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe("Cannot read property 'y' of undefined");
const regexp =
/Cannot read properties of undefined \(reading 'y'\)|Cannot read property 'y' of undefined/g;
expect(error.message).toMatch(regexp);
});
test("simple catchError", async () => {
-6
View File
@@ -75,8 +75,6 @@ describe("hooks", () => {
}
const component = new MyComponent();
await component.mount(fixture);
expect(component).not.toHaveProperty("mounted");
expect(component).not.toHaveProperty("willUnmount");
expect(fixture.innerHTML).toBe("<div>hey</div>");
expect(steps).toEqual(["mounted"]);
component.unmount();
@@ -383,8 +381,6 @@ describe("hooks", () => {
const component = new MyComponent();
await component.mount(fixture);
expect(component).not.toHaveProperty("patched");
expect(component).not.toHaveProperty("willPatch");
expect(steps).toEqual([]);
expect(fixture.innerHTML).toBe("<div>hey</div>");
@@ -630,8 +626,6 @@ describe("hooks", () => {
const app = new App();
await app.mount(fixture);
expect(app).not.toHaveProperty("willStart");
expect(app).not.toHaveProperty("willUpdateProps");
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
// NOTE: 'on2ndStart' appears first in the list even though
+3 -1
View File
@@ -546,7 +546,9 @@ describe("Portal: Basic use and DOM placement", () => {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe("Cannot read property 'crash' of undefined");
const regexp =
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error.message).toMatch(regexp);
});
test("portal manual unmount", async () => {
+58 -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);
@@ -3943,6 +3965,20 @@ exports[`t-set value priority 1`] = `
}"
`;
exports[`translation support can add additional attributes to the list of translatable attributes 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let h = this.h;
let _1 = 'word';
let _2 = 'mot';
let c3 = [], p3 = {key:3,attrs:{tomato: _1,potato: _2}};
let vn3 = h('div', p3, c3);
c3.push({text: \`text\`});
return vn3;
}"
`;
exports[`translation support can translate node content 1`] = `
"function anonymous(context, extra
) {
@@ -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;
}"
`;
+15 -4
View File
@@ -1,4 +1,5 @@
import { QWeb } from "../../src/qweb/index";
import { config } from "../../src/index";
import { nextTick, normalize, renderToDOM, renderToString, trim } from "../helpers";
import { patch } from "../../src/vdom";
@@ -13,6 +14,7 @@ let qweb: QWeb;
beforeEach(() => {
QWeb.TEMPLATES = {};
QWeb.nextId = 1;
qweb = new QWeb();
});
@@ -1862,8 +1864,7 @@ describe("t-ref", () => {
describe("loading templates", () => {
test("can initialize qweb with a string", () => {
const templates = `
<?xml version="1.0" encoding="UTF-8"?>
const templates = `<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<div t-name="hey">jupiler</div>
</templates>`;
@@ -1872,8 +1873,7 @@ describe("loading templates", () => {
});
test("can load a few templates from a xml string", () => {
const data = `
<?xml version="1.0" encoding="UTF-8"?>
const data = `<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="items"><li>ok</li><li>foo</li></t>
@@ -2213,6 +2213,17 @@ describe("translation support", () => {
);
});
test("can add additional attributes to the list of translatable attributes", () => {
const translations = {
word: "mot",
};
const translateFn = (expr) => translations[expr] || expr;
const qweb = new QWeb({ translateFn });
config.translatableAttributes.push("potato");
qweb.addTemplate("test", `<div tomato="word" potato="word">text</div>`);
expect(renderToString(qweb, "test")).toBe('<div tomato="word" potato="mot">text</div>');
});
test("translation is done on the trimmed text, with extra spaces readded after", () => {
const translations = {
word: "mot",
+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);
});
});
+1 -1
View File
@@ -80,7 +80,7 @@ describe("Link component", () => {
await app.mount(fixture);
expect(window.location.pathname).toBe("/users");
var evt = new MouseEvent("click", {
var evt = new MouseEvent("contextmenu", {
button: 1,
});
+2 -2
View File
@@ -13,8 +13,8 @@
// "compileOnSave": false,                   // Signals to the IDE to generate all files for a given tsconfig.json upon saving.
"compilerOptions": {
                                                            // Main options
"target": "esnext",                                         // Specify ECMAScript target version: 'es3' (default), 'es5', 'es2015', 'es2016', 'es2017','es2018' or 'esnext'.
"module": "esnext",                                         // Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.
"target": "es2017",                                         // Specify ECMAScript target version: 'es3' (default), 'es5', 'es2015', 'es2016', 'es2017','es2018' or 'esnext'.
"module": "es6",                                         // Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.
// "lib": ["esnext", "dom"],                 // Specify library files to be included in the compilation.
// "allowJs": false,                 // Allow javascript files to be compiled.
// "checkJs": false,                 // Report errors in .js files.