Compare commits

...

16 Commits

Author SHA1 Message Date
Géry Debongnie e73fb462c5 [REL] v1.0.10
# v1.0.10

Bug fixes!

- qweb fix: scoping issue with t-call in t-foreach
- qweb fix: issue with t-set with a body in a t-call
- qweb fix: handle input value attribute as a property
- qweb fix: allow t-call on arbitrary nodes
- qweb fix: add indeterminate to special input properties
- component fix: allow using t-model with bracketed expression
- component fix: properly validate multiple props
- component fix: issue with higher order component, and t-keys
- component fix: issue with unmounted children that should be destroyed
- component fix: make concurrent renderings more robust in some cases
- component fix: allow using vars with body as props
- observer fix: do not proxify promises
- test infrastructure: stop mocking requestanimation frame
2020-09-18 15:16:27 +02:00
Géry Debongnie 38c7ad9629 [FIX] t-model: support expressions with [ ]
Before this commit, the t-model directive worked well with expressions
such as "state.value", but not with bracketed expression: "state[value]"
(it generated invalid code).

This commit make the t-model smarter by detecting this case, and
properly capturing the base expression and key variable.

closes #694
2020-09-18 14:58:48 +02:00
Géry Debongnie d0c76c5854 [IMP] qweb: add indeterminate to special input properties
Input with type="checkbox" have a special property (indeterminate) to
visually display the fact that the input value is non determinate (in my
chrome browser, the checkbox is then drawn with a simple - inside). It
does not actually modify the value of the input, only the way it is
displayed.

So, with this commit, owl will properly set the property, as expected.

closes #713
2020-09-18 13:43:25 +02:00
Géry Debongnie e032314739 [FIX] qweb: allow t-call on arbitrary html nodes
This is a rarely (if ever) used feature, but according to our qweb
reference implementation, it is possible to use the
t-call directive on an arbitrary html tag, like this:

<div t-call="my.template"/>

It is then interpreted as:

<div><t t-call="my.template"/></div>

So, with this commit, we make sure that the owl qweb implementation
matches that behaviour.

closes #706
2020-09-18 08:39:57 +02:00
Géry Debongnie b2db7f21ed [FIX] observer: does not proxify promises
Promises are kind of special, and do not behave like usual javascript
values.

For this issue, the problem is that when the observer tries to observe a
promise value, the code will crash with an error like this:

Uncaught TypeError: Method Promise.prototype.then called on incompatible receiver [object Object]

Also, note that it does not make much sense to proxify promise methods
anyway, since they are not (supposed) to be modified.

So, with this commit, we simply consider that promises should be treated
like a primitive value: simply ignored when determining if it should be
proxified

Note that I actually believe that putting promises in a useState is not
a good idea in general.

closes #677
2020-09-17 13:51:10 +02:00
Géry Debongnie 7b8ac13d3f [REF] qweb: mostly revert fix with t-call and vars with body
The previous fix (overriding tostring of VDomArray) is actually more
general, and solves the same issue. So, let us simplify the code and
keep the more general solution.

This reverts commit 3bf91afc3f.
2020-09-17 09:44:08 +02:00
Géry Debongnie 06d852fcf9 [FIX] component: allow using vars with body as props
Consider this scenario:

- a variable v (with a body) is defined in a template
- it is then passed to a sub component as a prop
- and now, it is t-esc-ed.

Before this commit, the displayed value was [object object], because the
value actually passed to the sub component was a VDomArray (internal
structure used to represent nodelists)

This issue is actually quite a problem in practice, because values in a
templates are translated, but not in attributes.  Therefore, using a
t-set directive with a body text content is the proper way to have
translated values at runtime.

We override in this commit the method toString of VDomArray to make sure
it is properly displayed.

Note that we considered changing the way props were generated (by trying
to detect VDomArray, then calling vDomToString), but then the value
would not be able to be used in a t-raw.  Also, it is quite elegant to
be able to format the VDomArray only at the end.

closes #670
2020-09-17 09:44:08 +02:00
Géry Debongnie 7e4baf668a [FIX] test: timing issue
Before this commit, we artificially replaced in the tests the
requestAnimationFrame by a setTimeout, to actually increase the speed of
the tests.  However, this is not really a true replacement.  For
example, a real setTimeout can come before or after a real
nextAnimationFrame, depending on when/where it is requested.

Also, this change exposed another problem: the nextTick function did a
setTimeout before a nextanimationframe. This is not a problem when
nextAnimationFrame is replaced by a setTimeout, because then all
expectations holds in owl.  However, it is wrong: to get to the next
animation frame, we need to request an animation frame, and THEN wait
with a setTimeout.

closes #729
2020-09-17 09:18:05 +02:00
Géry Debongnie fe34ba00a6 [FIX] component: properly validate multiple props
Because of a "break" statement instead of "continue", the check for valid
props was stopping as soon as it met an optional props, which kind of
invalidate the whole system.

closes #717
2020-09-17 08:33:18 +02:00
Géry Debongnie e5e7790530 [FIX] qweb: handle input value attribute as a property
Sometimes, HTML is slightly more subtle than what I initially expect.
Rendering some html is simple, we have tags and attributes.  However,
once we add behaviour, then the situation is more complex:

<input value="abc"/>

is an input with an INITIAL value of "abc", but the attribute does not
actually represent the CURRENT value of the input, which may be
different if the user did change it.

This is basically the difference between "attribute" and "property".

So, when rendering html with owl, we sometimes want to actually set
the property (current value), instead of the html attribute.

This commit make sure that this is the case for inputs with the "value"
attribute.

closes #722
2020-09-17 08:32:02 +02:00
Géry Debongnie 3bf91afc3f [FIX] qweb: fix issue variables set in body of t-call
The body of a t-call directive may be used to define private variables
to the sub template call.

However, the code that handles t-call worked like this:

- compile sub template if necessary
- then compile body of t-call to extract variables

This means that the variables defined in the t-call body were not yet
processed and available in the context.  Because of that, when the call
to t-esc is done, there is not internal qweb var, and the code simply
outputs a scope['varname'], which is in our case a VDOMArray, so it is
displayed as [object object]

What this fix does is changing the way t-esc works: if we are in the
context of a sub template, then it assumes that any outside variable may
or may not be a VDomArray, so it needs to check and eventually convert
it to a string, if necessary.

closes #719
2020-09-16 11:39:32 +02:00
Géry Debongnie 2529aa3ef2 [FIX] component: make concurrent renderings more robust
Here is a situation that can happen in some complicated case:

1. a parent component is rendered, which includes some children
2. it is then willPatched
3. the sub components are then mounted/willUnmounted
4. because of complicated business logic, this causes the parent
component to be rerendered (before parent "patched" method is called)
5. owl will internally reset its currentfiber to null (but there is a
pending rendering!)
6. subsequent rendering will ignore pending rendering
7. havoc ensues

This is actually one of the reason why modifying a component state in a
willPatch component is actually not a good idea.  However, the good news
is that this specific situation can be properly handled: we can simply
make sure that we do not reset currentFiber to null if there is a new
pending rendering.

closes #728
2020-09-16 09:21:33 +02:00
Géry Debongnie 8d25bddda4 [FIX] component: subtle issue with unmounted children
Owl has to manage a lot of interesting situations.  One of them is when
a rendering is initiated, which creates a sub component, but then
another rendering starts, which invalidate the previous one, and will
create another sub component.  Since the first sub component was not
ever in the DOM, we cannot rely on the vdom patching process to remove
it, so we have to do it manually.

Sadly, this is actually a very tricky situation, since there are other
subtle situations where the code that remove an unmounted widget could
be executed, in particular when the parent component is unmounted, then
remounted, then modified to trigger yet another rendering.

In this commit, we handle this case more carefully by making sure that
the destroyed subcomponent properly configures its pvnode so the patch
process happens as expected.

joint work with the framework team, and in particular LPE for his work on
finding a testcase!

