[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:
Samuel Degueldre
2023-07-20 13:17:57 +02:00
parent 9d99f8936b
commit fb97955729
10 changed files with 2172 additions and 23 deletions
@@ -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
) {
+317
View File
@@ -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>");
});
});
+61
View File
@@ -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]);
}
}"
`;
+391
View File
@@ -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");
});
});