diff --git a/src/qweb/base_directives.ts b/src/qweb/base_directives.ts index 833efdf3..04f30daf 100644 --- a/src/qweb/base_directives.ts +++ b/src/qweb/base_directives.ts @@ -215,6 +215,7 @@ QWeb.addDirective({ // Step 1: sanity checks // ------------------------------------------------ ctx.rootContext.shouldDefineScope = true; + ctx.rootContext.shouldDefineUtils = true; if (node.nodeName !== "t") { throw new Error("Invalid tag for t-call directive (should be 't')"); } @@ -262,16 +263,17 @@ QWeb.addDirective({ // Step 4: add the appropriate function call to current component // ------------------------------------------------ const callingScope = hasBody ? "scope" : "Object.assign(Object.create(context), scope)"; + const parentComponent = `utils.getComponent(context)`; if (ctx.parentNode) { ctx.addLine( - `this.subTemplates['${subTemplate}'].call(this, ${callingScope}, Object.assign({}, extra, {parentNode: c${ctx.parentNode}}));` + `this.subTemplates['${subTemplate}'].call(this, ${callingScope}, Object.assign({}, extra, {parentNode: c${ctx.parentNode}, parent: ${parentComponent}}));` ); } else { // this is a t-call with no parentnode, we need to extract the result ctx.rootContext.shouldDefineResult = true; ctx.addLine(`result = []`); ctx.addLine( - `this.subTemplates['${subTemplate}'].call(this, ${callingScope}, Object.assign({}, extra, {parentNode: result}));` + `this.subTemplates['${subTemplate}'].call(this, ${callingScope}, Object.assign({}, extra, {parentNode: result, parent: ${parentComponent}}));` ); ctx.addLine(`result = result[0]`); } diff --git a/tests/component/__snapshots__/component.test.ts.snap b/tests/component/__snapshots__/component.test.ts.snap index 07d0b975..3f0447b1 100644 --- a/tests/component/__snapshots__/component.test.ts.snap +++ b/tests/component/__snapshots__/component.test.ts.snap @@ -901,11 +901,12 @@ exports[`random stuff/miscellaneous can inject values in tagged templates 1`] = "function anonymous(context, extra ) { // Template name: \\"__template__2\\" + let utils = this.constructor.utils; let scope = Object.create(context); let h = this.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - this.subTemplates['__template__1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['__template__1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); return vn1; }" `; @@ -1042,6 +1043,78 @@ exports[`t-call handlers with arguments are properly bound through a t-call 1`] }" `; +exports[`t-call parent is set within t-call 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"sub\\" + let utils = this.constructor.utils; + let QWeb = this.constructor; + let parent = extra.parent; + let scope = Object.create(context); + let h = this.h; + let c1 = extra.parentNode; + // Component 'Child' + let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false; + let props2 = {}; + if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) { + w2.destroy(); + w2 = false; + } + if (w2) { + w2.__updateProps(props2, extra.fiber, undefined); + let pvnode = w2.__owl__.pvnode; + c1.push(pvnode); + } else { + let componentKey2 = \`Child\`; + let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child']; + if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} + w2 = new W2(parent, props2); + parent.__owl__.cmap['__3__'] = w2.__owl__.id; + let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); + c1.push(pvnode); + w2.__owl__.pvnode = pvnode; + } + w2.__owl__.parentLastFiberId = extra.fiber.id; +}" +`; + +exports[`t-call parent is set within t-call with no parentNode 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"sub\\" + let utils = this.constructor.utils; + let QWeb = this.constructor; + let parent = extra.parent; + let scope = Object.create(context); + let h = this.h; + let c1 = extra.parentNode; + // Component 'Child' + let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false; + let props2 = {}; + if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) { + w2.destroy(); + w2 = false; + } + if (w2) { + w2.__updateProps(props2, extra.fiber, undefined); + let pvnode = w2.__owl__.pvnode; + c1.push(pvnode); + } else { + let componentKey2 = \`Child\`; + let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child']; + if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} + w2 = new W2(parent, props2); + parent.__owl__.cmap['__3__'] = w2.__owl__.id; + let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); + c1.push(pvnode); + w2.__owl__.pvnode = pvnode; + } + w2.__owl__.parentLastFiberId = extra.fiber.id; +}" +`; + exports[`t-model directive .lazy modifier 1`] = ` "function anonymous(context, extra ) { diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts index 93108ba0..bde684be 100644 --- a/tests/component/component.test.ts +++ b/tests/component/component.test.ts @@ -5577,6 +5577,50 @@ describe("t-call", () => { expect(env.qweb.subTemplates["sub"].toString()).toMatchSnapshot(); }); + test("parent is set within t-call", async () => { + env.qweb.addTemplate("sub", ``); + let child; + class Child extends Component { + static template = xml `lucas` + constructor() { + super(...arguments); + child = this; + } + } + class Parent extends Component { + static components = { Child } + static template = xml`
`; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
lucas
"); + expect(child.__owl__.parent).toBe(parent); + expect(env.qweb.subTemplates["sub"].toString()).toMatchSnapshot(); + }); + + test("parent is set within t-call with no parentNode", async () => { + env.qweb.addTemplate("sub", ``); + let child; + class Child extends Component { + constructor() { + super(...arguments); + child = this; + } + static template = xml `lucas` + } + class Parent extends Component { + static components = { Child } + static template = xml``; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("lucas"); + expect(child.__owl__.parent).toBe(parent); + expect(env.qweb.subTemplates["sub"].toString()).toMatchSnapshot(); + }); + test("handlers with arguments are properly bound through a t-call", async () => { expect.assertions(3); env.qweb.addTemplate("sub", `

lucas

`); diff --git a/tests/qweb/__snapshots__/qweb.test.ts.snap b/tests/qweb/__snapshots__/qweb.test.ts.snap index 7d14b275..f1af5dd1 100644 --- a/tests/qweb/__snapshots__/qweb.test.ts.snap +++ b/tests/qweb/__snapshots__/qweb.test.ts.snap @@ -722,11 +722,12 @@ exports[`loading templates can load a few templates from a xml string 1`] = ` "function anonymous(context, extra ) { // Template name: \\"main\\" + let utils = this.constructor.utils; let scope = Object.create(context); let h = this.h; let c1 = [], p1 = {key:1}; let vn1 = h('ul', p1, c1); - this.subTemplates['items'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['items'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); return vn1; }" `; @@ -776,20 +777,20 @@ exports[`misc global 1`] = ` scope.foo = 'aaa'; scope[utils.zero] = c__0; } - this.subTemplates['_callee-uses-foo'].call(this, scope, Object.assign({}, extra, {parentNode: c__0})); + this.subTemplates['_callee-uses-foo'].call(this, scope, Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context)})); scope = _origScope13; } - this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0})); + this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context)})); scope.foo = 'bbb'; - this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0})); + this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context)})); scope[utils.zero] = c__0; } - this.subTemplates['_callee-asc'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['_callee-asc'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); scope = _origScope10; } } scope = _origScope5; - this.subTemplates['_callee-asc-toto'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['_callee-asc-toto'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); return vn1; }" `; @@ -1023,11 +1024,12 @@ exports[`t-call (template calling basic caller 1`] = ` "function anonymous(context, extra ) { // Template name: \\"caller\\" + let utils = this.constructor.utils; let scope = Object.create(context); let h = this.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - this.subTemplates['_basic-callee'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['_basic-callee'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); return vn1; }" `; @@ -1049,11 +1051,12 @@ exports[`t-call (template calling basic caller, no parent node 1`] = ` "function anonymous(context, extra ) { // Template name: \\"caller\\" + let utils = this.constructor.utils; let scope = Object.create(context); let result; let h = this.h; result = [] - this.subTemplates['_basic-callee'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: result})); + this.subTemplates['_basic-callee'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context)})); result = result[0] return result; }" @@ -1097,7 +1100,7 @@ exports[`t-call (template calling call with several sub nodes on same line 1`] = c5.push({text: \`yay\`}); scope[utils.zero] = c__0; } - this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); scope = _origScope3; } return vn1; @@ -1144,7 +1147,7 @@ exports[`t-call (template calling cascading t-call t-raw='0' 1`] = ` c12.push({text: \`yay\`}); scope[utils.zero] = c__0; } - this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); scope = _origScope10; } return vn1; @@ -1155,12 +1158,13 @@ exports[`t-call (template calling inherit context 1`] = ` "function anonymous(context, extra ) { // Template name: \\"caller\\" + let utils = this.constructor.utils; let scope = Object.create(context); let h = this.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); scope.foo = 1; - this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); return vn1; }" `; @@ -1169,6 +1173,7 @@ exports[`t-call (template calling recursive template, part 1 1`] = ` "function anonymous(context, extra ) { // Template name: \\"recursive\\" + let utils = this.constructor.utils; let scope = Object.create(context); let h = this.h; let c1 = [], p1 = {key:1}; @@ -1178,7 +1183,7 @@ exports[`t-call (template calling recursive template, part 1 1`] = ` c1.push(vn2); c2.push({text: \`hey\`}); if (false) { - this.subTemplates['recursive'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['recursive'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); } return vn1; }" @@ -1188,6 +1193,7 @@ exports[`t-call (template calling recursive template, part 1 2`] = ` "function anonymous(context, extra ) { // Template name: \\"recursive\\" + let utils = this.constructor.utils; let scope = Object.create(context); let h = this.h; let c1 = extra.parentNode; @@ -1199,7 +1205,7 @@ exports[`t-call (template calling recursive template, part 1 2`] = ` c3.push(vn4); c4.push({text: \`hey\`}); if (false) { - this.subTemplates['recursive'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c3})); + this.subTemplates['recursive'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c3, parent: utils.getComponent(context)})); } }" `; @@ -1221,7 +1227,7 @@ exports[`t-call (template calling recursive template, part 2 1`] = ` scope.node = scope['root']; scope[utils.zero] = c__0; } - this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); scope = _origScope10; } return vn1; @@ -1270,7 +1276,7 @@ exports[`t-call (template calling recursive template, part 2 2`] = ` scope.node = scope['subtree']; scope[utils.zero] = c__0; } - this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c2})); + this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c2, parent: utils.getComponent(context)})); scope = _origScope9; } } @@ -1295,7 +1301,7 @@ exports[`t-call (template calling recursive template, part 3 1`] = ` scope.node = scope['root']; scope[utils.zero] = c__0; } - this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); scope = _origScope10; } return vn1; @@ -1344,7 +1350,7 @@ exports[`t-call (template calling recursive template, part 3 2`] = ` scope.node = scope['subtree']; scope[utils.zero] = c__0; } - this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c2})); + this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c2, parent: utils.getComponent(context)})); scope = _origScope9; } } @@ -1369,7 +1375,7 @@ exports[`t-call (template calling scoped parameters 1`] = ` scope.foo = 42; scope[utils.zero] = c__0; } - this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); scope = _origScope2; } if (scope.foo != null) { @@ -1383,12 +1389,13 @@ exports[`t-call (template calling t-call with t-if 1`] = ` "function anonymous(context, extra ) { // Template name: \\"caller\\" + let utils = this.constructor.utils; let scope = Object.create(context); let h = this.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); if (scope['flag']) { - this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); } return vn1; }" @@ -1441,7 +1448,7 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = ` scope.val3 = scope.val*3; scope[utils.zero] = c__0; } - this.subTemplates['sub'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['sub'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); scope = _origScope8; } } @@ -1454,12 +1461,13 @@ exports[`t-call (template calling t-call with t-set inside and outside. 2 1`] = "function anonymous(context, extra ) { // Template name: \\"wrapper\\" + let utils = this.constructor.utils; let scope = Object.create(context); let h = this.h; let c1 = [], p1 = {key:1}; let vn1 = h('p', p1, c1); scope.w = 'fromwrapper'; - this.subTemplates['main'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['main'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); return vn1; }" `; @@ -1475,7 +1483,7 @@ exports[`t-call (template calling t-call, conditional and t-set in t-call body 1 let vn1 = h('div', p1, c1); scope.v1 = 'elif'; if (scope.v1==='if') { - this.subTemplates['callee1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['callee1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); } else if (scope.v1==='elif') { { @@ -1486,7 +1494,7 @@ exports[`t-call (template calling t-call, conditional and t-set in t-call body 1 scope.v = 'success'; scope[utils.zero] = c__0; } - this.subTemplates['callee2'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['callee2'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); scope = _origScope5; } } @@ -1498,11 +1506,12 @@ exports[`t-call (template calling t-call, global templates 1`] = ` "function anonymous(context, extra ) { // Template name: \\"abcd\\" + let utils = this.constructor.utils; let scope = Object.create(context); let h = this.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - this.subTemplates['john'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['john'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); return vn1; }" `; @@ -1524,7 +1533,7 @@ exports[`t-call (template calling with unused body 1`] = ` scope[utils.zero] = c__0; } result = [] - this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result})); + this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context)})); result = result[0] scope = _origScope3; } @@ -1549,7 +1558,7 @@ exports[`t-call (template calling with unused setbody 1`] = ` scope[utils.zero] = c__0; } result = [] - this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result})); + this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context)})); result = result[0] scope = _origScope3; } @@ -1574,7 +1583,7 @@ exports[`t-call (template calling with used body 1`] = ` scope[utils.zero] = c__0; } result = [] - this.subTemplates['_callee-printsbody'].call(this, scope, Object.assign({}, extra, {parentNode: result})); + this.subTemplates['_callee-printsbody'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context)})); result = result[0] scope = _origScope3; } @@ -1599,7 +1608,7 @@ exports[`t-call (template calling with used set body 1`] = ` scope.foo = 'ok'; scope[utils.zero] = c__0; } - this.subTemplates['_callee-uses-foo'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['_callee-uses-foo'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); scope = _origScope3; } return vn1; @@ -1783,7 +1792,7 @@ exports[`t-esc t-esc=0 is escaped 1`] = ` c4.push({text: \`escaped\`}); scope[utils.zero] = c__0; } - this.subTemplates['test'].call(this, scope, Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['test'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); scope = _origScope3; } return vn1; @@ -2541,11 +2550,12 @@ exports[`t-on t-on with t-call 1`] = ` "function anonymous(context, extra ) { // Template name: \\"main\\" + let utils = this.constructor.utils; let scope = Object.create(context); let h = this.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); return vn1; }" `; @@ -2554,11 +2564,12 @@ exports[`t-on t-on, with arguments and t-call 1`] = ` "function anonymous(context, extra ) { // Template name: \\"main\\" + let utils = this.constructor.utils; let scope = Object.create(context); let h = this.h; let c1 = [], p1 = {key:1}; let vn1 = h('div', p1, c1); - this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1})); + this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); return vn1; }" `;