mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
217 lines
5.9 KiB
TypeScript
217 lines
5.9 KiB
TypeScript
import { Component, mount, useState } from "../../src";
|
|
import { xml } from "../../src/tags";
|
|
import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
|
|
|
|
let fixture: HTMLElement;
|
|
|
|
snapshotEverything();
|
|
|
|
beforeEach(() => {
|
|
fixture = makeTestFixture();
|
|
});
|
|
|
|
describe("t-component", () => {
|
|
test("t-component works in simple case", async () => {
|
|
class Child extends Component {
|
|
static template = xml`<div>child</div>`;
|
|
setup() {
|
|
useLogLifecycle();
|
|
}
|
|
}
|
|
|
|
class Parent extends Component {
|
|
static template = xml`<t t-component="Child"/>`;
|
|
Child = Child;
|
|
setup() {
|
|
useLogLifecycle();
|
|
}
|
|
}
|
|
|
|
await mount(Parent, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe("<div>child</div>");
|
|
expect([
|
|
"Parent:setup",
|
|
"Parent:willStart",
|
|
"Parent:willRender",
|
|
"Child:setup",
|
|
"Child:willStart",
|
|
"Parent:rendered",
|
|
"Child:willRender",
|
|
"Child:rendered",
|
|
"Child:mounted",
|
|
"Parent:mounted",
|
|
]).toBeLogged();
|
|
});
|
|
|
|
test("switching dynamic component", async () => {
|
|
class ChildA extends Component {
|
|
static template = xml`<div>child a</div>`;
|
|
setup() {
|
|
useLogLifecycle();
|
|
}
|
|
}
|
|
|
|
class ChildB extends Component {
|
|
static template = xml`child b`;
|
|
setup() {
|
|
useLogLifecycle();
|
|
}
|
|
}
|
|
|
|
class Parent extends Component {
|
|
static template = xml`<t t-component="Child"/>`;
|
|
Child = ChildA;
|
|
setup() {
|
|
useLogLifecycle();
|
|
}
|
|
}
|
|
|
|
const parent = await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe("<div>child a</div>");
|
|
expect([
|
|
"Parent:setup",
|
|
"Parent:willStart",
|
|
"Parent:willRender",
|
|
"ChildA:setup",
|
|
"ChildA:willStart",
|
|
"Parent:rendered",
|
|
"ChildA:willRender",
|
|
"ChildA:rendered",
|
|
"ChildA:mounted",
|
|
"Parent:mounted",
|
|
]).toBeLogged();
|
|
|
|
parent.Child = ChildB;
|
|
parent.render();
|
|
await nextTick();
|
|
expect(fixture.innerHTML).toBe("child b");
|
|
expect([
|
|
"Parent:willRender",
|
|
"ChildB:setup",
|
|
"ChildB:willStart",
|
|
"Parent:rendered",
|
|
"ChildB:willRender",
|
|
"ChildB:rendered",
|
|
"Parent:willPatch",
|
|
"ChildA:willUnmount",
|
|
"ChildA:willDestroy",
|
|
"ChildB:mounted",
|
|
"Parent:patched",
|
|
]).toBeLogged();
|
|
});
|
|
|
|
test("can switch between dynamic components without the need for a t-key", async () => {
|
|
class A extends Component {
|
|
static template = xml`<span>child a</span>`;
|
|
}
|
|
class B extends Component {
|
|
static template = xml`<span>child b</span>`;
|
|
}
|
|
class App extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<t t-component="constructor.components[state.child]"/>
|
|
</div>`;
|
|
static components = { A, B };
|
|
|
|
state = useState({ child: "A" });
|
|
}
|
|
const app = await mount(App, fixture);
|
|
expect(fixture.innerHTML).toBe("<div><span>child a</span></div>");
|
|
app.state.child = "B";
|
|
await nextTick();
|
|
expect(fixture.innerHTML).toBe("<div><span>child b</span></div>");
|
|
});
|
|
|
|
test("can use dynamic components (the class) if given", async () => {
|
|
class A extends Component {
|
|
static template = xml`<span>child a</span>`;
|
|
}
|
|
class B extends Component {
|
|
static template = xml`<span>child b</span>`;
|
|
}
|
|
class Parent extends Component {
|
|
static template = xml`<t t-component="myComponent" t-key="state.child"/>`;
|
|
state = useState({
|
|
child: "a",
|
|
});
|
|
get myComponent() {
|
|
return this.state.child === "a" ? A : B;
|
|
}
|
|
}
|
|
const parent = await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe("<span>child a</span>");
|
|
parent.state.child = "b";
|
|
await nextTick();
|
|
expect(fixture.innerHTML).toBe("<span>child b</span>");
|
|
});
|
|
|
|
test("can use dynamic components (the class) if given (with different root tagname)", async () => {
|
|
class A extends Component {
|
|
static template = xml`<span>child a</span>`;
|
|
}
|
|
class B extends Component {
|
|
static template = xml`<div>child b</div>`;
|
|
}
|
|
class Parent extends Component {
|
|
static template = xml`<t t-component="myComponent" t-key="state.child"/>`;
|
|
state = useState({
|
|
child: "a",
|
|
});
|
|
get myComponent() {
|
|
return this.state.child === "a" ? A : B;
|
|
}
|
|
}
|
|
const parent = await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe("<span>child a</span>");
|
|
parent.state.child = "b";
|
|
await nextTick();
|
|
expect(fixture.innerHTML).toBe("<div>child b</div>");
|
|
});
|
|
|
|
test("modifying a sub widget", async () => {
|
|
class Counter extends Component {
|
|
static template = xml`
|
|
<div><t t-esc="state.counter"/><button t-on-click="() => state.counter++">Inc</button></div>`;
|
|
state = useState({
|
|
counter: 0,
|
|
});
|
|
}
|
|
|
|
class ParentWidget extends Component {
|
|
static template = xml`<div><t t-component="Counter"/></div>`;
|
|
|
|
get Counter() {
|
|
return Counter;
|
|
}
|
|
}
|
|
await mount(ParentWidget, fixture);
|
|
expect(fixture.innerHTML).toBe("<div><div>0<button>Inc</button></div></div>");
|
|
const button = fixture.getElementsByTagName("button")[0];
|
|
await button.click();
|
|
await nextTick();
|
|
expect(fixture.innerHTML).toBe("<div><div>1<button>Inc</button></div></div>");
|
|
});
|
|
|
|
test("t-component not on a <t> node", async () => {
|
|
class Child extends Component {
|
|
static template = xml`<span>1</span>`;
|
|
}
|
|
class Parent extends Component {
|
|
static components = { Child };
|
|
static template = xml`<div><div t-component="Child"/></div>`;
|
|
}
|
|
let error: Error;
|
|
try {
|
|
await mount(Parent, fixture);
|
|
} catch (e) {
|
|
error = e as Error;
|
|
}
|
|
expect(error!).toBeDefined();
|
|
expect(error!.message).toBe(
|
|
`Directive 't-component' can only be used on <t> nodes (used on a <div>)`
|
|
);
|
|
});
|
|
});
|