2026-01-07 09:31:02 +00:00
|
|
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
|
import { FocusTrap } from './focus-trap';
|
|
|
|
|
|
|
|
|
|
describe('FocusTrap Component', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
// Reset focus before each test
|
|
|
|
|
document.body.focus();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders children', () => {
|
|
|
|
|
render(
|
|
|
|
|
<FocusTrap>
|
|
|
|
|
<button>Button 1</button>
|
|
|
|
|
<button>Button 2</button>
|
2026-01-13 18:47:57 +00:00
|
|
|
</FocusTrap>,
|
2026-01-07 09:31:02 +00:00
|
|
|
);
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
expect(screen.getByText('Button 1')).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText('Button 2')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('focuses first element when active', () => {
|
|
|
|
|
render(
|
|
|
|
|
<FocusTrap active={true}>
|
|
|
|
|
<button>Button 1</button>
|
|
|
|
|
<button>Button 2</button>
|
2026-01-13 18:47:57 +00:00
|
|
|
</FocusTrap>,
|
2026-01-07 09:31:02 +00:00
|
|
|
);
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
const button1 = screen.getByText('Button 1');
|
|
|
|
|
expect(document.activeElement).toBe(button1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('traps focus with Tab key', () => {
|
|
|
|
|
render(
|
|
|
|
|
<FocusTrap active={true}>
|
|
|
|
|
<button>Button 1</button>
|
|
|
|
|
<button>Button 2</button>
|
2026-01-13 18:47:57 +00:00
|
|
|
</FocusTrap>,
|
2026-01-07 09:31:02 +00:00
|
|
|
);
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
const button1 = screen.getByText('Button 1');
|
|
|
|
|
const button2 = screen.getByText('Button 2');
|
2026-01-13 18:47:57 +00:00
|
|
|
|
chore(refactor/sumi-migration): commit pending changes — tests, stream server, dist_verification
- apps/web: test updates (Vitest/setup), playbackAnalyticsService, TrackGrid, serviceErrorHandler
- veza-common: logging, metrics, traits, validation, random
- veza-stream-server: audio pipeline, codecs, cache, monitoring, routes
- apps/web/dist_verification: refresh build assets (content-hashed filenames)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 18:39:18 +00:00
|
|
|
// Focus the last element
|
|
|
|
|
button2.focus();
|
2026-01-07 09:31:02 +00:00
|
|
|
expect(document.activeElement).toBe(button2);
|
2026-01-13 18:47:57 +00:00
|
|
|
|
chore(refactor/sumi-migration): commit pending changes — tests, stream server, dist_verification
- apps/web: test updates (Vitest/setup), playbackAnalyticsService, TrackGrid, serviceErrorHandler
- veza-common: logging, metrics, traits, validation, random
- veza-stream-server: audio pipeline, codecs, cache, monitoring, routes
- apps/web/dist_verification: refresh build assets (content-hashed filenames)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 18:39:18 +00:00
|
|
|
// Tab from last element should wrap to first
|
2026-01-07 09:31:02 +00:00
|
|
|
fireEvent.keyDown(document, { key: 'Tab' });
|
|
|
|
|
expect(document.activeElement).toBe(button1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('traps focus with Shift+Tab key', () => {
|
|
|
|
|
render(
|
|
|
|
|
<FocusTrap active={true}>
|
|
|
|
|
<button>Button 1</button>
|
|
|
|
|
<button>Button 2</button>
|
2026-01-13 18:47:57 +00:00
|
|
|
</FocusTrap>,
|
2026-01-07 09:31:02 +00:00
|
|
|
);
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
const button1 = screen.getByText('Button 1');
|
|
|
|
|
const button2 = screen.getByText('Button 2');
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
button1.focus();
|
|
|
|
|
fireEvent.keyDown(document, { key: 'Tab', shiftKey: true });
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
// Should wrap to last button
|
|
|
|
|
expect(document.activeElement).toBe(button2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('calls onEscape when Escape key is pressed', () => {
|
|
|
|
|
const mockOnEscape = vi.fn();
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
render(
|
|
|
|
|
<FocusTrap active={true} onEscape={mockOnEscape}>
|
|
|
|
|
<button>Button 1</button>
|
2026-01-13 18:47:57 +00:00
|
|
|
</FocusTrap>,
|
2026-01-07 09:31:02 +00:00
|
|
|
);
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
fireEvent.keyDown(document, { key: 'Escape' });
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
expect(mockOnEscape).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('does not trap focus when active is false', () => {
|
|
|
|
|
render(
|
|
|
|
|
<FocusTrap active={false}>
|
|
|
|
|
<button>Button 1</button>
|
2026-01-13 18:47:57 +00:00
|
|
|
</FocusTrap>,
|
2026-01-07 09:31:02 +00:00
|
|
|
);
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
const button1 = screen.getByText('Button 1');
|
|
|
|
|
// Focus should not be automatically set
|
|
|
|
|
expect(document.activeElement).not.toBe(button1);
|
|
|
|
|
});
|
|
|
|
|
});
|