mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 45fa6745dd |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.0.0-beta-5",
|
"version": "2.0.0-beta-4",
|
||||||
"description": "Odoo Web Library (OWL)",
|
"description": "Odoo Web Library (OWL)",
|
||||||
"main": "dist/owl.cjs.js",
|
"main": "dist/owl.cjs.js",
|
||||||
"browser": "dist/owl.iife.js",
|
"browser": "dist/owl.iife.js",
|
||||||
|
|||||||
+1
-1
@@ -67,7 +67,7 @@ export class App<
|
|||||||
}
|
}
|
||||||
|
|
||||||
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
|
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
|
||||||
return new ComponentNode(Component, props, this, null, null);
|
return new ComponentNode(Component, props, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
mountNode(node: ComponentNode, target: HTMLElement, options?: MountOptions) {
|
mountNode(node: ComponentNode, target: HTMLElement, options?: MountOptions) {
|
||||||
|
|||||||
@@ -79,8 +79,13 @@ export function component<P extends object>(
|
|||||||
let node: any = ctx.children[key];
|
let node: any = ctx.children[key];
|
||||||
let isDynamic = typeof name !== "string";
|
let isDynamic = typeof name !== "string";
|
||||||
|
|
||||||
if (node && node.status === STATUS.DESTROYED) {
|
if (node) {
|
||||||
node = undefined;
|
if (node.status < STATUS.MOUNTED) {
|
||||||
|
node.destroy();
|
||||||
|
node = undefined;
|
||||||
|
} else if (node.status === STATUS.DESTROYED) {
|
||||||
|
node = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isDynamic && node && node.component.constructor !== name) {
|
if (isDynamic && node && node.component.constructor !== name) {
|
||||||
node = undefined;
|
node = undefined;
|
||||||
@@ -109,11 +114,13 @@ export function component<P extends object>(
|
|||||||
throw new Error(`Cannot find the definition of component "${name}"`);
|
throw new Error(`Cannot find the definition of component "${name}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node = new ComponentNode(C, props, ctx.app, ctx, key);
|
node = new ComponentNode(C, props, ctx.app, ctx);
|
||||||
ctx.children[key] = node;
|
ctx.children[key] = node;
|
||||||
node.initiateRender(new Fiber(node, parentFiber));
|
node.initiateRender(new Fiber(node, parentFiber));
|
||||||
}
|
}
|
||||||
parentFiber.childrenMap[key] = node;
|
parentFiber.childrenMap[key] = node;
|
||||||
|
|
||||||
|
parentFiber.root!.reachedChildren.add(node);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +138,6 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
|||||||
bdom: BDom | null = null;
|
bdom: BDom | null = null;
|
||||||
status: STATUS = STATUS.NEW;
|
status: STATUS = STATUS.NEW;
|
||||||
forceNextRender: boolean = false;
|
forceNextRender: boolean = false;
|
||||||
parentKey: string | null;
|
|
||||||
|
|
||||||
renderFn: Function;
|
renderFn: Function;
|
||||||
parent: ComponentNode | null;
|
parent: ComponentNode | null;
|
||||||
@@ -148,17 +154,10 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
|||||||
patched: LifecycleHook[] = [];
|
patched: LifecycleHook[] = [];
|
||||||
willDestroy: LifecycleHook[] = [];
|
willDestroy: LifecycleHook[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(C: ComponentConstructor<P, E>, props: P, app: App, parent?: ComponentNode) {
|
||||||
C: ComponentConstructor<P, E>,
|
|
||||||
props: P,
|
|
||||||
app: App,
|
|
||||||
parent: ComponentNode | null,
|
|
||||||
parentKey: string | null
|
|
||||||
) {
|
|
||||||
currentNode = this;
|
currentNode = this;
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.parent = parent;
|
this.parent = parent || null;
|
||||||
this.parentKey = parentKey;
|
|
||||||
this.level = parent ? parent.level + 1 : 0;
|
this.level = parent ? parent.level + 1 : 0;
|
||||||
applyDefaultProps(props, C);
|
applyDefaultProps(props, C);
|
||||||
const env = (parent && parent.childEnv) || app.env;
|
const env = (parent && parent.childEnv) || app.env;
|
||||||
@@ -195,7 +194,7 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
|||||||
|
|
||||||
async render(deep: boolean = false) {
|
async render(deep: boolean = false) {
|
||||||
let current = this.fiber;
|
let current = this.fiber;
|
||||||
if (current && (current.root!.locked || (current as any).bdom === true)) {
|
if (current && current.root!.locked) {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
// situation may have changed after the microtask tick
|
// situation may have changed after the microtask tick
|
||||||
current = this.fiber;
|
current = this.fiber;
|
||||||
@@ -217,7 +216,6 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
|||||||
const fiber = makeRootFiber(this);
|
const fiber = makeRootFiber(this);
|
||||||
fiber.deep = deep;
|
fiber.deep = deep;
|
||||||
this.fiber = fiber;
|
this.fiber = fiber;
|
||||||
|
|
||||||
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) {
|
||||||
@@ -257,14 +255,8 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
|||||||
for (let child of Object.values(this.children)) {
|
for (let child of Object.values(this.children)) {
|
||||||
child._destroy();
|
child._destroy();
|
||||||
}
|
}
|
||||||
if (this.willDestroy.length) {
|
for (let cb of this.willDestroy) {
|
||||||
try {
|
cb.call(component);
|
||||||
for (let cb of this.willDestroy) {
|
|
||||||
cb.call(component);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
handleError({ error: e, node: this });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.status = STATUS.DESTROYED;
|
this.status = STATUS.DESTROYED;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { Fiber } from "./fibers";
|
|||||||
export const fibersInError: WeakMap<Fiber, any> = new WeakMap();
|
export const fibersInError: WeakMap<Fiber, any> = new WeakMap();
|
||||||
export const nodeErrorHandlers: WeakMap<ComponentNode, ((error: any) => void)[]> = new WeakMap();
|
export const nodeErrorHandlers: WeakMap<ComponentNode, ((error: any) => void)[]> = new WeakMap();
|
||||||
|
|
||||||
function _handleError(node: ComponentNode | null, error: any): boolean {
|
function _handleError(node: ComponentNode | null, error: any, isFirstRound = false): boolean {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -16,19 +16,23 @@ function _handleError(node: ComponentNode | null, error: any): boolean {
|
|||||||
|
|
||||||
const errorHandlers = nodeErrorHandlers.get(node);
|
const errorHandlers = nodeErrorHandlers.get(node);
|
||||||
if (errorHandlers) {
|
if (errorHandlers) {
|
||||||
let handled = false;
|
let stopped = false;
|
||||||
// execute in the opposite order
|
// execute in the opposite order
|
||||||
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
||||||
try {
|
try {
|
||||||
errorHandlers[i](error);
|
errorHandlers[i](error);
|
||||||
handled = true;
|
stopped = true;
|
||||||
break;
|
break;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handled) {
|
if (stopped) {
|
||||||
|
if (isFirstRound && fiber && fiber.node.fiber) {
|
||||||
|
const root = fiber.root!;
|
||||||
|
root.setCounter(root.counter - 1);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +55,7 @@ export function handleError(params: ErrorParams) {
|
|||||||
|
|
||||||
fibersInError.set(fiber.root!, error);
|
fibersInError.set(fiber.root!, error);
|
||||||
|
|
||||||
const handled = _handleError(node, error);
|
const handled = _handleError(node, error, true);
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
||||||
try {
|
try {
|
||||||
|
|||||||
+17
-10
@@ -16,15 +16,13 @@ export function makeRootFiber(node: ComponentNode): Fiber {
|
|||||||
let current = node.fiber;
|
let current = node.fiber;
|
||||||
if (current) {
|
if (current) {
|
||||||
let root = current.root!;
|
let root = current.root!;
|
||||||
// lock root fiber because canceling children fibers may destroy components,
|
|
||||||
// which means any arbitrary code can be run in onWillDestroy, which may
|
|
||||||
// trigger new renderings
|
|
||||||
root.locked = true;
|
|
||||||
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
||||||
root.locked = false;
|
|
||||||
current.children = [];
|
current.children = [];
|
||||||
current.childrenMap = {};
|
current.childrenMap = {};
|
||||||
current.bdom = null;
|
current.bdom = null;
|
||||||
|
if (current === root) {
|
||||||
|
root.reachedChildren = new WeakSet();
|
||||||
|
}
|
||||||
if (fibersInError.has(current)) {
|
if (fibersInError.has(current)) {
|
||||||
fibersInError.delete(current);
|
fibersInError.delete(current);
|
||||||
fibersInError.delete(root);
|
fibersInError.delete(root);
|
||||||
@@ -101,11 +99,16 @@ export class Fiber {
|
|||||||
while (current) {
|
while (current) {
|
||||||
if (current.fiber) {
|
if (current.fiber) {
|
||||||
let root = current.fiber.root!;
|
let root = current.fiber.root!;
|
||||||
if (root.counter === 0 && prev.parentKey! in current.fiber.childrenMap) {
|
if (root.counter) {
|
||||||
current = root.node;
|
|
||||||
} else {
|
|
||||||
scheduler.delayedRenders.push(this);
|
scheduler.delayedRenders.push(this);
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
if (!root.reachedChildren.has(prev)) {
|
||||||
|
// is dead. but we keep the render around just in case
|
||||||
|
scheduler.delayedRenders.push(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
current = root.node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prev = current;
|
prev = current;
|
||||||
@@ -118,15 +121,17 @@ export class Fiber {
|
|||||||
|
|
||||||
_render() {
|
_render() {
|
||||||
const node = this.node;
|
const node = this.node;
|
||||||
|
if (node.status > STATUS.MOUNTED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const root = this.root;
|
const root = this.root;
|
||||||
if (root) {
|
if (root) {
|
||||||
try {
|
try {
|
||||||
(this.bdom as any) = true;
|
|
||||||
this.bdom = node.renderFn();
|
this.bdom = node.renderFn();
|
||||||
|
root.setCounter(root.counter - 1);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError({ node, error: e });
|
handleError({ node, error: e });
|
||||||
}
|
}
|
||||||
root.setCounter(root.counter - 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,6 +147,8 @@ export class RootFiber extends Fiber {
|
|||||||
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
||||||
locked: boolean = false;
|
locked: boolean = false;
|
||||||
|
|
||||||
|
reachedChildren: WeakSet<ComponentNode> = new WeakSet();
|
||||||
|
|
||||||
complete() {
|
complete() {
|
||||||
const node = this.node;
|
const node = this.node;
|
||||||
this.locked = true;
|
this.locked = true;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export class Scheduler {
|
|||||||
let renders = this.delayedRenders;
|
let renders = this.delayedRenders;
|
||||||
this.delayedRenders = [];
|
this.delayedRenders = [];
|
||||||
for (let f of renders) {
|
for (let f of renders) {
|
||||||
if (f.root && f.node.status !== STATUS.DESTROYED) {
|
if (f.root) {
|
||||||
f.render();
|
f.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1164,69 +1164,6 @@ exports[`delayed rendering, but then initial rendering is cancelled by yet anoth
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`delayed rendering, destruction, stuff happens 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const b2 = text(\`A\`);
|
|
||||||
const b3 = component(\`B\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`delayed rendering, destruction, stuff happens 2`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2,b3;
|
|
||||||
b2 = text(\`B\`);
|
|
||||||
if (ctx['state'].hasChild) {
|
|
||||||
b3 = component(\`C\`, {value: ctx['state'].someValue+ctx['props'].value}, key + \`__1\`, node, ctx);
|
|
||||||
}
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`delayed rendering, destruction, stuff happens 3`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block4 = createBlock(\`<p><block-text-0/></p>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const b2 = text(\`C\`);
|
|
||||||
const b3 = component(\`D\`, {}, key + \`__1\`, node, ctx);
|
|
||||||
let txt1 = ctx['props'].value;
|
|
||||||
const b4 = block4([txt1]);
|
|
||||||
return multi([b2, b3, b4]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`delayed rendering, destruction, stuff happens 4`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block3 = createBlock(\`<button block-handler-0=\\"click\\"><block-text-1/></button>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const b2 = text(\`D\`);
|
|
||||||
let hdlr1 = [ctx['increment'], ctx];
|
|
||||||
let txt1 = ctx['state'].val;
|
|
||||||
const b3 = block3([hdlr1, txt1]);
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`delayed rendering, reusing fiber and stuff 1`] = `
|
exports[`delayed rendering, reusing fiber and stuff 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -1352,46 +1289,6 @@ exports[`delayed rendering, then component is destroyed and stuff 3`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2,b3;
|
|
||||||
b2 = text(\` A \`);
|
|
||||||
if (ctx['state'].flag) {
|
|
||||||
const b4 = component(\`B\`, {value: ctx['state'].valueB}, key + \`__1\`, node, ctx);
|
|
||||||
const b5 = component(\`C\`, {value: ctx['state'].valueC}, key + \`__2\`, node, ctx);
|
|
||||||
b3 = multi([b4, b5]);
|
|
||||||
}
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 2`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return text(ctx['props'].value);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 3`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return text(ctx['state'].val+ctx['props'].value);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`destroying/recreating a subcomponent, other scenario 1`] = `
|
exports[`destroying/recreating a subcomponent, other scenario 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -1569,70 +1466,6 @@ exports[`rendering parent twice, with different props on child and stuff 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renderings, destruction, patch, stuff, ... yet another variation 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const b2 = text(\`A\`);
|
|
||||||
const b3 = component(\`B\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
|
||||||
const b4 = component(\`D\`, {}, key + \`__2\`, node, ctx);
|
|
||||||
return multi([b2, b3, b4]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`renderings, destruction, patch, stuff, ... yet another variation 2`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2,b3;
|
|
||||||
b2 = text(\`B\`);
|
|
||||||
if (ctx['props'].value===33) {
|
|
||||||
b3 = component(\`C\`, {}, key + \`__1\`, node, ctx);
|
|
||||||
}
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`renderings, destruction, patch, stuff, ... yet another variation 3`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block3 = createBlock(\`<p block-handler-0=\\"click\\"><block-text-1/></p>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const b2 = text(\`D\`);
|
|
||||||
let hdlr1 = [ctx['increment'], ctx];
|
|
||||||
let txt1 = ctx['state'].val;
|
|
||||||
const b3 = block3([hdlr1, txt1]);
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`renderings, destruction, patch, stuff, ... yet another variation 4`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block3 = createBlock(\`<span block-handler-0=\\"click\\"><block-text-1/></span>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const b2 = text(\`C\`);
|
|
||||||
let hdlr1 = [ctx['increment'], ctx];
|
|
||||||
let txt1 = ctx['state'].val;
|
|
||||||
const b3 = block3([hdlr1, txt1]);
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`t-foreach with dynamic async component 1`] = `
|
exports[`t-foreach with dynamic async component 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -85,64 +85,6 @@ exports[`basics simple catchError 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`can catch errors an error in onWillDestroy 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2,b3;
|
|
||||||
b2 = text(ctx['state'].value);
|
|
||||||
if (ctx['state'].hasChild) {
|
|
||||||
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
|
||||||
}
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`can catch errors an error in onWillDestroy 2`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<div>abc</div>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return block1();
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`can catch errors an error in onWillDestroy, variation 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2,b3;
|
|
||||||
b2 = text(ctx['state'].value);
|
|
||||||
if (ctx['state'].hasChild) {
|
|
||||||
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
|
||||||
}
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`can catch errors an error in onWillDestroy, variation 2`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<div>abc</div>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return block1();
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`can catch errors calling a hook outside setup should crash 1`] = `
|
exports[`can catch errors calling a hook outside setup should crash 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
mount,
|
mount,
|
||||||
onMounted,
|
onMounted,
|
||||||
onRendered,
|
onRendered,
|
||||||
onWillDestroy,
|
|
||||||
onWillStart,
|
onWillStart,
|
||||||
onWillUnmount,
|
onWillUnmount,
|
||||||
onWillUpdateProps,
|
onWillUpdateProps,
|
||||||
@@ -173,11 +172,6 @@ test("destroying/recreating a subcomponent, other scenario", async () => {
|
|||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect([
|
expect([
|
||||||
"Parent:willRender",
|
|
||||||
"Child:setup",
|
|
||||||
"Child:willStart",
|
|
||||||
"Parent:rendered",
|
|
||||||
"Child:willDestroy",
|
|
||||||
"Parent:willRender",
|
"Parent:willRender",
|
||||||
"Child:setup",
|
"Child:setup",
|
||||||
"Child:willStart",
|
"Child:willStart",
|
||||||
@@ -3696,294 +3690,6 @@ test("another scenario with delayed rendering", async () => {
|
|||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("destroyed component causes other soon to be destroyed component to rerender, weird stuff happens", async () => {
|
|
||||||
let def = makeDeferred();
|
|
||||||
let c: any = null;
|
|
||||||
|
|
||||||
class B extends Component {
|
|
||||||
static template = xml`<t t-esc="props.value"/>`;
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
onRendered(() => {
|
|
||||||
def.resolve();
|
|
||||||
});
|
|
||||||
onWillDestroy(() => {
|
|
||||||
c.state.val++;
|
|
||||||
c.render();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class C extends Component {
|
|
||||||
static template = xml`<t t-esc="state.val + props.value"/>`;
|
|
||||||
state = useState({ val: 0 });
|
|
||||||
setup() {
|
|
||||||
c = this;
|
|
||||||
useLogLifecycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class A extends Component {
|
|
||||||
static template = xml`
|
|
||||||
A
|
|
||||||
<t t-if="state.flag">
|
|
||||||
<B value="state.valueB"/>
|
|
||||||
<C value="state.valueC"/>
|
|
||||||
</t>`;
|
|
||||||
static components = { B, C };
|
|
||||||
state = useState({ flag: false, valueB: 1, valueC: 2 });
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parent = await mount(A, fixture);
|
|
||||||
expect(fixture.innerHTML).toBe(" A ");
|
|
||||||
expect(["A:setup", "A:willStart", "A:willRender", "A:rendered", "A:mounted"]).toBeLogged();
|
|
||||||
|
|
||||||
// initiate a render in A, but is blocked in B
|
|
||||||
parent.state.flag = true;
|
|
||||||
|
|
||||||
await def;
|
|
||||||
await nextMicroTick();
|
|
||||||
expect([
|
|
||||||
"A:willRender",
|
|
||||||
"B:setup",
|
|
||||||
"B:willStart",
|
|
||||||
"C:setup",
|
|
||||||
"C:willStart",
|
|
||||||
"A:rendered",
|
|
||||||
"B:willRender",
|
|
||||||
"B:rendered",
|
|
||||||
"C:willRender",
|
|
||||||
"C:rendered",
|
|
||||||
]).toBeLogged();
|
|
||||||
|
|
||||||
// initiate render in A => will cancel renders in B/C and restarts
|
|
||||||
parent.state.valueB = 2;
|
|
||||||
await nextTick();
|
|
||||||
expect([
|
|
||||||
"B:willDestroy",
|
|
||||||
"C:willDestroy",
|
|
||||||
"A:willRender",
|
|
||||||
"B:setup",
|
|
||||||
"B:willStart",
|
|
||||||
"C:setup",
|
|
||||||
"C:willStart",
|
|
||||||
"A:rendered",
|
|
||||||
"B:willRender",
|
|
||||||
"B:rendered",
|
|
||||||
"C:willRender",
|
|
||||||
"C:rendered",
|
|
||||||
"A:willPatch",
|
|
||||||
"C:mounted",
|
|
||||||
"B:mounted",
|
|
||||||
"A:patched",
|
|
||||||
]).toBeLogged();
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe(" A 22");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("delayed rendering, destruction, stuff happens", async () => {
|
|
||||||
const promC = makeDeferred();
|
|
||||||
let stateB: any = null;
|
|
||||||
|
|
||||||
class D extends Component {
|
|
||||||
static template = xml`D<button t-on-click="increment"><t t-esc="state.val"/></button>`;
|
|
||||||
state = useState({ val: 1 });
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
}
|
|
||||||
increment() {
|
|
||||||
this.state.val++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class C extends Component {
|
|
||||||
static template = xml`C<D/><p><t t-esc="props.value"/></p>`;
|
|
||||||
static components = { D };
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
onWillUpdateProps(() => promC);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class B extends Component {
|
|
||||||
static template = xml`B<t t-if="state.hasChild"><C value="state.someValue + props.value"/></t>`;
|
|
||||||
static components = { C };
|
|
||||||
state = useState({ someValue: 3, hasChild: true });
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
stateB = this.state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class A extends Component {
|
|
||||||
static template = xml`A<B value="state.value"/>`;
|
|
||||||
static components = { B };
|
|
||||||
state = useState({ value: 33 });
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parent = await mount(A, fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("ABCD<button>1</button><p>36</p>");
|
|
||||||
expect([
|
|
||||||
"A:setup",
|
|
||||||
"A:willStart",
|
|
||||||
"A:willRender",
|
|
||||||
"B:setup",
|
|
||||||
"B:willStart",
|
|
||||||
"A:rendered",
|
|
||||||
"B:willRender",
|
|
||||||
"C:setup",
|
|
||||||
"C:willStart",
|
|
||||||
"B:rendered",
|
|
||||||
"C:willRender",
|
|
||||||
"D:setup",
|
|
||||||
"D:willStart",
|
|
||||||
"C:rendered",
|
|
||||||
"D:willRender",
|
|
||||||
"D:rendered",
|
|
||||||
"D:mounted",
|
|
||||||
"C:mounted",
|
|
||||||
"B:mounted",
|
|
||||||
"A:mounted",
|
|
||||||
]).toBeLogged();
|
|
||||||
|
|
||||||
// render in A, it updates B and C, but render is blocked in C
|
|
||||||
parent.state.value = 50;
|
|
||||||
await nextTick();
|
|
||||||
expect([
|
|
||||||
"A:willRender",
|
|
||||||
"B:willUpdateProps",
|
|
||||||
"A:rendered",
|
|
||||||
"B:willRender",
|
|
||||||
"C:willUpdateProps",
|
|
||||||
"B:rendered",
|
|
||||||
]).toBeLogged();
|
|
||||||
|
|
||||||
// update B => removes child C
|
|
||||||
stateB.hasChild = false;
|
|
||||||
// update D => render should be delayed, because AB is currently rendering
|
|
||||||
fixture.querySelector("button")!.click();
|
|
||||||
await nextTick();
|
|
||||||
expect([
|
|
||||||
"B:willRender",
|
|
||||||
"B:rendered",
|
|
||||||
"A:willPatch",
|
|
||||||
"B:willPatch",
|
|
||||||
"C:willUnmount",
|
|
||||||
"D:willUnmount",
|
|
||||||
"D:willDestroy",
|
|
||||||
"C:willDestroy",
|
|
||||||
"B:patched",
|
|
||||||
"A:patched",
|
|
||||||
]).toBeLogged();
|
|
||||||
expect(fixture.innerHTML).toBe("AB");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("renderings, destruction, patch, stuff, ... yet another variation", async () => {
|
|
||||||
const promB = makeDeferred();
|
|
||||||
|
|
||||||
class D extends Component {
|
|
||||||
static template = xml`D<p t-on-click="increment"><t t-esc="state.val"/></p>`;
|
|
||||||
state = useState({ val: 1 });
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
}
|
|
||||||
increment() {
|
|
||||||
this.state.val++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// almost the same as D
|
|
||||||
class C extends Component {
|
|
||||||
static template = xml`C<span t-on-click="increment"><t t-esc="state.val"/></span>`;
|
|
||||||
state = useState({ val: 1 });
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
}
|
|
||||||
increment() {
|
|
||||||
this.state.val++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class B extends Component {
|
|
||||||
static template = xml`B<t t-if="props.value === 33"><C/></t>`;
|
|
||||||
static components = { C };
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
onWillUpdateProps(() => promB);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class A extends Component {
|
|
||||||
static template = xml`A<B value="state.value"/><D/>`;
|
|
||||||
static components = { B, D };
|
|
||||||
state = useState({ value: 33 });
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parent = await mount(A, fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("ABC<span>1</span>D<p>1</p>");
|
|
||||||
expect([
|
|
||||||
"A:setup",
|
|
||||||
"A:willStart",
|
|
||||||
"A:willRender",
|
|
||||||
"B:setup",
|
|
||||||
"B:willStart",
|
|
||||||
"D:setup",
|
|
||||||
"D:willStart",
|
|
||||||
"A:rendered",
|
|
||||||
"B:willRender",
|
|
||||||
"C:setup",
|
|
||||||
"C:willStart",
|
|
||||||
"B:rendered",
|
|
||||||
"D:willRender",
|
|
||||||
"D:rendered",
|
|
||||||
"C:willRender",
|
|
||||||
"C:rendered",
|
|
||||||
"C:mounted",
|
|
||||||
"D:mounted",
|
|
||||||
"B:mounted",
|
|
||||||
"A:mounted",
|
|
||||||
]).toBeLogged();
|
|
||||||
|
|
||||||
// render in A, it updates B, will remove C, stopped in B
|
|
||||||
parent.state.value = 50;
|
|
||||||
await nextTick();
|
|
||||||
expect(["A:willRender", "B:willUpdateProps", "A:rendered"]).toBeLogged();
|
|
||||||
|
|
||||||
// update C => render should be delayed, because AB is currently rendering
|
|
||||||
fixture.querySelector("span")!.click();
|
|
||||||
await nextTick();
|
|
||||||
expect([]).toBeLogged();
|
|
||||||
|
|
||||||
// resolve prom B => render is done, component C is destroyed
|
|
||||||
promB.resolve();
|
|
||||||
await nextTick();
|
|
||||||
expect([
|
|
||||||
"B:willRender",
|
|
||||||
"B:rendered",
|
|
||||||
"A:willPatch",
|
|
||||||
"B:willPatch",
|
|
||||||
"C:willUnmount",
|
|
||||||
"C:willDestroy",
|
|
||||||
"B:patched",
|
|
||||||
"A:patched",
|
|
||||||
]).toBeLogged();
|
|
||||||
expect(fixture.innerHTML).toBe("ABD<p>1</p>");
|
|
||||||
|
|
||||||
// update D => should just render completely independently
|
|
||||||
fixture.querySelector("p")!.click();
|
|
||||||
await nextTick();
|
|
||||||
expect(["D:willRender", "D:rendered", "D:willPatch", "D:patched"]).toBeLogged();
|
|
||||||
expect(fixture.innerHTML).toBe("ABD<p>2</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
// test.skip("components with shouldUpdate=false", async () => {
|
// test.skip("components with shouldUpdate=false", async () => {
|
||||||
// const state = { p: 1, cc: 10 };
|
// const state = { p: 1, cc: 10 };
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, mount, onWillDestroy } from "../../src";
|
import { Component, mount } from "../../src";
|
||||||
import {
|
import {
|
||||||
onError,
|
onError,
|
||||||
onMounted,
|
onMounted,
|
||||||
@@ -1197,126 +1197,4 @@ describe("can catch errors", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div>Child 2</div>");
|
expect(fixture.innerHTML).toBe("<div>Child 2</div>");
|
||||||
expect(steps).toEqual(["Error Component"]);
|
expect(steps).toEqual(["Error Component"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("an error in onWillDestroy", async () => {
|
|
||||||
class Child extends Component {
|
|
||||||
static template = xml`<div>abc</div>`;
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
onWillDestroy(() => {
|
|
||||||
throw new Error("boom");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Parent extends Component {
|
|
||||||
static template = xml`
|
|
||||||
<t t-esc="state.value"/>
|
|
||||||
<t t-if="state.hasChild"><Child/></t>`;
|
|
||||||
static components = { Child };
|
|
||||||
|
|
||||||
state = useState({ value: 1, hasChild: true });
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
onError(() => {
|
|
||||||
this.state.value++;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parent = await mount(Parent, fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("1<div>abc</div>");
|
|
||||||
expect([
|
|
||||||
"Parent:setup",
|
|
||||||
"Parent:willStart",
|
|
||||||
"Parent:willRender",
|
|
||||||
"Child:setup",
|
|
||||||
"Child:willStart",
|
|
||||||
"Parent:rendered",
|
|
||||||
"Child:willRender",
|
|
||||||
"Child:rendered",
|
|
||||||
"Child:mounted",
|
|
||||||
"Parent:mounted",
|
|
||||||
]).toBeLogged();
|
|
||||||
parent.state.hasChild = false;
|
|
||||||
await nextTick();
|
|
||||||
await nextTick();
|
|
||||||
await nextTick();
|
|
||||||
await nextTick();
|
|
||||||
expect([
|
|
||||||
"Parent:willRender",
|
|
||||||
"Parent:rendered",
|
|
||||||
"Parent:willPatch",
|
|
||||||
"Child:willUnmount",
|
|
||||||
"Child:willDestroy",
|
|
||||||
"Parent:willRender",
|
|
||||||
"Parent:rendered",
|
|
||||||
"Parent:willPatch",
|
|
||||||
"Parent:patched",
|
|
||||||
]).toBeLogged();
|
|
||||||
expect(fixture.innerHTML).toBe("2");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("an error in onWillDestroy, variation", async () => {
|
|
||||||
class Child extends Component {
|
|
||||||
static template = xml`<div>abc</div>`;
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
onWillDestroy(() => {
|
|
||||||
throw new Error("boom");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Parent extends Component {
|
|
||||||
static template = xml`
|
|
||||||
<t t-esc="state.value"/>
|
|
||||||
<t t-if="state.hasChild"><Child/></t>`;
|
|
||||||
static components = { Child };
|
|
||||||
|
|
||||||
state = useState({ value: 1, hasChild: false });
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
onError(() => {
|
|
||||||
this.state.value++;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parent = await mount(Parent, fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("1");
|
|
||||||
|
|
||||||
expect([
|
|
||||||
"Parent:setup",
|
|
||||||
"Parent:willStart",
|
|
||||||
"Parent:willRender",
|
|
||||||
"Parent:rendered",
|
|
||||||
"Parent:mounted",
|
|
||||||
]).toBeLogged();
|
|
||||||
|
|
||||||
parent.state.hasChild = true;
|
|
||||||
await nextMicroTick();
|
|
||||||
await nextMicroTick();
|
|
||||||
await nextMicroTick();
|
|
||||||
await nextMicroTick();
|
|
||||||
await nextMicroTick();
|
|
||||||
expect([
|
|
||||||
"Parent:willRender",
|
|
||||||
"Child:setup",
|
|
||||||
"Child:willStart",
|
|
||||||
"Parent:rendered",
|
|
||||||
"Child:willRender",
|
|
||||||
"Child:rendered",
|
|
||||||
]).toBeLogged();
|
|
||||||
parent.state.hasChild = false;
|
|
||||||
await nextTick();
|
|
||||||
expect([
|
|
||||||
"Child:willDestroy",
|
|
||||||
"Parent:willRender",
|
|
||||||
"Parent:rendered",
|
|
||||||
"Parent:willPatch",
|
|
||||||
"Parent:patched",
|
|
||||||
]).toBeLogged();
|
|
||||||
expect(fixture.innerHTML).toBe("2");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user