matchEvent.test.ts 3.6 KB

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