diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts index 3343b405..7b55f8ef 100644 --- a/src/compiler/code_generator.ts +++ b/src/compiler/code_generator.ts @@ -877,9 +877,9 @@ export class CodeGenerator { // Throw error on duplicate keys in dev mode this.helpers.add("OwlError"); this.addLine( - `if (keys${block.id}.has(key${this.target.loopLevel})) { throw new OwlError(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}` + `if (keys${block.id}.has(String(key${this.target.loopLevel}))) { throw new OwlError(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}` ); - this.addLine(`keys${block.id}.add(key${this.target.loopLevel});`); + this.addLine(`keys${block.id}.add(String(key${this.target.loopLevel}));`); } let id: string; if (ast.memo) { diff --git a/tests/components/__snapshots__/t_foreach.test.ts.snap b/tests/components/__snapshots__/t_foreach.test.ts.snap index 34c3f1df..665eeaec 100644 --- a/tests/components/__snapshots__/t_foreach.test.ts.snap +++ b/tests/components/__snapshots__/t_foreach.test.ts.snap @@ -53,8 +53,8 @@ exports[`list of components crash on duplicate key in dev mode 1`] = ` for (let i1 = 0; i1 < l_block1; i1++) { ctx[\`item\`] = v_block1[i1]; const key1 = 'child'; - if (keys1.has(key1)) { throw new OwlError(\`Got duplicate key in t-foreach: \${key1}\`)} - keys1.add(key1); + if (keys1.has(String(key1))) { throw new OwlError(\`Got duplicate key in t-foreach: \${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); @@ -75,6 +75,42 @@ exports[`list of components crash on duplicate key in dev mode 2`] = ` }" `; +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 { prepareList, OwlError, withKey } = helpers; + const comp1 = app.createComponent(\`Child\`, true, false, false, true); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k_block1, v_block1, l_block1, c_block1] = prepareList([{},{}]);; + const keys1 = new Set(); + for (let i1 = 0; i1 < l_block1; i1++) { + ctx[\`item\`] = v_block1[i1]; + const key1 = ctx['item']; + if (keys1.has(String(key1))) { throw new OwlError(\`Got duplicate key in t-foreach: \${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); + } + 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 ) { diff --git a/tests/components/t_foreach.test.ts b/tests/components/t_foreach.test.ts index 197a516b..f809fc97 100644 --- a/tests/components/t_foreach.test.ts +++ b/tests/components/t_foreach.test.ts @@ -331,4 +331,32 @@ describe("list of components", () => { 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-foreach: [object Object]" + ); + await expect(nextAppError(app)).resolves.toThrow( + "Got duplicate key in t-foreach: [object Object]" + ); + await mountProm; + console.info = consoleInfo; + expect(mockConsoleWarn).toBeCalledTimes(1); + }); });