[IMP] component: observe the state and rerender if it changes

closes #35
This commit is contained in:
Géry Debongnie
2019-04-16 22:59:54 +02:00
parent 94e7d9aa39
commit 619e455dd5
3 changed files with 45 additions and 9 deletions
+1
View File
@@ -3,6 +3,7 @@ module.exports = {
transform: {
"^.+\\.ts?$": "ts-jest"
},
verbose: false,
testRegex: "(/tests/.*(test|spec))\\.ts?$",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"]
};
+16 -4
View File
@@ -5,6 +5,7 @@ import sdListeners from "../libs/snabbdom/src/modules/eventlisteners";
import { init } from "../libs/snabbdom/src/snabbdom";
import { VNode } from "../libs/snabbdom/src/vnode";
import { EventBus } from "./event_bus";
import { Observer } from "./observer";
import { QWeb } from "./qweb";
import { idGenerator } from "./utils";
@@ -34,6 +35,7 @@ export interface Meta<T extends Env, Props> {
renderProps: Props | null;
renderPromise: Promise<VNode> | null;
boundHandlers: { [key: number]: any };
observer: Observer;
}
const patch = init([sdListeners, sdAttrs, sdProps]);
@@ -118,7 +120,8 @@ export class Component<
renderId: 1,
renderPromise: null,
renderProps: props || null,
boundHandlers: {}
boundHandlers: {},
observer: new Observer()
};
}
@@ -232,6 +235,7 @@ export class Component<
this._visitSubTree(w => {
if (!w.__owl__.isMounted && this.el!.contains(w.el)) {
w.__owl__.isMounted = true;
this._observeState();
w.mounted();
return true;
}
@@ -312,7 +316,6 @@ export class Component<
if (this.__owl__.isMounted) {
await this.render(true);
}
this.patched();
}
async updateProps(
@@ -340,7 +343,6 @@ export class Component<
if (this.__owl__.isStarted) {
await this.render();
}
this.patched();
}
//--------------------------------------------------------------------------
@@ -351,7 +353,6 @@ export class Component<
await this.willUpdateProps(nextProps);
this.props = nextProps;
await this.render();
this.patched();
}
_patch(vnode) {
@@ -359,6 +360,7 @@ export class Component<
if (this.__owl__.vnode) {
this.willPatch();
this.__owl__.vnode = patch(this.__owl__.vnode, vnode);
this.patched();
} else {
this.__owl__.vnode = patch(document.createElement(vnode.sel!), vnode);
}
@@ -419,6 +421,7 @@ export class Component<
if (this.__owl__.parent) {
if (this.__owl__.parent!.__owl__.isMounted) {
this.__owl__.isMounted = true;
this._observeState();
this.mounted();
const children = this.__owl__.children;
for (let id in children) {
@@ -437,4 +440,13 @@ export class Component<
}
}
}
_observeState() {
if (Object.keys(this.state).length) {
this.__owl__.observer.observe(this.state);
this.__owl__.observer.notifyCB = () => {
this.render();
};
}
}
}
+28 -5
View File
@@ -259,7 +259,8 @@ describe("lifecycle hooks", () => {
const widget = new ParentWidget(env);
await widget.mount(fixture);
expect(hookCounter).toBe(0); // sub widget not created yet
await widget.updateState({ ok: true });
widget.state.ok = true;
await nextTick();
expect(hookCounter).toBe(2);
});
@@ -566,7 +567,8 @@ describe("lifecycle hooks", () => {
const widget = new ParentWidget(env);
await widget.mount(fixture);
expect(steps).toEqual([]);
await widget.updateState({ n: 2 });
widget.state.n = 2;
await nextTick();
// Not sure about this order. If you disagree, feel free to open an issue...
expect(steps).toEqual([
@@ -1181,10 +1183,12 @@ describe("async rendering", () => {
const w = new W(env);
await w.mount(fixture);
expect(n).toBe(0);
w.updateState({ val: 2 });
w.state.val = 2;
await nextMicroTick();
expect(n).toBe(1);
await nextTick();
w.updateState({ val: 3 });
w.state.val = 3;
await nextMicroTick();
expect(n).toBe(2);
def.resolve();
await nextTick();
@@ -1391,3 +1395,22 @@ describe("updating environment", () => {
expect(fixture.innerHTML).toBe("<div><div>rerendered</div></div>");
});
});
describe("widget and observable state", () => {
test("widget is rerendered when its state is changed", async () => {
class TestWidget extends Widget {
state = { drink: "water" };
inlineTemplate = `<div><t t-esc="state.drink"/></div>`;
}
const widget = new TestWidget(env);
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div>water</div>");
widget.state.drink = "beer";
// 2 microtask ticks: one for observer, one for rendering
await nextMicroTick();
await nextMicroTick();
expect(fixture.innerHTML).toBe("<div>beer</div>");
});
});