matchEvent.test.ts121 lines · main
| 1 | import { getSequenceManager } from '@tanstack/react-hotkeys' |
| 2 | import { beforeEach, describe, expect, it, vi } from 'vitest' |
| 3 | |
| 4 | import { eventMatchesAnyShortcut } from './matchEvent' |
| 5 | import type { RegistryDefinations } from './types' |
| 6 | |
| 7 | vi.mock('@tanstack/react-hotkeys', async () => { |
| 8 | const actual = |
| 9 | await vi.importActual<typeof import('@tanstack/react-hotkeys')>('@tanstack/react-hotkeys') |
| 10 | return { |
| 11 | ...actual, |
| 12 | getSequenceManager: vi.fn(), |
| 13 | } |
| 14 | }) |
| 15 | |
| 16 | type FakeRegistration = { |
| 17 | options: { enabled?: boolean } |
| 18 | sequence: string[] |
| 19 | } |
| 20 | |
| 21 | /** Swap the SequenceManager's registrations for the ones we care about. */ |
| 22 | function withRegistrations(registrations: FakeRegistration[]) { |
| 23 | const state = new Map(registrations.map((r, i) => [String(i), r])) |
| 24 | vi.mocked(getSequenceManager).mockReturnValue({ |
| 25 | registrations: { state }, |
| 26 | } as unknown as ReturnType<typeof getSequenceManager>) |
| 27 | } |
| 28 | |
| 29 | const shiftX = new KeyboardEvent('keydown', { |
| 30 | key: 'X', |
| 31 | code: 'KeyX', |
| 32 | shiftKey: true, |
| 33 | }) |
| 34 | |
| 35 | const arrowDown = new KeyboardEvent('keydown', { |
| 36 | key: 'ArrowDown', |
| 37 | code: 'ArrowDown', |
| 38 | }) |
| 39 | |
| 40 | const g = new KeyboardEvent('keydown', { |
| 41 | key: 'g', |
| 42 | code: 'KeyG', |
| 43 | }) |
| 44 | |
| 45 | const shiftK = new KeyboardEvent('keydown', { |
| 46 | key: 'K', |
| 47 | code: 'KeyK', |
| 48 | shiftKey: true, |
| 49 | }) |
| 50 | |
| 51 | const registry: RegistryDefinations<string> = { |
| 52 | 'table.shift-x': { |
| 53 | id: 'table.shift-x', |
| 54 | label: 'Toggle row', |
| 55 | sequence: ['Shift+X'], |
| 56 | }, |
| 57 | 'table.arrow-down': { |
| 58 | id: 'table.arrow-down', |
| 59 | label: 'Start navigation (down)', |
| 60 | sequence: ['ArrowDown'], |
| 61 | }, |
| 62 | 'table.chord': { |
| 63 | id: 'table.chord', |
| 64 | label: 'Chord', |
| 65 | sequence: ['G', 'T'], |
| 66 | }, |
| 67 | } |
| 68 | |
| 69 | describe('eventMatchesAnyShortcut', () => { |
| 70 | beforeEach(() => { |
| 71 | vi.clearAllMocks() |
| 72 | }) |
| 73 | |
| 74 | it('returns false when there are no active registrations', () => { |
| 75 | withRegistrations([]) |
| 76 | expect(eventMatchesAnyShortcut(shiftX, registry)).toBe(false) |
| 77 | }) |
| 78 | |
| 79 | it('returns true when an active, enabled shortcut in scope matches', () => { |
| 80 | withRegistrations([{ options: {}, sequence: ['Shift+X'] }]) |
| 81 | expect(eventMatchesAnyShortcut(shiftX, registry)).toBe(true) |
| 82 | }) |
| 83 | |
| 84 | it('returns false when the matching shortcut is disabled', () => { |
| 85 | withRegistrations([{ options: { enabled: false }, sequence: ['ArrowDown'] }]) |
| 86 | expect(eventMatchesAnyShortcut(arrowDown, registry)).toBe(false) |
| 87 | }) |
| 88 | |
| 89 | it('treats enabled: undefined and enabled: true as enabled', () => { |
| 90 | withRegistrations([ |
| 91 | { options: {}, sequence: ['Shift+X'] }, |
| 92 | { options: { enabled: true }, sequence: ['ArrowDown'] }, |
| 93 | ]) |
| 94 | expect(eventMatchesAnyShortcut(arrowDown, registry)).toBe(true) |
| 95 | }) |
| 96 | |
| 97 | it('returns false when the matching registration is not in the target registry', () => { |
| 98 | // 'Shift+K' is registered and matches the event, but our scoped registry |
| 99 | // doesn't include it — so we should not claim a match. |
| 100 | withRegistrations([{ options: {}, sequence: ['Shift+K'] }]) |
| 101 | expect(eventMatchesAnyShortcut(shiftK, registry)).toBe(false) |
| 102 | }) |
| 103 | |
| 104 | it('matches any individual step of a chord sequence', () => { |
| 105 | withRegistrations([{ options: {}, sequence: ['G', 'T'] }]) |
| 106 | expect(eventMatchesAnyShortcut(g, registry)).toBe(true) |
| 107 | }) |
| 108 | |
| 109 | it('returns true if any enabled duplicate matches, even when a disabled one is also present', () => { |
| 110 | withRegistrations([ |
| 111 | { options: { enabled: false }, sequence: ['Shift+X'] }, |
| 112 | { options: {}, sequence: ['Shift+X'] }, |
| 113 | ]) |
| 114 | expect(eventMatchesAnyShortcut(shiftX, registry)).toBe(true) |
| 115 | }) |
| 116 | |
| 117 | it('returns false when no active sequence matches the event', () => { |
| 118 | withRegistrations([{ options: {}, sequence: ['Shift+X'] }]) |
| 119 | expect(eventMatchesAnyShortcut(arrowDown, registry)).toBe(false) |
| 120 | }) |
| 121 | }) |