mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8083678f03 | |||
| 61c2ec5d83 | |||
| e579a993fd | |||
| ea3c6f7bf0 | |||
| 9cfafc30b5 | |||
| ad42c583c6 | |||
| 6faaa6c361 | |||
| bff539cabf | |||
| 88fd1cf483 | |||
| f0b75b6890 | |||
| a1552117f9 | |||
| 17ae1d06c4 | |||
| 27629cedfa | |||
| 3a93370ab6 | |||
| a6bb4d8ee1 | |||
| caf842c482 | |||
| 1b513a1637 | |||
| 52a200878a | |||
| f4994a20d8 | |||
| 7419c1982a | |||
| a8c8f6affe | |||
| dabe7f9bfa | |||
| d08ea63565 | |||
| 7ebe0da962 | |||
| 3e0e8475a6 |
@@ -14,7 +14,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [10.x, 12.x, 14.x]
|
node-version: [12.x, 14.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|||||||
+4
-1
@@ -28,4 +28,7 @@ node_modules
|
|||||||
|
|
||||||
release-notes.md
|
release-notes.md
|
||||||
|
|
||||||
.rpt2_cache
|
.rpt2_cache
|
||||||
|
|
||||||
|
# useful in some cases
|
||||||
|
/temp
|
||||||
@@ -120,7 +120,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.3.2](https://github.com/odoo/owl/releases/tag/v1.3.2)
|
- [owl-1.4.2](https://github.com/odoo/owl/releases/tag/v1.4.2)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ OWL, React and Vue have the same main feature: they allow developers to build
|
|||||||
declarative user interfaces. To do that, all these frameworks uses a virtual dom. However, there are still obviously many differences.
|
declarative user interfaces. To do that, all these frameworks uses a virtual dom. However, there are still obviously many differences.
|
||||||
|
|
||||||
In this page, we try to highlight some of these differences. Obviously, a lot of
|
In this page, we try to highlight some of these differences. Obviously, a lot of
|
||||||
effort was done to be fair. However, if you disagree with some of the points
|
effort was put to be fair. However, if you disagree with some of the points
|
||||||
discussed, feel free to open an issue/submit a PR to correct this text.
|
discussed, feel free to open an issue/submit a PR to correct this text.
|
||||||
|
|
||||||
## Content
|
## Content
|
||||||
@@ -47,7 +47,7 @@ components are fast enough for all our usecases, and making it as simple as
|
|||||||
possible for developers is more valuable (for us).
|
possible for developers is more valuable (for us).
|
||||||
|
|
||||||
Also, functions or class based components are more than just syntax. Functions
|
Also, functions or class based components are more than just syntax. Functions
|
||||||
comes with a mindset of composition and class are about inheritance. Clearly,
|
come with a mindset of composition and class are about inheritance. Clearly,
|
||||||
both of these are important mechanisms for reusing code. Also, one does not
|
both of these are important mechanisms for reusing code. Also, one does not
|
||||||
exclude the other.
|
exclude the other.
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
- [Setting Variables](#setting-variables)
|
- [Setting Variables](#setting-variables)
|
||||||
- [Conditionals](#conditionals)
|
- [Conditionals](#conditionals)
|
||||||
- [Dynamic Attributes](#dynamic-attributes)
|
- [Dynamic Attributes](#dynamic-attributes)
|
||||||
|
- [Dynamic Class Attribute](#dynamic-class-attribute)
|
||||||
- [Dynamic Tag Names](#dynamic-tag-names)
|
- [Dynamic Tag Names](#dynamic-tag-names)
|
||||||
- [Loops](#loops)
|
- [Loops](#loops)
|
||||||
- [Rendering Sub Templates](#rendering-sub-templates)
|
- [Rendering Sub Templates](#rendering-sub-templates)
|
||||||
@@ -326,6 +327,24 @@ values) or a pair `[key, value]`. For example:
|
|||||||
<div t-att="['a', 'b']"/> <!-- <div a="b"></div> -->
|
<div t-att="['a', 'b']"/> <!-- <div a="b"></div> -->
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Dynamic class attribute
|
||||||
|
|
||||||
|
For convenience, Owl supports a special case for the `t-att-class` case: one can
|
||||||
|
use an object with keys describing the classes, and values boolean value denoting
|
||||||
|
if the class is or is not present:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div t-att-class="{'a': true, 'b': true}"/> <!-- result: <div class="a b"></div> -->
|
||||||
|
|
||||||
|
<div t-att-class="{'a b': true, 'c': true}"/> <!-- result: <div class="a b c"></div> -->
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that it can be combined with normal class attribute:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div class="a" t-att-class="{'b': true}"/> <!-- result: <div class="a b"></div> -->
|
||||||
|
```
|
||||||
|
|
||||||
### Dynamic tag names
|
### Dynamic tag names
|
||||||
|
|
||||||
When writing generic components or templates, the specific concrete tag for an
|
When writing generic components or templates, the specific concrete tag for an
|
||||||
|
|||||||
@@ -62,14 +62,14 @@ The CSS tag is useful to define a css stylesheet in the javascript file:
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
class MyComponent extends Component {
|
class MyComponent extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div class="my-component">some template</div>
|
<div class="my-component">some template</div>
|
||||||
`;
|
`;
|
||||||
static css`
|
static style = css`
|
||||||
.my-component {
|
.my-component {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "1.3.2",
|
"version": "1.4.2",
|
||||||
"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",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# 🦉 OWL Roadmap 🦉
|
# 🦉 OWL Roadmap 🦉
|
||||||
|
|
||||||
- Current version: 1.3.2
|
- Current version: 1.4.2
|
||||||
- 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
|
||||||
|
|||||||
@@ -245,7 +245,18 @@ QWeb.addDirective({
|
|||||||
.join(",");
|
.join(",");
|
||||||
let componentID = ctx.generateID();
|
let componentID = ctx.generateID();
|
||||||
|
|
||||||
const templateKey = ctx.generateTemplateKey();
|
let hasDefinedKey = false;
|
||||||
|
let templateKey;
|
||||||
|
if (node.tagName === "t" && !node.hasAttribute("t-key") && value.match(INTERP_REGEXP)) {
|
||||||
|
defineComponentKey();
|
||||||
|
const id = ctx.generateID();
|
||||||
|
// the ___ is to make sure we have no possible conflict with normal
|
||||||
|
// template keys
|
||||||
|
ctx.addLine(`let k${id} = '___' + componentKey${componentID}`);
|
||||||
|
templateKey = `k${id}`;
|
||||||
|
} else {
|
||||||
|
templateKey = ctx.generateTemplateKey();
|
||||||
|
}
|
||||||
let ref = node.getAttribute("t-ref");
|
let ref = node.getAttribute("t-ref");
|
||||||
let refExpr = "";
|
let refExpr = "";
|
||||||
let refKey: string = "";
|
let refKey: string = "";
|
||||||
@@ -291,7 +302,7 @@ QWeb.addDirective({
|
|||||||
if (tattClass) {
|
if (tattClass) {
|
||||||
let tattExpr = ctx.formatExpression(tattClass);
|
let tattExpr = ctx.formatExpression(tattClass);
|
||||||
if (tattExpr[0] !== "{" || tattExpr[tattExpr.length - 1] !== "}") {
|
if (tattExpr[0] !== "{" || tattExpr[tattExpr.length - 1] !== "}") {
|
||||||
tattExpr = `utils.toObj(${tattExpr})`;
|
tattExpr = `utils.toClassObj(${tattExpr})`;
|
||||||
}
|
}
|
||||||
if (classAttr) {
|
if (classAttr) {
|
||||||
ctx.addLine(`Object.assign(${classObj}, ${tattExpr})`);
|
ctx.addLine(`Object.assign(${classObj}, ${tattExpr})`);
|
||||||
@@ -336,7 +347,7 @@ QWeb.addDirective({
|
|||||||
}
|
}
|
||||||
if (hasDynamicProps) {
|
if (hasDynamicProps) {
|
||||||
const dynamicProp = ctx.formatExpression(node.getAttribute("t-props")!);
|
const dynamicProp = ctx.formatExpression(node.getAttribute("t-props")!);
|
||||||
ctx.addLine(`let props${componentID} = Object.assign({${propStr}}, ${dynamicProp});`);
|
ctx.addLine(`let props${componentID} = Object.assign({}, ${dynamicProp}, {${propStr}});`);
|
||||||
} else {
|
} else {
|
||||||
ctx.addLine(`let props${componentID} = {${propStr}};`);
|
ctx.addLine(`let props${componentID} = {${propStr}};`);
|
||||||
}
|
}
|
||||||
@@ -355,7 +366,7 @@ QWeb.addDirective({
|
|||||||
// SLOTS
|
// SLOTS
|
||||||
const hasSlots = node.childNodes.length;
|
const hasSlots = node.childNodes.length;
|
||||||
|
|
||||||
let scope = hasSlots ? `Object.assign(Object.create(context), scope)` : "undefined";
|
let scope = hasSlots ? `utils.combine(context, scope)` : "undefined";
|
||||||
|
|
||||||
ctx.addIf(`w${componentID}`);
|
ctx.addIf(`w${componentID}`);
|
||||||
|
|
||||||
@@ -378,9 +389,15 @@ QWeb.addDirective({
|
|||||||
ctx.addElse();
|
ctx.addElse();
|
||||||
|
|
||||||
// new component
|
// new component
|
||||||
|
function defineComponentKey() {
|
||||||
|
if (!hasDefinedKey) {
|
||||||
|
const interpValue = ctx.interpolate(value);
|
||||||
|
ctx.addLine(`let componentKey${componentID} = ${interpValue};`);
|
||||||
|
hasDefinedKey = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineComponentKey();
|
||||||
const contextualValue = value.match(INTERP_REGEXP) ? "false" : ctx.formatExpression(value);
|
const contextualValue = value.match(INTERP_REGEXP) ? "false" : ctx.formatExpression(value);
|
||||||
const interpValue = ctx.interpolate(value);
|
|
||||||
ctx.addLine(`let componentKey${componentID} = ${interpValue};`);
|
|
||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`let W${componentID} = ${contextualValue} || context.constructor.components[componentKey${componentID}] || QWeb.components[componentKey${componentID}];`
|
`let W${componentID} = ${contextualValue} || context.constructor.components[componentKey${componentID}] || QWeb.components[componentKey${componentID}];`
|
||||||
);
|
);
|
||||||
@@ -452,12 +469,17 @@ QWeb.addDirective({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (clone.childNodes.length) {
|
if (clone.childNodes.length) {
|
||||||
|
let hasContent = false;
|
||||||
const t = clone.ownerDocument!.createElement("t");
|
const t = clone.ownerDocument!.createElement("t");
|
||||||
for (let child of Object.values(clone.childNodes)) {
|
for (let child of Object.values(clone.childNodes)) {
|
||||||
|
hasContent =
|
||||||
|
hasContent || (child instanceof Text ? Boolean(child.textContent.trim().length) : true);
|
||||||
t.appendChild(child);
|
t.appendChild(child);
|
||||||
}
|
}
|
||||||
const slotFn = qweb._compile(`slot_default_template`, { elem: t, hasParent: true });
|
if (hasContent) {
|
||||||
QWeb.slots[`${slotId}_default`] = slotFn;
|
const slotFn = qweb._compile(`slot_default_template`, { elem: t, hasParent: true });
|
||||||
|
QWeb.slots[`${slotId}_default`] = slotFn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -355,7 +355,14 @@ export class Fiber {
|
|||||||
this.root.counter = 0;
|
this.root.counter = 0;
|
||||||
this.root.error = error;
|
this.root.error = error;
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
root.destroy();
|
// at this point, the state of the application is corrupted and we could
|
||||||
|
// have a lot of issues or crashes. So we destroy the application in a try
|
||||||
|
// catch and swallow these errors because the fiber is already in error,
|
||||||
|
// and this is the actual issue that needs to be solved, not those followup
|
||||||
|
// errors.
|
||||||
|
try {
|
||||||
|
root.destroy();
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -20,10 +20,10 @@ Object.defineProperty(config, "mode", {
|
|||||||
set(mode: string) {
|
set(mode: string) {
|
||||||
QWeb.dev = mode === "dev";
|
QWeb.dev = mode === "dev";
|
||||||
if (QWeb.dev) {
|
if (QWeb.dev) {
|
||||||
const url = `https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode`;
|
console.info(`Owl is running in 'dev' mode.
|
||||||
console.warn(
|
|
||||||
`Owl is running in 'dev' mode. This is not suitable for production use. See ${url} for more information.`
|
This is not suitable for production use.
|
||||||
);
|
See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`Owl is now running in 'prod' mode.`);
|
console.log(`Owl is now running in 'prod' mode.`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ type TKind =
|
|||||||
| "RIGHT_PAREN"
|
| "RIGHT_PAREN"
|
||||||
| "COMMA"
|
| "COMMA"
|
||||||
| "VALUE"
|
| "VALUE"
|
||||||
|
| "TEMPLATE_STRING"
|
||||||
| "SYMBOL"
|
| "SYMBOL"
|
||||||
| "OPERATOR"
|
| "OPERATOR"
|
||||||
| "COLON";
|
| "COLON";
|
||||||
@@ -67,6 +68,7 @@ interface Token {
|
|||||||
originalValue?: string;
|
originalValue?: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
varName?: string;
|
varName?: string;
|
||||||
|
replace?: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATIC_TOKEN_MAP: { [key: string]: TKind } = Object.assign(Object.create(null), {
|
const STATIC_TOKEN_MAP: { [key: string]: TKind } = Object.assign(Object.create(null), {
|
||||||
@@ -89,7 +91,7 @@ type Tokenizer = (expr: string) => Token | false;
|
|||||||
let tokenizeString: Tokenizer = function (expr) {
|
let tokenizeString: Tokenizer = function (expr) {
|
||||||
let s = expr[0];
|
let s = expr[0];
|
||||||
let start = s;
|
let start = s;
|
||||||
if (s !== "'" && s !== '"') {
|
if (s !== "'" && s !== '"' && s !== "`") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let i = 1;
|
let i = 1;
|
||||||
@@ -111,6 +113,17 @@ let tokenizeString: Tokenizer = function (expr) {
|
|||||||
throw new Error("Invalid expression");
|
throw new Error("Invalid expression");
|
||||||
}
|
}
|
||||||
s += start;
|
s += start;
|
||||||
|
if (start === "`") {
|
||||||
|
return {
|
||||||
|
type: "TEMPLATE_STRING",
|
||||||
|
value: s,
|
||||||
|
replace(replacer) {
|
||||||
|
return s.replace(/\$\{(.*?)\}/g, (match, group) => {
|
||||||
|
return "${" + replacer(group) + "}";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
return { type: "VALUE", value: s };
|
return { type: "VALUE", value: s };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -210,6 +223,10 @@ export function tokenize(expr: string): Token[] {
|
|||||||
// Expression "evaluator"
|
// Expression "evaluator"
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const isLeftSeparator = (token) => token && (token.type === "LEFT_BRACE" || token.type === "COMMA");
|
||||||
|
const isRightSeparator = (token) =>
|
||||||
|
token && (token.type === "RIGHT_BRACE" || token.type === "COMMA");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the main function exported by this file. This is the code that will
|
* This is the main function exported by this file. This is the code that will
|
||||||
* process an expression (given as a string) and returns another expression with
|
* process an expression (given as a string) and returns another expression with
|
||||||
@@ -238,13 +255,39 @@ export function tokenize(expr: string): Token[] {
|
|||||||
export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar }): Token[] {
|
export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar }): Token[] {
|
||||||
scope = Object.create(scope);
|
scope = Object.create(scope);
|
||||||
const tokens = tokenize(expr);
|
const tokens = tokenize(expr);
|
||||||
for (let i = 0; i < tokens.length; i++) {
|
|
||||||
|
let i = 0;
|
||||||
|
let stack = []; // to track last opening [ or {
|
||||||
|
|
||||||
|
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}
|
||||||
|
if (
|
||||||
|
groupType === "LEFT_BRACE" &&
|
||||||
|
isLeftSeparator(prevToken) &&
|
||||||
|
isRightSeparator(nextToken)
|
||||||
|
) {
|
||||||
|
tokens.splice(i + 1, 0, { type: "COLON", value: ":" }, { ...token });
|
||||||
|
nextToken = tokens[i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
if (prevToken.type === "OPERATOR" && prevToken.value === ".") {
|
if (prevToken.type === "OPERATOR" && prevToken.value === ".") {
|
||||||
isVar = false;
|
isVar = false;
|
||||||
} else if (prevToken.type === "LEFT_BRACE" || prevToken.type === "COMMA") {
|
} else if (prevToken.type === "LEFT_BRACE" || prevToken.type === "COMMA") {
|
||||||
@@ -254,6 +297,9 @@ export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (token.type === "TEMPLATE_STRING") {
|
||||||
|
token.value = token.replace((expr) => compileExpr(expr, scope));
|
||||||
|
}
|
||||||
if (nextToken && nextToken.type === "OPERATOR" && nextToken.value === "=>") {
|
if (nextToken && nextToken.type === "OPERATOR" && nextToken.value === "=>") {
|
||||||
if (token.type === "RIGHT_PAREN") {
|
if (token.type === "RIGHT_PAREN") {
|
||||||
let j = i - 1;
|
let j = i - 1;
|
||||||
@@ -278,6 +324,7 @@ export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar
|
|||||||
token.value = `scope['${token.value}']`;
|
token.value = `scope['${token.value}']`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -2,6 +2,7 @@ import { STATUS } from "../component/component";
|
|||||||
import { VNode } from "../vdom/index";
|
import { VNode } from "../vdom/index";
|
||||||
import { INTERP_REGEXP } from "./compilation_context";
|
import { INTERP_REGEXP } from "./compilation_context";
|
||||||
import { QWeb } from "./qweb";
|
import { QWeb } from "./qweb";
|
||||||
|
import { browser } from "../browser";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Owl QWeb Extensions
|
* Owl QWeb Extensions
|
||||||
@@ -75,6 +76,7 @@ export function makeHandlerCode(
|
|||||||
// we need to capture every variable in it
|
// we need to capture every variable in it
|
||||||
putInCache = false;
|
putInCache = false;
|
||||||
code = ctx.captureExpression(value);
|
code = ctx.captureExpression(value);
|
||||||
|
code = `const res = (() => { return ${code} })(); if (typeof res === 'function') { res(e) }`;
|
||||||
}
|
}
|
||||||
const modCode = mods.map((mod) => modcodes[mod]).join("");
|
const modCode = mods.map((mod) => modcodes[mod]).join("");
|
||||||
let handler = `function (e) {if (context.__owl__.status === ${STATUS.DESTROYED}){return}${modCode}${code}}`;
|
let handler = `function (e) {if (context.__owl__.status === ${STATUS.DESTROYED}){return}${modCode}${code}}`;
|
||||||
@@ -197,7 +199,14 @@ function whenTransitionEnd(elm: HTMLElement, cb) {
|
|||||||
const durations: Array<string> = (styles.transitionDuration || "").split(", ");
|
const durations: Array<string> = (styles.transitionDuration || "").split(", ");
|
||||||
const timeout: number = getTimeout(delays, durations);
|
const timeout: number = getTimeout(delays, durations);
|
||||||
if (timeout > 0) {
|
if (timeout > 0) {
|
||||||
elm.addEventListener("transitionend", cb, { once: true });
|
const transitionEndCB = () => {
|
||||||
|
if (!elm.parentNode) return;
|
||||||
|
cb();
|
||||||
|
browser.clearTimeout(fallbackTimeout);
|
||||||
|
elm.removeEventListener("transitionend", transitionEndCB);
|
||||||
|
};
|
||||||
|
elm.addEventListener("transitionend", transitionEndCB, { once: true });
|
||||||
|
const fallbackTimeout = browser.setTimeout(transitionEndCB, timeout + 1);
|
||||||
} else {
|
} else {
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-14
@@ -80,7 +80,7 @@ const NODE_HOOKS_PARAMS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface Utils {
|
interface Utils {
|
||||||
toObj(expr: any): Object;
|
toClassObj(expr: any): Object;
|
||||||
shallowEqual(p1: Object, p2: Object): boolean;
|
shallowEqual(p1: Object, p2: Object): boolean;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
@@ -111,20 +111,39 @@ function vDomToString(vdom: VNode[]): string {
|
|||||||
|
|
||||||
const UTILS: Utils = {
|
const UTILS: Utils = {
|
||||||
zero: Symbol("zero"),
|
zero: Symbol("zero"),
|
||||||
toObj(expr) {
|
toClassObj(expr) {
|
||||||
|
const result = {};
|
||||||
if (typeof expr === "string") {
|
if (typeof expr === "string") {
|
||||||
|
// we transform here a list of classes into an object:
|
||||||
|
// 'hey you' becomes {hey: true, you: true}
|
||||||
expr = expr.trim();
|
expr = expr.trim();
|
||||||
if (!expr) {
|
if (!expr) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
let words = expr.split(/\s+/);
|
let words = expr.split(/\s+/);
|
||||||
let result = {};
|
|
||||||
for (let i = 0; i < words.length; i++) {
|
for (let i = 0; i < words.length; i++) {
|
||||||
result[words[i]] = true;
|
result[words[i]] = true;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return expr;
|
// 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}
|
||||||
|
for (let key in expr) {
|
||||||
|
const value = expr[key];
|
||||||
|
const words = key.split(/\s+/);
|
||||||
|
for (let word of words) {
|
||||||
|
result[word] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
combine(context, scope) {
|
||||||
|
const clone = Object.create(context);
|
||||||
|
while (!isComponent(scope)) {
|
||||||
|
Object.assign(clone, scope);
|
||||||
|
scope = scope.__proto__;
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
},
|
},
|
||||||
shallowEqual,
|
shallowEqual,
|
||||||
addNameSpace(vnode) {
|
addNameSpace(vnode) {
|
||||||
@@ -617,13 +636,21 @@ export class QWeb extends EventBus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (node.nodeName !== "t" || node.hasAttribute("t-tag")) {
|
if (node.nodeName !== "t" || node.hasAttribute("t-tag")) {
|
||||||
let nodeID = this._compileGenericNode(node, ctx, withHandlers);
|
|
||||||
ctx = ctx.withParent(nodeID);
|
|
||||||
let nodeHooks = {};
|
let nodeHooks = {};
|
||||||
let addNodeHook = function (hook, handler) {
|
let addNodeHook = function (hook, handler) {
|
||||||
nodeHooks[hook] = nodeHooks[hook] || [];
|
nodeHooks[hook] = nodeHooks[hook] || [];
|
||||||
nodeHooks[hook].push(handler);
|
nodeHooks[hook].push(handler);
|
||||||
};
|
};
|
||||||
|
if (node.tagName === "select" && node.hasAttribute("t-att-value")) {
|
||||||
|
const value = node.getAttribute("t-att-value");
|
||||||
|
let exprId = ctx.generateID();
|
||||||
|
ctx.addLine(`let expr${exprId} = ${ctx.formatExpression(value)};`);
|
||||||
|
let expr = `expr${exprId}`;
|
||||||
|
node.setAttribute("t-att-value", expr);
|
||||||
|
addNodeHook("create", `n.elm.value=${expr};`);
|
||||||
|
}
|
||||||
|
let nodeID = this._compileGenericNode(node, ctx, withHandlers);
|
||||||
|
ctx = ctx.withParent(nodeID);
|
||||||
|
|
||||||
for (let { directive, value, fullName } of validDirectives) {
|
for (let { directive, value, fullName } of validDirectives) {
|
||||||
if (directive.atNodeCreation) {
|
if (directive.atNodeCreation) {
|
||||||
@@ -705,16 +732,18 @@ export class QWeb extends EventBus {
|
|||||||
isProp = key === "selected" || key === "disabled";
|
isProp = key === "selected" || key === "disabled";
|
||||||
break;
|
break;
|
||||||
case "textarea":
|
case "textarea":
|
||||||
isProp = key === "readonly" || key === "disabled";
|
isProp = key === "readonly" || key === "disabled" || key === "value";
|
||||||
|
break;
|
||||||
|
case "select":
|
||||||
|
isProp = key === "disabled" || key === "value";
|
||||||
break;
|
break;
|
||||||
case "button":
|
case "button":
|
||||||
case "select":
|
|
||||||
case "optgroup":
|
case "optgroup":
|
||||||
isProp = key === "disabled";
|
isProp = key === "disabled";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (isProp) {
|
if (isProp) {
|
||||||
props.push(`${key}: _${val}`);
|
props.push(`${key}: ${val}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let classObj = "";
|
let classObj = "";
|
||||||
@@ -750,7 +779,7 @@ export class QWeb extends EventBus {
|
|||||||
name = '"' + name + '"';
|
name = '"' + name + '"';
|
||||||
}
|
}
|
||||||
attrs.push(`${name}: _${attID}`);
|
attrs.push(`${name}: _${attID}`);
|
||||||
handleProperties(name, attID);
|
handleProperties(name, `_${attID}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -762,7 +791,7 @@ export class QWeb extends EventBus {
|
|||||||
|
|
||||||
if (attName === "class") {
|
if (attName === "class") {
|
||||||
ctx.rootContext.shouldDefineUtils = true;
|
ctx.rootContext.shouldDefineUtils = true;
|
||||||
formattedValue = `utils.toObj(${formattedValue})`;
|
formattedValue = `utils.toClassObj(${formattedValue})`;
|
||||||
if (classObj) {
|
if (classObj) {
|
||||||
ctx.addLine(`Object.assign(${classObj}, ${formattedValue})`);
|
ctx.addLine(`Object.assign(${classObj}, ${formattedValue})`);
|
||||||
} else {
|
} else {
|
||||||
@@ -785,9 +814,14 @@ export class QWeb extends EventBus {
|
|||||||
const attrIndex = attrs.findIndex((att) => att.startsWith(attName + ":"));
|
const attrIndex = attrs.findIndex((att) => att.startsWith(attName + ":"));
|
||||||
attrs.splice(attrIndex, 1);
|
attrs.splice(attrIndex, 1);
|
||||||
}
|
}
|
||||||
ctx.addLine(`let _${attID} = ${formattedValue};`);
|
if (node.nodeName === "select" && attName === "value") {
|
||||||
attrs.push(`${attName}: _${attID}`);
|
attrs.push(`${attName}: ${v}`);
|
||||||
handleProperties(attName, attID);
|
handleProperties(attName, v);
|
||||||
|
} else {
|
||||||
|
ctx.addLine(`let _${attID} = ${formattedValue};`);
|
||||||
|
attrs.push(`${attName}: _${attID}`);
|
||||||
|
handleProperties(attName, "_" + attID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,7 +125,10 @@ export class Router {
|
|||||||
const initialParams = this.currentParams;
|
const initialParams = this.currentParams;
|
||||||
const result = await this.matchAndApplyRules(path);
|
const result = await this.matchAndApplyRules(path);
|
||||||
if (result.type === "match") {
|
if (result.type === "match") {
|
||||||
const finalPath = this.routeToPath(result.route, result.params);
|
let finalPath = this.routeToPath(result.route, result.params);
|
||||||
|
if (path.indexOf("?") > -1) {
|
||||||
|
finalPath += "?" + path.split("?")[1];
|
||||||
|
}
|
||||||
const isPopStateEvent = ev && ev instanceof PopStateEvent;
|
const isPopStateEvent = ev && ev instanceof PopStateEvent;
|
||||||
if (!isPopStateEvent) {
|
if (!isPopStateEvent) {
|
||||||
this.setUrlFromPath(finalPath);
|
this.setUrlFromPath(finalPath);
|
||||||
@@ -239,6 +242,9 @@ export class Router {
|
|||||||
if (route.path === "*") {
|
if (route.path === "*") {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
if (path.indexOf("?") > -1) {
|
||||||
|
path = path.split("?")[0];
|
||||||
|
}
|
||||||
if (path.startsWith("#")) {
|
if (path.startsWith("#")) {
|
||||||
path = path.slice(1);
|
path = path.slice(1);
|
||||||
}
|
}
|
||||||
|
|||||||
+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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
|
|||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let _7 = {'c':true};
|
let _7 = {'c':true};
|
||||||
Object.assign(_7, utils.toObj({d:scope['state'].d}))
|
Object.assign(_7, utils.toClassObj({d:scope['state'].d}))
|
||||||
let c8 = [], p8 = {key:8,class:_7};
|
let c8 = [], p8 = {key:8,class:_7};
|
||||||
let vn8 = h('span', p8, c8);
|
let vn8 = h('span', p8, c8);
|
||||||
return vn8;
|
return vn8;
|
||||||
@@ -112,7 +112,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
|
|||||||
// Component 'Child'
|
// Component 'Child'
|
||||||
const ref4 = \`child\`;
|
const ref4 = \`child\`;
|
||||||
let _5 = {'a':true};
|
let _5 = {'a':true};
|
||||||
Object.assign(_5, utils.toObj(scope['state'].b?'b':''))
|
Object.assign(_5, utils.toClassObj(scope['state'].b?'b':''))
|
||||||
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
|
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
|
||||||
let props2 = {};
|
let props2 = {};
|
||||||
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
||||||
@@ -148,7 +148,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
|
|||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let _7 = {'c':true};
|
let _7 = {'c':true};
|
||||||
Object.assign(_7, utils.toObj(scope['state'].d?'d':''))
|
Object.assign(_7, utils.toClassObj(scope['state'].d?'d':''))
|
||||||
let c8 = [], p8 = {key:8,class:_7};
|
let c8 = [], p8 = {key:8,class:_7};
|
||||||
let vn8 = h('span', p8, c8);
|
let vn8 = h('span', p8, c8);
|
||||||
return vn8;
|
return vn8;
|
||||||
|
|||||||
@@ -257,6 +257,45 @@ exports[`basic widget properties t-key on a component with t-if, and a sibling c
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`composition can switch between dynamic components without the need for a t-key 1`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"__template__3\\"
|
||||||
|
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 '{{state.child}}'
|
||||||
|
let componentKey2 = (scope['state'].child);
|
||||||
|
let k3 = '___' + componentKey2
|
||||||
|
let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : 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 W2 = false || 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[k3] = w2.__owl__.id;
|
||||||
|
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||||
|
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
||||||
|
c1.push(pvnode);
|
||||||
|
w2.__owl__.pvnode = pvnode;
|
||||||
|
}
|
||||||
|
w2.__owl__.parentLastFiberId = extra.fiber.id;
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`composition sub components with some state rendered in a loop 1`] = `
|
exports[`composition sub components with some state rendered in a loop 1`] = `
|
||||||
"function anonymous(context, extra
|
"function anonymous(context, extra
|
||||||
) {
|
) {
|
||||||
@@ -330,7 +369,9 @@ exports[`composition t-component with dynamic value 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
// Component '{{state.widget}}'
|
// Component '{{state.widget}}'
|
||||||
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
|
let componentKey2 = (scope['state'].widget);
|
||||||
|
let k3 = '___' + componentKey2
|
||||||
|
let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : false;
|
||||||
let props2 = {};
|
let props2 = {};
|
||||||
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
||||||
w2.destroy();
|
w2.destroy();
|
||||||
@@ -341,13 +382,12 @@ exports[`composition t-component with dynamic value 1`] = `
|
|||||||
let pvnode = w2.__owl__.pvnode;
|
let pvnode = w2.__owl__.pvnode;
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
} else {
|
} else {
|
||||||
let componentKey2 = (scope['state'].widget);
|
|
||||||
let W2 = false || context.constructor.components[componentKey2] || QWeb.components[componentKey2];
|
let W2 = false || context.constructor.components[componentKey2] || QWeb.components[componentKey2];
|
||||||
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
|
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
|
||||||
w2 = new W2(parent, props2);
|
w2 = new W2(parent, props2);
|
||||||
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
|
parent.__owl__.cmap[k3] = w2.__owl__.id;
|
||||||
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
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();}}});
|
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
w2.__owl__.pvnode = pvnode;
|
w2.__owl__.pvnode = pvnode;
|
||||||
}
|
}
|
||||||
@@ -368,7 +408,9 @@ exports[`composition t-component with dynamic value 2 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
// Component 'Widget{{state.widget}}'
|
// Component 'Widget{{state.widget}}'
|
||||||
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
|
let componentKey2 = \`Widget\${scope['state'].widget}\`;
|
||||||
|
let k3 = '___' + componentKey2
|
||||||
|
let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : false;
|
||||||
let props2 = {};
|
let props2 = {};
|
||||||
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
||||||
w2.destroy();
|
w2.destroy();
|
||||||
@@ -379,13 +421,12 @@ exports[`composition t-component with dynamic value 2 1`] = `
|
|||||||
let pvnode = w2.__owl__.pvnode;
|
let pvnode = w2.__owl__.pvnode;
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
} else {
|
} else {
|
||||||
let componentKey2 = \`Widget\${scope['state'].widget}\`;
|
|
||||||
let W2 = false || context.constructor.components[componentKey2] || QWeb.components[componentKey2];
|
let W2 = false || context.constructor.components[componentKey2] || QWeb.components[componentKey2];
|
||||||
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
|
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
|
||||||
w2 = new W2(parent, props2);
|
w2 = new W2(parent, props2);
|
||||||
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
|
parent.__owl__.cmap[k3] = w2.__owl__.id;
|
||||||
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
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();}}});
|
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
w2.__owl__.pvnode = pvnode;
|
w2.__owl__.pvnode = pvnode;
|
||||||
}
|
}
|
||||||
@@ -467,7 +508,7 @@ exports[`dynamic t-props basic use 1`] = `
|
|||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
// Component 'Child'
|
// Component 'Child'
|
||||||
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
|
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
|
||||||
let props2 = Object.assign({}, scope['some'].obj);
|
let props2 = Object.assign({}, scope['some'].obj, {});
|
||||||
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
||||||
w2.destroy();
|
w2.destroy();
|
||||||
w2 = false;
|
w2 = false;
|
||||||
@@ -518,7 +559,7 @@ exports[`other directives with t-component slot setted value (with t-set) not ac
|
|||||||
w3 = false;
|
w3 = false;
|
||||||
}
|
}
|
||||||
if (w3) {
|
if (w3) {
|
||||||
w3.__updateProps(props3, extra.fiber, Object.assign(Object.create(context), scope));
|
w3.__updateProps(props3, extra.fiber, utils.combine(context, scope));
|
||||||
let pvnode = w3.__owl__.pvnode;
|
let pvnode = w3.__owl__.pvnode;
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
} else {
|
} else {
|
||||||
@@ -528,7 +569,7 @@ exports[`other directives with t-component slot setted value (with t-set) not ac
|
|||||||
w3 = new W3(parent, props3);
|
w3 = new W3(parent, props3);
|
||||||
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
|
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
|
||||||
w3.__owl__.slotId = 1;
|
w3.__owl__.slotId = 1;
|
||||||
let fiber = w3.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
let fiber = w3.__prepare(extra.fiber, utils.combine(context, scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||||
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
w3.__owl__.pvnode = pvnode;
|
w3.__owl__.pvnode = pvnode;
|
||||||
@@ -579,7 +620,7 @@ exports[`other directives with t-component t-on expression captured in t-foreach
|
|||||||
c6.push(vn7);
|
c6.push(vn7);
|
||||||
const otherState_8 = scope['otherState'];
|
const otherState_8 = scope['otherState'];
|
||||||
const iter_8 = scope.iter;
|
const iter_8 = scope.iter;
|
||||||
p7.on['click'] = function (e) {if (context.__owl__.status === 5){return}otherState_8.vals.push(iter_8+'_'+iter_8)};
|
p7.on['click'] = function (e) {if (context.__owl__.status === 5){return}const res = (() => { return otherState_8.vals.push(iter_8+'_'+iter_8) })(); if (typeof res === 'function') { res(e) }};
|
||||||
c7.push({text: \`expr\`});
|
c7.push({text: \`expr\`});
|
||||||
utils.getScope(scope, 'iter').iter = scope.iter+1;
|
utils.getScope(scope, 'iter').iter = scope.iter+1;
|
||||||
}
|
}
|
||||||
@@ -630,7 +671,7 @@ exports[`other directives with t-component t-on expression in t-foreach 1`] = `
|
|||||||
c6.push(vn9);
|
c6.push(vn9);
|
||||||
const otherState_10 = scope['otherState'];
|
const otherState_10 = scope['otherState'];
|
||||||
const val_10 = scope['val'];
|
const val_10 = scope['val'];
|
||||||
p9.on['click'] = function (e) {if (context.__owl__.status === 5){return}otherState_10.vals.push(val_10)};
|
p9.on['click'] = function (e) {if (context.__owl__.status === 5){return}const res = (() => { return otherState_10.vals.push(val_10) })(); if (typeof res === 'function') { res(e) }};
|
||||||
c9.push({text: \`Expr\`});
|
c9.push({text: \`Expr\`});
|
||||||
}
|
}
|
||||||
scope = _origScope5;
|
scope = _origScope5;
|
||||||
@@ -684,7 +725,7 @@ exports[`other directives with t-component t-on expression in t-foreach with t-s
|
|||||||
const otherState_10 = scope['otherState'];
|
const otherState_10 = scope['otherState'];
|
||||||
const val_10 = scope['val'];
|
const val_10 = scope['val'];
|
||||||
const bossa_10 = scope.bossa;
|
const bossa_10 = scope.bossa;
|
||||||
p9.on['click'] = function (e) {if (context.__owl__.status === 5){return}otherState_10.vals.push(val_10+'_'+bossa_10)};
|
p9.on['click'] = function (e) {if (context.__owl__.status === 5){return}const res = (() => { return otherState_10.vals.push(val_10+'_'+bossa_10) })(); if (typeof res === 'function') { res(e) }};
|
||||||
c9.push({text: \`Expr\`});
|
c9.push({text: \`Expr\`});
|
||||||
}
|
}
|
||||||
scope = _origScope5;
|
scope = _origScope5;
|
||||||
@@ -1011,7 +1052,7 @@ exports[`other directives with t-component t-on with inline statement 1`] = `
|
|||||||
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
|
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
|
||||||
w3 = new W3(parent, props3);
|
w3 = new W3(parent, props3);
|
||||||
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
|
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
|
||||||
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}state_5.counter++});}});});
|
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}const res = (() => { return state_5.counter++ })(); if (typeof res === 'function') { res(e) }});}});});
|
||||||
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
w3.__owl__.pvnode = pvnode;
|
w3.__owl__.pvnode = pvnode;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ exports[`t-slot directive can define and call slots 1`] = `
|
|||||||
w2 = false;
|
w2 = false;
|
||||||
}
|
}
|
||||||
if (w2) {
|
if (w2) {
|
||||||
w2.__updateProps(props2, extra.fiber, Object.assign(Object.create(context), scope));
|
w2.__updateProps(props2, extra.fiber, utils.combine(context, scope));
|
||||||
let pvnode = w2.__owl__.pvnode;
|
let pvnode = w2.__owl__.pvnode;
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
} else {
|
} else {
|
||||||
@@ -29,7 +29,7 @@ exports[`t-slot directive can define and call slots 1`] = `
|
|||||||
w2 = new W2(parent, props2);
|
w2 = new W2(parent, props2);
|
||||||
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
|
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
|
||||||
w2.__owl__.slotId = 1;
|
w2.__owl__.slotId = 1;
|
||||||
let fiber = w2.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
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();}}});
|
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
w2.__owl__.pvnode = pvnode;
|
w2.__owl__.pvnode = pvnode;
|
||||||
@@ -44,23 +44,23 @@ exports[`t-slot directive can define and call slots 2`] = `
|
|||||||
) {
|
) {
|
||||||
// Template name: \\"Dialog\\"
|
// Template name: \\"Dialog\\"
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
|
let c8 = [], p8 = {key:8};
|
||||||
|
let vn8 = h('div', p8, c8);
|
||||||
let c9 = [], p9 = {key:9};
|
let c9 = [], p9 = {key:9};
|
||||||
let vn9 = h('div', p9, c9);
|
let vn9 = h('div', p9, c9);
|
||||||
let c10 = [], p10 = {key:10};
|
c8.push(vn9);
|
||||||
let vn10 = h('div', p10, c10);
|
const slot10 = this.constructor.slots[context.__owl__.slotId + '_' + 'header'];
|
||||||
c9.push(vn10);
|
if (slot10) {
|
||||||
const slot11 = this.constructor.slots[context.__owl__.slotId + '_' + 'header'];
|
slot10.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c9, parent: extra.parent || context}));
|
||||||
if (slot11) {
|
|
||||||
slot11.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c10, parent: extra.parent || context}));
|
|
||||||
}
|
}
|
||||||
let c12 = [], p12 = {key:12};
|
let c11 = [], p11 = {key:11};
|
||||||
let vn12 = h('div', p12, c12);
|
let vn11 = h('div', p11, c11);
|
||||||
c9.push(vn12);
|
c8.push(vn11);
|
||||||
const slot13 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer'];
|
const slot12 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer'];
|
||||||
if (slot13) {
|
if (slot12) {
|
||||||
slot13.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c12, parent: extra.parent || context}));
|
slot12.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c11, parent: extra.parent || context}));
|
||||||
}
|
}
|
||||||
return vn9;
|
return vn8;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ exports[`t-slot directive can define and call slots using old t-set keyword 1`]
|
|||||||
w2 = false;
|
w2 = false;
|
||||||
}
|
}
|
||||||
if (w2) {
|
if (w2) {
|
||||||
w2.__updateProps(props2, extra.fiber, Object.assign(Object.create(context), scope));
|
w2.__updateProps(props2, extra.fiber, utils.combine(context, scope));
|
||||||
let pvnode = w2.__owl__.pvnode;
|
let pvnode = w2.__owl__.pvnode;
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
} else {
|
} else {
|
||||||
@@ -121,7 +121,7 @@ exports[`t-slot directive can define and call slots using old t-set keyword 1`]
|
|||||||
w2 = new W2(parent, props2);
|
w2 = new W2(parent, props2);
|
||||||
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
|
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
|
||||||
w2.__owl__.slotId = 1;
|
w2.__owl__.slotId = 1;
|
||||||
let fiber = w2.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
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();}}});
|
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
w2.__owl__.pvnode = pvnode;
|
w2.__owl__.pvnode = pvnode;
|
||||||
@@ -136,23 +136,23 @@ exports[`t-slot directive can define and call slots using old t-set keyword 2`]
|
|||||||
) {
|
) {
|
||||||
// Template name: \\"__template__1\\"
|
// Template name: \\"__template__1\\"
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
|
let c8 = [], p8 = {key:8};
|
||||||
|
let vn8 = h('div', p8, c8);
|
||||||
let c9 = [], p9 = {key:9};
|
let c9 = [], p9 = {key:9};
|
||||||
let vn9 = h('div', p9, c9);
|
let vn9 = h('div', p9, c9);
|
||||||
let c10 = [], p10 = {key:10};
|
c8.push(vn9);
|
||||||
let vn10 = h('div', p10, c10);
|
const slot10 = this.constructor.slots[context.__owl__.slotId + '_' + 'header'];
|
||||||
c9.push(vn10);
|
if (slot10) {
|
||||||
const slot11 = this.constructor.slots[context.__owl__.slotId + '_' + 'header'];
|
slot10.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c9, parent: extra.parent || context}));
|
||||||
if (slot11) {
|
|
||||||
slot11.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c10, parent: extra.parent || context}));
|
|
||||||
}
|
}
|
||||||
let c12 = [], p12 = {key:12};
|
let c11 = [], p11 = {key:11};
|
||||||
let vn12 = h('div', p12, c12);
|
let vn11 = h('div', p11, c11);
|
||||||
c9.push(vn12);
|
c8.push(vn11);
|
||||||
const slot13 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer'];
|
const slot12 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer'];
|
||||||
if (slot13) {
|
if (slot12) {
|
||||||
slot13.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c12, parent: extra.parent || context}));
|
slot12.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c11, parent: extra.parent || context}));
|
||||||
}
|
}
|
||||||
return vn9;
|
return vn8;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -215,6 +215,45 @@ exports[`t-slot directive dafault slots can define a default content 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-slot directive default slot next to named slot, with default content 1`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"__template__2\\"
|
||||||
|
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 'Dialog'
|
||||||
|
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 = \`Dialog\`;
|
||||||
|
let W2 = scope['Dialog'] || 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[`t-slot directive default slot work with text nodes 1`] = `
|
exports[`t-slot directive default slot work with text nodes 1`] = `
|
||||||
"function anonymous(context, extra
|
"function anonymous(context, extra
|
||||||
) {
|
) {
|
||||||
@@ -233,15 +272,15 @@ exports[`t-slot directive dynamic t-slot call 1`] = `
|
|||||||
let utils = this.constructor.utils;
|
let utils = this.constructor.utils;
|
||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c10 = [], p10 = {key:10,on:{}};
|
let c9 = [], p9 = {key:9,on:{}};
|
||||||
let vn10 = h('button', p10, c10);
|
let vn9 = h('button', p9, c9);
|
||||||
extra.handlers['click__11__'] = extra.handlers['click__11__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['toggle'](e);};
|
extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['toggle'](e);};
|
||||||
p10.on['click'] = extra.handlers['click__11__'];
|
p9.on['click'] = extra.handlers['click__10__'];
|
||||||
const slot12 = this.constructor.slots[context.__owl__.slotId + '_' + (scope['current'].slot)];
|
const slot11 = this.constructor.slots[context.__owl__.slotId + '_' + (scope['current'].slot)];
|
||||||
if (slot12) {
|
if (slot11) {
|
||||||
slot12.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c10, parent: extra.parent || context}));
|
slot11.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c9, parent: extra.parent || context}));
|
||||||
}
|
}
|
||||||
return vn10;
|
return vn9;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -402,7 +441,7 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
|
|||||||
w8 = false;
|
w8 = false;
|
||||||
}
|
}
|
||||||
if (w8) {
|
if (w8) {
|
||||||
w8.__updateProps(props8, extra.fiber, Object.assign(Object.create(context), scope));
|
w8.__updateProps(props8, extra.fiber, utils.combine(context, scope));
|
||||||
let pvnode = w8.__owl__.pvnode;
|
let pvnode = w8.__owl__.pvnode;
|
||||||
c7.push(pvnode);
|
c7.push(pvnode);
|
||||||
} else {
|
} else {
|
||||||
@@ -412,7 +451,7 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
|
|||||||
w8 = new W8(parent, props8);
|
w8 = new W8(parent, props8);
|
||||||
parent.__owl__.cmap[k9] = w8.__owl__.id;
|
parent.__owl__.cmap[k9] = w8.__owl__.id;
|
||||||
w8.__owl__.slotId = 1;
|
w8.__owl__.slotId = 1;
|
||||||
let fiber = w8.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
let fiber = w8.__prepare(extra.fiber, utils.combine(context, scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||||
let pvnode = h('dummy', {key: k9, hook: {remove() {},destroy(vn) {w8.destroy();}}});
|
let pvnode = h('dummy', {key: k9, hook: {remove() {},destroy(vn) {w8.destroy();}}});
|
||||||
c7.push(pvnode);
|
c7.push(pvnode);
|
||||||
w8.__owl__.pvnode = pvnode;
|
w8.__owl__.pvnode = pvnode;
|
||||||
@@ -501,7 +540,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
|
|||||||
w8 = false;
|
w8 = false;
|
||||||
}
|
}
|
||||||
if (w8) {
|
if (w8) {
|
||||||
w8.__updateProps(props8, extra.fiber, Object.assign(Object.create(context), scope));
|
w8.__updateProps(props8, extra.fiber, utils.combine(context, scope));
|
||||||
let pvnode = w8.__owl__.pvnode;
|
let pvnode = w8.__owl__.pvnode;
|
||||||
c7.push(pvnode);
|
c7.push(pvnode);
|
||||||
} else {
|
} else {
|
||||||
@@ -511,7 +550,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
|
|||||||
w8 = new W8(parent, props8);
|
w8 = new W8(parent, props8);
|
||||||
parent.__owl__.cmap[k9] = w8.__owl__.id;
|
parent.__owl__.cmap[k9] = w8.__owl__.id;
|
||||||
w8.__owl__.slotId = 1;
|
w8.__owl__.slotId = 1;
|
||||||
let fiber = w8.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
let fiber = w8.__prepare(extra.fiber, utils.combine(context, scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||||
let pvnode = h('dummy', {key: k9, hook: {remove() {},destroy(vn) {w8.destroy();}}});
|
let pvnode = h('dummy', {key: k9, hook: {remove() {},destroy(vn) {w8.destroy();}}});
|
||||||
c7.push(pvnode);
|
c7.push(pvnode);
|
||||||
w8.__owl__.pvnode = pvnode;
|
w8.__owl__.pvnode = pvnode;
|
||||||
@@ -558,7 +597,7 @@ exports[`t-slot directive slots are rendered with proper context, part 4 1`] = `
|
|||||||
w2 = false;
|
w2 = false;
|
||||||
}
|
}
|
||||||
if (w2) {
|
if (w2) {
|
||||||
w2.__updateProps(props2, extra.fiber, Object.assign(Object.create(context), scope));
|
w2.__updateProps(props2, extra.fiber, utils.combine(context, scope));
|
||||||
let pvnode = w2.__owl__.pvnode;
|
let pvnode = w2.__owl__.pvnode;
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
} else {
|
} else {
|
||||||
@@ -568,7 +607,7 @@ exports[`t-slot directive slots are rendered with proper context, part 4 1`] = `
|
|||||||
w2 = new W2(parent, props2);
|
w2 = new W2(parent, props2);
|
||||||
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
|
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
|
||||||
w2.__owl__.slotId = 1;
|
w2.__owl__.slotId = 1;
|
||||||
let fiber = w2.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
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();}}});
|
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
w2.__owl__.pvnode = pvnode;
|
w2.__owl__.pvnode = pvnode;
|
||||||
@@ -593,6 +632,94 @@ exports[`t-slot directive slots are rendered with proper context, part 4 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-slot directive slots in t-foreach in t-foreach 1`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"__template__2\\"
|
||||||
|
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 = scope['tree'];
|
||||||
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
|
let _3 = _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.node1_first = i1 === 0
|
||||||
|
scope.node1_last = i1 === _length3 - 1
|
||||||
|
scope.node1_index = i1
|
||||||
|
scope.node1 = _3[i1]
|
||||||
|
scope.node1_value = _4[i1]
|
||||||
|
let key1 = scope['node1'].key;
|
||||||
|
let c6 = [], p6 = {key:\`\${key1}_6\`};
|
||||||
|
let vn6 = h('div', p6, c6);
|
||||||
|
c1.push(vn6);
|
||||||
|
let _7 = scope['node1'].value;
|
||||||
|
if (_7 != null) {
|
||||||
|
c6.push({text: _7});
|
||||||
|
}
|
||||||
|
let c8 = [], p8 = {key:\`\${key1}_8\`};
|
||||||
|
let vn8 = h('ul', p8, c8);
|
||||||
|
c1.push(vn8);
|
||||||
|
let _9 = scope['node1'].nodes;
|
||||||
|
if (!_9) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
|
let _10 = _11 = _9;
|
||||||
|
if (!(_9 instanceof Array)) {
|
||||||
|
_10 = Object.keys(_9);
|
||||||
|
_11 = Object.values(_9);
|
||||||
|
}
|
||||||
|
let _length10 = _10.length;
|
||||||
|
let _origScope12 = scope;
|
||||||
|
scope = Object.create(scope);
|
||||||
|
for (let i2 = 0; i2 < _length10; i2++) {
|
||||||
|
scope.node2_first = i2 === 0
|
||||||
|
scope.node2_last = i2 === _length10 - 1
|
||||||
|
scope.node2_index = i2
|
||||||
|
scope.node2 = _10[i2]
|
||||||
|
scope.node2_value = _11[i2]
|
||||||
|
let key2 = scope['node2'].key;
|
||||||
|
// Component 'Child'
|
||||||
|
let k14 = \`__14__\${key1}__\${key2}__\`;
|
||||||
|
let w13 = k14 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k14]] : false;
|
||||||
|
let props13 = {};
|
||||||
|
if (w13 && w13.__owl__.currentFiber && !w13.__owl__.vnode) {
|
||||||
|
w13.destroy();
|
||||||
|
w13 = false;
|
||||||
|
}
|
||||||
|
if (w13) {
|
||||||
|
w13.__updateProps(props13, extra.fiber, utils.combine(context, scope));
|
||||||
|
let pvnode = w13.__owl__.pvnode;
|
||||||
|
c8.push(pvnode);
|
||||||
|
} else {
|
||||||
|
let componentKey13 = \`Child\`;
|
||||||
|
let W13 = scope['Child'] || context.constructor.components[componentKey13] || QWeb.components[componentKey13];
|
||||||
|
if (!W13) {throw new Error('Cannot find the definition of component \\"' + componentKey13 + '\\"')}
|
||||||
|
w13 = new W13(parent, props13);
|
||||||
|
parent.__owl__.cmap[k14] = w13.__owl__.id;
|
||||||
|
w13.__owl__.slotId = 1;
|
||||||
|
let fiber = w13.__prepare(extra.fiber, utils.combine(context, scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||||
|
let pvnode = h('dummy', {key: k14, hook: {remove() {},destroy(vn) {w13.destroy();}}});
|
||||||
|
c8.push(pvnode);
|
||||||
|
w13.__owl__.pvnode = pvnode;
|
||||||
|
}
|
||||||
|
w13.__owl__.parentLastFiberId = extra.fiber.id;
|
||||||
|
}
|
||||||
|
scope = _origScope12;
|
||||||
|
}
|
||||||
|
scope = _origScope5;
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-slot directive t-set t-value in a slot 1`] = `
|
exports[`t-slot directive t-set t-value in a slot 1`] = `
|
||||||
"function anonymous(context, extra
|
"function anonymous(context, extra
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -149,6 +149,24 @@ describe("basic widget properties", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div>1<button>Inc</button></div>");
|
expect(fixture.innerHTML).toBe("<div>1<button>Inc</button></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("support for callable expression in event handler", async () => {
|
||||||
|
class Counter extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div><t t-esc="state.value"/><input type="text" t-on-input="obj.onInput"/></div>`;
|
||||||
|
state = useState({ value: "" });
|
||||||
|
obj = { onInput: (ev) => (this.state.value = ev.target.value) };
|
||||||
|
}
|
||||||
|
|
||||||
|
const counter = await mount(Counter, { target: fixture });
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe(`<div><input type="text"></div>`);
|
||||||
|
const input = (<HTMLElement>counter.el).getElementsByTagName("input")[0];
|
||||||
|
input.value = "test";
|
||||||
|
input.dispatchEvent(new Event("input"));
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe(`<div>test<input type="text"></div>`);
|
||||||
|
});
|
||||||
|
|
||||||
test("can handle empty props", async () => {
|
test("can handle empty props", async () => {
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`<span><t t-esc="props.val"/></span>`;
|
static template = xml`<span><t t-esc="props.val"/></span>`;
|
||||||
@@ -1228,6 +1246,30 @@ describe("composition", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div>child b</div>");
|
expect(fixture.innerHTML).toBe("<div>child b</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("can switch between dynamic components without the need for a t-key", async () => {
|
||||||
|
class A extends Component {
|
||||||
|
static template = xml`<span>child a</span>`;
|
||||||
|
}
|
||||||
|
class B extends Component {
|
||||||
|
static template = xml`<span>child b</span>`;
|
||||||
|
}
|
||||||
|
class App extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<t t-component="{{state.child}}"/>
|
||||||
|
</div>`;
|
||||||
|
static components = { A, B };
|
||||||
|
|
||||||
|
state = useState({ child: "A" });
|
||||||
|
}
|
||||||
|
const app = await mount(App, { target: fixture });
|
||||||
|
expect(fixture.innerHTML).toBe("<div><span>child a</span></div>");
|
||||||
|
app.state.child = "B";
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe("<div><span>child b</span></div>");
|
||||||
|
expect(QWeb.TEMPLATES[App.template].fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
test("don't fallback to global registry if widget defined locally", async () => {
|
test("don't fallback to global registry if widget defined locally", async () => {
|
||||||
QWeb.registerComponent("WidgetB", WidgetB); // should not use this widget
|
QWeb.registerComponent("WidgetB", WidgetB); // should not use this widget
|
||||||
env.qweb.addTemplate("ParentWidget", `<div><t t-component="WidgetB"/></div>`);
|
env.qweb.addTemplate("ParentWidget", `<div><t t-component="WidgetB"/></div>`);
|
||||||
@@ -4022,6 +4064,30 @@ describe("dynamic t-props", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div><span>3</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>3</span></div>");
|
||||||
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("t-props with props", async () => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<div />`;
|
||||||
|
setup() {
|
||||||
|
expect(this.props).toEqual({ a: 1, b: 2, c: "c" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<Child t-props="props" a="1" b="2" />
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
static components = { Child };
|
||||||
|
|
||||||
|
props = { a: "a", c: "c" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const widget = new Parent();
|
||||||
|
await widget.mount(fixture);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("support svg components", () => {
|
describe("support svg components", () => {
|
||||||
|
|||||||
@@ -586,4 +586,25 @@ describe("component error handling (catchError)", () => {
|
|||||||
await mount(Parent, { target: fixture });
|
await mount(Parent, { target: fixture });
|
||||||
expect(fixture.innerHTML).toBe("<div>Error</div>");
|
expect(fixture.innerHTML).toBe("<div>Error</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("errors in mounted and in willUnmount", async () => {
|
||||||
|
expect.assertions(1);
|
||||||
|
class Example extends Component {
|
||||||
|
static template = xml`<div/>`;
|
||||||
|
val;
|
||||||
|
mounted() {
|
||||||
|
throw new Error("Error in mounted");
|
||||||
|
this.val = { foo: "bar" };
|
||||||
|
}
|
||||||
|
willUnmount() {
|
||||||
|
console.log(this.val.foo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mount(Example, { target: fixture });
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe("Error in mounted");
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -325,6 +325,70 @@ describe("t-slot directive", () => {
|
|||||||
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("slots in t-foreach in t-foreach", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div><t t-slot="default" /></div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<t t-foreach="tree" t-as="node1" t-key="node1.key">
|
||||||
|
<div t-esc="node1.value" />
|
||||||
|
<ul>
|
||||||
|
<t t-foreach="node1.nodes" t-as="node2" t-key="node2.key">
|
||||||
|
<Child>
|
||||||
|
<li t-esc="node1.value" />
|
||||||
|
</Child>
|
||||||
|
</t>
|
||||||
|
</ul>
|
||||||
|
</t>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
static components = { Child };
|
||||||
|
|
||||||
|
tree = [
|
||||||
|
{
|
||||||
|
key: "a",
|
||||||
|
value: "A",
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
key: "1",
|
||||||
|
value: "A-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "2",
|
||||||
|
value: "A-2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "b",
|
||||||
|
value: "B",
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
key: "1",
|
||||||
|
value: "B-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "2",
|
||||||
|
value: "B-2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(App, { target: fixture });
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div>A</div><ul><div><li>A</li></div><div><li>A</li></div></ul><div>B</div><ul><div><li>B</li></div><div><li>B</li></div></ul></div>"
|
||||||
|
);
|
||||||
|
expect(env.qweb.templates[App.template].fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
test("refs are properly bound in slots", async () => {
|
test("refs are properly bound in slots", async () => {
|
||||||
class Dialog extends Component {
|
class Dialog extends Component {
|
||||||
static template = xml`<span><t t-slot="footer"/></span>`;
|
static template = xml`<span><t t-slot="footer"/></span>`;
|
||||||
@@ -403,6 +467,48 @@ describe("t-slot directive", () => {
|
|||||||
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("default slot next to named slot, with default content", async () => {
|
||||||
|
class Dialog extends Component {
|
||||||
|
// We're using 2 slots here: a "default" one and a "footer",
|
||||||
|
// both having default children nodes.
|
||||||
|
static template = xml`
|
||||||
|
<div class="Dialog">
|
||||||
|
<div class="content">
|
||||||
|
<t t-slot="default">
|
||||||
|
Default content
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<t t-slot="footer">
|
||||||
|
Default footer
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
// Here we're trying to assign the "footer" slot with some content
|
||||||
|
static components = { Dialog };
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<Dialog>
|
||||||
|
<t t-set-slot="footer">
|
||||||
|
Overridden footer
|
||||||
|
</t>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(App, { target: fixture });
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><div class="Dialog"><div class="content"> Default content </div><div class="footer"> Overridden footer </div></div></div>'
|
||||||
|
);
|
||||||
|
expect(QWeb.TEMPLATES[App.template].fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
test("multiple roots are allowed in a named slot", async () => {
|
test("multiple roots are allowed in a named slot", async () => {
|
||||||
env.qweb.addTemplates(`
|
env.qweb.addTemplates(`
|
||||||
<templates>
|
<templates>
|
||||||
@@ -1151,4 +1257,50 @@ 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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ exports[`attributes class and t-att-class should combine together 1`] = `
|
|||||||
let utils = this.constructor.utils;
|
let utils = this.constructor.utils;
|
||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let _1 = utils.toObj(scope['value']);
|
let _1 = utils.toClassObj(scope['value']);
|
||||||
Object.assign(_1, {'hello':true})
|
Object.assign(_1, {'hello':true})
|
||||||
let c3 = [], p3 = {key:3,class:_1};
|
let c3 = [], p3 = {key:3,class:_1};
|
||||||
let vn3 = h('div', p3, c3);
|
let vn3 = h('div', p3, c3);
|
||||||
@@ -74,7 +74,7 @@ exports[`attributes dynamic class attribute 1`] = `
|
|||||||
let utils = this.constructor.utils;
|
let utils = this.constructor.utils;
|
||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let _1 = utils.toObj(scope['c']);
|
let _1 = utils.toClassObj(scope['c']);
|
||||||
let c2 = [], p2 = {key:2,class:_1};
|
let c2 = [], p2 = {key:2,class:_1};
|
||||||
let vn2 = h('div', p2, c2);
|
let vn2 = h('div', p2, c2);
|
||||||
return vn2;
|
return vn2;
|
||||||
@@ -88,7 +88,7 @@ exports[`attributes dynamic empty class attribute 1`] = `
|
|||||||
let utils = this.constructor.utils;
|
let utils = this.constructor.utils;
|
||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let _1 = utils.toObj(scope['c']);
|
let _1 = utils.toClassObj(scope['c']);
|
||||||
let c2 = [], p2 = {key:2,class:_1};
|
let c2 = [], p2 = {key:2,class:_1};
|
||||||
let vn2 = h('div', p2, c2);
|
let vn2 = h('div', p2, c2);
|
||||||
return vn2;
|
return vn2;
|
||||||
@@ -195,7 +195,7 @@ exports[`attributes from object variables set previously 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
scope.o = {a:'b'};
|
scope.o = {a:'b'};
|
||||||
let _2 = utils.toObj(scope.o.a);
|
let _2 = utils.toClassObj(scope.o.a);
|
||||||
let c3 = [], p3 = {key:3,class:_2};
|
let c3 = [], p3 = {key:3,class:_2};
|
||||||
let vn3 = h('span', p3, c3);
|
let vn3 = h('span', p3, c3);
|
||||||
c1.push(vn3);
|
c1.push(vn3);
|
||||||
@@ -213,7 +213,7 @@ exports[`attributes from variables set previously 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
let vn1 = h('div', p1, c1);
|
let vn1 = h('div', p1, c1);
|
||||||
scope.abc = 'def';
|
scope.abc = 'def';
|
||||||
let _2 = utils.toObj(scope.abc);
|
let _2 = utils.toClassObj(scope.abc);
|
||||||
let c3 = [], p3 = {key:3,class:_2};
|
let c3 = [], p3 = {key:3,class:_2};
|
||||||
let vn3 = h('span', p3, c3);
|
let vn3 = h('span', p3, c3);
|
||||||
c1.push(vn3);
|
c1.push(vn3);
|
||||||
@@ -288,7 +288,7 @@ exports[`attributes t-att-class and class should combine together 1`] = `
|
|||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let _2 = {'hello':true};
|
let _2 = {'hello':true};
|
||||||
Object.assign(_2, utils.toObj(scope['value']))
|
Object.assign(_2, utils.toClassObj(scope['value']))
|
||||||
let c3 = [], p3 = {key:3,class:_2};
|
let c3 = [], p3 = {key:3,class:_2};
|
||||||
let vn3 = h('div', p3, c3);
|
let vn3 = h('div', p3, c3);
|
||||||
return vn3;
|
return vn3;
|
||||||
@@ -303,7 +303,7 @@ exports[`attributes t-att-class with object 1`] = `
|
|||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let _2 = {'static':true};
|
let _2 = {'static':true};
|
||||||
Object.assign(_2, utils.toObj({a:scope['b'],c:scope['d'],e:scope['f']}))
|
Object.assign(_2, utils.toClassObj({a:scope['b'],c:scope['d'],e:scope['f']}))
|
||||||
let c3 = [], p3 = {key:3,class:_2};
|
let c3 = [], p3 = {key:3,class:_2};
|
||||||
let vn3 = h('div', p3, c3);
|
let vn3 = h('div', p3, c3);
|
||||||
return vn3;
|
return vn3;
|
||||||
@@ -1073,6 +1073,39 @@ exports[`special cases for some specific html attributes/properties input type=
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`special cases for some specific html attributes/properties select with t-att-value 1`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"test\\"
|
||||||
|
let scope = Object.create(context);
|
||||||
|
let h = this.h;
|
||||||
|
let expr1 = scope['value'];
|
||||||
|
let c3 = [], p3 = {key:3,attrs:{value: expr1},props:{value: expr1}};
|
||||||
|
let vn3 = h('select', p3, c3);
|
||||||
|
p3.hook = {
|
||||||
|
create: (_, n) => {
|
||||||
|
n.elm.value=expr1;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let _4 = 'potato';
|
||||||
|
let c5 = [], p5 = {key:5,attrs:{value: _4}};
|
||||||
|
let vn5 = h('option', p5, c5);
|
||||||
|
c3.push(vn5);
|
||||||
|
c5.push({text: \`Potato\`});
|
||||||
|
let _6 = 'tomato';
|
||||||
|
let c7 = [], p7 = {key:7,attrs:{value: _6}};
|
||||||
|
let vn7 = h('option', p7, c7);
|
||||||
|
c3.push(vn7);
|
||||||
|
c7.push({text: \`Tomato\`});
|
||||||
|
let _8 = 'onion';
|
||||||
|
let c9 = [], p9 = {key:9,attrs:{value: _8}};
|
||||||
|
let vn9 = h('option', p9, c9);
|
||||||
|
c3.push(vn9);
|
||||||
|
c9.push({text: \`Onion\`});
|
||||||
|
return vn3;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`special cases for some specific html attributes/properties various boolean html attributes 1`] = `
|
exports[`special cases for some specific html attributes/properties various boolean html attributes 1`] = `
|
||||||
"function anonymous(context, extra
|
"function anonymous(context, extra
|
||||||
) {
|
) {
|
||||||
@@ -1203,6 +1236,55 @@ exports[`static templates empty div 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`static templates inline template string in t-esc 1`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"test\\"
|
||||||
|
let scope = Object.create(context);
|
||||||
|
let result;
|
||||||
|
let h = this.h;
|
||||||
|
let _1 = \`text\`;
|
||||||
|
if (_1 != null) {
|
||||||
|
let vn2 = {text: _1};
|
||||||
|
result = vn2
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`static templates inline template string with content in t-esc 1`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"test\\"
|
||||||
|
let scope = Object.create(context);
|
||||||
|
let result;
|
||||||
|
let h = this.h;
|
||||||
|
scope.v = 1;
|
||||||
|
let _1 = \`text\${scope.v}\`;
|
||||||
|
if (_1 != null) {
|
||||||
|
let vn2 = {text: _1};
|
||||||
|
result = vn2
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`static templates inline template string with variable in context 1`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"test\\"
|
||||||
|
let scope = Object.create(context);
|
||||||
|
let result;
|
||||||
|
let h = this.h;
|
||||||
|
let _1 = \`text \${scope['v']}\`;
|
||||||
|
if (_1 != null) {
|
||||||
|
let vn2 = {text: _1};
|
||||||
|
result = vn2
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`static templates properly handle comments 1`] = `
|
exports[`static templates properly handle comments 1`] = `
|
||||||
"function anonymous(context, extra
|
"function anonymous(context, extra
|
||||||
) {
|
) {
|
||||||
@@ -2950,7 +3032,7 @@ exports[`t-on t-on with empty handler (only modifiers) 1`] = `
|
|||||||
let c2 = [], p2 = {key:2,on:{}};
|
let c2 = [], p2 = {key:2,on:{}};
|
||||||
let vn2 = h('button', p2, c2);
|
let vn2 = h('button', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
p2.on['click'] = function (e) {if (context.__owl__.status === 5){return}e.preventDefault();};
|
p2.on['click'] = function (e) {if (context.__owl__.status === 5){return}e.preventDefault();const res = (() => { return })(); if (typeof res === 'function') { res(e) }};
|
||||||
c2.push({text: \`Button\`});
|
c2.push({text: \`Button\`});
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -2965,7 +3047,7 @@ exports[`t-on t-on with inline statement (function call) 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'];
|
||||||
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.incrementCounter(2)};
|
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}const res = (() => { return state_2.incrementCounter(2) })(); if (typeof res === 'function') { res(e) }};
|
||||||
c1.push({text: \`Click\`});
|
c1.push({text: \`Click\`});
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -2980,7 +3062,7 @@ exports[`t-on t-on with inline statement 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'];
|
||||||
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.counter++};
|
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}const res = (() => { return state_2.counter++ })(); if (typeof res === 'function') { res(e) }};
|
||||||
c1.push({text: \`Click\`});
|
c1.push({text: \`Click\`});
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -2995,7 +3077,7 @@ exports[`t-on t-on with inline statement, part 2 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'];
|
||||||
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.flag=!state_2.flag};
|
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}const res = (() => { return state_2.flag=!state_2.flag })(); if (typeof res === 'function') { res(e) }};
|
||||||
c1.push({text: \`Toggle\`});
|
c1.push({text: \`Toggle\`});
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -3011,7 +3093,7 @@ exports[`t-on t-on with inline statement, part 3 1`] = `
|
|||||||
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'];
|
const someFunction_2 = scope['someFunction'];
|
||||||
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.n=someFunction_2(3)};
|
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;
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`qweb t-att t-att-class with multiple classes 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']});
|
||||||
|
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 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']});
|
||||||
|
let c4 = [], p4 = {key:4,class:_3};
|
||||||
|
let vn4 = h('div', p4, c4);
|
||||||
|
return vn4;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
@@ -31,6 +31,21 @@ describe("static templates", () => {
|
|||||||
expect(renderToString(qweb, "test", { text: "hello vdom" })).toBe("hello vdom");
|
expect(renderToString(qweb, "test", { text: "hello vdom" })).toBe("hello vdom");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("inline template string in t-esc", () => {
|
||||||
|
qweb.addTemplate("test", '<t><t t-esc="`text`"/></t>');
|
||||||
|
expect(renderToString(qweb, "test")).toBe("text");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("inline template string with content in t-esc", () => {
|
||||||
|
qweb.addTemplate("test", '<t><t t-set="v" t-value="1"/><t t-esc="`text${v}`"/></t>');
|
||||||
|
expect(renderToString(qweb, "test")).toBe("text1");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("inline template string with variable in context", () => {
|
||||||
|
qweb.addTemplate("test", '<t><t t-esc="`text ${v}`"/></t>');
|
||||||
|
expect(renderToString(qweb, "test", { v: "from context" })).toBe("text from context");
|
||||||
|
});
|
||||||
|
|
||||||
test("simple string, with some dynamic value", () => {
|
test("simple string, with some dynamic value", () => {
|
||||||
qweb.addTemplate("test", '<t>hello <t t-esc="text"/></t>');
|
qweb.addTemplate("test", '<t>hello <t t-esc="text"/></t>');
|
||||||
expect(renderToString(qweb, "test", { text: "vdom" })).toBe("hello vdom");
|
expect(renderToString(qweb, "test", { text: "vdom" })).toBe("hello vdom");
|
||||||
@@ -1931,6 +1946,49 @@ describe("special cases for some specific html attributes/properties", () => {
|
|||||||
let elm = vnode2.elm as HTMLInputElement;
|
let elm = vnode2.elm as HTMLInputElement;
|
||||||
expect(elm.indeterminate).toBe(true);
|
expect(elm.indeterminate).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("textarea with t-att-value", () => {
|
||||||
|
// render input with initial value
|
||||||
|
qweb.addTemplate("test", `<textarea t-att-value="v"/>`);
|
||||||
|
const vnode1 = qweb.render("test", { v: "zucchini" });
|
||||||
|
const vnode2 = patch(document.createElement("textarea"), vnode1);
|
||||||
|
let elm = vnode2.elm as HTMLInputElement;
|
||||||
|
expect(elm.value).toBe("zucchini");
|
||||||
|
|
||||||
|
// change value manually in textarea, to simulate user textarea
|
||||||
|
elm.value = "tomato";
|
||||||
|
expect(elm.value).toBe("tomato");
|
||||||
|
|
||||||
|
// rerender with a different value, and patch actual dom, to check that
|
||||||
|
// textarea value was properly reset by owl
|
||||||
|
const vnode3 = qweb.render("test", { v: "potato" });
|
||||||
|
patch(vnode2, vnode3);
|
||||||
|
expect(elm.value).toBe("potato");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("select with t-att-value", () => {
|
||||||
|
const template = `
|
||||||
|
<select t-att-value="value">
|
||||||
|
<option value="potato">Potato</option>
|
||||||
|
<option value="tomato">Tomato</option>
|
||||||
|
<option value="onion">Onion</option>
|
||||||
|
</select>`;
|
||||||
|
qweb.addTemplate("test", template);
|
||||||
|
const vnode1 = qweb.render("test", { value: "tomato" });
|
||||||
|
const vnode2 = patch(document.createElement("select"), vnode1);
|
||||||
|
let elm = vnode2.elm as HTMLSelectElement;
|
||||||
|
expect(elm.value).toBe("tomato");
|
||||||
|
|
||||||
|
elm.value = "potato";
|
||||||
|
expect(elm.value).toBe("potato");
|
||||||
|
|
||||||
|
// rerender with a different value, and patch actual dom, to check that
|
||||||
|
// select value was properly reset by owl
|
||||||
|
const vnode3 = qweb.render("test", { value: "onion" });
|
||||||
|
patch(vnode2, vnode3);
|
||||||
|
expect(elm.value).toBe("onion");
|
||||||
|
expect(qweb.templates.test.fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("whitespace handling", () => {
|
describe("whitespace handling", () => {
|
||||||
|
|||||||
@@ -200,4 +200,27 @@ describe("expression evaluation", () => {
|
|||||||
test("works with builtin properties", () => {
|
test("works with builtin properties", () => {
|
||||||
expect(compileExpr("state.constructor.name", {})).toBe("scope['state'].constructor.name");
|
expect(compileExpr("state.constructor.name", {})).toBe("scope['state'].constructor.name");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("works with shortcut object key description", () => {
|
||||||
|
expect(compileExpr("{a}", {})).toBe("{a:scope['a']}");
|
||||||
|
expect(compileExpr("{a,b}", {})).toBe("{a:scope['a'],b:scope['b']}");
|
||||||
|
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", () => {
|
||||||
|
expect(compileExpr("`hey`", {})).toBe("`hey`");
|
||||||
|
expect(compileExpr("`hey ${you}`", {})).toBe("`hey ${scope['you']}`");
|
||||||
|
expect(compileExpr("`hey ${1 + 2}`", {})).toBe("`hey ${1+2}`");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { QWeb } from "../../src/qweb/index";
|
||||||
|
import { renderToString } from "../helpers";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Setup and helpers
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function render(template, context = {}) {
|
||||||
|
const qweb = new QWeb();
|
||||||
|
qweb.addTemplate("test", template);
|
||||||
|
return renderToString(qweb, "test", context);
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Tests
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe("qweb t-att", () => {
|
||||||
|
test("t-att-class with multiple classes", () => {
|
||||||
|
expect(render(`<div t-att-class="{'a b c': value}" />`, { value: true })).toBe(
|
||||||
|
'<div class="a b c"></div>'
|
||||||
|
);
|
||||||
|
expect(render(`<div t-att-class="{['a b c']: value}" />`, { value: true })).toBe(
|
||||||
|
'<div class="a b c"></div>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,7 +7,7 @@ exports[`Link component can render simple cases 1`] = `
|
|||||||
let utils = this.constructor.utils;
|
let utils = this.constructor.utils;
|
||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let _5 = utils.toObj({'router-link-active':scope['isActive']});
|
let _5 = utils.toClassObj({'router-link-active':scope['isActive']});
|
||||||
let _6 = scope['href'];
|
let _6 = scope['href'];
|
||||||
let c7 = [], p7 = {key:7,attrs:{href: _6},class:_5,on:{}};
|
let c7 = [], p7 = {key:7,attrs:{href: _6},class:_5,on:{}};
|
||||||
let vn7 = h('a', p7, c7);
|
let vn7 = h('a', p7, c7);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ exports[`RouteComponent can render simple cases 1`] = `
|
|||||||
let w4 = k5 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k5]] : false;
|
let w4 = k5 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k5]] : false;
|
||||||
let vn6 = {};
|
let vn6 = {};
|
||||||
result = vn6;
|
result = vn6;
|
||||||
let props4 = Object.assign({}, scope['env'].router.currentParams);
|
let props4 = Object.assign({}, scope['env'].router.currentParams, {});
|
||||||
if (w4 && w4.__owl__.currentFiber && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.currentFiber && !w4.__owl__.vnode) {
|
||||||
w4.destroy();
|
w4.destroy();
|
||||||
w4 = false;
|
w4 = false;
|
||||||
|
|||||||
@@ -60,6 +60,13 @@ describe("router miscellaneous", () => {
|
|||||||
await router.navigate({ to: "users", params: { id: 3 } });
|
await router.navigate({ to: "users", params: { id: 3 } });
|
||||||
expect(window.location.href).toBe("http://localhost/test.html#/users/3");
|
expect(window.location.href).toBe("http://localhost/test.html#/users/3");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("navigate using path and query string should preserve query string", async () => {
|
||||||
|
router = new TestRouter(env, [{ name: "users", path: "/users/{{id}}" }]);
|
||||||
|
await router.navigate({ path: "/users/3?test=1" });
|
||||||
|
expect(window.location.pathname).toBe("/users/3");
|
||||||
|
expect(window.location.search).toBe("?test=1");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("routeToPath", () => {
|
describe("routeToPath", () => {
|
||||||
@@ -117,6 +124,11 @@ describe("getRouteParams", () => {
|
|||||||
expect(getRouteParams({ path: "*" }, "somepath")).toEqual({});
|
expect(getRouteParams({ path: "*" }, "somepath")).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("properly match routes with query params", () => {
|
||||||
|
expect(getRouteParams({ path: "/home" }, "/home?test=1")).toEqual({});
|
||||||
|
expect(getRouteParams({ path: "/home" }, "/home?test1=1&test2=2")).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
test("properly match simple routes, mode hash", () => {
|
test("properly match simple routes, mode hash", () => {
|
||||||
// simple route
|
// simple route
|
||||||
expect(getRouteParams({ path: "/home" }, "#/home")).toEqual({});
|
expect(getRouteParams({ path: "/home" }, "#/home")).toEqual({});
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ if __name__ == "__main__":
|
|||||||
* Make an iframe, with all the js, css and xml properly injected.
|
* Make an iframe, with all the js, css and xml properly injected.
|
||||||
*/
|
*/
|
||||||
function makeCodeIframe(js, css, xml) {
|
function makeCodeIframe(js, css, xml) {
|
||||||
const sanitizedXML = xml.replace(/<!--[\s\S]*?-->/g, "");
|
const sanitizedXML = xml.replace(/<!--[\s\S]*?-->/g, "").replace(/`/g, '\\\`');
|
||||||
|
|
||||||
|
|
||||||
// create iframe
|
// create iframe
|
||||||
|
|||||||
+2
-1
@@ -79,7 +79,8 @@ async function startRelease() {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
log(`Step 4/${STEPS}: creating git commit...`);
|
log(`Step 4/${STEPS}: creating git commit...`);
|
||||||
const gitResult = await execCommand(`git commit -am "[REL] v${next}\n\n${content}"`);
|
const escapedContent = content.replace(/\"/g, '\\\"').replace(/\`/g, '\\\`');
|
||||||
|
const gitResult = await execCommand(`git commit -am "[REL] v${next}\n\n${escapedContent}"`);
|
||||||
if (gitResult !== 0) {
|
if (gitResult !== 0) {
|
||||||
logError("Git commit failed. Aborting.");
|
logError("Git commit failed. Aborting.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user