[FIX] component: multiple t-calls

Before this commit, owl could crash in some specific situations:
multiple t-calls with sub components, outside a loop.  The reason is
that the t-call did not generate a key, so from the point of view of the
children component, it had the same key, even though it was at a
different place in a template.

Also, with this fix, we can fix the playground responsive example.

closes #602
This commit is contained in:
Géry Debongnie
2020-01-08 15:05:19 +01:00
committed by aab-odoo
parent 55889febd5
commit 23012f3e7c
5 changed files with 60 additions and 34 deletions
+2 -2
View File
@@ -265,9 +265,9 @@ QWeb.addDirective({
// ------------------------------------------------
const callingScope = hasBody ? "scope" : "Object.assign(Object.create(context), scope)";
const parentComponent = `utils.getComponent(context)`;
const keyCode = ctx.loopNumber || ctx.hasKey0 ? `, key: ${ctx.generateTemplateKey()}` : "";
const key = ctx.generateTemplateKey();
const parentNode = ctx.parentNode ? `c${ctx.parentNode}` : "result";
const extra = `Object.assign({}, extra, {parentNode: ${parentNode}, parent: ${parentComponent}${keyCode}})`;
const extra = `Object.assign({}, extra, {parentNode: ${parentNode}, parent: ${parentComponent}, key: ${key}})`;
if (ctx.parentNode) {
ctx.addLine(`this.subTemplates['${subTemplate}'].call(this, ${callingScope}, ${extra});`);
} else {
@@ -1026,7 +1026,7 @@ exports[`random stuff/miscellaneous can inject values in tagged templates 1`] =
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, parent: utils.getComponent(context)}));
this.subTemplates['__template__1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
return vn1;
}"
`;
+26
View File
@@ -5726,4 +5726,30 @@ describe("t-call", () => {
fixture.querySelector("p")!.click();
});
test("sub components in two t-calls", async () => {
class Child extends Component<any,any> {
static template = xml`<span><t t-esc="props.val"/></span>`;
}
env.qweb.addTemplate("sub", `<Child val="state.val"/>`);
class Parent extends Component<any, any> {
static template = xml`
<div>
<t t-if="state.val===1">
<t t-call="sub"/>
</t>
<div t-else=""><t t-call="sub"/></div>
</div>`;
static components = { Child };
state = useState({val: 1});
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
parent.state.val = 2;
await nextTick();
expect(fixture.innerHTML).toBe("<div><div><span>2</span></div></div>");
});
});
+25 -25
View File
@@ -734,7 +734,7 @@ exports[`loading templates can load a few templates from a xml string 1`] = `
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, parent: utils.getComponent(context)}));
this.subTemplates['items'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
return vn1;
}"
`;
@@ -802,7 +802,7 @@ exports[`misc global 1`] = `
}
}
scope = _origScope5;
this.subTemplates['_callee-asc-toto'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['_callee-asc-toto'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__20__'}));
return vn1;
}"
`;
@@ -1041,7 +1041,7 @@ exports[`t-call (template calling basic caller 1`] = `
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, parent: utils.getComponent(context)}));
this.subTemplates['_basic-callee'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
return vn1;
}"
`;
@@ -1069,7 +1069,7 @@ exports[`t-call (template calling basic caller, no parent node 1`] = `
let result;
let h = this.h;
result = []
this.subTemplates['_basic-callee'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context)}));
this.subTemplates['_basic-callee'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__3__'}));
result = result[0]
return result;
}"
@@ -1114,7 +1114,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, parent: utils.getComponent(context)}));
this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'}));
scope = _origScope3;
}
return vn1;
@@ -1162,7 +1162,7 @@ exports[`t-call (template calling cascading t-call t-raw='0' 1`] = `
c14.push({text: \`yay\`});
scope[utils.zero] = c__0;
}
this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__15__'}));
scope = _origScope12;
}
return vn1;
@@ -1179,7 +1179,7 @@ exports[`t-call (template calling inherit context 1`] = `
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, parent: utils.getComponent(context)}));
this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__2__'}));
return vn1;
}"
`;
@@ -1198,7 +1198,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, parent: utils.getComponent(context)}));
this.subTemplates['recursive'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'}));
}
return vn1;
}"
@@ -1244,7 +1244,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, parent: utils.getComponent(context)}));
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
scope = _origScope11;
}
return vn1;
@@ -1321,7 +1321,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, parent: utils.getComponent(context)}));
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
scope = _origScope11;
}
return vn1;
@@ -1398,7 +1398,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, parent: utils.getComponent(context)}));
this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
scope = _origScope2;
}
if (scope.foo != null) {
@@ -1418,7 +1418,7 @@ exports[`t-call (template calling t-call with t-if 1`] = `
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, parent: utils.getComponent(context)}));
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
}
return vn1;
}"
@@ -1493,7 +1493,7 @@ exports[`t-call (template calling t-call with t-set inside and outside. 2 1`] =
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, parent: utils.getComponent(context)}));
this.subTemplates['main'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__11__'}));
return vn1;
}"
`;
@@ -1509,19 +1509,19 @@ 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, parent: utils.getComponent(context)}));
this.subTemplates['callee1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
}
else if (scope.v1==='elif') {
{
let _origScope5 = scope;
let _origScope6 = scope;
scope = Object.assign(Object.create(context), scope);
{
let c__0 = [];
scope.v = 'success';
scope[utils.zero] = c__0;
}
this.subTemplates['callee2'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)}));
scope = _origScope5;
this.subTemplates['callee2'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__7__'}));
scope = _origScope6;
}
}
return vn1;
@@ -1537,7 +1537,7 @@ exports[`t-call (template calling t-call, global templates 1`] = `
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, parent: utils.getComponent(context)}));
this.subTemplates['john'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
return vn1;
}"
`;
@@ -1559,7 +1559,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, parent: utils.getComponent(context)}));
this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
result = result[0]
scope = _origScope3;
}
@@ -1584,7 +1584,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, parent: utils.getComponent(context)}));
this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
result = result[0]
scope = _origScope3;
}
@@ -1609,7 +1609,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, parent: utils.getComponent(context)}));
this.subTemplates['_callee-printsbody'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
result = result[0]
scope = _origScope3;
}
@@ -1634,7 +1634,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, parent: utils.getComponent(context)}));
this.subTemplates['_callee-uses-foo'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
scope = _origScope3;
}
return vn1;
@@ -1834,7 +1834,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, parent: utils.getComponent(context)}));
this.subTemplates['test'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
scope = _origScope3;
}
return vn1;
@@ -2605,7 +2605,7 @@ exports[`t-on t-on with t-call 1`] = `
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, parent: utils.getComponent(context)}));
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
return vn1;
}"
`;
@@ -2619,7 +2619,7 @@ exports[`t-on t-on, with arguments and t-call 1`] = `
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, parent: utils.getComponent(context)}));
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
return vn1;
}"
`;
+6 -6
View File
@@ -1089,20 +1089,20 @@ const RESPONSIVE_XML = `<templates>
<button>Button!</button>
</div>
<t t-name="maincontent">
<FormView />
<Chatter />
</t>
<div t-name="App" class="app" t-att-class="{mobile: env.isMobile, desktop: !env.isMobile}">
<t t-set="maincontent">
<FormView />
<Chatter />
</t>
<Navbar/>
<ControlPanel/>
<div class="content-wrapper" t-if="!env.isMobile">
<div class="content">
<t t-raw="maincontent"/>
<t t-call="maincontent"/>
</div>
</div>
<t t-else="">
<t t-raw="maincontent"/>
<t t-call="maincontent"/>
</t>
</div>
</templates>