matchEvent.test.ts121 lines · main
1import { getSequenceManager } from '@tanstack/react-hotkeys'
2import { beforeEach, describe, expect, it, vi } from 'vitest'
3
4import { eventMatchesAnyShortcut } from './matchEvent'
5import type { RegistryDefinations } from './types'
6
7vi.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
16type FakeRegistration = {
17 options: { enabled?: boolean }
18 sequence: string[]
19}
20
21/** Swap the SequenceManager's registrations for the ones we care about. */
22function 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
29const shiftX = new KeyboardEvent('keydown', {
30 key: 'X',
31 code: 'KeyX',
32 shiftKey: true,
33})
34
35const arrowDown = new KeyboardEvent('keydown', {
36 key: 'ArrowDown',
37 code: 'ArrowDown',
38})
39
40const g = new KeyboardEvent('keydown', {
41 key: 'g',
42 code: 'KeyG',
43})
44
45const shiftK = new KeyboardEvent('keydown', {
46 key: 'K',
47 code: 'KeyK',
48 shiftKey: true,
49})
50
51const 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
69describe('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})