[FIX] qweb/component: refactoring of scoping/variables/slots

This commit is a significant refactoring of the internal of QWeb. It
simplifies the way variables/scoping and slots interact together.  The
main idea is that we use a simple scope object instead of a
context/var/scope object.

This commit also implement the actual correct QWeb semantic for the
t-call directive with a sub body.  Before, we simply extracted the
variables from the body and injected them at the top of the sub
template.  We now simply compile the body before the sub template.

This is a joint work with Lucas (lpe).

closes #541
closes #544
closes #545

closes #557
closes #556
This commit is contained in:
Géry Debongnie
2019-12-07 22:56:00 +01:00
parent b890e7ceae
commit ce9c2f8613
18 changed files with 1625 additions and 1279 deletions
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
let utils = this.constructor.utils;
let QWeb = this.constructor;
let parent = context;
let owner = context;
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
@@ -20,16 +20,16 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
w2 = false;
}
if (w2) {
w2.__updateProps(props2, extra.fiber, undefined, undefined);
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]|| context['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[k3] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
+153
View File
@@ -4586,6 +4586,159 @@ describe("t-slot directive", () => {
expect(fixture.innerHTML).toBe("<div><span>B0</span><span>B1</span></div>");
});
test("nested slots in same template", async () => {
let child, child2, child3;
class Child extends Widget {
static template = xml`
<span id="c1">
<div>
<t t-slot="default"/>
</div>
</span>`;
constructor(parent, props) {
super(parent, props);
child = this;
}
}
class Child2 extends Widget {
static template = xml`
<span id="c2">
<t t-slot="default"/>
</span>`;
constructor(parent, props) {
super(parent, props);
child2 = this;
}
}
class Child3 extends Widget {
static template = xml`
<span>Child 3</span>`;
constructor(parent, props) {
super(parent, props);
child3 = this;
}
}
class Parent extends Widget {
static components = { Child, Child2, Child3 };
static template = xml`
<span id="parent">
<Child>
<Child2>
<Child3/>
</Child2>
</Child>
</span>`;
}
const widget = new Parent();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe(
'<span id="parent"><span id="c1"><div><span id="c2"><span>Child 3</span></span></div></span></span>'
);
expect(child3.__owl__.parent).toStrictEqual(child2);
expect(child2.__owl__.parent).toStrictEqual(child);
expect(child.__owl__.parent).toStrictEqual(widget);
});
test("t-slot nested within another slot", async () => {
let portal, modal, child3;
class Child3 extends Widget {
static template = xml`
<span>Child 3</span>`;
constructor(parent, props) {
super(parent, props);
child3 = this;
}
}
class Modal extends Widget {
static template = xml`
<span id="modal">
<t t-slot="default"/>
</span>`;
constructor(parent, props) {
super(parent, props);
modal = this;
}
}
class Portal extends Widget {
static template = xml`
<span id="portal">
<t t-slot="default"/>
</span>`;
constructor(parent, props) {
super(parent, props);
portal = this;
}
}
class Dialog extends Widget {
static components = { Modal, Portal };
static template = xml`
<span id="c2">
<Modal>
<Portal>
<t t-slot="default"/>
</Portal>
</Modal>
</span>`;
}
class Parent extends Widget {
static components = { Child3, Dialog };
static template = xml`
<span id="c1">
<Dialog>
<Child3/>
</Dialog>
</span>`;
}
const widget = new Parent();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe(
'<span id="c1"><span id="c2"><span id="modal"><span id="portal"><span>Child 3</span></span></span></span></span>'
);
expect(child3.__owl__.parent).toStrictEqual(portal);
expect(portal.__owl__.parent).toStrictEqual(modal);
});
test("t-slot supports many instances", async () => {
let child3;
class Child3 extends Widget {
static template = xml`
<span>Child 3</span>`;
constructor(parent, props) {
super(parent, props);
child3 = this;
}
}
class Dialog extends Widget {
static template = xml`
<span id="c2">
<t t-slot="default"/>
</span>`;
}
class Parent extends Widget {
static components = { Child3, Dialog };
static template = xml`
<span id="c1">
<Dialog>
<Child3 val="state.lol"/>
</Dialog>
</span>`;
state = { lol: "k" };
}
const widget = new Parent();
await widget.mount(fixture);
expect(child3.props.val).toBe("k");
const widget_1 = new Parent();
widget_1.state.lol = "m";
await widget_1.mount(fixture);
expect(child3.props.val).toBe("m");
});
test("slots in slots, with vars", async () => {
class B extends Component<any, any> {
static template = xml`<span><t t-slot="default"/></span>`;