mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] tests: component mounting
We re-add some tests for component mounting.
This commit is contained in:
committed by
Aaron Bohy
parent
ebd2e4324f
commit
a0b2551e4a
@@ -0,0 +1,65 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`reactivity in lifecycle can use a state hook 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||||
|
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let d1 = ctx['counter'].value;
|
||||||
|
return block1([d1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`reactivity in lifecycle change state while mounting component 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||||
|
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let d1 = ctx['state'].val;
|
||||||
|
return block1([d1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`reactivity in lifecycle state changes in willUnmount do not trigger rerender 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||||
|
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<span><block-text-0/><block-text-1/></span>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let d1 = ctx['props'].val;
|
||||||
|
let d2 = ctx['state'].n;
|
||||||
|
return block1([d1, d2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`reactivity in lifecycle state changes in willUnmount do not trigger rerender 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||||
|
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2;
|
||||||
|
if (ctx['state'].flag) {
|
||||||
|
b2 = component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
return block1([], [b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
+16
-128
@@ -938,22 +938,23 @@ describe("mount targets", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe.skip("mount special cases", () => {
|
describe.skip("mount special cases", () => {
|
||||||
test("widget can be mounted on different target", async () => {
|
test("component can be mounted on different target", async () => {
|
||||||
// class MyWidget extends Component {
|
// class Comp extends Component {
|
||||||
// static template = xml`<div>Hey</div>`;
|
// static template = xml`<div>Hey</div>`;
|
||||||
// patched() {
|
// setup() {
|
||||||
// throw new Error("patched should not be called");
|
// onPatched(() => {
|
||||||
|
// throw new Error("patched should not be called");
|
||||||
|
// });
|
||||||
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// const div = document.createElement("div");
|
||||||
// const div = document.createElement("div");
|
// const span = document.createElement("span");
|
||||||
// const span = document.createElement("span");
|
// fixture.appendChild(div);
|
||||||
// fixture.appendChild(div);
|
// fixture.appendChild(span);
|
||||||
// fixture.appendChild(span);
|
// const comp = await mount(Comp, div);
|
||||||
// const w = new MyWidget();
|
// expect(fixture.innerHTML).toBe("<div><div>Hey</div></div><span></span>");
|
||||||
// await w.mount(div);
|
// await comp.__owl__.mount(span, );
|
||||||
// expect(fixture.innerHTML).toBe("<div><div>Hey</div></div><span></span>");
|
// expect(fixture.innerHTML).toBe("<div></div><span><div>Hey</div></span>");
|
||||||
// await w.mount(span);
|
|
||||||
// expect(fixture.innerHTML).toBe("<div></div><span><div>Hey</div></span>");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("widget can be mounted on different target, another situation", async () => {
|
test("widget can be mounted on different target, another situation", async () => {
|
||||||
@@ -1017,119 +1018,6 @@ describe.skip("mount special cases", () => {
|
|||||||
// expect(fixture.innerHTML).toBe("<div>Hey</div>");
|
// expect(fixture.innerHTML).toBe("<div>Hey</div>");
|
||||||
// expect(steps).toEqual(["1 resolved", "2 resolved"]);
|
// expect(steps).toEqual(["1 resolved", "2 resolved"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("mounting a destroyed widget", async () => {
|
|
||||||
// class MyWidget extends Component {
|
|
||||||
// static template = xml`<div>Hey</div>`;
|
|
||||||
// }
|
|
||||||
// const w = new MyWidget();
|
|
||||||
// w.destroy(); // because, why not
|
|
||||||
// let error;
|
|
||||||
// try {
|
|
||||||
// await w.mount(fixture);
|
|
||||||
// } catch (e) {
|
|
||||||
// error = e;
|
|
||||||
// }
|
|
||||||
// expect(scheduler.tasks.length).toBe(0);
|
|
||||||
// expect(error).toBeDefined();
|
|
||||||
// expect(error.message).toBe("Cannot mount a destroyed component");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("destroying a sub-component cleans itself from parent's vnode", async () => {
|
|
||||||
// class C1 extends Component {
|
|
||||||
// static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
|
|
||||||
// }
|
|
||||||
// class P extends Component {
|
|
||||||
// static components = { C1 };
|
|
||||||
// static template = xml`<div><div><C1 t-props="state" t-if="state.a"/></div></div>`;
|
|
||||||
// state = {
|
|
||||||
// a: "first",
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// const parent = new P();
|
|
||||||
// await parent.mount(fixture);
|
|
||||||
// expect(fixture.textContent).toBe("first");
|
|
||||||
// parent.unmount();
|
|
||||||
// parent.state.a = "";
|
|
||||||
// parent.mount(fixture);
|
|
||||||
// parent.state.a = "fixed";
|
|
||||||
// await parent.render();
|
|
||||||
// expect(fixture.textContent).toBe("fixed");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("destroying a sub-component cleans itself from parent's vnode, part 2", async () => {
|
|
||||||
// class C1 extends Component {
|
|
||||||
// static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
|
|
||||||
// }
|
|
||||||
// class P extends Component {
|
|
||||||
// static components = { C1 };
|
|
||||||
// static template = xml`<div><div><C1 t-props="state" t-if="state.a"/>some text</div></div>`;
|
|
||||||
// state = {
|
|
||||||
// a: "first",
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// const parent = new P();
|
|
||||||
// await parent.mount(fixture);
|
|
||||||
// expect(fixture.textContent).toBe("firstsome text");
|
|
||||||
// parent.unmount();
|
|
||||||
// parent.state.a = "";
|
|
||||||
// parent.mount(fixture);
|
|
||||||
// parent.state.a = "fixed";
|
|
||||||
// await parent.render();
|
|
||||||
// expect(fixture.textContent).toBe("fixedsome text");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("destroying a sub-component cleans itself from parent's vnode, part 3", async () => {
|
|
||||||
// class C1 extends Component {
|
|
||||||
// static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
|
|
||||||
// }
|
|
||||||
// class C2 extends Component {
|
|
||||||
// static template = xml`<C1 a="props.a"/>`;
|
|
||||||
// static components = { C1 };
|
|
||||||
// }
|
|
||||||
// class P extends Component {
|
|
||||||
// static components = { C2 };
|
|
||||||
// static template = xml`<div><div><C2 t-props="state" t-if="state.a"/></div></div>`;
|
|
||||||
// state = {
|
|
||||||
// a: "first",
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// const parent = new P();
|
|
||||||
// await parent.mount(fixture);
|
|
||||||
// expect(fixture.textContent).toBe("first");
|
|
||||||
// parent.unmount();
|
|
||||||
// parent.state.a = "";
|
|
||||||
// parent.mount(fixture);
|
|
||||||
// parent.state.a = "fixed";
|
|
||||||
// await parent.render();
|
|
||||||
// expect(fixture.textContent).toBe("fixed");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("destroying a sub-component cleans itself from parent's vnode, part 4", async () => {
|
|
||||||
// class C1 extends Component {
|
|
||||||
// static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
|
|
||||||
// }
|
|
||||||
// class C2 extends Component {
|
|
||||||
// static template = xml`<C1 a="props.a"/>`;
|
|
||||||
// static components = { C1 };
|
|
||||||
// }
|
|
||||||
// class P extends Component {
|
|
||||||
// static components = { C2 };
|
|
||||||
// static template = xml`<div><div><C2 t-props="state" t-if="state.a"/>some text</div></div>`;
|
|
||||||
// state = {
|
|
||||||
// a: "first",
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// const parent = new P();
|
|
||||||
// await parent.mount(fixture);
|
|
||||||
// expect(fixture.textContent).toBe("firstsome text");
|
|
||||||
// parent.unmount();
|
|
||||||
// parent.state.a = "";
|
|
||||||
// parent.mount(fixture);
|
|
||||||
// parent.state.a = "fixed";
|
|
||||||
// await parent.render();
|
|
||||||
// expect(fixture.textContent).toBe("fixedsome text");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("support svg components", () => {
|
describe("support svg components", () => {
|
||||||
|
|||||||
@@ -1,77 +1,75 @@
|
|||||||
describe.skip("reactivity in lifecycle", () => {
|
import {
|
||||||
test.skip("can use a state hook", async () => {
|
Component,
|
||||||
/* class Counter extends Component {
|
mount,
|
||||||
|
onPatched,
|
||||||
|
onRender,
|
||||||
|
onWillPatch,
|
||||||
|
onWillUnmount,
|
||||||
|
useState,
|
||||||
|
} from "../../src";
|
||||||
|
import { xml } from "../../src/tags";
|
||||||
|
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
||||||
|
|
||||||
|
let fixture: HTMLElement;
|
||||||
|
|
||||||
|
snapshotEverything();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = makeTestFixture();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("reactivity in lifecycle", () => {
|
||||||
|
test("can use a state hook", async () => {
|
||||||
|
class Counter extends Component {
|
||||||
static template = xml`<div><t t-esc="counter.value"/></div>`;
|
static template = xml`<div><t t-esc="counter.value"/></div>`;
|
||||||
counter = useState({ value: 42 });
|
counter = useState({ value: 42 });
|
||||||
}
|
}
|
||||||
const counter = new Counter();
|
const counter = await mount(Counter, fixture);
|
||||||
await counter.mount(fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("<div>42</div>");
|
expect(fixture.innerHTML).toBe("<div>42</div>");
|
||||||
counter.counter.value = 3;
|
counter.counter.value = 3;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div>3</div>");*/
|
expect(fixture.innerHTML).toBe("<div>3</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("state changes in willUnmount do not trigger rerender", async () => {
|
test("state changes in willUnmount do not trigger rerender", async () => {
|
||||||
// const steps: string[] = [];
|
const steps: string[] = [];
|
||||||
// class Child extends Component {
|
class Child extends Component {
|
||||||
// static template = xml`
|
static template = xml`
|
||||||
// <span><t t-esc="props.val"/><t t-esc="state.n"/></span>
|
<span><t t-esc="props.val"/><t t-esc="state.n"/></span>
|
||||||
// `;
|
`;
|
||||||
// state = useState({ n: 2 });
|
state = useState({ n: 2 });
|
||||||
// __render(f) {
|
setup() {
|
||||||
// steps.push("render");
|
onRender(() => {
|
||||||
// return super.__render(f);
|
steps.push("render");
|
||||||
// }
|
});
|
||||||
// willPatch() {
|
onWillPatch(() => {
|
||||||
// steps.push("willPatch");
|
steps.push("willPatch");
|
||||||
// }
|
});
|
||||||
// patched() {
|
onPatched(() => {
|
||||||
// steps.push("patched");
|
steps.push("patched");
|
||||||
// }
|
});
|
||||||
// willUnmount() {
|
onWillUnmount(() => {
|
||||||
// steps.push("willUnmount");
|
steps.push("willUnmount");
|
||||||
// this.state.n = 3;
|
this.state.n = 3;
|
||||||
// }
|
});
|
||||||
// }
|
}
|
||||||
// class Parent extends Component {
|
}
|
||||||
// static template = xml`
|
class Parent extends Component {
|
||||||
// <div>
|
static template = xml`
|
||||||
// <Child t-if="state.flag" val="state.val"/>
|
<div>
|
||||||
// </div>
|
<Child t-if="state.flag" val="state.val"/>
|
||||||
// `;
|
</div>
|
||||||
// static components = { Child };
|
`;
|
||||||
// state = useState({ val: 1, flag: true });
|
static components = { Child };
|
||||||
// }
|
state = useState({ val: 1, flag: true });
|
||||||
// const widget = await mount(Parent, { target: fixture });
|
}
|
||||||
// expect(steps).toEqual(["render"]);
|
const parent = await mount(Parent, fixture);
|
||||||
// expect(fixture.innerHTML).toBe("<div><span>12</span></div>");
|
expect(steps).toEqual(["render"]);
|
||||||
// widget.state.flag = false;
|
expect(fixture.innerHTML).toBe("<div><span>12</span></div>");
|
||||||
// await nextTick();
|
parent.state.flag = false;
|
||||||
// // we make sure here that no call to __render is done
|
await nextTick();
|
||||||
// expect(steps).toEqual(["render", "willUnmount"]);
|
// we make sure here that no call to __render is done
|
||||||
});
|
expect(steps).toEqual(["render", "willUnmount"]);
|
||||||
|
|
||||||
test("state changes in willUnmount will be applied on remount", async () => {
|
|
||||||
// class TestWidget extends Component {
|
|
||||||
// static template = xml`
|
|
||||||
// <div><t t-esc="state.val"/></div>
|
|
||||||
// `;
|
|
||||||
// state = useState({ val: 1 });
|
|
||||||
// willUnmount() {
|
|
||||||
// this.state.val = 3;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// const widget = new TestWidget();
|
|
||||||
// await widget.mount(fixture);
|
|
||||||
// expect(fixture.innerHTML).toBe("<div>1</div>");
|
|
||||||
// widget.unmount();
|
|
||||||
// expect(fixture.innerHTML).toBe("");
|
|
||||||
// await nextTick(); // wait for changes to be detected before remounting
|
|
||||||
// await widget.mount(fixture);
|
|
||||||
// expect(fixture.innerHTML).toBe("<div>3</div>");
|
|
||||||
// // we want to make sure that there are no remaining tasks left at this point.
|
|
||||||
// expect(Component.scheduler.tasks.length).toBe(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("change state just before mounting component", async () => {
|
test("change state just before mounting component", async () => {
|
||||||
@@ -103,89 +101,24 @@ describe.skip("reactivity in lifecycle", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("change state while mounting component", async () => {
|
test("change state while mounting component", async () => {
|
||||||
// const steps: number[] = [];
|
const steps: any[] = [];
|
||||||
// class TestWidget extends Component {
|
let STATE;
|
||||||
// static template = xml`
|
class Comp extends Component {
|
||||||
// <div><t t-esc="state.val"/></div>
|
static template = xml`
|
||||||
// `;
|
<div><t t-esc="state.val"/></div>
|
||||||
// state = useState({ val: 1 });
|
`;
|
||||||
// __render(f) {
|
state = useState({ val: 1 });
|
||||||
// steps.push(this.state.val);
|
setup() {
|
||||||
// return super.__render(f);
|
STATE = this.state;
|
||||||
// }
|
onRender(() => {
|
||||||
// }
|
steps.push(this.state.val);
|
||||||
// TestWidget.prototype.__render = jest.fn(TestWidget.prototype.__render);
|
});
|
||||||
// TestWidget.prototype.__patch = jest.fn(TestWidget.prototype.__patch);
|
}
|
||||||
// const widget = new TestWidget();
|
}
|
||||||
// let prom = widget.mount(fixture);
|
const prom = mount(Comp, fixture);
|
||||||
// widget.state.val = 2;
|
(STATE as any).val = 2;
|
||||||
// await prom;
|
await prom;
|
||||||
// expect(fixture.innerHTML).toBe("<div>2</div>");
|
expect(steps).toEqual([2]);
|
||||||
// expect(TestWidget.prototype.__render).toHaveBeenCalledTimes(1);
|
expect(fixture.innerHTML).toBe("<div>2</div>");
|
||||||
// // unmount and re-mount, as in this case, willStart won't be called, so it's
|
|
||||||
// // slightly different
|
|
||||||
// widget.unmount();
|
|
||||||
// prom = widget.mount(fixture);
|
|
||||||
// widget.state.val = 3;
|
|
||||||
// await prom;
|
|
||||||
// expect(fixture.innerHTML).toBe("<div>3</div>");
|
|
||||||
// expect(TestWidget.prototype.__render).toHaveBeenCalledTimes(3);
|
|
||||||
// expect(TestWidget.prototype.__patch).toHaveBeenCalledTimes(2);
|
|
||||||
// expect(steps).toEqual([2, 2, 3]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("change state and render while mounted in detached dom", async () => {
|
|
||||||
// class App extends Component {
|
|
||||||
// static template = xml`<div><t t-esc="state.val"/></div>`;
|
|
||||||
// state = useState({ val: 1 });
|
|
||||||
// }
|
|
||||||
// const detachedDiv = document.createElement("div");
|
|
||||||
// const app = await mount(App, { target: detachedDiv });
|
|
||||||
// expect(detachedDiv.innerHTML).toBe("<div>1</div>");
|
|
||||||
// app.state.val = 2;
|
|
||||||
// await nextTick();
|
|
||||||
// expect(detachedDiv.innerHTML).toBe("<div>2</div>");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("destroy and change state after mounted in detached dom", async () => {
|
|
||||||
// class App extends Component {
|
|
||||||
// static template = xml`<div><t t-esc="state.val"/></div>`;
|
|
||||||
// state = useState({ val: 1 });
|
|
||||||
// }
|
|
||||||
// const detachedDiv = document.createElement("div");
|
|
||||||
// const app = await mount(App, { target: detachedDiv });
|
|
||||||
// expect(detachedDiv.innerHTML).toBe("<div>1</div>");
|
|
||||||
// app.destroy();
|
|
||||||
// app.state.val = 2;
|
|
||||||
// await nextTick();
|
|
||||||
// expect(detachedDiv.innerHTML).toBe("");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("change state while component is unmounted", async () => {
|
|
||||||
// let child;
|
|
||||||
// class Child extends Component {
|
|
||||||
// static template = xml`<span t-esc="state.val"/>`;
|
|
||||||
// state = useState({
|
|
||||||
// val: "C1",
|
|
||||||
// });
|
|
||||||
// constructor(parent, props) {
|
|
||||||
// super(parent, props);
|
|
||||||
// child = this;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// class Parent extends Component {
|
|
||||||
// static components = { Child };
|
|
||||||
// static template = xml`<div><t t-esc="state.val"/><Child/></div>`;
|
|
||||||
// state = useState({ val: "P1" });
|
|
||||||
// }
|
|
||||||
// const parent = new Parent();
|
|
||||||
// await parent.mount(fixture);
|
|
||||||
// expect(fixture.innerHTML).toBe("<div>P1<span>C1</span></div>");
|
|
||||||
// parent.unmount();
|
|
||||||
// expect(fixture.innerHTML).toBe("");
|
|
||||||
// parent.state.val = "P2";
|
|
||||||
// child.state.val = "C2";
|
|
||||||
// await parent.mount(fixture);
|
|
||||||
// expect(fixture.innerHTML).toBe("<div>P2<span>C2</span></div>");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user