[IMP] hooks: implement useRef hook

closes #194
This commit is contained in:
Géry Debongnie
2019-09-26 21:56:01 +02:00
parent 91b9e78182
commit 5cdaa7b473
20 changed files with 321 additions and 171 deletions
+60 -44
View File
@@ -1,7 +1,7 @@
import { Component, Env } from "../../src/component/component";
import { QWeb } from "../../src/qweb/qweb";
import { xml } from "../../src/tags";
import { useState } from "../../src/hooks";
import { useState, useRef } from "../../src/hooks";
import { EventBus } from "../../src/core/event_bus";
import {
makeDeferred,
@@ -989,13 +989,15 @@ describe("composition", () => {
});
test("t-refs on widget are components", async () => {
env.qweb.addTemplate("WidgetC", `<div>Hello<t t-ref="mywidgetb" t-component="b"/></div>`);
class WidgetC extends Widget {
static components = { b: WidgetB };
static template = xml`<div>Hello<WidgetB t-ref="mywidgetb" /></div>`;
static components = { WidgetB };
widget = useRef("mywidgetb");
}
const widget = new WidgetC(env);
await widget.mount(fixture);
expect(widget.refs.mywidgetb instanceof WidgetB).toBe(true);
expect(widget.widget.comp).toBeInstanceOf(WidgetB);
});
test("t-refs are bound at proper timing", async () => {
@@ -1008,11 +1010,12 @@ describe("composition", () => {
`;
static components = { Widget };
state = useState({ list: <any>[] });
child = useRef("child");
willPatch() {
expect(this.refs.child).toBeUndefined();
expect(this.child.comp).toBeNull();
}
patched() {
expect(this.refs.child).not.toBeUndefined();
expect(this.child.comp).not.toBeNull();
}
}
@@ -1035,29 +1038,31 @@ describe("composition", () => {
class ParentWidget extends Widget {
static components = { Widget };
state = useState({ child1: true, child2: false });
child1 = useRef("child1");
child2 = useRef("child2");
count = 0;
mounted() {
expect(this.refs.child1).toBeDefined();
expect(this.refs.child2).toBeUndefined();
expect(this.child1.comp).toBeDefined();
expect(this.child2.comp).toBeNull();
}
willPatch() {
if (this.count === 0) {
expect(this.refs.child1).toBeDefined();
expect(this.refs.child2).toBeUndefined();
expect(this.child1.comp).toBeDefined();
expect(this.child2.comp).toBeNull();
}
if (this.count === 1) {
expect(this.refs.child1).toBeDefined();
expect(this.refs.child2).toBeDefined();
expect(this.child1.comp).toBeDefined();
expect(this.child2.comp).toBeDefined();
}
}
patched() {
if (this.count === 0) {
expect(this.refs.child1).toBeDefined();
expect(this.refs.child2).toBeDefined();
expect(this.child1.comp).toBeDefined();
expect(this.child2.comp).toBeDefined();
}
if (this.count === 1) {
expect(this.refs.child1).toBeUndefined();
expect(this.refs.child2).toBeDefined();
expect(this.child1.comp).toBeNull();
expect(this.child2.comp).toBeDefined();
}
this.count++;
}
@@ -1095,12 +1100,19 @@ describe("composition", () => {
</div>`
);
class ParentWidget extends Widget {
state = useState({ items: [1, 2, 3] });
static components = { Child: Widget };
elem1 = useRef("1");
elem2 = useRef("2");
elem3 = useRef("3");
elem4 = useRef("4");
state = useState({ items: [1, 2, 3] });
}
const parent = new ParentWidget(env);
await parent.mount(fixture);
expect(Object.keys(parent.refs)).toEqual(["1", "2", "3"]);
expect(parent.elem1.comp).toBeDefined();
expect(parent.elem2.comp).toBeDefined();
expect(parent.elem3.comp).toBeDefined();
expect(parent.elem4.comp).toBeNull();
});
test("parent's elm for a children === children's elm, even after rerender", async () => {
@@ -1217,36 +1229,37 @@ describe("composition", () => {
});
test("sub widget with t-ref and t-keepalive", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="ParentWidget">
<t t-if="state.ok"><ChildWidget t-ref="child" t-keepalive="1"/></t>
</div>
<span t-name="ChildWidget">Hello</span>
</templates>`);
class ChildWidget extends Widget {}
class ChildWidget extends Widget {
static template = xml`<span>Hello</span>`;
}
class ParentWidget extends Widget {
state = useState({ ok: true });
static template = xml`
<div>
<t t-if="state.ok"><ChildWidget t-ref="child" t-keepalive="1"/></t>
</div>
`;
static components = { ChildWidget };
state = useState({ ok: true });
child = useRef("child");
}
const widget = new ParentWidget(env);
await widget.mount(fixture);
let child = children(widget)[0];
expect(fixture.innerHTML).toBe("<div><span>Hello</span></div>");
expect(widget.refs.child).toEqual(child);
expect(widget.child.comp).toEqual(child);
widget.state.ok = false;
await nextTick();
expect(fixture.innerHTML).toBe("<div></div>");
expect(widget.refs.child).toEqual(child);
expect(widget.child.comp).toEqual(child);
widget.state.ok = true;
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>Hello</span></div>");
expect(widget.refs.child).toEqual(child);
expect(widget.child.comp).toEqual(child);
});
test("sub components rendered in a loop", async () => {
@@ -1582,6 +1595,7 @@ describe("class and style attributes with t-component", () => {
class ParentWidget extends Widget {
static components = { Child };
state = useState({ b: true });
child = useRef("child");
}
const widget = new ParentWidget(env);
await widget.mount(fixture);
@@ -1593,7 +1607,7 @@ describe("class and style attributes with t-component", () => {
await nextTick();
expect(span.className).toBe("c d a");
(<any>widget.refs.child).state.d = false;
(widget.child.comp as Child).state.d = false;
await nextTick();
expect(span.className).toBe("c a");
@@ -1601,7 +1615,7 @@ describe("class and style attributes with t-component", () => {
await nextTick();
expect(span.className).toBe("c a b");
(<any>widget.refs.child).state.d = true;
(widget.child.comp as Child).state.d = true;
await nextTick();
expect(span.className).toBe("c a b d");
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
@@ -1623,6 +1637,7 @@ describe("class and style attributes with t-component", () => {
class ParentWidget extends Widget {
static components = { Child };
state = useState({ b: true });
child = useRef("child");
}
const widget = new ParentWidget(env);
await widget.mount(fixture);
@@ -1634,7 +1649,7 @@ describe("class and style attributes with t-component", () => {
await nextTick();
expect(span.className).toBe("c d a");
(<any>widget.refs.child).state.d = false;
(widget.child.comp as Child).state.d = false;
await nextTick();
expect(span.className).toBe("c a");
@@ -1642,7 +1657,7 @@ describe("class and style attributes with t-component", () => {
await nextTick();
expect(span.className).toBe("c a b");
(<any>widget.refs.child).state.d = true;
(widget.child.comp as Child).state.d = true;
await nextTick();
expect(span.className).toBe("c a b d");
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
@@ -3000,12 +3015,13 @@ describe("t-mounted directive", () => {
test("combined with a t-ref", async () => {
env.qweb.addTemplate("TestWidget", `<div><input t-ref="input" t-mounted="f"/></div>`);
class TestWidget extends Widget {
input = useRef("input");
f() {}
}
const widget = new TestWidget(env);
widget.f = jest.fn();
await widget.mount(fixture);
expect(widget.refs.input).toBeDefined();
expect(widget.input.el).toBeDefined();
expect(widget.f).toHaveBeenCalledTimes(1);
});
});
@@ -3233,21 +3249,21 @@ describe("t-slot directive", () => {
});
test("refs are properly bound in slots", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="Parent">
class Dialog extends Widget {
static template = xml`<span><t t-slot="footer"/></span>`;
}
class Parent extends Widget {
static template = xml`
<div>
<span class="counter"><t t-esc="state.val"/></span>
<Dialog>
<t t-set="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
</Dialog>
</div>
<span t-name="Dialog"><t t-slot="footer"/></span>
</templates>
`);
class Dialog extends Widget {}
class Parent extends Widget {
`;
static components = { Dialog };
state = useState({ val: 0 });
button = useRef("myButton");
doSomething() {
this.state.val++;
}
@@ -3259,7 +3275,7 @@ describe("t-slot directive", () => {
'<div><span class="counter">0</span><span><button>do something</button></span></div>'
);
(<any>parent.refs.myButton).click();
parent.button.el!.click();
await nextTick();
expect(fixture.innerHTML).toBe(