mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] compiler: add support for t-for directive
This commit adds support for the `t-for` whose syntax and usage is similar to the syntax of the for..of loop in JS (and in fact it compiles to a for..of loop). This looping construct supports looping on arbitrary iterables and destructuring assignments, which would be difficult to support in a backward compatible manner on the existing t-foreach directive.
This commit is contained in:
@@ -117,7 +117,7 @@ const { loadFile, mount } = owl;
|
||||
Dev mode activates some additional checks and developer amenities:
|
||||
|
||||
- [Props validation](./props.md#props-validation) is performed
|
||||
- [t-foreach](./templates.md#loops) loops check for key unicity
|
||||
- [t-for and t-foreach](./templates.md#loops) loops check for key unicity
|
||||
- Lifecycle hooks are wrapped to report their errors in a more developer-friendly way
|
||||
- onWillStart and onWillUpdateProps will emit a warning in the console when they
|
||||
take longer than 3 seconds in an effort to ease debugging the presence of deadlocks
|
||||
|
||||
+31
-20
@@ -61,7 +61,7 @@ For reference, here is a list of all standard QWeb directives:
|
||||
| `t-out` | [Outputting value, possibly without escaping](#outputting-data) |
|
||||
| `t-set`, `t-value` | [Setting variables](#setting-variables) |
|
||||
| `t-if`, `t-elif`, `t-else`, | [conditionally rendering](#conditionals) |
|
||||
| `t-foreach`, `t-as` | [Loops](#loops) |
|
||||
| `t-for/t-of`, `t-foreach/t-as` | [Loops](#loops) |
|
||||
| `t-att`, `t-attf-*`, `t-att-*` | [Dynamic attributes](#dynamic-attributes) |
|
||||
| `t-call` | [Rendering sub templates](#sub-templates) |
|
||||
| `t-debug`, `t-log` | [Debugging](#debugging) |
|
||||
@@ -372,6 +372,24 @@ Like conditions, `t-foreach` applies to the element bearing the directive’s at
|
||||
|
||||
is equivalent to the previous example.
|
||||
|
||||
Owl also has another pair of directives that can be used for looping that allow
|
||||
destructuring the contents of the loop item: `t-for` and `t-of`, which behaves
|
||||
much like `for..of` in javascript:
|
||||
|
||||
```xml
|
||||
<t t-for="[left, right]" t-of="[['a', 1], ['b', 2], ['c', 3]]" t-key="left">
|
||||
<p><t t-esc="left"/>: <t t-esc="right"/></p>
|
||||
</t>
|
||||
```
|
||||
|
||||
will be rendered as:
|
||||
|
||||
```xml
|
||||
<p>a: 1</p>
|
||||
<p>b: 2</p>
|
||||
<p>c: 3</p>
|
||||
```
|
||||
|
||||
An important difference should be made with the usual `QWeb` behaviour: Owl
|
||||
requires the presence of a `t-key` directive, to be able to properly reconcile
|
||||
renderings.
|
||||
@@ -380,8 +398,9 @@ renderings.
|
||||
and maps, it will expose the key of the current iteration as the contents of the
|
||||
`t-as`, and the corresponding value with the same name and the suffix `_value`.
|
||||
|
||||
In addition to the name passed via t-as, `t-foreach` provides a few other useful
|
||||
variables (note: `$as` will be replaced with the name passed to `t-as`):
|
||||
In addition to the name passed via t-as, `t-foreach` (but not `t-for`) provides
|
||||
a few other useful variables (note: `$as` will be replaced with the name passed
|
||||
to `t-as`):
|
||||
|
||||
- `$as_value`: the current iteration value, identical to `$as` for arrays and
|
||||
other iterables, but for objects and maps, it provides the value (where `$as`
|
||||
@@ -393,10 +412,9 @@ variables (note: `$as` will be replaced with the name passed to `t-as`):
|
||||
(equivalent to `$as_index + 1 == $as_size`), requires the iteratee’s size be
|
||||
available
|
||||
|
||||
These extra variables provided and all new variables created into the `t-foreach`
|
||||
are only available in the scope of the `t-foreach`. If the variable exists outside
|
||||
the context of the `t-foreach`, the value is copied at the end of the foreach
|
||||
into the global context.
|
||||
These variables and all new variables created inside`t-foreach` and `t-for` are
|
||||
only available inside of the loop. If a variable existed outside the context of
|
||||
the loop, the assignment will affect the outer variable.
|
||||
|
||||
```xml
|
||||
<t t-set="existing_variable" t-value="false"/>
|
||||
@@ -408,7 +426,7 @@ into the global context.
|
||||
<!-- existing_variable and new_variable now true -->
|
||||
</p>
|
||||
|
||||
<!-- existing_variable always true -->
|
||||
<!-- existing_variable still true -->
|
||||
<!-- new_variable undefined -->
|
||||
```
|
||||
|
||||
@@ -471,18 +489,11 @@ are all equivalent:
|
||||
</t>
|
||||
```
|
||||
|
||||
If there is no `t-key` directive, Owl will use the index as a default key.
|
||||
|
||||
Note: the `t-foreach` directive only accepts arrays (lists) or objects. It does
|
||||
not work with other iterables, such as `Set`. However, it is only a matter of
|
||||
using the `...` javascript operator. For example:
|
||||
|
||||
```xml
|
||||
<t t-foreach="[...items]" t-as="item">...</t>
|
||||
```
|
||||
|
||||
The `...` operator will convert the `Set` (or any other iterables) into a list,
|
||||
which will work with Owl QWeb.
|
||||
The `t-key` directive is mandatory, and as mentioned should represent the object's
|
||||
identity. You may be tempted to use the loop index as a key, but keep in mind that
|
||||
this is only correct if items in the loop cannot be reordered. If this is not the
|
||||
case, using the index as the key can lead to bugs that are difficult to find, so
|
||||
use the index as the key only if you are sure items cannot be reordered.
|
||||
|
||||
### Sub Templates
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
ASTTCallBlock,
|
||||
ASTTEsc,
|
||||
ASTText,
|
||||
ASTTFor,
|
||||
ASTTForEach,
|
||||
ASTTif,
|
||||
ASTTKey,
|
||||
@@ -471,6 +472,8 @@ export class CodeGenerator {
|
||||
return this.compileTIf(ast, ctx);
|
||||
case ASTType.TForEach:
|
||||
return this.compileTForeach(ast, ctx);
|
||||
case ASTType.TFor:
|
||||
return this.compileTFor(ast, ctx);
|
||||
case ASTType.TKey:
|
||||
return this.compileTKey(ast, ctx);
|
||||
case ASTType.Multi:
|
||||
@@ -907,7 +910,7 @@ export class CodeGenerator {
|
||||
if (!ast.hasNoValue) {
|
||||
this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${loopVar}];`);
|
||||
}
|
||||
this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
|
||||
this.define(`key${this.target.loopLevel}`, compileExpr(ast.key));
|
||||
if (this.dev) {
|
||||
// Throw error on duplicate keys in dev mode
|
||||
this.helpers.add("OwlError");
|
||||
@@ -954,6 +957,46 @@ export class CodeGenerator {
|
||||
return block.varName;
|
||||
}
|
||||
|
||||
compileTFor(ast: ASTTFor, ctx: Context): string {
|
||||
let { block } = ctx;
|
||||
if (block) {
|
||||
this.insertAnchor(block);
|
||||
}
|
||||
block = this.createBlock(block, "list", ctx);
|
||||
this.target.loopLevel++;
|
||||
this.addLine(`ctx = Object.create(ctx);`);
|
||||
// Throw errors on duplicate keys in dev mode
|
||||
if (this.dev) {
|
||||
this.define(`keys${block.id}`, `new Set()`);
|
||||
}
|
||||
this.define(`c_block${block.id}`, "[]");
|
||||
const index = `i${this.target.loopLevel}`;
|
||||
this.addLine(`let ${index} = ${0};`);
|
||||
const binding = compileExpr(ast.binding);
|
||||
this.addLine(`for (${binding} of ${compileExpr(ast.iterable)}) {`);
|
||||
this.target.indentLevel++;
|
||||
this.define(`key${this.target.loopLevel}`, compileExpr(ast.key));
|
||||
if (this.dev) {
|
||||
// Throw error on duplicate keys in dev mode
|
||||
this.helpers.add("OwlError");
|
||||
this.addLine(
|
||||
`if (keys${block.id}.has(String(key${this.target.loopLevel}))) { throw new OwlError(\`Got duplicate key in t-for: \${key${this.target.loopLevel}}\`)}`
|
||||
);
|
||||
this.addLine(`keys${block.id}.add(String(key${this.target.loopLevel}));`);
|
||||
}
|
||||
const subCtx = createContext(ctx, { block, index });
|
||||
this.compileAST(ast.body, subCtx);
|
||||
this.addLine(`${index}++;`);
|
||||
this.target.indentLevel--;
|
||||
this.target.loopLevel--;
|
||||
this.addLine(`}`);
|
||||
if (!ctx.isLast) {
|
||||
this.addLine(`ctx = ctx.__proto__;`);
|
||||
}
|
||||
this.insertBlock("l", block, ctx);
|
||||
return block.varName;
|
||||
}
|
||||
|
||||
compileTKey(ast: ASTTKey, ctx: Context): string | null {
|
||||
const tKeyExpr = generateId("tKey_");
|
||||
this.define(tKeyExpr, compileExpr(ast.expr));
|
||||
|
||||
+42
-1
@@ -26,6 +26,7 @@ export const enum ASTType {
|
||||
TCallBlock,
|
||||
TTranslation,
|
||||
TPortal,
|
||||
TFor,
|
||||
}
|
||||
|
||||
export interface ASTText {
|
||||
@@ -104,7 +105,15 @@ export interface ASTTForEach {
|
||||
hasNoLast: boolean;
|
||||
hasNoIndex: boolean;
|
||||
hasNoValue: boolean;
|
||||
key: string | null;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface ASTTFor {
|
||||
type: ASTType.TFor;
|
||||
iterable: string;
|
||||
binding: string;
|
||||
body: AST;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface ASTTKey {
|
||||
@@ -183,6 +192,7 @@ export type AST =
|
||||
| ASTTCall
|
||||
| ASTTOut
|
||||
| ASTTForEach
|
||||
| ASTTFor
|
||||
| ASTTKey
|
||||
| ASTComponent
|
||||
| ASTSlot
|
||||
@@ -230,6 +240,7 @@ function parseNode(node: Node, ctx: ParsingContext): AST | null {
|
||||
return (
|
||||
parseTDebugLog(node, ctx) ||
|
||||
parseTForEach(node, ctx) ||
|
||||
parseTFor(node, ctx) ||
|
||||
parseTIf(node, ctx) ||
|
||||
parseTPortal(node, ctx) ||
|
||||
parseTCall(node, ctx) ||
|
||||
@@ -531,6 +542,36 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
|
||||
};
|
||||
}
|
||||
|
||||
function parseTFor(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (!node.hasAttribute("t-for")) {
|
||||
return null;
|
||||
}
|
||||
const binding = node.getAttribute("t-for")!;
|
||||
node.removeAttribute("t-for");
|
||||
const iterable = node.getAttribute("t-of") || "";
|
||||
node.removeAttribute("t-of");
|
||||
const key = node.getAttribute("t-key");
|
||||
if (!key) {
|
||||
throw new OwlError(
|
||||
`"Directive t-for should always be used with a t-key!" (expression: t-for="${binding}" t-of="${iterable}")`
|
||||
);
|
||||
}
|
||||
node.removeAttribute("t-key");
|
||||
const body = parseNode(node, ctx);
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: ASTType.TFor,
|
||||
iterable,
|
||||
binding,
|
||||
body,
|
||||
key,
|
||||
};
|
||||
}
|
||||
|
||||
function parseTKey(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (!node.hasAttribute("t-key")) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,670 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`t-for destructuring array items 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for ([ctx['key'],ctx['value']] of Object.entries({a:1,b:2})) {
|
||||
const key1 = ctx['key'];
|
||||
const b3 = text(\`(\`);
|
||||
const b4 = text(ctx['key']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['value']);
|
||||
const b7 = text(\`)\`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7]), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for destructuring array items: rest 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for ([ctx['head'],...ctx['tail']] of [[1,2,3],[4,5,6]]) {
|
||||
const key1 = ctx['head'];
|
||||
const b3 = text(\`(\`);
|
||||
const b4 = text(ctx['head']);
|
||||
const b5 = text(\`;\`);
|
||||
const b6 = text(ctx['tail']);
|
||||
const b7 = text(\`)\`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7]), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for destructuring object items 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for ({k:ctx['k'],v:ctx['v']} of [{k:'a',v:1},{k:'b',v:2}]) {
|
||||
const key1 = ctx['k'];
|
||||
const b3 = text(\`(\`);
|
||||
const b4 = text(ctx['k']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['v']);
|
||||
const b7 = text(\`)\`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7]), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for destructuring object items: rest 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for ({k:ctx['k'],v:ctx['v']} of [{k:'a',v:1},{k:'b',v:2}]) {
|
||||
const key1 = ctx['k'];
|
||||
const b3 = text(\`(\`);
|
||||
const b4 = text(ctx['k']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['v']);
|
||||
const b7 = text(\`)\`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7]), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for does not pollute the rendering context 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['item'] of [1]) {
|
||||
const key1 = ctx['item'];
|
||||
c_block2[i1] = withKey(text(ctx['item']), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for iterate on items (on a element node) 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<span><block-text-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['item'] of [1,2]) {
|
||||
const key1 = ctx['item'];
|
||||
let txt1 = ctx['item'];
|
||||
c_block2[i1] = withKey(block3([txt1]), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for iterate, Map param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for ([ctx['key'],ctx['value']] of ctx['map']) {
|
||||
const key1 = ctx['key'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['key']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['value']);
|
||||
const b7 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7]), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for iterate, Set param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['item'] of ctx['set']) {
|
||||
const key1 = ctx['item'];
|
||||
c_block1[i1] = withKey(text(ctx['item']), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for iterate, generator param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['item'] of ctx['gen']()) {
|
||||
const key1 = ctx['item'];
|
||||
c_block1[i1] = withKey(text(ctx['item']), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for iterate, iterable param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['item'] of ctx['map'].values()) {
|
||||
const key1 = ctx['item'];
|
||||
c_block1[i1] = withKey(text(ctx['item']), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for nested destructuring 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for ([ctx['key'],{left:ctx['left'],right:ctx['right']}] of Object.entries(ctx['obj'])) {
|
||||
const key1 = ctx['key'];
|
||||
const b3 = text(\`(\`);
|
||||
const b4 = text(ctx['key']);
|
||||
const b5 = text(\`: [\`);
|
||||
const b6 = text(ctx['left']);
|
||||
const b7 = text(\`, \`);
|
||||
const b8 = text(ctx['right']);
|
||||
const b9 = text(\`])\`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for simple iteration (in a node) 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['item'] of [3,2,1]) {
|
||||
const key1 = ctx['item'];
|
||||
c_block2[i1] = withKey(text(ctx['item']), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for simple iteration 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['item'] of [3,2,1]) {
|
||||
const key1 = ctx['item'];
|
||||
c_block1[i1] = withKey(text(ctx['item']), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for simple iteration with two nodes inside 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
let block3 = createBlock(\`<span>a<block-text-0/></span>\`);
|
||||
let block4 = createBlock(\`<span>b<block-text-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['item'] of [3,2,1]) {
|
||||
const key1 = ctx['item'];
|
||||
let txt1 = ctx['item'];
|
||||
const b3 = block3([txt1]);
|
||||
let txt2 = ctx['item'];
|
||||
const b4 = block4([txt2]);
|
||||
c_block1[i1] = withKey(multi([b3, b4]), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for t-call with body in t-for in t-for 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue, withKey } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`sub\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/><span>[<block-text-0/>][<block-text-1/>][<block-text-2/>]</span></div>\`);
|
||||
let block6 = createBlock(\`<span><block-text-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['a'] of ctx['numbers']) {
|
||||
const key1 = ctx['a'];
|
||||
ctx = Object.create(ctx);
|
||||
const c_block4 = [];
|
||||
let i2 = 0;
|
||||
for (ctx['b'] of ctx['letters']) {
|
||||
const key2 = ctx['b'];
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
setContextValue(ctx, \\"c\\", 'x'+'_'+ctx['a']+'_'+ctx['b']);
|
||||
c_block4[i2] = withKey(callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}__\${key2}\`), key2);
|
||||
ctx = ctx.__proto__;
|
||||
i2++;
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
const b4 = list(c_block4);
|
||||
let txt1 = ctx['c'];
|
||||
const b6 = block6([txt1]);
|
||||
c_block2[i1] = withKey(multi([b4, b6]), key1);
|
||||
i1++;
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
const b2 = list(c_block2);
|
||||
let txt2 = ctx['a'];
|
||||
let txt3 = ctx['b'];
|
||||
let txt4 = ctx['c'];
|
||||
return block1([txt2, txt3, txt4], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for t-call with body in t-for in t-for 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = text(\` [\`);
|
||||
const b3 = text(ctx['a']);
|
||||
const b4 = text(\`] [\`);
|
||||
const b5 = text(ctx['b']);
|
||||
const b6 = text(\`] [\`);
|
||||
const b7 = text(ctx['c']);
|
||||
const b8 = text(\`] \`);
|
||||
return multi([b2, b3, b4, b5, b6, b7, b8]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for t-call without body in t-for in t-for 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
const callTemplate_1 = app.getTemplate(\`sub\`);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/><span>[<block-text-0/>][<block-text-1/>][<block-text-2/>]</span></div>\`);
|
||||
let block6 = createBlock(\`<span><block-text-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['a'] of ctx['numbers']) {
|
||||
const key1 = ctx['a'];
|
||||
ctx = Object.create(ctx);
|
||||
const c_block4 = [];
|
||||
let i2 = 0;
|
||||
for (ctx['b'] of ctx['letters']) {
|
||||
const key2 = ctx['b'];
|
||||
c_block4[i2] = withKey(callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}__\${key2}\`), key2);
|
||||
i2++;
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
const b4 = list(c_block4);
|
||||
let txt1 = ctx['c'];
|
||||
const b6 = block6([txt1]);
|
||||
c_block2[i1] = withKey(multi([b4, b6]), key1);
|
||||
i1++;
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
const b2 = list(c_block2);
|
||||
let txt2 = ctx['a'];
|
||||
let txt3 = ctx['b'];
|
||||
let txt4 = ctx['c'];
|
||||
return block1([txt2, txt3, txt4], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for t-call without body in t-for in t-for 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"c\\", 'x'+'_'+ctx['a']+'_'+ctx['b']);
|
||||
const b2 = text(\` [\`);
|
||||
const b3 = text(ctx['a']);
|
||||
const b4 = text(\`] [\`);
|
||||
const b5 = text(ctx['b']);
|
||||
const b6 = text(\`] [\`);
|
||||
const b7 = text(ctx['c']);
|
||||
const b8 = text(\`] \`);
|
||||
return multi([b2, b3, b4, b5, b6, b7, b8]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for t-for in t-for 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['number'] of ctx['numbers']) {
|
||||
const key1 = ctx['number'];
|
||||
ctx = Object.create(ctx);
|
||||
const c_block3 = [];
|
||||
let i2 = 0;
|
||||
for (ctx['letter'] of ctx['letters']) {
|
||||
const key2 = ctx['letter'];
|
||||
const b5 = text(\` [\`);
|
||||
const b6 = text(ctx['number']);
|
||||
const b7 = text(ctx['letter']);
|
||||
const b8 = text(\`] \`);
|
||||
c_block3[i2] = withKey(multi([b5, b6, b7, b8]), key2);
|
||||
i2++;
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
c_block2[i1] = withKey(list(c_block3), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for t-for in t-foreach 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { prepareList, withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['numbers']);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`number\`] = v_block2[i1];
|
||||
const key1 = ctx['number'];
|
||||
ctx = Object.create(ctx);
|
||||
const c_block3 = [];
|
||||
let i2 = 0;
|
||||
for (ctx['letter'] of ctx['letters']) {
|
||||
const key2 = ctx['letter'];
|
||||
const b5 = text(\` [\`);
|
||||
const b6 = text(ctx['number']);
|
||||
const b7 = text(ctx['letter']);
|
||||
const b8 = text(\`] \`);
|
||||
c_block3[i2] = withKey(multi([b5, b6, b7, b8]), key2);
|
||||
i2++;
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
c_block2[i1] = withKey(list(c_block3), key1);
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for t-for with t-if inside (no external node) 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
let block3 = createBlock(\`<span><block-text-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for ({id:ctx['id'],text:ctx['text']} of ctx['elems']) {
|
||||
const key1 = ctx['id'];
|
||||
let b3;
|
||||
if (ctx['id']<3) {
|
||||
let txt1 = ctx['text'];
|
||||
b3 = block3([txt1]);
|
||||
}
|
||||
c_block1[i1] = withKey(multi([b3]), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for t-for with t-if inside 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block4 = createBlock(\`<span><block-text-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for ({id:ctx['id'],text:ctx['text']} of ctx['elems']) {
|
||||
const key1 = ctx['id'];
|
||||
let b4;
|
||||
if (ctx['id']<3) {
|
||||
let txt1 = ctx['text'];
|
||||
b4 = block4([txt1]);
|
||||
}
|
||||
c_block2[i1] = withKey(multi([b4]), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for t-foreach in t-for 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { prepareList, withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['number'] of ctx['numbers']) {
|
||||
const key1 = ctx['number'];
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block3, v_block3, l_block3, c_block3] = prepareList(ctx['letters']);;
|
||||
for (let i2 = 0; i2 < l_block3; i2++) {
|
||||
ctx[\`letter\`] = v_block3[i2];
|
||||
const key2 = ctx['letter'];
|
||||
const b5 = text(\` [\`);
|
||||
const b6 = text(ctx['number']);
|
||||
const b7 = text(ctx['letter']);
|
||||
const b8 = text(\`] \`);
|
||||
c_block3[i2] = withKey(multi([b5, b6, b7, b8]), key2);
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
c_block2[i1] = withKey(list(c_block3), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for t-key on t-for 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<span/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['thing'] of ctx['things']) {
|
||||
const key1 = ctx['thing'];
|
||||
c_block2[i1] = withKey(block3(), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-for throws error if invalid loop expression 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<span/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['item'] of ctx['abc']) {
|
||||
const key1 = ctx['item'];
|
||||
const tKey_1 = ctx['item'];
|
||||
c_block2[i1] = withKey(block3(), tKey_1 + key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -335,6 +335,99 @@ exports[`t-set t-set evaluates an expression only once 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-set t-set outside modified in t-for 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue, withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/><p>EndLoop: <block-text-0/></p></div>\`);
|
||||
let block3 = createBlock(\`<p>InLoop: <block-text-0/></p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"iter\\", 0);
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['val'] of ['a','b']) {
|
||||
const key1 = ctx['val'];
|
||||
let txt1 = ctx['iter'];
|
||||
c_block2[i1] = withKey(block3([txt1]), key1);
|
||||
setContextValue(ctx, \\"iter\\", ctx['iter']+1);
|
||||
i1++;
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
const b2 = list(c_block2);
|
||||
let txt2 = ctx['iter'];
|
||||
return block1([txt2], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-set t-set outside modified in t-for increment-after operator 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue, withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/><p>EndLoop: <block-text-0/></p></div>\`);
|
||||
let block3 = createBlock(\`<p>InLoop: <block-text-0/></p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"iter\\", 0);
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['val'] of ['a','b']) {
|
||||
const key1 = ctx['val'];
|
||||
let txt1 = ctx['iter'];
|
||||
c_block2[i1] = withKey(block3([txt1]), key1);
|
||||
setContextValue(ctx, \\"iter\\", ctx['iter']++);
|
||||
i1++;
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
const b2 = list(c_block2);
|
||||
let txt2 = ctx['iter'];
|
||||
return block1([txt2], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-set t-set outside modified in t-for increment-before operator 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue, withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/><p>EndLoop: <block-text-0/></p></div>\`);
|
||||
let block3 = createBlock(\`<p>InLoop: <block-text-0/></p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"iter\\", 0);
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['val'] of ['a','b']) {
|
||||
const key1 = ctx['val'];
|
||||
let txt1 = ctx['iter'];
|
||||
c_block2[i1] = withKey(block3([txt1]), key1);
|
||||
setContextValue(ctx, \\"iter\\", ++ctx['iter']);
|
||||
i1++;
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
const b2 = list(c_block2);
|
||||
let txt2 = ctx['iter'];
|
||||
return block1([txt2], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-set t-set outside modified in t-foreach 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -454,6 +547,35 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-set t-set should reuse variable if possible: for..of 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue, withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<div><span>v<block-text-0/></span></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"v\\", 1);
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['elem'] of ctx['list']) {
|
||||
const key1 = ctx['elem_index'];
|
||||
let txt1 = ctx['v'];
|
||||
setContextValue(ctx, \\"v\\", ctx['elem']);
|
||||
c_block2[i1] = withKey(block3([txt1]), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-set t-set with content and sub t-esc 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
import {
|
||||
renderToBdom,
|
||||
renderToString,
|
||||
snapshotEverything,
|
||||
TestContext,
|
||||
makeTestFixture,
|
||||
} from "../helpers";
|
||||
import { mount, patch } from "../../src/runtime/blockdom";
|
||||
|
||||
snapshotEverything();
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// t-for
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
describe("t-for", () => {
|
||||
test("simple iteration", () => {
|
||||
const template = `<t t-for="item" t-of="[3, 2, 1]" t-key="item"><t t-esc="item"/></t>`;
|
||||
expect(renderToString(template)).toBe("321");
|
||||
});
|
||||
|
||||
test("simple iteration with two nodes inside", () => {
|
||||
const template = `
|
||||
<t t-for="item" t-of="[3, 2, 1]" t-key="item">
|
||||
<span>a<t t-esc="item"/></span>
|
||||
<span>b<t t-esc="item"/></span>
|
||||
</t>`;
|
||||
const expected =
|
||||
"<span>a3</span><span>b3</span><span>a2</span><span>b2</span><span>a1</span><span>b1</span>";
|
||||
expect(renderToString(template)).toBe(expected);
|
||||
});
|
||||
|
||||
test("destructuring array items", () => {
|
||||
const template = `<t t-for="[key, value]" t-of="Object.entries({ a: 1, b: 2 })" t-key="key">(<t t-esc="key"/>: <t t-esc="value"/>)</t>`;
|
||||
expect(renderToString(template)).toBe("(a: 1)(b: 2)");
|
||||
});
|
||||
|
||||
test("destructuring array items: rest", () => {
|
||||
const template = `<t t-for="[head, ...tail]" t-of="[[1, 2, 3], [4, 5, 6]]" t-key="head">(<t t-esc="head"/>;<t t-esc="tail"/>)</t>`;
|
||||
expect(renderToString(template)).toBe("(1;2,3)(4;5,6)");
|
||||
});
|
||||
|
||||
test("destructuring object items", () => {
|
||||
const template = `<t t-for="{ k, v }" t-of="[{ k: 'a', v: 1 }, { k: 'b', v: 2 }]" t-key="k">(<t t-esc="k"/>: <t t-esc="v"/>)</t>`;
|
||||
expect(renderToString(template)).toBe("(a: 1)(b: 2)");
|
||||
});
|
||||
|
||||
test("destructuring object items: rest", () => {
|
||||
const template = `<t t-for="{ k, v }" t-of="[{ k: 'a', v: 1 }, { k: 'b', v: 2 }]" t-key="k">(<t t-esc="k"/>: <t t-esc="v"/>)</t>`;
|
||||
expect(renderToString(template)).toBe("(a: 1)(b: 2)");
|
||||
});
|
||||
|
||||
test("nested destructuring", () => {
|
||||
const template = `<t t-for="[key, {left, right}]" t-of="Object.entries(obj)" t-key="key">(<t t-esc="key"/>: [<t t-esc="left"/>, <t t-esc="right"/>])</t>`;
|
||||
expect(
|
||||
renderToString(template, {
|
||||
obj: {
|
||||
a: { left: 1, right: 2 },
|
||||
b: { left: 3, right: 4 },
|
||||
},
|
||||
})
|
||||
).toBe("(a: [1, 2])(b: [3, 4])");
|
||||
});
|
||||
|
||||
test("t-key on t-for", async () => {
|
||||
const template = `
|
||||
<div>
|
||||
<t t-for="thing" t-of="things" t-key="thing">
|
||||
<span/>
|
||||
</t>
|
||||
</div>`;
|
||||
|
||||
const fixture = makeTestFixture();
|
||||
|
||||
const vnode1 = renderToBdom(template, { things: [1, 2] });
|
||||
mount(vnode1, fixture);
|
||||
let elm = fixture;
|
||||
expect(elm.innerHTML).toBe("<div><span></span><span></span></div>");
|
||||
const first = elm.querySelectorAll("span")[0];
|
||||
const second = elm.querySelectorAll("span")[1];
|
||||
|
||||
const vnode2 = renderToBdom(template, { things: [2, 1] });
|
||||
patch(vnode1, vnode2);
|
||||
|
||||
expect(elm.innerHTML).toBe("<div><span></span><span></span></div>");
|
||||
expect(first).toBe(elm.querySelectorAll("span")[1]);
|
||||
expect(second).toBe(elm.querySelectorAll("span")[0]);
|
||||
});
|
||||
|
||||
test("simple iteration (in a node)", () => {
|
||||
const template = `
|
||||
<div>
|
||||
<t t-for="item" t-of="[3, 2, 1]" t-key="item"><t t-esc="item"/></t>
|
||||
</div>`;
|
||||
expect(renderToString(template)).toBe("<div>321</div>");
|
||||
});
|
||||
|
||||
test("iterate on items (on a element node)", () => {
|
||||
const template = `
|
||||
<div>
|
||||
<span t-for="item" t-of="[1, 2]" t-key="item"><t t-esc="item"/></span>
|
||||
</div>`;
|
||||
const expected = `<div><span>1</span><span>2</span></div>`;
|
||||
expect(renderToString(template)).toBe(expected);
|
||||
});
|
||||
|
||||
test("iterate, Map param", () => {
|
||||
const template = `
|
||||
<t t-for="[key, value]" t-of="map" t-key="key">
|
||||
[<t t-esc="key"/>: <t t-esc="value"/>]
|
||||
</t>`;
|
||||
const expected = ` [a: 1] [b: 2] [c: 3] `;
|
||||
const context = {
|
||||
map: new Map([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
["c", 3],
|
||||
]),
|
||||
};
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("iterate, Set param", () => {
|
||||
const template = `
|
||||
<t t-for="item" t-of="set" t-key="item">
|
||||
<t t-esc="item"/>
|
||||
</t>`;
|
||||
const expected = `123`;
|
||||
const context = { set: new Set([1, 2, 3]) };
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("iterate, iterable param", () => {
|
||||
const template = `
|
||||
<t t-for="item" t-of="map.values()" t-key="item">
|
||||
<t t-esc="item"/>
|
||||
</t>`;
|
||||
const expected = `123`;
|
||||
const context = {
|
||||
map: new Map([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
["c", 3],
|
||||
]),
|
||||
};
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("iterate, generator param", () => {
|
||||
const template = `
|
||||
<t t-for="item" t-of="gen()" t-key="item">
|
||||
<t t-esc="item"/>
|
||||
</t>`;
|
||||
const expected = `123`;
|
||||
const context = {
|
||||
*gen() {
|
||||
yield 1;
|
||||
yield 2;
|
||||
yield 3;
|
||||
},
|
||||
};
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("does not pollute the rendering context", () => {
|
||||
const template = `
|
||||
<div>
|
||||
<t t-for="item" t-of="[1]" t-key="item"><t t-esc="item"/></t>
|
||||
</div>`;
|
||||
const context = { __owl__: {} };
|
||||
renderToString(template, context);
|
||||
expect(Object.keys(context)).toEqual(["__owl__"]);
|
||||
});
|
||||
|
||||
test("t-for in t-for", () => {
|
||||
const template = `
|
||||
<div>
|
||||
<t t-for="number" t-of="numbers" t-key="number">
|
||||
<t t-for="letter" t-of="letters" t-key="letter">
|
||||
[<t t-esc="number"/><t t-esc="letter"/>]
|
||||
</t>
|
||||
</t>
|
||||
</div>`;
|
||||
|
||||
const context = { numbers: [1, 2, 3], letters: ["a", "b"] };
|
||||
const expected = "<div> [1a] [1b] [2a] [2b] [3a] [3b] </div>";
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("t-for in t-foreach", () => {
|
||||
const template = `
|
||||
<div>
|
||||
<t t-foreach="numbers" t-as="number" t-key="number">
|
||||
<t t-for="letter" t-of="letters" t-key="letter">
|
||||
[<t t-esc="number"/><t t-esc="letter"/>]
|
||||
</t>
|
||||
</t>
|
||||
</div>`;
|
||||
|
||||
const context = { numbers: [1, 2, 3], letters: ["a", "b"] };
|
||||
const expected = "<div> [1a] [1b] [2a] [2b] [3a] [3b] </div>";
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("t-foreach in t-for", () => {
|
||||
const template = `
|
||||
<div>
|
||||
<t t-for="number" t-of="numbers" t-key="number">
|
||||
<t t-foreach="letters" t-as="letter" t-key="letter">
|
||||
[<t t-esc="number"/><t t-esc="letter"/>]
|
||||
</t>
|
||||
</t>
|
||||
</div>`;
|
||||
|
||||
const context = { numbers: [1, 2, 3], letters: ["a", "b"] };
|
||||
const expected = "<div> [1a] [1b] [2a] [2b] [3a] [3b] </div>";
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("t-call without body in t-for in t-for", () => {
|
||||
const context = new TestContext();
|
||||
const sub = `
|
||||
<t>
|
||||
<t t-set="c" t-value="'x' + '_' + a + '_'+ b" />
|
||||
[<t t-esc="a" />]
|
||||
[<t t-esc="b" />]
|
||||
[<t t-esc="c" />]
|
||||
</t>`;
|
||||
|
||||
const main = `
|
||||
<div>
|
||||
<t t-for="a" t-of="numbers" t-key="a">
|
||||
<t t-for="b" t-of="letters" t-key="b">
|
||||
<t t-call="sub" />
|
||||
</t>
|
||||
<span t-esc="c"/>
|
||||
</t>
|
||||
<span>[<t t-esc="a" />][<t t-esc="b" />][<t t-esc="c" />]</span>
|
||||
</div>`;
|
||||
|
||||
context.addTemplate("sub", sub);
|
||||
context.addTemplate("main", main);
|
||||
|
||||
const ctx = { numbers: [1, 2, 3], letters: ["a", "b"] };
|
||||
const expected =
|
||||
"<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>";
|
||||
expect(context.renderToString("main", ctx)).toBe(expected);
|
||||
});
|
||||
|
||||
test("t-call with body in t-for in t-for", () => {
|
||||
const context = new TestContext();
|
||||
const sub = `
|
||||
<t>
|
||||
[<t t-esc="a" />]
|
||||
[<t t-esc="b" />]
|
||||
[<t t-esc="c" />]
|
||||
</t>`;
|
||||
|
||||
const main = `
|
||||
<div>
|
||||
<t t-for="a" t-of="numbers" t-key="a">
|
||||
<t t-for="b" t-of="letters" t-key="b">
|
||||
<t t-call="sub" >
|
||||
<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>`;
|
||||
|
||||
context.addTemplate("sub", sub);
|
||||
context.addTemplate("main", main);
|
||||
|
||||
const ctx = { numbers: [1, 2, 3], letters: ["a", "b"] };
|
||||
const expected =
|
||||
"<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>";
|
||||
expect(context.renderToString("main", ctx)).toBe(expected);
|
||||
});
|
||||
|
||||
test("throws error if invalid loop expression", () => {
|
||||
const test = `<div><t t-for="item" t-of="abc" t-key="item"><span t-key="item"/></t></div>`;
|
||||
expect(() => renderToString(test)).toThrow("ctx.abc is not iterable");
|
||||
});
|
||||
|
||||
test("t-for with t-if inside", () => {
|
||||
const template = `
|
||||
<div>
|
||||
<t t-for="{ id, text }" t-of="elems" t-key="id">
|
||||
<span t-if="id lt 3"><t t-esc="text"/></span>
|
||||
</t>
|
||||
</div>`;
|
||||
const ctx = {
|
||||
elems: [
|
||||
{ id: 1, text: "a" },
|
||||
{ id: 2, text: "b" },
|
||||
{ id: 3, text: "c" },
|
||||
],
|
||||
};
|
||||
expect(renderToString(template, ctx)).toBe("<div><span>a</span><span>b</span></div>");
|
||||
});
|
||||
|
||||
test("t-for with t-if inside (no external node)", () => {
|
||||
const template = `
|
||||
<t t-for="{ id, text }" t-of="elems" t-key="id">
|
||||
<span t-if="id lt 3"><t t-esc="text"/></span>
|
||||
</t>`;
|
||||
const ctx = {
|
||||
elems: [
|
||||
{ id: 1, text: "a" },
|
||||
{ id: 2, text: "b" },
|
||||
{ id: 3, text: "c" },
|
||||
],
|
||||
};
|
||||
expect(renderToString(template, ctx)).toBe("<span>a</span><span>b</span>");
|
||||
});
|
||||
});
|
||||
@@ -122,6 +122,19 @@ describe("t-set", () => {
|
||||
expect(renderToString(template, { list: ["a", "b"] })).toBe(expected);
|
||||
});
|
||||
|
||||
test("t-set should reuse variable if possible: for..of", () => {
|
||||
const template = `
|
||||
<div>
|
||||
<t t-set="v" t-value="1"/>
|
||||
<div t-for="elem" t-of="list" t-key="elem_index">
|
||||
<span>v<t t-esc="v"/></span>
|
||||
<t t-set="v" t-value="elem"/>
|
||||
</div>
|
||||
</div>`;
|
||||
const expected = "<div><div><span>v1</span></div><div><span>va</span></div></div>";
|
||||
expect(renderToString(template, { list: ["a", "b"] })).toBe(expected);
|
||||
});
|
||||
|
||||
test("t-set with content and sub t-esc", () => {
|
||||
const template = `
|
||||
<div>
|
||||
@@ -232,6 +245,22 @@ describe("t-set", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("t-set outside modified in t-for", async () => {
|
||||
const template = `
|
||||
<div>
|
||||
<t t-set="iter" t-value="0"/>
|
||||
<t t-for="val" t-of="['a','b']" t-key="val">
|
||||
<p>InLoop: <t t-esc="iter"/></p>
|
||||
<t t-set="iter" t-value="iter + 1"/>
|
||||
</t>
|
||||
<p>EndLoop: <t t-esc="iter"/></p>
|
||||
</div>
|
||||
`;
|
||||
expect(renderToString(template)).toBe(
|
||||
"<div><p>InLoop: 0</p><p>InLoop: 1</p><p>EndLoop: 2</p></div>"
|
||||
);
|
||||
});
|
||||
|
||||
test("t-set outside modified in t-foreach increment-after operator", async () => {
|
||||
const template = `
|
||||
<div>
|
||||
@@ -248,6 +277,22 @@ describe("t-set", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("t-set outside modified in t-for increment-after operator", async () => {
|
||||
const template = `
|
||||
<div>
|
||||
<t t-set="iter" t-value="0"/>
|
||||
<t t-for="val" t-of="['a','b']" t-key="val">
|
||||
<p>InLoop: <t t-esc="iter"/></p>
|
||||
<t t-set="iter" t-value="iter++"/>
|
||||
</t>
|
||||
<p>EndLoop: <t t-esc="iter"/></p>
|
||||
</div>
|
||||
`;
|
||||
expect(renderToString(template)).toBe(
|
||||
"<div><p>InLoop: 0</p><p>InLoop: 0</p><p>EndLoop: 0</p></div>"
|
||||
);
|
||||
});
|
||||
|
||||
test("t-set outside modified in t-foreach increment-before operator", async () => {
|
||||
const template = `
|
||||
<div>
|
||||
@@ -264,6 +309,22 @@ describe("t-set", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("t-set outside modified in t-for increment-before operator", async () => {
|
||||
const template = `
|
||||
<div>
|
||||
<t t-set="iter" t-value="0"/>
|
||||
<t t-for="val" t-of="['a','b']" t-key="val">
|
||||
<p>InLoop: <t t-esc="iter"/></p>
|
||||
<t t-set="iter" t-value="++iter"/>
|
||||
</t>
|
||||
<p>EndLoop: <t t-esc="iter"/></p>
|
||||
</div>
|
||||
`;
|
||||
expect(renderToString(template)).toBe(
|
||||
"<div><p>InLoop: 0</p><p>InLoop: 1</p><p>EndLoop: 0</p></div>"
|
||||
);
|
||||
});
|
||||
|
||||
test("t-set can't alter from within callee", async () => {
|
||||
const context = new TestContext();
|
||||
const sub = `<div><t t-esc="iter"/><t t-set="iter" t-value="'called'"/><t t-esc="iter"/></div>`;
|
||||
|
||||
@@ -0,0 +1,493 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`list of components components in a node in a t-for 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, [\\"item\\"]);
|
||||
|
||||
let block1 = createBlock(\`<div><ul><block-child-0/></ul></div>\`);
|
||||
let block3 = createBlock(\`<li><block-child-0/></li>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['item'] of ctx['items']) {
|
||||
const key1 = 'li_'+ctx['item'];
|
||||
const b4 = comp1({item: ctx['item']}, key + \`__1__\${key1}\`, node, this, null);
|
||||
c_block2[i1] = withKey(block3([], [b4]), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components components in a node in a t-for 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let txt1 = ctx['props'].item;
|
||||
return block1([txt1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components crash on duplicate key in dev mode 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { OwlError, withKey } = helpers;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const keys1 = new Set();
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['item'] of [1,2]) {
|
||||
const key1 = 'child';
|
||||
if (keys1.has(String(key1))) { throw new OwlError(\`Got duplicate key in t-for: \${key1}\`)}
|
||||
keys1.add(String(key1));
|
||||
const props1 = {};
|
||||
helpers.validateProps(\`Child\`, props1, this);
|
||||
c_block1[i1] = withKey(comp1(props1, key + \`__1__\${key1}\`, node, this, null), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components crash on duplicate key in dev mode 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components crash when using object as keys that serialize to the same string 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { OwlError, withKey } = helpers;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const keys1 = new Set();
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['item'] of [{},{}]) {
|
||||
const key1 = ctx['item'];
|
||||
if (keys1.has(String(key1))) { throw new OwlError(\`Got duplicate key in t-for: \${key1}\`)}
|
||||
keys1.add(String(key1));
|
||||
const props1 = {};
|
||||
helpers.validateProps(\`Child\`, props1, this);
|
||||
c_block1[i1] = withKey(comp1(props1, key + \`__1__\${key1}\`, node, this, null), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components crash when using object as keys that serialize to the same string 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components list of sub components inside other nodes 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
const comp1 = app.createComponent(\`SubComponent\`, true, false, false, []);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['blip'] of ctx['state'].blips) {
|
||||
const key1 = ctx['blip'].id;
|
||||
const b4 = comp1({}, key + \`__1__\${key1}\`, node, this, null);
|
||||
c_block2[i1] = withKey(block3([], [b4]), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components list of sub components inside other nodes 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<span>asdf</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components order is correct when slots are not of same type 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { capture, markRaw } = helpers;
|
||||
const comp1 = app.createComponent(\`Child\`, true, true, false, []);
|
||||
|
||||
let block2 = createBlock(\`<div>A</div>\`);
|
||||
|
||||
function slot1(ctx, node, key = \\"\\") {
|
||||
let b2;
|
||||
if (!ctx['state'].active) {
|
||||
b2 = block2();
|
||||
}
|
||||
return multi([b2]);
|
||||
}
|
||||
|
||||
function slot2(ctx, node, key = \\"\\") {
|
||||
return text(\`B\`);
|
||||
}
|
||||
|
||||
function slot3(ctx, node, key = \\"\\") {
|
||||
return text(\`C\`);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx1 = capture(ctx);
|
||||
return comp1({slots: markRaw({'a': {__render: slot1.bind(this), __ctx: ctx1, active: !ctx['state'].active}, 'b': {__render: slot2.bind(this), __ctx: ctx1, active: true}, 'c': {__render: slot3.bind(this), __ctx: ctx1, active: ctx['state'].active}})}, key + \`__1\`, node, this, null);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components order is correct when slots are not of same type 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { callSlot, withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['slotName'] of ctx['slotNames']) {
|
||||
const key1 = ctx['slotName'];
|
||||
const slot1 = (ctx['slotName']);
|
||||
c_block1[i1] = withKey(toggler(slot1, callSlot(ctx, node, key1 + \`__1__\${key1}\`, slot1, true, {})), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components reconciliation alg works for t-for in t-for 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, [\\"blip\\"]);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['section'] of ctx['state'].s) {
|
||||
const key1 = ctx['section'];
|
||||
ctx = Object.create(ctx);
|
||||
const c_block3 = [];
|
||||
let i2 = 0;
|
||||
for (ctx['blip'] of ctx['section'].blips) {
|
||||
const key2 = ctx['blip'];
|
||||
c_block3[i2] = withKey(comp1({blip: ctx['blip']}, key + \`__1__\${key1}__\${key2}\`, node, this, null), key2);
|
||||
i2++;
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
c_block2[i1] = withKey(list(c_block3), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components reconciliation alg works for t-for in t-for 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let txt1 = ctx['props'].blip;
|
||||
return block1([txt1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components reconciliation alg works for t-for in t-for, 2 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, [\\"row\\",\\"col\\"]);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<p><block-child-0/></p>\`);
|
||||
let block5 = createBlock(\`<p><block-child-0/></p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['row'] of ctx['state'].rows) {
|
||||
const key1 = ctx['row'];
|
||||
ctx = Object.create(ctx);
|
||||
const c_block4 = [];
|
||||
let i2 = 0;
|
||||
for (ctx['col'] of ctx['state'].cols) {
|
||||
const key2 = ctx['col'];
|
||||
const b6 = comp1({row: ctx['row'],col: ctx['col']}, key + \`__1__\${key1}__\${key2}\`, node, this, null);
|
||||
c_block4[i2] = withKey(block5([], [b6]), key2);
|
||||
i2++;
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
const b4 = list(c_block4);
|
||||
c_block2[i1] = withKey(block3([], [b4]), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components reconciliation alg works for t-for in t-for, 2 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let txt1 = ctx['props'].row+'_'+ctx['props'].col;
|
||||
return block1([txt1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components simple list 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, [\\"value\\"]);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block1 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['elem'] of ctx['state'].elems) {
|
||||
const key1 = ctx['elem'].id;
|
||||
c_block1[i1] = withKey(comp1({value: ctx['elem'].value}, key + \`__1__\${key1}\`, node, this, null), key1);
|
||||
i1++;
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components simple list 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<span><block-text-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let txt1 = ctx['props'].value;
|
||||
return block1([txt1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components sub components rendered in a loop 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, [\\"n\\"]);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['number'] of ctx['state'].numbers) {
|
||||
const key1 = ctx['number'];
|
||||
c_block2[i1] = withKey(comp1({n: ctx['number']}, key + \`__1__\${key1}\`, node, this, null), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components sub components rendered in a loop 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<p><block-text-0/></p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let txt1 = ctx['props'].n;
|
||||
return block1([txt1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components sub components with some state rendered in a loop 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, []);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['number'] of ctx['state'].numbers) {
|
||||
const key1 = ctx['number'];
|
||||
c_block2[i1] = withKey(comp1({}, key + \`__1__\${key1}\`, node, this, null), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components sub components with some state rendered in a loop 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<p><block-text-0/></p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let txt1 = ctx['state'].n;
|
||||
return block1([txt1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components switch component position 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, [\\"key\\"]);
|
||||
|
||||
let block1 = createBlock(\`<span><block-child-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['c'] of ctx['clist']) {
|
||||
const key1 = ctx['c'];
|
||||
c_block2[i1] = withKey(comp1({key: ctx['c']}, key + \`__1__\${key1}\`, node, this, null), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components switch component position 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let txt1 = ctx['props'].key;
|
||||
return block1([txt1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components t-for with t-component, and update 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { withKey } = helpers;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, [\\"val\\"]);
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const c_block2 = [];
|
||||
let i1 = 0;
|
||||
for (ctx['n'] of [0,1]) {
|
||||
const key1 = ctx['n'];
|
||||
c_block2[i1] = withKey(comp1({val: ctx['n']}, key + \`__1__\${key1}\`, node, this, null), key1);
|
||||
i1++;
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components t-for with t-component, and update 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<span><block-text-0/><block-text-1/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let txt1 = ctx['state'].val;
|
||||
let txt2 = ctx['props'].val;
|
||||
return block1([txt1, txt2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -0,0 +1,391 @@
|
||||
import { App, Component, mount, onMounted, useState, xml } from "../../src/index";
|
||||
import {
|
||||
makeTestFixture,
|
||||
nextAppError,
|
||||
nextTick,
|
||||
snapshotEverything,
|
||||
useLogLifecycle,
|
||||
} from "../helpers";
|
||||
|
||||
snapshotEverything();
|
||||
|
||||
let originalconsoleWarn = console.warn;
|
||||
let mockConsoleWarn: any;
|
||||
|
||||
let fixture: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
mockConsoleWarn = jest.fn(() => {});
|
||||
console.warn = mockConsoleWarn;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
console.warn = originalconsoleWarn;
|
||||
});
|
||||
|
||||
describe("list of components", () => {
|
||||
test("simple list", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<span><t t-esc="props.value"/></span>`;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<t t-for="elem" t-of="state.elems" t-key="elem.id">
|
||||
<Child value="elem.value"/>
|
||||
</t>`;
|
||||
static components = { Child };
|
||||
|
||||
state = useState({
|
||||
elems: [
|
||||
{ id: 1, value: "a" },
|
||||
{ id: 2, value: "b" },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("<span>a</span><span>b</span>");
|
||||
parent.state.elems.push({ id: 4, value: "d" });
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<span>a</span><span>b</span><span>d</span>");
|
||||
|
||||
parent.state.elems.pop();
|
||||
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<span>a</span><span>b</span>");
|
||||
});
|
||||
|
||||
test("components in a node in a t-for ", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<div><t t-esc="props.item"/></div>`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<ul>
|
||||
<t t-for="item" t-of="items" t-key="'li_'+item">
|
||||
<li>
|
||||
<Child item="item"/>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</div>`;
|
||||
static components = { Child };
|
||||
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
|
||||
get items() {
|
||||
return [1, 2];
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<div><ul><li><div>1</div></li><li><div>2</div></li></ul></div>"
|
||||
);
|
||||
expect([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
]).toBeLogged();
|
||||
});
|
||||
|
||||
test("reconciliation alg works for t-for in t-for", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<div><t t-esc="props.blip"/></div>`;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-for="section" t-of="state.s" t-key="section">
|
||||
<t t-for="blip" t-of="section.blips" t-key="blip">
|
||||
<Child blip="blip"/>
|
||||
</t>
|
||||
</t>
|
||||
</div>`;
|
||||
static components = { Child };
|
||||
state = { s: [{ blips: ["a1", "a2"] }, { blips: ["b1"] }] };
|
||||
}
|
||||
|
||||
await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><div>a1</div><div>a2</div><div>b1</div></div>");
|
||||
});
|
||||
|
||||
test("reconciliation alg works for t-for in t-for, 2", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<div><t t-esc="props.row + '_' + props.col"/></div>`;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<p t-for="row" t-of="state.rows" t-key="row">
|
||||
<p t-for="col" t-of="state.cols" t-key="col">
|
||||
<Child row="row" col="col"/>
|
||||
</p>
|
||||
</p>
|
||||
</div>`;
|
||||
static components = { Child };
|
||||
state = useState({ rows: [1, 2], cols: ["a", "b"] });
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<div><p><p><div>1_a</div></p><p><div>1_b</div></p></p><p><p><div>2_a</div></p><p><div>2_b</div></p></p></div>"
|
||||
);
|
||||
parent.state.rows = [2, 1];
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<div><p><p><div>2_a</div></p><p><div>2_b</div></p></p><p><p><div>1_a</div></p><p><div>1_b</div></p></p></div>"
|
||||
);
|
||||
});
|
||||
|
||||
test("sub components rendered in a loop", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<p><t t-esc="props.n"/></p>`;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-for="number" t-of="state.numbers" t-key="number" >
|
||||
<Child n="number"/>
|
||||
</t>
|
||||
</div>`;
|
||||
static components = { Child };
|
||||
|
||||
state = useState({ numbers: [1, 2, 3] });
|
||||
}
|
||||
await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe(`<div><p>1</p><p>2</p><p>3</p></div>`);
|
||||
});
|
||||
|
||||
test("sub components with some state rendered in a loop", async () => {
|
||||
let n = 1;
|
||||
|
||||
class Child extends Component {
|
||||
static template = xml`<p><t t-esc="state.n"/></p>`;
|
||||
state: any;
|
||||
setup() {
|
||||
this.state = useState({ n });
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-for="number" t-of="state.numbers" t-key="number">
|
||||
<Child/>
|
||||
</t>
|
||||
</div>`;
|
||||
static components = { Child };
|
||||
|
||||
state = useState({
|
||||
numbers: [1, 2, 3],
|
||||
});
|
||||
}
|
||||
const parent = await mount(Parent, fixture);
|
||||
|
||||
parent.state.numbers = [1, 3];
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe(`<div><p>1</p><p>3</p></div>`);
|
||||
});
|
||||
|
||||
test("list of sub components inside other nodes", async () => {
|
||||
// this confuses the patching algorithm...
|
||||
class SubComponent extends Component {
|
||||
static template = xml`<span>asdf</span>`;
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<div t-for="blip" t-of="state.blips" t-key="blip.id">
|
||||
<SubComponent />
|
||||
</div>
|
||||
</div>`;
|
||||
static components = { SubComponent };
|
||||
state = useState({
|
||||
blips: [
|
||||
{ a: "a", id: 1 },
|
||||
{ b: "b", id: 2 },
|
||||
{ c: "c", id: 4 },
|
||||
],
|
||||
});
|
||||
}
|
||||
const parent = await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<div><div><span>asdf</span></div><div><span>asdf</span></div><div><span>asdf</span></div></div>"
|
||||
);
|
||||
parent.state.blips.splice(0, 1);
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<div><div><span>asdf</span></div><div><span>asdf</span></div></div>"
|
||||
);
|
||||
});
|
||||
|
||||
test("t-for with t-component, and update", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`
|
||||
<span>
|
||||
<t t-esc="state.val"/>
|
||||
<t t-esc="props.val"/>
|
||||
</span>`;
|
||||
state = useState({ val: "A" });
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
this.state.val = "B";
|
||||
});
|
||||
}
|
||||
}
|
||||
class Parent extends Component {
|
||||
static components = { Child };
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-for="n" t-of="[0, 1]" t-key="n">
|
||||
<Child val="n"/>
|
||||
</t>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><span>A0</span><span>A1</span></div>");
|
||||
|
||||
await nextTick(); // wait for changes triggered in mounted to be applied
|
||||
expect(fixture.innerHTML).toBe("<div><span>B0</span><span>B1</span></div>");
|
||||
});
|
||||
|
||||
test("switch component position", async () => {
|
||||
const childInstances = [];
|
||||
class Child extends Component {
|
||||
static template = xml`<div t-esc="props.key"></div>`;
|
||||
setup() {
|
||||
childInstances.push(this);
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static components = { Child };
|
||||
static template = xml`<span>
|
||||
<t t-for="c" t-of="clist" t-key="c">
|
||||
<Child key="c"/>
|
||||
</t>
|
||||
</span>`;
|
||||
|
||||
clist = [1, 2];
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("<span><div>1</div><div>2</div></span>");
|
||||
parent.clist = [2, 1];
|
||||
parent.render();
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<span><div>2</div><div>1</div></span>");
|
||||
expect(childInstances.length).toBe(2);
|
||||
});
|
||||
|
||||
test("crash on duplicate key in dev mode", async () => {
|
||||
const consoleInfo = console.info;
|
||||
console.info = jest.fn();
|
||||
class Child extends Component {
|
||||
static template = xml``;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<t t-for="item" t-of="[1, 2]" t-key="'child'">
|
||||
<Child/>
|
||||
</t>
|
||||
`;
|
||||
static components = { Child };
|
||||
}
|
||||
|
||||
const app = new App(Parent, { test: true });
|
||||
const mountProm = expect(app.mount(fixture)).rejects.toThrow(
|
||||
"Got duplicate key in t-for: child"
|
||||
);
|
||||
await expect(nextAppError(app)).resolves.toThrow("Got duplicate key in t-for: child");
|
||||
await mountProm;
|
||||
console.info = consoleInfo;
|
||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test("crash when using object as keys that serialize to the same string", async () => {
|
||||
const consoleInfo = console.info;
|
||||
console.info = jest.fn();
|
||||
class Child extends Component {
|
||||
static template = xml``;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<t t-for="item" t-of="[{}, {}]" t-key="item">
|
||||
<Child/>
|
||||
</t>
|
||||
`;
|
||||
static components = { Child };
|
||||
}
|
||||
|
||||
const app = new App(Parent, { test: true });
|
||||
const mountProm = expect(app.mount(fixture)).rejects.toThrow(
|
||||
"Got duplicate key in t-for: [object Object]"
|
||||
);
|
||||
await expect(nextAppError(app)).resolves.toThrow("Got duplicate key in t-for: [object Object]");
|
||||
await mountProm;
|
||||
console.info = consoleInfo;
|
||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test("order is correct when slots are not of same type", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`
|
||||
<t t-slot="{{ slotName }}" t-for="slotName" t-of="slotNames" t-key="slotName"/>
|
||||
`;
|
||||
get slotNames() {
|
||||
return Object.entries(this.props.slots)
|
||||
.filter((entry: any) => entry[1].active)
|
||||
.map((entry) => entry[0]);
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<Child>
|
||||
<t t-set-slot="a" active="!state.active"><div t-if="!state.active">A</div></t>
|
||||
<t t-set-slot="b" active="true">B</t>
|
||||
<t t-set-slot="c" active="state.active">C</t>
|
||||
</Child>
|
||||
`;
|
||||
static components = { Child };
|
||||
state = useState({ active: false });
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
expect(fixture.textContent).toBe("AB");
|
||||
parent.state.active = true;
|
||||
await nextTick();
|
||||
expect(fixture.textContent).toBe("BC");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user