import * as React from 'react'
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import { CFocusTrap } from '../CFocusTrap'
// Helper function to create a test component with focusable elements
interface TestComponentProps {
children?: React.ReactNode
[key: string]: any
}
const TestComponent = ({ children, ...props }: TestComponentProps) => (
)
describe('CFocusTrap', () => {
beforeEach(() => {
// Reset document focus before each test
document.body.focus()
})
test('loads and displays CFocusTrap component', () => {
const { container } = render(
Test Content
)
expect(container).toMatchSnapshot()
expect(screen.getByTestId('test-content')).toBeInTheDocument()
})
test('CFocusTrap with custom props', () => {
const onActivate = jest.fn()
const onDeactivate = jest.fn()
const { container } = render(
Custom Content
)
expect(container).toMatchSnapshot()
expect(onActivate).toHaveBeenCalledTimes(1)
})
test('focuses container when focusFirstElement is false (default)', () => {
const mockFocus = jest.fn()
const originalFocus = HTMLElement.prototype.focus
HTMLElement.prototype.focus = mockFocus
render( )
const container = screen.getByTestId('container')
expect(container).toBeInTheDocument()
expect(mockFocus).toHaveBeenCalledWith({ preventScroll: true })
HTMLElement.prototype.focus = originalFocus
})
test('does not trap focus when active is false', () => {
render( )
// Focus should not be moved to any element
expect(screen.getByTestId('container')).not.toHaveFocus()
expect(screen.getByTestId('first-button')).not.toHaveFocus()
})
test('handles container with no tabbable elements', () => {
const mockFocus = jest.fn()
const originalFocus = HTMLElement.prototype.focus
HTMLElement.prototype.focus = mockFocus
render(
No focusable elements
)
const container = screen.getByTestId('empty-container')
expect(container).toBeInTheDocument()
expect(mockFocus).toHaveBeenCalledWith({ preventScroll: true })
HTMLElement.prototype.focus = originalFocus
})
test('calls onActivate callback when trap becomes active', () => {
const onActivate = jest.fn()
const { rerender } = render( )
expect(onActivate).not.toHaveBeenCalled()
// Re-render with active=true
rerender( )
expect(onActivate).toHaveBeenCalledTimes(1)
})
test('calls onDeactivate callback when trap becomes inactive', () => {
const onDeactivate = jest.fn()
const { rerender } = render( )
expect(onDeactivate).not.toHaveBeenCalled()
// Deactivate the trap
rerender( )
expect(onDeactivate).toHaveBeenCalledTimes(1)
})
test('cleans up event listeners on unmount', () => {
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener')
const { unmount } = render( )
unmount()
expect(removeEventListenerSpy).toHaveBeenCalledWith('focusin', expect.any(Function), true)
removeEventListenerSpy.mockRestore()
})
test('focuses first element when focusFirstElement is true', () => {
const mockFocus = jest.fn()
const originalFocus = HTMLElement.prototype.focus
HTMLElement.prototype.focus = mockFocus
render( )
const firstButton = screen.getByTestId('first-button')
expect(firstButton).toBeInTheDocument()
expect(mockFocus).toHaveBeenCalledWith({ preventScroll: true })
HTMLElement.prototype.focus = originalFocus
})
test('works with additionalContainer prop', () => {
const additionalRef = React.createRef()
render(
Main Button
Additional Button
)
const mainContainer = screen.getByTestId('main-container')
const additionalContainer = screen.getByTestId('additional-container')
expect(mainContainer).toBeInTheDocument()
expect(additionalContainer).toBeInTheDocument()
})
test('restores focus when restoreFocus is true', () => {
// Mock document.activeElement to simulate a focused element
const focusButton = document.createElement('button')
focusButton.dataset.testid = 'focus-button'
document.body.appendChild(focusButton)
const mockFocus = jest.fn()
focusButton.focus = mockFocus
// Mock document.activeElement
Object.defineProperty(document, 'activeElement', {
value: focusButton,
writable: true,
configurable: true,
})
const { rerender } = render( )
// Activate the trap (should store the previous focused element)
rerender( )
// Deactivate the trap (should restore focus)
rerender( )
// Verify focus restoration was attempted
expect(mockFocus).toHaveBeenCalledWith({ preventScroll: true })
focusButton.remove()
})
test('does not restore focus when restoreFocus is false', () => {
// Mock document.activeElement to simulate a focused element
const focusButton = document.createElement('button')
focusButton.dataset.testid = 'focus-button'
document.body.appendChild(focusButton)
const mockFocus = jest.fn()
focusButton.focus = mockFocus
// Mock document.activeElement
Object.defineProperty(document, 'activeElement', {
value: focusButton,
writable: true,
configurable: true,
})
const { rerender } = render( )
// Activate the trap (should store the previous focused element)
rerender( )
// Deactivate the trap (should NOT restore focus)
rerender( )
// Verify focus restoration was not attempted
expect(mockFocus).not.toHaveBeenCalledWith({ preventScroll: true })
focusButton.remove()
})
})