React 19: разделяем логику и разметку, тестируем с Shallowly и Vitest

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

В прошлой статье я показал короткий тест с передачей пропсов. Теперь разберу компонент чуть подробнее: сначала приведу код к тому виду, в котором мне удобно с ним работать, а потом проверю его через свою библиотеку Shallowly.

Начнём с панели профиля. Пока данные грузятся, она показывает сообщение. Когда профиль готов — передаёт имя в карточку. Входных пропсов у самой панели пока нет, усложнять пример ими не будем.

Исходный компонент

import { UserCard } from './UserCard';
import { useProfile } from './useProfile';

export function ProfilePanel() {
  const state = useProfile();

  if (state.status === 'loading') {
    return <p role="status">Loading profile...</p>;
  }

  return (
    <section aria-label="Profile">
      <h1>Your profile</h1>
      <UserCard name={state.profile.name} />
    </section>
  );
}

useProfile возвращает либо { status: 'loading' }, либо { status: 'ready', profile }. В примере внутри него обычный useState с локальным профилем. Запросов к бэку здесь нет. UserCard принимает name и умеет раскрывать детали через свой useState.

Тесты и архитектура

Тесты не должны диктовать архитектуру проекта. Если ради удобства одной библиотеки приходится выворачивать рабочий код наизнанку, я бы сначала посмотрел на сам способ тестирования. Но это не мешает менять архитектуру в лучшую сторону. У более понятного кода и тесты часто получаются проще.

В нашей панели вместе живут разметка и подготовка данных для неё. Сейчас это несколько строк. В рабочем компоненте к ним легко добавляются вычисления, проверки прав, обработка ошибок. Всё это приходится читать вперемешку с JSX. Вынесем подготовку данных в хук, чтобы в компоненте было видно, какие элементы он собирает и что им передаёт.

  • Separation of Concerns — разделение задач. Подготовка данных живёт в хуке, разметка — в компоненте. В JSX больше не нужно разбираться в устройстве результата useProfile.

  • Single Responsibility Principle — более узкая ответственность. Изменение разметки относится к компоненту, изменение способа подготовки данных — к setup-хуку. Это разные причины менять код.

  • Инкапсуляция в custom hook. Хук возвращает то, что нужно конкретной панели: признак загрузки и готовые пропсы карточки. Детали источника данных остаются внутри. Такой вынос логики в custom hooks описан и в документации React.

Идею подсмотрел во Vue 3 с его setup. В React предлагаю использовать соглашение useSetup[Component]: название сразу обозначает роль хука — собрать логику и подготовить данные для конкретного компонента. Для ProfilePanel это будет useSetupProfilePanel. В самом компоненте оставляем разметку.

Выносим подготовку данных

Добавим useSetupProfilePanel.ts:

import { useProfile } from './useProfile';

export function useSetupProfilePanel() {
  const state = useProfile();

  if (state.status === 'loading') {
    return {
      isLoading: true as const,
      userCardProps: null,
    };
  }

  return {
    isLoading: false as const,
    userCardProps: {
      name: state.profile.name,
    },
  };
}

Теперь исходный state.status превращается в понятный панели isLoading, а имя собирается в userCardProps. В состоянии загрузки пропсов карточки ещё нет. as const сохраняет для TypeScript значения true и false в качестве признаков двух вариантов результата. После проверки isLoading он понимает, что userCardProps уже не null.

В ProfilePanel.tsx остаётся:

import { UserCard } from './UserCard';
import { useSetupProfilePanel } from './useSetupProfilePanel';

export function ProfilePanel() {
  const { isLoading, userCardProps } = useSetupProfilePanel();

  if (isLoading) {
    return <p role="status">Loading profile...</p>;
  }

  return (
    <section aria-label="Profile">
      <h1>Your profile</h1>
      <UserCard {...userCardProps} />
    </section>
  );
}

Теперь тестируем компонент

Входных пропсов у ProfilePanel нет: тест рендерит его как <ProfilePanel />. Данные приходят из useSetupProfilePanel, а панель передаёт их дальше — в UserCard через userCardProps. Вот эту передачу и проверим: имя из хука должно попасть в карточку. Заодно проверим заголовок, подпись секции и сообщение о загрузке.

В shallow-тесте замокаю useSetupProfilePanel и задам готовый результат. Как хук получил имя и почему решил, что загрузка закончилась, здесь меня не интересует: это проверим отдельно в тестах хука.

Компонент и хук написаны на TypeScript, тесты ниже — на JavaScript. ProfilePanel.shallow.test.jsx целиком:

import { shallow } from 'shallowly';
import { describe, expect, it, vi } from 'vitest';

import { ProfilePanel } from './ProfilePanel';
import { UserCard } from './UserCard';
import { useSetupProfilePanel } from './useSetupProfilePanel';

vi.mock('./useSetupProfilePanel');

/**
 * Задаёт готовый профиль для тестов, которым не нужен особый ответ хука.
 *
 * @returns {void}
 */
const mockDefault = () => {
  useSetupProfilePanel.mockReturnValue({
    isLoading: false,
    userCardProps: { name: 'имя по умолчанию' },
  });
};

describe('						
Источник: https://habr.com/ru/articles/1095628/


Интересные статьи

Интересные статьи

Bitrix24 и amoCRM — две доминирующие CRM в России — отправляют email принципиально разным образом, но имеют общую проблему: ни одна из них не показывает, дошло ли письмо до инбокса получателя. Зелёный...
Представлена Кипрская ассоциация разработчиков игр — Cyprus Game Developers Association или CYGMA. Ассоциация будет сфокусирована на повышении международной видимости игровых команд, работающих на тер...
Вести бизнес без аналитики — все равно что управлять кораблем вслепую. Раньше собрать полноценный отчет прямо в Битрикс24 было непросто: приходилось выгружать данные и обрабатывать их в сторонних серв...
В настоящее время интерференция наиболее актуальна для диапазона 2.4 ГГц, который перенасыщен работой вездесущих точек доступа. В статье я рассмотрел, какие возможности по исследованию радиочастотно...
В наше время многие компании используют различные сервисы для связи с клиентами и партнерами. Один из самых популярных мессенджеров - WhatsApp, стал незаменимым инструментом коммуникации в бизнесе. Од...