moex-vibe/docs/superpowers/specs/2026-06-13-frontend-test-coverage.md
Sergey Krylov 729e94b4db
Some checks failed
CI / lint (pull_request) Failing after 1m53s
CI / test (pull_request) Failing after 1m45s
CI / build (pull_request) Failing after 1m44s
CI / lint (push) Failing after 1m39s
CI / test (push) Failing after 1m40s
CI / build (push) Failing after 1m34s
test: add 95 frontend unit tests with vitest, RTL, and MSW
95 tests across 22 files covering all frontend modules:
- API layer: client, auth
- Components: BondDetails, Layout, PriceChart, ProtectedRoute, SearchBar, StockDetails
- Context: AuthContext
- Hooks: useAuth, useBond, useBondCandles, useSearch, useStock, useStockCandles, useStockDividends
- Pages: BondPage, HomePage, LoginPage, ProfilePage, RegisterPage, StockPage

Infrastructure:
- vitest + @testing-library/react + MSW v2 with 13 API handlers
- Co-located test files alongside source files
- Test utilities: setup, server, factories, test-utils
- BrowserRouter future flags for MemoryRouter test compatibility
- Root test:frontend script for workspace-wide execution
2026-06-14 08:27:15 +03:00

5.4 KiB

Frontend Unit Test Coverage

Stack

  • vitest — test runner
  • @testing-library/react + @testing-library/jest-dom + @testing-library/user-event — component testing
  • jsdom — DOM environment
  • msw (v2) — network-level mocking via setupServer

Project structure (new files)

apps/frontend/
├── vitest.config.ts
└── src/
    └── test/
        ├── setup.ts                  # jest-dom matchers, matchMedia mock
        ├── server.ts                 # MSW setupServer instance
        ├── handlers.ts              # Default MSW handlers for all endpoints
        ├── test-utils.tsx            # renderWithProviders wrapper
        └── factories.ts              # Mock data factories (share, bond, candle, user)

Conventions

Rule Value
Location ComponentName.test.tsx alongside ComponentName.tsx
Pattern AAA (Arrange -> Act -> Assert)
Describe nesting describe('Component') -> describe('when ...')
Snapshots None
Mocks MSW for API; vi.fn() for callbacks only
QueryClient Fresh instance per test via renderWithProviders with retry: false

renderWithProviders

Custom wrapper that composes:

function renderWithProviders(ui, { authState, queryClient, route } = {})
  • QueryClientProvider with fresh client (defaultOptions: queries: { retry: false })
  • AuthProvider with optional override of initial state
  • MemoryRouter with optional initial route
  • Returns { ...render(ui), queryClient }

MSW handlers (default)

All API endpoints return 200 with valid mock data by default. Handlers:

  • GET /api/v1/auth/me -> user
  • POST /api/v1/auth/login -> tokens
  • POST /api/v1/auth/register -> tokens
  • POST /api/v1/auth/refresh -> tokens
  • POST /api/v1/auth/logout -> 204
  • PATCH /api/v1/auth/me -> updated user
  • GET /api/v1/shares/:secid -> share
  • GET /api/v1/shares/:secid/candles -> candles
  • GET /api/v1/shares/:secid/dividends -> dividends
  • GET /api/v1/bonds/:secid -> bond
  • GET /api/v1/bonds/:secid/candles -> candles
  • GET /api/v1/securities/search -> search results
  • GET /api/v1/health -> ok

Errors are tested by overriding handlers per-test via server.use().

Implementation order (5 phases)

Phase 1: Infrastructure + API client

Files to create:

  • vitest.config.ts
  • src/test/setup.ts
  • src/test/server.ts
  • src/test/handlers.ts
  • src/test/test-utils.tsx
  • src/test/factories.ts

Tests:

  • api/client.test.ts — basic get/post, 401 -> refresh -> retry, persistent 401 -> throws
  • api/auth.test.ts — login, register, refresh, logout, getMe, updateProfile

Phase 2: Auth + Hooks

  • context/AuthContext.test.tsxlogin/register/logout/updateProfile update context state
  • hooks/useAuth.test.tsx — returns context values, throws outside provider
  • hooks/useSearch.test.ts — empty query, results found, no results, error
  • hooks/useStock.test.ts — data loaded, loading, error
  • hooks/useStockCandles.test.ts — data loaded, empty array, error
  • hooks/useStockDividends.test.ts — data loaded, no dividends, error
  • hooks/useBond.test.ts — data loaded, loading, error
  • hooks/useBondCandles.test.ts — data loaded, empty array, error

Phase 3: UI Components

  • components/SearchBar.test.tsx — typing triggers search, dropdown shows results, clicking result navigates, empty input hides dropdown, loading state
  • components/ProtectedRoute.test.tsx — authenticated -> renders children; unauthenticated -> redirects to /login?redirect=
  • components/StockDetails.test.tsx — renders all fields, missing optional fields, null/undefined data
  • components/BondDetails.test.tsx — renders all fields, missing optional fields, null/undefined data
  • components/PriceChart.test.tsx — renders chart with candle data, empty data array, error state
  • components/Layout.test.tsx — renders search bar, auth state toggles login/profile link, logout button works on profile

Phase 4: Pages

  • pages/LoginPage.test.tsx — renders form, validation errors, successful login redirects, server error displayed
  • pages/RegisterPage.test.tsx — renders form, validation errors, successful register redirects, password mismatch
  • pages/HomePage.test.tsx — renders welcome content and navigation links
  • pages/StockPage.test.tsx — loading skeleton, renders all sections (details, chart, dividends), error state, not found
  • pages/BondPage.test.tsx — loading skeleton, renders all sections (details, chart), error state, not found
  • pages/ProfilePage.test.tsx — loads user data, renders form, successful update, validation error, not authenticated -> redirect

Phase 5: Scripts & wiring

  • Add "test": "vitest run" and "test:watch": "vitest" to apps/frontend/package.json
  • Add "test:frontend": "npm run test -w apps/frontend" to root package.json
  • Verify: npm run test:frontend passes

Testing rules

  1. No waitFor for queries — use findBy* / findAllBy* which already await
  2. userEvent over fireEvent — simulates real interactions
  3. MSW handlers reset in afterEachserver.resetHandlers()
  4. server.close() in afterAll
  5. No snapshot tests
  6. Factory data only includes required fields — tests explicitly add optional fields when needed
  7. Each describe tests one concern — no 200-line tests