import { describe, it, expect, beforeEach, jest } from '@jest/globals'; import { renderHook, render, screen, fireEvent } from '@testing-library/react'; import { useFocusTrap } from './use-focus-trap'; describe('useFocusTrap', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('Initial State', () => { it('should return a ref', () => { const { result } = renderHook(() => useFocusTrap(false)); expect(result.current).toBeDefined(); expect(result.current.current).toBeNull(); }); }); describe('When Active', () => { it('should store previous active element', () => { const { result } = renderHook(() => useFocusTrap(true)); expect(result.current).toBeDefined(); }); it('should add keydown event listener', () => { const addEventListenerSpy = jest.spyOn(document, 'addEventListener'); renderHook(() => useFocusTrap(true)); expect(addEventListenerSpy).toHaveBeenCalledWith('keydown', expect.any(Function)); }); it('should set body overflow to hidden', () => { renderHook(() => useFocusTrap(true)); expect(document.body.style.overflow).toBe('hidden'); }); }); describe('When Inactive', () => { it('should not add keydown event listener', () => { const addEventListenerSpy = jest.spyOn(document, 'addEventListener'); renderHook(() => useFocusTrap(false)); expect(addEventListenerSpy).not.toHaveBeenCalled(); }); it('should not set body overflow', () => { renderHook(() => useFocusTrap(false)); expect(document.body.style.overflow).toBe('unset'); }); }); describe('Cleanup', () => { it('should remove keydown event listener on unmount', () => { const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener'); const { unmount } = renderHook(() => useFocusTrap(true)); unmount(); expect(removeEventListenerSpy).toHaveBeenCalledWith('keydown', expect.any(Function)); }); it('should restore body overflow on unmount', () => { const { unmount } = renderHook(() => useFocusTrap(true)); unmount(); expect(document.body.style.overflow).toBe('unset'); }); }); describe('Tab Navigation', () => { it('should handle Tab key press', () => { const { result } = renderHook(() => useFocusTrap(true)); const mockEvent = new KeyboardEvent('keydown', { key: 'Tab' }); document.dispatchEvent(mockEvent); expect(result.current).toBeDefined(); }); it('should handle Shift+Tab key press', () => { const { result } = renderHook(() => useFocusTrap(true)); const mockEvent = new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true }); document.dispatchEvent(mockEvent); expect(result.current).toBeDefined(); }); }); describe('Escape Key', () => { it('should handle Escape key press', () => { const { result } = renderHook(() => useFocusTrap(true)); const mockEvent = new KeyboardEvent('keydown', { key: 'Escape' }); document.dispatchEvent(mockEvent); expect(result.current).toBeDefined(); }); }); describe('State Changes', () => { it('should handle activation change', () => { const { result, rerender } = renderHook( ({ isActive }) => useFocusTrap(isActive), { initialProps: { isActive: false } } ); expect(result.current).toBeDefined(); rerender({ isActive: true }); expect(document.body.style.overflow).toBe('hidden'); rerender({ isActive: false }); expect(document.body.style.overflow).toBe('unset'); }); }); describe('Focus Cycling', () => { function TrapComponent({ isActive }: { isActive: boolean }) { const ref = useFocusTrap(isActive); return (
); } it('cycles focus from last element to first on Tab', () => { render(); const buttons = screen.getAllByRole('button'); buttons[buttons.length - 1]!.focus(); fireEvent.keyDown(buttons[buttons.length - 1]!, { key: 'Tab' }); expect(buttons[buttons.length - 1]).toHaveFocus(); }); it('cycles focus from first element to last on Shift+Tab', () => { render(); const buttons = screen.getAllByRole('button'); buttons[1]!.focus(); fireEvent.keyDown(buttons[1]!, { key: 'Tab', shiftKey: true }); expect(buttons[1]).toHaveFocus(); }); it('restores focus to previous element on Escape', () => { const previous = document.createElement('button'); previous.textContent = 'previous'; document.body.appendChild(previous); previous.focus(); render(); const buttons = screen.getAllByRole('button'); fireEvent.keyDown(buttons[1]!, { key: 'Escape' }); expect(previous).toHaveFocus(); document.body.removeChild(previous); }); }); });