import { renderHook, act } from '@testing-library/react' import { useColumnVisibility } from '../../hooks/useColumnVisibility' describe('useColumnVisibility', () => { beforeEach(() => { // Clear localStorage before each test localStorage.clear() }) describe('Initialization', () => { it('should initialize with empty visibility config', () => { const { result } = renderHook(() => useColumnVisibility()) expect(result.current.columnVisibility).toEqual({}) }) it('should initialize with default visibility config', () => { const defaultVisibility = { col1: true, col2: false, col3: true, } const { result } = renderHook(() => useColumnVisibility({ defaultVisibility }) ) expect(result.current.columnVisibility).toEqual(defaultVisibility) }) it('should load from localStorage if persistKey provided', () => { const storedConfig = { col1: false, col2: true, } localStorage.setItem( 'column-visibility-test-table', JSON.stringify(storedConfig) ) const { result } = renderHook(() => useColumnVisibility({ persistKey: 'test-table' }) ) expect(result.current.columnVisibility).toEqual(storedConfig) }) it('should use default visibility if localStorage is invalid', () => { localStorage.setItem('column-visibility-test-table', 'invalid-json') const defaultVisibility = { col1: true } const { result } = renderHook(() => useColumnVisibility({ defaultVisibility, persistKey: 'test-table', }) ) expect(result.current.columnVisibility).toEqual(defaultVisibility) }) it('should prioritize localStorage over default visibility', () => { const storedConfig = { col1: false } const defaultVisibility = { col1: true, col2: true } localStorage.setItem( 'column-visibility-priority-test', JSON.stringify(storedConfig) ) const { result } = renderHook(() => useColumnVisibility({ defaultVisibility, persistKey: 'priority-test', }) ) expect(result.current.columnVisibility).toEqual(storedConfig) }) }) describe('Toggle Column', () => { it('should toggle column visibility from true to false', () => { const { result } = renderHook(() => useColumnVisibility({ defaultVisibility: { col1: true } }) ) act(() => { result.current.toggleColumn('col1') }) expect(result.current.columnVisibility.col1).toBe(false) }) it('should toggle column visibility from false to true', () => { const { result } = renderHook(() => useColumnVisibility({ defaultVisibility: { col1: false } }) ) act(() => { result.current.toggleColumn('col1') }) expect(result.current.columnVisibility.col1).toBe(true) }) it('should toggle unset column to false (default is true)', () => { const { result } = renderHook(() => useColumnVisibility()) act(() => { result.current.toggleColumn('newColumn') }) expect(result.current.columnVisibility.newColumn).toBe(false) }) it('should toggle multiple columns independently', () => { const { result } = renderHook(() => useColumnVisibility()) act(() => { result.current.toggleColumn('col1') result.current.toggleColumn('col2') }) expect(result.current.columnVisibility).toEqual({ col1: false, col2: false, }) act(() => { result.current.toggleColumn('col1') }) expect(result.current.columnVisibility).toEqual({ col1: true, col2: false, }) }) }) describe('Show/Hide Column', () => { it('should show column', () => { const { result } = renderHook(() => useColumnVisibility()) act(() => { result.current.showColumn('col1') }) expect(result.current.columnVisibility.col1).toBe(true) }) it('should hide column', () => { const { result } = renderHook(() => useColumnVisibility()) act(() => { result.current.hideColumn('col1') }) expect(result.current.columnVisibility.col1).toBe(false) }) it('should not change already shown column', () => { const { result } = renderHook(() => useColumnVisibility({ defaultVisibility: { col1: true } }) ) act(() => { result.current.showColumn('col1') }) expect(result.current.columnVisibility.col1).toBe(true) }) it('should not change already hidden column', () => { const { result } = renderHook(() => useColumnVisibility({ defaultVisibility: { col1: false } }) ) act(() => { result.current.hideColumn('col1') }) expect(result.current.columnVisibility.col1).toBe(false) }) }) describe('Show/Hide All Columns', () => { it('should show all columns', () => { const { result } = renderHook(() => useColumnVisibility({ defaultVisibility: { col1: false, col2: false, col3: false, }, }) ) act(() => { result.current.showAllColumns() }) expect(result.current.columnVisibility).toEqual({}) }) it('should hide all columns', () => { const { result } = renderHook(() => useColumnVisibility({ defaultVisibility: { col1: true, col2: true, col3: true, }, }) ) act(() => { result.current.hideAllColumns() }) expect(result.current.columnVisibility).toEqual({ col1: false, col2: false, col3: false, }) }) it('should hide all except specified columns', () => { const { result } = renderHook(() => useColumnVisibility({ defaultVisibility: { col1: true, col2: true, col3: true, }, }) ) act(() => { result.current.hideAllColumns(['col1', 'col3']) }) expect(result.current.columnVisibility.col1).toBe(true) expect(result.current.columnVisibility.col2).toBe(false) expect(result.current.columnVisibility.col3).toBe(true) }) }) describe('Reset Visibility', () => { it('should reset to default visibility', () => { const defaultVisibility = { col1: true, col2: false, } const { result } = renderHook(() => useColumnVisibility({ defaultVisibility }) ) act(() => { result.current.toggleColumn('col1') result.current.toggleColumn('col2') }) expect(result.current.columnVisibility).toEqual({ col1: false, col2: true, }) act(() => { result.current.resetVisibility() }) expect(result.current.columnVisibility).toEqual(defaultVisibility) }) it('should reset to empty when no default provided', () => { const { result } = renderHook(() => useColumnVisibility()) act(() => { result.current.hideColumn('col1') result.current.hideColumn('col2') }) act(() => { result.current.resetVisibility() }) expect(result.current.columnVisibility).toEqual({}) }) }) describe('Is Column Visible', () => { it('should return true for explicitly visible column', () => { const { result } = renderHook(() => useColumnVisibility({ defaultVisibility: { col1: true } }) ) expect(result.current.isColumnVisible('col1')).toBe(true) }) it('should return false for explicitly hidden column', () => { const { result } = renderHook(() => useColumnVisibility({ defaultVisibility: { col1: false } }) ) expect(result.current.isColumnVisible('col1')).toBe(false) }) it('should return true for unset column (default visible)', () => { const { result } = renderHook(() => useColumnVisibility()) expect(result.current.isColumnVisible('unknownColumn')).toBe(true) }) it('should update after toggling', () => { const { result } = renderHook(() => useColumnVisibility()) expect(result.current.isColumnVisible('col1')).toBe(true) act(() => { result.current.toggleColumn('col1') }) expect(result.current.isColumnVisible('col1')).toBe(false) }) }) describe('Persistence', () => { it('should persist to localStorage when persistKey provided', () => { const { result } = renderHook(() => useColumnVisibility({ persistKey: 'persist-test' }) ) act(() => { result.current.hideColumn('col1') }) const stored = localStorage.getItem('column-visibility-persist-test') expect(stored).toBeTruthy() expect(JSON.parse(stored!)).toEqual({ col1: false }) }) it('should update localStorage on changes', () => { const { result } = renderHook(() => useColumnVisibility({ persistKey: 'update-test' }) ) act(() => { result.current.hideColumn('col1') }) let stored = localStorage.getItem('column-visibility-update-test') expect(JSON.parse(stored!)).toEqual({ col1: false }) act(() => { result.current.showColumn('col2') }) stored = localStorage.getItem('column-visibility-update-test') expect(JSON.parse(stored!)).toEqual({ col1: false, col2: true, }) }) it('should not persist when persistKey not provided', () => { const { result } = renderHook(() => useColumnVisibility()) act(() => { result.current.hideColumn('col1') }) expect(localStorage.length).toBe(0) }) it('should persist across multiple operations', () => { const { result } = renderHook(() => useColumnVisibility({ persistKey: 'multi-op-test' }) ) act(() => { result.current.hideColumn('col1') result.current.showColumn('col2') result.current.toggleColumn('col3') }) const stored = localStorage.getItem('column-visibility-multi-op-test') expect(JSON.parse(stored!)).toEqual({ col1: false, col2: true, col3: false, }) }) }) describe('Callback Integration', () => { it('should call onVisibilityChange when visibility changes', () => { const onVisibilityChange = jest.fn() const { result } = renderHook(() => useColumnVisibility({ onVisibilityChange }) ) // Clear the initial call from useEffect onVisibilityChange.mockClear() act(() => { result.current.toggleColumn('col1') }) expect(onVisibilityChange).toHaveBeenCalled() expect(onVisibilityChange.mock.calls[0][0]).toEqual({ col1: false }) }) it('should call callback on initialization with persisted data', () => { const persistedData = { col1: false } localStorage.setItem( 'column-visibility-callback-test', JSON.stringify(persistedData) ) const onVisibilityChange = jest.fn() renderHook(() => useColumnVisibility({ persistKey: 'callback-test', onVisibilityChange, }) ) expect(onVisibilityChange).toHaveBeenCalledWith(persistedData) }) it('should call callback multiple times for multiple changes', () => { const onVisibilityChange = jest.fn() const { result } = renderHook(() => useColumnVisibility({ onVisibilityChange }) ) act(() => { result.current.hideColumn('col1') result.current.showColumn('col2') }) expect(onVisibilityChange).toHaveBeenCalledTimes(2) }) }) describe('Edge Cases', () => { it('should handle undefined column IDs gracefully', () => { const { result } = renderHook(() => useColumnVisibility()) expect(() => { act(() => { result.current.toggleColumn(undefined as any) }) }).not.toThrow() }) it('should handle empty string column IDs', () => { const { result } = renderHook(() => useColumnVisibility()) act(() => { result.current.toggleColumn('') }) expect(result.current.columnVisibility['']).toBe(false) }) it('should handle special characters in column IDs', () => { const { result } = renderHook(() => useColumnVisibility()) act(() => { result.current.toggleColumn('col-1.2.3') }) expect(result.current.columnVisibility['col-1.2.3']).toBe(false) }) it('should handle many columns', () => { const { result } = renderHook(() => useColumnVisibility()) act(() => { for (let i = 0; i < 100; i++) { result.current.toggleColumn(`col${i}`) } }) expect(Object.keys(result.current.columnVisibility).length).toBe(100) }) }) describe('Complex Scenarios', () => { it('should handle mixed operations correctly', () => { const { result } = renderHook(() => useColumnVisibility({ defaultVisibility: { col1: true, col2: false, col3: true, }, }) ) act(() => { result.current.toggleColumn('col1') result.current.showColumn('col2') result.current.hideColumn('col3') result.current.showColumn('col4') }) expect(result.current.columnVisibility).toEqual({ col1: false, col2: true, col3: false, col4: true, }) }) it('should maintain state across re-renders', () => { const { result, rerender } = renderHook(() => useColumnVisibility()) act(() => { result.current.hideColumn('col1') }) rerender() expect(result.current.isColumnVisible('col1')).toBe(false) }) it('should handle persistence with complex visibility state', () => { const { result } = renderHook(() => useColumnVisibility({ persistKey: 'complex-test' }) ) const complexState = { col1: true, col2: false, col3: true, col4: false, col5: true, } act(() => { result.current.setColumnVisibility(complexState) }) const stored = localStorage.getItem('column-visibility-complex-test') expect(JSON.parse(stored!)).toEqual(complexState) // Verify loading in new hook instance const { result: result2 } = renderHook(() => useColumnVisibility({ persistKey: 'complex-test' }) ) expect(result2.current.columnVisibility).toEqual(complexState) }) }) describe('Set Column Visibility Directly', () => { it('should set entire visibility config', () => { const { result } = renderHook(() => useColumnVisibility()) const newConfig = { col1: true, col2: false, col3: true, } act(() => { result.current.setColumnVisibility(newConfig) }) expect(result.current.columnVisibility).toEqual(newConfig) }) it('should replace previous config', () => { const { result } = renderHook(() => useColumnVisibility({ defaultVisibility: { col1: false, col2: true, }, }) ) act(() => { result.current.setColumnVisibility({ col3: false }) }) expect(result.current.columnVisibility).toEqual({ col3: false }) }) }) })