From 0024f33fa12fe01406dc34f8ec17d0240c81a734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Thu, 9 Mar 2023 15:21:12 +0100 Subject: [PATCH] [FIX] components: do not crash when binding anonymous function It is not really useful, but it is possible to bind an anonymous function prop. However, with the recent change on how the bind feature works, it now crashes. This commit makes sure that we properly apply the `bind` operation to the function, and not to the last term of the function. --- src/compiler/code_generator.ts | 2 +- .../__snapshots__/props.test.ts.snap | 30 +++++++++++++++++-- .../__snapshots__/slots.test.ts.snap | 4 +-- .../__snapshots__/t_call.test.ts.snap | 2 +- tests/components/props.test.ts | 25 ++++++++++++++++ 5 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts index 18b99314..d56bbb2b 100644 --- a/src/compiler/code_generator.ts +++ b/src/compiler/code_generator.ts @@ -1135,7 +1135,7 @@ export class CodeGenerator { name = _name; switch (suffix) { case "bind": - value = `${value}.bind(this)`; + value = `(${value}).bind(this)`; break; case "alike": break; diff --git a/tests/components/__snapshots__/props.test.ts.snap b/tests/components/__snapshots__/props.test.ts.snap index af9913b7..840c577f 100644 --- a/tests/components/__snapshots__/props.test.ts.snap +++ b/tests/components/__snapshots__/props.test.ts.snap @@ -349,7 +349,7 @@ exports[`bound functions are considered 'alike' 1`] = ` return function template(ctx, node, key = \\"\\") { const b2 = text(ctx['state'].val); - const b3 = comp1({fn: ctx['someFunction'].bind(this)}, key + \`__1\`, node, this, null); + const b3 = comp1({fn: (ctx['someFunction']).bind(this)}, key + \`__1\`, node, this, null); return multi([b2, b3]); } }" @@ -373,7 +373,7 @@ exports[`bound functions is not referentially equal after update 1`] = ` const comp1 = app.createComponent(\`Child\`, true, false, false, [\\"val\\"]); return function template(ctx, node, key = \\"\\") { - return comp1({val: ctx['state'].val,fn: ctx['someFunction'].bind(this)}, key + \`__1\`, node, this, null); + return comp1({val: ctx['state'].val,fn: (ctx['someFunction']).bind(this)}, key + \`__1\`, node, this, null); } }" `; @@ -396,7 +396,7 @@ exports[`can bind function prop with bind suffix 1`] = ` const comp1 = app.createComponent(\`Child\`, true, false, false, []); return function template(ctx, node, key = \\"\\") { - return comp1({doSomething: ctx['doSomething'].bind(this)}, key + \`__1\`, node, this, null); + return comp1({doSomething: (ctx['doSomething']).bind(this)}, key + \`__1\`, node, this, null); } }" `; @@ -411,3 +411,27 @@ exports[`can bind function prop with bind suffix 2`] = ` } }" `; + +exports[`do not crash when binding anonymous function prop with bind suffix 1`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + const comp1 = app.createComponent(\`Child\`, true, false, false, []); + + return function template(ctx, node, key = \\"\\") { + const v1 = ctx['this']; + return comp1({doSomething: ((_val)=>v1.doSomething(_val)).bind(this)}, key + \`__1\`, node, this, null); + } +}" +`; + +exports[`do not crash when binding anonymous function prop with bind suffix 2`] = ` +"function anonymous(app, bdom, helpers +) { + let { text, createBlock, list, multi, html, toggler, comment } = bdom; + + return function template(ctx, node, key = \\"\\") { + return text(\`child\`); + } +}" +`; diff --git a/tests/components/__snapshots__/slots.test.ts.snap b/tests/components/__snapshots__/slots.test.ts.snap index ee492341..8a45d655 100644 --- a/tests/components/__snapshots__/slots.test.ts.snap +++ b/tests/components/__snapshots__/slots.test.ts.snap @@ -90,7 +90,7 @@ exports[`slots can define and call slots with bound params 1`] = ` return function template(ctx, node, key = \\"\\") { const ctx1 = capture(ctx); - return comp1({slots: markRaw({'abc': {__render: slot1.bind(this), __ctx: ctx1, getValue: ctx['getValue'].bind(this)}})}, key + \`__1\`, node, this, null); + return comp1({slots: markRaw({'abc': {__render: slot1.bind(this), __ctx: ctx1, getValue: (ctx['getValue']).bind(this)}})}, key + \`__1\`, node, this, null); } }" `; @@ -1516,7 +1516,7 @@ exports[`slots simple default slot with params and bound function 2`] = ` let { callSlot } = helpers; return function template(ctx, node, key = \\"\\") { - return callSlot(ctx, node, key, 'default', false, {fn: ctx['getValue'].bind(this)}); + return callSlot(ctx, node, key, 'default', false, {fn: (ctx['getValue']).bind(this)}); } }" `; diff --git a/tests/components/__snapshots__/t_call.test.ts.snap b/tests/components/__snapshots__/t_call.test.ts.snap index 4478fc6a..31284ce1 100644 --- a/tests/components/__snapshots__/t_call.test.ts.snap +++ b/tests/components/__snapshots__/t_call.test.ts.snap @@ -556,7 +556,7 @@ exports[`t-call t-call-context: ComponentNode is not looked up in the context 2` let ref1 = (el) => this.__owl__.setRef((\`myRef\`), el); const b2 = block2([ref1]); const ctx1 = capture(ctx); - const b6 = comp1({prop: ctx['method'].bind(this),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null); + const b6 = comp1({prop: (ctx['method']).bind(this),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null); return multi([b2, b6]); } }" diff --git a/tests/components/props.test.ts b/tests/components/props.test.ts index 8b8c7d82..3cbea392 100644 --- a/tests/components/props.test.ts +++ b/tests/components/props.test.ts @@ -200,6 +200,31 @@ test("can bind function prop with bind suffix", async () => { expect(fixture.innerHTML).toBe("child"); }); +test("do not crash when binding anonymous function prop with bind suffix", async () => { + class Child extends Component { + static template = xml`child`; + setup() { + this.props.doSomething(123); + } + } + + let boundedThing: any = null; + + class Parent extends Component { + static template = xml``; + static components = { Child }; + + doSomething(val: number) { + expect(val).toBe(123); + boundedThing = this; + } + } + + const parent = await mount(Parent, fixture); + expect(boundedThing).toBe(parent); + expect(fixture.innerHTML).toBe("child"); +}); + test("bound functions is not referentially equal after update", async () => { let isEqual = false; class Child extends Component {