diff --git a/ROADMAP.md b/ROADMAP.md index a76989a83..057cb77ba 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -835,6 +835,407 @@ All criteria met ✅ --- + +## Testing Strategy & Best Practices + +MetaBuilder follows a comprehensive testing strategy to ensure code quality, reliability, and maintainability. + +### Testing Philosophy + +We emphasize **Test-Driven Development (TDD)** as a core practice: + +1. **Write tests first** - Design APIs through tests +2. **Fast feedback** - Tests provide immediate confidence +3. **High coverage** - Target >80% code coverage +4. **Maintainable tests** - Tests are as important as production code +5. **Realistic scenarios** - Tests reflect real-world usage + +### Testing Pyramid + +``` + /\ + / \ + /E2E \ Few, Slow, Expensive + /------\ Critical user flows + / \ + /Integration\ More, Medium Speed + /------------\ Components working together + / \ + / Unit Tests \ Many, Fast, Cheap + /------------------\ Individual functions +``` + +### Test Coverage + +| Component | Unit Tests | Integration Tests | E2E Tests | +|-----------|-----------|-------------------|-----------| +| **Utilities** | >90% | - | - | +| **Database Layer** | >85% | >70% | - | +| **API Routes** | >80% | >75% | Critical paths | +| **React Components** | >75% | - | - | +| **User Flows** | - | - | All critical paths | +| **Overall** | >80% | >70% | 100% critical | + +### Test-Driven Development (TDD) + +**Red-Green-Refactor Cycle:** + +``` +1. 🔴 RED: Write a failing test + ↓ +2. 🟢 GREEN: Write minimal code to pass + ↓ +3. 🔵 REFACTOR: Improve code while keeping tests green + ↓ +4. ♻️ REPEAT: Move to next feature +``` + +**Example TDD Workflow:** +```typescript +// Step 1: RED - Write failing test +describe('validatePassword', () => { + it.each([ + { password: 'short', expected: { valid: false, error: 'Too short' } }, + { password: 'validpass123', expected: { valid: true, error: null } }, + ])('should validate "$password"', ({ password, expected }) => { + expect(validatePassword(password)).toEqual(expected) + }) +}) + +// Step 2: GREEN - Implement to pass tests +export function validatePassword(password: string) { + if (password.length < 8) { + return { valid: false, error: 'Too short' } + } + return { valid: true, error: null } +} + +// Step 3: REFACTOR - Improve while keeping tests green +``` + +### Testing Tools + +| Tool | Purpose | Usage | +|------|---------|-------| +| **Vitest** | Unit & integration testing | `npm test` | +| **Playwright** | E2E browser testing | `npx playwright test` | +| **Testing Library** | React component testing | Integrated with Vitest | +| **Prisma** | Database testing | Test database utilities | + +### Unit Testing Best Practices + +**✅ Use Parameterized Tests:** +```typescript +it.each([ + { input: 'hello', expected: 'HELLO' }, + { input: 'world', expected: 'WORLD' }, +])('should uppercase $input', ({ input, expected }) => { + expect(uppercase(input)).toBe(expected) +}) +``` + +**✅ Follow AAA Pattern:** +```typescript +it('should calculate total', () => { + // ARRANGE + const items = [{ price: 10 }, { price: 20 }] + // ACT + const total = calculateTotal(items) + // ASSERT + expect(total).toBe(30) +}) +``` + +**✅ Mock External Dependencies:** +```typescript +vi.mock('@/lib/database', () => ({ + Database: { + getUser: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }) + } +})) +``` + +### E2E Testing with Playwright + +**Configuration:** `e2e/playwright.config.ts` + +**Key Features:** +- Cross-browser testing (Chromium, Firefox, WebKit) +- Auto-waiting for elements +- Screenshots and videos on failure +- Trace viewer for debugging +- Page Object Model support + +**Example E2E Test:** +```typescript +import { test, expect } from '@playwright/test' + +test('should login successfully', async ({ page }) => { + await page.goto('/login') + await page.fill('input[name="email"]', 'admin@example.com') + await page.fill('input[name="password"]', 'password123') + await page.click('button[type="submit"]') + + await expect(page).toHaveURL(/\/dashboard/) + await expect(page.locator('text=Welcome')).toBeVisible() +}) +``` + +### Running Tests + +```bash +# Unit tests +npm test # Run all unit tests +npm run test:watch # Watch mode +npm run test:coverage # With coverage report + +# E2E tests +npx playwright test # Run all E2E tests +npx playwright test --ui # Interactive UI mode +npx playwright test --debug # Debug mode +npx playwright show-report # View HTML report + +# CI +npm run test:ci # All tests for CI +``` + +### Test Maintenance + +**Keep Tests Fast:** +- Unit tests: <1s each +- Integration tests: <5s each +- E2E tests: <30s each + +**Keep Tests Isolated:** +- Each test is independent +- Use beforeEach/afterEach for setup/cleanup +- Don't rely on test execution order + +**Keep Tests Readable:** +- Descriptive test names +- Clear arrange-act-assert sections +- Meaningful variable names +- Document complex scenarios + +### CI/CD Integration + +Tests run automatically on: +- Every push to main/develop branches +- Every pull request +- Pre-deployment checks + +**GitHub Actions:** +- Unit tests with coverage reporting +- E2E tests across multiple browsers +- Automatic artifact upload on failure +- Retry failed tests (2x in CI) + +### Comprehensive Testing Guide + +For detailed testing documentation, examples, and best practices, see: +**[docs/TESTING_GUIDE.md](docs/TESTING_GUIDE.md)** + +This comprehensive guide includes: +- Complete TDD workflows with examples +- Advanced Playwright patterns (POM, fixtures) +- Integration testing strategies +- Troubleshooting common issues +- CI/CD configuration examples + +--- + +## Development Best Practices + +### Core Principles + +1. **One Lambda Per File** - Functions in separate files, classes only as containers +2. **Write Tests First (TDD)** - Red-Green-Refactor cycle +3. **TypeScript Strict Mode** - No `any` types, explicit types everywhere +4. **Follow DRY** - Don't Repeat Yourself, extract common logic +5. **Pure Functions** - Make functions pure when possible + +### Code Quality + +**✅ Good Practices:** +```typescript +// Explicit types +function getUser(id: string): Promise { + return Database.getUser(id) +} + +// Pure function +function calculateTotal(items: Item[]): number { + return items.reduce((sum, item) => sum + item.price, 0) +} + +// One function per file +// src/lib/users/get-user-by-id.ts +export async function getUserById(id: string): Promise { + return Database.getUser(id) +} +``` + +**❌ Bad Practices:** +```typescript +// Using 'any' +function getUser(id: any): Promise { + return Database.getUser(id) +} + +// Multiple unrelated functions +export function getUserById(id) { ... } +export function formatUserName(user) { ... } +export function validateEmail(email) { ... } +``` + +### Development Workflow + +``` +1. Planning → Review requirements, check docs +2. Design → Data models, API contracts, interfaces +3. TDD → Write tests, implement, refactor +4. Integration → Run tests, lint, type check +5. Review → Self-review, create PR, address feedback +6. Deploy → Staging → E2E tests → Production +``` + +### Git Workflow + +```bash +# Feature branch +git checkout -b feature/user-auth + +# Commit frequently +git commit -m "feat: add login form" +git commit -m "test: add validation tests" + +# Push and create PR +git push origin feature/user-auth +``` + +### Code Review Checklist + +**Before PR:** +- [ ] All tests pass +- [ ] Lint and type check pass +- [ ] New code has tests (>80% coverage) +- [ ] Self-reviewed changes +- [ ] Documentation updated + +**Reviewer checks:** +- [ ] Code follows conventions +- [ ] No security vulnerabilities +- [ ] Performance considered +- [ ] Accessible (a11y) +- [ ] Mobile responsive + +### Security Best Practices + +**✅ Input Validation:** +```typescript +import { z } from 'zod' + +const userSchema = z.object({ + email: z.string().email(), + password: z.string().min(8), +}) + +export function createUser(input: unknown) { + const validated = userSchema.parse(input) + return Database.createUser(validated) +} +``` + +**✅ SQL Injection Prevention:** +```typescript +// Use Prisma's parameterized queries +const users = await prisma.user.findMany({ + where: { email: userEmail }, +}) +``` + +**✅ XSS Prevention:** +```typescript +// React automatically escapes +
{user.name}
+ +// Sanitize HTML when needed +import DOMPurify from 'dompurify' +
+``` + +### Performance Best Practices + +**✅ Database Queries:** +```typescript +// Query only needed fields +const users = await Database.getUsers({ + select: ['id', 'name', 'email'], + where: { active: true }, + limit: 10, +}) +``` + +**✅ React Performance:** +```typescript +import { useMemo, useCallback } from 'react' + +function UserList({ users, onSelect }) { + const sortedUsers = useMemo( + () => users.sort((a, b) => a.name.localeCompare(b.name)), + [users] + ) + + const handleSelect = useCallback( + (userId) => onSelect(userId), + [onSelect] + ) + + return +} +``` + +### Accessibility (a11y) + +```typescript +// ✅ Accessible components + + +
+ + + + {emailError} + +
+``` + +### Development Tools + +| Tool | Purpose | Command | +|------|---------|---------| +| **ESLint** | Code linting | `npm run lint:fix` | +| **Prettier** | Code formatting | `npm run format` | +| **TypeScript** | Type checking | `npm run typecheck` | +| **Prisma** | Database ORM | `npm run db:generate` | +| **Vitest** | Unit testing | `npm test` | +| **Playwright** | E2E testing | `npx playwright test` | + ## Known Issues & Technical Debt ### Active Issues diff --git a/e2e/auth/authentication.spec.ts b/e2e/auth/authentication.spec.ts new file mode 100644 index 000000000..d1849c24a --- /dev/null +++ b/e2e/auth/authentication.spec.ts @@ -0,0 +1,98 @@ +import { test, expect, Page } from '@playwright/test' + +/** + * Authentication E2E Tests + * + * These tests demonstrate TDD principles and Playwright best practices: + * - Page Object Model (POM) pattern + * - Test fixtures for common setup + * - Descriptive test names + * - Proper waiting strategies + */ + +/** + * LoginPage - Page Object Model + * Encapsulates login page interactions + */ +class LoginPage { + constructor(private page: Page) {} + + // Locators + get emailInput() { + return this.page.locator('input[name="email"], input[type="email"]') + } + + get passwordInput() { + return this.page.locator('input[name="password"], input[type="password"]') + } + + get submitButton() { + return this.page.locator('button[type="submit"]') + } + + get errorMessage() { + return this.page.locator('[role="alert"], .error-message') + } + + // Actions + async goto() { + await this.page.goto('/login') + await this.page.waitForLoadState('networkidle') + } + + async fillEmail(email: string) { + await this.emailInput.fill(email) + } + + async fillPassword(password: string) { + await this.passwordInput.fill(password) + } + + async submit() { + await this.submitButton.click() + } + + async login(email: string, password: string) { + await this.fillEmail(email) + await this.fillPassword(password) + await this.submit() + } + + // Assertions + async expectLoginFormVisible() { + await expect(this.emailInput).toBeVisible() + await expect(this.passwordInput).toBeVisible() + await expect(this.submitButton).toBeVisible() + } + + async expectErrorMessage(message?: string) { + await expect(this.errorMessage).toBeVisible() + if (message) { + await expect(this.errorMessage).toContainText(message) + } + } + + async expectRedirectToDashboard() { + await this.page.waitForURL(/\/dashboard/, { timeout: 10000 }) + } +} + +test.describe('Authentication Flow - TDD Examples', () => { + test.describe('Login Page', () => { + test('should display login form elements', async ({ page }) => { + const loginPage = new LoginPage(page) + await loginPage.goto() + await loginPage.expectLoginFormVisible() + }) + + test('should show error with invalid credentials', async ({ page }) => { + const loginPage = new LoginPage(page) + await loginPage.goto() + + await loginPage.login('invalid@example.com', 'wrongpassword') + + // Should display error message + await loginPage.expectErrorMessage() + }) + }) +}) diff --git a/package.json b/package.json index 3f1d1c017..31dc30c41 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,12 @@ "build": "npm --prefix frontends/nextjs run build", "typecheck": "npm --prefix frontends/nextjs run typecheck", "lint": "npm --prefix frontends/nextjs run lint", - "test": "npm --prefix frontends/nextjs run test" + "test": "npm --prefix frontends/nextjs run test", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:headed": "playwright test --headed", + "test:e2e:debug": "playwright test --debug", + "test:e2e:report": "playwright show-report" }, "workspaces": [ "frontends/nextjs", @@ -20,6 +25,7 @@ "storybook" ], "devDependencies": { + "@playwright/test": "^1.57.0", "jsdom": "^27.4.0" } }