mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
## Summary Closes [FE-3056](https://linear.app/supabase/issue/FE-3056/add-unit-tests-for-new-shortcut-primitives). Backfills unit coverage for the shortcut primitives added in the recent keyboard-shortcut overhaul — these shipped with no tests. - **`state/shortcuts/formatShortcut.test.ts`** (16 tests) — `hotkeyToKeys`: `Mod → Meta` mapping (case-sensitive, substring-safe), single/multi-modifier combos, named keys, punctuation, edge cases. - **`state/shortcuts/useShortcut.test.tsx`** (26 tests) — mocks `useHotkeySequence`, `useRegisterCommands`, and `useIsShortcutEnabled` to unit-test: sequence/callback wiring (single-step + multi-step G-chords), full option-resolution priority (caller → registry → fallback) for `enabled` / `timeout` / `ignoreInputs`, global-preference AND gating, command-menu registration across all four enable permutations, stable action identity, stale-closure protection via `useLatest`, and badge rendering with/without the "then" separator. - **`components/ui/Shortcut.test.tsx`** (12 tests) — mocks `useShortcut` and `ShortcutTooltip` to verify prop forwarding (including rerender behavior), tooltip positioning props, unset-prop hygiene, and the `asChild` pass-through contract (clicks on the child do not fire `onTrigger`). 54 tests total, all passing. ## Test plan - [x] `pnpm --filter studio test state/shortcuts/formatShortcut.test.ts state/shortcuts/useShortcut.test.tsx components/ui/Shortcut.test.tsx` - [x] `tsc --noEmit` clean across the three test files <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Added comprehensive test coverage for the Shortcut component, verifying rendering, hook integration, and tooltip behavior. * Added test suite for hotkey key formatting, covering plain keys, named keys, modifiers, and edge cases. * Added test suite for the useShortcut hook, validating hotkey sequences, enablement logic, command menu integration, and callback handling. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
100 lines
3.6 KiB
TypeScript
100 lines
3.6 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { hotkeyToKeys } from './formatShortcut'
|
|
|
|
describe('hotkeyToKeys', () => {
|
|
describe('single-step hotkeys', () => {
|
|
it('returns a single letter key unchanged', () => {
|
|
expect(hotkeyToKeys('K')).toEqual(['K'])
|
|
})
|
|
|
|
it('preserves named keys', () => {
|
|
expect(hotkeyToKeys('Enter')).toEqual(['Enter'])
|
|
expect(hotkeyToKeys('Escape')).toEqual(['Escape'])
|
|
expect(hotkeyToKeys('Tab')).toEqual(['Tab'])
|
|
expect(hotkeyToKeys('ArrowUp')).toEqual(['ArrowUp'])
|
|
expect(hotkeyToKeys('ArrowDown')).toEqual(['ArrowDown'])
|
|
expect(hotkeyToKeys('ArrowLeft')).toEqual(['ArrowLeft'])
|
|
expect(hotkeyToKeys('ArrowRight')).toEqual(['ArrowRight'])
|
|
})
|
|
|
|
it('preserves punctuation keys', () => {
|
|
expect(hotkeyToKeys(',')).toEqual([','])
|
|
expect(hotkeyToKeys('.')).toEqual(['.'])
|
|
expect(hotkeyToKeys('/')).toEqual(['/'])
|
|
})
|
|
})
|
|
|
|
describe('Mod mapping', () => {
|
|
it('converts standalone Mod to Meta', () => {
|
|
expect(hotkeyToKeys('Mod')).toEqual(['Meta'])
|
|
})
|
|
|
|
it('converts Mod to Meta when combined with a single key', () => {
|
|
expect(hotkeyToKeys('Mod+K')).toEqual(['Meta', 'K'])
|
|
})
|
|
|
|
it('is case sensitive — only exact "Mod" is converted', () => {
|
|
expect(hotkeyToKeys('mod+K')).toEqual(['mod', 'K'])
|
|
expect(hotkeyToKeys('MOD+K')).toEqual(['MOD', 'K'])
|
|
})
|
|
|
|
it('does not rewrite keys that merely contain "Mod" as a substring', () => {
|
|
expect(hotkeyToKeys('Model')).toEqual(['Model'])
|
|
expect(hotkeyToKeys('Mod+Model')).toEqual(['Meta', 'Model'])
|
|
})
|
|
})
|
|
|
|
describe('modifier combos', () => {
|
|
it('handles Mod+Shift combos', () => {
|
|
expect(hotkeyToKeys('Mod+Shift+M')).toEqual(['Meta', 'Shift', 'M'])
|
|
expect(hotkeyToKeys('Mod+Shift+J')).toEqual(['Meta', 'Shift', 'J'])
|
|
expect(hotkeyToKeys('Mod+Shift+C')).toEqual(['Meta', 'Shift', 'C'])
|
|
})
|
|
|
|
it('preserves modifier order', () => {
|
|
expect(hotkeyToKeys('Shift+Mod+K')).toEqual(['Shift', 'Meta', 'K'])
|
|
expect(hotkeyToKeys('Alt+Mod+K')).toEqual(['Alt', 'Meta', 'K'])
|
|
})
|
|
|
|
it('leaves Ctrl, Alt, Shift, Meta literals untouched', () => {
|
|
expect(hotkeyToKeys('Ctrl+K')).toEqual(['Ctrl', 'K'])
|
|
expect(hotkeyToKeys('Alt+K')).toEqual(['Alt', 'K'])
|
|
expect(hotkeyToKeys('Shift+K')).toEqual(['Shift', 'K'])
|
|
expect(hotkeyToKeys('Meta+K')).toEqual(['Meta', 'K'])
|
|
})
|
|
|
|
it('handles three-modifier combos', () => {
|
|
expect(hotkeyToKeys('Mod+Alt+Shift+K')).toEqual(['Meta', 'Alt', 'Shift', 'K'])
|
|
})
|
|
})
|
|
|
|
describe('named-key combos from the registry', () => {
|
|
it('handles Mod+ArrowUp/Down/Left/Right', () => {
|
|
expect(hotkeyToKeys('Mod+ArrowUp')).toEqual(['Meta', 'ArrowUp'])
|
|
expect(hotkeyToKeys('Mod+ArrowDown')).toEqual(['Meta', 'ArrowDown'])
|
|
expect(hotkeyToKeys('Mod+ArrowLeft')).toEqual(['Meta', 'ArrowLeft'])
|
|
expect(hotkeyToKeys('Mod+ArrowRight')).toEqual(['Meta', 'ArrowRight'])
|
|
})
|
|
|
|
it('handles Mod+Enter and Mod+Escape', () => {
|
|
expect(hotkeyToKeys('Mod+Enter')).toEqual(['Meta', 'Enter'])
|
|
expect(hotkeyToKeys('Mod+Escape')).toEqual(['Meta', 'Escape'])
|
|
})
|
|
|
|
it('handles Mod+. (operation queue toggle / logs reset)', () => {
|
|
expect(hotkeyToKeys('Mod+.')).toEqual(['Meta', '.'])
|
|
})
|
|
})
|
|
|
|
describe('edge cases', () => {
|
|
it('returns a single-element array for empty input', () => {
|
|
expect(hotkeyToKeys('')).toEqual([''])
|
|
})
|
|
|
|
it('preserves empty segments from trailing plus', () => {
|
|
expect(hotkeyToKeys('Mod+')).toEqual(['Meta', ''])
|
|
})
|
|
})
|
|
})
|