diff --git a/src/component.ts b/src/component.ts index bc6835e0..7c6b89fb 100644 --- a/src/component.ts +++ b/src/component.ts @@ -227,7 +227,6 @@ export class Component< } this._patch(vnode); target.appendChild(this.el!); - this._observeState(); if (document.body.contains(target)) { this._visitSubTree(w => { @@ -357,6 +356,7 @@ export class Component< true ); } + this._observeState(); return this._render(); }); return this.__owl__.renderPromise; @@ -366,11 +366,13 @@ export class Component< this.__owl__.renderId++; const promises: Promise[] = []; const template = this.inlineTemplate || this.template; + this.__owl__.observer.allowMutations = false; let vnode = this.env.qweb.render(template, this, { promises, handlers: this.__owl__.boundHandlers, forceUpdate: force }); + this.__owl__.observer.allowMutations = true; // this part is critical for the patching process to be done correctly. The // tricky part is that a child widget can be rerendered on its own, which diff --git a/tests/component.test.ts b/tests/component.test.ts index aabb2d80..0b8b3765 100644 --- a/tests/component.test.ts +++ b/tests/component.test.ts @@ -1426,4 +1426,27 @@ describe("widget and observable state", () => { await nextMicroTick(); expect(fixture.innerHTML).toBe("
beer
"); }); + + test("subwidgets cannot change observable state received from parent", async () => { + expect.assertions(1); + class Parent extends Widget { + state = { obj: { coffee: 1 } }; + widgets = { Child }; + inlineTemplate = `
`; + } + class Child extends Widget { + constructor(parent, props) { + super(parent, props); + props.coffee = 2; + } + } + const parent = new Parent(env); + try { + await parent.mount(fixture); + } catch (e) { + expect(e.message).toBe( + 'Observed state cannot be changed here! (key: "coffee", val: "2")' + ); + } + }); });