# Frontend tests — Vitest + Testing Library + MSW

Tests live in `tests/{module}/` (mirroring `web/src/`), pattern `*.test.{ts,tsx}`. The harness
(`tests/test-utils.tsx`, `tests/setup.ts`, `tests/msw/*`) provides a shared `renderWithProviders`
(MemoryRouter + React Query + i18n) and **MSW** for API mocking. Component `.tsx` files
`import React from 'react'`.

## Golden rules

- **`vi.mock(...)` is HOISTED — declare it ABOVE the import of the mocked module** (the page/hook
  under test). Never after.
- **Mock `react-i18next`** in component/hook tests (no real i18n instance → no
  `NO_I18NEXT_INSTANCE` warning); `t(key)` returns the key, so assert on the key.
- **`MemoryRouter`** with `initialEntries` — never `BrowserRouter`.
- Testing Library queries: `getByRole` / `findByRole` (async) / `queryBy*` (absence).
- `vi.clearAllMocks()` in `beforeEach`.
- Random ids: `crypto.randomUUID()` — never a hardcoded literal.
- **No stubs** — never `expect(true).toBe(true)`.

## Canonical skeleton (page/component)

```tsx
import React from 'react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';

// Hoisted — above the page import.
vi.mock('react-i18next', () => ({
  useTranslation: () => ({ t: (k: string) => k, i18n: { language: 'en', changeLanguage: vi.fn() } }),
}));
vi.mock('@/api/orders/useOrders', () => ({
  useOrders: () => ({ data: { items: [], total: 0 }, isLoading: false, error: null }),
}));

import { OrderListPage } from '@/pages/orders/order/OrderListPage';

describe('OrderListPage', () => {
  beforeEach(() => vi.clearAllMocks());

  it('renders the heading', async () => {
    render(<MemoryRouter><OrderListPage /></MemoryRouter>);
    expect(await screen.findByRole('heading')).toBeInTheDocument();
  });
});
```

## API mocking — MSW (kept; do not mock the api client directly here)

`tests/msw/handlers.ts` holds the defaults; override per test with `server.use(...)`:

```ts
import { http, HttpResponse } from 'msw';
import { server } from '../msw/server';

server.use(
  http.get('/api/orders/orders', () => HttpResponse.json({ items: [{ id: crypto.randomUUID() }], total: 1 })),
);
```

## Hooks

```ts
import { renderHook, waitFor } from '@testing-library/react';

const { result } = renderHook(() => useOrders());
await waitFor(() => expect(result.current.isLoading).toBe(false));
expect(result.current.data?.items).toHaveLength(0);
```

## Anti-patterns

- ❌ `vi.mock(...)` AFTER importing the mocked module.
- ❌ `BrowserRouter` in a test — `MemoryRouter`.
- ❌ Wrapping a real Auth/Tenant provider — mock the hook.
- ❌ `await waitFor(() => {})` with no assertion (no-op).
- ❌ `expect(true).toBe(true)`.
