import { TemplateSet } from "../../src/runtime/template_set"; import { mount } from "../../src/runtime/blockdom"; import { makeTestFixture, renderToBdom, renderToString, snapshotEverything } from "../helpers"; import { markup } from "../../src/runtime/utils"; import { STATUS } from "../../src/runtime/status"; snapshotEverything(); // ----------------------------------------------------------------------------- // t-on // ----------------------------------------------------------------------------- describe("t-on", () => { function mountToFixture(template: string, ctx: any = {}, node?: any): HTMLDivElement { if (!node) { node = { component: ctx, status: STATUS.MOUNTED }; ctx.__owl__ = node; } const block = renderToBdom(template, ctx, node); const fixture = makeTestFixture(); mount(block, fixture); return fixture; } test("can bind event handler", () => { const template = ``; let a = 1; const fixture = mountToFixture(template, { add: () => (a = 3) }); expect(a).toBe(1); fixture.querySelector("button")!.click(); expect(a).toBe(3); }); test("receive event in first argument", () => { expect.assertions(2); const template = ``; const fixture = mountToFixture(template, { add: (ev: any) => { expect(ev).toBeInstanceOf(Event); }, }); fixture.querySelector("button")!.click(); }); test("can bind two event handlers", () => { const template = ` `; let steps: string[] = []; const fixture = mountToFixture(template, { handleClick() { steps.push("click"); }, handleDblClick() { steps.push("dblclick"); }, }); expect(steps).toEqual([]); fixture.querySelector("button")!.click(); expect(steps).toEqual(["click"]); fixture.querySelector("button")!.dispatchEvent(new Event("dblclick", { bubbles: true })); expect(steps).toEqual(["click", "dblclick"]); }); test("can bind handlers with arguments", () => { const template = ``; let a = 1; const fixture = mountToFixture(template, { add: (n: number) => (a = a + n) }); expect(a).toBe(1); fixture.querySelector("button")!.click(); expect(a).toBe(6); }); test("can bind handlers with object arguments", () => { const template = ``; let a = 1; const fixture = mountToFixture(template, { add: ({ val }: any) => (a = a + val) }); expect(a).toBe(1); fixture.querySelector("button")!.click(); expect(a).toBe(6); }); test("can bind handlers with empty object", () => { expect.assertions(2); const template = ``; const fixture = mountToFixture(template, { doSomething(arg: any) { expect(arg).toEqual({}); }, }); fixture.querySelector("button")!.click(); }); test("can bind handlers with empty object (with non empty inner string)", () => { expect.assertions(2); const template = ``; const fixture = mountToFixture(template, { doSomething(arg: any) { expect(arg).toEqual({}); }, }); fixture.querySelector("button")!.click(); }); test("can bind handlers with empty object (with non empty inner string)", () => { expect.assertions(2); const template = ` `; const fixture = mountToFixture(template, { activate(action: string) { expect(action).toBe("someval"); }, }); fixture.querySelector("a")!.click(); }); test("handler is bound to proper owner", () => { expect.assertions(2); const template = ``; let owner = { add() { expect(this).toBe(owner); }, }; const fixture = mountToFixture(template, owner); fixture.querySelector("button")!.click(); }); test("handler is bound to proper owner, part 2", () => { expect.assertions(2); const template = ` `; let owner = { add() { expect(this).toBe(owner); }, }; const fixture = mountToFixture(template, owner); fixture.querySelector("button")!.click(); }); test("handler is bound to proper owner, part 3", () => { expect.assertions(3); const context = new TemplateSet(); const sub = ``; const main = ``; context.addTemplate("sub", sub); context.addTemplate("main", main); let owner: any = { add() { expect(this).toBe(owner); }, }; const node = { component: owner, status: STATUS.MOUNTED }; owner.__owl__ = node; const fixture = makeTestFixture(); const render = context.getTemplate("main"); const ctx = Object.create(owner); ctx.this = owner; const bdom = render.call(owner, ctx, node); mount(bdom, fixture); fixture.querySelector("button")!.click(); }); test("handler is bound to proper owner, part 4", () => { expect.assertions(3); const context = new TemplateSet(); const sub = ``; const main = ` `; context.addTemplate("sub", sub); context.addTemplate("main", main); let owner: any = { add() { expect(this).toBe(owner); }, }; const node = { component: owner, status: STATUS.MOUNTED }; owner.__owl__ = node; const fixture = makeTestFixture(); const render = context.getTemplate("main"); const ctx = Object.create(owner); ctx.this = owner; const bdom = render.call(owner, ctx, node); mount(bdom, fixture); fixture.querySelector("button")!.click(); }); test("t-on with inline statement", () => { const template = ``; let owner = { state: { counter: 0 } }; const fixture = mountToFixture(template, owner); expect(owner.state.counter).toBe(0); fixture.querySelector("button")!.click(); expect(owner.state.counter).toBe(1); }); test("t-on with inline statement (function call)", () => { const template = ``; let owner = { state: { counter: 0, incrementCounter: (inc: number) => { owner.state.counter += inc; }, }, }; const fixture = mountToFixture(template, owner); expect(owner.state.counter).toBe(0); fixture.querySelector("button")!.click(); expect(owner.state.counter).toBe(2); }); test("t-on with inline statement, part 2", () => { const template = ``; let owner = { state: { flag: true, }, }; const fixture = mountToFixture(template, owner); expect(owner.state.flag).toBe(true); fixture.querySelector("button")!.click(); expect(owner.state.flag).toBe(false); fixture.querySelector("button")!.click(); expect(owner.state.flag).toBe(true); }); test("t-on with inline statement, part 3", () => { const template = ``; let owner = { someFunction(n: number) { return n + 1; }, state: { n: 11, }, }; const fixture = mountToFixture(template, owner); expect(owner.state.n).toBe(11); fixture.querySelector("button")!.click(); expect(owner.state.n).toBe(4); }); test("t-on with t-call", async () => { expect.assertions(3); const app = new TemplateSet(); const sub = `