closes #724, #731
2020-09-15 15:51:30 +02:00
Lucas Perais (lpe) 81af21a025 [FIX] component, fiber: update props with virtual node should not crash
Have a hierarchy of A, B, C components where:

```xml
<div t-name="A">
  <div>
    <B t-key="key1"/>
  </div>
</div>

<t t-name="B">
  <C t-key="key2"/>
</t>

<div t-name="C">
  <div><t t-esc="keys_as_props" /></div>
</div>
```

The subtility of the issues lies in B, which doesn't have its own
concrete DOM element, rather, it borrows it from C.

With the sequence of events:
- change key2
C1 is destroyed and replaced by another instance, and another node.
B1 has its props updated and is patched with the C2's node (CRITICAL)
A1 is patched

- change key1 AND key2
C2 is destroyed
B1 is destroyed
A1 is patched replacing B1 by B2, and their nodes too (which at this point should be C2's to C3's)

Before this commit, at the CRITICAL point, the node representing the component itself
(technically its pvnode) was not updated with the new concrete node provided by B1 patch with C2 node
i.e. it held the previous node still
The second array of steps crashed because at A1 patch, the new B2 node would replace B1, which
was out of the DOM (removed because C1 was destroyed long before),
and therefore without a viable parent to insert B2 node.

After this commit, we update the component's pvnode after the patch which elm had possibly changed
There is no crash anymore for this use case.
2020-09-14 15:17:45 +02:00
Jigar Patel 9baea2c1cd [DOC] props validation: The validation type should be a String object. 2020-07-28 08:56:33 +02:00
Lucas Perais (lpe) 8e03f9cd9c [FIX] qweb: t-call should protect scope and let it accessible
Have a t-call nested in a t-foreach nested in a t-foreach

```xml
<t t-name="template">
  <t t-foreach="..." t-as="a">
    <t t-foreach="..." t-as="b">
      <t-call="templateCalled" />
    </t>
  </t>
</t>
```

Before this commit, the `a` variable was not accessible within the t-call.
That was because the way t-call protected its scope by hiding other protected scope
in this case, the first protected scope for the first `t-foreach` was hidden

After this commit, `a` and `b` are accessible in the t-call, whether the t-call
defines its own variables by `t-set` or not.
Also, as expected from other fixes, there is no leaks of variables defined within a `t-call`

