diff --git a/frontend/docs/MSW_AUTO_GENERATION.md b/frontend/docs/MSW_AUTO_GENERATION.md
index 6accc7f..677fa6a 100644
--- a/frontend/docs/MSW_AUTO_GENERATION.md
+++ b/frontend/docs/MSW_AUTO_GENERATION.md
@@ -28,11 +28,13 @@ src/mocks/handlers/
### 1. Automatic Generation
When you run:
+
```bash
npm run generate:api
```
The system:
+
1. Fetches `/api/v1/openapi.json` from backend
2. Generates TypeScript API client (`src/lib/api/generated/`)
3. **NEW:** Generates MSW handlers (`src/mocks/handlers/generated.ts`)
@@ -42,12 +44,14 @@ The system:
The generator (`scripts/generate-msw-handlers.ts`) creates handlers with:
**Smart Response Logic:**
+
- **Auth endpoints** → Use `validateCredentials()` and `setCurrentUser()`
- **User endpoints** → Use `currentUser` and mock data
- **Admin endpoints** → Check `is_superuser` + return paginated data
- **Generic endpoints** → Return success response
**Example Generated Handler:**
+
```typescript
/**
* Login
@@ -91,10 +95,7 @@ export const overrideHandlers = [
http.post(`${API_BASE_URL}/api/v1/auth/login`, async ({ request }) => {
// 10% chance of rate limit
if (Math.random() < 0.1) {
- return HttpResponse.json(
- { detail: 'Too many login attempts' },
- { status: 429 }
- );
+ return HttpResponse.json({ detail: 'Too many login attempts' }, { status: 429 });
}
// Fall through to generated handler
}),
@@ -105,10 +106,7 @@ export const overrideHandlers = [
// Custom validation logic
if (body.email.endsWith('@blocked.com')) {
- return HttpResponse.json(
- { detail: 'Email domain not allowed' },
- { status: 400 }
- );
+ return HttpResponse.json({ detail: 'Email domain not allowed' }, { status: 400 });
}
// Fall through to generated handler
@@ -124,6 +122,7 @@ Overrides are applied FIRST, so they take precedence over generated handlers.
### ✅ Zero Manual Work
**Before:**
+
```bash
# Backend adds new endpoint
# 1. Run npm run generate:api
@@ -134,6 +133,7 @@ Overrides are applied FIRST, so they take precedence over generated handlers.
```
**After:**
+
```bash
# Backend adds new endpoint
npm run generate:api # Done! MSW auto-synced
@@ -160,6 +160,7 @@ import { adminStats } from '../data/stats';
### ✅ Batteries Included
Generated handlers include:
+
- ✅ Network delays (300ms - realistic UX)
- ✅ Auth checks (401/403 responses)
- ✅ Pagination support
@@ -218,6 +219,7 @@ If generated handler doesn't fit your needs:
3. **Override takes precedence** automatically
Example:
+
```typescript
// overrides.ts
export const overrideHandlers = [
@@ -227,10 +229,7 @@ export const overrideHandlers = [
// Simulate 2FA requirement for admin users
if (body.email.includes('admin') && !body.two_factor_code) {
- return HttpResponse.json(
- { detail: 'Two-factor authentication required' },
- { status: 403 }
- );
+ return HttpResponse.json({ detail: 'Two-factor authentication required' }, { status: 403 });
}
// Fall through to generated handler
@@ -254,6 +253,7 @@ export const demoUser: UserResponse = {
```
**To update:**
+
1. Edit `data/*.ts` files
2. Handlers automatically use updated data
3. No regeneration needed!
@@ -263,6 +263,7 @@ export const demoUser: UserResponse = {
The generator (`scripts/generate-msw-handlers.ts`) does:
1. **Parse OpenAPI spec**
+
```typescript
const spec = JSON.parse(fs.readFileSync(specPath, 'utf-8'));
```
@@ -284,12 +285,14 @@ The generator (`scripts/generate-msw-handlers.ts`) does:
### Generated handler doesn't work
**Check:**
+
1. Is backend running? (`npm run generate:api` requires backend)
2. Check console for `[MSW]` warnings
3. Verify `generated.ts` exists and has your endpoint
4. Check path parameters match exactly
**Debug:**
+
```bash
# See what endpoints were generated
cat src/mocks/handlers/generated.ts | grep "http\."
diff --git a/frontend/scripts/sync-msw-with-openapi.md b/frontend/scripts/sync-msw-with-openapi.md
index 1cd2b5c..c160990 100644
--- a/frontend/scripts/sync-msw-with-openapi.md
+++ b/frontend/scripts/sync-msw-with-openapi.md
@@ -1,6 +1,7 @@
# Keeping MSW Handlers Synced with OpenAPI Spec
## Problem
+
MSW handlers can drift out of sync with the backend API as it evolves.
## Solution Options
@@ -60,6 +61,7 @@ Add to `package.json`:
Our MSW handlers currently cover:
**Auth Endpoints:**
+
- POST `/api/v1/auth/register`
- POST `/api/v1/auth/login`
- POST `/api/v1/auth/refresh`
@@ -70,6 +72,7 @@ Our MSW handlers currently cover:
- POST `/api/v1/auth/change-password`
**User Endpoints:**
+
- GET `/api/v1/users/me`
- PATCH `/api/v1/users/me`
- DELETE `/api/v1/users/me`
@@ -80,6 +83,7 @@ Our MSW handlers currently cover:
- DELETE `/api/v1/sessions/:id`
**Admin Endpoints:**
+
- GET `/api/v1/admin/stats`
- GET `/api/v1/admin/users`
- GET `/api/v1/admin/users/:id`
diff --git a/frontend/src/components/demo/DemoModeBanner.tsx b/frontend/src/components/demo/DemoModeBanner.tsx
index bc43b6c..62b112b 100644
--- a/frontend/src/components/demo/DemoModeBanner.tsx
+++ b/frontend/src/components/demo/DemoModeBanner.tsx
@@ -11,11 +11,7 @@ import { useState } from 'react';
import config from '@/config/app.config';
import { Sparkles } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from '@/components/ui/popover';
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
export function DemoModeBanner() {
// Only show in demo mode
diff --git a/frontend/src/components/docs/CodeBlock.tsx b/frontend/src/components/docs/CodeBlock.tsx
index 92d3658..eb1ffc7 100644
--- a/frontend/src/components/docs/CodeBlock.tsx
+++ b/frontend/src/components/docs/CodeBlock.tsx
@@ -55,11 +55,7 @@ export function CodeBlock({ children, className, title }: CodeBlockProps) {
onClick={handleCopy}
aria-label="Copy code"
>
- {copied ? (
-
- ) : (
-
- )}
+ {copied ? : }
diff --git a/frontend/src/components/docs/MarkdownContent.tsx b/frontend/src/components/docs/MarkdownContent.tsx
index 8b3db7b..d1561a4 100644
--- a/frontend/src/components/docs/MarkdownContent.tsx
+++ b/frontend/src/components/docs/MarkdownContent.tsx
@@ -136,7 +136,10 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
return (
{children}
@@ -147,7 +150,10 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
return (
{children}
diff --git a/frontend/src/mocks/browser.ts b/frontend/src/mocks/browser.ts
index 6a9af04..d277662 100644
--- a/frontend/src/mocks/browser.ts
+++ b/frontend/src/mocks/browser.ts
@@ -68,7 +68,12 @@ export async function startMockServiceWorker() {
}
// Ignore locale routes (Next.js i18n)
- if (url.pathname === '/en' || url.pathname === '/it' || url.pathname.startsWith('/en/') || url.pathname.startsWith('/it/')) {
+ if (
+ url.pathname === '/en' ||
+ url.pathname === '/it' ||
+ url.pathname.startsWith('/en/') ||
+ url.pathname.startsWith('/it/')
+ ) {
return;
}
diff --git a/frontend/tests/components/charts/RegistrationActivityChart.test.tsx b/frontend/tests/components/charts/RegistrationActivityChart.test.tsx
index 12a6c34..37b70b8 100644
--- a/frontend/tests/components/charts/RegistrationActivityChart.test.tsx
+++ b/frontend/tests/components/charts/RegistrationActivityChart.test.tsx
@@ -8,80 +8,80 @@ import type { RegistrationActivityData } from '@/components/charts/RegistrationA
// Mock recharts to avoid rendering issues in tests
jest.mock('recharts', () => {
- const OriginalModule = jest.requireActual('recharts');
- return {
- ...OriginalModule,
- ResponsiveContainer: ({ children }: { children: React.ReactNode }) => (
- {children}
- ),
- };
+ const OriginalModule = jest.requireActual('recharts');
+ return {
+ ...OriginalModule,
+ ResponsiveContainer: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+ };
});
describe('RegistrationActivityChart', () => {
- const mockData: RegistrationActivityData[] = [
- { date: 'Jan 1', registrations: 5 },
- { date: 'Jan 2', registrations: 8 },
- { date: 'Jan 3', registrations: 3 },
+ const mockData: RegistrationActivityData[] = [
+ { date: 'Jan 1', registrations: 5 },
+ { date: 'Jan 2', registrations: 8 },
+ { date: 'Jan 3', registrations: 3 },
+ ];
+
+ it('renders chart card with title and description', () => {
+ render();
+
+ expect(screen.getByText('User Registration Activity')).toBeInTheDocument();
+ expect(screen.getByText('New user registrations over the last 14 days')).toBeInTheDocument();
+ });
+
+ it('renders chart with provided data', () => {
+ render();
+
+ expect(screen.getByTestId('responsive-container')).toBeInTheDocument();
+ });
+
+ it('shows empty state when no data is provided', () => {
+ render();
+
+ expect(screen.getByText('User Registration Activity')).toBeInTheDocument();
+ expect(screen.getByText('No registration data available')).toBeInTheDocument();
+ expect(screen.queryByTestId('responsive-container')).not.toBeInTheDocument();
+ });
+
+ it('shows empty state when data array is empty', () => {
+ render();
+
+ expect(screen.getByText('User Registration Activity')).toBeInTheDocument();
+ expect(screen.getByText('No registration data available')).toBeInTheDocument();
+ expect(screen.queryByTestId('responsive-container')).not.toBeInTheDocument();
+ });
+
+ it('shows empty state when data has no registrations', () => {
+ const emptyData = [
+ { date: 'Jan 1', registrations: 0 },
+ { date: 'Jan 2', registrations: 0 },
];
+ render();
- it('renders chart card with title and description', () => {
- render();
+ expect(screen.getByText('User Registration Activity')).toBeInTheDocument();
+ expect(screen.getByText('No registration data available')).toBeInTheDocument();
+ expect(screen.queryByTestId('responsive-container')).not.toBeInTheDocument();
+ });
- expect(screen.getByText('User Registration Activity')).toBeInTheDocument();
- expect(screen.getByText('New user registrations over the last 14 days')).toBeInTheDocument();
- });
+ it('shows loading state', () => {
+ render();
- it('renders chart with provided data', () => {
- render();
+ expect(screen.getByText('User Registration Activity')).toBeInTheDocument();
- expect(screen.getByTestId('responsive-container')).toBeInTheDocument();
- });
+ // Chart should not be visible when loading
+ expect(screen.queryByTestId('responsive-container')).not.toBeInTheDocument();
+ expect(screen.queryByText('No registration data available')).not.toBeInTheDocument();
+ });
- it('shows empty state when no data is provided', () => {
- render();
+ it('shows error state', () => {
+ render();
- expect(screen.getByText('User Registration Activity')).toBeInTheDocument();
- expect(screen.getByText('No registration data available')).toBeInTheDocument();
- expect(screen.queryByTestId('responsive-container')).not.toBeInTheDocument();
- });
+ expect(screen.getByText('User Registration Activity')).toBeInTheDocument();
+ expect(screen.getByText('Failed to load chart data')).toBeInTheDocument();
- it('shows empty state when data array is empty', () => {
- render();
-
- expect(screen.getByText('User Registration Activity')).toBeInTheDocument();
- expect(screen.getByText('No registration data available')).toBeInTheDocument();
- expect(screen.queryByTestId('responsive-container')).not.toBeInTheDocument();
- });
-
- it('shows empty state when data has no registrations', () => {
- const emptyData = [
- { date: 'Jan 1', registrations: 0 },
- { date: 'Jan 2', registrations: 0 },
- ];
- render();
-
- expect(screen.getByText('User Registration Activity')).toBeInTheDocument();
- expect(screen.getByText('No registration data available')).toBeInTheDocument();
- expect(screen.queryByTestId('responsive-container')).not.toBeInTheDocument();
- });
-
- it('shows loading state', () => {
- render();
-
- expect(screen.getByText('User Registration Activity')).toBeInTheDocument();
-
- // Chart should not be visible when loading
- expect(screen.queryByTestId('responsive-container')).not.toBeInTheDocument();
- expect(screen.queryByText('No registration data available')).not.toBeInTheDocument();
- });
-
- it('shows error state', () => {
- render();
-
- expect(screen.getByText('User Registration Activity')).toBeInTheDocument();
- expect(screen.getByText('Failed to load chart data')).toBeInTheDocument();
-
- // Chart should not be visible when error
- expect(screen.queryByTestId('responsive-container')).not.toBeInTheDocument();
- });
+ // Chart should not be visible when error
+ expect(screen.queryByTestId('responsive-container')).not.toBeInTheDocument();
+ });
});
diff --git a/frontend/tests/components/home/DemoCredentialsModal.test.tsx b/frontend/tests/components/home/DemoCredentialsModal.test.tsx
index 5810ea9..d999897 100644
--- a/frontend/tests/components/home/DemoCredentialsModal.test.tsx
+++ b/frontend/tests/components/home/DemoCredentialsModal.test.tsx
@@ -86,7 +86,9 @@ describe('DemoCredentialsModal', () => {
fireEvent.click(adminCopyButton!);
await waitFor(() => {
- expect(navigator.clipboard.writeText).toHaveBeenCalledWith('admin@example.com\nAdminPass1234!');
+ expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
+ 'admin@example.com\nAdminPass1234!'
+ );
const copiedButtons = screen.getAllByRole('button');
const copiedButton = copiedButtons.find((btn) => btn.textContent?.includes('Copied!'));
expect(copiedButton).toBeInTheDocument();