Compare commits

...

1 Commits

Author SHA1 Message Date
Géry Debongnie 7933328d0a wip 2019-12-02 13:18:50 +01:00
3 changed files with 67 additions and 61 deletions
+49 -34
View File
@@ -180,6 +180,7 @@ export class Fiber {
*/ */
complete() { complete() {
let component = this.component; let component = this.component;
let fiber: Fiber = this;
this.isCompleted = true; this.isCompleted = true;
if (!this.target && !component.__owl__.isMounted) { if (!this.target && !component.__owl__.isMounted) {
return; return;
@@ -194,48 +195,58 @@ export class Fiber {
this._walk(doWork); this._walk(doWork);
const patchLen = patchQueue.length; const patchLen = patchQueue.length;
// call willPatch hook on each fiber of patchQueue try {
for (let i = 0; i < patchLen; i++) { // call willPatch hook on each fiber of patchQueue
const fiber = patchQueue[i]; for (let i = 0; i < patchLen; i++) {
if (fiber.shouldPatch) { fiber = patchQueue[i];
if (fiber.shouldPatch) {
component = fiber.component;
if (component.__owl__.willPatchCB) {
component.__owl__.willPatchCB();
}
component.willPatch();
}
}
// call __patch on each fiber of (reversed) patchQueue
for (let i = patchLen - 1; i >= 0; i--) {
fiber = patchQueue[i];
component = fiber.component; component = fiber.component;
if (component.__owl__.willPatchCB) { component.__patch(fiber.vnode!);
component.__owl__.willPatchCB(); if (!fiber.shouldPatch && (!fiber.target || i !== 0)) {
component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm;
} }
component.willPatch(); component.__owl__.currentFiber = null;
} }
}
// call __patch on each fiber of (reversed) patchQueue // insert into the DOM (mount case)
for (let i = patchLen - 1; i >= 0; i--) { let inDOM = false;
const fiber = patchQueue[i]; if (this.target) {
component = fiber.component; this.target.appendChild(this.component.el!);
component.__patch(fiber.vnode!); inDOM = document.body.contains(this.target);
if (!fiber.shouldPatch && (!fiber.target || i !== 0)) {
component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm;
} }
component.__owl__.currentFiber = null;
}
// insert into the DOM (mount case) // call patched/mounted hook on each fiber of (reversed) patchQueue
let inDOM = false; for (let i = patchLen - 1; i >= 0; i--) {
if (this.target) { fiber = patchQueue[i];
this.target.appendChild(this.component.el!); component = fiber.component;
inDOM = document.body.contains(this.target); if (fiber.shouldPatch && !this.target) {
} component.patched();
if (component.__owl__.patchedCB) {
// call patched/mounted hook on each fiber of (reversed) patchQueue component.__owl__.patchedCB();
for (let i = patchLen - 1; i >= 0; i--) { }
const fiber = patchQueue[i]; } else if (this.target ? inDOM : true) {
component = fiber.component; component.__callMounted();
if (fiber.shouldPatch && !this.target) {
component.patched();
if (component.__owl__.patchedCB) {
component.__owl__.patchedCB();
} }
} else if (this.target ? inDOM : true) {
component.__callMounted();
} }
} catch (e) {
// if there is no current fiber on component, we are in the situation where
// components were patched to the DOM, but a mounted/patched hook threw an
// error. In that case, we cannot manage the error at a lower level than
// the root fiber, since some components may not have been properly mounted
// patched yet.
const errorFiber = component.__owl__.currentFiber ? fiber : this;
errorFiber.handleError(e);
} }
} }
@@ -274,7 +285,11 @@ export class Fiber {
qweb.trigger("error", error); qweb.trigger("error", error);
if (canCatch) { if (canCatch) {
// this.root.isCompleted = false
this.root.isCompleted = false;
// component.__owl__.currentFiber!.root.isCompleted = false;
component.catchError!(error); component.catchError!(error);
} else { } else {
// the 3 next lines aim to mark the root fiber as being in error, and // the 3 next lines aim to mark the root fiber as being in error, and
// to force it to end, without waiting for its children // to force it to end, without waiting for its children
+3 -4
View File
@@ -62,10 +62,9 @@ export class Scheduler {
} }
if (task.fiber.counter === 0) { if (task.fiber.counter === 0) {
if (!task.fiber.error) { if (!task.fiber.error) {
try { task.fiber.complete();
task.fiber.complete(); if (!task.fiber.isCompleted) {
} catch (e) { return true;
task.fiber.handleError(e);
} }
} }
task.callback(); task.callback();
+15 -23
View File
@@ -4992,41 +4992,35 @@ describe("component error handling (catchError)", () => {
test.skip("can catch an error in the mounted call", async () => { test.skip("can catch an error in the mounted call", async () => {
// we do not catch error in mounted anymore // we do not catch error in mounted anymore
console.error = jest.fn(); console.error = jest.fn();
env.qweb.addTemplates(`
<templates> class ErrorComponent extends Component<any,any> {
<div t-name="ErrorBoundary"> static template = xml`<div>Some text</div>`;
<t t-if="state.error">Error handled</t>
<t t-else="1"><t t-slot="default" /></t>
</div>
<div t-name="ErrorComponent">Some text</div>
<div t-name="App">
<ErrorBoundary><ErrorComponent /></ErrorBoundary>
</div>
</templates>`);
class ErrorComponent extends Widget {
mounted() { mounted() {
throw new Error("NOOOOO"); throw new Error("NOOOOO");
} }
} }
class ErrorBoundary extends Widget { class ErrorBoundary extends Component<any,any> {
static template = xml`
<div>
<t t-if="state.error">Error handled</t>
<t t-else="1"><t t-slot="default" /></t>
</div>`;
state = useState({ error: false }); state = useState({ error: false });
catchError() { catchError() {
this.state.error = true; this.state.error = true;
} }
} }
class App extends Widget { class App extends Component<any,any> {
static template = xml`<div><ErrorBoundary><ErrorComponent /></ErrorBoundary></div>`;
static components = { ErrorBoundary, ErrorComponent }; static components = { ErrorBoundary, ErrorComponent };
} }
const app = new App(); const app = new App();
await app.mount(fixture); await app.mount(fixture);
await nextTick();
await nextTick();
await nextTick();
expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>"); expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>");
}); });
test.skip("can catch an error in the willPatch call", async () => { test("can catch an error in the willPatch call", async () => {
// we do not catch error in willPatch anymore // we do not catch error in willPatch anymore
const consoleError = console.error; const consoleError = console.error;
console.error = jest.fn(); console.error = jest.fn();
@@ -5054,18 +5048,16 @@ describe("component error handling (catchError)", () => {
<span><t t-esc="state.message"/></span> <span><t t-esc="state.message"/></span>
<ErrorBoundary><ErrorComponent message="state.message" /></ErrorBoundary> <ErrorBoundary><ErrorComponent message="state.message" /></ErrorBoundary>
</div>`; </div>`;
state = useState({ message: "abc" }); state = { message: "abc" };
static components = { ErrorBoundary, ErrorComponent }; static components = { ErrorBoundary, ErrorComponent };
} }
const app = new App(); const app = new App();
await app.mount(fixture); await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>abc</span><div><div>abc</div></div></div>"); expect(fixture.innerHTML).toBe("<div><span>abc</span><div><div>abc</div></div></div>");
app.state.message = "def"; app.state.message = "def";
await nextTick(); await app.render();
await nextTick();
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>def</span><div>Error handled</div></div>"); expect(fixture.innerHTML).toBe("<div><span>def</span><div>Error handled</div></div>");
expect(console.error).toHaveBeenCalledTimes(1); expect(console.error).toHaveBeenCalledTimes(0);
console.error = consoleError; console.error = consoleError;
}); });