diff --git a/jest.config.js b/jest.config.js index 6b3958da..39af7057 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,6 +3,7 @@ module.exports = { transform: { "^.+\\.ts?$": "ts-jest" }, + verbose: false, testRegex: "(/tests/.*(test|spec))\\.ts?$", moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"] }; diff --git a/src/component.ts b/src/component.ts index ccffa84c..11d1cc40 100644 --- a/src/component.ts +++ b/src/component.ts @@ -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 { renderProps: Props | null; renderPromise: Promise | 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(); + }; + } + } } diff --git a/tests/component.test.ts b/tests/component.test.ts index 5b50204e..4174d46b 100644 --- a/tests/component.test.ts +++ b/tests/component.test.ts @@ -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("
rerendered
"); }); }); + +describe("widget and observable state", () => { + test("widget is rerendered when its state is changed", async () => { + class TestWidget extends Widget { + state = { drink: "water" }; + inlineTemplate = `
`; + } + const widget = new TestWidget(env); + await widget.mount(fixture); + + expect(fixture.innerHTML).toBe("
water
"); + widget.state.drink = "beer"; + + // 2 microtask ticks: one for observer, one for rendering + await nextMicroTick(); + await nextMicroTick(); + expect(fixture.innerHTML).toBe("
beer
"); + }); +});