Files
owl/tests/components/t_component.test.ts
T

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>)`
);
});
});