mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] *: re-add a bunch of tests
This commit is contained in:
committed by
Aaron Bohy
parent
b6eb4d009e
commit
ced5d0f69f
@@ -201,6 +201,41 @@ exports[`Portal portal could have dynamically no content 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Portal portal destroys on crash 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/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['props'].error&&this.will.crash;
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Portal portal destroys on crash 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 assign = Object.assign;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`Child\`, {error: ctx['state'].error}, key + \`__3\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Portal portal with child and props 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
//import { AsyncRoot } from "../../src/misc/async_root";
|
||||
import { useState, mount } from "../../src";
|
||||
import { xml } from "../../src/tags";
|
||||
import { makeDeferred, makeTestFixture, snapshotEverything, nextTick } from "../helpers";
|
||||
import { Component } from "../../src/component/component";
|
||||
|
||||
let fixture: HTMLElement;
|
||||
|
||||
snapshotEverything();
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
});
|
||||
|
||||
describe("Asyncroot", () => {
|
||||
test.skip("delayed component with AsyncRoot component", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<span><t t-esc="props.val"/></span>`;
|
||||
}
|
||||
class AsyncChild extends Child {
|
||||
willUpdateProps() {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<button t-on-click="updateApp">Update App State</button>
|
||||
<div class="children">
|
||||
<Child val="state.val"/>
|
||||
<AsyncRoot>
|
||||
<AsyncChild val="state.val"/>
|
||||
</AsyncRoot>
|
||||
</div>
|
||||
</div>`;
|
||||
static components = { Child, AsyncChild, /*AsyncRoot*/ };
|
||||
state = useState({ val: 0 });
|
||||
|
||||
updateApp() {
|
||||
this.state.val++;
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>0</span><span>0</span>");
|
||||
|
||||
// click on button to increment Parent counter
|
||||
const def = makeDeferred();
|
||||
fixture.querySelector("button")!.click();
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1</span><span>0</span>");
|
||||
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1</span><span>1</span>");
|
||||
});
|
||||
|
||||
test.skip("fast component with AsyncRoot", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<span><t t-esc="props.val"/></span>`;
|
||||
}
|
||||
class AsyncChild extends Child {
|
||||
willUpdateProps() {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<button t-on-click="updateApp">Update App State</button>
|
||||
<div class="children">
|
||||
<AsyncRoot>
|
||||
<Child val="state.val"/>
|
||||
</AsyncRoot>
|
||||
<AsyncChild val="state.val"/>
|
||||
</div>
|
||||
</div>`;
|
||||
static components = { Child, AsyncChild, /*AsyncRoot*/ };
|
||||
state = useState({ val: 0 });
|
||||
|
||||
updateApp() {
|
||||
this.state.val++;
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>0</span><span>0</span>");
|
||||
|
||||
// click on button to increment Parent counter
|
||||
const def = makeDeferred();
|
||||
fixture.querySelector("button")!.click();
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1</span><span>0</span>");
|
||||
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1</span><span>1</span>");
|
||||
});
|
||||
|
||||
test.skip("asyncroot component: mixed re-renderings", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`
|
||||
<span t-on-click="increment">
|
||||
<t t-esc="state.val"/>/<t t-esc="props.val"/>
|
||||
</span>`;
|
||||
state = useState({ val: 0 });
|
||||
|
||||
increment() {
|
||||
this.state.val++;
|
||||
}
|
||||
}
|
||||
class AsyncChild extends Child {
|
||||
willUpdateProps() {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<button t-on-click="updateApp">Update App State</button>
|
||||
<div class="children">
|
||||
<Child val="state.val"/>
|
||||
<AsyncRoot>
|
||||
<AsyncChild val="state.val"/>
|
||||
</AsyncRoot>
|
||||
</div>
|
||||
</div>`;
|
||||
static components = { Child, AsyncChild, /*AsyncRoot*/ };
|
||||
state = useState({ val: 0 });
|
||||
|
||||
updateApp() {
|
||||
this.state.val++;
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>0/0</span><span>0/0</span>");
|
||||
|
||||
// click on button to increment Parent counter
|
||||
const def = makeDeferred();
|
||||
fixture.querySelector("button")!.click();
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>0/1</span><span>0/0</span>");
|
||||
|
||||
// click on each Child to increment their local counter
|
||||
const children = (<HTMLElement>parent.el).querySelectorAll("span");
|
||||
children[0]!.click();
|
||||
await nextTick();
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1/1</span><span>0/0</span>");
|
||||
|
||||
children[1]!.click();
|
||||
await nextTick();
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1/1</span><span>0/0</span>");
|
||||
|
||||
// finalize first re-rendering (coming from the props update)
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1/1</span><span>1/1</span>");
|
||||
});
|
||||
});
|
||||
+70
-276
@@ -379,283 +379,35 @@ describe("Portal", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
// test.skip("portal destroys on crash", async () => {
|
||||
// class Child extends Component {
|
||||
// static template = xml`<span t-esc="props.error and this.will.crash" />`;
|
||||
// state = {};
|
||||
// }
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal, Child };
|
||||
// static template = xml`
|
||||
// <div>
|
||||
// <Portal target="'#outside'" >
|
||||
// <Child error="state.error"/>
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
// state = { error: false };
|
||||
// }
|
||||
test("portal destroys on crash", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<span t-esc="props.error and this.will.crash" />`;
|
||||
state = {};
|
||||
}
|
||||
class Parent extends Component {
|
||||
static components = { Portal, Child };
|
||||
static template = xml`
|
||||
<div>
|
||||
<Portal target="'#outside'" >
|
||||
<Child error="state.error"/>
|
||||
</Portal>
|
||||
</div>`;
|
||||
state = { error: false };
|
||||
}
|
||||
addOutsideDiv(fixture);
|
||||
const parent = await mount(Parent, fixture);
|
||||
parent.state.error = true;
|
||||
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
// parent.state.error = true;
|
||||
|
||||
// let error;
|
||||
// try {
|
||||
// await parent.render();
|
||||
// } catch (e) {
|
||||
// error = e;
|
||||
// }
|
||||
// expect(error).toBeDefined();
|
||||
// const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g
|
||||
// expect(error.message).toMatch(regexp);
|
||||
// });
|
||||
|
||||
// test.skip("portal manual unmount", async () => {
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal };
|
||||
// static template = xml`
|
||||
// <div>
|
||||
// <Portal target="'#outside'">
|
||||
// <span>gloria</span>
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
// }
|
||||
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
|
||||
// expect(outside.innerHTML).toBe("<span>gloria</span>");
|
||||
// expect(parent.el!.innerHTML).toBe("<portal></portal>");
|
||||
|
||||
// parent.unmount();
|
||||
// expect(outside.innerHTML).toBe("");
|
||||
// expect(parent.el!.innerHTML).toBe("<portal><span>gloria</span></portal>");
|
||||
|
||||
// await parent.mount(fixture);
|
||||
// expect(outside.innerHTML).toBe("<span>gloria</span>");
|
||||
// expect(parent.el!.innerHTML).toBe("<portal></portal>");
|
||||
// });
|
||||
|
||||
// test.skip("portal manual unmount with subcomponent", async () => {
|
||||
// expect.assertions(9);
|
||||
// class Child extends Component {
|
||||
// static template = xml`<span>gloria</span>`;
|
||||
// mounted() {
|
||||
// expect(outside.contains(this.el)).toBeTruthy();
|
||||
// }
|
||||
// willUnmount() {
|
||||
// expect(outside.contains(this.el)).toBeTruthy();
|
||||
// }
|
||||
// }
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal, Child };
|
||||
// static template = xml`
|
||||
// <div>
|
||||
// <Portal target="'#outside'">
|
||||
// <Child />
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
// }
|
||||
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
|
||||
// expect(outside.innerHTML).toBe("<span>gloria</span>");
|
||||
// expect(parent.el!.innerHTML).toBe("<portal></portal>");
|
||||
|
||||
// parent.unmount();
|
||||
// expect(outside.innerHTML).toBe("");
|
||||
// expect(parent.el!.innerHTML).toBe("<portal><span>gloria</span></portal>");
|
||||
|
||||
// await parent.mount(fixture);
|
||||
// expect(outside.innerHTML).toBe("<span>gloria</span>");
|
||||
// expect(parent.el!.innerHTML).toBe("<portal></portal>");
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe("Portal: Events handling", () => {
|
||||
// test.skip("events triggered on movable pure node are handled", async () => {
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal };
|
||||
// static template = xml`
|
||||
// <div>
|
||||
// <Portal target="'#outside'">
|
||||
// <span id="trigger-me" t-on-custom="_onCustom" t-esc="state.val"/>
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
// state = useState({ val: "ab" });
|
||||
|
||||
// _onCustom() {
|
||||
// this.state.val = "triggered";
|
||||
// }
|
||||
// }
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
|
||||
// expect(outside.innerHTML).toBe(`<span id="trigger-me">ab</span>`);
|
||||
// outside.querySelector("#trigger-me")!.dispatchEvent(new Event("custom"));
|
||||
// await nextTick();
|
||||
// expect(outside.innerHTML).toBe(`<span id="trigger-me">triggered</span>`);
|
||||
// });
|
||||
|
||||
// test.skip("events triggered on movable owl components are redirected", async () => {
|
||||
// let childInst: Component | null = null;
|
||||
// class Child extends Component {
|
||||
// static template = xml`
|
||||
// <span t-on-custom="_onCustom" t-esc="props.val"/>`;
|
||||
|
||||
// constructor(parent, props) {
|
||||
// super(parent, props);
|
||||
// childInst = this;
|
||||
// }
|
||||
|
||||
// _onCustom() {
|
||||
// this.trigger("custom-portal");
|
||||
// }
|
||||
// }
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal, Child };
|
||||
// static template = xml`
|
||||
// <div t-on-custom-portal="_onCustomPortal">
|
||||
// <Portal target="'#outside'">
|
||||
// <Child val="state.val"/>
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
// state = useState({ val: "ab" });
|
||||
|
||||
// _onCustomPortal() {
|
||||
// this.state.val = "triggered";
|
||||
// }
|
||||
// }
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
|
||||
// expect(outside.innerHTML).toBe(`<span>ab</span>`);
|
||||
// childInst!.trigger("custom");
|
||||
// await nextTick();
|
||||
// expect(outside.innerHTML).toBe(`<span>triggered</span>`);
|
||||
// });
|
||||
|
||||
// test.skip("events triggered on contained movable owl components are redirected", async () => {
|
||||
// const steps: string[] = [];
|
||||
// let childInst: Component | null = null;
|
||||
// class Child extends Component {
|
||||
// static template = xml`
|
||||
// <span t-on-custom="_onCustom"/>`;
|
||||
|
||||
// constructor(parent, props) {
|
||||
// super(parent, props);
|
||||
// childInst = this;
|
||||
// }
|
||||
|
||||
// _onCustom() {
|
||||
// this.trigger("custom-portal");
|
||||
// }
|
||||
// }
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal, Child };
|
||||
// static template = xml`
|
||||
// <div t-on-custom="_handled" t-on-custom-portal="_handled">
|
||||
// <Portal target="'#outside'">
|
||||
// <div>
|
||||
// <Child/>
|
||||
// </div>
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
|
||||
// _handled(ev) {
|
||||
// steps.push(ev.type);
|
||||
// }
|
||||
// }
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
|
||||
// childInst!.trigger("custom");
|
||||
// await nextTick();
|
||||
|
||||
// // This is expected because trigger is synchronous
|
||||
// expect(steps).toMatchObject(["custom-portal", "custom"]);
|
||||
// });
|
||||
|
||||
// test.skip("Dom events are not mapped", async () => {
|
||||
// let childInst: Component | null = null;
|
||||
// const steps: string[] = [];
|
||||
// class Child extends Component {
|
||||
// static template = xml`
|
||||
// <button>child</button>`;
|
||||
|
||||
// constructor(parent, props) {
|
||||
// super(parent, props);
|
||||
// childInst = this;
|
||||
// }
|
||||
// }
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal, Child };
|
||||
// static template = xml`
|
||||
// <div t-on-click="_handled">
|
||||
// <Portal target="'#outside'">
|
||||
// <Child />
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
|
||||
// _handled(ev) {
|
||||
// steps.push(ev.type as string);
|
||||
// }
|
||||
// }
|
||||
// const bodyListener = (ev) => {
|
||||
// steps.push(`body: ${ev.type}`);
|
||||
// };
|
||||
// document.body.addEventListener("click", bodyListener);
|
||||
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
// childInst!.el!.click();
|
||||
|
||||
// expect(steps).toEqual(["body: click"]);
|
||||
// document.body.removeEventListener("click", bodyListener);
|
||||
// });
|
||||
|
||||
// test.skip("Nested portals event propagation", async () => {
|
||||
// const outside2 = document.createElement("div");
|
||||
// outside2.setAttribute("id", "outside2");
|
||||
// fixture.appendChild(outside2);
|
||||
|
||||
// const steps: Array<string> = [];
|
||||
// let childInst: Component | null = null;
|
||||
// class Child2 extends Component {
|
||||
// static template = xml`<div>child2</div>`;
|
||||
// constructor(parent, props) {
|
||||
// super(parent, props);
|
||||
// childInst = this;
|
||||
// }
|
||||
// }
|
||||
// class Child extends Component {
|
||||
// static components = { Portal, Child2 };
|
||||
// static template = xml`
|
||||
// <Portal target="'#outside2'">
|
||||
// <Child2 />
|
||||
// </Portal>`;
|
||||
// }
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal, Child };
|
||||
// static template = xml`
|
||||
// <div t-on-custom='_handled'>
|
||||
// <Portal target="'#outside'">
|
||||
// <Child/>
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
|
||||
// _handled(ev) {
|
||||
// steps.push(`${ev.type} from ${ev.originalComponent.constructor.name}`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
|
||||
// childInst!.trigger("custom");
|
||||
// expect(steps).toEqual(["custom from Child2"]);
|
||||
// });
|
||||
let error;
|
||||
try {
|
||||
await parent.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g
|
||||
expect(error.message).toMatch(regexp);
|
||||
});
|
||||
|
||||
test("portal's parent's env is not polluted", async () => {
|
||||
class Child extends Component {
|
||||
@@ -750,3 +502,45 @@ describe("Portal: UI/UX", () => {
|
||||
expect(document.activeElement === inputReRendered).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Portal: Props validation", () => {
|
||||
test.skip("target is mandatory", async () => {
|
||||
class Parent extends Component {
|
||||
static components = { Portal };
|
||||
static template = xml`
|
||||
<div>
|
||||
<Portal>
|
||||
<div>2</div>
|
||||
</Portal>
|
||||
</div>`;
|
||||
}
|
||||
let error;
|
||||
try {
|
||||
await mount(Parent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Missing props 'target' (component 'Portal')`);
|
||||
});
|
||||
|
||||
test.skip("target is not list", async () => {
|
||||
class Parent extends Component {
|
||||
static components = { Portal };
|
||||
static template = xml`
|
||||
<div>
|
||||
<Portal target="['body']">
|
||||
<div>2</div>
|
||||
</Portal>
|
||||
</div>`;
|
||||
}
|
||||
let error;
|
||||
try {
|
||||
await mount(Parent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Invalid Prop 'target' in component 'Portal'`);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user