-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: add unit tests for FocusTrap component
Added comprehensive tests for the FocusTrap component to verify its behavior with various scenarios. Tests cover functionality such as correct rendering of children, focus redirection, focus trapping, handling of class names, usage of refs, managing non-focusable elements, and event listeners management.
- Loading branch information
1 parent
6f7d285
commit 62106ea
Showing
1 changed file
with
147 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
// FocusTrap.test.tsx | ||
|
||
import React, { createRef } from 'react'; | ||
import { render, screen, fireEvent } from '@testing-library/react'; | ||
import '@testing-library/jest-dom'; | ||
import { FocusTrap } from '@/components/FocusTrap'; | ||
|
||
// Mock the isBrowser function to always return true during tests | ||
jest.mock('@/utils/environment', () => ({ | ||
isBrowser: jest.fn(() => true), | ||
})); | ||
|
||
describe('FocusTrap Component', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('renders children correctly', () => { | ||
render( | ||
<FocusTrap> | ||
<button>Button 1</button> | ||
<button>Button 2</button> | ||
</FocusTrap>, | ||
); | ||
|
||
expect(screen.getByText('Button 1')).toBeInTheDocument(); | ||
expect(screen.getByText('Button 2')).toBeInTheDocument(); | ||
}); | ||
|
||
it('redirects focus to the first focusable element when focus is set outside the container', () => { | ||
render( | ||
<> | ||
<button>Outside Button</button> | ||
<FocusTrap> | ||
<button>Button 1</button> | ||
<button>Button 2</button> | ||
</FocusTrap> | ||
</>, | ||
); | ||
|
||
const outsideButton = screen.getByText('Outside Button'); | ||
const insideButton1 = screen.getByText('Button 1'); | ||
const currentContainer = insideButton1.parentElement as HTMLDivElement; | ||
|
||
fireEvent.focusIn(outsideButton); | ||
|
||
const focusEvent = new FocusEvent('focusin', { bubbles: true }); | ||
Object.defineProperty(focusEvent, 'target', { writable: false, value: outsideButton }); | ||
currentContainer.dispatchEvent(focusEvent); | ||
|
||
expect(insideButton1).toHaveFocus(); | ||
}); | ||
|
||
it('traps focus within the container', () => { | ||
render( | ||
<FocusTrap> | ||
<button>Button 1</button> | ||
<button>Button 2</button> | ||
</FocusTrap>, | ||
); | ||
|
||
const buttons = screen.getAllByRole('button'); | ||
const container = buttons[0].parentElement!; | ||
|
||
buttons[0].focus(); | ||
expect(buttons[0]).toHaveFocus(); | ||
|
||
fireEvent.keyDown(container, { key: 'Tab', code: 'Tab' }); | ||
expect(buttons[1]).toHaveFocus(); | ||
|
||
fireEvent.keyDown(container, { key: 'Tab', code: 'Tab', shiftKey: true }); | ||
expect(buttons[0]).toHaveFocus(); | ||
|
||
fireEvent.keyDown(container, { key: 'Tab', code: 'Tab' }); | ||
expect(buttons[1]).toHaveFocus(); | ||
|
||
fireEvent.keyDown(container, { key: 'Tab', code: 'Tab' }); | ||
expect(buttons[0]).toHaveFocus(); | ||
|
||
// Shift + Tab should move focus back to the last element | ||
fireEvent.keyDown(container, { key: 'Tab', code: 'Tab', shiftKey: true }); | ||
expect(buttons[1]).toHaveFocus(); | ||
}); | ||
|
||
it('applies additional class names correctly', () => { | ||
const className = 'custom-class'; | ||
|
||
render( | ||
<FocusTrap className={className}> | ||
<button>Button 1</button> | ||
</FocusTrap>, | ||
); | ||
|
||
const container = screen.getByText('Button 1').parentElement; | ||
expect(container).toHaveClass(className); | ||
}); | ||
|
||
it('uses the ref to access the container element', () => { | ||
const ref = createRef<HTMLDivElement>(); | ||
|
||
render( | ||
<FocusTrap ref={ref}> | ||
<button>Button 1</button> | ||
</FocusTrap>, | ||
); | ||
|
||
expect(ref.current).toBeInstanceOf(HTMLDivElement); | ||
}); | ||
|
||
it('handles no focusable elements gracefully', () => { | ||
render( | ||
<FocusTrap> | ||
<div>Non-focusable Content</div> | ||
</FocusTrap>, | ||
); | ||
|
||
const container = screen.getByText('Non-focusable Content').parentElement; | ||
expect(container).toBeInTheDocument(); | ||
|
||
// Simulate Tab key press, should not throw errors | ||
fireEvent.keyDown(container!, { key: 'Tab', code: 'Tab' }); | ||
expect(document.activeElement).not.toBe(null); | ||
}); | ||
|
||
it('adds and removes event listeners on mount and unmount', () => { | ||
const addEventListenerSpy = jest.spyOn(HTMLDivElement.prototype, 'addEventListener'); | ||
const removeEventListenerSpy = jest.spyOn(HTMLDivElement.prototype, 'removeEventListener'); | ||
|
||
const { unmount, container } = render( | ||
<FocusTrap> | ||
<button>Button 1</button> | ||
</FocusTrap>, | ||
); | ||
|
||
expect(container).not.toBe(null); | ||
|
||
fireEvent.keyDown(container, { key: 'Tab', code: 'Tab', bubbles: true }); | ||
|
||
expect(addEventListenerSpy).toHaveBeenCalledWith('keydown', expect.any(Function)); | ||
expect(addEventListenerSpy).toHaveBeenCalledWith('focusin', expect.any(Function)); | ||
|
||
unmount(); | ||
|
||
expect(removeEventListenerSpy).toHaveBeenCalledWith('keydown', expect.any(Function)); | ||
expect(removeEventListenerSpy).toHaveBeenCalledWith('focusin', expect.any(Function)); | ||
}); | ||
}); |