@@ -248,6 +277,22 @@ describe("t-set", () => {
);
});
+ test("t-set outside modified in t-for increment-after operator", async () => {
+ const template = `
+
"
+ );
+ });
+
test("t-set outside modified in t-foreach increment-before operator", async () => {
const template = `
@@ -264,6 +309,22 @@ describe("t-set", () => {
);
});
+ test("t-set outside modified in t-for increment-before operator", async () => {
+ const template = `
+
+
+
+ InLoop:
+
+
+
EndLoop:
+
+ `;
+ expect(renderToString(template)).toBe(
+ "
InLoop: 0
InLoop: 1
EndLoop: 0
"
+ );
+ });
+
test("t-set can't alter from within callee", async () => {
const context = new TestContext();
const sub = `
`;
diff --git a/tests/components/__snapshots__/t_for.test.ts.snap b/tests/components/__snapshots__/t_for.test.ts.snap
new file mode 100644
index 000000000..15591e69f
--- /dev/null
+++ b/tests/components/__snapshots__/t_for.test.ts.snap
@@ -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(\`
\`);
+ let block3 = createBlock(\`
\`);
+
+ 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(\`
\`);
+
+ 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(\`
\`);
+ let block3 = createBlock(\`
\`);
+
+ 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(\`
asdf\`);
+
+ 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(\`
A
\`);
+
+ 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(\`
\`);
+
+ 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(\`
\`);
+
+ 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(\`
\`);
+ let block3 = createBlock(\`
\`);
+ let block5 = createBlock(\`
\`);
+
+ 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(\`
\`);
+
+ 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(\`
\`);
+
+ 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(\`
\`);
+
+ 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(\`
\`);
+
+ 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(\`
\`);
+
+ 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(\`
\`);
+
+ 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(\`
\`);
+
+ 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(\`
\`);
+
+ 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(\`
\`);
+
+ 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(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let txt1 = ctx['state'].val;
+ let txt2 = ctx['props'].val;
+ return block1([txt1, txt2]);
+ }
+}"
+`;
diff --git a/tests/components/t_for.test.ts b/tests/components/t_for.test.ts
new file mode 100644
index 000000000..2c469542d
--- /dev/null
+++ b/tests/components/t_for.test.ts
@@ -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`
`;
+ }
+
+ class Parent extends Component {
+ static template = xml`
+
+
+ `;
+ static components = { Child };
+
+ state = useState({
+ elems: [
+ { id: 1, value: "a" },
+ { id: 2, value: "b" },
+ ],
+ });
+ }
+
+ const parent = await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe("
ab");
+ parent.state.elems.push({ id: 4, value: "d" });
+ await nextTick();
+ expect(fixture.innerHTML).toBe("
abd");
+
+ parent.state.elems.pop();
+
+ await nextTick();
+ expect(fixture.innerHTML).toBe("
ab");
+ });
+
+ test("components in a node in a t-for ", async () => {
+ class Child extends Component {
+ static template = xml`
`;
+ setup() {
+ useLogLifecycle();
+ }
+ }
+
+ class Parent extends Component {
+ static template = xml`
+
`;
+ static components = { Child };
+
+ setup() {
+ useLogLifecycle();
+ }
+
+ get items() {
+ return [1, 2];
+ }
+ }
+
+ await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe(
+ "
"
+ );
+ 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`
`;
+ }
+
+ class Parent extends Component {
+ static template = xml`
+
+
+
+
+
+
+
`;
+ static components = { Child };
+ state = { s: [{ blips: ["a1", "a2"] }, { blips: ["b1"] }] };
+ }
+
+ await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe("
");
+ });
+
+ test("reconciliation alg works for t-for in t-for, 2", async () => {
+ class Child extends Component {
+ static template = xml`
`;
+ }
+
+ class Parent extends Component {
+ static template = xml`
+
`;
+ static components = { Child };
+ state = useState({ rows: [1, 2], cols: ["a", "b"] });
+ }
+
+ const parent = await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe(
+ "
"
+ );
+ parent.state.rows = [2, 1];
+ await nextTick();
+ expect(fixture.innerHTML).toBe(
+ "
"
+ );
+ });
+
+ test("sub components rendered in a loop", async () => {
+ class Child extends Component {
+ static template = xml`
`;
+ }
+
+ class Parent extends Component {
+ static template = xml`
+
+
+
+
+
`;
+ static components = { Child };
+
+ state = useState({ numbers: [1, 2, 3] });
+ }
+ await mount(Parent, fixture);
+
+ expect(fixture.innerHTML).toBe(`
`);
+ });
+
+ test("sub components with some state rendered in a loop", async () => {
+ let n = 1;
+
+ class Child extends Component {
+ static template = xml`
`;
+ state: any;
+ setup() {
+ this.state = useState({ n });
+ n++;
+ }
+ }
+
+ class Parent extends Component {
+ static template = xml`
+
+
+
+
+
`;
+ 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(`
`);
+ });
+
+ test("list of sub components inside other nodes", async () => {
+ // this confuses the patching algorithm...
+ class SubComponent extends Component {
+ static template = xml`
asdf`;
+ }
+ class Parent extends Component {
+ static template = xml`
+
`;
+ 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(
+ "
"
+ );
+ parent.state.blips.splice(0, 1);
+ await nextTick();
+ expect(fixture.innerHTML).toBe(
+ "
"
+ );
+ });
+
+ test("t-for with t-component, and update", async () => {
+ class Child extends Component {
+ static template = xml`
+
+
+
+ `;
+ state = useState({ val: "A" });
+ setup() {
+ onMounted(() => {
+ this.state.val = "B";
+ });
+ }
+ }
+ class Parent extends Component {
+ static components = { Child };
+ static template = xml`
+
+
+
+
+
`;
+ }
+
+ await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe("
A0A1
");
+
+ await nextTick(); // wait for changes triggered in mounted to be applied
+ expect(fixture.innerHTML).toBe("
B0B1
");
+ });
+
+ test("switch component position", async () => {
+ const childInstances = [];
+ class Child extends Component {
+ static template = xml`
`;
+ setup() {
+ childInstances.push(this);
+ }
+ }
+
+ class Parent extends Component {
+ static components = { Child };
+ static template = xml`
+
+
+
+ `;
+
+ clist = [1, 2];
+ }
+
+ const parent = await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe("
1
2
");
+ parent.clist = [2, 1];
+ parent.render();
+ await nextTick();
+ expect(fixture.innerHTML).toBe("
2
1
");
+ 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`
+
+
+
+ `;
+ 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`
+
+
+
+ `;
+ 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`
+
+ `;
+ 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`
+
+ A
+ B
+ C
+
+ `;
+ 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");
+ });
+});