lucas

`; const main = `
`; app.addTemplate("sub", sub); app.addTemplate("main", main); let owner: any = { update() { expect(this).toBe(owner); }, }; const node = { component: owner, status: STATUS.MOUNTED }; owner.__owl__ = node; const fixture = makeTestFixture(); const render = app.getTemplate("main"); const ctx = Object.create(owner); ctx.this = owner; const bdom = render.call(owner, ctx, node); mount(bdom, fixture); fixture.querySelector("p")!.click(); }); test("t-on, with arguments and t-call", async () => { expect.assertions(4); const app = new TemplateSet(); const sub = `

lucas

`; const main = `
`; app.addTemplate("sub", sub); app.addTemplate("main", main); let owner: any = { update(val: number) { expect(this).toBe(owner); expect(val).toBe(444); }, get this() { return owner; }, value: 444, }; const node = { component: owner, status: STATUS.MOUNTED }; owner.__owl__ = node; const fixture = makeTestFixture(); const render = app.getTemplate("main"); const bdom = render.call(owner, owner, node); mount(bdom, fixture); fixture.querySelector("p")!.click(); }); test("nice error when t-on is evaluated with a missing event", () => { const template = `
`; expect(() => renderToString(template, { someMethod() {} })).toThrow( "Missing event name with t-on directive" ); }); describe("t-on modifiers (native listener)", () => { test("basic support for native listener", () => { const template = `
`; const steps: string[] = []; const owner = { divClicked(ev: Event) { expect(ev.currentTarget).toBe(div); steps.push("divClicked"); }, btnClicked(ev: Event) { expect(ev.currentTarget).toBe(button); steps.push("btnClicked"); }, }; const node = mountToFixture(template, owner); const div = node.querySelector(".myClass"); const button = (node).getElementsByTagName("button")[0]; button.click(); expect(steps).toEqual(["btnClicked", "divClicked"]); }); test("t-on with prevent and/or stop modifiers", async () => { expect.assertions(7); const template = `
`; let owner = { onClickPrevented(e: Event) { expect(e.defaultPrevented).toBe(true); expect(e.cancelBubble).toBe(false); }, onClickStopped(e: Event) { expect(e.defaultPrevented).toBe(false); expect(e.cancelBubble).toBe(true); }, onClickPreventedAndStopped(e: Event) { expect(e.defaultPrevented).toBe(true); expect(e.cancelBubble).toBe(true); }, }; const node = mountToFixture(template, owner); const buttons = (node).getElementsByTagName("button"); buttons[0].click(); buttons[1].click(); buttons[2].click(); }); test("t-on with self modifier", async () => { expect.assertions(2); const template = `
`; let steps: string[] = []; let owner = { onClick(e: Event) { steps.push("onClick"); }, onClickSelf(e: Event) { steps.push("onClickSelf"); }, }; const node = mountToFixture(template, owner); const buttons = (node).getElementsByTagName("button"); const spans = (node).getElementsByTagName("span"); spans[0].click(); spans[1].click(); buttons[0].click(); buttons[1].click(); expect(steps).toEqual(["onClick", "onClick", "onClickSelf"]); }); test("t-on with self and prevent modifiers (order matters)", async () => { expect.assertions(2); const template = `
`; let steps: boolean[] = []; let owner = { onClick() {}, }; const node = mountToFixture(template, owner); (node).addEventListener("click", function (e) { steps.push(e.defaultPrevented); }); const button = (node).getElementsByTagName("button")[0]; const span = (node).getElementsByTagName("span")[0]; span.click(); button.click(); expect(steps).toEqual([false, true]); }); test("t-on with prevent and self modifiers (order matters)", async () => { expect.assertions(2); const template = `
`; let steps: boolean[] = []; let owner = { onClick() {}, }; const node = mountToFixture(template, owner); (node).addEventListener("click", function (e) { steps.push(e.defaultPrevented); }); const button = (node).getElementsByTagName("button")[0]; const span = (node).getElementsByTagName("span")[0]; span.click(); button.click(); expect(steps).toEqual([true, true]); }); test("t-on with prevent modifier in t-foreach", async () => { expect.assertions(5); const template = ``; const steps: string[] = []; const owner = { projects: [ { id: 1, name: "Project 1" }, { id: 2, name: "Project 2" }, ], onEdit(projectId: string, ev: Event) { expect(ev.defaultPrevented).toBe(true); steps.push(projectId); }, }; const node = mountToFixture(template, owner); expect(node.innerHTML).toBe( `` ); const links = node.querySelectorAll("a")!; links[0].click(); links[1].click(); expect(steps).toEqual([1, 2]); }); test("t-on with empty handler (only modifiers)", () => { expect.assertions(2); const template = `
`; const node = mountToFixture(template, {}); node.addEventListener("click", (e) => { expect(e.defaultPrevented).toBe(true); }); const button = (node).getElementsByTagName("button")[0]; button.click(); }); test("t-on crashes when used with unknown modifier", async () => { const template = `
`; let owner = { onClick(e: Event) {} }; expect(() => mountToFixture(template, owner)).toThrowError("Unknown event modifier"); }); test("t-on combined with t-esc", async () => { expect.assertions(3); const template = `
`; const steps: string[] = []; const owner = { text: "Click here", onClick() { steps.push("onClick"); }, }; const node = mountToFixture(template, owner); expect(node.innerHTML).toBe(`
`); node.querySelector("button")!.click(); expect(steps).toEqual(["onClick"]); }); test("t-on combined with t-out", async () => { expect.assertions(3); const template = `
`; const steps: string[] = []; const owner = { html: markup("Click here"), onClick() { steps.push("onClick"); }, }; const node = mountToFixture(template, owner); expect(node.innerHTML).toBe(`
`); node.querySelector("button")!.click(); expect(steps).toEqual(["onClick"]); }); test("t-on with .capture modifier", () => { expect.assertions(2); const template = `
`; const steps: string[] = []; const owner = { onCapture() { steps.push("captured"); }, doSomething() { steps.push("normal"); }, }; const node = mountToFixture(template, owner); const button = (node).getElementsByTagName("button")[0]; button.click(); expect(steps).toEqual(["captured", "normal"]); }); }); describe("t-on modifiers (synthetic listener)", () => { test("basic support for synthetic", () => { const template = `
`; const steps: string[] = []; const owner = { divClicked(ev: Event) { expect(ev.currentTarget).toBe(document); steps.push("divClicked"); }, btnClicked(ev: Event) { expect(ev.currentTarget).toBe(document); steps.push("btnClicked"); }, }; const node = mountToFixture(template, owner); const button = (node).getElementsByTagName("button")[0]; button.click(); expect(steps).toEqual(["btnClicked", "divClicked"]); }); }); });