Some checks failed
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
128 lines
5.4 KiB
Markdown
128 lines
5.4 KiB
Markdown
# 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
|