From 63a8fcd7e257d3c5fd6a537aeb507c096d1a75f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Fri, 21 Jun 2019 16:23:42 +0200 Subject: [PATCH] [FIX] component: scoping issue with t-on and t-foreach closes #193 --- src/qweb_extensions.ts | 26 ++++++++-- tests/__snapshots__/component.test.ts.snap | 58 ++++++++++++++++++++++ tests/component.test.ts | 28 +++++++++++ 3 files changed, 107 insertions(+), 5 deletions(-) diff --git a/src/qweb_extensions.ts b/src/qweb_extensions.ts index 6d16a718..1bccf35a 100644 --- a/src/qweb_extensions.ts +++ b/src/qweb_extensions.ts @@ -479,9 +479,21 @@ QWeb.addDirective({ } let eventsCode = events .map(function([eventName, mods, handlerName, extraArgs]) { - let params = extraArgs - ? `owner, ${ctx.formatExpression(extraArgs)}` - : "owner"; + let params = "owner"; + if (extraArgs) { + if (ctx.inLoop) { + let argId = ctx.generateID(); + // we need to evaluate the arguments now, because the handler will + // be set asynchronously later when the widget is ready, and the + // context might be different. + ctx.addLine( + `let arg${argId} = ${ctx.formatExpression(extraArgs)};` + ); + params = `owner, arg${argId}`; + } else { + params = `owner, ${ctx.formatExpression(extraArgs)}`; + } + } let handler; if (mods.length > 0) { handler = `function (e) {`; @@ -541,7 +553,9 @@ QWeb.addDirective({ ctx.addLine( `if (!W${componentID}) {throw new Error('Cannot find the definition of component "' + componentKey${componentID} + '"')}` ); - ctx.addLine(`w${componentID} = new W${componentID}(owner, props${componentID});`); + ctx.addLine( + `w${componentID} = new W${componentID}(owner, props${componentID});` + ); ctx.addLine( `context.__owl__.cmap[${templateID}] = w${componentID}.__owl__.id;` ); @@ -579,7 +593,9 @@ QWeb.addDirective({ ctx.addElse(); // need to update component - const patchQueueCode = async ? `patchQueue${componentID}` : "extra.patchQueue"; + const patchQueueCode = async + ? `patchQueue${componentID}` + : "extra.patchQueue"; ctx.addLine( `def${defID} = def${defID} || w${componentID}._updateProps(props${componentID}, extra.forceUpdate, ${patchQueueCode});` ); diff --git a/tests/__snapshots__/component.test.ts.snap b/tests/__snapshots__/component.test.ts.snap index 788d5197..ec0d302d 100644 --- a/tests/__snapshots__/component.test.ts.snap +++ b/tests/__snapshots__/component.test.ts.snap @@ -827,6 +827,64 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = ` }" `; +exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument on a t-foreach 1`] = ` +"function anonymous(context,extra +) { + let utils = this.utils; + let QWeb = this.constructor; + let owner = context; + context = Object.create(context); + var h = this.utils.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + var _2 = context['props'].items; + if (!_2) { throw new Error('QWeb error: Invalid loop expression')} + var _3 = _4 = _2; + if (!(_2 instanceof Array)) { + _3 = Object.keys(_2); + _4 = Object.values(_2); + } + var _length3 = _3.length; + for (let i = 0; i < _length3; i++) { + context.item_first = i === 0; + context.item_last = i === _length3 - 1; + context.item_index = i; + context.item = _3[i]; + context.item_value = _4[i]; + //COMPONENT + let key8 = context['item']; + let def6; + let arg9 = context['item']; + let w7 = key8 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[key8]] : false; + let _5_index = c1.length; + c1.push(null); + let props7 = {}; + if (w7 && w7.__owl__.renderPromise && !w7.__owl__.vnode) { + if (utils.shallowEqual(props7, w7.__owl__.renderProps)) { + def6 = w7.__owl__.renderPromise; + } else { + w7.destroy(); + w7 = false; + } + } + if (!w7) { + let componentKey7 = \`Child\`; + let W7 = context.components && context.components[componentKey7] || QWeb.components[componentKey7]; + if (!W7) {throw new Error('Cannot find the definition of component \\"' + componentKey7 + '\\"')} + w7 = new W7(owner, props7); + context.__owl__.cmap[key8] = w7.__owl__.id; + def6 = w7._prepare(); + def6 = def6.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, arg9));}};let pvnode=h(vnode.sel, {key: key8, hook: {insert(vn) {let nvn=w7._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c1[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;}); + } else { + def6 = def6 || w7._updateProps(props7, extra.forceUpdate, extra.patchQueue); + def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c1[_5_index]=pvnode;}); + } + extra.promises.push(def6); + } + return vn1; +}" +`; + exports[`t-model directive .lazy modifier 1`] = ` "function anonymous(context,extra ) { diff --git a/tests/component.test.ts b/tests/component.test.ts index 8f0cd0d9..3cf895eb 100644 --- a/tests/component.test.ts +++ b/tests/component.test.ts @@ -1888,6 +1888,34 @@ describe("random stuff/miscellaneous", () => { expect(fixture.innerHTML).toBe("
txttxt
"); }); + test("t-on with handler bound to dynamic argument on a t-foreach", async () => { + expect.assertions(3); + env.qweb.addTemplates(` + +
+ + + +
+
+ `); + const items = [1, 2, 3, 4]; + + class ParentWidget extends Widget { + components = { Child }; + onEv(n, ev) { + expect(n).toBe(1); + expect(ev.detail).toBe(43); + } + } + class Child extends Widget {} + + const widget = new ParentWidget(env, { items }); + await widget.mount(fixture); + children(widget)[0].trigger("ev", 43); + expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot(); + }); + test("updating widget immediately", async () => { // in this situation, we protect against a bug that occurred: because of the // interplay between components and vnodes, a sub widget vnode was patched