Files
owl/tests/components/t_on.test.ts
T
Géry Debongnie c7bd0ab85c [FIX] component: fix wrong behaviour when using t-on on t-component
Before this commit, using t-on on t-component was not correctly
implemented by owl: when more than one component shared the same
parentelement and have an handler with the same name, the second handler
would override the first (using an internal key). With this commit, we
simply recreate event handler for each instance of a catcher block. Note
that it means that using t-on on components is slightly slower now.

Also, this commit fixes an additional issue that was noticed: the
catcher block would not update its internal handler properly, so it
would not behave properly after being patched.

closes #1199
2022-06-03 16:12:56 +02:00

387 lines
12 KiB
TypeScript

import { Component, mount, onMounted, useState, xml } from "../../src/index";
import { elem, logStep, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
import { status } from "../../src/runtime/status";
snapshotEverything();
let fixture: HTMLElement;
beforeEach(() => {
fixture = makeTestFixture();
});
describe("t-on", () => {
test("t-on on destroyed components", async () => {
const steps: string[] = [];
let child: any;
class Child extends Component {
static template = xml`<div t-on-click="onClick"/>`;
setup() {
onMounted(() => {
child = this;
});
}
onClick() {
steps.push("click");
}
}
class Parent extends Component {
static template = xml`<div><Child t-if="state.flag"/></div>`;
static components = { Child };
state = useState({ flag: true });
}
const parent = await mount(Parent, fixture);
let el = elem(child!);
el.click();
expect(steps).toEqual(["click"]);
(parent as any).state.flag = false;
await nextTick();
expect(status(child as any)).toBe("destroyed");
el.click();
expect(steps).toEqual(["click"]);
});
test("t-on expression in t-foreach", async () => {
class Comp extends Component {
static template = xml`
<div>
<div t-foreach="state.values" t-as="val" t-key="val">
<t t-esc="val_index"/>: <t t-esc="val + ''"/>
<button t-on-click="() => otherState.vals.push(val)">Expr</button>
</div>
</div>
`;
state = useState({ values: ["a", "b"] });
otherState = { vals: [] };
}
const comp = await mount(Comp, fixture);
expect(fixture.innerHTML).toBe(
"<div><div>0: a<button>Expr</button></div><div>1: b<button>Expr</button></div></div>"
);
expect(comp.otherState.vals).toStrictEqual([]);
const buttons = fixture.querySelectorAll("button");
buttons[0].click();
buttons[1].click();
expect(comp.otherState.vals).toStrictEqual(["a", "b"]);
});
test("t-on expression in t-foreach with t-set", async () => {
class Comp extends Component {
static template = xml`
<div>
<t t-set="bossa" t-value="'nova'"/>
<div t-foreach="state.values" t-as="val" t-key="val">
<t t-set="bossa" t-value="bossa + '_' + val_index" />
<t t-esc="val_index"/>: <t t-esc="val + ''"/>
<button t-on-click="() => otherState.vals.push(val + '_' + bossa)">Expr</button>
</div>
</div>
`;
state = useState({ values: ["a", "b"] });
otherState = { vals: [] };
}
const comp = await mount(Comp, fixture);
expect(fixture.innerHTML).toBe(
"<div><div>0: a<button>Expr</button></div><div>1: b<button>Expr</button></div></div>"
);
expect(comp.otherState.vals).toStrictEqual([]);
const buttons = fixture.querySelectorAll("button");
buttons[0].click();
buttons[1].click();
expect(comp.otherState.vals).toStrictEqual(["a_nova_0", "b_nova_0_1"]);
});
test("t-on method call in t-foreach", async () => {
class Comp extends Component {
static template = xml`
<div>
<div t-foreach="state.values" t-as="val" t-key="val">
<t t-esc="val_index"/>: <t t-esc="val + ''"/>
<button t-on-click="() => this.addVal(val)">meth call</button>
</div>
</div>
`;
state = useState({ values: ["a", "b"] });
otherState = { vals: new Array<string>() };
addVal(val: string) {
this.otherState.vals.push(val);
}
}
const comp = await mount(Comp, fixture);
expect(fixture.innerHTML).toBe(
"<div><div>0: a<button>meth call</button></div><div>1: b<button>meth call</button></div></div>"
);
expect(comp.otherState.vals).toStrictEqual([]);
const buttons = fixture.querySelectorAll("button");
buttons[0].click();
buttons[1].click();
expect(comp.otherState.vals).toStrictEqual(["a", "b"]);
});
test("t-on expression captured in t-foreach", async () => {
class Comp extends Component {
static template = xml`
<div>
<t t-set="iter" t-value="0" />
<div t-foreach="arr" t-as="val" t-key="val">
<button t-on-click="() => otherState.vals.push(iter + '_' + iter)">expr</button>
<t t-set="iter" t-value="iter + 1" />
</div>
</div>
`;
arr = ["a", "b"];
otherState = { vals: new Array<string>() };
}
const comp = await mount(Comp, fixture);
expect(fixture.innerHTML).toBe(
"<div><div><button>expr</button></div><div><button>expr</button></div></div>"
);
expect(comp.otherState.vals).toStrictEqual([]);
const buttons = fixture.querySelectorAll("button");
buttons[0].click();
buttons[1].click();
expect(comp.otherState.vals).toStrictEqual(["0_0", "1_1"]);
});
test("t-on on components", async () => {
class Child extends Component {
static template = xml`<button t-esc="props.value"/>`;
}
class Parent extends Component {
static template = xml`<Child t-on-click="increment" value="state.value"/>`;
static components = { Child };
state = useState({ value: 1 });
increment() {
this.state.value++;
}
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<button>1</button>");
fixture.querySelector("button")!.click();
await nextTick();
expect(fixture.innerHTML).toBe("<button>2</button>");
});
test("t-on on components, variation", async () => {
class Child extends Component {
static template = xml`<button t-esc="props.value"/>`;
}
class Parent extends Component {
static template = xml`
<div>
<span/>
<Child t-on-click="increment" value="state.value"/>
<p/>
</div>`;
static components = { Child };
state = useState({ value: 1 });
increment() {
this.state.value++;
}
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div><span></span><button>1</button><p></p></div>");
fixture.querySelector("span")!.click();
await nextTick();
expect(fixture.innerHTML).toBe("<div><span></span><button>1</button><p></p></div>");
fixture.querySelector("button")!.click();
await nextTick();
expect(fixture.innerHTML).toBe("<div><span></span><button>2</button><p></p></div>");
fixture.querySelector("p")!.click();
await nextTick();
expect(fixture.innerHTML).toBe("<div><span></span><button>2</button><p></p></div>");
});
test("t-on on component next to t-on on div", async () => {
class Child extends Component {
static template = xml`<button t-esc="props.value"/>`;
}
class Parent extends Component {
static template = xml`
<div>
<Child t-on-click="increment" value="state.value"/>
<p t-on-click="decrement">dec</p>
</div>`;
static components = { Child };
state = useState({ value: 1 });
increment() {
this.state.value++;
}
decrement() {
this.state.value--;
}
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div><button>1</button><p>dec</p></div>");
fixture.querySelector("button")!.click();
await nextTick();
expect(fixture.innerHTML).toBe("<div><button>2</button><p>dec</p></div>");
fixture.querySelector("p")!.click();
await nextTick();
expect(fixture.innerHTML).toBe("<div><button>1</button><p>dec</p></div>");
});
test("t-on on t-slots", async () => {
class Child extends Component {
static template = xml`
[<t t-esc="state.count"/>]
<t t-slot="default" t-on-click="() => this.state.count++"/>`;
state = useState({ count: 0 });
}
class Parent extends Component {
static template = xml`
<Child>
<p>something</p>
</Child>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe(" [0] <p>something</p>");
fixture.querySelector("p")!.click();
await nextTick();
expect(fixture.innerHTML).toBe(" [1] <p>something</p>");
});
test("t-on on t-set-slots", async () => {
class Child extends Component {
static template = xml`<t t-slot="myslot"/>`;
}
class Parent extends Component {
static template = xml`
[<t t-esc="state.count"/>]
<Child>
<t t-set-slot="myslot" t-on-click="() => this.state.count++">
<p>something</p>
<p>paragraph</p>
</t>
</Child>`;
static components = { Child };
state = useState({ count: 0 });
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe(" [0] <p>something</p><p>paragraph</p>");
fixture.querySelector("p")!.click();
await nextTick();
expect(fixture.innerHTML).toBe(" [1] <p>something</p><p>paragraph</p>");
});
test("t-on on components, with 'prevent' modifier", async () => {
expect.assertions(4); // 2 snaps and 2 expects
class Child extends Component {
static template = xml`<button t-esc="props.value"/>`;
}
class Parent extends Component {
static template = xml`<Child t-on-click.prevent="increment" value="state.value"/>`;
static components = { Child };
state = useState({ value: 1 });
increment(ev: MouseEvent) {
expect(ev.defaultPrevented).toBe(true);
this.state.value++;
}
}
await mount(Parent, fixture);
fixture.querySelector("button")!.click();
await nextTick();
expect(fixture.innerHTML).toBe("<button>2</button>");
});
test("t-on on slot, with 'prevent' modifier", async () => {
class Child extends Component {
static template = xml`<t t-slot="default" t-on-click.prevent="doSomething"/>`;
doSomething(ev: MouseEvent) {
expect(ev.defaultPrevented).toBe(true);
logStep("hey");
}
}
class Parent extends Component {
static template = xml`
<Child>
<button>button</button>
</Child>`;
static components = { Child };
}
await mount(Parent, fixture);
fixture.querySelector("button")!.click();
await nextTick();
expect(fixture.innerHTML).toBe("<button>button</button>");
expect(["hey"]).toBeLogged();
});
test("t-on on components and t-foreach", async () => {
class Child extends Component {
static template = xml`<div t-esc="props.value"/>`;
}
class Parent extends Component {
static template = xml`
<t t-foreach="['John', 'Raoul', 'Gérald']" t-as="name" t-key="name">
<Child t-on-click="() => this.log(name)" value="name"/>
</t>`;
static components = { Child };
log(name: string) {
logStep(name);
}
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div>John</div><div>Raoul</div><div>Gérald</div>");
expect([]).toBeLogged();
fixture.querySelectorAll("div")[0].click();
await nextTick();
expect(["John"]).toBeLogged();
fixture.querySelectorAll("div")[1].click();
await nextTick();
expect(["Raoul"]).toBeLogged();
fixture.querySelectorAll("div")[2].click();
await nextTick();
expect(["Gérald"]).toBeLogged();
});
test("t-on on components, with a handler update", async () => {
class Child extends Component {
static template = xml`<div t-esc="props.value"/>`;
}
class Parent extends Component {
static template = xml`
<t t-set="name" t-value="state.name"/>
<Child value="name" t-on-click="() => this.log(name)"
/>`;
static components = { Child };
state = useState({ name: "aaron" });
log(name: string) {
logStep(name);
}
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div>aaron</div>");
expect([]).toBeLogged();
fixture.querySelectorAll("div")[0].click();
await nextTick();
expect(["aaron"]).toBeLogged();
parent.state.name = "lucas";
await nextTick();
expect(fixture.innerHTML).toBe("<div>lucas</div>");
fixture.querySelectorAll("div")[0].click();
await nextTick();
expect(["lucas"]).toBeLogged();
});
});