fixes #695
2020-07-28 08:56:00 +02:00
21 changed files with 1151 additions and 199 deletions
+2 -2
View File
@@ -121,8 +121,8 @@ npm install @odoo/owl
If you want to use a simple `<script>` tag, the last release can be downloaded here:
- [owl-1.0.9.js](https://github.com/odoo/owl/releases/download/v1.0.9/owl.js)
- [owl-1.0.9.min.js](https://github.com/odoo/owl/releases/download/v1.0.9/owl.min.js)
- [owl-1.0.10.js](https://github.com/odoo/owl/releases/download/v1.0.10/owl.js)
- [owl-1.0.10.min.js](https://github.com/odoo/owl/releases/download/v1.0.10/owl.min.js)
## License
+1 -1
View File
@@ -30,7 +30,7 @@ class ComponentB extends owl.Component {
count: {type: Number},
messages: {
type: Array,
element: {type: Object, shape: {id: Boolean, text: 'string' }
element: {type: Object, shape: {id: Boolean, text: String }
},
date: Date,
combinedVal: [Number, Boolean]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "1.0.9",
"version": "1.0.10",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.js",
"types": "dist/types/index.d.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
# 🦉 OWL Roadmap 🦉
- Current version: 1.0.9
- Current version: 1.0.10
- Status: stable
This roadmap is only an attempt at predicting Owl's future. Everything may
+25 -6
View File
@@ -496,9 +496,9 @@ export class Component<Props extends {} = any, T extends Env = Env> {
}
this.willUnmount();
__owl__.isMounted = false;
if (this.__owl__.currentFiber) {
this.__owl__.currentFiber.isCompleted = true;
this.__owl__.currentFiber.root.counter = 0;
if (__owl__.currentFiber) {
__owl__.currentFiber.isCompleted = true;
__owl__.currentFiber.root.counter = 0;
}
const children = __owl__.children;
for (let id in children) {
@@ -656,9 +656,28 @@ export class Component<Props extends {} = any, T extends Env = Env> {
// destroyed right now, because they are not in the DOM, and thus we won't
// be notified later on (when patching), that they are removed from the DOM
for (let childKey in __owl__.children) {
let child = __owl__.children[childKey];
if (!child.__owl__.isMounted && child.__owl__.parentLastFiberId < fiber.id) {
child.destroy();
const child = __owl__.children[childKey];
const childOwl = child.__owl__;
if (!childOwl.isMounted && childOwl.parentLastFiberId < fiber.id) {
// we only do here a "soft" destroy, meaning that we leave the child
// dom node alone, without removing it. Most of the time, it does not
// matter, because the child component is already unmounted. However,
// if some of its parent have been unmounted, the child could actually
// still be attached to its parent, and this may be important if we
// want to remount the parent, because the vdom need to match the
// actual DOM
child.__destroy(childOwl.parent);
if (childOwl.pvnode) {
// we remove the key here to make sure that the patching algorithm
// is able to make the difference between this pvnode and an eventual
// other instance of the same component
delete childOwl.pvnode.key;
// Since the component has been unmounted, we do not want to actually
// call a remove hook. This is pretty important, since the t-component
// directive actually disabled it, so the vdom algorithm will just
// not remove the child elm if we don't remove the hook.
delete childOwl.pvnode.data!.hook!.remove;
}
}
}
if (!vnode) {
+9 -1
View File
@@ -238,12 +238,20 @@ export class Fiber {
} else {
if (fiber.shouldPatch) {
component.__patch(component.__owl__.vnode!, fiber.vnode!);
// When updating a Component's props (in directive),
// the component has a pvnode AND should be patched.
// However, its pvnode.elm may have changed if it is a High Order Component
if (component.__owl__.pvnode) {
component.__owl__.pvnode.elm = component.__owl__.vnode!.elm;
}
} else {
component.__patch(document.createElement(fiber.vnode!.sel!), fiber.vnode!);
component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm;
}
}
component.__owl__.currentFiber = null;
if (fiber === component.__owl__.currentFiber) {
component.__owl__.currentFiber = null;
}
}
// insert into the DOM (mount case)
+1 -1
View File
@@ -37,7 +37,7 @@ QWeb.utils.validateProps = function (Widget, props: Object) {
if (propsDef[propName] && !propsDef[propName].optional) {
throw new Error(`Missing props '${propName}' (component '${Widget.name}')`);
} else {
break;
continue;
}
}
let isValid;
+6 -1
View File
@@ -25,7 +25,12 @@ export class Observer {
notifyCB() {}
observe<T>(value: T, parent?: any): T {
if (value === null || typeof value !== "object" || value instanceof Date) {
if (
value === null ||
typeof value !== "object" ||
value instanceof Date ||
value instanceof Promise
) {
// fun fact: typeof null === 'object'
return value;
}
+4 -13
View File
@@ -223,9 +223,6 @@ QWeb.addDirective({
// ------------------------------------------------
ctx.rootContext.shouldDefineScope = true;
ctx.rootContext.shouldDefineUtils = true;
if (node.nodeName !== "t") {
throw new Error("Invalid tag for t-call directive (should be 't')");
}
const subTemplate = node.getAttribute("t-call")!;
const nodeTemplate = qweb.templates[subTemplate];
if (!nodeTemplate) {
@@ -245,12 +242,11 @@ QWeb.addDirective({
// Step 3: compile t-call body if necessary
// ------------------------------------------------
let hasBody = node.hasChildNodes();
let protectID;
const protectID = ctx.startProtectScope();
if (hasBody) {
// we add a sub scope to protect the ambient scope
ctx.addLine(`{`);
ctx.indent();
protectID = ctx.startProtectScope();
const nodeCopy = node.cloneNode(true) as Element;
for (let attr of ["t-if", "t-else", "t-elif", "t-call"]) {
nodeCopy.removeAttribute(attr);
@@ -271,32 +267,27 @@ QWeb.addDirective({
// Step 4: add the appropriate function call to current component
// ------------------------------------------------
const callingScope = hasBody ? "scope" : "Object.assign(Object.create(context), scope)";
const parentComponent = `utils.getComponent(context)`;
const key = ctx.generateTemplateKey();
const parentNode = ctx.parentNode ? `c${ctx.parentNode}` : "result";
const extra = `Object.assign({}, extra, {parentNode: ${parentNode}, parent: ${parentComponent}, key: ${key}})`;
if (ctx.parentNode) {
ctx.addLine(
`this.constructor.subTemplates['${subId}'].call(this, ${callingScope}, ${extra});`
);
ctx.addLine(`this.constructor.subTemplates['${subId}'].call(this, scope, ${extra});`);
} else {
// this is a t-call with no parentnode, we need to extract the result
ctx.rootContext.shouldDefineResult = true;
ctx.addLine(`result = []`);
ctx.addLine(
`this.constructor.subTemplates['${subId}'].call(this, ${callingScope}, ${extra});`
);
ctx.addLine(`this.constructor.subTemplates['${subId}'].call(this, scope, ${extra});`);
ctx.addLine(`result = result[0]`);
}
// Step 5: restore previous scope
// ------------------------------------------------
if (hasBody) {
ctx.stopProtectScope(protectID);
ctx.dedent();
ctx.addLine(`}`);
}
ctx.stopProtectScope(protectID);
return true;
},
+37 -8
View File
@@ -262,6 +262,9 @@ QWeb.utils.toNumber = function (val: string): number | string {
return isNaN(n) ? val : n;
};
const hasDotAtTheEnd = /\.[\w_]+\s*$/;
const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
QWeb.addDirective({
name: "model",
priority: 42,
@@ -270,15 +273,41 @@ QWeb.addDirective({
let handler;
let event = fullName.includes(".lazy") ? "change" : "input";
// we keep here a reference to the "base expression" (if the expression
// is `t-model="some.expr.value", then the base expression is "some.expr").
// This is necessary so we can capture it in the handler closure.
let expr = ctx.formatExpression(value);
const index = expr.lastIndexOf(".");
const baseExpr = expr.slice(0, index);
ctx.addLine(`let expr${nodeID} = ${baseExpr};`);
// First step: we need to understand the structure of the expression, and
// from it, extract a base expression (that we can capture, which is
// important because it will be used in a handler later) and a formatted
// expression (which uses the captured base expression)
//
// Also, we support 2 kinds of values: some.expr.value or some.expr[value]
// For the first one, we have:
// - base expression = scope[some].expr
// - expression = exprX.value (where exprX is the var that captures the base expr)
// and for the expression with brackets:
// - base expression = scope[some].expr
// - expression = exprX[keyX] (where exprX is the var that captures the base expr
// and keyX captures scope[value])
let expr: string;
let baseExpr: string;
if (hasDotAtTheEnd.test(value)) {
// we manage the case where the expr has a dot: some.expr.value
const index = value.lastIndexOf(".");
baseExpr = value.slice(0, index);
ctx.addLine(`let expr${nodeID} = ${ctx.formatExpression(baseExpr)};`);
expr = `expr${nodeID}${value.slice(index)}`;
} else if (hasBracketsAtTheEnd.test(value)) {
// we manage here the case where the expr ends in a bracket expression:
// some.expr[value]
const index = value.lastIndexOf("[");
baseExpr = value.slice(0, index);
ctx.addLine(`let expr${nodeID} = ${ctx.formatExpression(baseExpr)};`);
let exprKey = value.trimRight().slice(index + 1, -1);
ctx.addLine(`let exprKey${nodeID} = ${ctx.formatExpression(exprKey)};`);
expr = `expr${nodeID}[exprKey${nodeID}]`;
} else {
throw new Error(`Invalid t-model expression: "${value}" (it should be assignable)`);
}
expr = `expr${nodeID}.${expr.slice(index + 1)}`;
const key = ctx.generateTemplateKey();
if (node.tagName === "select") {
ctx.addLine(`p${nodeID}.props = {value: ${expr}};`);
+59 -37
View File
@@ -66,8 +66,6 @@ interface QWebConfig {
// Const/global stuff/helpers
//------------------------------------------------------------------------------
const DISABLED_TAGS = ["input", "textarea", "button", "select", "option", "optgroup"];
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
const lineBreakRE = /[\r\n]/;
@@ -86,10 +84,30 @@ interface Utils {
[key: string]: any;
}
function isComponent(obj) {
function isComponent(obj): boolean {
return obj && obj.hasOwnProperty("__owl__");
}
class VDomArray extends Array {
toString() {
return vDomToString(this);
}
}
function vDomToString(vdom: VNode[]): string {
return vdom
.map((vnode) => {
if (vnode.sel) {
const node = document.createElement(vnode.sel);
const result = patch(node, vnode);
return (<HTMLElement>result.elm).outerHTML;
} else {
return vnode.text;
}
})
.join("");
}
const UTILS: Utils = {
zero: Symbol("zero"),
toObj(expr) {
@@ -111,20 +129,8 @@ const UTILS: Utils = {
addNameSpace(vnode) {
addNS(vnode.data, vnode.children, vnode.sel);
},
VDomArray: class VDomArray extends Array {},
vDomToString: function (vdom: VNode[]): string {
return vdom
.map((vnode) => {
if (vnode.sel) {
const node = document.createElement(vnode.sel);
const result = patch(node, vnode);
return (<HTMLElement>result.elm).outerHTML;
} else {
return vnode.text;
}
})
.join("");
},
VDomArray,
vDomToString,
getComponent(obj) {
while (obj && !isComponent(obj)) {
obj = obj.__proto__;
@@ -216,8 +222,8 @@ export class QWeb extends EventBus {
// id, and a (global) mapping from an id to the compiled function. This is
// necessary to ensure that global templates can be called with more than one
// QWeb instance.
subTemplates: {[key: string]: number} = {};
static subTemplates: {[id: number]: Function} = {};
subTemplates: { [key: string]: number } = {};
static subTemplates: { [id: number]: Function } = {};
isUpdating: boolean = false;
translateFn?: QWebConfig["translateFn"];
@@ -509,10 +515,17 @@ export class QWeb extends EventBus {
return;
}
if (node.tagName !== "t" && node.hasAttribute("t-call")) {
const tCallNode = document.createElement("t");
tCallNode.setAttribute("t-call", node.getAttribute("t-call")!);
node.removeAttribute("t-call");
node.prepend(tCallNode);
}
const firstLetter = node.tagName[0];
if (firstLetter === firstLetter.toUpperCase()) {
// this is a component, we modify in place the xml document to change
// <SomeComponent ... /> to <t t-component="SomeComponent" ... />
// <SomeComponent ... /> to <SomeComponent t-component="SomeComponent" ... />
node.setAttribute("t-component", node.tagName);
} else if (node.tagName !== "t" && node.hasAttribute("t-component")) {
throw new Error(
@@ -666,22 +679,31 @@ export class QWeb extends EventBus {
const props: string[] = [];
const tattrs: number[] = [];
function handleBooleanProps(key, val) {
function handleProperties(key, val) {
let isProp = false;
if (node.nodeName === "input" && key === "checked") {
let type = (<Element>node).getAttribute("type");
if (type === "checkbox" || type === "radio") {
isProp = true;
}
}
if (node.nodeName === "option" && key === "selected") {
isProp = true;
}
if (key === "disabled" && DISABLED_TAGS.indexOf(node.nodeName) > -1) {
isProp = true;
}
if ((key === "readonly" && node.nodeName === "input") || node.nodeName === "textarea") {
isProp = true;
switch (node.nodeName) {
case "input":
let type = (<Element>node).getAttribute("type");
if (type === "checkbox" || type === "radio") {
if (key === "checked" || key === "indeterminate") {
isProp = true;
}
}
if (key === "value" || key === "readonly" || key === "disabled") {
isProp = true;
}
break;
case "option":
isProp = key === "selected" || key === "disabled";
break;
case "textarea":
isProp = key === "readonly" || key === "disabled";
break;
case "button":
case "select":
case "optgroup":
isProp = key === "disabled";
break;
}
if (isProp) {
props.push(`${key}: _${val}`);
@@ -720,7 +742,7 @@ export class QWeb extends EventBus {
name = '"' + name + '"';
}
attrs.push(`${name}: _${attID}`);
handleBooleanProps(name, attID);
handleProperties(name, attID);
}
}
@@ -757,7 +779,7 @@ export class QWeb extends EventBus {
}
ctx.addLine(`let _${attID} = ${formattedValue};`);
attrs.push(`${attName}: _${attID}`);
handleBooleanProps(attName, attID);
handleProperties(attName, attID);
}
}
@@ -1253,12 +1253,16 @@ exports[`other directives with t-component t-set can't alter from within callee
if (scope.iter != null) {
c2.push({text: scope.iter});
}
this.constructor.subTemplates['2'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
let c5 = [], p5 = {key:5};
let vn5 = h('p', p5, c5);
c1.push(vn5);
let _origScope4 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
scope = _origScope4;
let c6 = [], p6 = {key:6};
let vn6 = h('p', p6, c6);
c1.push(vn6);
if (scope.iter != null) {
c5.push({text: scope.iter});
c6.push({text: scope.iter});
}
return vn1;
}"
@@ -1280,18 +1284,18 @@ exports[`other directives with t-component t-set can't alter in t-call body 1`]
if (scope.iter != null) {
c2.push({text: scope.iter});
}
let _origScope4 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope4 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'iter').iter = 'inCall';
scope[utils.zero] = c__0;
}
this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
scope = _origScope4;
}
scope = _origScope4;
let c6 = [], p6 = {key:6};
let vn6 = h('p', p6, c6);
c1.push(vn6);
@@ -1401,6 +1405,47 @@ exports[`other directives with t-component t-set outside modified in t-foreach 1
}"
`;
exports[`props evaluation t-set with a body expression can be used as textual prop 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 c2 = new utils.VDomArray();
c2.push({text: \`42\`});
scope.abc = c2
// Component 'Child'
let w3 = '__4__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__4__']] : false;
let props3 = {val:scope.abc};
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
w3.destroy();
w3 = false;
}
if (w3) {
w3.__updateProps(props3, extra.fiber, undefined);
let pvnode = w3.__owl__.pvnode;
c1.push(pvnode);
} else {
let componentKey3 = \`Child\`;
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['Child'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3);
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
c1.push(pvnode);
w3.__owl__.pvnode = pvnode;
}
w3.__owl__.parentLastFiberId = extra.fiber.id;
return vn1;
}"
`;
exports[`random stuff/miscellaneous can inject values in tagged templates 1`] = `
"function anonymous(context, extra
) {
@@ -1410,7 +1455,11 @@ exports[`random stuff/miscellaneous can inject values in tagged templates 1`] =
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.constructor.subTemplates['3'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
let _origScope4 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['3'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
scope = _origScope4;
return vn1;
}"
`;
@@ -1683,6 +1732,33 @@ exports[`t-model directive basic use, on an input 1`] = `
}"
`;
exports[`t-model directive basic use, on an input with bracket expression 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__1\\"
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('input', p2, c2);
c1.push(vn2);
let expr2 = scope['state'];
let exprKey2 = 'text';
p2.props = {value: expr2[exprKey2]};
extra.handlers['__3__'] = extra.handlers['__3__'] || ((ev) => {expr2[exprKey2] = ev.target.value});
p2.on['input'] = extra.handlers['__3__'];
let c4 = [], p4 = {key:4};
let vn4 = h('span', p4, c4);
c1.push(vn4);
let _5 = scope['state'].text;
if (_5 != null) {
c4.push({text: _5});
}
return vn1;
}"
`;
exports[`t-model directive basic use, on another key in component 1`] = `
"function anonymous(context, extra
) {
@@ -1749,6 +1825,46 @@ exports[`t-model directive in a t-foreach 1`] = `
}"
`;
exports[`t-model directive in a t-foreach, part 2 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__1\\"
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _2 = scope['state'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
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.thing_first = i1 === 0
scope.thing_last = i1 === _length3 - 1
scope.thing_index = i1
scope.thing = _3[i1]
scope.thing_value = _4[i1]
let key1 = scope['thing_index'];
let c6 = [], p6 = {key:\`\${key1}_6\`,on:{}};
let vn6 = h('input', p6, c6);
c1.push(vn6);
let expr6 = scope['state'];
let exprKey6 = scope['thing_index'];
let k7 = \`__7__\${key1}__\`;
p6.props = {value: expr6[exprKey6]};
extra.handlers[k7] = extra.handlers[k7] || ((ev) => {expr6[exprKey6] = ev.target.value});
p6.on['input'] = extra.handlers[k7];
}
scope = _origScope5;
return vn1;
}"
`;
exports[`t-model directive on a select 1`] = `
"function anonymous(context, extra
) {
@@ -1833,7 +1949,7 @@ exports[`t-model directive on an input type=radio 1`] = `
let _2 = 'radio';
let _3 = 'one';
let _4 = 'One';
let c5 = [], p5 = {key:5,attrs:{type: _2,id: _3,value: _4},on:{}};
let c5 = [], p5 = {key:5,attrs:{type: _2,id: _3,value: _4},props:{value: _4},on:{}};
let vn5 = h('input', p5, c5);
c1.push(vn5);
let expr5 = scope['state'];
@@ -1843,7 +1959,7 @@ exports[`t-model directive on an input type=radio 1`] = `
let _7 = 'radio';
let _8 = 'two';
let _9 = 'Two';
let c10 = [], p10 = {key:10,attrs:{type: _7,id: _8,value: _9},on:{}};
let c10 = [], p10 = {key:10,attrs:{type: _7,id: _8,value: _9},props:{value: _9},on:{}};
let vn10 = h('input', p10, c10);
c1.push(vn10);
let expr10 = scope['state'];
+182
View File
@@ -1725,6 +1725,47 @@ describe("props evaluation ", () => {
await widget.mount(fixture);
expect(normalize(fixture.innerHTML)).toBe("<div><span>42</span></div>");
});
test("t-set with a body expression can be used as textual prop", async () => {
class Child extends Component {
static template = xml`<span t-esc="props.val"/>`;
}
class Parent extends Component {
static components = { Child };
static template = xml`
<div>
<t t-set="abc">42</t>
<Child val="abc"/>
</div>`;
}
const widget = new Parent();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
});
test("t-set with a body expression can be passed in props, and then t-raw", async () => {
class Child extends Component {
static template = xml`
<span>
<t t-esc="props.val"/>
<t t-raw="props.val"/>
</span>`;
}
class Parent extends Component {
static components = { Child };
static template = xml`
<div>
<t t-set="abc"><p>43</p></t>
<Child val="abc"/>
</div>`;
}
const widget = new Parent();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>&lt;p&gt;4343&lt;/p&gt;<p>43</p></span></div>");
});
});
describe("other directives with t-component", () => {
@@ -3015,6 +3056,85 @@ describe("random stuff/miscellaneous", () => {
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
});
test("update props of component without concrete own node", async () => {
class Custom extends Component {
static template = xml`
<div class="widget-subkey">
<t t-esc="props.key"/>__<t t-esc="props.subKey"/>
</div>`;
}
class Child extends Component {
static components = { Custom };
static template = xml`
<t t-component="Custom"
t-key="props.subKey"
key="props.key"
subKey="props.subKey"/>`;
}
class Parent extends Component {
static components = { Child };
static template = xml`
<div>
<Child t-key="childProps.key" t-props="childProps"/>
</div>`;
childProps = {
key: 1,
subKey: 1,
};
}
const parent = new Parent(null);
await parent.mount(fixture);
expect(fixture.textContent!.trim()).toBe("1__1");
// First step: change the Custom's instance
Object.assign(parent.childProps, {
subKey: 2,
});
parent.render();
await nextTick();
expect(fixture.textContent!.trim()).toBe("1__2");
// Second step, change both Child's and Custom's instance
Object.assign(parent.childProps, {
key: 2,
subKey: 3,
});
parent.render();
await nextTick();
expect(fixture.textContent!.trim()).toBe("2__3");
});
test("two renderings initiated between willPatch and patched", async () => {
let app;
class Panel extends Component {
static template = xml`<abc><t t-esc="props.val"/></abc>`;
mounted() {
app.render();
}
willUnmount() {
app.render();
}
}
// Main root component
class App extends Component {
static components = { Panel };
static template = xml`<div><Panel t-key="'panel_' + state.panel" val="state.panel"/></div>`;
state = useState({ panel: "Panel1" });
}
app = new App();
await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div><abc>Panel1</abc></div>");
app.state.panel = "Panel2";
await nextTick();
expect(fixture.innerHTML).toBe("<div><abc>Panel2</abc></div>");
});
});
describe("widget and observable state", () => {
@@ -3138,6 +3258,46 @@ describe("t-model directive", () => {
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
});
test("basic use, on an input with bracket expression", async () => {
class SomeComponent extends Component {
static template = xml`
<div>
<input t-model="state['text']"/>
<span><t t-esc="state.text"/></span>
</div>`;
state = useState({ text: "" });
}
const comp = new SomeComponent();
await comp.mount(fixture);
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
const input = fixture.querySelector("input")!;
await editInput(input, "test");
expect(comp.state.text).toBe("test");
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
});
test("throws if invalid expression", async () => {
class SomeComponent extends Component {
static template = xml`
<div>
<input t-model="state"/>
</div>`;
state = useState({ text: "" });
}
const comp = new SomeComponent();
let error;
try {
await comp.mount(fixture);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe(`Invalid t-model expression: "state" (it should be assignable)`);
});
test("basic use, on another key in component", async () => {
env.qweb.addTemplates(`
<templates>
@@ -3427,6 +3587,28 @@ describe("t-model directive", () => {
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
});
test("in a t-foreach, part 2", async () => {
class SomeComponent extends Component {
static template = xml`
<div>
<t t-foreach="state" t-as="thing" t-key="thing_index" >
<input t-model="state[thing_index]"/>
</t>
</div>
`;
state = useState(["zuko", "iroh"]);
}
const comp = new SomeComponent();
await comp.mount(fixture);
expect(comp.state).toEqual(["zuko", "iroh"]);
const input = fixture.querySelectorAll("input")[1]!;
input.value = "uncle iroh";
input.dispatchEvent(new Event("input"));
expect(comp.state).toEqual(["zuko", "uncle iroh"]);
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
});
test("two inputs in a div with a t-key", async () => {
class SomeComponent extends Component {
static template = xml`
+25
View File
@@ -786,6 +786,31 @@ describe("props validation", () => {
await nextTick();
expect(fixture.innerHTML).toBe("<div><div>4</div></div>");
});
test("mix of optional and mandatory", async () => {
class Child extends Component {
static props = {
optional: { type: String, optional: true },
mandatory: Number,
};
static template = xml` <div><t t-esc="props.mandatory"/></div>`;
}
class App extends Component {
static components = { Child };
static template = xml`<div><Child/></div>`;
}
const w = new App(undefined, {});
let error;
try {
await w.mount(fixture);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe("Missing props 'mandatory' (component 'Child')");
});
});
describe("default props", () => {
+58
View File
@@ -929,4 +929,62 @@ describe("t-slot directive", () => {
expect(fixture.innerHTML).toBe("<div><span>dash</span></div>");
expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot();
});
test("slot and t-esc", async () => {
class Dialog extends Component {
static template = xml`<span><t t-slot="default"/></span>`;
}
class Parent extends Component {
static template = xml`<div><Dialog><t t-esc="'toph'"/></Dialog></div>`;
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>toph</span></div>");
});
test("slot and (inline) t-esc", async () => {
class Dialog extends Component {
static template = xml`<span><t t-slot="default"/></span>`;
}
class Parent extends Component {
static template = xml`<div><Dialog t-esc="'toph'"/></div>`;
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>toph</span></div>");
});
test("slot and t-call", async () => {
env.qweb.addTemplate("sokka", "<p>sokka</p>");
class Dialog extends Component {
static template = xml`<span><t t-slot="default"/></span>`;
}
class Parent extends Component {
static template = xml`<div><Dialog><t t-call="sokka"/></Dialog></div>`;
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span><p>sokka</p></span></div>");
});
test("slot and (inline) t-call", async () => {
env.qweb.addTemplate("sokka", "<p>sokka</p>");
class Dialog extends Component {
static template = xml`<span><t t-slot="default"/></span>`;
}
class Parent extends Component {
static template = xml`<div><Dialog t-call="sokka"/></div>`;
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span><p>sokka</p></span></div>");
});
});
+99
View File
@@ -509,4 +509,103 @@ describe("unmounting and remounting", () => {
expect(error).toBeDefined();
expect(error.message).toBe("Cannot mount a destroyed component");
});
test("destroying a sub-component cleans itself from parent's vnode", async () => {
class C1 extends Component {
static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
}
class P extends Component {
static components = { C1 };
static template = xml`<div><div><C1 t-props="state" t-if="state.a"/></div></div>`;
state = {
a: "first",
};
}
const parent = new P();
await parent.mount(fixture);
expect(fixture.textContent).toBe("first");
parent.unmount();
parent.state.a = "";
parent.mount(fixture);
parent.state.a = "fixed";
await parent.render();
expect(fixture.textContent).toBe("fixed");
});
test("destroying a sub-component cleans itself from parent's vnode, part 2", async () => {
class C1 extends Component {
static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
}
class P extends Component {
static components = { C1 };
static template = xml`<div><div><C1 t-props="state" t-if="state.a"/>some text</div></div>`;
state = {
a: "first",
};
}
const parent = new P();
await parent.mount(fixture);
expect(fixture.textContent).toBe("firstsome text");
parent.unmount();
parent.state.a = "";
parent.mount(fixture);
parent.state.a = "fixed";
await parent.render();
expect(fixture.textContent).toBe("fixedsome text");
});
test("destroying a sub-component cleans itself from parent's vnode, part 3", async () => {
class C1 extends Component {
static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
}
class C2 extends Component {
static template = xml`<C1 a="props.a"/>`;
static components = { C1 };
}
class P extends Component {
static components = { C2 };
static template = xml`<div><div><C2 t-props="state" t-if="state.a"/></div></div>`;
state = {
a: "first",
};
}
const parent = new P();
await parent.mount(fixture);
expect(fixture.textContent).toBe("first");
parent.unmount();
parent.state.a = "";
parent.mount(fixture);
parent.state.a = "fixed";
await parent.render();
expect(fixture.textContent).toBe("fixed");
});
test("destroying a sub-component cleans itself from parent's vnode, part 4", async () => {
class C1 extends Component {
static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
}
class C2 extends Component {
static template = xml`<C1 a="props.a"/>`;
static components = { C1 };
}
class P extends Component {
static components = { C2 };
static template = xml`<div><div><C2 t-props="state" t-if="state.a"/>some text</div></div>`;
state = {
a: "first",
};
}
const parent = new P();
await parent.mount(fixture);
expect(fixture.textContent).toBe("firstsome text");
parent.unmount();
parent.state.a = "";
parent.mount(fixture);
parent.state.a = "fixed";
await parent.render();
expect(fixture.textContent).toBe("fixedsome text");
});
});
+17
View File
@@ -68,6 +68,23 @@ describe("observer", () => {
expect(obj.date).not.toBe(date);
});
test("properly handle promises (i.e.: treat them like primitive values", async () => {
const observer = new Observer();
let resolved = false;
const prom = new Promise((r) => r());
const obj: any = observer.observe({ prom });
expect(obj.prom).toBeInstanceOf(Promise);
obj.prom.then(() => (resolved = true));
expect(observer.revNumber(obj)).toBe(1);
expect(resolved).toBe(false);
await Promise.resolve();
expect(resolved).toBe(true);
expect(observer.revNumber(obj)).toBe(1);
});
test("can change values in array", () => {
const observer = new Observer();
const obj: any = observer.observe({ arr: [1, 2] });
+2 -9
View File
@@ -8,12 +8,6 @@ import "../src/qweb/extensions";
import "../src/component/directive";
import { browser } from "../src/browser";
// modifies scheduler to make it faster to test components
scheduler.requestAnimationFrame = function (callback: FrameRequestCallback) {
setTimeout(callback, 1);
return 1;
};
// Some static cleanup
let nextSlotId;
let slots;
@@ -43,9 +37,8 @@ export function nextMicroTick(): Promise<void> {
}
export async function nextTick(): Promise<void> {
return new Promise(function (resolve) {
setTimeout(() => scheduler.requestAnimationFrame(() => resolve()));
});
await new Promise((resolve) => scheduler.requestAnimationFrame(resolve));
await new Promise((resolve) => setTimeout(resolve));
}
export function makeTestFixture() {
+386 -98
View File
@@ -630,6 +630,177 @@ exports[`foreach iterate, position 1`] = `
}"
`;
exports[`foreach t-call with body in t-foreach in t-foreach 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _2 = scope['numbers'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
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.a_first = i1 === 0
scope.a_last = i1 === _length3 - 1
scope.a_index = i1
scope.a = _3[i1]
scope.a_value = _4[i1]
let key1 = i1;
let _6 = scope['letters'];
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
let _7 = _8 = _6;
if (!(_6 instanceof Array)) {
_7 = Object.keys(_6);
_8 = Object.values(_6);
}
let _length7 = _7.length;
let _origScope9 = scope;
scope = Object.create(scope);
for (let i2 = 0; i2 < _length7; i2++) {
scope.b_first = i2 === 0
scope.b_last = i2 === _length7 - 1
scope.b_index = i2
scope.b = _7[i2]
scope.b_value = _8[i2]
let key2 = i2;
let _origScope13 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
{
let c__0 = [];
utils.getScope(scope, 'c').c = 'x'+'_'+scope['a']+'_'+scope['b'];
scope[utils.zero] = c__0;
}
let k14 = \`__14__\${key1}__\${key2}__\`;
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: k14}));
}
scope = _origScope13;
}
scope = _origScope9;
let c15 = [], p15 = {key:\`\${key1}_15\`};
let vn15 = h('span', p15, c15);
c1.push(vn15);
if (scope.c != null) {
c15.push({text: scope.c});
}
}
scope = _origScope5;
let c16 = [], p16 = {key:16};
let vn16 = h('span', p16, c16);
c1.push(vn16);
c16.push({text: \`[\`});
let _17 = scope['a'];
if (_17 != null) {
c16.push({text: _17});
}
c16.push({text: \`][\`});
let _18 = scope['b'];
if (_18 != null) {
c16.push({text: _18});
}
c16.push({text: \`][\`});
if (scope.c != null) {
c16.push({text: scope.c});
}
c16.push({text: \`]\`});
return vn1;
}"
`;
exports[`foreach t-call without body in t-foreach in t-foreach 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _2 = scope['numbers'];
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
let _3 = _4 = _2;
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.a_first = i1 === 0
scope.a_last = i1 === _length3 - 1
scope.a_index = i1
scope.a = _3[i1]
scope.a_value = _4[i1]
let key1 = i1;
let _6 = scope['letters'];
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
let _7 = _8 = _6;
if (!(_6 instanceof Array)) {
_7 = Object.keys(_6);
_8 = Object.values(_6);
}
let _length7 = _7.length;
let _origScope9 = scope;
scope = Object.create(scope);
for (let i2 = 0; i2 < _length7; i2++) {
scope.b_first = i2 === 0
scope.b_last = i2 === _length7 - 1
scope.b_index = i2
scope.b = _7[i2]
scope.b_value = _8[i2]
let key2 = i2;
let _origScope12 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
let k13 = \`__13__\${key1}__\${key2}__\`;
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: k13}));
scope = _origScope12;
}
scope = _origScope9;
let c14 = [], p14 = {key:\`\${key1}_14\`};
let vn14 = h('span', p14, c14);
c1.push(vn14);
let _15 = scope['c'];
if (_15 != null) {
c14.push({text: _15});
}
}
scope = _origScope5;
let c16 = [], p16 = {key:16};
let vn16 = h('span', p16, c16);
c1.push(vn16);
c16.push({text: \`[\`});
let _17 = scope['a'];
if (_17 != null) {
c16.push({text: _17});
}
c16.push({text: \`][\`});
let _18 = scope['b'];
if (_18 != null) {
c16.push({text: _18});
}
c16.push({text: \`][\`});
let _19 = scope['c'];
if (_19 != null) {
c16.push({text: _19});
}
c16.push({text: \`]\`});
return vn1;
}"
`;
exports[`foreach t-foreach in t-forach 1`] = `
"function anonymous(context, extra
) {
@@ -749,7 +920,11 @@ exports[`loading templates can load a few templates from a xml string 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('ul', p1, c1);
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
let _origScope4 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
scope = _origScope4;
return vn1;
}"
`;
@@ -787,16 +962,16 @@ exports[`misc global 1`] = `
if (_7 != null) {
c6.push({text: _7});
}
let _origScope10 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope10 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
let _origScope13 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope13 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'foo').foo = 'aaa';
@@ -804,22 +979,34 @@ exports[`misc global 1`] = `
}
let k14 = \`__14__\${key1}__\`;
this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k14}));
scope = _origScope13;
}
let k15 = \`__15__\${key1}__\`;
this.constructor.subTemplates['2'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k15}));
utils.getScope(scope, 'foo').foo = 'bbb';
scope = _origScope13;
let _origScope15 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
let k16 = \`__16__\${key1}__\`;
this.constructor.subTemplates['2'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k16}));
this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k16}));
scope = _origScope15;
utils.getScope(scope, 'foo').foo = 'bbb';
let _origScope17 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
let k18 = \`__18__\${key1}__\`;
this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k18}));
scope = _origScope17;
scope[utils.zero] = c__0;
}
let k17 = \`__17__\${key1}__\`;
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: k17}));
scope = _origScope10;
let k19 = \`__19__\${key1}__\`;
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: k19}));
}
scope = _origScope10;
}
scope = _origScope5;
this.constructor.subTemplates['3'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__20__'}));
let _origScope22 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['3'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__23__'}));
scope = _origScope22;
return vn1;
}"
`;
@@ -872,7 +1059,7 @@ exports[`properly support svg add proper namespace to svg 1`] = `
}"
`;
exports[`special cases for some boolean html attributes/properties input type= checkbox, with t-att-checked 1`] = `
exports[`special cases for some specific html attributes/properties input type= checkbox, with t-att-checked 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
@@ -886,7 +1073,7 @@ exports[`special cases for some boolean html attributes/properties input type= c
}"
`;
exports[`special cases for some boolean 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
) {
// Template name: \\"test\\"
@@ -1108,7 +1295,11 @@ exports[`t-call (template calling basic caller 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
scope = _origScope3;
return vn1;
}"
`;
@@ -1121,9 +1312,13 @@ exports[`t-call (template calling basic caller, no parent node 1`] = `
let scope = Object.create(context);
let result;
let h = this.h;
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
result = []
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__3__'}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
result = result[0]
scope = _origScope3;
return result;
}"
`;
@@ -1137,10 +1332,10 @@ exports[`t-call (template calling call with several sub nodes on same line 1`] =
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
let c4 = [], p4 = {key:4};
@@ -1155,8 +1350,8 @@ exports[`t-call (template calling call with several sub nodes on same line 1`] =
scope[utils.zero] = c__0;
}
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'}));
scope = _origScope3;
}
scope = _origScope3;
return vn1;
}"
`;
@@ -1172,10 +1367,10 @@ exports[`t-call (template calling cascading t-call t-raw='0' 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _origScope12 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope12 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
let c13 = [], p13 = {key:13};
@@ -1190,8 +1385,8 @@ exports[`t-call (template calling cascading t-call t-raw='0' 1`] = `
scope[utils.zero] = c__0;
}
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__15__'}));
scope = _origScope12;
}
scope = _origScope12;
return vn1;
}"
`;
@@ -1206,7 +1401,11 @@ exports[`t-call (template calling inherit context 1`] = `
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
scope.foo = 1;
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__2__'}));
let _origScope2 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
scope = _origScope2;
return vn1;
}"
`;
@@ -1225,7 +1424,11 @@ exports[`t-call (template calling recursive template, part 1 1`] = `
c1.push(vn2);
c2.push({text: \`hey\`});
if (false) {
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'}));
let _origScope7 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__8__'}));
scope = _origScope7;
}
return vn1;
}"
@@ -1248,8 +1451,12 @@ exports[`t-call (template calling recursive template, part 1 2`] = `
c3.push(vn4);
c4.push({text: \`hey\`});
if (false) {
let k5 = \`__5__\${key0}__\`;
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c3, parent: utils.getComponent(context), key: k5}));
let _origScope5 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
let k6 = \`__6__\${key0}__\`;
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c3, parent: utils.getComponent(context), key: k6}));
scope = _origScope5;
}
}"
`;
@@ -1263,18 +1470,18 @@ exports[`t-call (template calling recursive template, part 2 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _origScope11 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope11 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'node').node = scope['root'];
scope[utils.zero] = c__0;
}
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
scope = _origScope11;
}
scope = _origScope11;
return vn1;
}"
`;
@@ -1315,10 +1522,10 @@ exports[`t-call (template calling recursive template, part 2 2`] = `
scope.subtree = _6[i1]
scope.subtree_value = _7[i1]
let key1 = i1;
let _origScope9 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope9 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'node').node = scope['subtree'];
@@ -1326,8 +1533,8 @@ exports[`t-call (template calling recursive template, part 2 2`] = `
}
let k10 = \`__10__\${key0}__\${key1}__\`;
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c2, parent: utils.getComponent(context), key: k10}));
scope = _origScope9;
}
scope = _origScope9;
}
scope = _origScope8;
}"
@@ -1342,18 +1549,18 @@ exports[`t-call (template calling recursive template, part 3 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _origScope11 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope11 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'node').node = scope['root'];
scope[utils.zero] = c__0;
}
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
scope = _origScope11;
}
scope = _origScope11;
return vn1;
}"
`;
@@ -1394,10 +1601,10 @@ exports[`t-call (template calling recursive template, part 3 2`] = `
scope.subtree = _6[i1]
scope.subtree_value = _7[i1]
let key1 = i1;
let _origScope9 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope9 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'node').node = scope['subtree'];
@@ -1405,8 +1612,8 @@ exports[`t-call (template calling recursive template, part 3 2`] = `
}
let k10 = \`__10__\${key0}__\${key1}__\`;
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c2, parent: utils.getComponent(context), key: k10}));
scope = _origScope9;
}
scope = _origScope9;
}
scope = _origScope8;
}"
@@ -1421,10 +1628,10 @@ exports[`t-call (template calling recursive template, part 4: with t-set recursi
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _origScope11 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope11 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'recursive_idx').recursive_idx = 1;
@@ -1432,8 +1639,8 @@ exports[`t-call (template calling recursive template, part 4: with t-set recursi
scope[utils.zero] = c__0;
}
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
scope = _origScope11;
}
scope = _origScope11;
return vn1;
}"
`;
@@ -1479,10 +1686,10 @@ exports[`t-call (template calling recursive template, part 4: with t-set recursi
scope.subtree = _6[i1]
scope.subtree_value = _7[i1]
let key1 = i1;
let _origScope9 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope9 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'node').node = scope['subtree'];
@@ -1490,8 +1697,8 @@ exports[`t-call (template calling recursive template, part 4: with t-set recursi
}
let k10 = \`__10__\${key0}__\${key1}__\`;
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c2, parent: utils.getComponent(context), key: k10}));
scope = _origScope9;
}
scope = _origScope9;
}
scope = _origScope8;
}"
@@ -1506,18 +1713,18 @@ exports[`t-call (template calling scoped parameters 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _origScope2 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope2 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'foo').foo = 42;
scope[utils.zero] = c__0;
}
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
scope = _origScope2;
}
scope = _origScope2;
if (scope.foo != null) {
c1.push({text: scope.foo});
}
@@ -1525,6 +1732,24 @@ exports[`t-call (template calling scoped parameters 1`] = `
}"
`;
exports[`t-call (template calling t-call allowed on a non t node 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"caller\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
scope = _origScope3;
return vn1;
}"
`;
exports[`t-call (template calling t-call with t-if 1`] = `
"function anonymous(context, extra
) {
@@ -1535,12 +1760,43 @@ exports[`t-call (template calling t-call with t-if 1`] = `
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
if (scope['flag']) {
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
scope = _origScope3;
}
return vn1;
}"
`;
exports[`t-call (template calling t-call with t-set inside and body text content 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"main\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _origScope4 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
{
let c__0 = [];
let c5 = new utils.VDomArray();
c5.push({text: \`yip yip\`});
scope.val = c5
scope[utils.zero] = c__0;
}
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'}));
}
scope = _origScope4;
return vn1;
}"
`;
exports[`t-call (template calling t-call with t-set inside and outside 1`] = `
"function anonymous(context, extra
) {
@@ -1568,10 +1824,10 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = `
scope.v_value = _4[i1]
let key1 = i1;
utils.getScope(scope, 'val').val = scope['v'].val;
let _origScope8 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope8 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'val3').val3 = scope.val*3;
@@ -1579,8 +1835,8 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = `
}
let k9 = \`__9__\${key1}__\`;
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: k9}));
scope = _origScope8;
}
scope = _origScope8;
}
scope = _origScope5;
return vn1;
@@ -1597,7 +1853,11 @@ exports[`t-call (template calling t-call with t-set inside and outside. 2 1`] =
let c1 = [], p1 = {key:1};
let vn1 = h('p', p1, c1);
scope.w = 'fromwrapper';
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__11__'}));
let _origScope11 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
scope = _origScope11;
return vn1;
}"
`;
@@ -1613,21 +1873,25 @@ exports[`t-call (template calling t-call, conditional and t-set in t-call body 1
let vn1 = h('div', p1, c1);
scope.v1 = 'elif';
if (scope.v1==='if') {
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
scope = _origScope3;
}
else if (scope.v1==='elif') {
let _origScope7 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope6 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'v').v = 'success';
scope[utils.zero] = c__0;
}
this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__7__'}));
scope = _origScope6;
this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__8__'}));
}
scope = _origScope7;
}
return vn1;
}"
@@ -1642,7 +1906,11 @@ exports[`t-call (template calling t-call, global templates 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
scope = _origScope3;
return vn1;
}"
`;
@@ -1656,7 +1924,11 @@ exports[`t-call (template calling two different QWeb instances, and shared templ
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
scope = _origScope3;
return vn1;
}"
`;
@@ -1670,7 +1942,11 @@ exports[`t-call (template calling two different QWeb instances, and shared templ
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
scope = _origScope3;
return vn1;
}"
`;
@@ -1683,10 +1959,10 @@ exports[`t-call (template calling with unused body 1`] = `
let scope = Object.create(context);
let result;
let h = this.h;
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
c__0.push({text: \`WHEEE\`});
@@ -1695,8 +1971,8 @@ exports[`t-call (template calling with unused body 1`] = `
result = []
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
result = result[0]
scope = _origScope3;
}
scope = _origScope3;
return result;
}"
`;
@@ -1709,10 +1985,10 @@ exports[`t-call (template calling with unused setbody 1`] = `
let scope = Object.create(context);
let result;
let h = this.h;
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'qux').qux = 3;
@@ -1721,8 +1997,8 @@ exports[`t-call (template calling with unused setbody 1`] = `
result = []
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
result = result[0]
scope = _origScope3;
}
scope = _origScope3;
return result;
}"
`;
@@ -1735,10 +2011,10 @@ exports[`t-call (template calling with used body 1`] = `
let scope = Object.create(context);
let result;
let h = this.h;
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
c__0.push({text: \`ok\`});
@@ -1747,8 +2023,8 @@ exports[`t-call (template calling with used body 1`] = `
result = []
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
result = result[0]
scope = _origScope3;
}
scope = _origScope3;
return result;
}"
`;
@@ -1762,18 +2038,18 @@ exports[`t-call (template calling with used set body 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('span', p1, c1);
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'foo').foo = 'ok';
scope[utils.zero] = c__0;
}
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
scope = _origScope3;
}
scope = _origScope3;
return vn1;
}"
`;
@@ -1921,7 +2197,11 @@ exports[`t-esc t-esc inside t-call, with t-set outside 1`] = `
let c2 = new utils.VDomArray();
c2.push({text: \`Hi\`});
scope.v = c2
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
let _origScope5 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'}));
scope = _origScope5;
return vn1;
}"
`;
@@ -1978,10 +2258,10 @@ exports[`t-esc t-esc=0 is escaped 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let _origScope3 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
let c4 = [], p4 = {key:4};
@@ -1991,8 +2271,8 @@ exports[`t-esc t-esc=0 is escaped 1`] = `
scope[utils.zero] = c__0;
}
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
scope = _origScope3;
}
scope = _origScope3;
return vn1;
}"
`;
@@ -2786,7 +3066,11 @@ exports[`t-on t-on with t-call 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
let _origScope4 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
scope = _origScope4;
return vn1;
}"
`;
@@ -2800,7 +3084,11 @@ exports[`t-on t-on, with arguments and t-call 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
let _origScope4 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
scope = _origScope4;
return vn1;
}"
`;
+104 -4
View File
@@ -766,10 +766,11 @@ describe("t-call (template calling", () => {
expect(qweb.subTemplates["sub"]).toBeTruthy();
});
test("t-call not allowed on a non t node", () => {
qweb.addTemplate("_basic-callee", "<t>ok</t>");
test("t-call allowed on a non t node", () => {
qweb.addTemplate("_basic-callee", "<span>ok</span>");
qweb.addTemplate("caller", '<div t-call="_basic-callee"/>');
expect(() => renderToString(qweb, "caller")).toThrow("Invalid tag");
const expected = "<div><span>ok</span></div>";
expect(renderToString(qweb, "caller")).toBe(expected);
});
test("with unused body", () => {
@@ -1072,6 +1073,21 @@ describe("t-call (template calling", () => {
expect(renderToString(qweb1, "main")).toBe("<div><span>ok</span></div>");
expect(renderToString(qweb2, "main")).toBe("<div><span>ok</span></div>");
});
test("t-call with t-set inside and body text content", () => {
qweb.addTemplate("sub", `<p><t t-esc="val"/></p>`);
qweb.addTemplate(
"main",
`
<div>
<t t-call="sub">
<t t-set="val">yip yip</t>
</t>
</div>`
);
const expected = "<div><p>yip yip</p></div>";
expect(renderToString(qweb, "main")).toBe(expected);
});
});
describe("foreach", () => {
@@ -1162,6 +1178,63 @@ describe("foreach", () => {
);
});
test("t-call without body in t-foreach in t-foreach", () => {
qweb.addTemplate(
"test_called",
`<t>
<t t-set="c" t-value="'x' + '_' + a + '_'+ b" />
[<t t-esc="a" />]
[<t t-esc="b" />]
[<t t-esc="c" />]
</t>`
);
qweb.addTemplate(
"test",
`<div>
<t t-foreach="numbers" t-as="a">
<t t-foreach="letters" t-as="b">
<t t-call="test_called" />
</t>
<span t-esc="c"/>
</t>
<span>[<t t-esc="a" />][<t t-esc="b" />][<t t-esc="c" />]</span>
</div>`
);
const context = { numbers: [1, 2, 3], letters: ["a", "b"] };
expect(renderToString(qweb, "test", context)).toBe(
"<div> [1] [a] [x_1_a] [1] [b] [x_1_b] <span></span> [2] [a] [x_2_a] [2] [b] [x_2_b] <span></span> [3] [a] [x_3_a] [3] [b] [x_3_b] <span></span><span>[][][]</span></div>"
);
});
test("t-call with body in t-foreach in t-foreach", () => {
qweb.addTemplate(
"test_called",
`<t>
[<t t-esc="a" />]
[<t t-esc="b" />]
[<t t-esc="c" />]
</t>`
);
qweb.addTemplate(
"test",
`<div>
<t t-foreach="numbers" t-as="a">
<t t-foreach="letters" t-as="b">
<t t-call="test_called" >
<t t-set="c" t-value="'x' + '_' + a + '_'+ b" />
</t>
</t>
<span t-esc="c"/>
</t>
<span>[<t t-esc="a" />][<t t-esc="b" />][<t t-esc="c" />]</span>
</div>`
);
const context = { numbers: [1, 2, 3], letters: ["a", "b"] };
expect(renderToString(qweb, "test", context)).toBe(
"<div> [1] [a] [x_1_a] [1] [b] [x_1_b] <span></span> [2] [a] [x_2_a] [2] [b] [x_2_b] <span></span> [3] [a] [x_3_a] [3] [b] [x_3_b] <span></span><span>[][][]</span></div>"
);
});
test("throws error if invalid loop expression", () => {
qweb.addTemplate(
"test",
@@ -1777,7 +1850,7 @@ describe("loading templates", () => {
});
});
describe("special cases for some boolean html attributes/properties", () => {
describe("special cases for some specific html attributes/properties", () => {
test("input type= checkbox, with t-att-checked", () => {
qweb.addTemplate("test", `<input type="checkbox" t-att-checked="flag"/>`);
const result = renderToString(qweb, "test", { flag: true });
@@ -1804,6 +1877,33 @@ describe("special cases for some boolean html attributes/properties", () => {
);
renderToString(qweb, "test", { flag: true });
});
test("input with t-att-value", () => {
// render input with initial value
qweb.addTemplate("test", `<input t-att-value="v"/>`);
const vnode1 = qweb.render("test", { v: "zucchini" });
const vnode2 = patch(document.createElement("input"), vnode1);
let elm = vnode2.elm as HTMLInputElement;
expect(elm.value).toBe("zucchini");
// change value manually in input, to simulate user input
elm.value = "tomato";
expect(elm.value).toBe("tomato");
// rerender with a different value, and patch actual dom, to check that
// input value was properly reset by owl
const vnode3 = qweb.render("test", { v: "potato" });
patch(vnode2, vnode3);
expect(elm.value).toBe("potato");
});
test("input of type checkbox with t-att-indeterminate", () => {
qweb.addTemplate("test", `<input type="checkbox" t-att-indeterminate="v"/>`);
const vnode1 = qweb.render("test", { v: true });
const vnode2 = patch(document.createElement("input"), vnode1);
let elm = vnode2.elm as HTMLInputElement;
expect(elm.indeterminate).toBe(true);
});
});
describe("whitespace handling", () => {
+4 -4
View File
@@ -1107,9 +1107,9 @@ describe("html to vdom", function () {
});
test("svg", function () {
const nodeList = htmlToVDOM(`<svg></svg>`);
expect(nodeList).toHaveLength(1);
elm = patch(vnode0, nodeList[0]).elm;
expect(elm).toBeInstanceOf(SVGSVGElement);
const nodeList = htmlToVDOM(`<svg></svg>`);
expect(nodeList).toHaveLength(1);
elm = patch(vnode0, nodeList[0]).elm;
expect(elm).toBeInstanceOf(SVGSVGElement);
});
});