diff --git a/src/blockdom/config.ts b/src/blockdom/config.ts index fc18e286..9e918f70 100644 --- a/src/blockdom/config.ts +++ b/src/blockdom/config.ts @@ -16,12 +16,13 @@ export const config = { // this is the main event handler. Every event handler registered with blockdom // will go through this function, giving it the data registered in the block // and the event - mainEventHandler: (data: any, ev: Event) => { + mainEventHandler: (data: any, ev: Event, currentTarget?: EventTarget | null): boolean => { if (typeof data === "function") { data(ev); } else if (Array.isArray(data)) { data = filterOutModifiersFromData(data).data; data[0](data[1], ev); } + return false; }, }; diff --git a/src/blockdom/events.ts b/src/blockdom/events.ts index 95b4dd0b..a3eb5043 100644 --- a/src/blockdom/events.ts +++ b/src/blockdom/events.ts @@ -9,27 +9,32 @@ interface EventHandlerCreator { export function createEventHandler(rawEvent: string): EventHandlerCreator { const eventName = rawEvent.split(".")[0]; + const capture = rawEvent.includes(".capture"); if (rawEvent.includes(".synthetic")) { - return createSyntheticHandler(eventName); + return createSyntheticHandler(eventName, capture); } else { - return createElementHandler(eventName); + return createElementHandler(eventName, capture); } } // Native listener -function createElementHandler(evName: string): EventHandlerCreator { +function createElementHandler(evName: string, capture: boolean = false): EventHandlerCreator { let eventKey = `__event__${evName}`; + if (capture) { + eventKey = `${eventKey}_capture`; + } + function listener(ev: Event) { const currentTarget = ev.currentTarget; if (!currentTarget || !document.contains(currentTarget as HTMLElement)) return; const data = (currentTarget as any)[eventKey]; if (!data) return; - config.mainEventHandler(data, ev); + config.mainEventHandler(data, ev, currentTarget); } function setup(this: HTMLElement, data: any) { (this as any)[eventKey] = data; - this.addEventListener(evName, listener); + this.addEventListener(evName, listener, { capture }); } function update(this: HTMLElement, data: any) { @@ -41,9 +46,12 @@ function createElementHandler(evName: string): EventHandlerCreator { // Synthetic handler: a form of event delegation that allows placing only one // listener per event type. -function createSyntheticHandler(evName: string): EventHandlerCreator { +function createSyntheticHandler(evName: string, capture: boolean = false): EventHandlerCreator { let eventKey = `__event__synthetic_${evName}`; - setupSyntheticEvent(evName, eventKey); + if (capture) { + eventKey = `${eventKey}_capture`; + } + setupSyntheticEvent(evName, eventKey, capture); function setup(this: HTMLElement, data: any) { (this as any)[eventKey] = data; } @@ -55,7 +63,8 @@ function nativeToSyntheticEvent(eventKey: string, event: Event) { while (dom !== null) { const data = (dom as any)[eventKey]; if (data) { - config.mainEventHandler(data, event); + const stopped = config.mainEventHandler(data, event, dom); + if (stopped) return; } dom = (dom as any).parentNode; } @@ -63,10 +72,10 @@ function nativeToSyntheticEvent(eventKey: string, event: Event) { const CONFIGURED_SYNTHETIC_EVENTS: { [event: string]: boolean } = {}; -function setupSyntheticEvent(evName: string, eventKey: string) { +function setupSyntheticEvent(evName: string, eventKey: string, capture: boolean = false) { if (CONFIGURED_SYNTHETIC_EVENTS[eventKey]) { return; } - document.addEventListener(evName, (event) => nativeToSyntheticEvent(eventKey, event)); + document.addEventListener(evName, (event) => nativeToSyntheticEvent(eventKey, event), { capture }); CONFIGURED_SYNTHETIC_EVENTS[eventKey] = true; } diff --git a/src/component/handler.ts b/src/component/handler.ts index 2fb8b5d1..c6acf616 100644 --- a/src/component/handler.ts +++ b/src/component/handler.ts @@ -1,13 +1,26 @@ import { filterOutModifiersFromData } from "../blockdom/config"; -export function mainEventHandler(data: any, ev: Event) { - if (typeof data === "function") { - data(ev); - } else { - data = filterOutModifiersFromData(data).data; - const ctx = data[0]; +export const mainEventHandler = (data: any, ev: Event, currentTarget?: EventTarget|null) => { + const { data: _data, modifiers } = filterOutModifiersFromData(data); + data = _data; + let stopped = false; + if (modifiers.length) { + let selfMode = false; + const isSelf = ev.target === currentTarget; + for (const mod of modifiers) { + switch (mod) { + case "self": selfMode = true; if (isSelf) { continue; } else { return stopped; }; + case "prevent": if ((selfMode && isSelf) || (!selfMode)) ev.preventDefault(); continue; + case "stop": if ((selfMode && isSelf) || (!selfMode)) ev.stopPropagation() ; stopped = true; continue; + } + } + } + if (typeof data[0] === "function") { + data[0](ev); + } else if (data[0].__owl__) { const method = data[1]; const args = data[2] || []; - ctx.__owl__.component[method](...args, ev); + data[0].__owl__.component[method](...args, ev); } + return stopped; } diff --git a/src/qweb/compiler.ts b/src/qweb/compiler.ts index d74f6923..5c456b8c 100644 --- a/src/qweb/compiler.ts +++ b/src/qweb/compiler.ts @@ -468,12 +468,17 @@ export class QWebCompiler { } } - generateHandlerCode(handler: string, event: string = ""): string { + generateHandlerCode(rawEvent: string, handler: string): string { let args: string = ""; const name: string = handler.replace(/\(.*\)/, function (_args) { args = _args.slice(1, -1); return ""; }); + const modifiers = rawEvent.split(".").slice(1).map(m => `"${m}"`); + let modifiersCode = ""; + if (modifiers.length) { + modifiersCode = `${modifiers.join(",")}, `; + } const isMethodCall = name.match(FNAMEREGEXP); if (isMethodCall) { let handlerFn: string; @@ -484,15 +489,11 @@ export class QWebCompiler { } else { handlerFn = `'${name}'`; } - return `[${event ? `\`${event}\`` + ", " : ""}ctx, ${handlerFn!}]`; + return `[${modifiersCode}ctx, ${handlerFn!}]`; } else { let code = this.captureExpression(handler); code = `{const res = (() => { return ${code} })(); if (typeof res === 'function') { res(e) }}`; - let handlerFn = `(e) => ${code}`; - if (event) { - handlerFn = `[\`${event}\`, ${handlerFn}]`; - } - return handlerFn; + return `[${modifiersCode}(e) => ${code}]`; } } @@ -528,7 +529,7 @@ export class QWebCompiler { // event handlers for (let ev in ast.on) { - const name = this.generateHandlerCode(ast.on[ev]); + const name = this.generateHandlerCode(ev, ast.on[ev]); const idx = block!.insertData(name); attrs[`block-handler-${idx}`] = ev; } diff --git a/tests/blockdom/block_event_handling.test.ts b/tests/blockdom/block_event_handling.test.ts index 14827fc1..d95a8d42 100644 --- a/tests/blockdom/block_event_handling.test.ts +++ b/tests/blockdom/block_event_handling.test.ts @@ -61,6 +61,7 @@ test("simple event handling ", async () => { const [owner, method] = data; owner[method](); } + return false; }; const block = createBlock('
'); diff --git a/tests/components/__snapshots__/basics.test.ts.snap b/tests/components/__snapshots__/basics.test.ts.snap index 9af23e4c..a9216c3c 100644 --- a/tests/components/__snapshots__/basics.test.ts.snap +++ b/tests/components/__snapshots__/basics.test.ts.snap @@ -98,7 +98,7 @@ exports[`basics can be clicked on and updated 1`] = ` return function template(ctx, node, key = \\"\\") { let d1 = ctx['state'].counter; const v1 = ctx['state']; - let d2 = (e) => {const res = (() => { return v1.counter++ })(); if (typeof res === 'function') { res(e) }}; + let d2 = [(e) => {const res = (() => { return v1.counter++ })(); if (typeof res === 'function') { res(e) }}]; return block1([d1, d2]); } }" @@ -598,7 +598,7 @@ exports[`basics rerendering a widget with a sub widget 1`] = ` return function template(ctx, node, key = \\"\\") { let d1 = ctx['state'].counter; const v1 = ctx['state']; - let d2 = (e) => {const res = (() => { return v1.counter++ })(); if (typeof res === 'function') { res(e) }}; + let d2 = [(e) => {const res = (() => { return v1.counter++ })(); if (typeof res === 'function') { res(e) }}]; return block1([d1, d2]); } }" diff --git a/tests/components/__snapshots__/event_handling.test.ts.snap b/tests/components/__snapshots__/event_handling.test.ts.snap index eebd4334..1126700c 100644 --- a/tests/components/__snapshots__/event_handling.test.ts.snap +++ b/tests/components/__snapshots__/event_handling.test.ts.snap @@ -42,7 +42,7 @@ exports[`event handling support for callable expression in event handler 1`] = ` return function template(ctx, node, key = \\"\\") { let d1 = ctx['state'].value; const v1 = ctx['obj']; - let d2 = (e) => {const res = (() => { return v1.onInput })(); if (typeof res === 'function') { res(e) }}; + let d2 = [(e) => {const res = (() => { return v1.onInput })(); if (typeof res === 'function') { res(e) }}]; return block1([d1, d2]); } }" diff --git a/tests/components/__snapshots__/t_component.test.ts.snap b/tests/components/__snapshots__/t_component.test.ts.snap index ec4b3078..7116e703 100644 --- a/tests/components/__snapshots__/t_component.test.ts.snap +++ b/tests/components/__snapshots__/t_component.test.ts.snap @@ -137,7 +137,7 @@ exports[`t-component modifying a sub widget 1`] = ` return function template(ctx, node, key = \\"\\") { let d1 = ctx['state'].counter; const v1 = ctx['state']; - let d2 = (e) => {const res = (() => { return v1.counter++ })(); if (typeof res === 'function') { res(e) }}; + let d2 = [(e) => {const res = (() => { return v1.counter++ })(); if (typeof res === 'function') { res(e) }}]; return block1([d1, d2]); } }" diff --git a/tests/components/__snapshots__/t_on.test.ts.snap b/tests/components/__snapshots__/t_on.test.ts.snap index 03977e39..312a68cf 100644 --- a/tests/components/__snapshots__/t_on.test.ts.snap +++ b/tests/components/__snapshots__/t_on.test.ts.snap @@ -20,7 +20,7 @@ exports[`t-on t-on expression captured in t-foreach 1`] = ` let key1 = ctx['val']; const v1 = ctx['otherState']; const v2 = ctx['iter']; - let d1 = (e) => {const res = (() => { return v1.vals.push(v2+'_'+v2) })(); if (typeof res === 'function') { res(e) }}; + let d1 = [(e) => {const res = (() => { return v1.vals.push(v2+'_'+v2) })(); if (typeof res === 'function') { res(e) }}]; setContextValue(ctx, \\"iter\\", ctx['iter']+1); c_block2[i1] = withKey(block3([d1]), key1); } @@ -50,7 +50,7 @@ exports[`t-on t-on expression in t-foreach 1`] = ` let d2 = ctx['val']+''; const v1 = ctx['otherState']; const v2 = ctx['val']; - let d3 = (e) => {const res = (() => { return v1.vals.push(v2) })(); if (typeof res === 'function') { res(e) }}; + let d3 = [(e) => {const res = (() => { return v1.vals.push(v2) })(); if (typeof res === 'function') { res(e) }}]; c_block2[i1] = withKey(block3([d1, d2, d3]), key1); } let b2 = list(c_block2); @@ -84,7 +84,7 @@ exports[`t-on t-on expression in t-foreach with t-set 1`] = ` const v1 = ctx['otherState']; const v2 = ctx['val']; const v3 = ctx['bossa']; - let d3 = (e) => {const res = (() => { return v1.vals.push(v2+'_'+v3) })(); if (typeof res === 'function') { res(e) }}; + let d3 = [(e) => {const res = (() => { return v1.vals.push(v2+'_'+v3) })(); if (typeof res === 'function') { res(e) }}]; c_block2[i1] = withKey(block3([d1, d2, d3]), key1); } let b2 = list(c_block2); diff --git a/tests/qweb/__snapshots__/event_handling.test.ts.snap b/tests/qweb/__snapshots__/event_handling.test.ts.snap index bf6a05a9..ed328a01 100644 --- a/tests/qweb/__snapshots__/event_handling.test.ts.snap +++ b/tests/qweb/__snapshots__/event_handling.test.ts.snap @@ -256,6 +256,158 @@ exports[`t-on t-on modifiers (native listener) basic support for native listener }" `; +exports[`t-on t-on modifiers (native listener) t-on combined with t-esc 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(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'onClick']; + let d2 = ctx['text']; + return block1([d1, d2]); + } +}" +`; + +exports[`t-on t-on modifiers (native listener) t-on combined with t-raw 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(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'onClick']; + let b2 = html(ctx['html']); + return block1([d1], [b2]); + } +}" +`; + +exports[`t-on t-on modifiers (native listener) t-on with .capture modifier 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(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [\\"capture\\", ctx, 'onCapture']; + let d2 = [ctx, 'doSomething']; + return block1([d1, d2]); + } +}" +`; + +exports[`t-on t-on modifiers (native listener) t-on with empty handler (only modifiers) 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(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [\\"prevent\\", (e) => {const res = (() => { return })(); if (typeof res === 'function') { res(e) }}]; + return block1([d1]); + } +}" +`; + +exports[`t-on t-on modifiers (native listener) t-on with prevent and self modifiers (order matters) 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(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [\\"prevent\\",\\"self\\", ctx, 'onClick']; + return block1([d1]); + } +}" +`; + +exports[`t-on t-on modifiers (native listener) t-on with prevent and/or stop modifiers 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(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [\\"prevent\\", ctx, 'onClickPrevented']; + let d2 = [\\"stop\\", ctx, 'onClickStopped']; + let d3 = [\\"prevent\\",\\"stop\\", ctx, 'onClickPreventedAndStopped']; + return block1([d1, d2, d3]); + } +}" +`; + +exports[`t-on t-on modifiers (native listener) t-on with prevent modifier in t-foreach 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(\`
\`); + let block3 = createBlock(\` Edit \`); + + return function template(ctx, node, key = \\"\\") { + ctx = Object.create(ctx); + const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['projects']); + for (let i1 = 0; i1 < l_block2; i1++) { + ctx[\`project\`] = v_block2[i1]; + let key1 = ctx['project']; + const arg1 = [ctx['project'].id]; + let d1 = [\\"prevent\\", ctx, 'onEdit', arg1]; + let d2 = ctx['project'].name; + c_block2[i1] = withKey(block3([d1, d2]), key1); + } + let b2 = list(c_block2); + return block1([], [b2]); + } +}" +`; + +exports[`t-on t-on modifiers (native listener) t-on with self and prevent modifiers (order matters) 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(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [\\"self\\",\\"prevent\\", ctx, 'onClick']; + return block1([d1]); + } +}" +`; + +exports[`t-on t-on modifiers (native listener) t-on with self modifier 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(\`
\`); + + return function template(ctx, node, key = \\"\\") { + let d1 = [ctx, 'onClick']; + let d2 = [\\"self\\", ctx, 'onClickSelf']; + return block1([d1, d2]); + } +}" +`; + exports[`t-on t-on modifiers (synthetic listener) basic support for synthetic 1`] = ` "function anonymous(bdom, helpers ) { @@ -265,8 +417,8 @@ exports[`t-on t-on modifiers (synthetic listener) basic support for synthetic 1` let block1 = createBlock(\`
\`); return function template(ctx, node, key = \\"\\") { - let d1 = [ctx, 'divClicked']; - let d2 = [ctx, 'btnClicked']; + let d1 = [\\"synthetic\\", ctx, 'divClicked']; + let d2 = [\\"synthetic\\", ctx, 'btnClicked']; return block1([d1, d2]); } }" @@ -282,7 +434,7 @@ exports[`t-on t-on with inline statement (function call) 1`] = ` return function template(ctx, node, key = \\"\\") { const v1 = ctx['state']; - let d1 = (e) => {const res = (() => { return v1.incrementCounter(2) })(); if (typeof res === 'function') { res(e) }}; + let d1 = [(e) => {const res = (() => { return v1.incrementCounter(2) })(); if (typeof res === 'function') { res(e) }}]; return block1([d1]); } }" @@ -298,7 +450,7 @@ exports[`t-on t-on with inline statement 1`] = ` return function template(ctx, node, key = \\"\\") { const v1 = ctx['state']; - let d1 = (e) => {const res = (() => { return v1.counter++ })(); if (typeof res === 'function') { res(e) }}; + let d1 = [(e) => {const res = (() => { return v1.counter++ })(); if (typeof res === 'function') { res(e) }}]; return block1([d1]); } }" @@ -314,7 +466,7 @@ exports[`t-on t-on with inline statement, part 2 1`] = ` return function template(ctx, node, key = \\"\\") { const v1 = ctx['state']; - let d1 = (e) => {const res = (() => { return v1.flag=!v1.flag })(); if (typeof res === 'function') { res(e) }}; + let d1 = [(e) => {const res = (() => { return v1.flag=!v1.flag })(); if (typeof res === 'function') { res(e) }}]; return block1([d1]); } }" @@ -331,7 +483,7 @@ exports[`t-on t-on with inline statement, part 3 1`] = ` return function template(ctx, node, key = \\"\\") { const v1 = ctx['state']; const v2 = ctx['someFunction']; - let d1 = (e) => {const res = (() => { return v1.n=v2(3) })(); if (typeof res === 'function') { res(e) }}; + let d1 = [(e) => {const res = (() => { return v1.n=v2(3) })(); if (typeof res === 'function') { res(e) }}]; return block1([d1]); } }" diff --git a/tests/qweb/event_handling.test.ts b/tests/qweb/event_handling.test.ts index 72f839a4..5546bb62 100644 --- a/tests/qweb/event_handling.test.ts +++ b/tests/qweb/event_handling.test.ts @@ -326,6 +326,217 @@ describe("t-on", () => { 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 = `
+ + + Edit + + +
`; + 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 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-raw", async () => { + expect.assertions(3); + const template = `
`; + const steps: string[] = []; + const owner = { + html: "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)", () => {