[FIX] component: concurrent rendering issue

Resolved rendering with cancelled fiber for (not yet) destroyed
component -> Cannot read property 'sel' of null

Closes #421
This commit is contained in:
Aaron Bohy
2019-11-08 09:16:04 +01:00
committed by Géry Debongnie
parent 7e721a96b5
commit e47f604449
7 changed files with 98 additions and 33 deletions
+62
View File
@@ -3301,6 +3301,68 @@ describe("async rendering", () => {
await nextTick();
expect(fixture.innerHTML).toBe("<div>a2<b>a2</b><p><span>a2b2</span></p></div>");
});
test("concurrent renderings scenario 10", async () => {
// Here is the global idea of this scenario:
// A
// |
// B <- async willUpdateProps
// ----- <- conditional (initialy false)
// |
// C <- async willStart
// Render A and B normally
// Change the condition on B to trigger a re-rendering with C (async willStart)
// Change the state on A to trigger a global re-rendering, which is blocked
// in B (async willUpdateProps)
// Resolve the willStart of C: the first re-rendering has been cancelled by
// the global re-rendering, but handlers waiting for the rendering promise to
// resolve might execute and we don't want them to crash/do anything
const defB = makeDeferred();
const defC = makeDeferred();
let stateB;
class ComponentC extends Component<any, any> {
static template = xml`<span><t t-esc="props.value"/></span>`;
willStart() {
return defC;
}
}
ComponentC.prototype.__render = jest.fn(ComponentC.prototype.__render);
class ComponentB extends Component<any, any> {
static template = xml`<p><ComponentC t-if="state.hasChild" value="props.value"/></p>`;
state = useState({ hasChild: false });
static components = { ComponentC };
constructor(parent, props) {
super(parent, props);
stateB = this.state;
}
willUpdateProps() {
return defB;
}
}
class ComponentA extends Component<any, any> {
static template = xml`<div><ComponentB value="state.value"/></div>`;
static components = { ComponentB };
state = useState({ value: 1 });
}
const componentA = new ComponentA();
await componentA.mount(fixture);
expect(fixture.innerHTML).toBe("<div><p></p></div>");
stateB.hasChild = true;
await nextTick();
expect(fixture.innerHTML).toBe("<div><p></p></div>");
componentA.state.value = 2;
defC.resolve();
await nextTick();
expect(fixture.innerHTML).toBe("<div><p></p></div>");
defB.resolve();
await nextTick();
expect(fixture.innerHTML).toBe("<div><p><span>2</span></p></div>");
expect(ComponentC.prototype.__render).toHaveBeenCalledTimes(1);
});
});
describe("widget and observable state", () => {