Refactor tests, documentation, and component code for consistent formatting and improved readability

- Reformatted test files (`RegistrationActivityChart.test.tsx`, `DemoCredentialsModal.test.tsx`) for indentation consistency.
- Reduced inline style verbosity across components and docs (`DemoModeBanner`, `CodeBlock`, `MarkdownContent`).
- Enhanced Markdown documentation (`sync-msw-with-openapi.md`, `MSW_AUTO_GENERATION.md`) with spacing updates for improved clarity.
- Updated MSW configuration to simplify locale route handling in `browser.ts`.
This commit is contained in:
Felipe Cardoso
2025-11-24 20:25:40 +01:00
parent 3bf28aa121
commit e79215b4de
8 changed files with 103 additions and 91 deletions

View File

@@ -28,11 +28,13 @@ src/mocks/handlers/
### 1. Automatic Generation ### 1. Automatic Generation
When you run: When you run:
```bash ```bash
npm run generate:api npm run generate:api
``` ```
The system: The system:
1. Fetches `/api/v1/openapi.json` from backend 1. Fetches `/api/v1/openapi.json` from backend
2. Generates TypeScript API client (`src/lib/api/generated/`) 2. Generates TypeScript API client (`src/lib/api/generated/`)
3. **NEW:** Generates MSW handlers (`src/mocks/handlers/generated.ts`) 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: The generator (`scripts/generate-msw-handlers.ts`) creates handlers with:
**Smart Response Logic:** **Smart Response Logic:**
- **Auth endpoints** → Use `validateCredentials()` and `setCurrentUser()` - **Auth endpoints** → Use `validateCredentials()` and `setCurrentUser()`
- **User endpoints** → Use `currentUser` and mock data - **User endpoints** → Use `currentUser` and mock data
- **Admin endpoints** → Check `is_superuser` + return paginated data - **Admin endpoints** → Check `is_superuser` + return paginated data
- **Generic endpoints** → Return success response - **Generic endpoints** → Return success response
**Example Generated Handler:** **Example Generated Handler:**
```typescript ```typescript
/** /**
* Login * Login
@@ -91,10 +95,7 @@ export const overrideHandlers = [
http.post(`${API_BASE_URL}/api/v1/auth/login`, async ({ request }) => { http.post(`${API_BASE_URL}/api/v1/auth/login`, async ({ request }) => {
// 10% chance of rate limit // 10% chance of rate limit
if (Math.random() < 0.1) { if (Math.random() < 0.1) {
return HttpResponse.json( return HttpResponse.json({ detail: 'Too many login attempts' }, { status: 429 });
{ detail: 'Too many login attempts' },
{ status: 429 }
);
} }
// Fall through to generated handler // Fall through to generated handler
}), }),
@@ -105,10 +106,7 @@ export const overrideHandlers = [
// Custom validation logic // Custom validation logic
if (body.email.endsWith('@blocked.com')) { if (body.email.endsWith('@blocked.com')) {
return HttpResponse.json( return HttpResponse.json({ detail: 'Email domain not allowed' }, { status: 400 });
{ detail: 'Email domain not allowed' },
{ status: 400 }
);
} }
// Fall through to generated handler // Fall through to generated handler
@@ -124,6 +122,7 @@ Overrides are applied FIRST, so they take precedence over generated handlers.
### ✅ Zero Manual Work ### ✅ Zero Manual Work
**Before:** **Before:**
```bash ```bash
# Backend adds new endpoint # Backend adds new endpoint
# 1. Run npm run generate:api # 1. Run npm run generate:api
@@ -134,6 +133,7 @@ Overrides are applied FIRST, so they take precedence over generated handlers.
``` ```
**After:** **After:**
```bash ```bash
# Backend adds new endpoint # Backend adds new endpoint
npm run generate:api # Done! MSW auto-synced npm run generate:api # Done! MSW auto-synced
@@ -160,6 +160,7 @@ import { adminStats } from '../data/stats';
### ✅ Batteries Included ### ✅ Batteries Included
Generated handlers include: Generated handlers include:
- ✅ Network delays (300ms - realistic UX) - ✅ Network delays (300ms - realistic UX)
- ✅ Auth checks (401/403 responses) - ✅ Auth checks (401/403 responses)
- ✅ Pagination support - ✅ Pagination support
@@ -218,6 +219,7 @@ If generated handler doesn't fit your needs:
3. **Override takes precedence** automatically 3. **Override takes precedence** automatically
Example: Example:
```typescript ```typescript
// overrides.ts // overrides.ts
export const overrideHandlers = [ export const overrideHandlers = [
@@ -227,10 +229,7 @@ export const overrideHandlers = [
// Simulate 2FA requirement for admin users // Simulate 2FA requirement for admin users
if (body.email.includes('admin') && !body.two_factor_code) { if (body.email.includes('admin') && !body.two_factor_code) {
return HttpResponse.json( return HttpResponse.json({ detail: 'Two-factor authentication required' }, { status: 403 });
{ detail: 'Two-factor authentication required' },
{ status: 403 }
);
} }
// Fall through to generated handler // Fall through to generated handler
@@ -254,6 +253,7 @@ export const demoUser: UserResponse = {
``` ```
**To update:** **To update:**
1. Edit `data/*.ts` files 1. Edit `data/*.ts` files
2. Handlers automatically use updated data 2. Handlers automatically use updated data
3. No regeneration needed! 3. No regeneration needed!
@@ -263,6 +263,7 @@ export const demoUser: UserResponse = {
The generator (`scripts/generate-msw-handlers.ts`) does: The generator (`scripts/generate-msw-handlers.ts`) does:
1. **Parse OpenAPI spec** 1. **Parse OpenAPI spec**
```typescript ```typescript
const spec = JSON.parse(fs.readFileSync(specPath, 'utf-8')); 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 ### Generated handler doesn't work
**Check:** **Check:**
1. Is backend running? (`npm run generate:api` requires backend) 1. Is backend running? (`npm run generate:api` requires backend)
2. Check console for `[MSW]` warnings 2. Check console for `[MSW]` warnings
3. Verify `generated.ts` exists and has your endpoint 3. Verify `generated.ts` exists and has your endpoint
4. Check path parameters match exactly 4. Check path parameters match exactly
**Debug:** **Debug:**
```bash ```bash
# See what endpoints were generated # See what endpoints were generated
cat src/mocks/handlers/generated.ts | grep "http\." cat src/mocks/handlers/generated.ts | grep "http\."

View File

@@ -1,6 +1,7 @@
# Keeping MSW Handlers Synced with OpenAPI Spec # Keeping MSW Handlers Synced with OpenAPI Spec
## Problem ## Problem
MSW handlers can drift out of sync with the backend API as it evolves. MSW handlers can drift out of sync with the backend API as it evolves.
## Solution Options ## Solution Options
@@ -60,6 +61,7 @@ Add to `package.json`:
Our MSW handlers currently cover: Our MSW handlers currently cover:
**Auth Endpoints:** **Auth Endpoints:**
- POST `/api/v1/auth/register` - POST `/api/v1/auth/register`
- POST `/api/v1/auth/login` - POST `/api/v1/auth/login`
- POST `/api/v1/auth/refresh` - POST `/api/v1/auth/refresh`
@@ -70,6 +72,7 @@ Our MSW handlers currently cover:
- POST `/api/v1/auth/change-password` - POST `/api/v1/auth/change-password`
**User Endpoints:** **User Endpoints:**
- GET `/api/v1/users/me` - GET `/api/v1/users/me`
- PATCH `/api/v1/users/me` - PATCH `/api/v1/users/me`
- DELETE `/api/v1/users/me` - DELETE `/api/v1/users/me`
@@ -80,6 +83,7 @@ Our MSW handlers currently cover:
- DELETE `/api/v1/sessions/:id` - DELETE `/api/v1/sessions/:id`
**Admin Endpoints:** **Admin Endpoints:**
- GET `/api/v1/admin/stats` - GET `/api/v1/admin/stats`
- GET `/api/v1/admin/users` - GET `/api/v1/admin/users`
- GET `/api/v1/admin/users/:id` - GET `/api/v1/admin/users/:id`

View File

@@ -11,11 +11,7 @@ import { useState } from 'react';
import config from '@/config/app.config'; import config from '@/config/app.config';
import { Sparkles } from 'lucide-react'; import { Sparkles } from 'lucide-react';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
export function DemoModeBanner() { export function DemoModeBanner() {
// Only show in demo mode // Only show in demo mode

View File

@@ -55,11 +55,7 @@ export function CodeBlock({ children, className, title }: CodeBlockProps) {
onClick={handleCopy} onClick={handleCopy}
aria-label="Copy code" aria-label="Copy code"
> >
{copied ? ( {copied ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
<Check className="h-4 w-4 text-green-500" />
) : (
<Copy className="h-4 w-4" />
)}
</Button> </Button>
</div> </div>
</div> </div>

View File

@@ -136,7 +136,10 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
return ( return (
<a <a
href={href} href={href}
className={cn("opacity-0 group-hover:opacity-100 transition-opacity text-muted-foreground hover:text-primary ml-2 no-underline", className)} className={cn(
'opacity-0 group-hover:opacity-100 transition-opacity text-muted-foreground hover:text-primary ml-2 no-underline',
className
)}
{...props} {...props}
> >
{children} {children}
@@ -147,7 +150,10 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
return ( return (
<a <a
href={href} href={href}
className={cn("font-medium text-primary underline decoration-primary/30 underline-offset-4 hover:decoration-primary/60 hover:text-primary/90 transition-all", className)} className={cn(
'font-medium text-primary underline decoration-primary/30 underline-offset-4 hover:decoration-primary/60 hover:text-primary/90 transition-all',
className
)}
{...props} {...props}
> >
{children} {children}

View File

@@ -68,7 +68,12 @@ export async function startMockServiceWorker() {
} }
// Ignore locale routes (Next.js i18n) // 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; return;
} }

View File

@@ -86,7 +86,9 @@ describe('DemoCredentialsModal', () => {
fireEvent.click(adminCopyButton!); fireEvent.click(adminCopyButton!);
await waitFor(() => { 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 copiedButtons = screen.getAllByRole('button');
const copiedButton = copiedButtons.find((btn) => btn.textContent?.includes('Copied!')); const copiedButton = copiedButtons.find((btn) => btn.textContent?.includes('Copied!'));
expect(copiedButton).toBeInTheDocument(); expect(copiedButton).toBeInTheDocument();