Compare commits

...

2 Commits

Author SHA1 Message Date
Samuel Degueldre 94e978815f sync rendering 2023-09-29 14:07:44 +02:00
Samuel Degueldre 9c40ee67e1 lifecycle-snapshots 2023-09-29 13:33:56 +02:00
25 changed files with 3508 additions and 2605 deletions
+27 -10
View File
@@ -6,7 +6,7 @@ import { OwlError } from "../common/owl_error";
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers"; import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
import { clearReactivesForCallback, getSubscriptions, reactive, targets } from "./reactivity"; import { clearReactivesForCallback, getSubscriptions, reactive, targets } from "./reactivity";
import { STATUS } from "./status"; import { STATUS } from "./status";
import { batched, Callback } from "./utils"; import { batched, Callback, possiblySync } from "./utils";
let currentNode: ComponentNode | null = null; let currentNode: ComponentNode | null = null;
@@ -128,21 +128,29 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
this.initiateRender(fiber); this.initiateRender(fiber);
} }
async initiateRender(fiber: Fiber | MountFiber) { initiateRender(fiber: Fiber | MountFiber) {
this.fiber = fiber; this.fiber = fiber;
if (this.mounted.length) { if (this.mounted.length) {
fiber.root!.mounted.push(fiber); fiber.root!.mounted.push(fiber);
} }
const component = this.component; const component = this.component;
try { return possiblySync(
await Promise.all(this.willStart.map((f) => f.call(component))); () => {
} catch (e) { const willStartResults = this.willStart.map((f) => f.call(component));
this.app.handleError({ node: this, error: e }); if (willStartResults.some((r) => typeof r?.then === "function")) {
return; return Promise.all(willStartResults);
} }
return;
},
() => {
if (this.status === STATUS.NEW && this.fiber === fiber) { if (this.status === STATUS.NEW && this.fiber === fiber) {
fiber.render(); fiber.render();
} }
},
(e: any) => {
this.app.handleError({ node: this, error: e });
}
);
} }
async render(deep: boolean) { async render(deep: boolean) {
@@ -238,7 +246,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
this.status = STATUS.DESTROYED; this.status = STATUS.DESTROYED;
} }
async updateAndRender(props: P, parentFiber: Fiber) { updateAndRender(props: P, parentFiber: Fiber) {
this.nextProps = props; this.nextProps = props;
props = Object.assign({}, props); props = Object.assign({}, props);
// update // update
@@ -258,8 +266,15 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
} }
} }
currentNode = null; currentNode = null;
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props))); return possiblySync(
await prom; () => {
const willUpdateProps = this.willUpdateProps.map((f) => f.call(component, props));
if (willUpdateProps.some((p) => typeof p?.then === "function")) {
return Promise.all(willUpdateProps);
}
return;
},
() => {
if (fiber !== this.fiber) { if (fiber !== this.fiber) {
return; return;
} }
@@ -273,6 +288,8 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
parentRoot.patched.push(fiber); parentRoot.patched.push(fiber);
} }
} }
);
}
/** /**
* Finds a child that has dom that is not yet updated, and update it. This * Finds a child that has dom that is not yet updated, and update it. This
+21
View File
@@ -88,3 +88,24 @@ export class Markup extends String {}
export function markup(value: any) { export function markup(value: any) {
return new Markup(value); return new Markup(value);
} }
export function possiblySync(computation: Function, onSuccess: Function, onError?: Function) {
try {
let result;
if (onError) {
try {
result = computation();
} catch (e) {
return onError(e);
}
} else {
result = computation();
}
if (typeof result?.then === "function") {
return result.then(onSuccess, onError);
}
return onSuccess(result);
} catch (e) {
return Promise.reject(e);
}
}
+1 -1
View File
@@ -32,7 +32,7 @@ exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately
}" }"
`; `;
exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately on destroy 2`] = ` exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately on destroy 3`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
+31 -6
View File
@@ -8,6 +8,7 @@ import {
useLogLifecycle, useLogLifecycle,
makeDeferred, makeDeferred,
nextMicroTick, nextMicroTick,
steps,
} from "../helpers"; } from "../helpers";
let fixture: HTMLElement; let fixture: HTMLElement;
@@ -123,23 +124,47 @@ describe("app", () => {
const app = new App(A); const app = new App(A);
const comp = await app.mount(fixture); const comp = await app.mount(fixture);
expect(["A:setup", "A:willStart", "A:willRender", "A:rendered", "A:mounted"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:setup",
"A:willStart",
"A:willRender",
"A:rendered",
"A:mounted",
]
`);
comp.state.value = true; comp.state.value = true;
await nextTick(); await nextTick();
expect(["A:willRender", "B:setup", "B:willStart", "A:rendered"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:willRender",
"B:setup",
"B:willStart",
"A:rendered",
]
`);
// rerender to force the instantiation of a new B component (and cancelling the first) // rerender to force the instantiation of a new B component (and cancelling the first)
comp.render(); comp.render();
await nextMicroTick(); await nextMicroTick();
expect(["A:willRender", "B:setup", "B:willStart", "A:rendered"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:willRender",
"B:setup",
"B:willStart",
"A:rendered",
]
`);
app.destroy(); app.destroy();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:willUnmount", "A:willUnmount",
"B:willDestroy", "B:willDestroy",
"A:willDestroy", "A:willDestroy",
"B:willDestroy", // make sure the 2 B instances have been destroyed synchronously "B:willDestroy",
]).toBeLogged(); ]
`);
}); });
}); });
@@ -184,7 +184,7 @@ exports[`changing state before first render does not trigger a render (with pare
}" }"
`; `;
exports[`changing state before first render does not trigger a render (with parent) 2`] = ` exports[`changing state before first render does not trigger a render (with parent) 3`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -254,7 +254,7 @@ exports[`components are not destroyed between animation frame 1`] = `
}" }"
`; `;
exports[`components are not destroyed between animation frame 2`] = ` exports[`components are not destroyed between animation frame 3`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -268,7 +268,7 @@ exports[`components are not destroyed between animation frame 2`] = `
}" }"
`; `;
exports[`components are not destroyed between animation frame 3`] = ` exports[`components are not destroyed between animation frame 5`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -748,7 +748,7 @@ exports[`concurrent renderings scenario 10 2`] = `
}" }"
`; `;
exports[`concurrent renderings scenario 10 3`] = ` exports[`concurrent renderings scenario 10 4`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -993,7 +993,7 @@ exports[`concurrent renderings scenario 16 3`] = `
}" }"
`; `;
exports[`concurrent renderings scenario 16 4`] = ` exports[`concurrent renderings scenario 16 6`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -1024,7 +1024,7 @@ exports[`creating two async components, scenario 1 1`] = `
}" }"
`; `;
exports[`creating two async components, scenario 1 2`] = ` exports[`creating two async components, scenario 1 3`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -1038,7 +1038,7 @@ exports[`creating two async components, scenario 1 2`] = `
}" }"
`; `;
exports[`creating two async components, scenario 1 3`] = ` exports[`creating two async components, scenario 1 5`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -1085,7 +1085,7 @@ exports[`creating two async components, scenario 2 2`] = `
}" }"
`; `;
exports[`creating two async components, scenario 2 3`] = ` exports[`creating two async components, scenario 2 5`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -1133,7 +1133,7 @@ exports[`creating two async components, scenario 3 (patching in the same frame)
}" }"
`; `;
exports[`creating two async components, scenario 3 (patching in the same frame) 3`] = ` exports[`creating two async components, scenario 3 (patching in the same frame) 5`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -1308,7 +1308,7 @@ exports[`delayed render does not go through when t-component value changed 2`] =
}" }"
`; `;
exports[`delayed render does not go through when t-component value changed 3`] = ` exports[`delayed render does not go through when t-component value changed 4`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -1617,7 +1617,7 @@ exports[`destroyed component causes other soon to be destroyed component to rere
}" }"
`; `;
exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 2`] = ` exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 3`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -1628,7 +1628,7 @@ exports[`destroyed component causes other soon to be destroyed component to rere
}" }"
`; `;
exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 3`] = ` exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 4`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -1656,7 +1656,7 @@ exports[`destroying/recreating a subcomponent, other scenario 1`] = `
}" }"
`; `;
exports[`destroying/recreating a subcomponent, other scenario 2`] = ` exports[`destroying/recreating a subcomponent, other scenario 3`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -1685,7 +1685,7 @@ exports[`destroying/recreating a subwidget with different props (if start is not
}" }"
`; `;
exports[`destroying/recreating a subwidget with different props (if start is not over) 2`] = ` exports[`destroying/recreating a subwidget with different props (if start is not over) 3`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -1787,7 +1787,7 @@ exports[`rendering component again in next microtick 1`] = `
}" }"
`; `;
exports[`rendering component again in next microtick 2`] = ` exports[`rendering component again in next microtick 3`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -1861,10 +1861,10 @@ exports[`renderings, destruction, patch, stuff, ... yet another variation 3`] =
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block3 = createBlock(\`<p block-handler-0=\\"click\\"><block-text-1/></p>\`); let block3 = createBlock(\`<span block-handler-0=\\"click\\"><block-text-1/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b2 = text(\`D\`); const b2 = text(\`C\`);
let hdlr1 = [ctx['increment'], ctx]; let hdlr1 = [ctx['increment'], ctx];
let txt1 = ctx['state'].val; let txt1 = ctx['state'].val;
const b3 = block3([hdlr1, txt1]); const b3 = block3([hdlr1, txt1]);
@@ -1878,10 +1878,10 @@ exports[`renderings, destruction, patch, stuff, ... yet another variation 4`] =
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block3 = createBlock(\`<span block-handler-0=\\"click\\"><block-text-1/></span>\`); let block3 = createBlock(\`<p block-handler-0=\\"click\\"><block-text-1/></p>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b2 = text(\`C\`); const b2 = text(\`D\`);
let hdlr1 = [ctx['increment'], ctx]; let hdlr1 = [ctx['increment'], ctx];
let txt1 = ctx['state'].val; let txt1 = ctx['state'].val;
const b3 = block3([hdlr1, txt1]); const b3 = block3([hdlr1, txt1]);
@@ -241,7 +241,7 @@ exports[`can catch errors an error in onWillDestroy, variation 1`] = `
}" }"
`; `;
exports[`can catch errors an error in onWillDestroy, variation 2`] = ` exports[`can catch errors an error in onWillDestroy, variation 3`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -92,7 +92,7 @@ exports[`lifecycle hooks component semantics 5`] = `
}" }"
`; `;
exports[`lifecycle hooks component semantics 6`] = ` exports[`lifecycle hooks component semantics 7`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -185,7 +185,7 @@ exports[`lifecycle hooks destroy new children before being mountged 1`] = `
}" }"
`; `;
exports[`lifecycle hooks destroy new children before being mountged 2`] = ` exports[`lifecycle hooks destroy new children before being mountged 3`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -291,7 +291,7 @@ exports[`lifecycle hooks lifecycle semantics, part 2 1`] = `
}" }"
`; `;
exports[`lifecycle hooks lifecycle semantics, part 2 2`] = ` exports[`lifecycle hooks lifecycle semantics, part 2 3`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -303,7 +303,7 @@ exports[`lifecycle hooks lifecycle semantics, part 2 2`] = `
}" }"
`; `;
exports[`lifecycle hooks lifecycle semantics, part 2 3`] = ` exports[`lifecycle hooks lifecycle semantics, part 2 4`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -348,7 +348,7 @@ exports[`lifecycle hooks lifecycle semantics, part 4 1`] = `
}" }"
`; `;
exports[`lifecycle hooks lifecycle semantics, part 4 2`] = ` exports[`lifecycle hooks lifecycle semantics, part 4 3`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -360,7 +360,7 @@ exports[`lifecycle hooks lifecycle semantics, part 4 2`] = `
}" }"
`; `;
exports[`lifecycle hooks lifecycle semantics, part 4 3`] = ` exports[`lifecycle hooks lifecycle semantics, part 4 4`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
@@ -180,7 +180,7 @@ exports[`t-component switching dynamic component 2`] = `
}" }"
`; `;
exports[`t-component switching dynamic component 3`] = ` exports[`t-component switching dynamic component 4`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
+29 -10
View File
@@ -5,6 +5,7 @@ import {
nextAppError, nextAppError,
nextTick, nextTick,
snapshotEverything, snapshotEverything,
steps,
useLogLifecycle, useLogLifecycle,
} from "../helpers"; } from "../helpers";
import { markup } from "../../src/runtime/utils"; import { markup } from "../../src/runtime/utils";
@@ -868,19 +869,26 @@ describe("basics", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(Object.keys(parent.__owl__.children).length).toStrictEqual(1); expect(Object.keys(parent.__owl__.children).length).toStrictEqual(1);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Child:mounted", "Child:mounted",
]).toBeLogged(); ]
`);
parent.ifVar = false; parent.ifVar = false;
parent.render(); parent.render();
await nextTick(); await nextTick();
expect(Object.keys(parent.__owl__.children).length).toStrictEqual(0); expect(Object.keys(parent.__owl__.children).length).toStrictEqual(0);
expect(["Child:willUnmount", "Child:willDestroy"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Child:willUnmount",
"Child:willDestroy",
]
`);
}); });
test("component children doesn't leak (t-key case)", async () => { test("component children doesn't leak (t-key case)", async () => {
@@ -899,19 +907,22 @@ describe("basics", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(Object.keys(parent.__owl__.children).length).toStrictEqual(1); expect(Object.keys(parent.__owl__.children).length).toStrictEqual(1);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Child:mounted", "Child:mounted",
]).toBeLogged(); ]
`);
parent.keyVar = 2; parent.keyVar = 2;
parent.render(); parent.render();
await nextTick(); await nextTick();
expect(Object.keys(parent.__owl__.children).length).toStrictEqual(1); expect(Object.keys(parent.__owl__.children).length).toStrictEqual(1);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Child:willRender", "Child:willRender",
@@ -919,7 +930,8 @@ describe("basics", () => {
"Child:willUnmount", "Child:willUnmount",
"Child:willDestroy", "Child:willDestroy",
"Child:mounted", "Child:mounted",
]).toBeLogged(); ]
`);
}); });
test("GrandChild display is controlled by its GrandParent", async () => { test("GrandChild display is controlled by its GrandParent", async () => {
@@ -943,20 +955,27 @@ describe("basics", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div></div>"); expect(fixture.innerHTML).toBe("<div></div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"GrandChild:setup", "GrandChild:setup",
"GrandChild:willStart", "GrandChild:willStart",
"GrandChild:willRender", "GrandChild:willRender",
"GrandChild:rendered", "GrandChild:rendered",
"GrandChild:mounted", "GrandChild:mounted",
]).toBeLogged(); ]
`);
parent.displayGrandChild = false; parent.displayGrandChild = false;
parent.render(); parent.render();
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe(""); expect(fixture.innerHTML).toBe("");
expect(["GrandChild:willUnmount", "GrandChild:willDestroy"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"GrandChild:willUnmount",
"GrandChild:willDestroy",
]
`);
}); });
}); });
File diff suppressed because it is too large Load Diff
+95 -52
View File
@@ -19,6 +19,7 @@ import {
snapshotEverything, snapshotEverything,
useLogLifecycle, useLogLifecycle,
nextAppError, nextAppError,
steps,
} from "../helpers"; } from "../helpers";
import { OwlError } from "../../src/common/owl_error"; import { OwlError } from "../../src/common/owl_error";
@@ -75,8 +76,9 @@ describe("basics", () => {
} }
const app = new App(Parent); const app = new App(Parent);
let error: Error; let error: Error;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow( await expect(appError).resolves.toThrow(
'Cannot find the definition of component "SomeMispelledComponent"' 'Cannot find the definition of component "SomeMispelledComponent"'
); );
await mountProm; await mountProm;
@@ -95,8 +97,9 @@ describe("basics", () => {
} }
const app = new App(Parent, { test: true }); const app = new App(Parent, { test: true });
let error: Error; let error: Error;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow( await expect(appError).resolves.toThrow(
'Cannot find the definition of component "SomeMispelledComponent"' 'Cannot find the definition of component "SomeMispelledComponent"'
); );
await mountProm; await mountProm;
@@ -115,8 +118,9 @@ describe("basics", () => {
} }
const app = new App(Parent as typeof Component); const app = new App(Parent as typeof Component);
let error: Error; let error: Error;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow( await expect(appError).resolves.toThrow(
'"SomeComponent" is not a Component. It must inherit from the Component class' '"SomeComponent" is not a Component. It must inherit from the Component class'
); );
await mountProm; await mountProm;
@@ -132,8 +136,9 @@ describe("basics", () => {
} }
const app = new App(Parent as typeof Component); const app = new App(Parent as typeof Component);
let error: Error; let error: Error;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow( await expect(appError).resolves.toThrow(
'Cannot find the definition of component "MissingChild", missing static components key in parent' 'Cannot find the definition of component "MissingChild", missing static components key in parent'
); );
await mountProm; await mountProm;
@@ -196,8 +201,9 @@ function(app, bdom, helpers) {
}`; }`;
const app = new App(Parent as typeof Component); const app = new App(Parent as typeof Component);
let error: Error; let error: Error;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(expectedErrorMessage); await expect(appError).resolves.toThrow(expectedErrorMessage);
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe(expectedErrorMessage); expect(error!.message).toBe(expectedErrorMessage);
@@ -243,8 +249,9 @@ describe("errors and promises", () => {
const app = new App(Root); const app = new App(Root);
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await expect(appError).resolves.toThrow("error occured in the owl lifecycle");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.cause).toBeDefined(); expect(error!.cause).toBeDefined();
@@ -267,8 +274,9 @@ describe("errors and promises", () => {
const app = new App(Root); const app = new App(Root);
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await expect(appError).resolves.toThrow("error occured in the owl lifecycle");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.cause).toBeDefined(); expect(error!.cause).toBeDefined();
@@ -290,8 +298,9 @@ describe("errors and promises", () => {
const app = new App(Root, { test: true }); const app = new App(Root, { test: true });
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted"); await expect(appError).resolves.toThrow("error occurred in onMounted");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.stack).toContain("Root.setup"); expect(error!.stack).toContain("Root.setup");
@@ -316,8 +325,9 @@ describe("errors and promises", () => {
const app = new App(Root, { test: true }); const app = new App(Root, { test: true });
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillRender"); await expect(appError).resolves.toThrow("error occurred in onWillRender");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
@@ -357,8 +367,9 @@ describe("errors and promises", () => {
const app = new App(Root, { test: true }); const app = new App(Root, { test: true });
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart"); await expect(appError).resolves.toThrow("error occurred in onWillStart");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
@@ -425,8 +436,9 @@ describe("errors and promises", () => {
const app = new App(Parent); const app = new App(Parent);
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await expect(appError).resolves.toThrow("error occured in the owl lifecycle");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.cause).toBeDefined(); expect(error!.cause).toBeDefined();
@@ -471,8 +483,9 @@ describe("errors and promises", () => {
const app = new App(Parent); const app = new App(Parent);
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await expect(appError).resolves.toThrow("error occured in the owl lifecycle");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.cause).toBeDefined(); expect(error!.cause).toBeDefined();
@@ -501,8 +514,9 @@ describe("errors and promises", () => {
const app = new App(Example, { test: true }); const app = new App(Example, { test: true });
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted"); await expect(appError).resolves.toThrow("error occurred in onMounted");
await mountProm; await mountProm;
expect(error!.message).toBe(`The following error occurred in onMounted: "Error in mounted"`); expect(error!.message).toBe(`The following error occurred in onMounted: "Error in mounted"`);
// 1 additional error is logged because the destruction of the app causes // 1 additional error is logged because the destruction of the app causes
@@ -576,9 +590,9 @@ describe("can catch errors", () => {
} }
const app = new App(Root, { test: true }); const app = new App(Root, { test: true });
let error: OwlError; let error: OwlError;
const crashProm = expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart"); const appError = nextAppError(app);
await app.mount(fixture).catch((e: Error) => (error = e)); await app.mount(fixture).catch((e: Error) => (error = e));
await crashProm; await expect(appError).resolves.toThrow("error occurred in onWillStart");
expect(error!.message).toBe( expect(error!.message).toBe(
`The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"` `The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"`
); );
@@ -598,8 +612,9 @@ describe("can catch errors", () => {
} }
const app = new App(Root, { test: true }); const app = new App(Root, { test: true });
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted"); await expect(appError).resolves.toThrow("error occurred in onMounted");
await mountProm; await mountProm;
expect(error!.message).toBe(`The following error occurred in onMounted: "test error"`); expect(error!.message).toBe(`The following error occurred in onMounted: "test error"`);
expect(error!.cause).toBe(err); expect(error!.cause).toBe(err);
@@ -620,8 +635,9 @@ describe("can catch errors", () => {
} }
const app = new App(Root, { test: true }); const app = new App(Root, { test: true });
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart"); await expect(appError).resolves.toThrow("error occurred in onWillStart");
await mountProm; await mountProm;
expect(error!.message).toBe(`The following error occurred in onWillStart: "test error"`); expect(error!.message).toBe(`The following error occurred in onWillStart: "test error"`);
expect(error!.cause).toBe(err); expect(error!.cause).toBe(err);
@@ -641,8 +657,9 @@ describe("can catch errors", () => {
} }
const app = new App(Root); const app = new App(Root);
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await expect(appError).resolves.toThrow("error occured in the owl lifecycle");
await mountProm; await mountProm;
expect(error!.message).toBe( expect(error!.message).toBe(
`An error occured in the owl lifecycle (see this Error's "cause" property)` `An error occured in the owl lifecycle (see this Error's "cause" property)`
@@ -665,8 +682,9 @@ describe("can catch errors", () => {
} }
const app = new App(Root); const app = new App(Root);
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await expect(appError).resolves.toThrow("error occured in the owl lifecycle");
await mountProm; await mountProm;
expect(error!.message).toBe( expect(error!.message).toBe(
`An error occured in the owl lifecycle (see this Error's "cause" property)` `An error occured in the owl lifecycle (see this Error's "cause" property)`
@@ -687,8 +705,9 @@ describe("can catch errors", () => {
} }
const app = new App(Root, { test: true }); const app = new App(Root, { test: true });
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("not an Error was thrown in onMounted"); await expect(appError).resolves.toThrow("not an Error was thrown in onMounted");
await mountProm; await mountProm;
expect(error!.message).toBe( expect(error!.message).toBe(
`Something that is not an Error was thrown in onMounted (see this Error's "cause" property)` `Something that is not an Error was thrown in onMounted (see this Error's "cause" property)`
@@ -709,8 +728,9 @@ describe("can catch errors", () => {
} }
const app = new App(Root); const app = new App(Root);
let error: OwlError; let error: OwlError;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await expect(appError).resolves.toThrow("error occured in the owl lifecycle");
await mountProm; await mountProm;
expect(error!.message).toBe( expect(error!.message).toBe(
`An error occured in the owl lifecycle (see this Error's "cause" property)` `An error occured in the owl lifecycle (see this Error's "cause" property)`
@@ -948,26 +968,28 @@ describe("can catch errors", () => {
} }
await mount(Root, fixture); await mount(Root, fixture);
expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>"); expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Root:setup", "Root:setup",
"Root:willStart", "Root:willStart",
"Root:willRender", "Root:willRender",
"ErrorBoundary:setup", "ErrorBoundary:setup",
"ErrorBoundary:willStart", "ErrorBoundary:willStart",
"Root:rendered",
"ErrorBoundary:willRender", "ErrorBoundary:willRender",
"ErrorComponent:setup", "ErrorComponent:setup",
"ErrorComponent:willStart", "ErrorComponent:willStart",
"ErrorBoundary:rendered",
"ErrorComponent:willRender", "ErrorComponent:willRender",
"ErrorComponent:rendered", "ErrorComponent:rendered",
"ErrorBoundary:rendered",
"Root:rendered",
"ErrorComponent:mounted", "ErrorComponent:mounted",
"boom", "boom",
"ErrorBoundary:willRender", "ErrorBoundary:willRender",
"ErrorBoundary:rendered", "ErrorBoundary:rendered",
"ErrorBoundary:mounted", "ErrorBoundary:mounted",
"Root:mounted", "Root:mounted",
]).toBeLogged(); ]
`);
expect(mockConsoleError).toBeCalledTimes(0); expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0); expect(mockConsoleWarn).toBeCalledTimes(0);
}); });
@@ -998,21 +1020,23 @@ describe("can catch errors", () => {
} }
await mount(Root, fixture); await mount(Root, fixture);
expect(fixture.innerHTML).toBe("<div>Error handled</div>"); expect(fixture.innerHTML).toBe("<div>Error handled</div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Root:setup", "Root:setup",
"Root:willStart", "Root:willStart",
"Root:willRender", "Root:willRender",
"ErrorComponent:setup", "ErrorComponent:setup",
"ErrorComponent:willStart", "ErrorComponent:willStart",
"Root:rendered",
"ErrorComponent:willRender", "ErrorComponent:willRender",
"ErrorComponent:rendered", "ErrorComponent:rendered",
"Root:rendered",
"ErrorComponent:mounted", "ErrorComponent:mounted",
"boom", "boom",
"Root:willRender", "Root:willRender",
"Root:rendered", "Root:rendered",
"Root:mounted", "Root:mounted",
]).toBeLogged(); ]
`);
expect(mockConsoleError).toBeCalledTimes(0); expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0); expect(mockConsoleWarn).toBeCalledTimes(0);
}); });
@@ -1059,23 +1083,24 @@ describe("can catch errors", () => {
} }
await mount(A, fixture); await mount(A, fixture);
expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>"); expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:setup", "A:setup",
"A:willStart", "A:willStart",
"A:willRender", "A:willRender",
"B:setup", "B:setup",
"B:willStart", "B:willStart",
"A:rendered",
"B:willRender", "B:willRender",
"C:setup", "C:setup",
"C:willStart", "C:willStart",
"B:rendered",
"C:willRender", "C:willRender",
"Boom:setup", "Boom:setup",
"Boom:willStart", "Boom:willStart",
"C:rendered",
"Boom:willRender", "Boom:willRender",
"Boom:rendered", "Boom:rendered",
"C:rendered",
"B:rendered",
"A:rendered",
"Boom:mounted", "Boom:mounted",
"boom", "boom",
"C:willRender", "C:willRender",
@@ -1083,7 +1108,8 @@ describe("can catch errors", () => {
"C:mounted", "C:mounted",
"B:mounted", "B:mounted",
"A:mounted", "A:mounted",
]).toBeLogged(); ]
`);
expect(mockConsoleError).toBeCalledTimes(0); expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0); expect(mockConsoleWarn).toBeCalledTimes(0);
}); });
@@ -1130,23 +1156,24 @@ describe("can catch errors", () => {
} }
await mount(Root, fixture); await mount(Root, fixture);
expect(fixture.innerHTML).toBe("<div>OK<div>Error handled</div></div>"); expect(fixture.innerHTML).toBe("<div>OK<div>Error handled</div></div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Root:setup", "Root:setup",
"Root:willStart", "Root:willStart",
"Root:willRender", "Root:willRender",
"OK:setup", "OK:setup",
"OK:willStart", "OK:willStart",
"ErrorBoundary:setup",
"ErrorBoundary:willStart",
"Root:rendered",
"OK:willRender", "OK:willRender",
"OK:rendered", "OK:rendered",
"ErrorBoundary:setup",
"ErrorBoundary:willStart",
"ErrorBoundary:willRender", "ErrorBoundary:willRender",
"ErrorComponent:setup", "ErrorComponent:setup",
"ErrorComponent:willStart", "ErrorComponent:willStart",
"ErrorBoundary:rendered",
"ErrorComponent:willRender", "ErrorComponent:willRender",
"ErrorComponent:rendered", "ErrorComponent:rendered",
"ErrorBoundary:rendered",
"Root:rendered",
"ErrorComponent:mounted", "ErrorComponent:mounted",
"boom", "boom",
"ErrorBoundary:willRender", "ErrorBoundary:willRender",
@@ -1154,7 +1181,8 @@ describe("can catch errors", () => {
"ErrorBoundary:mounted", "ErrorBoundary:mounted",
"OK:mounted", "OK:mounted",
"Root:mounted", "Root:mounted",
]).toBeLogged(); ]
`);
expect(mockConsoleError).toBeCalledTimes(0); expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0); expect(mockConsoleWarn).toBeCalledTimes(0);
}); });
@@ -1481,24 +1509,27 @@ describe("can catch errors", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("1<div>abc</div>"); expect(fixture.innerHTML).toBe("1<div>abc</div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.hasChild = false; parent.state.hasChild = false;
await nextTick(); await nextTick();
await nextTick(); await nextTick();
await nextTick(); await nextTick();
await nextTick(); await nextTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
@@ -1509,7 +1540,8 @@ describe("can catch errors", () => {
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
expect(fixture.innerHTML).toBe("2"); expect(fixture.innerHTML).toBe("2");
}); });
@@ -1542,13 +1574,15 @@ describe("can catch errors", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("1"); expect(fixture.innerHTML).toBe("1");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.hasChild = true; parent.state.hasChild = true;
await nextMicroTick(); await nextMicroTick();
@@ -1556,26 +1590,35 @@ describe("can catch errors", () => {
await nextMicroTick(); await nextMicroTick();
await nextMicroTick(); await nextMicroTick();
await nextMicroTick(); await nextMicroTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
]).toBeLogged(); "Parent:rendered",
]
`);
parent.state.hasChild = false; parent.state.hasChild = false;
await nextTick(); await nextTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Child:willDestroy", "Child:willDestroy",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
]).toBeLogged(); ]
`);
expect(fixture.innerHTML).toBe("1"); expect(fixture.innerHTML).toBe("1");
await nextTick(); await nextTick();
expect(["Parent:willPatch", "Parent:patched"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willPatch",
"Parent:patched",
]
`);
expect(fixture.innerHTML).toBe("2"); expect(fixture.innerHTML).toBe("2");
}); });
}); });
+2 -1
View File
@@ -660,8 +660,9 @@ describe("hooks", () => {
let error: OwlError; let error: OwlError;
const app = new App(MyComponent); const app = new App(MyComponent);
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await expect(appError).resolves.toThrow("error occured in the owl lifecycle");
await mountProm; await mountProm;
expect(error!.cause.message).toBe("Intentional error"); expect(error!.cause.message).toBe("Intentional error");
// no console.error because the error has been caught in this test // no console.error because the error has been caught in this test
+194 -98
View File
@@ -17,6 +17,7 @@ import {
nextMicroTick, nextMicroTick,
nextTick, nextTick,
snapshotEverything, snapshotEverything,
steps,
useLogLifecycle, useLogLifecycle,
} from "../helpers"; } from "../helpers";
@@ -341,15 +342,16 @@ describe("lifecycle hooks", () => {
parent.state.n = 2; parent.state.n = 2;
await nextTick(); await nextTick();
app.destroy(); app.destroy();
expect(steps).toEqual([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"parent:willPatch", "parent:willPatch",
"child:willPatch",
"childchild:willPatch", "childchild:willPatch",
"childchild:patched", "child:willPatch",
"child:patched", "child:patched",
"childchild:patched",
"parent:patched", "parent:patched",
]); ]
Object.freeze(steps); `);
}); });
test("willStart, mounted on subwidget rendered after main is mounted in some other position", async () => { test("willStart, mounted on subwidget rendered after main is mounted in some other position", async () => {
@@ -455,45 +457,51 @@ describe("lifecycle hooks", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div><span>0</span></div>"); expect(fixture.innerHTML).toBe("<div><span>0</span></div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.increment(); parent.increment();
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div><span>1</span></div>"); expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Child:willUpdateProps", "Child:willUpdateProps",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:willPatch", "Child:willPatch",
"Child:patched", "Child:patched",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
parent.toggleSubWidget(); parent.toggleSubWidget();
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe(""); expect(fixture.innerHTML).toBe("");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:willUnmount", "Child:willUnmount",
"Child:willDestroy", "Child:willDestroy",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
}); });
test("hooks are called in proper order in widget creation/destruction", async () => { test("hooks are called in proper order in widget creation/destruction", async () => {
@@ -514,26 +522,30 @@ describe("lifecycle hooks", () => {
const app = new App(Parent); const app = new App(Parent);
await app.mount(fixture); await app.mount(fixture);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
app.destroy(); app.destroy();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willUnmount", "Parent:willUnmount",
"Child:willUnmount", "Child:willUnmount",
"Child:willDestroy", "Child:willDestroy",
"Parent:willDestroy", "Parent:willDestroy",
]).toBeLogged(); ]
`);
}); });
test("willUpdateProps hook is called", async () => { test("willUpdateProps hook is called", async () => {
@@ -632,26 +644,30 @@ describe("lifecycle hooks", () => {
const app = new App(Parent); const app = new App(Parent);
await app.mount(fixture); await app.mount(fixture);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
app.destroy(); app.destroy();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willUnmount", "Parent:willUnmount",
"Child:willUnmount", "Child:willUnmount",
"Child:willDestroy", "Child:willDestroy",
"Parent:willDestroy", "Parent:willDestroy",
]).toBeLogged(); ]
`);
}); });
test("lifecycle semantics, part 2", async () => { test("lifecycle semantics, part 2", async () => {
@@ -680,42 +696,48 @@ describe("lifecycle hooks", () => {
const app = new App(Parent); const app = new App(Parent);
const parent = await app.mount(fixture); const parent = await app.mount(fixture);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.hasChild = true; parent.state.hasChild = true;
await nextTick(); await nextTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"GrandChild:setup", "GrandChild:setup",
"GrandChild:willStart", "GrandChild:willStart",
"Child:rendered",
"GrandChild:willRender", "GrandChild:willRender",
"GrandChild:rendered", "GrandChild:rendered",
"Child:rendered",
"Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"GrandChild:mounted", "GrandChild:mounted",
"Child:mounted", "Child:mounted",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
app.destroy(); app.destroy();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willUnmount", "Parent:willUnmount",
"Child:willUnmount", "Child:willUnmount",
"GrandChild:willUnmount", "GrandChild:willUnmount",
"GrandChild:willDestroy", "GrandChild:willDestroy",
"Child:willDestroy", "Child:willDestroy",
"Parent:willDestroy", "Parent:willDestroy",
]).toBeLogged(); ]
`);
}); });
test("lifecycle semantics, part 3", async () => { test("lifecycle semantics, part 3", async () => {
@@ -744,19 +766,26 @@ describe("lifecycle hooks", () => {
const app = new App(Parent); const app = new App(Parent);
const parent = await app.mount(fixture); const parent = await app.mount(fixture);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.hasChild = true; parent.state.hasChild = true;
// immediately destroy everything // immediately destroy everything
app.destroy(); app.destroy();
await nextTick(); await nextTick();
expect(["Parent:willUnmount", "Parent:willDestroy"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willUnmount",
"Parent:willDestroy",
]
`);
}); });
test("lifecycle semantics, part 4", async () => { test("lifecycle semantics, part 4", async () => {
@@ -789,34 +818,40 @@ describe("lifecycle hooks", () => {
const app = new App(Parent); const app = new App(Parent);
const parent = await app.mount(fixture); const parent = await app.mount(fixture);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.hasChild = true; parent.state.hasChild = true;
await nextTick(); await nextTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"GrandChild:setup", "GrandChild:setup",
"GrandChild:willStart", "GrandChild:willStart",
"Child:rendered", "Child:rendered",
]).toBeLogged(); "Parent:rendered",
]
`);
app.destroy(); app.destroy();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willUnmount", "Parent:willUnmount",
"GrandChild:willDestroy", "GrandChild:willDestroy",
"Child:willDestroy", "Child:willDestroy",
"Parent:willDestroy", "Parent:willDestroy",
]).toBeLogged(); ]
`);
}); });
test("lifecycle semantics, part 5", async () => { test("lifecycle semantics, part 5", async () => {
@@ -837,29 +872,33 @@ describe("lifecycle hooks", () => {
} }
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.hasChild = false; parent.state.hasChild = false;
await nextTick(); await nextTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:willUnmount", "Child:willUnmount",
"Child:willDestroy", "Child:willDestroy",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
}); });
test("lifecycle semantics, part 6", async () => { test("lifecycle semantics, part 6", async () => {
@@ -880,32 +919,36 @@ describe("lifecycle hooks", () => {
} }
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.value = 2; parent.state.value = 2;
await nextTick(); await nextTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Child:willUpdateProps", "Child:willUpdateProps",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:willPatch", "Child:willPatch",
"Child:patched", "Child:patched",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
}); });
test("onWillRender", async () => { test("onWillRender", async () => {
@@ -937,43 +980,53 @@ describe("lifecycle hooks", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<button>1</button>"); expect(fixture.innerHTML).toBe("<button>1</button>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.value++; // to block child render parent.state.value++; // to block child render
await nextTick(); await nextTick();
expect(["Parent:willRender", "Child:willUpdateProps", "Parent:rendered"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender",
"Child:willUpdateProps",
"Parent:rendered",
]
`);
fixture.querySelector("button")!.click(); fixture.querySelector("button")!.click();
await nextTick(); await nextTick();
expect([]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`Array []`);
fixture.querySelector("button")!.click(); fixture.querySelector("button")!.click();
await nextTick(); await nextTick();
expect([]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`Array []`);
expect(fixture.innerHTML).toBe("<button>1</button>"); expect(fixture.innerHTML).toBe("<button>1</button>");
def.resolve(); def.resolve();
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<button>3</button>"); expect(fixture.innerHTML).toBe("<button>3</button>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:willPatch", "Child:willPatch",
"Child:patched", "Child:patched",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
}); });
// TODO: rename (remove? seems covered by lifecycle semantics) // TODO: rename (remove? seems covered by lifecycle semantics)
@@ -1054,50 +1107,54 @@ describe("lifecycle hooks", () => {
await mount(A, fixture); await mount(A, fixture);
expect(fixture.innerHTML).toBe(`<div>A<div>B</div><div>C<div>D</div><div>E</div></div></div>`); expect(fixture.innerHTML).toBe(`<div>A<div>B</div><div>C<div>D</div><div>E</div></div></div>`);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:setup", "A:setup",
"A:willStart", "A:willStart",
"A:willRender", "A:willRender",
"B:setup", "B:setup",
"B:willStart", "B:willStart",
"C:setup",
"C:willStart",
"A:rendered",
"B:willRender", "B:willRender",
"B:rendered", "B:rendered",
"C:setup",
"C:willStart",
"C:willRender", "C:willRender",
"D:setup", "D:setup",
"D:willStart", "D:willStart",
"E:setup",
"E:willStart",
"C:rendered",
"D:willRender", "D:willRender",
"D:rendered", "D:rendered",
"E:setup",
"E:willStart",
"E:willRender", "E:willRender",
"E:rendered", "E:rendered",
"C:rendered",
"A:rendered",
"E:mounted", "E:mounted",
"D:mounted", "D:mounted",
"C:mounted", "C:mounted",
"B:mounted", "B:mounted",
"A:mounted", "A:mounted",
]).toBeLogged(); ]
`);
// update // update
c!.state.flag = false; c!.state.flag = false;
await nextTick(); await nextTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"C:willRender", "C:willRender",
"F:setup", "F:setup",
"F:willStart", "F:willStart",
"C:rendered",
"F:willRender", "F:willRender",
"F:rendered", "F:rendered",
"C:rendered",
"C:willPatch", "C:willPatch",
"E:willUnmount", "E:willUnmount",
"E:willDestroy", "E:willDestroy",
"F:mounted", "F:mounted",
"C:patched", "C:patched",
]).toBeLogged(); ]
`);
}); });
test("mounted hook is called on every mount, not just the first one", async () => { test("mounted hook is called on every mount, not just the first one", async () => {
@@ -1118,43 +1175,49 @@ describe("lifecycle hooks", () => {
} }
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.hasChild = false; parent.state.hasChild = false;
await nextTick(); await nextTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:willUnmount", "Child:willUnmount",
"Child:willDestroy", "Child:willDestroy",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
parent.state.hasChild = true; parent.state.hasChild = true;
await nextTick(); await nextTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:mounted", "Child:mounted",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
}); });
test("render in mounted", async () => { test("render in mounted", async () => {
@@ -1172,7 +1235,8 @@ describe("lifecycle hooks", () => {
await mount(Parent, fixture); await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<span></span>"); expect(fixture.innerHTML).toBe("<span></span>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
@@ -1180,11 +1244,17 @@ describe("lifecycle hooks", () => {
"Parent:mounted", "Parent:mounted",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
]).toBeLogged(); ]
`);
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<span>Patched</span>"); expect(fixture.innerHTML).toBe("<span>Patched</span>");
expect(["Parent:willPatch", "Parent:patched"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willPatch",
"Parent:patched",
]
`);
}); });
test("render in patched", async () => { test("render in patched", async () => {
@@ -1205,29 +1275,38 @@ describe("lifecycle hooks", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<span></span>"); expect(fixture.innerHTML).toBe("<span></span>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.render(); parent.render();
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<span></span>"); expect(fixture.innerHTML).toBe("<span></span>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Parent:patched", "Parent:patched",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
]).toBeLogged(); ]
`);
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<span>Patched</span>"); expect(fixture.innerHTML).toBe("<span>Patched</span>");
expect(["Parent:willPatch", "Parent:patched"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willPatch",
"Parent:patched",
]
`);
}); });
test("render in willPatch", async () => { test("render in willPatch", async () => {
@@ -1248,29 +1327,38 @@ describe("lifecycle hooks", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<span></span>"); expect(fixture.innerHTML).toBe("<span></span>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.render(); parent.render();
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<span></span>"); expect(fixture.innerHTML).toBe("<span></span>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Parent:patched", "Parent:patched",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
]).toBeLogged(); ]
`);
await nextTick(); await nextTick();
expect(["Parent:willPatch", "Parent:patched"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willPatch",
"Parent:patched",
]
`);
expect(fixture.innerHTML).toBe("<span>Patched</span>"); expect(fixture.innerHTML).toBe("<span>Patched</span>");
}); });
@@ -1313,7 +1401,8 @@ describe("lifecycle hooks", () => {
await nextTick(); await nextTick();
app.destroy(); app.destroy();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"onWillStart", "onWillStart",
"onWillRender", "onWillRender",
"onRendered", "onRendered",
@@ -1325,7 +1414,8 @@ describe("lifecycle hooks", () => {
"onPatched", "onPatched",
"onWillUnmount", "onWillUnmount",
"onWillDestroy", "onWillDestroy",
]).toBeLogged(); ]
`);
}); });
test("destroy new children before being mountged", async () => { test("destroy new children before being mountged", async () => {
@@ -1357,26 +1447,32 @@ describe("lifecycle hooks", () => {
const app = new App(Parent); const app = new App(Parent);
const parent = await app.mount(fixture); const parent = await app.mount(fixture);
expect(fixture.innerHTML).toBe("beforeafter"); expect(fixture.innerHTML).toBe("beforeafter");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.flag = true; parent.state.flag = true;
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe(""); expect(fixture.innerHTML).toBe("");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Child:willRender",
"Child:rendered",
"Parent:rendered", "Parent:rendered",
"Parent:willUnmount", "Parent:willUnmount",
"Child:willDestroy", "Child:willDestroy",
"Parent:willDestroy", "Parent:willDestroy",
]).toBeLogged(); ]
`);
}); });
}); });
+31 -19
View File
@@ -1,5 +1,5 @@
import { Component, mount, onWillUpdateProps, useState, xml } from "../../src"; import { Component, mount, onWillUpdateProps, useState, xml } from "../../src";
import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers"; import { makeTestFixture, nextTick, snapshotEverything, steps, useLogLifecycle } from "../helpers";
let fixture: HTMLElement; let fixture: HTMLElement;
@@ -272,26 +272,30 @@ test("bound functions are considered 'alike'", async () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("1child"); expect(fixture.innerHTML).toBe("1child");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.val = 3; parent.state.val = 3;
await nextTick(); await nextTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
expect(fixture.innerHTML).toBe("3child"); expect(fixture.innerHTML).toBe("3child");
}); });
@@ -330,29 +334,33 @@ test(".alike suffix in a simple case", async () => {
} }
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
expect(fixture.innerHTML).toBe("01"); expect(fixture.innerHTML).toBe("01");
parent.state.counter++; parent.state.counter++;
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("11"); expect(fixture.innerHTML).toBe("11");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
}); });
test(".alike suffix in a list", async () => { test(".alike suffix in a list", async () => {
@@ -388,29 +396,32 @@ test(".alike suffix in a list", async () => {
} }
await mount(Parent, fixture); await mount(Parent, fixture);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Todo:setup", "Todo:setup",
"Todo:willStart", "Todo:willStart",
"Todo:willRender",
"Todo:rendered",
"Todo:setup", "Todo:setup",
"Todo:willStart", "Todo:willStart",
"Todo:willRender",
"Todo:rendered",
"Parent:rendered", "Parent:rendered",
"Todo:willRender",
"Todo:rendered",
"Todo:willRender",
"Todo:rendered",
"Todo:mounted", "Todo:mounted",
"Todo:mounted", "Todo:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
expect(fixture.innerHTML).toBe("<button>1</button><button>2V</button>"); expect(fixture.innerHTML).toBe("<button>1</button><button>2V</button>");
fixture.querySelector("button")?.click(); fixture.querySelector("button")?.click();
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<button>1V</button><button>2V</button>"); expect(fixture.innerHTML).toBe("<button>1V</button><button>2V</button>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Todo:willRender", "Todo:willRender",
@@ -419,5 +430,6 @@ test(".alike suffix in a list", async () => {
"Todo:patched", "Todo:patched",
"Parent:willPatch", "Parent:willPatch",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
}); });
+38 -21
View File
@@ -51,8 +51,9 @@ describe("props validation", () => {
const app = new App(Parent, { test: true }); const app = new App(Parent, { test: true });
let error: OwlError | undefined; let error: OwlError | undefined;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow( await expect(appError).resolves.toThrow(
"Invalid props for component 'SubComp': 'message' is missing" "Invalid props for component 'SubComp': 'message' is missing"
); );
await mountProm; await mountProm;
@@ -80,8 +81,9 @@ describe("props validation", () => {
const app = new App(Parent, { test: true }); const app = new App(Parent, { test: true });
let error: OwlError | undefined; let error: OwlError | undefined;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow( await expect(appError).resolves.toThrow(
"Invalid props for component 'SubComp': 'message' is missing" "Invalid props for component 'SubComp': 'message' is missing"
); );
await mountProm; await mountProm;
@@ -131,8 +133,9 @@ describe("props validation", () => {
props = {}; props = {};
let app = new App(Parent, { test: true }); let app = new App(Parent, { test: true });
let error: OwlError | undefined; let error: OwlError | undefined;
let appError = nextAppError(app);
let mountProm = app.mount(fixture).catch((e: Error) => (error = e)); let mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'"); await expect(appError).resolves.toThrow("Invalid props for component '_a'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
@@ -148,8 +151,9 @@ describe("props validation", () => {
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: test.ko }; props = { p: test.ko };
app = new App(Parent, { test: true }); app = new App(Parent, { test: true });
appError = nextAppError(app);
mountProm = app.mount(fixture).catch((e: Error) => (error = e)); mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'"); await expect(appError).resolves.toThrow("Invalid props for component '_a'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
@@ -183,8 +187,9 @@ describe("props validation", () => {
props = {}; props = {};
let app = new App(Parent, { test: true }); let app = new App(Parent, { test: true });
let error: OwlError | undefined; let error: OwlError | undefined;
let appError = nextAppError(app);
let mountProm = app.mount(fixture).catch((e: Error) => (error = e)); let mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'"); await expect(appError).resolves.toThrow("Invalid props for component '_a'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
@@ -200,8 +205,9 @@ describe("props validation", () => {
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: test.ko }; props = { p: test.ko };
app = new App(Parent, { test: true }); app = new App(Parent, { test: true });
appError = nextAppError(app);
mountProm = app.mount(fixture).catch((e: Error) => (error = e)); mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'"); await expect(appError).resolves.toThrow("Invalid props for component '_a'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
@@ -240,8 +246,9 @@ describe("props validation", () => {
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: 1 }; props = { p: 1 };
const app = new App(Parent, { test: true }); const app = new App(Parent, { test: true });
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); await expect(appError).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
@@ -279,8 +286,9 @@ describe("props validation", () => {
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: 1 }; props = { p: 1 };
const app = new App(Parent, { test: true }); const app = new App(Parent, { test: true });
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); await expect(appError).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is not a string"); expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is not a string");
@@ -316,14 +324,16 @@ describe("props validation", () => {
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: [1] }; props = { p: [1] };
let app = new App(Parent, { test: true }); let app = new App(Parent, { test: true });
let appError = nextAppError(app);
let mountProm = app.mount(fixture).catch((e: Error) => (error = e)); let mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); await expect(appError).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
error = undefined; error = undefined;
app = new App(Parent, { test: true }); app = new App(Parent, { test: true });
appError = nextAppError(app);
mountProm = app.mount(fixture).catch((e: Error) => (error = e)); mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); await expect(appError).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
}); });
@@ -365,8 +375,9 @@ describe("props validation", () => {
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: [true, 1] }; props = { p: [true, 1] };
const app = new App(Parent, { test: true }); const app = new App(Parent, { test: true });
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); await expect(appError).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
@@ -399,8 +410,9 @@ describe("props validation", () => {
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: { id: 1, url: "url", extra: true } }; props = { p: { id: 1, url: "url", extra: true } };
let app = new App(Parent, { test: true }); let app = new App(Parent, { test: true });
let appError = nextAppError(app);
let mountProm = app.mount(fixture).catch((e: Error) => (error = e)); let mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); await expect(appError).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
@@ -408,8 +420,9 @@ describe("props validation", () => {
); );
props = { p: { id: "1", url: "url" } }; props = { p: { id: "1", url: "url" } };
app = new App(Parent, { test: true }); app = new App(Parent, { test: true });
appError = nextAppError(app);
mountProm = app.mount(fixture).catch((e: Error) => (error = e)); mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); await expect(appError).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
@@ -418,8 +431,9 @@ describe("props validation", () => {
error = undefined; error = undefined;
props = { p: { id: 1 } }; props = { p: { id: 1 } };
app = new App(Parent, { test: true }); app = new App(Parent, { test: true });
appError = nextAppError(app);
mountProm = app.mount(fixture).catch((e: Error) => (error = e)); mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); await expect(appError).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
@@ -465,8 +479,9 @@ describe("props validation", () => {
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: { id: 1, url: [12, true] } }; props = { p: { id: 1, url: [12, true] } };
const app = new App(Parent, { test: true }); const app = new App(Parent, { test: true });
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); await expect(appError).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
@@ -675,8 +690,9 @@ describe("props validation", () => {
} }
let error: Error; let error: Error;
const app = new App(Parent, { test: true }); const app = new App(Parent, { test: true });
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); await expect(appError).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is missing"); expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is missing");
@@ -765,8 +781,9 @@ describe("props validation", () => {
} }
let error: Error; let error: Error;
const app = new App(Parent, { test: true }); const app = new App(Parent, { test: true });
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'Child'"); await expect(appError).resolves.toThrow("Invalid props for component 'Child'");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
@@ -821,8 +838,9 @@ describe("props validation", () => {
const app = new App(Parent, { test: true }); const app = new App(Parent, { test: true });
let error: OwlError | undefined; let error: OwlError | undefined;
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow( await expect(appError).resolves.toThrow(
"Invalid props for component 'Child': 'message' is missing" "Invalid props for component 'Child': 'message' is missing"
); );
await mountProm; await mountProm;
@@ -895,10 +913,9 @@ describe("default props", () => {
} }
let error: Error; let error: Error;
const app = new App(Parent, { test: true }); const app = new App(Parent, { test: true });
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow( await expect(appError).resolves.toThrow("default value cannot be defined for a mandatory prop");
"default value cannot be defined for a mandatory prop"
);
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
+12 -7
View File
@@ -9,7 +9,7 @@ import {
xml, xml,
toRaw, toRaw,
} from "../../src"; } from "../../src";
import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers"; import { makeTestFixture, nextTick, snapshotEverything, steps, useLogLifecycle } from "../helpers";
let fixture: HTMLElement; let fixture: HTMLElement;
@@ -151,9 +151,10 @@ describe("reactivity in lifecycle", () => {
} }
} }
const prom = mount(Comp, fixture); const prom = mount(Comp, fixture);
expect(steps).toEqual([1]);
(STATE as any).val = 2; (STATE as any).val = 2;
await prom; await prom;
expect(steps).toEqual([2]); expect(steps).toEqual([1, 2]);
expect(fixture.innerHTML).toBe("<div>2</div>"); expect(fixture.innerHTML).toBe("<div>2</div>");
}); });
@@ -175,30 +176,34 @@ describe("reactivity in lifecycle", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("2"); expect(fixture.innerHTML).toBe("2");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.content = null; parent.state.content = null;
parent.state.renderChild = false; parent.state.renderChild = false;
await nextTick(); await nextTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:willUnmount", "Child:willUnmount",
"Child:willDestroy", "Child:willDestroy",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
}); });
test("Component is automatically subscribed to reactive object received as prop", async () => { test("Component is automatically subscribed to reactive object received as prop", async () => {
+2 -1
View File
@@ -125,10 +125,11 @@ describe("refs", () => {
} }
const app = new App(Test, { test: true }); const app = new App(Test, { test: true });
const appError = nextAppError(app);
const mountProm = expect(app.mount(fixture)).rejects.toThrowError( const mountProm = expect(app.mount(fixture)).rejects.toThrowError(
'Cannot set the same ref more than once in the same component, ref "coucou" was set multiple times in Test' 'Cannot set the same ref more than once in the same component, ref "coucou" was set multiple times in Test'
); );
await expect(nextAppError(app)).resolves.toThrow( await expect(appError).resolves.toThrow(
'Cannot set the same ref more than once in the same component, ref "coucou" was set multiple times in Test' 'Cannot set the same ref more than once in the same component, ref "coucou" was set multiple times in Test'
); );
await mountProm; await mountProm;
+102 -47
View File
@@ -6,6 +6,7 @@ import {
useLogLifecycle, useLogLifecycle,
makeDeferred, makeDeferred,
nextMicroTick, nextMicroTick,
steps,
} from "../helpers"; } from "../helpers";
let fixture: HTMLElement; let fixture: HTMLElement;
@@ -41,28 +42,32 @@ describe("rendering semantics", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("Achild"); expect(fixture.innerHTML).toBe("Achild");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.value = "B"; parent.state.value = "B";
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("Bchild"); expect(fixture.innerHTML).toBe("Bchild");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
}); });
test("can force a render to update sub tree", async () => { test("can force a render to update sub tree", async () => {
@@ -167,18 +172,20 @@ describe("rendering semantics", () => {
const parent = await mount(Parent, fixture, { env }); const parent = await mount(Parent, fixture, { env });
expect(fixture.innerHTML).toBe("parentAchild3"); expect(fixture.innerHTML).toBe("parentAchild3");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
value = 4; value = 4;
parent.render(true); parent.render(true);
@@ -187,30 +194,34 @@ describe("rendering semantics", () => {
await nextMicroTick(); await nextMicroTick();
await nextMicroTick(); await nextMicroTick();
await nextMicroTick(); await nextMicroTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Child:willUpdateProps", "Child:willUpdateProps",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
]).toBeLogged(); "Parent:rendered",
]
`);
parent.state.value = "B"; parent.state.value = "B";
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("parentBchild4"); expect(fixture.innerHTML).toBe("parentBchild4");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Child:willUpdateProps", "Child:willUpdateProps",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:willPatch", "Child:willPatch",
"Child:patched", "Child:patched",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
}); });
test("props are reactive", async () => { test("props are reactive", async () => {
@@ -235,23 +246,32 @@ describe("rendering semantics", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("1"); expect(fixture.innerHTML).toBe("1");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.b = 3; parent.state.b = 3;
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("3"); expect(fixture.innerHTML).toBe("3");
expect(["Child:willRender", "Child:rendered", "Child:willPatch", "Child:patched"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Child:willRender",
"Child:rendered",
"Child:willPatch",
"Child:patched",
]
`);
}); });
test("props are reactive (nested prop)", async () => { test("props are reactive (nested prop)", async () => {
@@ -278,28 +298,38 @@ describe("rendering semantics", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("1"); expect(fixture.innerHTML).toBe("1");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.b.c = 3; // parent is now subscribed to 'b' key parent.state.b.c = 3; // parent is now subscribed to 'b' key
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("3"); expect(fixture.innerHTML).toBe("3");
expect(["Child:willRender", "Child:rendered", "Child:willPatch", "Child:patched"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Child:willRender",
"Child:rendered",
"Child:willPatch",
"Child:patched",
]
`);
parent.state.b = { c: 444 }; // triggers a parent and a child render parent.state.b = { c: 444 }; // triggers a parent and a child render
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("444"); expect(fixture.innerHTML).toBe("444");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Child:willRender", "Child:willRender",
@@ -308,7 +338,8 @@ describe("rendering semantics", () => {
"Parent:patched", "Parent:patched",
"Child:willPatch", "Child:willPatch",
"Child:patched", "Child:patched",
]).toBeLogged(); ]
`);
}); });
test("works as expected for dynamic number of props", async () => { test("works as expected for dynamic number of props", async () => {
@@ -366,28 +397,31 @@ describe("rendering semantics", () => {
const parent = await mount(A, fixture); const parent = await mount(A, fixture);
expect(fixture.innerHTML).toBe("11"); expect(fixture.innerHTML).toBe("11");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:setup", "A:setup",
"A:willStart", "A:willStart",
"A:willRender", "A:willRender",
"B:setup", "B:setup",
"B:willStart", "B:willStart",
"A:rendered",
"B:willRender", "B:willRender",
"C:setup", "C:setup",
"C:willStart", "C:willStart",
"B:rendered",
"C:willRender", "C:willRender",
"C:rendered", "C:rendered",
"B:rendered",
"A:rendered",
"C:mounted", "C:mounted",
"B:mounted", "B:mounted",
"A:mounted", "A:mounted",
]).toBeLogged(); ]
`);
parent.state.obj.val = 3; parent.state.obj.val = 3;
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("33"); expect(fixture.innerHTML).toBe("33");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:willRender", "A:willRender",
"A:rendered", "A:rendered",
"C:willRender", "C:willRender",
@@ -396,11 +430,12 @@ describe("rendering semantics", () => {
"A:patched", "A:patched",
"C:willPatch", "C:willPatch",
"C:patched", "C:patched",
]).toBeLogged(); ]
`);
def.resolve(); def.resolve();
await nextTick(); await nextTick();
expect([]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`Array []`);
}); });
}); });
@@ -431,51 +466,67 @@ test("force render in case of existing render", async () => {
} }
const parent = await mount(A, fixture); const parent = await mount(A, fixture);
expect(fixture.innerHTML).toBe("C1"); expect(fixture.innerHTML).toBe("C1");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:setup", "A:setup",
"A:willStart", "A:willStart",
"A:willRender", "A:willRender",
"B:setup", "B:setup",
"B:willStart", "B:willStart",
"A:rendered",
"B:willRender", "B:willRender",
"C:setup", "C:setup",
"C:willStart", "C:willStart",
"B:rendered",
"C:willRender", "C:willRender",
"C:rendered", "C:rendered",
"B:rendered",
"A:rendered",
"C:mounted", "C:mounted",
"B:mounted", "B:mounted",
"A:mounted", "A:mounted",
]).toBeLogged(); ]
`);
// trigger a new rendering, blocked in B // trigger a new rendering, blocked in B
parent.state.val = 2; parent.state.val = 2;
await nextTick(); await nextTick();
expect(["A:willRender", "B:willUpdateProps", "A:rendered"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:willRender",
"B:willUpdateProps",
"A:rendered",
]
`);
// initiate a new render with deep=true. it should cancel the current render // initiate a new render with deep=true. it should cancel the current render
// and also be blocked in B // and also be blocked in B
parent.render(true); parent.render(true);
await nextTick(); await nextTick();
expect(["A:willRender", "B:willUpdateProps", "A:rendered"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"A:willRender",
"B:willUpdateProps",
"A:rendered",
]
`);
def.resolve(); def.resolve();
await nextTick(); await nextTick();
// we check here that the render reaches C (so, that it was properly forced) // we check here that the render reaches C (so, that it was properly forced)
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"B:willRender", "B:willRender",
"C:willUpdateProps", "C:willUpdateProps",
"B:rendered",
"C:willRender", "C:willRender",
"C:rendered", "C:rendered",
"B:rendered",
"A:willPatch", "A:willPatch",
"B:willPatch",
"C:willPatch", "C:willPatch",
"C:patched", "B:willPatch",
"B:patched", "B:patched",
"C:patched",
"A:patched", "A:patched",
]).toBeLogged(); ]
`);
}); });
test("children, default props and renderings", async () => { test("children, default props and renderings", async () => {
@@ -503,26 +554,30 @@ test("children, default props and renderings", async () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("Achild"); expect(fixture.innerHTML).toBe("Achild");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.state.value = "B"; parent.state.value = "B";
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("Bchild"); expect(fixture.innerHTML).toBe("Bchild");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
}); });
+2 -1
View File
@@ -223,8 +223,9 @@ describe("slots", () => {
let error: Error; let error: Error;
const app = new App(Parent); const app = new App(Parent);
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await expect(appError).resolves.toThrow("error occured in the owl lifecycle");
await mountProm; await mountProm;
expect(error!).not.toBeNull(); expect(error!).not.toBeNull();
expect(mockConsoleWarn).toBeCalledTimes(1); expect(mockConsoleWarn).toBeCalledTimes(1);
+2 -1
View File
@@ -349,8 +349,9 @@ describe("style and class handling", () => {
} }
let error: OwlError; let error: OwlError;
const app = new App(Parent); const app = new App(Parent);
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await expect(appError).resolves.toThrow("error occured in the owl lifecycle");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.cause).toBeDefined(); expect(error!.cause).toBeDefined();
+16 -10
View File
@@ -1,5 +1,5 @@
import { Component, mount, useState, xml } from "../../src"; import { Component, mount, useState, xml } from "../../src";
import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers"; import { makeTestFixture, nextTick, snapshotEverything, steps, useLogLifecycle } from "../helpers";
let fixture: HTMLElement; let fixture: HTMLElement;
@@ -29,18 +29,20 @@ describe("t-component", () => {
await mount(Parent, fixture); await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div>child</div>"); expect(fixture.innerHTML).toBe("<div>child</div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
}); });
test("switching dynamic component", async () => { test("switching dynamic component", async () => {
@@ -68,36 +70,40 @@ describe("t-component", () => {
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div>child a</div>"); expect(fixture.innerHTML).toBe("<div>child a</div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"ChildA:setup", "ChildA:setup",
"ChildA:willStart", "ChildA:willStart",
"Parent:rendered",
"ChildA:willRender", "ChildA:willRender",
"ChildA:rendered", "ChildA:rendered",
"Parent:rendered",
"ChildA:mounted", "ChildA:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
parent.Child = ChildB; parent.Child = ChildB;
parent.render(); parent.render();
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("child b"); expect(fixture.innerHTML).toBe("child b");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"ChildB:setup", "ChildB:setup",
"ChildB:willStart", "ChildB:willStart",
"Parent:rendered",
"ChildB:willRender", "ChildB:willRender",
"ChildB:rendered", "ChildB:rendered",
"Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"ChildA:willUnmount", "ChildA:willUnmount",
"ChildA:willDestroy", "ChildA:willDestroy",
"ChildB:mounted", "ChildB:mounted",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
}); });
test("can switch between dynamic components without the need for a t-key", async () => { test("can switch between dynamic components without the need for a t-key", async () => {
+13 -10
View File
@@ -4,6 +4,7 @@ import {
nextAppError, nextAppError,
nextTick, nextTick,
snapshotEverything, snapshotEverything,
steps,
useLogLifecycle, useLogLifecycle,
} from "../helpers"; } from "../helpers";
@@ -91,23 +92,25 @@ describe("list of components", () => {
expect(fixture.innerHTML).toBe( expect(fixture.innerHTML).toBe(
"<div><ul><li><div>1</div></li><li><div>2</div></li></ul></div>" "<div><ul><li><div>1</div></li><li><div>2</div></li></ul></div>"
); );
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Child:willRender",
"Child:rendered",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Child:willRender",
"Child:rendered",
"Parent:rendered", "Parent:rendered",
"Child:willRender",
"Child:rendered",
"Child:willRender",
"Child:rendered",
"Child:mounted", "Child:mounted",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
}); });
test("reconciliation alg works for t-foreach in t-foreach", async () => { test("reconciliation alg works for t-foreach in t-foreach", async () => {
@@ -323,10 +326,11 @@ describe("list of components", () => {
} }
const app = new App(Parent, { test: true }); const app = new App(Parent, { test: true });
const appError = nextAppError(app);
const mountProm = expect(app.mount(fixture)).rejects.toThrow( const mountProm = expect(app.mount(fixture)).rejects.toThrow(
"Got duplicate key in t-foreach: child" "Got duplicate key in t-foreach: child"
); );
await expect(nextAppError(app)).resolves.toThrow("Got duplicate key in t-foreach: child"); await expect(appError).resolves.toThrow("Got duplicate key in t-foreach: child");
await mountProm; await mountProm;
console.info = consoleInfo; console.info = consoleInfo;
expect(mockConsoleWarn).toBeCalledTimes(1); expect(mockConsoleWarn).toBeCalledTimes(1);
@@ -349,12 +353,11 @@ describe("list of components", () => {
} }
const app = new App(Parent, { test: true }); const app = new App(Parent, { test: true });
const appError = nextAppError(app);
const mountProm = expect(app.mount(fixture)).rejects.toThrow( const mountProm = expect(app.mount(fixture)).rejects.toThrow(
"Got duplicate key in t-foreach: [object Object]" "Got duplicate key in t-foreach: [object Object]"
); );
await expect(nextAppError(app)).resolves.toThrow( await expect(appError).resolves.toThrow("Got duplicate key in t-foreach: [object Object]");
"Got duplicate key in t-foreach: [object Object]"
);
await mountProm; await mountProm;
console.info = consoleInfo; console.info = consoleInfo;
expect(mockConsoleWarn).toBeCalledTimes(1); expect(mockConsoleWarn).toBeCalledTimes(1);
+2 -2
View File
@@ -137,7 +137,7 @@ export function snapshotEverything() {
}; };
} }
const steps: string[] = []; export const steps: string[] = [];
export function logStep(step: string) { export function logStep(step: string) {
steps.push(step); steps.push(step);
@@ -235,7 +235,7 @@ expect.extend({
}; };
const currentSteps = steps.splice(0); const currentSteps = steps.splice(0);
const pass = this.equals(currentSteps, expected); let pass = this.equals(currentSteps, expected);
const message = pass const message = pass
? () => ? () =>
+6 -3
View File
@@ -270,8 +270,9 @@ describe("Portal", () => {
let error: Error; let error: Error;
const app = new App(Parent); const app = new App(Parent);
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("invalid portal target"); await expect(appError).resolves.toThrow("invalid portal target");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
@@ -1002,8 +1003,9 @@ describe("Portal: Props validation", () => {
} }
let error: OwlError; let error: OwlError;
const app = new App(Parent); const app = new App(Parent);
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await expect(appError).resolves.toThrow("error occured in the owl lifecycle");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.cause).toBeDefined(); expect(error!.cause).toBeDefined();
@@ -1021,8 +1023,9 @@ describe("Portal: Props validation", () => {
} }
let error: Error; let error: Error;
const app = new App(Parent); const app = new App(Parent);
const appError = nextAppError(app);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("invalid portal target"); await expect(appError).resolves.toThrow("invalid portal target");
await mountProm; await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe(`invalid portal target`); expect(error!.message).toBe(`invalid portal target`);
+71 -33
View File
@@ -17,6 +17,7 @@ import {
nextMicroTick, nextMicroTick,
nextTick, nextTick,
snapshotEverything, snapshotEverything,
steps,
useLogLifecycle, useLogLifecycle,
} from "./helpers"; } from "./helpers";
@@ -1850,28 +1851,31 @@ describe("Reactivity: useState", () => {
} }
} }
await mount(Parent, fixture); await mount(Parent, fixture);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Child:willRender",
"Child:rendered",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Child:willRender",
"Child:rendered",
"Parent:rendered", "Parent:rendered",
"Child:willRender",
"Child:rendered",
"Child:willRender",
"Child:rendered",
"Child:mounted", "Child:mounted",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
expect(fixture.innerHTML).toBe("<div><span>123</span><span>123</span></div>"); expect(fixture.innerHTML).toBe("<div><span>123</span><span>123</span></div>");
testContext.value = 321; testContext.value = 321;
await nextTick(); await nextTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Child:willRender", "Child:willRender",
@@ -1880,7 +1884,8 @@ describe("Reactivity: useState", () => {
"Child:patched", "Child:patched",
"Child:willPatch", "Child:willPatch",
"Child:patched", "Child:patched",
]).toBeLogged(); ]
`);
expect(fixture.innerHTML).toBe("<div><span>321</span><span>321</span></div>"); expect(fixture.innerHTML).toBe("<div><span>321</span><span>321</span></div>");
}); });
@@ -1904,38 +1909,49 @@ describe("Reactivity: useState", () => {
} }
await mount(Parent, fixture); await mount(Parent, fixture);
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Child:willRender",
"Child:rendered",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Child:willRender",
"Child:rendered",
"Parent:rendered", "Parent:rendered",
"Child:willRender",
"Child:rendered",
"Child:willRender",
"Child:rendered",
"Child:mounted", "Child:mounted",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
expect(fixture.innerHTML).toBe("<div><span>123</span><span>123</span></div>"); expect(fixture.innerHTML).toBe("<div><span>123</span><span>123</span></div>");
testContext.value = 321; testContext.value = 321;
await nextMicroTick(); await nextMicroTick();
await nextMicroTick(); await nextMicroTick();
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
]).toBeLogged(); ]
`);
expect(fixture.innerHTML).toBe("<div><span>123</span><span>123</span></div>"); expect(fixture.innerHTML).toBe("<div><span>123</span><span>123</span></div>");
await nextTick(); await nextTick();
expect(["Child:willPatch", "Child:patched", "Child:willPatch", "Child:patched"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Child:willPatch",
"Child:patched",
"Child:willPatch",
"Child:patched",
]
`);
expect(fixture.innerHTML).toBe("<div><span>321</span><span>321</span></div>"); expect(fixture.innerHTML).toBe("<div><span>321</span><span>321</span></div>");
}); });
@@ -1969,43 +1985,54 @@ describe("Reactivity: useState", () => {
await mount(GrandFather, fixture); await mount(GrandFather, fixture);
expect(fixture.innerHTML).toBe("<div><span>123</span><div><span>123</span></div></div>"); expect(fixture.innerHTML).toBe("<div><span>123</span><div><span>123</span></div></div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"GrandFather:setup", "GrandFather:setup",
"GrandFather:willStart", "GrandFather:willStart",
"GrandFather:willRender", "GrandFather:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:setup",
"Parent:willStart",
"GrandFather:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:setup",
"Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"GrandFather:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
"Child:mounted", "Child:mounted",
"GrandFather:mounted", "GrandFather:mounted",
]).toBeLogged(); ]
`);
testContext.value = 321; testContext.value = 321;
await nextMicroTick(); await nextMicroTick();
await nextMicroTick(); await nextMicroTick();
expect(fixture.innerHTML).toBe("<div><span>123</span><div><span>123</span></div></div>"); expect(fixture.innerHTML).toBe("<div><span>123</span><div><span>123</span></div></div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
]).toBeLogged(); ]
`);
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div><span>321</span><div><span>321</span></div></div>"); expect(fixture.innerHTML).toBe("<div><span>321</span><div><span>321</span></div></div>");
expect(["Child:willPatch", "Child:patched", "Child:willPatch", "Child:patched"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Child:willPatch",
"Child:patched",
"Child:willPatch",
"Child:patched",
]
`);
}); });
test("one components can subscribe twice to same context", async () => { test("one components can subscribe twice to same context", async () => {
@@ -2181,38 +2208,49 @@ describe("Reactivity: useState", () => {
} }
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div><span>123</span></div>"); expect(fixture.innerHTML).toBe("<div><span>123</span></div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Parent:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]
`);
testContext.a = 321; testContext.a = 321;
await nextTick(); await nextTick();
expect(["Child:willRender", "Child:rendered", "Child:willPatch", "Child:patched"]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Child:willRender",
"Child:rendered",
"Child:willPatch",
"Child:patched",
]
`);
parent.state.flag = false; parent.state.flag = false;
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div></div>"); expect(fixture.innerHTML).toBe("<div></div>");
expect([ expect(steps.splice(0)).toMatchInlineSnapshot(`
Array [
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:willUnmount", "Child:willUnmount",
"Child:willDestroy", "Child:willDestroy",
"Parent:patched", "Parent:patched",
]).toBeLogged(); ]
`);
testContext.a = 456; testContext.a = 456;
await nextTick(); await nextTick();
expect([]).toBeLogged(); expect(steps.splice(0)).toMatchInlineSnapshot(`Array []`);
}); });
test("destroyed component before being mounted is inactive", async () => { test("destroyed component before being mounted is inactive", async () => {