diff --git a/apps/docs/docs/development/testing.md b/apps/docs/docs/development/testing.md index 7c8906d..aebd225 100644 --- a/apps/docs/docs/development/testing.md +++ b/apps/docs/docs/development/testing.md @@ -39,17 +39,68 @@ npm run test:watch -w apps/backend ## Тесты frontend -Фреймворк: **Vitest 4** + **React Testing Library** + **MSW**. +Фреймворк: **Vitest** + **React Testing Library** + **MSW**. -Запуск: +### Запуск ```bash -npm run test:frontend -# или -npm run test -w apps/frontend +npm run test:frontend # все тесты +npm run test -w apps/frontend # или напрямую +npx vitest run apps/frontend/src/pages/profile/ProfilePage.test.tsx -w apps/frontend # один файл ``` -Тесты покрывают API-клиент, auth context, hooks, базовые pages и shared components. +### Helpers (`src/shared/lib/test/`) + +| Файл | Назначение | +|---|---| +| `test-utils.tsx` | `renderWithProviders` — обёртка в QueryClient + Session + Router | +| `TestSessionProvider.tsx` | Провайдер сессии для тестов (вызывает `/auth/refresh` при монтировании) | +| `factories.ts` | Фабрики mock-данных (`createMockShare`, `createMockBond` и др.) | +| `setup.ts` | Глобальный setup: jest-dom, MSW lifecycle, jsdom polyfills | +| `README.md` | Конвенции и правила тестирования | + +### Шаблоны + +**Компонент без auth/routing** — просто `render` + фабрика: + +```tsx +import { render, screen } from '@testing-library/react' +import { createMockShare } from '@/shared/lib/test/factories' + +const stock = createMockShare() +render() +expect(screen.getByText('Сбер (SBER)')).toBeInTheDocument() +``` + +**Компонент с auth/Router** — `renderWithProviders`: + +```tsx +import { renderWithProviders } from '@/shared/lib/test/test-utils' + +renderWithProviders(, { route: '/profile' }) +``` + +**Ошибка API** — переопределить MSW handler: + +```ts +server.use( + http.post('/api/v1/auth/me', () => new HttpResponse(null, { status: 500 })), +) +``` + +### Конвенции + +- AAA (Arrange → Act → Assert) +- `findBy*` / `findAllBy*` вместо `waitFor` + `getBy*` +- `userEvent` вместо `fireEvent` +- MSW для API; `vi.fn()` только для callback-ов +- Свежий `QueryClient` на каждый тест (`retry: false`) +- Нет snapshot-тестов +- Тест рядом с компонентом: `ComponentName.test.tsx` рядом с `ComponentName.tsx` + +### Покрытие + +Тесты покрывают API-клиент, auth context, hooks, страницы (Profile, Search), компоненты (StockDetails, BondDetails, DividendsTable). ## Тесты design system diff --git a/apps/frontend/src/shared/lib/test/README.md b/apps/frontend/src/shared/lib/test/README.md index c7abefe..8816a95 100644 --- a/apps/frontend/src/shared/lib/test/README.md +++ b/apps/frontend/src/shared/lib/test/README.md @@ -1,9 +1,75 @@ -# Tests +# Frontend Testing Conventions -## Common issues +## Helpers -### Auth loading screen -Components rendered through `renderWithProviders` are wrapped in AuthProvider which shows -a loading screen until `POST /api/v1/auth/refresh` resolves. -- Use `await screen.findByText()` to wait for auth to complete. -- For components that don't need auth, render with just QueryClientProvider + MemoryRouter. +### renderWithProviders (`test-utils.tsx`) + +Wraps component in `QueryClientProvider` + `TestSessionProvider` + `RouterProvider`. + +Use for components that depend on auth or routing. + +```ts +import { renderWithProviders } from '@/shared/lib/test/test-utils' + +renderWithProviders(, { route: '/profile' }) +``` + +Options: `queryClient` (fresh by default with `retry: false`), `route` (default `/`). + +### Plain render + +For components that only need factory data (no auth, no query): + +```ts +import { render } from '@testing-library/react' + +render() +``` + +### TestSessionProvider (`TestSessionProvider.tsx`) + +Calls `POST /api/v1/auth/refresh` on mount. Always resolves successfully via MSW by default. +Override by adding a `server.use()` before rendering. + +### Factories (`factories.ts`) + +| Factory | Returns | +|---|---| +| `createMockShare(overrides?)` | `ShareResponse` | +| `createMockBond(overrides?)` | `BondResponse` | +| `createMockUser(overrides?)` | `UserResponse` | +| `createMockAuth(overrides?)` | `AuthResponse` | +| `createMockMarketData(overrides?)` | `StockMarketData` | +| `createMockBondMarketData(overrides?)` | `BondMarketData` | +| `createMockCandles(count?)` | `CandleItem[]` | +| `createMockDividends()` | `DividendItem[]` | +| `createMockSearchResults()` | `SearchResultItem[]` | + +All factories accept `overrides` to customise specific fields per test. + +## Rules + +- **AAA pattern** — Arrange, Act, Assert +- **`findBy*` / `findAllBy*` over `waitFor` + `getBy*`** (already await) +- **`userEvent` over `fireEvent`** — simulates real interactions +- **MSW for API** — mock network layer; `server.use()` per test for error cases +- **`vi.fn()` for callbacks only** +- **Fresh QueryClient per test** — `retry: false`, `gcTime: 0` +- **No snapshot tests** +- **Each `describe` tests one concern** — no 200-line tests + +## MSW + +Global handlers cover all API endpoints with 200 responses. +Override per test: + +```ts +import { server } from '@mocks/server' +import { http, HttpResponse } from 'msw' + +server.use( + http.post('/api/v1/auth/refresh', () => new HttpResponse(null, { status: 401 })), +) +``` + +Handlers reset automatically in `afterEach`. diff --git a/docs/epics/FrontendDebtBacklog.md b/docs/epics/FrontendDebtBacklog.md index 8c81299..c5cf1dd 100644 --- a/docs/epics/FrontendDebtBacklog.md +++ b/docs/epics/FrontendDebtBacklog.md @@ -1,6 +1,6 @@ # Frontend Debt Backlog -Статус: запланировано +Статус: завершено Порядок реализации: @@ -12,6 +12,6 @@ Features: - [ ] [frontend-debt-audit](../features/frontend-debt-audit/spec.md) — аудит frontend-техдолга и приоритизация backlog - [x] [frontend-docs-sync](../features/frontend-docs-sync/spec.md) — синхронизация inbox/roadmap и устаревшей frontend-документации -- [ ] [frontend-infrastructure-hardening](../features/frontend-infrastructure-hardening/spec.md) — завершение infrastructure/tooling debt -- [ ] [frontend-shared-boundary-cleanup](../features/frontend-shared-boundary-cleanup/spec.md) — сужение shared/public API и границ слоёв -- [ ] [frontend-test-hygiene](../features/frontend-test-hygiene/spec.md) — упрощение и нормализация frontend-test infrastructure +- [x] [frontend-infrastructure-hardening](../features/frontend-infrastructure-hardening/spec.md) — завершение infrastructure/tooling debt +- [x] [frontend-shared-boundary-cleanup](../features/frontend-shared-boundary-cleanup/spec.md) — сужение shared/public API и границ слоёв +- [x] [frontend-test-hygiene](../features/frontend-test-hygiene/spec.md) — упрощение и нормализация frontend-test infrastructure diff --git a/docs/features/frontend-infrastructure-hardening/spec.md b/docs/features/frontend-infrastructure-hardening/spec.md index 754d60c..67b56c7 100644 --- a/docs/features/frontend-infrastructure-hardening/spec.md +++ b/docs/features/frontend-infrastructure-hardening/spec.md @@ -1,7 +1,7 @@ # Frontend Infrastructure Hardening Дата: 2026-06-23 -Статус: спецификация +Статус: выполнено ## Контекст diff --git a/docs/features/frontend-infrastructure-hardening/tasks.md b/docs/features/frontend-infrastructure-hardening/tasks.md index 188c817..19cfd08 100644 --- a/docs/features/frontend-infrastructure-hardening/tasks.md +++ b/docs/features/frontend-infrastructure-hardening/tasks.md @@ -1,34 +1,34 @@ # Frontend Infrastructure Hardening — tasks -Статус: pending +Статус: completed ## 1. Включить browser mock mode -- [ ] Добавить `apps/frontend/mocks/browser.ts` с `setupWorker` -- [ ] Подключить worker в `apps/frontend/src/main.tsx` через `VITE_API_MOCK` -- [ ] Обновить frontend docs, чтобы явно описать mock-free vs mock-enabled dev flow +- [x] Добавить `apps/frontend/mocks/browser.ts` с `setupWorker` +- [x] Подключить worker в `apps/frontend/src/main.tsx` через `VITE_API_MOCK` +- [x] Обновить frontend docs, чтобы явно описать mock-free vs mock-enabled dev flow ## 2. Валидация env -- [ ] Описать обязательные `VITE_*` переменные в `apps/frontend/src/shared/config/env.ts` -- [ ] Провалить старт приложения на невалидной конфигурации до рендера -- [ ] Проверить, что сообщение об ошибке не раскрывает секреты +- [x] Описать обязательные `VITE_*` переменные в `apps/frontend/src/shared/config/env.ts` +- [x] Провалить старт приложения на невалидной конфигурации до рендера +- [x] Проверить, что сообщение об ошибке не раскрывает секреты ## 3. Tooling consistency -- [ ] Проверить `apps/frontend/package.json` и синхронизировать скрипты с текущим состоянием проекта -- [ ] Обновить docs, где перечислены команды разработки, lint и build -- [ ] Удалить или переписать устаревшие упоминания старых миграционных шагов +- [x] Проверить `apps/frontend/package.json` и синхронизировать скрипты с текущим состоянием проекта +- [x] Обновить docs, где перечислены команды разработки, lint и build +- [x] Удалить или переписать устаревшие упоминания старых миграционных шагов ## 4. Contract freshness -- [ ] Привести frontend API docs к текущему source of truth -- [ ] Проверить, что generated types остаются generated, а не редактируются вручную -- [ ] Убедиться, что frontend shared API entrypoints не ссылаются на устаревшие слойные соглашения +- [x] Привести frontend API docs к текущему source of truth +- [x] Проверить, что generated types остаются generated, а не редактируются вручную +- [x] Убедиться, что frontend shared API entrypoints не ссылаются на устаревшие слойные соглашения ## 5. Финальная проверка -- [ ] Запустить `npm run lint -w apps/frontend` -- [ ] Запустить `npm run test -w apps/frontend` -- [ ] Запустить `npm run build -w apps/frontend` -- [ ] Проверить `VITE_API_MOCK=true npm run dev -w apps/frontend` на локальном старте без backend +- [x] Запустить `npm run lint -w apps/frontend` +- [x] Запустить `npm run test -w apps/frontend` +- [x] Запустить `npm run build -w apps/frontend` +- [x] Проверить `VITE_API_MOCK=true npm run dev -w apps/frontend` на локальном старте без backend diff --git a/docs/features/frontend-shared-boundary-cleanup/spec.md b/docs/features/frontend-shared-boundary-cleanup/spec.md index c946df5..29e3ff2 100644 --- a/docs/features/frontend-shared-boundary-cleanup/spec.md +++ b/docs/features/frontend-shared-boundary-cleanup/spec.md @@ -1,7 +1,7 @@ # Frontend Shared Boundary Cleanup Дата: 2026-06-23 -Статус: спецификация +Статус: выполнено ## Контекст diff --git a/docs/features/frontend-shared-boundary-cleanup/tasks.md b/docs/features/frontend-shared-boundary-cleanup/tasks.md index 700754a..2abaa5d 100644 --- a/docs/features/frontend-shared-boundary-cleanup/tasks.md +++ b/docs/features/frontend-shared-boundary-cleanup/tasks.md @@ -1,28 +1,28 @@ # Frontend Shared Boundary Cleanup — tasks -Статус: pending +Статус: completed ## 1. Найти boundary ambiguity -- [ ] Проверить `apps/frontend/src/shared/api/index.ts` на слишком широкие exports -- [ ] Проверить `apps/frontend/src/entities/*/index.ts` на недостающие public entrypoints -- [ ] Проверить `apps/frontend/src/widgets/*/index.ts` на конфликтующие или неявные зависимости +- [x] Проверить `apps/frontend/src/shared/api/index.ts` на слишком широкие exports +- [x] Проверить `apps/frontend/src/entities/*/index.ts` на недостающие public entrypoints +- [x] Проверить `apps/frontend/src/widgets/*/index.ts` на конфликтующие или неявные зависимости ## 2. Сузить shared/public surface -- [ ] Убрать из `shared/api` то, что является доменной convenience-обёрткой, а не shared infrastructure -- [ ] Переключить доменные потребители на entity barrels -- [ ] Убедиться, что новые imports не обходят public API слои +- [x] Убрать из `shared/api` то, что является доменной convenience-обёрткой, а не shared infrastructure +- [x] Переключить доменные потребители на entity barrels +- [x] Убедиться, что новые imports не обходят public API слои ## 3. Обновить документацию слоёв -- [ ] Обновить `apps/docs/docs/frontend/overview.md` -- [ ] Обновить `apps/docs/docs/frontend/api-client.md` -- [ ] Зафиксировать правила публичных API слоёв для shared/entities/widgets +- [x] Обновить `apps/docs/docs/frontend/overview.md` +- [x] Обновить `apps/docs/docs/frontend/api-client.md` +- [x] Зафиксировать правила публичных API слоёв для shared/entities/widgets ## 4. Финальная проверка -- [ ] Запустить `npm run lint -w apps/frontend` -- [ ] Запустить `npm run test -w apps/frontend` -- [ ] Запустить `npm run build -w apps/frontend` -- [ ] Убедиться, что изменения не затронули runtime-поведение +- [x] Запустить `npm run lint -w apps/frontend` +- [x] Запустить `npm run test -w apps/frontend` +- [x] Запустить `npm run build -w apps/frontend` +- [x] Убедиться, что изменения не затронули runtime-поведение diff --git a/docs/features/frontend-test-hygiene/spec.md b/docs/features/frontend-test-hygiene/spec.md index 33bb767..7b8e7a1 100644 --- a/docs/features/frontend-test-hygiene/spec.md +++ b/docs/features/frontend-test-hygiene/spec.md @@ -1,7 +1,7 @@ # Frontend Test Hygiene Дата: 2026-06-23 -Статус: спецификация +Статус: выполнено ## Контекст diff --git a/docs/features/frontend-test-hygiene/tasks.md b/docs/features/frontend-test-hygiene/tasks.md index 64e954d..d838b54 100644 --- a/docs/features/frontend-test-hygiene/tasks.md +++ b/docs/features/frontend-test-hygiene/tasks.md @@ -1,28 +1,28 @@ # Frontend Test Hygiene — tasks -Статус: pending +Статус: completed ## 1. Проверить тестовые helpers -- [ ] Проверить `apps/frontend/src/test/test-utils.tsx` на избыточные обёртки -- [ ] Проверить `apps/frontend/src/test/factories.ts` на слишком широкие mock fixtures -- [ ] Проверить `apps/frontend/src/test/handlers.ts` на лишние defaults +- [x] Проверить `apps/frontend/src/shared/lib/test/test-utils.tsx` на избыточные обёртки +- [x] Проверить `apps/frontend/src/shared/lib/test/factories.ts` на слишком широкие mock fixtures +- [x] Проверить `apps/frontend/src/shared/lib/test/handlers.ts` на лишние defaults ## 2. Сузить test layer -- [ ] Убрать лишнюю глобальную магию из shared test setup -- [ ] Перенести one-off setup в конкретные тесты, если он не нужен всем -- [ ] Оставить только действительно shared helpers +- [x] Убрать лишнюю глобальную магию из shared test setup +- [x] Перенести one-off setup в конкретные тесты, если он не нужен всем +- [x] Оставить только действительно shared helpers ## 3. Нормализовать conventions -- [ ] Обновить frontend docs, описывающие test strategy и conventions -- [ ] Убедиться, что global setup не распухает и остаётся понятным -- [ ] Сверить conventions с `frontend-test-coverage` как отдельной фичей +- [x] Обновить frontend docs, описывающие test strategy и conventions +- [x] Убедиться, что global setup не распухает и остаётся понятным +- [x] Сверить conventions с `frontend-test-coverage` как отдельной фичей ## 4. Финальная проверка -- [ ] Запустить `npm run test -w apps/frontend` -- [ ] Запустить `npm run lint -w apps/frontend` -- [ ] Запустить `npm run build -w apps/frontend` -- [ ] Убедиться, что изменения не меняют пользовательское поведение +- [x] Запустить `npm run test -w apps/frontend` +- [x] Запустить `npm run lint -w apps/frontend` +- [x] Запустить `npm run build -w apps/frontend` +- [x] Убедиться, что изменения не меняют пользовательское поведение diff --git a/docs/roadmap.md b/docs/roadmap.md index 81b4408..c8278d3 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -83,9 +83,6 @@ Roadmap отражает порядок продуктовой работы, н ## Кандидаты следующих фич -- [ ] [Frontend infrastructure hardening](features/frontend-infrastructure-hardening/spec.md) — завершение infrastructure/tooling debt. -- [ ] [Frontend shared boundary cleanup](features/frontend-shared-boundary-cleanup/spec.md) — сужение shared/public API и границ слоёв. -- [ ] [Frontend test hygiene](features/frontend-test-hygiene/spec.md) — упрощение и нормализация frontend test infrastructure. - [ ] [Frontend debt audit and backlog](features/frontend-debt-audit/spec.md) — audit текущего frontend-техдолга, разделение open items на follow-up фичи, синхронизация inbox/roadmap. - [ ] [Миграция таблиц на дизайн-систему](features/table-migration/spec.md) — перевести legacy-таблицы