118 lines
4.6 KiB
Markdown
118 lines
4.6 KiB
Markdown
|
|
# Storybook Contract (Veza Web)
|
|||
|
|
|
|||
|
|
This document defines the development contract for Storybook in `apps/web`: how to write and maintain stories without breaking the deterministic, zero-leak setup.
|
|||
|
|
|
|||
|
|
## 1. Use the global decorator only
|
|||
|
|
|
|||
|
|
**All stories are wrapped by `StorybookDecorator`** (see `.storybook/decorators.tsx`). It provides:
|
|||
|
|
|
|||
|
|
- `I18nextProvider` (i18n)
|
|||
|
|
- `ThemeProvider` (light/dark via Storybook backgrounds)
|
|||
|
|
- `QueryClientProvider` (React Query, `retry: false`, `staleTime: Infinity`)
|
|||
|
|
- `ToastProvider`
|
|||
|
|
- `AudioProvider`
|
|||
|
|
- `AuthProvider` (mock logged-in user by default)
|
|||
|
|
- `MemoryRouter` (initial route configurable via parameters)
|
|||
|
|
|
|||
|
|
**You must not:**
|
|||
|
|
|
|||
|
|
- Import or wrap stories with these providers yourself.
|
|||
|
|
- Import app-level providers from `src/` (e.g. `AuthProvider`, `QueryClientProvider`) inside a story file.
|
|||
|
|
|
|||
|
|
**Why:** A single decorator avoids duplicate providers, inconsistent configs, and hooks running outside their context (`useAuth`, `useNavigate`, `useQueryClient`, `useToast`, etc.).
|
|||
|
|
|
|||
|
|
### Route-dependent stories
|
|||
|
|
|
|||
|
|
If the component under test uses `useParams()`, `useSearchParams()`, or a specific path, set the route in the story’s `parameters`:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export const MyStory: StoryObj<typeof MyPage> = {
|
|||
|
|
parameters: {
|
|||
|
|
router: {
|
|||
|
|
initialEntries: ['/tracks/123'],
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Do not add a local `MemoryRouter` unless you need a different base path for that story only; the global decorator already provides `MemoryRouter` and will use `parameters.router.initialEntries` when present.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Mock all API calls with MSW
|
|||
|
|
|
|||
|
|
Storybook runs with **MSW (Mock Service Worker)** and is configured with **`onUnhandledRequest: 'error'`**. Any request that is not handled by a mock will cause an error in the console and can fail the story/audit.
|
|||
|
|
|
|||
|
|
**You must:**
|
|||
|
|
|
|||
|
|
- Ensure every API call used by your story is covered by a handler in `src/mocks/handlers.ts` (or by story-level `parameters.msw.handlers` overrides).
|
|||
|
|
- Use relative API base (e.g. `VITE_API_URL=/api/v1` in Storybook scripts) so requests are same-origin and intercepted by MSW.
|
|||
|
|
|
|||
|
|
**Adding a mock for a new feature:**
|
|||
|
|
|
|||
|
|
1. Open `apps/web/src/mocks/handlers.ts`.
|
|||
|
|
2. Add an `http.get()` or `http.post()` (etc.) handler for the endpoint (e.g. `*/api/v1/your/resource`).
|
|||
|
|
3. Return a `HttpResponse.json(...)` with a shape that matches what your app expects.
|
|||
|
|
4. If a story needs a different response (e.g. 404, empty list), override in that story:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
parameters: {
|
|||
|
|
msw: {
|
|||
|
|
handlers: [
|
|||
|
|
http.get('*/api/v1/your/resource', () => HttpResponse.json({ data: [] })),
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**You must not:**
|
|||
|
|
|
|||
|
|
- Rely on a real backend when running or building Storybook.
|
|||
|
|
- Add stories that trigger unhandled network requests; they will fail under the current strict MSW setup.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Hierarchy and tags
|
|||
|
|
|
|||
|
|
Stories are grouped in the sidebar as follows:
|
|||
|
|
|
|||
|
|
| Segment | Usage |
|
|||
|
|
|------------------|--------|
|
|||
|
|
| **App/Pages** | Full-page components (e.g. Dashboard, Login, TrackDetail). |
|
|||
|
|
| **App/Layouts** | Global layout components (Header, Sidebar, DashboardLayout). |
|
|||
|
|
| **Components/UI**| Atomic UI (Button, Alert, Input). |
|
|||
|
|
| **Components/Features** | Feature-specific and complex components (Playlists, Chat, Studio, etc.). |
|
|||
|
|
| **Docs/Failures**| Error states and error boundaries (ErrorBoundary, NotFoundPage, ServerErrorPage). |
|
|||
|
|
|
|||
|
|
Set the meta `title` in your `.stories.tsx` accordingly (e.g. `title: 'App/Pages/Dashboard/DashboardPage'`, `title: 'Components/UI/Button'`).
|
|||
|
|
|
|||
|
|
Autodocs are enabled globally; each component gets a generated docs page.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Verification
|
|||
|
|
|
|||
|
|
- **Audit:** From `apps/web`, build Storybook, serve the static build on port 6007, then run:
|
|||
|
|
```bash
|
|||
|
|
npm run build-storybook
|
|||
|
|
npx serve -s storybook-static -p 6007 &
|
|||
|
|
node scripts/audit-storybook.js
|
|||
|
|
```
|
|||
|
|
The audit must report **0 application errors** (console errors, page errors, unhandled network failures). MSW strict mode ensures any unhandled request is treated as an error.
|
|||
|
|
|
|||
|
|
- **Local run:** Use `npm run storybook` (port 6006). Ensure no console errors when opening the stories you changed.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Summary
|
|||
|
|
|
|||
|
|
| Rule | Action |
|
|||
|
|
|------|--------|
|
|||
|
|
| Providers | Use only `StorybookDecorator`; do not import app providers in stories. |
|
|||
|
|
| Routes | Use `parameters.router.initialEntries` for route-dependent stories. |
|
|||
|
|
| API | Mock every request in `handlers.ts` or per-story `parameters.msw.handlers`. |
|
|||
|
|
| Hierarchy | Use `App/Pages`, `App/Layouts`, `Components/UI`, `Components/Features`, `Docs/Failures`. |
|
|||
|
|
| Strict MSW | `onUnhandledRequest: 'error'` is on; add mocks for any new API used by a story. |
|
|||
|
|
|
|||
|
|
This contract keeps Storybook deterministic, free of network leaks, and safe for future changes.
|