Unit тесты компонентов

Unit тесты позволяют проверять отдельные компоненты интерфейса на корректность их работы без необходимости рендерить весь интерфейс приложения. В контексте Radix UI это особенно важно, так как библиотека предоставляет низкоуровневые, но мощные компоненты с богатым набором состояний и событий.

Для unit тестов чаще всего используют комбинацию Jest для запуска тестов и @testing-library/react для имитации пользовательских взаимодействий и проверки состояния DOM.


Настройка окружения для тестирования

  1. Установка зависимостей:
npm install --save-dev jest @testing-library/react @testing-library/jest-dom
  1. Конфигурация Jest: Создаётся файл jest.config.js с базовыми настройками:
module.exports = {
  testEnvironment: "jsdom",
  setupFilesAfterEnv: ["@testing-library/jest-dom/extend-expect"],
  moduleFileExtensions: ["js", "jsx", "ts", "tsx"],
};
  1. Подключение полифиллов: Компоненты Radix UI используют Portals и ARIA-атрибуты. Для корректной работы тестов нужно убедиться, что DOM-среда эмулируется полностью. Обычно jsdom покрывает все основные случаи.

Тестирование базовых компонентов

Пример: Radix UI Dialog

Компонент Dialog состоит из Dialog.Root, Dialog.Trigger и Dialog.Content. Unit тест должен проверять:

  • правильное открытие и закрытие окна
  • рендер контента после открытия
  • корректное управление фокусом и доступностью
import { render, screen, fireEvent } from "@testing-library/react";
import * as Dialog from "@radix-ui/react-dialog";

test("Dialog открывается и закрывается", () => {
  render(
    <Dialog.Root>
      <Dialog.Trigger>Открыть</Dialog.Trigger>
      <Dialog.Content>
        <p>Содержимое диалога</p>
      </Dialog.Content>
    </Dialog.Root>
  );

  expect(screen.queryByText("Содержимое диалога")).toBeNull();

  fireEvent.click(screen.getByText("Открыть"));

  expect(screen.getByText("Содержимое диалога")).toBeVisible();

  fireEvent.keyDown(document, { key: "Escape" });

  expect(screen.queryByText("Содержимое диалога")).toBeNull();
});

Ключевые моменты:

  • fireEvent имитирует действия пользователя
  • Проверка ARIA-атрибутов помогает убедиться, что компонент доступен для пользователей с ассистивными технологиями

Работа с асинхронными состояниями

Многие компоненты Radix UI, такие как Popover или Tooltip, используют анимации и таймауты. Для тестов это требует применения асинхронных утилит:

import { render, screen, waitFor, fireEvent } from "@testing-library/react";
import * as Tooltip from "@radix-ui/react-tooltip";

test("Tooltip отображается после наведения", async () => {
  render(
    <Tooltip.Provider>
      <Tooltip.Root>
        <Tooltip.Trigger>Наведи на меня</Tooltip.Trigger>
        <Tooltip.Content>Подсказка</Tooltip.Content>
      </Tooltip.Root>
    </Tooltip.Provider>
  );

  fireEvent.mouseOver(screen.getByText("Наведи на меня"));

  await waitFor(() => {
    expect(screen.getByText("Подсказка")).toBeVisible();
  });
});

Особенности:

  • Использование waitFor позволяет дождаться окончания анимаций или обновлений состояния
  • Tooltip.Provider обязателен для корректного рендеринга порталов

Тестирование сложных интерактивных компонентов

Radix UI Tabs

Компонент Tabs имеет несколько панелей и управляет состоянием активного таба. Важно тестировать:

  • переключение вкладок
  • сохранение состояния после повторного рендера
  • правильное управление фокусом
import { render, screen, fireEvent } from "@testing-library/react";
import * as Tabs from "@radix-ui/react-tabs";

test("Tabs переключаются корректно", () => {
  render(
    <Tabs.Root defaultValue="tab1">
      <Tabs.List>
        <Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
        <Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
      </Tabs.List>
      <Tabs.Content value="tab1">Содержимое 1</Tabs.Content>
      <Tabs.Content value="tab2">Содержимое 2</Tabs.Content>
    </Tabs.Root>
  );

  expect(screen.getByText("Содержимое 1")).toBeVisible();
  expect(screen.queryByText("Содержимое 2")).toBeNull();

  fireEvent.click(screen.getByText("Tab 2"));

  expect(screen.getByText("Содержимое 2")).toBeVisible();
  expect(screen.queryByText("Содержимое 1")).toBeNull();
});

Проверка ARIA-атрибутов и доступности

Radix UI строго следует стандартам ARIA. Unit тесты могут проверять наличие нужных атрибутов:

import { render, screen } from "@testing-library/react";
import * as Toggle from "@radix-ui/react-toggle";

test("Toggle имеет правильный ARIA state", () => {
  render(<Toggle.Root>Вкл/Выкл</Toggle.Root>);
  const toggle = screen.getByRole("button");

  expect(toggle).toHaveAttribute("aria-pressed", "false");

  toggle.click();

  expect(toggle).toHaveAttribute("aria-pressed", "true");
});

Преимущества такой проверки:

  • Гарантирует доступность интерфейса
  • Позволяет выявлять ошибки при обновлениях компонентов

Мокирование событий и внешних зависимостей

Иногда компоненты используют window или контекст, который не нужен в unit тестах. В таких случаях используют jest.mock:

jest.mock("some-external-library", () => ({
  someFunction: jest.fn(() => "mocked value"),
}));

Это позволяет изолировать тестируемый компонент и проверять только его логику.


Рекомендации по структуре тестов

  • Разделять рендеринг, взаимодействие и проверку состояния
  • Использовать screen и селекторы, ориентированные на текст или роли
  • Проверять видимость, ARIA-атрибуты, изменение классов/стилей
  • Оборачивать компоненты, использующие провайдеры или порталы, в соответствующие контексты

Unit тесты компонентов Radix UI позволяют создавать стабильный и доступный интерфейс, минимизировать ошибки при рефакторинге и гарантировать корректную работу интерактивных элементов. Правильная организация тестов обеспечивает как покрытие базовой функциональности, так и проверку сложных состояний компонентов.