Testing Library — это инструмент для тестирования пользовательских
интерфейсов, который ориентирован на взаимодействие с компонентами так,
как это делает пользователь. В случае с Radix UI это особенно важно, так
как библиотека предоставляет низкоуровневые, полностью управляемые
компоненты, часто скрывающие внутренние элементы через
aria-* атрибуты или portal-рендеринг. Понимание специфики
работы Testing Library позволяет создавать надежные тесты без хрупкой
зависимости от структуры DOM.
Radix UI предоставляет примитивы, которые управляют состоянием
компонентов (например, Dialog, Popover,
DropdownMenu). Важно понимать, что напрямую проверять
внутренние DOM-структуры нежелательно, вместо этого ориентируются
на:
aria-expanded, aria-selected,
aria-hidden и других для подтверждения корректной работы
интерактивных элементов.npm install @testing-library/react @testing-library/jest-dom @testing-library/user-event
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
import { Dialog } from '@radix-ui/react-dialog';
Testing Library предоставляет методы, такие как
getByRole, queryByText,
findByLabelText, которые позволяют находить элементы по
семантике, а не по конкретной структуре DOM. Это критично для Radix UI,
где компоненты могут использовать порталы.
Dialog в Radix UI обычно рендерится в portal. Прямое
обращение к DOM без использования семантики может привести к ошибкам.
Правильный подход:
test('открытие и закрытие диалога', async () => {
render(
<Dialog>
<Dialog.Trigger>Открыть диалог</Dialog.Trigger>
<Dialog.Content>Содержимое диалога</Dialog.Content>
</Dialog>
);
const trigger = screen.getByRole('button', { name: /открыть диалог/i });
await userEvent.click(trigger);
const content = await screen.findByText(/содержимое диалога/i);
expect(content).toBeVisible();
// Закрытие диалога через ESC
await userEvent.keyboard('{Escape}');
expect(content).not.toBeVisible();
});
Особенности:
findByText, так как рендер через портал
может быть асинхронным.toBeVisible вместо прямого
доступа к DOM.import { DropdownMenu, DropdownMenuTrigger, DropdownMenuItem } fr om '@radix-ui/react-dropdown-menu';
test('выбор элемента в DropdownMenu', async () => {
render(
<DropdownMenu>
<DropdownMenuTrigger>Меню</DropdownMenuTrigger>
<DropdownMenuItem>Элемент 1</DropdownMenuItem>
<DropdownMenuItem>Элемент 2</DropdownMenuItem>
</DropdownMenu>
);
const trigger = screen.getByRole('button', { name: /меню/i });
await userEvent.click(trigger);
const item = screen.getByText(/элемент 2/i);
await userEvent.click(item);
expect(item).toHaveFocus();
});
Ключевые моменты:
container.querySelector.Многие компоненты Radix UI используют анимации или отложенные рендеры. Для тестирования следует применять:
findBy* методы для асинхронного поиска элементов.waitFor для ожидания изменений состояния или появления
элементов:import { waitFor } from '@testing-library/react';
await waitFor(() => expect(screen.getByText(/содержимое/i)).toBeVisible());
Это предотвращает ложные отрицательные результаты тестов.
Radix UI активно использует ARIA для управления доступностью. В тестах важно проверять корректность этих атрибутов:
const trigger = screen.getByRole('button', { name: /меню/i });
expect(trigger).toHaveAttribute('aria-expanded', 'false');
await userEvent.click(trigger);
expect(trigger).toHaveAttribute('aria-expanded', 'true');
@testing-library/user-event обеспечивает более
реалистичное моделирование действий пользователя, чем
fireEvent. Для Radix UI это критично при работе с:
getByRole,
getByLabelText) вместо CSS-классов.findBy* и
waitFor при работе с порталами и анимациями.userEvent, а не fireEvent, чтобы сохранить
естественность сценариев.Эти подходы обеспечивают надежные, поддерживаемые тесты и позволяют полностью использовать возможности компонентов Radix UI без хрупкой привязки к DOM.