@@ -272,4 +272,187 @@ describe('renderer/components/Sidebar.tsx', () => {
272272
273273 expect ( quitAppSpy ) . toHaveBeenCalledTimes ( 1 ) ;
274274 } ) ;
275+
276+ describe ( 'keyboard bindings' , ( ) => {
277+ it ( 'should navigate home when pressing H key' , async ( ) => {
278+ renderWithAppContext (
279+ < MemoryRouter >
280+ < Sidebar />
281+ </ MemoryRouter > ,
282+ ) ;
283+
284+ await userEvent . keyboard ( 'h' ) ;
285+
286+ expect ( navigateMock ) . toHaveBeenCalledTimes ( 1 ) ;
287+ expect ( navigateMock ) . toHaveBeenCalledWith ( '/' , { replace : true } ) ;
288+ } ) ;
289+
290+ it ( 'should refresh notifications when pressing R key' , async ( ) => {
291+ renderWithAppContext (
292+ < MemoryRouter >
293+ < Sidebar />
294+ </ MemoryRouter > ,
295+ {
296+ fetchNotifications : fetchNotificationsMock ,
297+ status : 'success' ,
298+ } ,
299+ ) ;
300+
301+ await userEvent . keyboard ( 'r' ) ;
302+
303+ expect ( navigateMock ) . toHaveBeenCalledWith ( '/' , { replace : true } ) ;
304+ expect ( fetchNotificationsMock ) . toHaveBeenCalledTimes ( 1 ) ;
305+ } ) ;
306+
307+ it ( 'should not refresh notifications when pressing R key if status is loading' , async ( ) => {
308+ renderWithAppContext (
309+ < MemoryRouter >
310+ < Sidebar />
311+ </ MemoryRouter > ,
312+ {
313+ fetchNotifications : fetchNotificationsMock ,
314+ status : 'loading' ,
315+ } ,
316+ ) ;
317+
318+ await userEvent . keyboard ( 'r' ) ;
319+
320+ expect ( fetchNotificationsMock ) . not . toHaveBeenCalled ( ) ;
321+ } ) ;
322+
323+ it ( 'should toggle settings when pressing S key while logged in' , async ( ) => {
324+ renderWithAppContext (
325+ < MemoryRouter >
326+ < Sidebar />
327+ </ MemoryRouter > ,
328+ {
329+ isLoggedIn : true ,
330+ } ,
331+ ) ;
332+
333+ await userEvent . keyboard ( 's' ) ;
334+
335+ expect ( navigateMock ) . toHaveBeenCalledTimes ( 1 ) ;
336+ expect ( navigateMock ) . toHaveBeenCalledWith ( '/settings' ) ;
337+ } ) ;
338+
339+ it ( 'should not toggle settings when pressing S key while logged out' , async ( ) => {
340+ renderWithAppContext (
341+ < MemoryRouter >
342+ < Sidebar />
343+ </ MemoryRouter > ,
344+ {
345+ isLoggedIn : false ,
346+ } ,
347+ ) ;
348+
349+ await userEvent . keyboard ( 's' ) ;
350+
351+ expect ( navigateMock ) . not . toHaveBeenCalled ( ) ;
352+ } ) ;
353+
354+ it ( 'should toggle filters when pressing F key while logged in' , async ( ) => {
355+ renderWithAppContext (
356+ < MemoryRouter >
357+ < Sidebar />
358+ </ MemoryRouter > ,
359+ {
360+ isLoggedIn : true ,
361+ } ,
362+ ) ;
363+
364+ await userEvent . keyboard ( 'f' ) ;
365+
366+ expect ( navigateMock ) . toHaveBeenCalledTimes ( 1 ) ;
367+ expect ( navigateMock ) . toHaveBeenCalledWith ( '/filters' ) ;
368+ } ) ;
369+
370+ it ( 'should not toggle filters when pressing F key while logged out' , async ( ) => {
371+ renderWithAppContext (
372+ < MemoryRouter >
373+ < Sidebar />
374+ </ MemoryRouter > ,
375+ {
376+ isLoggedIn : false ,
377+ } ,
378+ ) ;
379+
380+ await userEvent . keyboard ( 'f' ) ;
381+
382+ expect ( navigateMock ) . not . toHaveBeenCalled ( ) ;
383+ } ) ;
384+
385+ it ( 'should ignore keyboard shortcuts when typing in an input' , async ( ) => {
386+ renderWithAppContext (
387+ < MemoryRouter >
388+ < Sidebar />
389+ </ MemoryRouter > ,
390+ ) ;
391+
392+ const input = document . createElement ( 'input' ) ;
393+ document . body . appendChild ( input ) ;
394+ input . focus ( ) ;
395+
396+ await userEvent . keyboard ( 'h' ) ;
397+
398+ expect ( navigateMock ) . not . toHaveBeenCalled ( ) ;
399+
400+ document . body . removeChild ( input ) ;
401+ } ) ;
402+
403+ it ( 'should ignore keyboard shortcuts when typing in a textarea' , async ( ) => {
404+ renderWithAppContext (
405+ < MemoryRouter >
406+ < Sidebar />
407+ </ MemoryRouter > ,
408+ ) ;
409+
410+ const textarea = document . createElement ( 'textarea' ) ;
411+ document . body . appendChild ( textarea ) ;
412+ textarea . focus ( ) ;
413+
414+ await userEvent . keyboard ( 'r' ) ;
415+
416+ expect ( navigateMock ) . not . toHaveBeenCalled ( ) ;
417+ expect ( fetchNotificationsMock ) . not . toHaveBeenCalled ( ) ;
418+
419+ document . body . removeChild ( textarea ) ;
420+ } ) ;
421+
422+ it ( 'should ignore keyboard shortcuts when modifier keys are pressed' , async ( ) => {
423+ renderWithAppContext (
424+ < MemoryRouter >
425+ < Sidebar />
426+ </ MemoryRouter > ,
427+ {
428+ fetchNotifications : fetchNotificationsMock ,
429+ status : 'success' ,
430+ } ,
431+ ) ;
432+
433+ // Note: userEvent.keyboard with modifier syntax like {Shift>h</Shift>}
434+ // would require holding shift, but we test that metaKey/ctrlKey/altKey
435+ // prevent action through manual event dispatch
436+ const event = new KeyboardEvent ( 'keydown' , {
437+ key : 'h' ,
438+ metaKey : true ,
439+ } ) ;
440+ document . dispatchEvent ( event ) ;
441+
442+ expect ( navigateMock ) . not . toHaveBeenCalled ( ) ;
443+ } ) ;
444+
445+ it ( 'should work with uppercase key press' , async ( ) => {
446+ renderWithAppContext (
447+ < MemoryRouter >
448+ < Sidebar />
449+ </ MemoryRouter > ,
450+ ) ;
451+
452+ // userEvent.keyboard converts to lowercase automatically
453+ await userEvent . keyboard ( 'H' ) ;
454+
455+ expect ( navigateMock ) . toHaveBeenCalledWith ( '/' , { replace : true } ) ;
456+ } ) ;
457+ } ) ;
275458} ) ;
0 commit comments