From db5c343c33c099d18419c1559c063f371b44024c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=8B=E9=93=84=E8=BF=90?= Date: Wed, 16 Oct 2019 05:08:44 +0800 Subject: [PATCH] test(runtime-dom): add test coverage for v-on runtime guards, fix "exact" guard (#298) --- .../__tests__/transforms/vOn.spec.ts | 13 +++ packages/compiler-dom/src/transforms/vOn.ts | 10 ++- .../__tests__/directives/vOn.spec.ts | 86 +++++++++++++++---- packages/runtime-dom/src/directives/vOn.ts | 6 +- 4 files changed, 91 insertions(+), 24 deletions(-) diff --git a/packages/compiler-dom/__tests__/transforms/vOn.spec.ts b/packages/compiler-dom/__tests__/transforms/vOn.spec.ts index 775de3dc9..e0e75d93e 100644 --- a/packages/compiler-dom/__tests__/transforms/vOn.spec.ts +++ b/packages/compiler-dom/__tests__/transforms/vOn.spec.ts @@ -90,4 +90,17 @@ describe('compiler-dom: transform v-on', () => { }) }) }) + + it('should not wrap keys guard if no key modifier is present', () => { + const [prop] = parseVOnProperties(`
`, { + prefixIdentifiers: true + }) + expect(prop).toMatchObject({ + type: NodeTypes.JS_PROPERTY, + value: { + callee: V_ON_MODIFIERS_GUARD, + arguments: [{ content: '_ctx.test' }, '["exact"]'] + } + }) + }) }) diff --git a/packages/compiler-dom/src/transforms/vOn.ts b/packages/compiler-dom/src/transforms/vOn.ts index 10394f81b..da9f9ac2f 100644 --- a/packages/compiler-dom/src/transforms/vOn.ts +++ b/packages/compiler-dom/src/transforms/vOn.ts @@ -41,15 +41,17 @@ export const transformOn: DirectiveTransform = (dir, node, context) => { value, JSON.stringify(runtimeModifiers.filter(m => m in NOT_KEY_MODIFIERS)) ]) + const keyModifiers = runtimeModifiers.filter(m => !(m in NOT_KEY_MODIFIERS)) if ( + keyModifiers.length && // if event name is dynamic, always wrap with keys guard - key.type === NodeTypes.COMPOUND_EXPRESSION || - !key.isStatic || - key.content.toLowerCase() in KEYBOARD_EVENTS + (key.type === NodeTypes.COMPOUND_EXPRESSION || + !key.isStatic || + key.content.toLowerCase() in KEYBOARD_EVENTS) ) { handler = createCallExpression(context.helper(V_ON_KEYS_GUARD), [ handler, - JSON.stringify(runtimeModifiers.filter(m => !(m in NOT_KEY_MODIFIERS))) + JSON.stringify(keyModifiers) ]) } diff --git a/packages/runtime-dom/__tests__/directives/vOn.spec.ts b/packages/runtime-dom/__tests__/directives/vOn.spec.ts index 336839dda..21e539fc0 100644 --- a/packages/runtime-dom/__tests__/directives/vOn.spec.ts +++ b/packages/runtime-dom/__tests__/directives/vOn.spec.ts @@ -1,5 +1,5 @@ import { patchEvent } from '../../src/modules/events' -import { vOnModifiersGuard } from '@vue/runtime-dom' +import { vOnModifiersGuard, vOnKeysGuard } from '@vue/runtime-dom' function triggerEvent( target: Element, @@ -17,41 +17,93 @@ function triggerEvent( } describe('runtime-dom: v-on directive', () => { - test('it should support stop and prevent', async () => { + test('it should support "stop" and "prevent"', () => { const parent = document.createElement('div') const child = document.createElement('input') parent.appendChild(child) - const childNextValue = { - handler: vOnModifiersGuard(jest.fn(), ['prevent', 'stop']), - options: {} - } + const childNextValue = vOnModifiersGuard(jest.fn(), ['prevent', 'stop']) patchEvent(child, 'click', null, childNextValue, null) - const parentHandler = jest.fn() - const parentNextValue = { handler: parentHandler, options: {} } + const parentNextValue = jest.fn() patchEvent(parent, 'click', null, parentNextValue, null) expect(triggerEvent(child, 'click').defaultPrevented).toBe(true) - expect(parentHandler).not.toBeCalled() + expect(parentNextValue).not.toBeCalled() + }) + + test('it should support "self"', () => { + const parent = document.createElement('div') + const child = document.createElement('input') + parent.appendChild(child) + const fn = jest.fn() + const handler = vOnModifiersGuard(fn, ['self']) + patchEvent(parent, 'click', null, handler, null) + triggerEvent(child, 'click') + expect(fn).not.toBeCalled() }) test('it should support key modifiers and system modifiers', () => { const el = document.createElement('div') const fn = jest.fn() - const nextValue = { - handler: vOnModifiersGuard(fn, ['ctrl', 'esc']), - options: {} - } - patchEvent(el, 'click', null, nextValue, null) - triggerEvent(el, 'click', e => { + //
+ const nextValue = vOnKeysGuard(vOnModifiersGuard(fn, ['ctrl']), ['esc']) + patchEvent(el, 'keyup', null, nextValue, null) + triggerEvent(el, 'keyup', e => (e.key = 'a')) + expect(fn).not.toBeCalled() + triggerEvent(el, 'keyup', e => { e.ctrlKey = false e.key = 'esc' }) expect(fn).not.toBeCalled() - triggerEvent(el, 'click', e => { + triggerEvent(el, 'keyup', e => { e.ctrlKey = true e.key = 'Escape' }) expect(fn).toBeCalled() }) - test('it should support "exact" modifier', () => {}) + test('it should support "exact" modifier', () => { + const el = document.createElement('div') + // Case 1:
+ const fn1 = jest.fn() + const next1 = vOnModifiersGuard(fn1, ['exact']) + patchEvent(el, 'keyup', null, next1, null) + triggerEvent(el, 'keyup') + expect(fn1.mock.calls.length).toBe(1) + triggerEvent(el, 'keyup', e => (e.ctrlKey = true)) + expect(fn1.mock.calls.length).toBe(1) + // Case 2:
+ const fn2 = jest.fn() + const next2 = vOnKeysGuard(vOnModifiersGuard(fn2, ['ctrl', 'exact']), ['a']) + patchEvent(el, 'keyup', null, next2, null) + triggerEvent(el, 'keyup', e => (e.key = 'a')) + expect(fn2).not.toBeCalled() + triggerEvent(el, 'keyup', e => { + e.key = 'a' + e.ctrlKey = true + }) + expect(fn2.mock.calls.length).toBe(1) + triggerEvent(el, 'keyup', e => { + // should not trigger if has other system modifiers + e.key = 'a' + e.ctrlKey = true + e.altKey = true + }) + expect(fn2.mock.calls.length).toBe(1) + }) + + it('should support mouse modifiers', () => { + const buttons = ['left', 'middle', 'right'] as const + const buttonCodes = { left: 0, middle: 1, right: 2 } + buttons.forEach(button => { + const el = document.createElement('div') + const fn = jest.fn() + const handler = vOnModifiersGuard(fn, [button]) + patchEvent(el, 'mousedown', null, handler, null) + buttons.filter(b => b !== button).forEach(button => { + triggerEvent(el, 'mousedown', e => (e.button = buttonCodes[button])) + }) + expect(fn).not.toBeCalled() + triggerEvent(el, 'mousedown', e => (e.button = buttonCodes[button])) + expect(fn).toBeCalled() + }) + }) }) diff --git a/packages/runtime-dom/src/directives/vOn.ts b/packages/runtime-dom/src/directives/vOn.ts index c1d788d52..5ffbcc305 100644 --- a/packages/runtime-dom/src/directives/vOn.ts +++ b/packages/runtime-dom/src/directives/vOn.ts @@ -1,4 +1,4 @@ -const systemModifiers = new Set(['ctrl', 'shift', 'alt', 'meta']) +const systemModifiers = ['ctrl', 'shift', 'alt', 'meta'] type KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent; @@ -16,8 +16,8 @@ const modifierGuards: Record< left: e => 'button' in e && (e as MouseEvent).button !== 0, middle: e => 'button' in e && (e as MouseEvent).button !== 1, right: e => 'button' in e && (e as MouseEvent).button !== 2, - exact: (e, modifiers) => - modifiers!.some(m => systemModifiers.has(m) && (e as any)[`${m}Key`]) + exact: (e, modifiers: string[]) => + systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m)) } export const vOnModifiersGuard = (fn: Function, modifiers: string[]) => {