[FIX] slots: use correct scope and vars at update

Part of #473
This commit is contained in:
Aaron Bohy
2019-11-28 12:11:57 +01:00
committed by Géry Debongnie
parent 9400c0adad
commit 6e5d6aa226
3 changed files with 115 additions and 18 deletions
+21 -12
View File
@@ -65,6 +65,12 @@ interface Internal<T extends Env, Props> {
// its children, those that are not used anymore and thus can be destroyed
parentLastFiberId: number;
// when a rendering is initiated by a parent, it may set variables in 'scope'
// and 'vars' (typically when the component is rendered in a slot). We need to
// store that information in case the component would be re-rendered later on.
scope: any;
vars: any;
boundHandlers: { [key: number]: any };
observer: Observer | null;
renderFn: CompiledTemplate;
@@ -184,7 +190,9 @@ export class Component<T extends Env, Props extends {}> {
observer: null,
renderFn: qweb.render.bind(qweb, template),
classObj: null,
refs: null
refs: null,
scope: null,
vars: null
};
}
@@ -287,11 +295,13 @@ export class Component<T extends Env, Props extends {}> {
return Promise.resolve();
}
if (!(target instanceof HTMLElement || target instanceof DocumentFragment)) {
let message = `Component '${this.constructor.name}' cannot be mounted: the target is not a valid DOM node.`;
let message = `Component '${
this.constructor.name
}' cannot be mounted: the target is not a valid DOM node.`;
message += `\nMaybe the DOM is not ready yet? (in that case, you can use owl.utils.whenReady)`;
throw new Error(message);
}
const fiber = new Fiber(null, this, undefined, undefined, false, target);
const fiber = new Fiber(null, this, false, target);
if (!__owl__.vnode) {
this.__prepareAndRender(fiber);
} else {
@@ -335,7 +345,7 @@ export class Component<T extends Env, Props extends {}> {
// currentFiber that is already rendered (isRendered is true), so we are
// about to be mounted
const isMounted = __owl__.isMounted;
const fiber = new Fiber(null, this, undefined, undefined, force, null);
const fiber = new Fiber(null, this, force, null);
Promise.resolve().then(() => {
if (__owl__.isMounted || !isMounted) {
// we are mounted (__owl__.isMounted), or if we are currently being
@@ -476,16 +486,13 @@ export class Component<T extends Env, Props extends {}> {
* The __updateProps method is called by the t-component directive whenever
* it updates a component (so, when the parent template is rerendered).
*/
async __updateProps(
nextProps: Props,
parentFiber: Fiber,
scope: any,
vars: any,
): Promise<void> {
async __updateProps(nextProps: Props, parentFiber: Fiber, scope: any, vars: any): Promise<void> {
this.__owl__.scope = scope;
this.__owl__.vars = vars;
const shouldUpdate = parentFiber.force || this.shouldUpdate(nextProps);
if (shouldUpdate) {
const __owl__ = this.__owl__;
const fiber = new Fiber(parentFiber, this, scope, vars, parentFiber.force, null);
const fiber = new Fiber(parentFiber, this, parentFiber.force, null);
if (!parentFiber.child) {
parentFiber.child = fiber;
} else {
@@ -529,7 +536,9 @@ export class Component<T extends Env, Props extends {}> {
* parent template.
*/
__prepare(parentFiber: Fiber, scope: any, vars: any) {
const fiber = new Fiber(parentFiber, this, scope, vars, parentFiber.force, null);
this.__owl__.scope = scope;
this.__owl__.vars = vars;
const fiber = new Fiber(parentFiber, this, parentFiber.force, null);
fiber.shouldPatch = false;
if (!parentFiber.child) {
parentFiber.child = fiber;
+8 -6
View File
@@ -62,17 +62,19 @@ export class Fiber {
error?: Error;
constructor(parent: Fiber | null, component: Component<any, any>, scope, vars, force, target) {
this.force = force;
this.scope = scope;
this.vars = vars;
constructor(parent: Fiber | null, component: Component<any, any>, force, target) {
this.component = component;
this.force = force;
this.target = target;
const __owl__ = component.__owl__;
this.scope = __owl__.scope;
this.vars = __owl__.vars;
this.root = parent ? parent.root : this;
this.parent = parent;
let oldFiber = component.__owl__.currentFiber;
let oldFiber = __owl__.currentFiber;
if (oldFiber && !oldFiber.isCompleted) {
if (oldFiber.root === oldFiber && !parent) {
// both oldFiber and this fiber are root fibers
@@ -85,7 +87,7 @@ export class Fiber {
this.root.counter++;
component.__owl__.currentFiber = this;
__owl__.currentFiber = this;
}
/**
+86
View File
@@ -2236,6 +2236,36 @@ describe("other directives with t-component", () => {
await nextTick();
expect(normalize(fixture.innerHTML)).toBe("<div><span>hey</span></div>");
});
test("t-foreach with t-component, and update", async () => {
class Child extends Widget {
static template = xml`
<span>
<t t-esc="state.val"/>
<t t-esc="props.val"/>
</span>`;
state = useState({ val: 'A' });
mounted() {
this.state.val = 'B';
}
}
class ParentWidget extends Widget {
static components = { Child };
static template = xml`
<div>
<t t-foreach="Array(2)" t-as="n" t-key="n_index">
<Child val="n_index"/>
</t>
</div>`;
}
const widget = new ParentWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>A0</span><span>A1</span></div>");
await nextTick(); // wait for changes triggered in mounted to be applied
expect(fixture.innerHTML).toBe("<div><span>B0</span><span>B1</span></div>");
});
});
describe("random stuff/miscellaneous", () => {
@@ -4206,6 +4236,62 @@ describe("t-slot directive", () => {
expect(fixture.innerHTML).toBe(`<div><span>1</span><span>2</span></div>`);
});
test("slots in t-foreach and re-rendering", async () => {
class Child extends Widget {
static template = xml`<span><t t-esc="state.val"/><t t-slot="default"/></span>`;
state = useState({ val: 'A' });
mounted() {
this.state.val = 'B';
}
}
class Parent extends Widget {
static components = { Child };
static template = xml`
<div>
<t t-foreach="Array(2)" t-as="n" t-key="n_index">
<Child><t t-esc="n_index"/></Child>
</t>
</div>`;
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>A0</span><span>A1</span></div>");
await nextTick(); // wait for the changes triggered in mounted to be applied
expect(fixture.innerHTML).toBe("<div><span>B0</span><span>B1</span></div>");
});
test("slots in t-foreach with t-set and re-rendering", async () => {
class Child extends Widget {
static template = xml`
<span>
<t t-esc="state.val"/>
<t t-slot="default"/>
</span>`;
state = useState({ val: 'A' });
mounted() {
this.state.val = 'B';
}
}
class ParentWidget extends Widget {
static components = { Child };
static template = xml`
<div>
<t t-foreach="Array(2)" t-as="n" t-key="n_index">
<t t-set="dummy" t-value="n_index"/>
<Child><t t-esc="dummy"/></Child>
</t>
</div>`;
}
const widget = new ParentWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>A0</span><span>A1</span></div>");
await nextTick(); // wait for changes triggered in mounted to be applied
expect(fixture.innerHTML).toBe("<div><span>B0</span><span>B1</span></div>");
});
});
describe("t-model directive", () => {