mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a38c53419a | |||
| 0d0f64c0ed | |||
| 940ac64340 | |||
| c06049076a | |||
| bc04f727ac | |||
| 0bc9573a8a | |||
| 73f94fba3f | |||
| 7a16449724 | |||
| 718c765e3b | |||
| 150d620b8e | |||
| 307b936d01 | |||
| 6950f8e628 | |||
| 4e3b7c74da | |||
| 98bb2842d8 | |||
| 6e185f987b | |||
| c0f495661d | |||
| 0f7a8289a6 | |||
| 1c3b04f6a8 | |||
| 64db7777dd | |||
| 37313c47a3 | |||
| 3f563de9c0 | |||
| 97564a7612 | |||
| f1abf7f2ea | |||
| 8464a1b04e | |||
| c0a62dfd05 | |||
| 600f1e35d4 | |||
| b3181f119d | |||
| 1e1e05350a | |||
| b242230e20 | |||
| 9fe2da704e | |||
| 21b1661d39 | |||
| ec05b1f5e3 | |||
| 8083678f03 | |||
| 61c2ec5d83 | |||
| e579a993fd | |||
| ea3c6f7bf0 | |||
| 9cfafc30b5 | |||
| ad42c583c6 |
@@ -14,7 +14,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [10.x, 12.x, 14.x]
|
node-version: [12.x, 14.x, 16.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -24,4 +24,4 @@ jobs:
|
|||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm run test
|
- run: npm run test
|
||||||
- run: npm run prettier
|
- run: npm run check-formatting
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<h1 align="center">🦉 <a href="https://odoo.github.io/owl/">OWL Framework</a> 🦉</h1>
|
<h1 align="center">🦉 <a href="https://odoo.github.io/owl/">OWL Framework</a> 🦉</h1>
|
||||||
|
|
||||||
|
[](https://www.gnu.org/licenses/lgpl-3.0)
|
||||||
|
[](https://badge.fury.io/js/@odoo%2Fowl)
|
||||||
|
[](https://www.npmjs.com/package/@odoo/owl)
|
||||||
|
|
||||||
_Class based components with hooks, reactive state and concurrent mode_
|
_Class based components with hooks, reactive state and concurrent mode_
|
||||||
|
|
||||||
## Project Overview
|
## 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:
|
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
||||||
|
|
||||||
- [owl-1.4.0](https://github.com/odoo/owl/releases/tag/v1.4.0)
|
- [owl-1.4.11](https://github.com/odoo/owl/releases/tag/v1.4.11)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,9 @@ just put the following code:
|
|||||||
Note that we put everything inside an immediately executed function to avoid leaking
|
Note that we put everything inside an immediately executed function to avoid leaking
|
||||||
anything to the global scope.
|
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
|
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
|
should show an empty page, with the title `Owl Todo App`, and it should log a
|
||||||
|
|||||||
+9
-8
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "1.4.0",
|
"version": "1.4.11",
|
||||||
"description": "Odoo Web Library (OWL)",
|
"description": "Odoo Web Library (OWL)",
|
||||||
"main": "dist/owl.cjs.js",
|
"main": "dist/owl.cjs.js",
|
||||||
"browser": "dist/owl.iife.js",
|
"browser": "dist/owl.iife.js",
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.15.3"
|
"node": ">=12.18.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:bundle": "rollup -c",
|
"build:bundle": "rollup -c",
|
||||||
@@ -37,28 +37,29 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/odoo/owl#readme",
|
"homepage": "https://github.com/odoo/owl#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^23.3.14",
|
"@types/jest": "^27.0.1",
|
||||||
"@types/node": "^14.11.8",
|
"@types/node": "^14.11.8",
|
||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
"cpx": "^1.5.0",
|
"cpx": "^1.5.0",
|
||||||
"current-git-branch": "^1.1.0",
|
"current-git-branch": "^1.1.0",
|
||||||
"git-rev-sync": "^1.12.0",
|
"git-rev-sync": "^1.12.0",
|
||||||
"github-api": "^3.3.0",
|
"github-api": "^3.3.0",
|
||||||
"jest": "^23.6.0",
|
"jest": "^27.1.0",
|
||||||
"jest-environment-jsdom": "^24.7.1",
|
"jest-environment-jsdom": "^27.1.0",
|
||||||
"live-server": "^1.2.1",
|
"live-server": "^1.2.1",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^2.0.4",
|
"prettier": "^2.0.4",
|
||||||
"rollup": "^1.6.0",
|
"rollup": "^2.56.3",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"rollup-plugin-typescript2": "^0.27.3",
|
"rollup-plugin-typescript2": "^0.30.0",
|
||||||
"sass": "^1.16.1",
|
"sass": "^1.16.1",
|
||||||
"source-map-support": "^0.5.10",
|
"source-map-support": "^0.5.10",
|
||||||
"ts-jest": "^23.10.5",
|
"ts-jest": "^27.0.5",
|
||||||
"typescript": "^3.7.2",
|
"typescript": "^3.7.2",
|
||||||
"uglify-es": "^3.3.9"
|
"uglify-es": "^3.3.9"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
|
"testEnvironment": "jsdom",
|
||||||
"roots": [
|
"roots": [
|
||||||
"<rootDir>/src",
|
"<rootDir>/src",
|
||||||
"<rootDir>/tests"
|
"<rootDir>/tests"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# 🦉 OWL Roadmap 🦉
|
# 🦉 OWL Roadmap 🦉
|
||||||
|
|
||||||
- Current version: 1.4.0
|
- Current version: 1.4.11
|
||||||
- Status: stable
|
- Status: stable
|
||||||
|
|
||||||
This roadmap is only an attempt at predicting Owl's future. Everything may
|
This roadmap is only an attempt at predicting Owl's future. Everything may
|
||||||
|
|||||||
@@ -400,6 +400,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
|||||||
if (currentFiber && !currentFiber.isRendered && !currentFiber.isCompleted) {
|
if (currentFiber && !currentFiber.isRendered && !currentFiber.isCompleted) {
|
||||||
return scheduler.addFiber(currentFiber.root);
|
return scheduler.addFiber(currentFiber.root);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we aren't mounted at this point, it implies that there is a
|
// 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
|
// currentFiber that is already rendered (isRendered is true), so we are
|
||||||
// about to be mounted
|
// about to be mounted
|
||||||
@@ -505,7 +506,6 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
|||||||
const __owl__ = this.__owl__;
|
const __owl__ = this.__owl__;
|
||||||
|
|
||||||
__owl__.status = STATUS.MOUNTED;
|
__owl__.status = STATUS.MOUNTED;
|
||||||
__owl__.currentFiber = null;
|
|
||||||
this.mounted();
|
this.mounted();
|
||||||
if (__owl__.mountedCB) {
|
if (__owl__.mountedCB) {
|
||||||
__owl__.mountedCB();
|
__owl__.mountedCB();
|
||||||
@@ -767,7 +767,7 @@ export async function mount<T extends Type<Component>>(
|
|||||||
const { env, props, target } = params;
|
const { env, props, target } = params;
|
||||||
let origEnv = C.hasOwnProperty("env") ? (C as any).env : null;
|
let origEnv = C.hasOwnProperty("env") ? (C as any).env : null;
|
||||||
if (env) {
|
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);
|
const component: Component = new C(null, props);
|
||||||
if (origEnv) {
|
if (origEnv) {
|
||||||
|
|||||||
@@ -234,10 +234,14 @@ QWeb.addDirective({
|
|||||||
} else if (!name.startsWith("t-")) {
|
} else if (!name.startsWith("t-")) {
|
||||||
if (name !== "class" && name !== "style") {
|
if (name !== "class" && name !== "style") {
|
||||||
// this is a prop!
|
// this is a prop!
|
||||||
|
if (value.includes("=>")) {
|
||||||
|
props[name] = ctx.captureExpression(value);
|
||||||
|
} else {
|
||||||
props[name] = ctx.formatExpression(value) || "undefined";
|
props[name] = ctx.formatExpression(value) || "undefined";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// computing the props string representing the props object
|
// computing the props string representing the props object
|
||||||
let propStr = Object.keys(props)
|
let propStr = Object.keys(props)
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ export class Fiber {
|
|||||||
// build patchQueue
|
// build patchQueue
|
||||||
const patchQueue: Fiber[] = [];
|
const patchQueue: Fiber[] = [];
|
||||||
const doWork: (Fiber) => Fiber | null = function (f) {
|
const doWork: (Fiber) => Fiber | null = function (f) {
|
||||||
|
f.component.__owl__.currentFiber = null;
|
||||||
patchQueue.push(f);
|
patchQueue.push(f);
|
||||||
return f.child;
|
return f.child;
|
||||||
};
|
};
|
||||||
@@ -242,8 +243,9 @@ export class Fiber {
|
|||||||
}
|
}
|
||||||
component.__patch(target!, fiber.vnode!);
|
component.__patch(target!, fiber.vnode!);
|
||||||
} else {
|
} else {
|
||||||
if (fiber.shouldPatch) {
|
const vnode = component.__owl__.vnode;
|
||||||
component.__patch(component.__owl__.vnode!, fiber.vnode!);
|
if (fiber.shouldPatch && vnode) {
|
||||||
|
component.__patch(vnode, fiber.vnode!);
|
||||||
// When updating a Component's props (in directive),
|
// When updating a Component's props (in directive),
|
||||||
// the component has a pvnode AND should be patched.
|
// the component has a pvnode AND should be patched.
|
||||||
// However, its pvnode.elm may have changed if it is a High Order Component
|
// 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;
|
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)
|
// insert into the DOM (mount case)
|
||||||
|
|||||||
@@ -40,15 +40,16 @@ QWeb.utils.validateProps = function (Widget, props: Object) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let isValid;
|
let whyInvalid;
|
||||||
try {
|
try {
|
||||||
isValid = isValidProp(props[propName], propsDef[propName]);
|
whyInvalid = whyInvalidProp(props[propName], propsDef[propName]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
e.message = `Invalid prop '${propName}' in component ${Widget.name} (${e.message})`;
|
e.message = `Invalid prop '${propName}' in component ${Widget.name} (${e.message})`;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
if (!isValid) {
|
if (whyInvalid !== null) {
|
||||||
throw new Error(`Invalid Prop '${propName}' in component '${Widget.name}'`);
|
whyInvalid = whyInvalid.replace(/\${propName}/g, propName);
|
||||||
|
throw new Error(`Invalid Prop '${propName}' in component '${Widget.name}': ${whyInvalid}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let propName in props) {
|
for (let propName in props) {
|
||||||
@@ -60,11 +61,11 @@ QWeb.utils.validateProps = function (Widget, props: Object) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if an invidual prop value matches its (static) prop definition
|
* Check why an invidual prop value doesn't match its (static) prop definition
|
||||||
*/
|
*/
|
||||||
function isValidProp(prop, propDef): boolean {
|
function whyInvalidProp(prop, propDef): string | null {
|
||||||
if (propDef === true) {
|
if (propDef === true) {
|
||||||
return true;
|
return null;
|
||||||
}
|
}
|
||||||
if (typeof propDef === "function") {
|
if (typeof propDef === "function") {
|
||||||
// Check if a value is constructed by some Constructor. Note that there is a
|
// Check if a value is constructed by some Constructor. Note that there is a
|
||||||
@@ -73,43 +74,66 @@ function isValidProp(prop, propDef): boolean {
|
|||||||
// So, even though 1 is not an instance of Number, we want to consider that
|
// So, even though 1 is not an instance of Number, we want to consider that
|
||||||
// it is valid.
|
// it is valid.
|
||||||
if (typeof prop === "object") {
|
if (typeof prop === "object") {
|
||||||
return prop instanceof propDef;
|
if (prop instanceof propDef) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return typeof prop === propDef.name.toLowerCase();
|
return `\${propName} is not an instance of ${propDef.name}`;
|
||||||
|
}
|
||||||
|
if (typeof prop === propDef.name.toLowerCase()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return `type of \${propName} is not ${propDef.name}`;
|
||||||
} else if (propDef instanceof Array) {
|
} else if (propDef instanceof Array) {
|
||||||
// If this code is executed, this means that we want to check if a prop
|
// If this code is executed, this means that we want to check if a prop
|
||||||
// matches at least one of its descriptor.
|
// matches at least one of its descriptor.
|
||||||
let result = false;
|
let reasons: string[] = [];
|
||||||
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
||||||
result = result || isValidProp(prop, propDef[i]);
|
const why = whyInvalidProp(prop, propDef[i]);
|
||||||
|
if (why === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
reasons.push(why);
|
||||||
|
}
|
||||||
|
if (reasons.length > 1) {
|
||||||
|
return reasons.slice(0, -1).join(", ") + " and " + reasons[reasons.length - 1];
|
||||||
|
} else {
|
||||||
|
return reasons[0];
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
// propsDef is an object
|
// propsDef is an object
|
||||||
if (propDef.optional && prop === undefined) {
|
if (propDef.optional && prop === undefined) {
|
||||||
return true;
|
return null;
|
||||||
}
|
}
|
||||||
let result = propDef.type ? isValidProp(prop, propDef.type) : true;
|
if (propDef.type) {
|
||||||
if (propDef.validate) {
|
const why = whyInvalidProp(prop, propDef.type);
|
||||||
result = result && propDef.validate(prop);
|
if (why !== null) {
|
||||||
|
return why;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (propDef.validate && !propDef.validate(prop)) {
|
||||||
|
return "${propName} could not be validated by `validate` function";
|
||||||
}
|
}
|
||||||
if (propDef.type === Array && propDef.element) {
|
if (propDef.type === Array && propDef.element) {
|
||||||
for (let i = 0, iLen = prop.length; i < iLen; i++) {
|
for (let i = 0, iLen = prop.length; i < iLen; i++) {
|
||||||
result = result && isValidProp(prop[i], propDef.element);
|
const why = whyInvalidProp(prop[i], propDef.element);
|
||||||
|
if (why !== null) {
|
||||||
|
return why.replace(/\${propName}/g, `\${propName}[${i}]`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (propDef.type === Object && propDef.shape) {
|
if (propDef.type === Object && propDef.shape) {
|
||||||
const shape = propDef.shape;
|
const shape = propDef.shape;
|
||||||
for (let key in shape) {
|
for (let key in shape) {
|
||||||
result = result && isValidProp(prop[key], shape[key]);
|
const why = whyInvalidProp(prop[key], shape[key]);
|
||||||
|
if (why !== null) {
|
||||||
|
return why.replace(/\${propName}/g, `\${propName}['${key}']`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (result) {
|
|
||||||
for (let propName in prop) {
|
for (let propName in prop) {
|
||||||
if (!(propName in shape)) {
|
if (!(propName in shape)) {
|
||||||
throw new Error(`unknown prop '${propName}'`);
|
return `unknown prop \${propName}['${propName}']`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return null;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -1,4 +1,5 @@
|
|||||||
import { QWeb } from "./qweb/index";
|
import { QWeb } from "./qweb/index";
|
||||||
|
import { TRANSLATABLE_ATTRS } from "./qweb/qweb";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file creates and exports the OWL 'config' object, with keys:
|
* This file creates and exports the OWL 'config' object, with keys:
|
||||||
@@ -9,9 +10,12 @@ import { QWeb } from "./qweb/index";
|
|||||||
interface Config {
|
interface Config {
|
||||||
mode: string;
|
mode: string;
|
||||||
enableTransitions: boolean;
|
enableTransitions: boolean;
|
||||||
|
translatableAttributes: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {} as Config;
|
export const config = {
|
||||||
|
translatableAttributes: TRANSLATABLE_ATTRS,
|
||||||
|
} as Config;
|
||||||
|
|
||||||
Object.defineProperty(config, "mode", {
|
Object.defineProperty(config, "mode", {
|
||||||
get() {
|
get() {
|
||||||
@@ -24,8 +28,6 @@ Object.defineProperty(config, "mode", {
|
|||||||
|
|
||||||
This is not suitable for production use.
|
This is not suitable for production use.
|
||||||
See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`);
|
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.`);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+10
-2
@@ -104,10 +104,18 @@ export function useRef<C extends Component = Component>(name: string): Ref<C> {
|
|||||||
return {
|
return {
|
||||||
get el(): HTMLElement | null {
|
get el(): HTMLElement | null {
|
||||||
const val = __owl__.refs && __owl__.refs[name];
|
const val = __owl__.refs && __owl__.refs[name];
|
||||||
|
if (val instanceof Component) {
|
||||||
|
return val.el;
|
||||||
|
}
|
||||||
if (val instanceof HTMLElement) {
|
if (val instanceof HTMLElement) {
|
||||||
return val;
|
return val;
|
||||||
} else if (val instanceof Component) {
|
}
|
||||||
return val.el;
|
// Extra check in case the app was created outside an iframe but mounted into one
|
||||||
|
// on Firefox 109+, the prototype of the element changes to use the iframe window's HTMLElement
|
||||||
|
// see https://bugzilla.mozilla.org/show_bug.cgi?id=1813499
|
||||||
|
const ownerWindow = (val as any)?.ownerDocument?.defaultView;
|
||||||
|
if (ownerWindow && (val as any) instanceof ownerWindow.HTMLElement) {
|
||||||
|
return val;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -326,7 +326,8 @@ QWeb.addDirective({
|
|||||||
ctx.addLine(`if (!_${arrayID}) { throw new Error('QWeb error: Invalid loop expression')}`);
|
ctx.addLine(`if (!_${arrayID}) { throw new Error('QWeb error: Invalid loop expression')}`);
|
||||||
let keysID = ctx.generateID();
|
let keysID = ctx.generateID();
|
||||||
let valuesID = 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.addIf(`!(_${arrayID} instanceof Array)`);
|
||||||
ctx.addLine(`_${keysID} = Object.keys(_${arrayID});`);
|
ctx.addLine(`_${keysID} = Object.keys(_${arrayID});`);
|
||||||
ctx.addLine(`_${valuesID} = Object.values(_${arrayID});`);
|
ctx.addLine(`_${valuesID} = Object.values(_${arrayID});`);
|
||||||
|
|||||||
@@ -162,8 +162,26 @@ export class CompilationContext {
|
|||||||
const tokens = compileExprToArray(expr, this.variables);
|
const tokens = compileExprToArray(expr, this.variables);
|
||||||
const done = new Set();
|
const done = new Set();
|
||||||
return tokens
|
return tokens
|
||||||
.map((tok) => {
|
.map((tok, i) => {
|
||||||
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 &&
|
||||||
|
// HACK: for backwards compatibility, we don't capture bare methods
|
||||||
|
// this allows them to be called with the rendering context/scope
|
||||||
|
// as their this value.
|
||||||
|
(!tokens[i + 1] || tokens[i + 1].type !== "LEFT_PAREN")
|
||||||
|
) {
|
||||||
if (!done.has(tok.varName)) {
|
if (!done.has(tok.varName)) {
|
||||||
done.add(tok.varName);
|
done.add(tok.varName);
|
||||||
this.addLine(`const ${tok.varName}_${argId} = ${tok.value};`);
|
this.addLine(`const ${tok.varName}_${argId} = ${tok.value};`);
|
||||||
|
|||||||
@@ -25,7 +25,8 @@
|
|||||||
// Misc types, constants and helpers
|
// 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(
|
||||||
","
|
","
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -69,6 +70,7 @@ interface Token {
|
|||||||
size?: number;
|
size?: number;
|
||||||
varName?: string;
|
varName?: string;
|
||||||
replace?: Function;
|
replace?: Function;
|
||||||
|
isLocal?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATIC_TOKEN_MAP: { [key: string]: TKind } = Object.assign(Object.create(null), {
|
const STATIC_TOKEN_MAP: { [key: string]: TKind } = Object.assign(Object.create(null), {
|
||||||
@@ -253,20 +255,38 @@ const isRightSeparator = (token) =>
|
|||||||
* the list of variables so it does not get replaced by a lookup in the context
|
* 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[] {
|
export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar }): Token[] {
|
||||||
|
const localVars = new Set<string>();
|
||||||
scope = Object.create(scope);
|
scope = Object.create(scope);
|
||||||
const tokens = tokenize(expr);
|
const tokens = tokenize(expr);
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
let stack = []; // to track last opening [ or {
|
||||||
|
|
||||||
while (i < tokens.length) {
|
while (i < tokens.length) {
|
||||||
let token = tokens[i];
|
let token = tokens[i];
|
||||||
let prevToken = tokens[i - 1];
|
let prevToken = tokens[i - 1];
|
||||||
let nextToken = tokens[i + 1];
|
let nextToken = tokens[i + 1];
|
||||||
|
let groupType = stack[stack.length - 1];
|
||||||
|
|
||||||
|
switch (token.type) {
|
||||||
|
case "LEFT_BRACE":
|
||||||
|
case "LEFT_BRACKET":
|
||||||
|
stack.push(token.type);
|
||||||
|
break;
|
||||||
|
case "RIGHT_BRACE":
|
||||||
|
case "RIGHT_BRACKET":
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
let isVar = token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value);
|
let isVar = token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value);
|
||||||
if (token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value)) {
|
if (token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value)) {
|
||||||
if (prevToken) {
|
if (prevToken) {
|
||||||
// normalize missing tokens: {a} should be equivalent to {a:a}
|
// normalize missing tokens: {a} should be equivalent to {a:a}
|
||||||
if (isLeftSeparator(prevToken) && isRightSeparator(nextToken)) {
|
if (
|
||||||
|
groupType === "LEFT_BRACE" &&
|
||||||
|
isLeftSeparator(prevToken) &&
|
||||||
|
isRightSeparator(nextToken)
|
||||||
|
) {
|
||||||
tokens.splice(i + 1, 0, { type: "COLON", value: ":" }, { ...token });
|
tokens.splice(i + 1, 0, { type: "COLON", value: ":" }, { ...token });
|
||||||
nextToken = tokens[i + 1];
|
nextToken = tokens[i + 1];
|
||||||
}
|
}
|
||||||
@@ -290,11 +310,13 @@ export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar
|
|||||||
if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) {
|
if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) {
|
||||||
tokens[j].value = tokens[j].originalValue!;
|
tokens[j].value = tokens[j].originalValue!;
|
||||||
scope[tokens[j].value] = { id: tokens[j].value, expr: tokens[j].value };
|
scope[tokens[j].value] = { id: tokens[j].value, expr: tokens[j].value };
|
||||||
|
localVars.add(tokens[j].value);
|
||||||
}
|
}
|
||||||
j--;
|
j--;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope[token.value] = { id: token.value, expr: token.value };
|
scope[token.value] = { id: token.value, expr: token.value };
|
||||||
|
localVars.add(token.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,6 +331,13 @@ export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar
|
|||||||
}
|
}
|
||||||
i++;
|
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;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+37
-20
@@ -1,7 +1,7 @@
|
|||||||
import { EventBus } from "../core/event_bus";
|
import { EventBus } from "../core/event_bus";
|
||||||
import { h, patch, VNode } from "../vdom/index";
|
import { h, patch, VNode } from "../vdom/index";
|
||||||
import { CompilationContext } from "./compilation_context";
|
import { CompilationContext } from "./compilation_context";
|
||||||
import { shallowEqual, escape } from "../utils";
|
import { shallowEqual } from "../utils";
|
||||||
import { addNS } from "../vdom/vdom";
|
import { addNS } from "../vdom/vdom";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,7 +66,7 @@ interface QWebConfig {
|
|||||||
// Const/global stuff/helpers
|
// Const/global stuff/helpers
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
|
export const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
|
||||||
|
|
||||||
const lineBreakRE = /[\r\n]/;
|
const lineBreakRE = /[\r\n]/;
|
||||||
const whitespaceRE = /\s+/g;
|
const whitespaceRE = /\s+/g;
|
||||||
@@ -127,20 +127,35 @@ const UTILS: Utils = {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// this is already an object, but we may need to split keys:
|
// this is already an object, but we may need to split keys:
|
||||||
// {'a': true, 'b c': true} should become {a: true, b: true, c: true}
|
// {'a b': true, 'a c': false} should become {a: true, b: true, c: false}
|
||||||
for (let key in expr) {
|
for (let key in expr) {
|
||||||
const value = expr[key];
|
const value = expr[key];
|
||||||
const words = key.split(/\s+/);
|
const words = key.split(/\s+/);
|
||||||
for (let word of words) {
|
for (let word of words) {
|
||||||
result[word] = value;
|
result[word] = result[word] || value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* This method combines the current context with the variables defined in a
|
||||||
|
* scope for use in a slot.
|
||||||
|
*
|
||||||
|
* The implementation is kind of tricky because we want to preserve the
|
||||||
|
* prototype chain structure of the cloned result. So we need to traverse the
|
||||||
|
* prototype chain, cloning each level respectively.
|
||||||
|
*/
|
||||||
combine(context, scope) {
|
combine(context, scope) {
|
||||||
const clone = Object.create(context);
|
let clone = context;
|
||||||
for (let k in scope) {
|
const scopeStack = [];
|
||||||
clone[k] = scope[k];
|
while (!isComponent(scope)) {
|
||||||
|
scopeStack.push(scope);
|
||||||
|
scope = scope.__proto__;
|
||||||
|
}
|
||||||
|
while (scopeStack.length) {
|
||||||
|
let scope = scopeStack.pop();
|
||||||
|
clone = Object.create(clone);
|
||||||
|
Object.assign(clone, scope);
|
||||||
}
|
}
|
||||||
return clone;
|
return clone;
|
||||||
},
|
},
|
||||||
@@ -314,6 +329,9 @@ export class QWeb extends EventBus {
|
|||||||
* template, with the name given by the t-name attribute.
|
* template, with the name given by the t-name attribute.
|
||||||
*/
|
*/
|
||||||
addTemplates(xmlstr: string | Document) {
|
addTemplates(xmlstr: string | Document) {
|
||||||
|
if (!xmlstr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const doc = typeof xmlstr === "string" ? parseXML(xmlstr) : xmlstr;
|
const doc = typeof xmlstr === "string" ? parseXML(xmlstr) : xmlstr;
|
||||||
const templates = doc.getElementsByTagName("templates")[0];
|
const templates = doc.getElementsByTagName("templates")[0];
|
||||||
if (!templates) {
|
if (!templates) {
|
||||||
@@ -407,17 +425,8 @@ export class QWeb extends EventBus {
|
|||||||
return vnode.text!;
|
return vnode.text!;
|
||||||
}
|
}
|
||||||
const node = document.createElement(vnode.sel);
|
const node = document.createElement(vnode.sel);
|
||||||
const elem = patch(node, vnode).elm as HTMLElement;
|
const result = patch(node, vnode);
|
||||||
function escapeTextNodes(node) {
|
return (result.elm as HTMLElement).outerHTML;
|
||||||
if (node.nodeType === 3) {
|
|
||||||
node.textContent = escape(node.textContent);
|
|
||||||
}
|
|
||||||
for (let n of node.childNodes) {
|
|
||||||
escapeTextNodes(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
escapeTextNodes(elem);
|
|
||||||
return elem.outerHTML;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -542,7 +551,11 @@ export class QWeb extends EventBus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (node.tagName !== "t" && node.hasAttribute("t-call")) {
|
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")!);
|
tCallNode.setAttribute("t-call", node.getAttribute("t-call")!);
|
||||||
node.removeAttribute("t-call");
|
node.removeAttribute("t-call");
|
||||||
node.prepend(tCallNode);
|
node.prepend(tCallNode);
|
||||||
@@ -578,7 +591,11 @@ export class QWeb extends EventBus {
|
|||||||
throw new Error(`Unknown QWeb directive: '${attrName}'`);
|
throw new Error(`Unknown QWeb directive: '${attrName}'`);
|
||||||
}
|
}
|
||||||
if (node.tagName !== "t" && (attrName === "t-esc" || attrName === "t-raw")) {
|
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)!);
|
tNode.setAttribute(attrName, node.getAttribute(attrName)!);
|
||||||
for (let child of Array.from(node.childNodes)) {
|
for (let child of Array.from(node.childNodes)) {
|
||||||
tNode.appendChild(child);
|
tNode.appendChild(child);
|
||||||
|
|||||||
+2
-1
@@ -231,7 +231,8 @@ function updateClass(oldVnode: VNode, vnode: VNode): void {
|
|||||||
elm = vnode.elm as Element;
|
elm = vnode.elm as Element;
|
||||||
|
|
||||||
for (name in oldClass) {
|
for (name in oldClass) {
|
||||||
if (name && !klass[name]) {
|
if (name && !klass[name] && !Object.prototype.hasOwnProperty.call(klass, name)) {
|
||||||
|
// was `true` and now not provided
|
||||||
elm.classList.remove(name);
|
elm.classList.remove(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
|
|||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['state'].s;
|
let _2 = scope['state'].s;
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_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 key1 = i1;
|
||||||
let _6 = scope['section'].blips;
|
let _6 = scope['section'].blips;
|
||||||
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _7 = _8 = _6;
|
let _7 = _6;
|
||||||
|
let _8 = _6;
|
||||||
if (!(_6 instanceof Array)) {
|
if (!(_6 instanceof Array)) {
|
||||||
_7 = Object.keys(_6);
|
_7 = Object.keys(_6);
|
||||||
_8 = Object.values(_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 vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['state'].numbers;
|
let _2 = scope['state'].numbers;
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_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;
|
scope.iter = 0;
|
||||||
let _2 = scope['arr'];
|
let _2 = scope['arr'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_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 vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['state'].values;
|
let _2 = scope['state'].values;
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_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';
|
scope.bossa = 'nova';
|
||||||
let _2 = scope['state'].values;
|
let _2 = scope['state'].values;
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_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 vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['state'].values;
|
let _2 = scope['state'].values;
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_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;
|
scope.iter = 0;
|
||||||
let _2 = scope['state'].values;
|
let _2 = scope['state'].values;
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -1446,6 +1454,405 @@ 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 bare function calls in arrow function has rendering context 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);
|
||||||
|
scope.ctxVal = 2;
|
||||||
|
// Component 'Child'
|
||||||
|
let w3 = '__4__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__4__']] : false;
|
||||||
|
let props3 = {callback:value=>scope['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 bare function calls in arrow function has rendering context 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 t-set with a body expression can be used as textual prop 1`] = `
|
exports[`props evaluation t-set with a body expression can be used as textual prop 1`] = `
|
||||||
"function anonymous(context, extra
|
"function anonymous(context, extra
|
||||||
) {
|
) {
|
||||||
@@ -1560,7 +1967,8 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument
|
|||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['items'];
|
let _2 = scope['items'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -1838,7 +2246,8 @@ exports[`t-model directive in a t-foreach 1`] = `
|
|||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['state'];
|
let _2 = scope['state'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -1878,7 +2287,8 @@ exports[`t-model directive in a t-foreach, part 2 1`] = `
|
|||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['state'];
|
let _2 = scope['state'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_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);
|
c1.push(vn2);
|
||||||
let _3 = scope['state'].users;
|
let _3 = scope['state'].users;
|
||||||
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _4 = _5 = _3;
|
let _4 = _3;
|
||||||
|
let _5 = _3;
|
||||||
if (!(_3 instanceof Array)) {
|
if (!(_3 instanceof Array)) {
|
||||||
_4 = Object.keys(_3);
|
_4 = Object.keys(_3);
|
||||||
_5 = Object.values(_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);
|
c1.push(vn2);
|
||||||
let _3 = scope['state'].users;
|
let _3 = scope['state'].users;
|
||||||
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _4 = _5 = _3;
|
let _4 = _3;
|
||||||
|
let _5 = _3;
|
||||||
if (!(_3 instanceof Array)) {
|
if (!(_3 instanceof Array)) {
|
||||||
_4 = Object.keys(_3);
|
_4 = Object.keys(_3);
|
||||||
_5 = Object.values(_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 vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['tree'];
|
let _2 = scope['tree'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -672,7 +675,8 @@ exports[`t-slot directive slots in t-foreach in t-foreach 1`] = `
|
|||||||
c1.push(vn8);
|
c1.push(vn8);
|
||||||
let _9 = scope['node1'].nodes;
|
let _9 = scope['node1'].nodes;
|
||||||
if (!_9) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_9) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _10 = _11 = _9;
|
let _10 = _9;
|
||||||
|
let _11 = _9;
|
||||||
if (!(_9 instanceof Array)) {
|
if (!(_9 instanceof Array)) {
|
||||||
_10 = Object.keys(_9);
|
_10 = Object.keys(_9);
|
||||||
_11 = Object.values(_9);
|
_11 = Object.values(_9);
|
||||||
|
|||||||
@@ -1388,6 +1388,62 @@ describe("async rendering", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<span>4</span>");
|
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 () => {
|
test("change state and call manually render: no unnecessary rendering", async () => {
|
||||||
class Widget extends Component {
|
class Widget extends Component {
|
||||||
static template = xml`<div><t t-esc="state.val"/></div>`;
|
static template = xml`<div><t t-esc="state.val"/></div>`;
|
||||||
|
|||||||
@@ -265,7 +265,9 @@ describe("class and style attributes with t-component", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
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("");
|
expect(fixture.innerHTML).toBe("");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1881,6 +1881,219 @@ describe("props evaluation ", () => {
|
|||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div><span><p>4343</p><p>43</p></span></div>");
|
expect(fixture.innerHTML).toBe("<div><span><p>4343</p><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("bare function calls in arrow function has rendering context as 'this'", 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 Parent extends Component {
|
||||||
|
static components = { Child };
|
||||||
|
state = useState({ val: 42 });
|
||||||
|
setup() {
|
||||||
|
parent = this;
|
||||||
|
}
|
||||||
|
setValue(value) {
|
||||||
|
// 'this' is the rendering context, NOT the instance
|
||||||
|
expect(this).not.toBe(parent);
|
||||||
|
// the state in the rendering context should be the same as the instance's
|
||||||
|
expect(this.state).toBe(parent.state);
|
||||||
|
expect((this as any).ctxVal).toBe(2);
|
||||||
|
this.state.val = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env.qweb.addTemplate(
|
||||||
|
"Parent",
|
||||||
|
`<div>
|
||||||
|
<t t-set="ctxVal" t-value="2"/>
|
||||||
|
<Child callback="value => 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", () => {
|
describe("other directives with t-component", () => {
|
||||||
|
|||||||
@@ -95,7 +95,9 @@ describe("component error handling (catchError)", () => {
|
|||||||
try {
|
try {
|
||||||
await super.render();
|
await super.render();
|
||||||
} catch (e) {
|
} 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;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
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);
|
expect(console.error).toBeCalledTimes(0);
|
||||||
console.error = consoleError;
|
console.error = consoleError;
|
||||||
@@ -469,7 +473,9 @@ describe("component error handling (catchError)", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
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);
|
expect(console.error).toBeCalledTimes(0);
|
||||||
console.error = consoleError;
|
console.error = consoleError;
|
||||||
@@ -495,7 +501,9 @@ describe("component error handling (catchError)", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
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);
|
expect(console.error).toBeCalledTimes(0);
|
||||||
console.error = consoleError;
|
console.error = consoleError;
|
||||||
@@ -518,7 +526,9 @@ describe("component error handling (catchError)", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
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 () => {
|
test("simple catchError", async () => {
|
||||||
|
|||||||
@@ -138,7 +138,9 @@ describe("props validation", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe("Invalid Prop 'p' in component '_a'");
|
expect(error.message).toBe(
|
||||||
|
`Invalid Prop 'p' in component '_a': type of p is not ${test.type.name}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -195,7 +197,9 @@ describe("props validation", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe("Invalid Prop 'p' in component '_a'");
|
expect(error.message).toBe(
|
||||||
|
`Invalid Prop 'p' in component '_a': type of p is not ${test.type.name}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -240,7 +244,9 @@ describe("props validation", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe("Invalid Prop 'p' in component 'TestWidget'");
|
expect(error.message).toBe(
|
||||||
|
"Invalid Prop 'p' in component 'TestWidget': type of p is not String and type of p is not Boolean"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can validate an optional props", async () => {
|
test("can validate an optional props", async () => {
|
||||||
@@ -284,7 +290,9 @@ describe("props validation", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe("Invalid Prop 'p' in component 'TestWidget'");
|
expect(error.message).toBe(
|
||||||
|
"Invalid Prop 'p' in component 'TestWidget': type of p is not String"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can validate an array with given primitive type", async () => {
|
test("can validate an array with given primitive type", async () => {
|
||||||
@@ -389,7 +397,9 @@ describe("props validation", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe("Invalid Prop 'p' in component 'TestWidget'");
|
expect(error.message).toBe(
|
||||||
|
"Invalid Prop 'p' in component 'TestWidget': type of p[1] is not String and type of p[1] is not Boolean"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can validate an object with simple shape", async () => {
|
test("can validate an object with simple shape", async () => {
|
||||||
@@ -426,7 +436,9 @@ describe("props validation", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe("Invalid prop 'p' in component TestWidget (unknown prop 'extra')");
|
expect(error.message).toBe(
|
||||||
|
"Invalid Prop 'p' in component 'TestWidget': unknown prop p['extra']"
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
props = { p: { id: "1", url: "url" } };
|
props = { p: { id: "1", url: "url" } };
|
||||||
@@ -436,7 +448,9 @@ describe("props validation", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe("Invalid Prop 'p' in component 'TestWidget'");
|
expect(error.message).toBe(
|
||||||
|
"Invalid Prop 'p' in component 'TestWidget': type of p['id'] is not Number"
|
||||||
|
);
|
||||||
|
|
||||||
error = undefined;
|
error = undefined;
|
||||||
try {
|
try {
|
||||||
@@ -447,7 +461,9 @@ describe("props validation", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe("Invalid Prop 'p' in component 'TestWidget'");
|
expect(error.message).toBe(
|
||||||
|
"Invalid Prop 'p' in component 'TestWidget': type of p['url'] is not String"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can validate recursively complicated prop def", async () => {
|
test("can validate recursively complicated prop def", async () => {
|
||||||
@@ -499,7 +515,9 @@ describe("props validation", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe("Invalid Prop 'p' in component 'TestWidget'");
|
expect(error.message).toBe(
|
||||||
|
"Invalid Prop 'p' in component 'TestWidget': p['url'] is not an instance of Boolean and type of p['url'][1] is not Number"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can validate optional attributes in nested sub props", () => {
|
test("can validate optional attributes in nested sub props", () => {
|
||||||
@@ -531,7 +549,7 @@ describe("props validation", () => {
|
|||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe(
|
expect(error.message).toBe(
|
||||||
"Invalid prop 'myprop' in component TestComponent (unknown prop 'a')"
|
"Invalid Prop 'myprop' in component 'TestComponent': unknown prop myprop[0]['a']"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -557,7 +575,9 @@ describe("props validation", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe("Invalid Prop 'size' in component 'TestComponent'");
|
expect(error.message).toBe(
|
||||||
|
"Invalid Prop 'size' in component 'TestComponent': size could not be validated by `validate` function"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can validate with a custom validator, and a type", () => {
|
test("can validate with a custom validator, and a type", () => {
|
||||||
@@ -585,7 +605,9 @@ describe("props validation", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe("Invalid Prop 'n' in component 'TestComponent'");
|
expect(error.message).toBe(
|
||||||
|
"Invalid Prop 'n' in component 'TestComponent': type of n is not Number"
|
||||||
|
);
|
||||||
expect(validator).toBeCalledTimes(1);
|
expect(validator).toBeCalledTimes(1);
|
||||||
|
|
||||||
error = null;
|
error = null;
|
||||||
@@ -595,7 +617,9 @@ describe("props validation", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe("Invalid Prop 'n' in component 'TestComponent'");
|
expect(error.message).toBe(
|
||||||
|
"Invalid Prop 'n' in component 'TestComponent': n could not be validated by `validate` function"
|
||||||
|
);
|
||||||
expect(validator).toBeCalledTimes(2);
|
expect(validator).toBeCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1257,4 +1257,117 @@ describe("t-slot directive", () => {
|
|||||||
`<div><div class="slotted"><div class="slot"><div class="child"></div></div></div></div>`
|
`<div><div class="slotted"><div class="slot"><div class="child"></div></div></div></div>`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("t-slot scope context", async () => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
class Wrapper extends Component {
|
||||||
|
static template = xml`<t t-slot="default"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dialog;
|
||||||
|
|
||||||
|
class Dialog extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<Wrapper>
|
||||||
|
<div t-on-click="onClick">
|
||||||
|
<t t-slot="default" />
|
||||||
|
</div>
|
||||||
|
</Wrapper>
|
||||||
|
`;
|
||||||
|
|
||||||
|
static components = { Wrapper };
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
dialog = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick(ev) {
|
||||||
|
// we do not use expect(this).toBe(dialog) here because if it fails, it
|
||||||
|
// may blow up jest because it then tries to compute a diff, which is
|
||||||
|
// infinite if there is a cycle
|
||||||
|
expect(this === dialog).toBe(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<Dialog>
|
||||||
|
<button>The Button</button>
|
||||||
|
</Dialog>`;
|
||||||
|
static components = { Dialog };
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Parent, { target: fixture });
|
||||||
|
|
||||||
|
document.querySelector("button").click();
|
||||||
|
await nextTick();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-slot in recursive templates", async () => {
|
||||||
|
QWeb.registerTemplate(
|
||||||
|
"_test_recursive_template",
|
||||||
|
`
|
||||||
|
<Wrapper>
|
||||||
|
<t t-esc="name" />
|
||||||
|
<t t-foreach="items" t-as="item">
|
||||||
|
|
||||||
|
<t t-if="!item.children.length">
|
||||||
|
<t t-esc="item.name" />
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<t t-else="" t-call="_test_recursive_template">
|
||||||
|
<t t-set="name" t-value="item.name" />
|
||||||
|
<t t-set="items" t-value="item.children" />
|
||||||
|
</t>
|
||||||
|
|
||||||
|
</t>
|
||||||
|
</Wrapper>`
|
||||||
|
);
|
||||||
|
|
||||||
|
class Wrapper extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<wrapper>
|
||||||
|
<t t-slot="default"/>
|
||||||
|
</wrapper>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = "_test_recursive_template";
|
||||||
|
static components = { Wrapper };
|
||||||
|
name = "foo";
|
||||||
|
items = [
|
||||||
|
{
|
||||||
|
name: "foo-0",
|
||||||
|
children: [
|
||||||
|
{ name: "foo-00", children: [] },
|
||||||
|
{
|
||||||
|
name: "foo-01",
|
||||||
|
children: [
|
||||||
|
{ name: "foo-010", children: [] },
|
||||||
|
{ name: "foo-011", children: [] },
|
||||||
|
{
|
||||||
|
name: "foo-012",
|
||||||
|
children: [
|
||||||
|
{ name: "foo-0120", children: [] },
|
||||||
|
{ name: "foo-0121", children: [] },
|
||||||
|
{ name: "foo-0122", children: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: "foo-02", children: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: "foo-1", children: [] },
|
||||||
|
{ name: "foo-2", children: [] },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Parent, { target: fixture });
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<wrapper>foo<wrapper>foo-0foo-00<wrapper>foo-01foo-010foo-011<wrapper>foo-012foo-0120foo-0121foo-0122</wrapper></wrapper>foo-02</wrapper>foo-1foo-2</wrapper>"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -75,8 +75,6 @@ describe("hooks", () => {
|
|||||||
}
|
}
|
||||||
const component = new MyComponent();
|
const component = new MyComponent();
|
||||||
await component.mount(fixture);
|
await component.mount(fixture);
|
||||||
expect(component).not.toHaveProperty("mounted");
|
|
||||||
expect(component).not.toHaveProperty("willUnmount");
|
|
||||||
expect(fixture.innerHTML).toBe("<div>hey</div>");
|
expect(fixture.innerHTML).toBe("<div>hey</div>");
|
||||||
expect(steps).toEqual(["mounted"]);
|
expect(steps).toEqual(["mounted"]);
|
||||||
component.unmount();
|
component.unmount();
|
||||||
@@ -383,8 +381,6 @@ describe("hooks", () => {
|
|||||||
|
|
||||||
const component = new MyComponent();
|
const component = new MyComponent();
|
||||||
await component.mount(fixture);
|
await component.mount(fixture);
|
||||||
expect(component).not.toHaveProperty("patched");
|
|
||||||
expect(component).not.toHaveProperty("willPatch");
|
|
||||||
expect(steps).toEqual([]);
|
expect(steps).toEqual([]);
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div>hey</div>");
|
expect(fixture.innerHTML).toBe("<div>hey</div>");
|
||||||
@@ -630,8 +626,6 @@ describe("hooks", () => {
|
|||||||
|
|
||||||
const app = new App();
|
const app = new App();
|
||||||
await app.mount(fixture);
|
await app.mount(fixture);
|
||||||
expect(app).not.toHaveProperty("willStart");
|
|
||||||
expect(app).not.toHaveProperty("willUpdateProps");
|
|
||||||
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
|
||||||
|
|
||||||
// NOTE: 'on2ndStart' appears first in the list even though
|
// NOTE: 'on2ndStart' appears first in the list even though
|
||||||
|
|||||||
@@ -78,7 +78,9 @@ describe("Portal: Props validation", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.message).toBe(`Invalid Prop 'target' in component 'Portal'`);
|
expect(error.message).toBe(
|
||||||
|
"Invalid Prop 'target' in component 'Portal': target is not an instance of String"
|
||||||
|
);
|
||||||
|
|
||||||
QWeb.dev = dev;
|
QWeb.dev = dev;
|
||||||
});
|
});
|
||||||
@@ -546,7 +548,9 @@ describe("Portal: Basic use and DOM placement", () => {
|
|||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
expect(error).toBeDefined();
|
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 () => {
|
test("portal manual unmount", async () => {
|
||||||
|
|||||||
@@ -429,7 +429,8 @@ exports[`foreach does not pollute the rendering context 1`] = `
|
|||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
let _2 = [1];
|
let _2 = [1];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_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 vn1 = h('div', p1, c1);
|
||||||
let _2 = [1,2];
|
let _2 = [1,2];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -502,7 +504,8 @@ exports[`foreach iterate on items 1`] = `
|
|||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
let _2 = [3,2,1];
|
let _2 = [3,2,1];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -549,7 +552,8 @@ exports[`foreach iterate, dict param 1`] = `
|
|||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['value'];
|
let _2 = scope['value'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -596,7 +600,8 @@ exports[`foreach iterate, position 1`] = `
|
|||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
let _2 = Array(5);
|
let _2 = Array(5);
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_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 vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['numbers'];
|
let _2 = scope['numbers'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_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 key1 = i1;
|
||||||
let _6 = scope['letters'];
|
let _6 = scope['letters'];
|
||||||
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _7 = _8 = _6;
|
let _7 = _6;
|
||||||
|
let _8 = _6;
|
||||||
if (!(_6 instanceof Array)) {
|
if (!(_6 instanceof Array)) {
|
||||||
_7 = Object.keys(_6);
|
_7 = Object.keys(_6);
|
||||||
_8 = Object.values(_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 vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['numbers'];
|
let _2 = scope['numbers'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_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 key1 = i1;
|
||||||
let _6 = scope['letters'];
|
let _6 = scope['letters'];
|
||||||
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _7 = _8 = _6;
|
let _7 = _6;
|
||||||
|
let _8 = _6;
|
||||||
if (!(_6 instanceof Array)) {
|
if (!(_6 instanceof Array)) {
|
||||||
_7 = Object.keys(_6);
|
_7 = Object.keys(_6);
|
||||||
_8 = Object.values(_6);
|
_8 = Object.values(_6);
|
||||||
@@ -811,7 +820,8 @@ exports[`foreach t-foreach in t-forach 1`] = `
|
|||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['numbers'];
|
let _2 = scope['numbers'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -828,7 +838,8 @@ exports[`foreach t-foreach in t-forach 1`] = `
|
|||||||
let key1 = i1;
|
let key1 = i1;
|
||||||
let _6 = scope['letters'];
|
let _6 = scope['letters'];
|
||||||
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _7 = _8 = _6;
|
let _7 = _6;
|
||||||
|
let _8 = _6;
|
||||||
if (!(_6 instanceof Array)) {
|
if (!(_6 instanceof Array)) {
|
||||||
_7 = Object.keys(_6);
|
_7 = Object.keys(_6);
|
||||||
_8 = Object.values(_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 vn1 = h('div', p1, c1);
|
||||||
let _2 = [1,2];
|
let _2 = [1,2];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -940,7 +952,8 @@ exports[`misc global 1`] = `
|
|||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
let _2 = [4,5,6];
|
let _2 = [4,5,6];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -1666,7 +1679,8 @@ exports[`t-call (template calling recursive template, part 2 2`] = `
|
|||||||
}
|
}
|
||||||
let _6 = scope['node'].children||[];
|
let _6 = scope['node'].children||[];
|
||||||
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _7 = _8 = _6;
|
let _7 = _6;
|
||||||
|
let _8 = _6;
|
||||||
if (!(_6 instanceof Array)) {
|
if (!(_6 instanceof Array)) {
|
||||||
_7 = Object.keys(_6);
|
_7 = Object.keys(_6);
|
||||||
_8 = Object.values(_6);
|
_8 = Object.values(_6);
|
||||||
@@ -1746,7 +1760,8 @@ exports[`t-call (template calling recursive template, part 3 2`] = `
|
|||||||
}
|
}
|
||||||
let _6 = scope['node'].children||[];
|
let _6 = scope['node'].children||[];
|
||||||
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _7 = _8 = _6;
|
let _7 = _6;
|
||||||
|
let _8 = _6;
|
||||||
if (!(_6 instanceof Array)) {
|
if (!(_6 instanceof Array)) {
|
||||||
_7 = Object.keys(_6);
|
_7 = Object.keys(_6);
|
||||||
_8 = Object.values(_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||[];
|
let _6 = scope['node'].children||[];
|
||||||
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _7 = _8 = _6;
|
let _7 = _6;
|
||||||
|
let _8 = _6;
|
||||||
if (!(_6 instanceof Array)) {
|
if (!(_6 instanceof Array)) {
|
||||||
_7 = Object.keys(_6);
|
_7 = Object.keys(_6);
|
||||||
_8 = Object.values(_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 vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['list'];
|
let _2 = scope['list'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_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 vn1 = h('ul', p1, c1);
|
||||||
let _2 = scope['beers'];
|
let _2 = scope['beers'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_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 vn1 = h('ul', p1, c1);
|
||||||
let _2 = ['someval'];
|
let _2 = ['someval'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -3092,8 +3111,7 @@ exports[`t-on t-on with inline statement, part 3 1`] = `
|
|||||||
let c1 = [], p1 = {key:1,on:{}};
|
let c1 = [], p1 = {key:1,on:{}};
|
||||||
let vn1 = h('button', p1, c1);
|
let vn1 = h('button', p1, c1);
|
||||||
const state_2 = scope['state'];
|
const state_2 = scope['state'];
|
||||||
const someFunction_2 = scope['someFunction'];
|
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}const res = (() => { return state_2.n=scope['someFunction'](3) })(); if (typeof res === 'function') { res(e) }};
|
||||||
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}const res = (() => { return state_2.n=someFunction_2(3) })(); if (typeof res === 'function') { res(e) }};
|
|
||||||
c1.push({text: \`Toggle\`});
|
c1.push({text: \`Toggle\`});
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -3161,7 +3179,8 @@ exports[`t-on t-on with prevent modifier in t-foreach 1`] = `
|
|||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['projects'];
|
let _2 = scope['projects'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -3432,7 +3451,8 @@ exports[`t-ref refs in a loop 1`] = `
|
|||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
let _2 = scope['items'];
|
let _2 = scope['items'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -3676,7 +3696,8 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
|
|||||||
scope.v = 1;
|
scope.v = 1;
|
||||||
let _2 = scope['list'];
|
let _2 = scope['list'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _2;
|
||||||
|
let _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
_3 = Object.keys(_2);
|
||||||
_4 = Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
@@ -3943,6 +3964,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`] = `
|
exports[`translation support can translate node content 1`] = `
|
||||||
"function anonymous(context, extra
|
"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;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
@@ -27,3 +27,31 @@ exports[`qweb t-att t-att-class with multiple classes 2`] = `
|
|||||||
return vn4;
|
return vn4;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`qweb t-att t-att-class with multiple classes, some of which are duplicate 1`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"test\\"
|
||||||
|
let utils = this.constructor.utils;
|
||||||
|
let scope = Object.create(context);
|
||||||
|
let h = this.h;
|
||||||
|
let _1 = utils.toClassObj({'a b c':scope['value'],'a b d':!scope['value']});
|
||||||
|
let c2 = [], p2 = {key:2,class:_1};
|
||||||
|
let vn2 = h('div', p2, c2);
|
||||||
|
return vn2;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`qweb t-att t-att-class with multiple classes, some of which are duplicate 2`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"test\\"
|
||||||
|
let utils = this.constructor.utils;
|
||||||
|
let scope = Object.create(context);
|
||||||
|
let h = this.h;
|
||||||
|
let _3 = utils.toClassObj({'a b c':scope['value'],'a b d':!scope['value']});
|
||||||
|
let c4 = [], p4 = {key:4,class:_3};
|
||||||
|
let vn4 = h('div', p4, c4);
|
||||||
|
return vn4;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|||||||
+16
-5
@@ -1,4 +1,5 @@
|
|||||||
import { QWeb } from "../../src/qweb/index";
|
import { QWeb } from "../../src/qweb/index";
|
||||||
|
import { config } from "../../src/index";
|
||||||
import { nextTick, normalize, renderToDOM, renderToString, trim } from "../helpers";
|
import { nextTick, normalize, renderToDOM, renderToString, trim } from "../helpers";
|
||||||
import { patch } from "../../src/vdom";
|
import { patch } from "../../src/vdom";
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ let qweb: QWeb;
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
QWeb.TEMPLATES = {};
|
QWeb.TEMPLATES = {};
|
||||||
|
QWeb.nextId = 1;
|
||||||
qweb = new QWeb();
|
qweb = new QWeb();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -161,7 +163,7 @@ describe("t-esc", () => {
|
|||||||
test("escaping", () => {
|
test("escaping", () => {
|
||||||
qweb.addTemplate("test", `<span><t t-esc="var"/></span>`);
|
qweb.addTemplate("test", `<span><t t-esc="var"/></span>`);
|
||||||
expect(renderToString(qweb, "test", { var: "<ok>abc</ok>" })).toBe(
|
expect(renderToString(qweb, "test", { var: "<ok>abc</ok>" })).toBe(
|
||||||
"<span>&lt;ok&gt;abc&lt;/ok&gt;</span>"
|
"<span><ok>abc</ok></span>"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1862,8 +1864,7 @@ describe("t-ref", () => {
|
|||||||
|
|
||||||
describe("loading templates", () => {
|
describe("loading templates", () => {
|
||||||
test("can initialize qweb with a string", () => {
|
test("can initialize qweb with a string", () => {
|
||||||
const templates = `
|
const templates = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<templates id="template" xml:space="preserve">
|
<templates id="template" xml:space="preserve">
|
||||||
<div t-name="hey">jupiler</div>
|
<div t-name="hey">jupiler</div>
|
||||||
</templates>`;
|
</templates>`;
|
||||||
@@ -1872,8 +1873,7 @@ describe("loading templates", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("can load a few templates from a xml string", () => {
|
test("can load a few templates from a xml string", () => {
|
||||||
const data = `
|
const data = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<templates id="template" xml:space="preserve">
|
<templates id="template" xml:space="preserve">
|
||||||
|
|
||||||
<t t-name="items"><li>ok</li><li>foo</li></t>
|
<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", () => {
|
test("translation is done on the trimmed text, with extra spaces readded after", () => {
|
||||||
const translations = {
|
const translations = {
|
||||||
word: "mot",
|
word: "mot",
|
||||||
|
|||||||
@@ -207,6 +207,17 @@ describe("expression evaluation", () => {
|
|||||||
expect(compileExpr("{a,b:3,c}", {})).toBe("{a:scope['a'],b:3,c:scope['c']}");
|
expect(compileExpr("{a,b:3,c}", {})).toBe("{a:scope['a'],b:3,c:scope['c']}");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("works with short object description and lists ", () => {
|
||||||
|
expect(compileExpr("[a, b]", {})).toBe("[scope['a'],scope['b']]");
|
||||||
|
expect(compileExpr("[a, b, c]", {})).toBe("[scope['a'],scope['b'],scope['c']]");
|
||||||
|
expect(compileExpr("[a, {b, c},d]", {})).toBe(
|
||||||
|
"[scope['a'],{b:scope['b'],c:scope['c']},scope['d']]"
|
||||||
|
);
|
||||||
|
expect(compileExpr("{a:[b, {c, d: e}]}", {})).toBe(
|
||||||
|
"{a:[scope['b'],{c:scope['c'],d:scope['e']}]}"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("template strings", () => {
|
test("template strings", () => {
|
||||||
expect(compileExpr("`hey`", {})).toBe("`hey`");
|
expect(compileExpr("`hey`", {})).toBe("`hey`");
|
||||||
expect(compileExpr("`hey ${you}`", {})).toBe("`hey ${scope['you']}`");
|
expect(compileExpr("`hey ${you}`", {})).toBe("`hey ${scope['you']}`");
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -24,4 +24,13 @@ describe("qweb t-att", () => {
|
|||||||
'<div class="a b c"></div>'
|
'<div class="a b c"></div>'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("t-att-class with multiple classes, some of which are duplicate", () => {
|
||||||
|
expect(render(`<div t-att-class="{'a b c': value, 'a b d': !value}" />`, { value: true })).toBe(
|
||||||
|
'<div class="a b c"></div>'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
render(`<div t-att-class="{'a b c': value, 'a b d': !value}" />`, { value: false })
|
||||||
|
).toBe('<div class="a b d"></div>');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ describe("Link component", () => {
|
|||||||
await app.mount(fixture);
|
await app.mount(fixture);
|
||||||
|
|
||||||
expect(window.location.pathname).toBe("/users");
|
expect(window.location.pathname).toBe("/users");
|
||||||
var evt = new MouseEvent("click", {
|
var evt = new MouseEvent("contextmenu", {
|
||||||
button: 1,
|
button: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -13,8 +13,8 @@
|
|||||||
// "compileOnSave": false, // Signals to the IDE to generate all files for a given tsconfig.json upon saving.
|
// "compileOnSave": false, // Signals to the IDE to generate all files for a given tsconfig.json upon saving.
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// Main options
|
// Main options
|
||||||
"target": "esnext", // Specify ECMAScript target version: 'es3' (default), 'es5', 'es2015', 'es2016', 'es2017','es2018' or 'esnext'.
|
"target": "es2017", // 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'.
|
"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.
|
// "lib": ["esnext", "dom"], // Specify library files to be included in the compilation.
|
||||||
// "allowJs": false, // Allow javascript files to be compiled.
|
// "allowJs": false, // Allow javascript files to be compiled.
|
||||||
// "checkJs": false, // Report errors in .js files.
|
// "checkJs": false, // Report errors in .js files.
|
||||||
|
|||||||
Reference in New Issue
Block a user