import type React from 'react' import { renderHook } from '@testing-library/react' import { useGridNavigation } from './use-grid-navigation' import { useGridContext } from '../context/grid-context' import { createGrid } from './use-grid' jest.mock('../context/grid-context', () => ({ useGridContext: jest.fn(), })) describe('useGridNavigation', () => { let grid: ReturnType const createMockEvent = ( key: string, modifiers: Partial<{ ctrlKey: boolean metaKey: boolean shiftKey: boolean }> = {} ) => ({ key, preventDefault: jest.fn(), ctrlKey: false, metaKey: false, shiftKey: false, ...modifiers, }) as unknown as React.KeyboardEvent beforeEach(() => { grid = createGrid() jest.spyOn(grid, 'getState') jest.spyOn(grid.selectors, 'selectNavigationEnabled') jest.spyOn(grid.selectors, 'selectIsEditing') jest.spyOn(grid.selectors, 'selectAreHeadersHidden') jest.spyOn(grid.selectors, 'selectIsSpreadsheet') jest.spyOn(grid.selectors, 'selectRowIds') jest.spyOn(grid.selectors, 'selectColumnIds') jest.spyOn(grid.selectors, 'selectCurrentFocus') jest.spyOn(grid.selectors, 'selectAggregationEnabled') jest.spyOn(grid.selectors, 'selectNumberOfRowsInView') jest.spyOn(grid.selectors, 'selectIsRowSelected') jest.spyOn(grid.api.focus, 'set') jest.spyOn(grid.api.focus, 'move') jest.spyOn(grid.api.selection, 'select') jest.spyOn(grid.events, 'emit') jest.mocked(useGridContext).mockReturnValue(grid) // Default state jest.mocked(grid.getState).mockReturnValue({} as any) jest.mocked(grid.selectors.selectNavigationEnabled).mockReturnValue( true ) jest.mocked(grid.selectors.selectIsEditing).mockReturnValue(false) jest.mocked(grid.selectors.selectAreHeadersHidden).mockReturnValue( false ) jest.mocked(grid.selectors.selectIsSpreadsheet).mockReturnValue(false) jest.mocked(grid.selectors.selectRowIds).mockReturnValue([ 'row1', 'row2', 'row3', 'row4', ]) jest.mocked(grid.selectors.selectColumnIds).mockReturnValue([ 'col1', 'col2', 'col3', ]) }) it('should return a keyDownHandler function', () => { const { result } = renderHook(() => useGridNavigation()) expect(typeof result.current).toBe('function') }) it('should do nothing when navigation is disabled', () => { jest.mocked(grid.selectors.selectNavigationEnabled).mockReturnValue( false ) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent('ArrowDown') result.current(mockEvent) expect(mockEvent.preventDefault).not.toHaveBeenCalled() expect(grid.api.focus.move).not.toHaveBeenCalled() }) it('should do nothing when editing is active', () => { jest.mocked(grid.selectors.selectIsEditing).mockReturnValue(true) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent('ArrowDown') result.current(mockEvent) expect(mockEvent.preventDefault).not.toHaveBeenCalled() expect(grid.api.focus.move).not.toHaveBeenCalled() }) it('should handle Home key without modifiers', () => { jest.mocked(grid.selectors.selectCurrentFocus).mockReturnValue({ rowId: 'row1', area: 'body', columnId: 'col2', subFocus: 'first', }) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent('Home') result.current(mockEvent) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(grid.api.focus.set).toHaveBeenCalledWith('row1', 'col1', 'body') }) it('should handle Home key with ctrlKey when headers are hidden (select first cell in body)', () => { jest.mocked(grid.selectors.selectAreHeadersHidden).mockReturnValue(true) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent('Home', { ctrlKey: true }) result.current(mockEvent) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(grid.api.focus.set).toHaveBeenCalledWith('row1', 'col1', 'body') }) it('should handle Home key with Ctrl modifier', () => { jest.mocked(grid.selectors.selectAreHeadersHidden).mockReturnValue( false ) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent('Home', { ctrlKey: true }) result.current(mockEvent) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(grid.api.focus.set).toHaveBeenCalledWith('0', 'col1', 'header') }) it('should handle End key without modifiers', () => { jest.mocked(grid.selectors.selectCurrentFocus).mockReturnValue({ rowId: 'row1', area: 'body', columnId: 'col2', subFocus: 'first', }) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent('End') result.current(mockEvent) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(grid.api.focus.set).toHaveBeenCalledWith('row1', 'col3', 'body') }) it('should handle End key with ctrlKey when aggregation is disabled (select last cell in body)', () => { jest.mocked(grid.selectors.selectAggregationEnabled).mockReturnValue( false ) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent('End', { ctrlKey: true }) result.current(mockEvent) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(grid.api.focus.set).toHaveBeenCalledWith('row4', 'col3', 'body') }) it('should handle End key with ctrlKey when aggregation is enabled (select last cell in footer)', () => { jest.mocked(grid.selectors.selectAggregationEnabled).mockReturnValue( true ) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent('End', { ctrlKey: true }) result.current(mockEvent) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(grid.api.focus.set).toHaveBeenCalledWith('0', 'col3', 'footer') }) it('should handle PageUp key in body area', () => { jest.mocked(grid.selectors.selectCurrentFocus).mockReturnValue({ rowId: 'row3', columnId: 'col1', area: 'body', subFocus: 'first', }) jest.mocked(grid.selectors.selectNumberOfRowsInView).mockReturnValue(2) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent('PageUp') result.current(mockEvent) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(grid.api.focus.set).toHaveBeenCalledWith('row1', 'col1', 'body') }) it('should handle PageUp key in body area when it should move to the header', () => { jest.mocked(grid.selectors.selectCurrentFocus).mockReturnValue({ rowId: 'row3', columnId: 'col1', area: 'body', subFocus: 'first', }) jest.mocked(grid.selectors.selectNumberOfRowsInView).mockReturnValue(10) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent('PageUp') result.current(mockEvent) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(grid.api.focus.set).toHaveBeenCalledWith('0', 'col1', 'header') }) it('should handle PageUp key in body area when the header is hidden (jump to first row)', () => { jest.mocked(grid.selectors.selectCurrentFocus).mockReturnValue({ rowId: 'row3', columnId: 'col1', area: 'body', subFocus: 'first', }) jest.mocked(grid.selectors.selectNumberOfRowsInView).mockReturnValue(10) jest.mocked(grid.selectors.selectAreHeadersHidden).mockReturnValue(true) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent('PageUp') result.current(mockEvent) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(grid.api.focus.set).toHaveBeenCalledWith('row1', 'col1', 'body') }) it('should handle PageDown key', () => { jest.mocked(grid.selectors.selectCurrentFocus).mockReturnValue({ rowId: 'row1', columnId: 'col1', area: 'body', subFocus: 'first', }) jest.mocked(grid.selectors.selectNumberOfRowsInView).mockReturnValue(2) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent('PageDown') result.current(mockEvent) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(grid.api.focus.set).toHaveBeenCalledWith('row3', 'col1', 'body') }) it('should handle arrow navigation keys', () => { const { result } = renderHook(() => useGridNavigation()) ;['ArrowDown', 'ArrowUp', 'ArrowRight', 'ArrowLeft'].forEach((key) => { jest.mocked(grid.api.focus.move).mockClear() const mockEvent = createMockEvent(key) result.current(mockEvent) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(grid.api.focus.move).toHaveBeenCalledWith( key.replace('Arrow', '').toLowerCase() ) }) }) it('should handle space+shift for row selection', () => { jest.mocked(grid.selectors.selectCurrentFocus).mockReturnValue({ rowId: 'row1', area: 'body', columnId: 'col2', subFocus: 'first', }) jest.mocked(grid.selectors.selectIsRowSelected).mockReturnValue(false) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent(' ', { shiftKey: true }) result.current(mockEvent) expect(mockEvent.preventDefault).toHaveBeenCalled() expect(grid.api.selection.select).toHaveBeenCalledWith('row1', true) expect(grid.events.emit).toHaveBeenCalledWith('onRowClick', 'row1') }) it('should toggle selection state with space+shift', () => { jest.mocked(grid.selectors.selectCurrentFocus).mockReturnValue({ rowId: 'row1', area: 'body', columnId: 'col2', subFocus: 'first', }) jest.mocked(grid.selectors.selectIsRowSelected).mockReturnValue(true) const { result } = renderHook(() => useGridNavigation()) const mockEvent = createMockEvent(' ', { shiftKey: true }) result.current(mockEvent) expect(grid.api.selection.select).toHaveBeenCalledWith('row1', false) }) })