# 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: ```tsx 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.tsx` — `login`/`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 `afterEach`** — `server.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