[REF] tests: improve useLogLifecycle and helpers

This commit is contained in:
Géry Debongnie
2021-11-27 10:08:10 +01:00
committed by Samuel Degueldre
parent 922dab8e8f
commit dcefd26bee
7 changed files with 899 additions and 926 deletions
+1
View File
@@ -46,6 +46,7 @@
"git-rev-sync": "^1.12.0", "git-rev-sync": "^1.12.0",
"github-api": "^3.3.0", "github-api": "^3.3.0",
"jest": "^27.1.0", "jest": "^27.1.0",
"jest-diff": "^27.3.1",
"jest-environment-jsdom": "^27.1.0", "jest-environment-jsdom": "^27.1.0",
"live-server": "^1.2.1", "live-server": "^1.2.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
File diff suppressed because it is too large Load Diff
+121 -179
View File
@@ -346,12 +346,10 @@ describe("lifecycle hooks", () => {
}); });
test("components are unmounted and destroyed if no longer in DOM, even after updateprops", async () => { test("components are unmounted and destroyed if no longer in DOM, even after updateprops", async () => {
let steps: string[] = [];
class Child extends Component { class Child extends Component {
static template = xml`<span><t t-esc="props.n"/></span>`; static template = xml`<span><t t-esc="props.n"/></span>`;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
@@ -363,7 +361,7 @@ describe("lifecycle hooks", () => {
`; `;
static components = { Child }; static components = { Child };
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
state = useState({ n: 0, flag: true }); state = useState({ n: 0, flag: true });
increment() { increment() {
@@ -376,13 +374,7 @@ 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>");
parent.increment(); expect([
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
parent.toggleSubWidget();
await nextTick();
expect(fixture.innerHTML).toBe("");
expect(steps).toEqual([
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
@@ -393,6 +385,12 @@ describe("lifecycle hooks", () => {
"Child:rendered", "Child:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged();
parent.increment();
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
expect([
"Parent:willRender", "Parent:willRender",
"Child:willUpdateProps", "Child:willUpdateProps",
"Parent:rendered", "Parent:rendered",
@@ -402,32 +400,26 @@ describe("lifecycle hooks", () => {
"Child:willPatch", "Child:willPatch",
"Child:patched", "Child:patched",
"Parent:patched", "Parent:patched",
]).toBeLogged();
parent.toggleSubWidget();
await nextTick();
expect(fixture.innerHTML).toBe("");
expect([
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:willUnmount", "Child:willUnmount",
"Child:destroyed", "Child:destroyed",
"Parent:patched", "Parent:patched",
]); ]).toBeLogged();
Object.freeze(steps);
}); });
test("hooks are called in proper order in widget creation/destruction", async () => { test("hooks are called in proper order in widget creation/destruction", async () => {
let steps: string[] = [];
class Child extends Component { class Child extends Component {
static template = xml`<div/>`; static template = xml`<div/>`;
setup() { setup() {
steps.push("c init"); useLogLifecycle();
onWillStart(() => {
steps.push("c willstart");
});
onMounted(() => {
steps.push("c mounted");
});
onWillUnmount(() => {
steps.push("c willunmount");
});
} }
} }
@@ -435,33 +427,32 @@ describe("lifecycle hooks", () => {
static template = xml`<div><Child/></div>`; static template = xml`<div><Child/></div>`;
static components = { Child }; static components = { Child };
setup() { setup() {
steps.push("p init"); useLogLifecycle();
onWillStart(() => {
steps.push("p willstart");
});
onMounted(() => {
steps.push("p mounted");
});
onWillUnmount(() => {
steps.push("p willunmount");
});
} }
} }
const app = new App(Parent); const app = new App(Parent);
await app.mount(fixture); await app.mount(fixture);
expect([
"Parent:setup",
"Parent:willStart",
"Parent:willRender",
"Child:setup",
"Child:willStart",
"Parent:rendered",
"Child:willRender",
"Child:rendered",
"Child:mounted",
"Parent:mounted",
]).toBeLogged();
app.destroy(); app.destroy();
expect(steps).toEqual([ expect([
"p init", "Parent:willUnmount",
"p willstart", "Child:willUnmount",
"c init", "Child:destroyed",
"c willstart", "Parent:destroyed",
"c mounted", ]).toBeLogged();
"p mounted",
"p willunmount",
"c willunmount",
]);
Object.freeze(steps);
}); });
test("willUpdateProps hook is called", async () => { test("willUpdateProps hook is called", async () => {
@@ -543,12 +534,10 @@ describe("lifecycle hooks", () => {
}); });
test("lifecycle semantics", async () => { test("lifecycle semantics", async () => {
let steps: string[] = [];
class Child extends Component { class Child extends Component {
static template = xml`<div/>`; static template = xml`<div/>`;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
class Parent extends Component { class Parent extends Component {
@@ -556,14 +545,13 @@ describe("lifecycle hooks", () => {
static components = { Child }; static components = { Child };
state = useState({ a: 1 }); state = useState({ a: 1 });
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
const app = new App(Parent); const app = new App(Parent);
await app.mount(fixture); await app.mount(fixture);
expect([
expect(steps).toEqual([
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
@@ -574,33 +562,29 @@ describe("lifecycle hooks", () => {
"Child:rendered", "Child:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]); ]).toBeLogged();
steps.splice(0);
app.destroy(); app.destroy();
expect(steps).toEqual([ expect([
"Parent:willUnmount", "Parent:willUnmount",
"Child:willUnmount", "Child:willUnmount",
"Child:destroyed", "Child:destroyed",
"Parent:destroyed", "Parent:destroyed",
]); ]).toBeLogged();
Object.freeze(steps);
}); });
test("lifecycle semantics, part 2", async () => { test("lifecycle semantics, part 2", async () => {
let steps: string[] = [];
class GrandChild extends Component { class GrandChild extends Component {
static template = xml`<div/>`; static template = xml`<div/>`;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
class Child extends Component { class Child extends Component {
static template = xml`<GrandChild/>`; static template = xml`<GrandChild/>`;
static components = { GrandChild }; static components = { GrandChild };
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
@@ -609,26 +593,23 @@ describe("lifecycle hooks", () => {
static components = { Child }; static components = { Child };
state = useState({ hasChild: false }); state = useState({ hasChild: false });
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
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).toEqual([
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:mounted", "Parent:mounted",
]); ]).toBeLogged();
steps.splice(0);
parent.state.hasChild = true; parent.state.hasChild = true;
await nextTick(); await nextTick();
expect(steps).toEqual([ expect([
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
@@ -643,36 +624,31 @@ describe("lifecycle hooks", () => {
"GrandChild:mounted", "GrandChild:mounted",
"Child:mounted", "Child:mounted",
"Parent:patched", "Parent:patched",
]); ]).toBeLogged();
steps.splice(0);
app.destroy(); app.destroy();
expect(steps).toEqual([ expect([
"Parent:willUnmount", "Parent:willUnmount",
"Child:willUnmount", "Child:willUnmount",
"GrandChild:willUnmount", "GrandChild:willUnmount",
"GrandChild:destroyed", "GrandChild:destroyed",
"Child:destroyed", "Child:destroyed",
"Parent:destroyed", "Parent:destroyed",
]); ]).toBeLogged();
Object.freeze(steps);
}); });
test("lifecycle semantics, part 3", async () => { test("lifecycle semantics, part 3", async () => {
let steps: string[] = [];
class GrandChild extends Component { class GrandChild extends Component {
static template = xml`<div/>`; static template = xml`<div/>`;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
class Child extends Component { class Child extends Component {
static template = xml`<GrandChild/>`; static template = xml`<GrandChild/>`;
static components = { GrandChild }; static components = { GrandChild };
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
@@ -681,41 +657,34 @@ describe("lifecycle hooks", () => {
static components = { Child }; static components = { Child };
state = useState({ hasChild: false }); state = useState({ hasChild: false });
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
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).toEqual([
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:mounted", "Parent:mounted",
]); ]).toBeLogged();
steps.splice(0);
parent.state.hasChild = true; parent.state.hasChild = true;
// immediately destroy everything // immediately destroy everything
app.destroy(); app.destroy();
await nextTick(); await nextTick();
expect(steps).toEqual(["Parent:willUnmount", "Parent:destroyed"]); expect(["Parent:willUnmount", "Parent:destroyed"]).toBeLogged();
Object.freeze(steps);
}); });
test("lifecycle semantics, part 4", async () => { test("lifecycle semantics, part 4", async () => {
let def = makeDeferred(); let def = makeDeferred();
let steps: string[] = [];
class GrandChild extends Component { class GrandChild extends Component {
static template = xml`<div/>`; static template = xml`<div/>`;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
onWillStart(() => def); onWillStart(() => def);
} }
} }
@@ -723,7 +692,7 @@ describe("lifecycle hooks", () => {
static template = xml`<GrandChild/>`; static template = xml`<GrandChild/>`;
static components = { GrandChild }; static components = { GrandChild };
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
@@ -732,27 +701,24 @@ describe("lifecycle hooks", () => {
static components = { Child }; static components = { Child };
state = useState({ hasChild: false }); state = useState({ hasChild: false });
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
const app = new App(Parent); const app = new App(Parent);
const parent = await app.mount(fixture); const parent = await app.mount(fixture);
expect(steps).toEqual([ expect([
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:mounted", "Parent:mounted",
]); ]).toBeLogged();
steps.splice(0);
parent.state.hasChild = true; parent.state.hasChild = true;
await nextTick(); await nextTick();
expect(steps).toEqual([ expect([
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
@@ -761,27 +727,22 @@ describe("lifecycle hooks", () => {
"GrandChild:setup", "GrandChild:setup",
"GrandChild:willStart", "GrandChild:willStart",
"Child:rendered", "Child:rendered",
]); ]).toBeLogged();
steps.splice(0);
app.destroy(); app.destroy();
expect(steps).toEqual([ expect([
"Parent:willUnmount", "Parent:willUnmount",
"GrandChild:destroyed", "GrandChild:destroyed",
"Child:destroyed", "Child:destroyed",
"Parent:destroyed", "Parent:destroyed",
]); ]).toBeLogged();
Object.freeze(steps);
}); });
test("lifecycle semantics, part 5", async () => { test("lifecycle semantics, part 5", async () => {
let steps: string[] = [];
class Child extends Component { class Child extends Component {
static template = xml`<div/>`; static template = xml`<div/>`;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
@@ -790,13 +751,12 @@ describe("lifecycle hooks", () => {
static components = { Child }; static components = { Child };
state = useState({ hasChild: true }); state = useState({ hasChild: true });
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect([
expect(steps).toEqual([
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
@@ -807,31 +767,25 @@ describe("lifecycle hooks", () => {
"Child:rendered", "Child:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]); ]).toBeLogged();
steps.splice(0);
parent.state.hasChild = false; parent.state.hasChild = false;
await nextTick(); await nextTick();
expect(steps).toEqual([ expect([
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:willUnmount", "Child:willUnmount",
"Child:destroyed", "Child:destroyed",
"Parent:patched", "Parent:patched",
]); ]).toBeLogged();
Object.freeze(steps);
}); });
test("lifecycle semantics, part 6", async () => { test("lifecycle semantics, part 6", async () => {
let steps: string[] = [];
class Child extends Component { class Child extends Component {
static template = xml`<div/>`; static template = xml`<div/>`;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
@@ -840,13 +794,12 @@ describe("lifecycle hooks", () => {
static components = { Child }; static components = { Child };
state = useState({ value: 1 }); state = useState({ value: 1 });
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect([
expect(steps).toEqual([
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
@@ -857,14 +810,11 @@ describe("lifecycle hooks", () => {
"Child:rendered", "Child:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]); ]).toBeLogged();
steps.splice(0);
parent.state.value = 2; parent.state.value = 2;
await nextTick(); await nextTick();
expect(steps).toEqual([ expect([
"Parent:willRender", "Parent:willRender",
"Child:willUpdateProps", "Child:willUpdateProps",
"Parent:rendered", "Parent:rendered",
@@ -874,12 +824,10 @@ describe("lifecycle hooks", () => {
"Child:willPatch", "Child:willPatch",
"Child:patched", "Child:patched",
"Parent:patched", "Parent:patched",
]); ]).toBeLogged();
Object.freeze(steps);
}); });
test("onWillRender", async () => { test("onWillRender", async () => {
let steps: string[] = [];
const def = makeDeferred(); const def = makeDeferred();
class Child extends Component { class Child extends Component {
@@ -887,13 +835,12 @@ describe("lifecycle hooks", () => {
state = useState({ value: 1 }); state = useState({ value: 1 });
visibleState = this.state.value; visibleState = this.state.value;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
onWillUpdateProps(() => def); onWillUpdateProps(() => def);
onWillRender(() => (this.visibleState = this.state.value)); onWillRender(() => (this.visibleState = this.state.value));
} }
increment() { increment() {
this.state.value++; this.state.value++;
steps.push(`inc:${this.visibleState}`);
} }
} }
@@ -902,30 +849,13 @@ describe("lifecycle hooks", () => {
<Child />`; <Child />`;
static components = { Child }; static components = { Child };
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
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([
parent.render(); // to block child render
await nextTick();
fixture.querySelector("button")!.click();
await nextTick();
fixture.querySelector("button")!.click();
await nextTick();
expect(fixture.innerHTML).toBe("<button>1</button>");
def.resolve();
await nextTick();
expect(fixture.innerHTML).toBe("<button>3</button>");
expect(steps).toEqual([
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
@@ -936,19 +866,32 @@ describe("lifecycle hooks", () => {
"Child:rendered", "Child:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
"Parent:willRender", ]).toBeLogged();
"Child:willUpdateProps",
"Parent:rendered", parent.render(); // to block child render
"inc:1", await nextTick();
"inc:1", expect(["Parent:willRender", "Child:willUpdateProps", "Parent:rendered"]).toBeLogged();
fixture.querySelector("button")!.click();
await nextTick();
expect([]).toBeLogged();
fixture.querySelector("button")!.click();
await nextTick();
expect([]).toBeLogged();
expect(fixture.innerHTML).toBe("<button>1</button>");
def.resolve();
await nextTick();
expect(fixture.innerHTML).toBe("<button>3</button>");
expect([
"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();
Object.freeze(steps);
}); });
// TODO: rename (remove? seems covered by lifecycle semantics) // TODO: rename (remove? seems covered by lifecycle semantics)
@@ -982,12 +925,10 @@ describe("lifecycle hooks", () => {
// TODO: rename (corresponds to https://github.com/odoo/owl/blob/master/doc/reference/concurrency_model.md#semantics) // TODO: rename (corresponds to https://github.com/odoo/owl/blob/master/doc/reference/concurrency_model.md#semantics)
test("component semantics", async () => { test("component semantics", async () => {
let steps: string[] = [];
class TestWidget extends Component { class TestWidget extends Component {
name: string = "test"; name: string = "test";
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
class B extends TestWidget { class B extends TestWidget {
@@ -1031,7 +972,7 @@ 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(steps).toEqual([ expect([
"A:setup", "A:setup",
"A:willStart", "A:willStart",
"A:willRender", "A:willRender",
@@ -1057,13 +998,12 @@ describe("lifecycle hooks", () => {
"C:mounted", "C:mounted",
"B:mounted", "B:mounted",
"A:mounted", "A:mounted",
]); ]).toBeLogged();
// update // update
steps.splice(0);
c!.state.flag = false; c!.state.flag = false;
await nextTick(); await nextTick();
expect(steps).toEqual([ expect([
"C:willRender", "C:willRender",
"D:willUpdateProps", "D:willUpdateProps",
"F:setup", "F:setup",
@@ -1080,15 +1020,14 @@ describe("lifecycle hooks", () => {
"F:mounted", "F:mounted",
"D:patched", "D:patched",
"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 () => {
const steps: string[] = [];
class Child extends Component { class Child extends Component {
static template = xml`<div>child</div>`; static template = xml`<div>child</div>`;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
@@ -1097,18 +1036,12 @@ describe("lifecycle hooks", () => {
static components = { Child }; static components = { Child };
state = useState({ hasChild: true }); state = useState({ hasChild: true });
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
const parent = await mount(Parent, fixture); const parent = await mount(Parent, fixture);
expect([
parent.state.hasChild = false;
await nextTick();
parent.state.hasChild = true;
await nextTick();
expect(steps).toEqual([
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
@@ -1119,12 +1052,22 @@ describe("lifecycle hooks", () => {
"Child:rendered", "Child:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged();
parent.state.hasChild = false;
await nextTick();
expect([
"Parent:willRender", "Parent:willRender",
"Parent:rendered", "Parent:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:willUnmount", "Child:willUnmount",
"Child:destroyed", "Child:destroyed",
"Parent:patched", "Parent:patched",
]).toBeLogged();
parent.state.hasChild = true;
await nextTick();
expect([
"Parent:willRender", "Parent:willRender",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
@@ -1134,7 +1077,6 @@ describe("lifecycle hooks", () => {
"Parent:willPatch", "Parent:willPatch",
"Child:mounted", "Child:mounted",
"Parent:patched", "Parent:patched",
]); ]).toBeLogged();
Object.freeze(steps);
}); });
}); });
+16 -19
View File
@@ -12,12 +12,10 @@ beforeEach(() => {
describe("t-component", () => { describe("t-component", () => {
test("t-component works in simple case", async () => { test("t-component works in simple case", async () => {
let steps: string[] = [];
class Child extends Component { class Child extends Component {
static template = xml`<div>child</div>`; static template = xml`<div>child</div>`;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
@@ -25,14 +23,14 @@ describe("t-component", () => {
static template = xml`<t t-component="Child"/>`; static template = xml`<t t-component="Child"/>`;
Child = Child; Child = Child;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
await mount(Parent, fixture); await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div>child</div>"); expect(fixture.innerHTML).toBe("<div>child</div>");
expect(steps).toEqual([ expect([
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
@@ -43,23 +41,21 @@ describe("t-component", () => {
"Child:rendered", "Child:rendered",
"Child:mounted", "Child:mounted",
"Parent:mounted", "Parent:mounted",
]); ]).toBeLogged();
}); });
test("switching dynamic component", async () => { test("switching dynamic component", async () => {
let steps: string[] = [];
class ChildA extends Component { class ChildA extends Component {
static template = xml`<div>child a</div>`; static template = xml`<div>child a</div>`;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
class ChildB extends Component { class ChildB extends Component {
static template = xml`child b`; static template = xml`child b`;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
@@ -67,19 +63,13 @@ describe("t-component", () => {
static template = xml`<t t-component="Child"/>`; static template = xml`<t t-component="Child"/>`;
Child = ChildA; Child = ChildA;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
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([
parent.Child = ChildB;
parent.render();
await nextTick();
expect(fixture.innerHTML).toBe("child b");
expect(steps).toEqual([
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
@@ -90,6 +80,13 @@ describe("t-component", () => {
"ChildA:rendered", "ChildA:rendered",
"ChildA:mounted", "ChildA:mounted",
"Parent:mounted", "Parent:mounted",
]).toBeLogged();
parent.Child = ChildB;
parent.render();
await nextTick();
expect(fixture.innerHTML).toBe("child b");
expect([
"Parent:willRender", "Parent:willRender",
"ChildB:setup", "ChildB:setup",
"ChildB:willStart", "ChildB:willStart",
@@ -101,7 +98,7 @@ describe("t-component", () => {
"ChildA:destroyed", "ChildA:destroyed",
"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 () => {
+4 -6
View File
@@ -49,12 +49,10 @@ describe("list of components", () => {
}); });
test("components in a node in a t-foreach ", async () => { test("components in a node in a t-foreach ", async () => {
const steps: string[] = [];
class Child extends Component { class Child extends Component {
static template = xml`<div><t t-esc="props.item"/></div>`; static template = xml`<div><t t-esc="props.item"/></div>`;
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
} }
@@ -72,7 +70,7 @@ describe("list of components", () => {
static components = { Child }; static components = { Child };
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle();
} }
get items() { get items() {
@@ -84,7 +82,7 @@ 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(steps).toEqual([ expect([
"Parent:setup", "Parent:setup",
"Parent:willStart", "Parent:willStart",
"Parent:willRender", "Parent:willRender",
@@ -100,7 +98,7 @@ describe("list of components", () => {
"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 () => {
+68 -11
View File
@@ -161,53 +161,58 @@ export function snapshotEverything() {
}); });
} }
export function useLogLifecycle(steps: string[]) { const steps: string[] = [];
export function logStep(step: string) {
steps.push(step);
}
export function useLogLifecycle() {
const component = useComponent(); const component = useComponent();
const name = component.constructor.name; const name = component.constructor.name;
steps.push(`${name}:setup`); logStep(`${name}:setup`);
expect(name + ": " + status(component)).toBe(name + ": " + "new"); expect(name + ": " + status(component)).toBe(name + ": " + "new");
onWillStart(() => { onWillStart(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "new"); expect(name + ": " + status(component)).toBe(name + ": " + "new");
steps.push(`${name}:willStart`); logStep(`${name}:willStart`);
}); });
onMounted(() => { onMounted(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted"); expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
steps.push(`${name}:mounted`); logStep(`${name}:mounted`);
}); });
onWillUpdateProps(() => { onWillUpdateProps(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted"); expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
steps.push(`${name}:willUpdateProps`); logStep(`${name}:willUpdateProps`);
}); });
onWillRender(() => { onWillRender(() => {
steps.push(`${name}:willRender`); logStep(`${name}:willRender`);
}); });
onRendered(() => { onRendered(() => {
steps.push(`${name}:rendered`); logStep(`${name}:rendered`);
}); });
onWillPatch(() => { onWillPatch(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted"); expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
steps.push(`${name}:willPatch`); logStep(`${name}:willPatch`);
}); });
onPatched(() => { onPatched(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted"); expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
steps.push(`${name}:patched`); logStep(`${name}:patched`);
}); });
onWillUnmount(() => { onWillUnmount(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted"); expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
steps.push(`${name}:willUnmount`); logStep(`${name}:willUnmount`);
}); });
onDestroyed(() => { onDestroyed(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "destroyed"); expect(name + ": " + status(component)).toBe(name + ": " + "destroyed");
steps.push(`${name}:destroyed`); logStep(`${name}:destroyed`);
}); });
} }
@@ -226,3 +231,55 @@ export async function editInput(input: HTMLInputElement | HTMLTextAreaElement, v
input.dispatchEvent(new Event("change")); input.dispatchEvent(new Event("change"));
return nextTick(); return nextTick();
} }
import { diff } from "jest-diff";
afterEach(() => {
if (steps.length) {
steps.splice(0);
throw new Error("Remaining steps! Should be checked by a .toBeLogged() assertion!");
}
});
expect.extend({
toBeLogged(expected) {
const options = {
comment: "steps equality",
isNot: this.isNot,
promise: this.promise,
};
const currentSteps = steps.splice(0);
const pass = this.equals(currentSteps, expected);
const message = pass
? () =>
this.utils.matcherHint("toEqual", undefined, undefined, options) +
"\n\n" +
`Expected: not ${this.utils.printExpected(expected)}\n` +
`Received: ${this.utils.printReceived(currentSteps)}`
: () => {
const diffString = diff(expected, currentSteps, {
expand: this.expand,
});
return (
this.utils.matcherHint("toBe", undefined, undefined, options) +
"\n\n" +
(diffString && diffString.includes("- Expect")
? `Difference:\n\n${diffString}`
: `Expected: ${this.utils.printExpected(expected)}\n` +
`Received: ${this.utils.printReceived(currentSteps)}`)
);
};
return { actual: currentSteps, message, pass };
},
});
declare global {
namespace jest {
interface Matchers<R> {
toBeLogged(): R;
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ import {
makeTestFixture, makeTestFixture,
nextMicroTick, nextMicroTick,
nextTick, nextTick,
snapshotEverything, snapshotEverything
} from "./helpers"; } from "./helpers";
function createReactive(value: any, observer: any = () => {}) { function createReactive(value: any, observer: any = () => {}) {