ShortcutsReferenceSheet.test.tsx288 lines · main
1// @ts-nocheck
2import type { HotkeyRegistrationView, SequenceRegistrationView } from '@tanstack/react-hotkeys'
3import { screen } from '@testing-library/react'
4import userEvent from '@testing-library/user-event'
5import { beforeEach, describe, expect, it, vi } from 'vitest'
6
7import { ShortcutsReferenceSheet } from './ShortcutsReferenceSheet'
8import { SHORTCUT_DEFINITIONS, SHORTCUT_IDS, type ShortcutId } from '@/state/shortcuts/registry'
9import type { ShortcutHotkeyMeta } from '@/state/shortcuts/useShortcut'
10import { customRender } from '@/tests/lib/custom-render'
11
12const { mockUseHotkeyRegistrations } = vi.hoisted(() => ({
13 mockUseHotkeyRegistrations:
14 vi.fn<() => { hotkeys: HotkeyRegistrationView[]; sequences: SequenceRegistrationView[] }>(),
15}))
16
17vi.mock('@tanstack/react-hotkeys', async () => {
18 const actual =
19 await vi.importActual<typeof import('@tanstack/react-hotkeys')>('@tanstack/react-hotkeys')
20 return {
21 ...actual,
22 useHotkeyRegistrations: mockUseHotkeyRegistrations,
23 }
24})
25
26const ACTIVE_SHORTCUT_IDS = [
27 SHORTCUT_IDS.COMMAND_MENU_OPEN,
28 SHORTCUT_IDS.NAV_HOME,
29] satisfies ShortcutId[]
30
31const ACTIVE_DATABASE_SHORTCUT_IDS = [
32 ...ACTIVE_SHORTCUT_IDS,
33 SHORTCUT_IDS.NAV_DATABASE_TABLES,
34] satisfies ShortcutId[]
35
36const ACTIVE_AUTH_SHORTCUT_IDS = [
37 ...ACTIVE_SHORTCUT_IDS,
38 SHORTCUT_IDS.NAV_AUTH_USERS,
39] satisfies ShortcutId[]
40
41const ACTIVE_FUNCTION_DETAIL_NAV_SHORTCUT_IDS = [
42 ...ACTIVE_SHORTCUT_IDS,
43 SHORTCUT_IDS.NAV_FUNCTION_DETAIL_OVERVIEW,
44] satisfies ShortcutId[]
45
46const ACTIVE_REALTIME_NAV_SHORTCUT_IDS = [
47 ...ACTIVE_SHORTCUT_IDS,
48 SHORTCUT_IDS.NAV_REALTIME_INSPECTOR,
49] satisfies ShortcutId[]
50
51const ACTIVE_REALTIME_INSPECTOR_SHORTCUT_IDS = [
52 ...ACTIVE_SHORTCUT_IDS,
53 SHORTCUT_IDS.INSPECTOR_JOIN_CHANNEL,
54 SHORTCUT_IDS.INSPECTOR_TOGGLE_LISTENING,
55 SHORTCUT_IDS.INSPECTOR_BROADCAST,
56 SHORTCUT_IDS.INSPECTOR_COPY_MESSAGE,
57] satisfies ShortcutId[]
58
59const ACTIVE_SURFACE_SHORTCUT_IDS = [
60 ...ACTIVE_SHORTCUT_IDS,
61 SHORTCUT_IDS.AUTH_USERS_REFRESH,
62 SHORTCUT_IDS.FUNCTION_DETAIL_OPEN_TEST,
63 SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_15MIN,
64 SHORTCUT_IDS.FUNCTIONS_LIST_REFRESH,
65 SHORTCUT_IDS.LOGS_PREVIEW_REFRESH,
66 SHORTCUT_IDS.SQL_EDITOR_FORMAT,
67 SHORTCUT_IDS.STORAGE_BUCKETS_REFRESH,
68 SHORTCUT_IDS.STORAGE_EXPLORER_REFRESH,
69] satisfies ShortcutId[]
70
71let sequenceIdCounter = 0
72
73const buildSequenceRegistration = (id: ShortcutId): SequenceRegistrationView => {
74 const definition = SHORTCUT_DEFINITIONS[id]
75 const meta: ShortcutHotkeyMeta = {
76 id: definition.id,
77 name: definition.label,
78 referenceGroup: definition.referenceGroup,
79 }
80
81 return {
82 id: `sequence_${++sequenceIdCounter}`,
83 sequence: definition.sequence,
84 options: {
85 enabled: true,
86 meta,
87 },
88 target: document,
89 triggerCount: 0,
90 hasFired: false,
91 matchedStepCount: 0,
92 partialMatchLastKeyTime: 0,
93 }
94}
95
96const seedRegistrations = (ids: ShortcutId[]) => {
97 mockUseHotkeyRegistrations.mockReturnValue({
98 hotkeys: [],
99 sequences: ids.map(buildSequenceRegistration),
100 })
101}
102
103const renderShortcutsReferenceSheet = (ids: ShortcutId[] = ACTIVE_SHORTCUT_IDS) => {
104 seedRegistrations(ids)
105 const onOpenChange = vi.fn()
106
107 customRender(<ShortcutsReferenceSheet open onOpenChange={onOpenChange} />)
108
109 return { onOpenChange }
110}
111
112describe('ShortcutsReferenceSheet', () => {
113 beforeEach(() => {
114 sequenceIdCounter = 0
115 mockUseHotkeyRegistrations.mockReset()
116 mockUseHotkeyRegistrations.mockReturnValue({ hotkeys: [], sequences: [] })
117 })
118
119 it('renders the grouped shortcut list by default', async () => {
120 renderShortcutsReferenceSheet()
121
122 expect(await screen.findByText('Keyboard shortcuts')).toBeInTheDocument()
123 expect(screen.getByLabelText('Search shortcuts')).toBeInTheDocument()
124 expect(screen.getByText('Command Menu')).toBeInTheDocument()
125 expect(screen.getByText('Navigation')).toBeInTheDocument()
126 expect(screen.queryByText('Global Navigation')).not.toBeInTheDocument()
127 expect(screen.queryByText('Database Navigation')).not.toBeInTheDocument()
128 expect(screen.getByText('Open command menu')).toBeInTheDocument()
129 expect(screen.getByText('Go to Project Overview')).toBeInTheDocument()
130 })
131
132 it('shows only active shortcuts in a group when the group label matches the search', async () => {
133 const user = userEvent.setup()
134
135 renderShortcutsReferenceSheet()
136
137 await user.type(screen.getByLabelText('Search shortcuts'), 'navigation')
138
139 expect(screen.getByText('Navigation')).toBeInTheDocument()
140 expect(screen.getByText('Go to Project Overview')).toBeInTheDocument()
141 expect(screen.queryByText('Command Menu')).not.toBeInTheDocument()
142 expect(screen.queryByText('Go to Database')).not.toBeInTheDocument()
143 })
144
145 it('keeps the parent group header when only an item label matches', async () => {
146 const user = userEvent.setup()
147
148 renderShortcutsReferenceSheet()
149
150 await user.type(screen.getByLabelText('Search shortcuts'), 'Go to Project Overview')
151
152 expect(screen.getByText('Navigation')).toBeInTheDocument()
153 expect(screen.getByText('Go to Project Overview')).toBeInTheDocument()
154 expect(screen.queryByText('Open command menu')).not.toBeInTheDocument()
155 expect(screen.queryByText('Command Menu')).not.toBeInTheDocument()
156 })
157
158 it('shows the database navigation section when database shortcuts are active', async () => {
159 renderShortcutsReferenceSheet(ACTIVE_DATABASE_SHORTCUT_IDS)
160
161 expect(await screen.findByText('Global Navigation')).toBeInTheDocument()
162 expect(screen.getByText('Database Navigation')).toBeInTheDocument()
163 expect(screen.queryByText(/^Navigation$/)).not.toBeInTheDocument()
164 expect(screen.getByText('Go to Tables')).toBeInTheDocument()
165 })
166
167 it('shows the auth navigation section when auth shortcuts are active', async () => {
168 renderShortcutsReferenceSheet(ACTIVE_AUTH_SHORTCUT_IDS)
169
170 expect(await screen.findByText('Global Navigation')).toBeInTheDocument()
171 expect(screen.getByText('Auth Navigation')).toBeInTheDocument()
172 expect(screen.queryByText(/^Navigation$/)).not.toBeInTheDocument()
173 expect(screen.getByText('Go to Users')).toBeInTheDocument()
174 })
175
176 it('shows the edge function tabs section when function tab shortcuts are active', async () => {
177 renderShortcutsReferenceSheet(ACTIVE_FUNCTION_DETAIL_NAV_SHORTCUT_IDS)
178
179 expect(await screen.findByText('Global Navigation')).toBeInTheDocument()
180 expect(screen.getByText('Edge Function Tabs')).toBeInTheDocument()
181 expect(screen.queryByText('Edge Function Page Navigation')).not.toBeInTheDocument()
182 expect(screen.getByText('Go to Overview')).toBeInTheDocument()
183 })
184
185 it('shows the realtime navigation section when realtime shortcuts are active', async () => {
186 renderShortcutsReferenceSheet(ACTIVE_REALTIME_NAV_SHORTCUT_IDS)
187
188 expect(await screen.findByText('Global Navigation')).toBeInTheDocument()
189 expect(screen.getByText('Realtime Navigation')).toBeInTheDocument()
190 expect(screen.getByText('Go to Inspector')).toBeInTheDocument()
191 })
192
193 it('uses human labels for realtime inspector shortcut groups', async () => {
194 renderShortcutsReferenceSheet(ACTIVE_REALTIME_INSPECTOR_SHORTCUT_IDS)
195
196 expect(await screen.findByText('Realtime Inspector')).toBeInTheDocument()
197 expect(screen.getByText('Join a channel')).toBeInTheDocument()
198 expect(screen.getByText('Start/Stop listening')).toBeInTheDocument()
199 expect(screen.getByText('Broadcast a message')).toBeInTheDocument()
200 expect(screen.getByText('Copy selected message')).toBeInTheDocument()
201 expect(screen.queryByText('realtime-inspector')).not.toBeInTheDocument()
202 })
203
204 it('uses human labels for active surface shortcut groups', async () => {
205 renderShortcutsReferenceSheet(ACTIVE_SURFACE_SHORTCUT_IDS)
206
207 expect(await screen.findByText('Auth Users')).toBeInTheDocument()
208 expect(screen.getByText('Edge Function Actions')).toBeInTheDocument()
209 expect(screen.getByText('Edge Function Overview')).toBeInTheDocument()
210 expect(screen.getByText('Edge Functions')).toBeInTheDocument()
211 expect(screen.getByText('Logs Explorer')).toBeInTheDocument()
212 expect(screen.getByText('SQL Editor')).toBeInTheDocument()
213 expect(screen.getByText('Storage Buckets')).toBeInTheDocument()
214 expect(screen.getByText('Storage File Explorer')).toBeInTheDocument()
215 expect(screen.queryByText('auth-users')).not.toBeInTheDocument()
216 expect(screen.queryByText('functions-detail')).not.toBeInTheDocument()
217 expect(screen.queryByText('functions-list')).not.toBeInTheDocument()
218 expect(screen.queryByText('functions-overview')).not.toBeInTheDocument()
219 expect(screen.queryByText('logs-preview')).not.toBeInTheDocument()
220 expect(screen.queryByText('sql-editor')).not.toBeInTheDocument()
221 expect(screen.queryByText('storage-buckets')).not.toBeInTheDocument()
222 expect(screen.queryByText('storage-explorer')).not.toBeInTheDocument()
223 })
224
225 it('does not show inactive database shortcuts in search results', async () => {
226 const user = userEvent.setup()
227
228 renderShortcutsReferenceSheet()
229
230 await user.type(screen.getByLabelText('Search shortcuts'), 'Go to Tables')
231
232 expect(screen.getByText('No matching shortcuts found')).toBeInTheDocument()
233 expect(screen.queryByText('Database Navigation')).not.toBeInTheDocument()
234 })
235
236 it('hides shortcuts whose registration is soft-disabled', async () => {
237 sequenceIdCounter = 0
238 const enabled = buildSequenceRegistration(SHORTCUT_IDS.COMMAND_MENU_OPEN)
239 const disabled = buildSequenceRegistration(SHORTCUT_IDS.NAV_HOME)
240 disabled.options = { ...disabled.options, enabled: false }
241 mockUseHotkeyRegistrations.mockReturnValue({
242 hotkeys: [],
243 sequences: [enabled, disabled],
244 })
245
246 customRender(<ShortcutsReferenceSheet open onOpenChange={vi.fn()} />)
247
248 expect(await screen.findByText('Open command menu')).toBeInTheDocument()
249 expect(screen.queryByText('Go to Project Overview')).not.toBeInTheDocument()
250 })
251
252 it('shows a clear button when searching and resets the list when clicked', async () => {
253 const user = userEvent.setup()
254
255 renderShortcutsReferenceSheet()
256
257 await user.type(screen.getByLabelText('Search shortcuts'), 'navigation')
258
259 expect(screen.getByRole('button', { name: 'Clear search' })).toBeInTheDocument()
260
261 await user.click(screen.getByRole('button', { name: 'Clear search' }))
262
263 expect(screen.getByLabelText('Search shortcuts')).toHaveValue('')
264 expect(screen.getByText('Command Menu')).toBeInTheDocument()
265 expect(screen.getByText('Navigation')).toBeInTheDocument()
266 })
267
268 it.each(['⌘Esc', 'Mod+/'])('does not search shortcut values like %s', async (query) => {
269 const user = userEvent.setup()
270
271 renderShortcutsReferenceSheet()
272
273 await user.type(screen.getByLabelText('Search shortcuts'), query)
274
275 expect(screen.getByText('No matching shortcuts found')).toBeInTheDocument()
276 expect(screen.queryByText('Navigation')).not.toBeInTheDocument()
277 })
278
279 it('shows an empty state when nothing matches', async () => {
280 const user = userEvent.setup()
281
282 renderShortcutsReferenceSheet()
283
284 await user.type(screen.getByLabelText('Search shortcuts'), 'totally missing')
285
286 expect(screen.getByText('No matching shortcuts found')).toBeInTheDocument()
287 })
288})