[IMP] component: re-introduce error handling in lifecycle

This commit is contained in:
Lucas Perais (lpe)
2021-11-08 10:30:36 +01:00
committed by Aaron Bohy
parent bca6afeb90
commit e0c0306acd
8 changed files with 668 additions and 65 deletions
+22 -14
View File
@@ -10,6 +10,7 @@ import {
RootFiber, RootFiber,
__internal__destroyed, __internal__destroyed,
} from "./fibers"; } from "./fibers";
import { handleError, fibersInError } from "./error_handling";
import { applyDefaultProps } from "./props_validation"; import { applyDefaultProps } from "./props_validation";
import { STATUS } from "./status"; import { STATUS } from "./status";
import { applyStyles } from "./style"; import { applyStyles } from "./style";
@@ -41,7 +42,15 @@ export function component(
node.updateAndRender(props, parentFiber); node.updateAndRender(props, parentFiber);
} else { } else {
// new component // new component
const C = isDynamic ? name : parent.constructor.components[name as any]; let C;
if (isDynamic) {
C = name;
} else {
C = parent.constructor.components[name as any];
if (!C) {
throw new Error(`Cannot find the definition of component "${name}"`);
}
}
node = new ComponentNode(C, props, ctx.app, ctx); node = new ComponentNode(C, props, ctx.app, ctx);
ctx.children[key] = node; ctx.children[key] = node;
@@ -112,23 +121,27 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
fiber.root.mounted.push(fiber); fiber.root.mounted.push(fiber);
} }
const component = this.component; const component = this.component;
const prom = Promise.all(this.willStart.map((f) => f.call(component))); try {
await prom; await Promise.all(this.willStart.map((f) => f.call(component)));
} catch (e) {
handleError(this, e);
return;
}
if (this.status === STATUS.NEW && this.fiber === fiber) { if (this.status === STATUS.NEW && this.fiber === fiber) {
this._render(fiber); this._render(fiber);
} }
} }
async render() { async render() {
if (this.fiber && !this.fiber.bdom) { let fiber = this.fiber;
return this.fiber.root.promise; if (fiber && !fiber.bdom && !fibersInError.has(fiber)) {
return fiber.root.promise;
} }
if (!this.bdom && !this.fiber) { if (!this.bdom && !this.fiber) {
// should find a way to return the future mounting promise // should find a way to return the future mounting promise
return; return;
} }
fiber = makeRootFiber(this);
const fiber = makeRootFiber(this);
this.app.scheduler.addFiber(fiber); this.app.scheduler.addFiber(fiber);
await Promise.resolve(); await Promise.resolve();
if (this.status === STATUS.DESTROYED) { if (this.status === STATUS.DESTROYED) {
@@ -143,15 +156,10 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
_render(fiber: Fiber | RootFiber) { _render(fiber: Fiber | RootFiber) {
try { try {
fiber.bdom = this.renderFn(); fiber.bdom = this.renderFn();
fiber.root.counter--;
} catch (e) { } catch (e) {
fiber.root.error = e; handleError(this, e);
this.handleError(fiber);
} }
fiber.root.counter--;
}
handleError(fiber: Fiber) {
fiber.node.app.destroy();
} }
destroy() { destroy() {
+50
View File
@@ -0,0 +1,50 @@
import type { ComponentNode } from "./component_node";
import type { Fiber } from "./fibers";
export const fibersInError: WeakMap<Fiber, Error> = new WeakMap();
export const nodeErrorHandlers: WeakMap<ComponentNode, ((error: Error) => void)[]> = new WeakMap();
function _handleError(node: ComponentNode | null, error: Error): boolean {
if (!node) {
return false;
}
const fiber = node.fiber;
if (fiber) {
fibersInError.set(fiber, error);
}
const errorHandlers = nodeErrorHandlers.get(node);
if (errorHandlers) {
if (fiber && !fiber.children.length) {
fiber.root.counter--;
}
let propagate = true;
for (const h of errorHandlers) {
try {
h(error);
propagate = false;
} catch (e) {
error = e as Error;
}
}
if (propagate) {
return _handleError(node.parent, error);
}
return true;
} else {
return _handleError(node.parent, error);
}
}
export function handleError(node: ComponentNode, error: Error) {
const fiber = node.fiber!;
fibersInError.set(fiber.root, error);
if (!_handleError(node, error)) {
try {
node.app.destroy();
} catch (e) {}
}
}
+5 -1
View File
@@ -2,6 +2,7 @@ import type { BDom } from "../blockdom";
import { mount } from "../blockdom"; import { mount } from "../blockdom";
import type { ComponentNode } from "./component_node"; import type { ComponentNode } from "./component_node";
import { STATUS } from "./status"; import { STATUS } from "./status";
import { fibersInError } from "./error_handling";
// import { mountBlock } from "./bdom/block"; // import { mountBlock } from "./bdom/block";
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber { export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
@@ -27,6 +28,10 @@ export function makeRootFiber(node: ComponentNode): Fiber {
current.children = []; current.children = [];
root.counter++; root.counter++;
current.bdom = null; current.bdom = null;
if (fibersInError.has(current)) {
fibersInError.delete(current);
fibersInError.delete(root);
}
return current; return current;
} }
const fiber = new RootFiber(node); const fiber = new RootFiber(node);
@@ -81,7 +86,6 @@ export class Fiber {
export class RootFiber extends Fiber { export class RootFiber extends Fiber {
counter: number = 1; counter: number = 1;
error: Error | null = null;
resolve: any; resolve: any;
promise: Promise<any>; promise: Promise<any>;
reject: any; reject: any;
+13
View File
@@ -1,4 +1,5 @@
import { getCurrent } from "./component_node"; import { getCurrent } from "./component_node";
import { nodeErrorHandlers } from "./error_handling";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// hooks // hooks
@@ -47,3 +48,15 @@ export function onRender(fn: () => void | any) {
return renderFn(); return renderFn();
}; };
} }
export function onError(fn: (error: Error) => void | any) {
const node = getCurrent()!;
let handlers = nodeErrorHandlers.get(node);
if (handlers) {
handlers.push(fn);
} else {
handlers = [];
handlers.push(fn);
nodeErrorHandlers.set(node, handlers);
}
}
+13 -5
View File
@@ -1,4 +1,5 @@
import { Fiber, RootFiber } from "./fibers"; import { Fiber, RootFiber } from "./fibers";
import { handleError, fibersInError } from "./error_handling";
import { STATUS } from "./status"; import { STATUS } from "./status";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -46,20 +47,27 @@ export class Scheduler {
this.tasks.delete(fiber); this.tasks.delete(fiber);
return; return;
} }
if (fiber.error) { const hasError = fibersInError.has(fiber);
if (hasError && fiber.counter !== 0) {
this.tasks.delete(fiber); this.tasks.delete(fiber);
fiber.reject(fiber.error); fiber.reject(fibersInError.get(fiber));
return; return;
} }
if (fiber.node.status === STATUS.DESTROYED) { if (fiber.node.status === STATUS.DESTROYED) {
this.tasks.delete(fiber); this.tasks.delete(fiber);
return; return;
} }
if (fiber.counter === 0) { if (fiber.counter === 0) {
if (!fiber.error) { if (!hasError) {
fiber.complete(); try {
fiber.complete();
fiber.resolve();
} catch (e) {
handleError(fiber.node, e);
fiber.reject(e);
}
} }
fiber.resolve();
this.tasks.delete(fiber); this.tasks.delete(fiber);
} }
}); });
+1
View File
@@ -68,6 +68,7 @@ export {
onPatched, onPatched,
onRender, onRender,
onDestroyed, onDestroyed,
onError,
} from "./component/lifecycle_hooks"; } from "./component/lifecycle_hooks";
export const __info__ = {}; export const __info__ = {};
@@ -1,5 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`basics display a nice error if it cannot find component 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
return function template(ctx, node, key = \\"\\") {
return component(\`SomeMispelledComponent\`, {}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`basics no component catching error lead to full app destruction 1`] = ` exports[`basics no component catching error lead to full app destruction 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
@@ -29,3 +41,515 @@ exports[`basics no component catching error lead to full app destruction 2`] = `
} }
}" }"
`; `;
exports[`basics simple catchError 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['a'].b.c;
return block1([d1]);
}
}"
`;
exports[`basics simple catchError 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['error']) {
b2 = text(\`Error\`);
} else {
b3 = component(\`Boom\`, {}, key + \`__1\`, node, ctx);
}
return block1([], [b2, b3]);
}
}"
`;
exports[`can catch errors can catch an error in a component render function 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div>hey<block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['props'].flag&&ctx['state'].this.will.crash;
return block1([d1]);
}
}"
`;
exports[`can catch errors can catch an error in a component render function 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/><block-child-1/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['state'].error) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
}"
`;
exports[`can catch errors can catch an error in a component render function 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => {
return component(\`ErrorComponent\`, {flag: ctx['state'].flag}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
`;
exports[`can catch errors can catch an error in the constructor call of a component render function 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div>Some text</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`can catch errors can catch an error in the constructor call of a component render function 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/><block-child-1/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['state'].error) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
}"
`;
exports[`can catch errors can catch an error in the constructor call of a component render function 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
`;
exports[`can catch errors can catch an error in the initial call of a component render function (parent mounted) 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div>hey<block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['state'].this.will.crash;
return block1([d1]);
}
}"
`;
exports[`can catch errors can catch an error in the initial call of a component render function (parent mounted) 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/><block-child-1/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['state'].error) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
}"
`;
exports[`can catch errors can catch an error in the initial call of a component render function (parent mounted) 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
`;
exports[`can catch errors can catch an error in the initial call of a component render function (parent updated) 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div>hey<block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['state'].this.will.crash;
return block1([d1]);
}
}"
`;
exports[`can catch errors can catch an error in the initial call of a component render function (parent updated) 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/><block-child-1/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['state'].error) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
}"
`;
exports[`can catch errors can catch an error in the initial call of a component render function (parent updated) 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3;
if (ctx['state'].flag) {
b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
}
return block1([], [b3]);
}
}"
`;
exports[`can catch errors can catch an error in the willStart call 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div>Some text</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`can catch errors can catch an error in the willStart call 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/><block-child-1/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['state'].error) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
}"
`;
exports[`can catch errors can catch an error in the willStart call 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
`;
exports[`can catch errors catchError in catchError 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['a'].b.c;
return block1([d1]);
}
}"
`;
exports[`can catch errors catchError in catchError 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Boom\`, {}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
`;
exports[`can catch errors catchError in catchError 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['error']) {
b2 = text(\`Error\`);
} else {
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
}
return block1([], [b2, b3]);
}
}"
`;
exports[`errors and promises a rendering error in a sub component will reject the mount promise 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = this.will.crash;
return block1([d1]);
}
}"
`;
exports[`errors and promises a rendering error in a sub component will reject the mount promise 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
`;
exports[`errors and promises a rendering error will reject the mount promise 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = this.will.crash;
return block1([d1]);
}
}"
`;
exports[`errors and promises a rendering error will reject the render promise (with sub components) 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<span/>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`errors and promises a rendering error will reject the render promise (with sub components) 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
let d1 = ctx['x'].y;
return block1([d1], [b2]);
}
}"
`;
exports[`errors and promises a rendering error will reject the render promise 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2;
if (ctx['flag']) {
b2 = text(this.will.crash);
}
return block1([], [b2]);
}
}"
`;
exports[`errors and promises an error in mounted call will reject the mount promise 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div>abc</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`errors and promises an error in patched call will reject the render promise 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['val'];
return block1([d1]);
}
}"
`;
exports[`errors and promises an error in willPatch call will reject the render promise 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['val'];
return block1([d1]);
}
}"
`;
exports[`errors and promises errors in mounted and in willUnmount 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
let block1 = createBlock(\`<div/>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
+40 -45
View File
@@ -9,6 +9,7 @@ import {
onWillStart, onWillStart,
onWillUnmount, onWillUnmount,
useState, useState,
onError,
} from "../../src/index"; } from "../../src/index";
let fixture: HTMLElement; let fixture: HTMLElement;
@@ -48,7 +49,7 @@ describe("basics", () => {
expect(error.message).toMatch(regexp); expect(error.message).toMatch(regexp);
}); });
test.skip("display a nice error if it cannot find component", async () => { test("display a nice error if it cannot find component", async () => {
const consoleError = console.error; const consoleError = console.error;
console.error = jest.fn(); console.error = jest.fn();
@@ -64,12 +65,12 @@ describe("basics", () => {
error = e; error = e;
} }
expect(error).toBeDefined(); expect(error).toBeDefined();
expect(error.message).toBe('Cannot find the definition of component "SomeMispelledWidget"'); expect(error.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
expect(console.error).toBeCalledTimes(0); expect(console.error).toBeCalledTimes(0);
console.error = consoleError; console.error = consoleError;
}); });
test.skip("simple catchError", async () => { test("simple catchError", async () => {
class Boom extends Component { class Boom extends Component {
static template = xml`<div t-esc="a.b.c"/>`; static template = xml`<div t-esc="a.b.c"/>`;
} }
@@ -84,20 +85,21 @@ describe("basics", () => {
</div>`; </div>`;
static components = { Boom }; static components = { Boom };
error = false; error: any = false;
// catchError(error) { setup() {
// this.error = error; onError((err) => {
// this.render(); this.error = err;
// } this.render();
});
}
} }
await mount(Parent, fixture); await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div>Error</div>"); expect(fixture.innerHTML).toBe("<div>Error</div>");
}); });
}); });
describe.skip("errors and promises", () => { describe("errors and promises", () => {
test("a rendering error will reject the mount promise", async () => { test("a rendering error will reject the mount promise", async () => {
const consoleError = console.error; const consoleError = console.error;
console.error = jest.fn(() => {}); console.error = jest.fn(() => {});
@@ -283,9 +285,8 @@ describe.skip("errors and promises", () => {
expect(error.message).toMatch(regexp); expect(error.message).toMatch(regexp);
}); });
// LPE: relevant: dead code....?
test("errors in mounted and in willUnmount", async () => { test("errors in mounted and in willUnmount", async () => {
expect.assertions(1); expect.assertions(2); // apparently this expect count in assertions...
class Example extends Component { class Example extends Component {
static template = xml`<div/>`; static template = xml`<div/>`;
val: any; val: any;
@@ -309,12 +310,10 @@ describe.skip("errors and promises", () => {
}); });
}); });
describe.skip("can catch errors", () => { describe("can catch errors", () => {
test("can catch an error in a component render function", async () => { test("can catch an error in a component render function", async () => {
const consoleError = console.error; const consoleError = console.error;
console.error = jest.fn(); console.error = jest.fn();
const handler = jest.fn();
//env.qweb.on("error", null, handler);
class ErrorComponent extends Component { class ErrorComponent extends Component {
static template = xml`<div>hey<t t-esc="props.flag and state.this.will.crash"/></div>`; static template = xml`<div>hey<t t-esc="props.flag and state.this.will.crash"/></div>`;
} }
@@ -326,8 +325,8 @@ describe.skip("can catch errors", () => {
</div>`; </div>`;
state = useState({ error: false }); state = useState({ error: false });
catchError() { setup() {
this.state.error = true; onError(() => (this.state.error = true));
} }
} }
class App extends Component { class App extends Component {
@@ -346,12 +345,9 @@ describe.skip("can catch errors", () => {
expect(console.error).toBeCalledTimes(0); expect(console.error).toBeCalledTimes(0);
console.error = consoleError; console.error = consoleError;
expect(handler).toBeCalledTimes(1);
}); });
test("can catch an error in the initial call of a component render function (parent mounted)", async () => { test("can catch an error in the initial call of a component render function (parent mounted)", async () => {
const handler = jest.fn();
//env.qweb.on("error", null, handler);
const consoleError = console.error; const consoleError = console.error;
console.error = jest.fn(); console.error = jest.fn();
class ErrorComponent extends Component { class ErrorComponent extends Component {
@@ -365,8 +361,10 @@ describe.skip("can catch errors", () => {
</div>`; </div>`;
state = useState({ error: false }); state = useState({ error: false });
catchError() { setup() {
this.state.error = true; onError(() => {
this.state.error = true;
});
} }
} }
class App extends Component { class App extends Component {
@@ -381,12 +379,9 @@ describe.skip("can catch errors", () => {
expect(console.error).toBeCalledTimes(0); expect(console.error).toBeCalledTimes(0);
console.error = consoleError; console.error = consoleError;
expect(handler).toBeCalledTimes(1);
}); });
test("can catch an error in the initial call of a component render function (parent updated)", async () => { test("can catch an error in the initial call of a component render function (parent updated)", async () => {
const handler = jest.fn();
//env.qweb.on("error", null, handler);
const consoleError = console.error; const consoleError = console.error;
console.error = jest.fn(); console.error = jest.fn();
class ErrorComponent extends Component { class ErrorComponent extends Component {
@@ -400,8 +395,8 @@ describe.skip("can catch errors", () => {
</div>`; </div>`;
state = useState({ error: false }); state = useState({ error: false });
catchError() { setup() {
this.state.error = true; onError(() => (this.state.error = true));
} }
} }
class App extends Component { class App extends Component {
@@ -419,12 +414,9 @@ describe.skip("can catch errors", () => {
expect(console.error).toBeCalledTimes(0); expect(console.error).toBeCalledTimes(0);
console.error = consoleError; console.error = consoleError;
expect(handler).toBeCalledTimes(1);
}); });
test("can catch an error in the constructor call of a component render function", async () => { test("can catch an error in the constructor call of a component render function", async () => {
const handler = jest.fn();
//env.qweb.on("error", null, handler);
const consoleError = console.error; const consoleError = console.error;
console.error = jest.fn(); console.error = jest.fn();
@@ -441,12 +433,12 @@ describe.skip("can catch errors", () => {
</div>`; </div>`;
state = useState({ error: false }); state = useState({ error: false });
catchError() { setup() {
this.state.error = true; onError(() => (this.state.error = true));
} }
} }
class App extends Component { class App extends Component {
static template = xml`<div"> static template = xml`<div>
<ErrorBoundary><ErrorComponent /></ErrorBoundary> <ErrorBoundary><ErrorComponent /></ErrorBoundary>
</div>`; </div>`;
static components = { ErrorBoundary, ErrorComponent }; static components = { ErrorBoundary, ErrorComponent };
@@ -456,14 +448,13 @@ describe.skip("can catch errors", () => {
expect(console.error).toBeCalledTimes(0); expect(console.error).toBeCalledTimes(0);
console.error = consoleError; console.error = consoleError;
expect(handler).toBeCalledTimes(1);
}); });
test("can catch an error in the willStart call", async () => { test("can catch an error in the willStart call", async () => {
const consoleError = console.error; const consoleError = console.error;
console.error = jest.fn(); console.error = jest.fn();
class ErrorComponent extends Component { class ErrorComponent extends Component {
static template = xml`<div t-name="ErrorComponent">Some text</div>`; static template = xml`<div>Some text</div>`;
setup() { setup() {
onWillStart(async () => { onWillStart(async () => {
// we wait a little bit to be in a different stack frame // we wait a little bit to be in a different stack frame
@@ -480,8 +471,8 @@ describe.skip("can catch errors", () => {
</div>`; </div>`;
state = useState({ error: false }); state = useState({ error: false });
catchError() { setup() {
this.state.error = true; onError(() => (this.state.error = true));
} }
} }
class App extends Component { class App extends Component {
@@ -587,9 +578,11 @@ describe.skip("can catch errors", () => {
</div>`; </div>`;
static components = { Boom }; static components = { Boom };
// catchError(error) { setup() {
// throw error; onError((error) => {
// } throw error;
});
}
} }
class Parent extends Component { class Parent extends Component {
@@ -602,12 +595,14 @@ describe.skip("can catch errors", () => {
</div>`; </div>`;
static components = { Child }; static components = { Child };
error = false; error: any = false;
// catchError(error) { setup() {
// this.error = error; onError((error) => {
// this.render(); this.error = error;
// } this.render();
});
}
} }
await mount(Parent, fixture); await mount(Parent, fixture);