# Testing Guidelines

> Testing standards and best practices

## 📋 Overview

Comprehensive testing ensures code quality and prevents regressions. This guide covers testing standards, patterns, and best practices for Izri.

## 🎯 Testing Philosophy

### Testing Pyramid

```
         /\
        /E2E\
       /------\
      /  API  \
     /----------\
    /    Unit    \
   /--------------\
```

- **Unit Tests**: 70% - Fast, isolated, numerous
- **Integration Tests**: 20% - API/database interactions
- **E2E Tests**: 10% - Critical user flows

### Goals

- **Confidence**: Catch bugs before production
- **Documentation**: Tests as usage examples
- **Refactoring**: Safe code changes
- **Speed**: Fast feedback loop

## 🧪 Test Types

### Unit Tests

**Purpose**: Test individual functions/components in isolation

**Tools**: Vitest, Testing Library

**Examples**:

```typescript
// Function test
import { describe, expect, it } from 'vitest'
import { calculatePassRate } from './utils'

describe('calculatePassRate', () => {
  it('calculates correct pass rate', () => {
    expect(calculatePassRate(80, 100)).toBe(80)
  })

  it('handles zero total tests', () => {
    expect(calculatePassRate(0, 0)).toBe(0)
  })

  it('rounds to 2 decimal places', () => {
    expect(calculatePassRate(2, 3)).toBe(66.67)
  })
})
```

```tsx
// Component test
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { ProjectCard } from './ProjectCard'

describe('ProjectCard', () => {
  const mockProject = {
    id: 'proj_123',
    name: 'Test Project',
    repository: 'https://github.com/test/repo',
    language: 'TypeScript'
  }

  it('renders project name', () => {
    render(<ProjectCard project={mockProject} />)
    expect(screen.getByText('Test Project')).toBeInTheDocument()
  })

  it('displays repository URL', () => {
    render(<ProjectCard project={mockProject} />)
    expect(screen.getByText(/github.com\/test\/repo/)).toBeInTheDocument()
  })
})
```

### Integration Tests

**Purpose**: Test multiple units working together

**Examples**:

```typescript
// tRPC procedure test
import { describe, expect, it, beforeEach, afterEach } from 'vitest'
import { createCaller } from '../context'

describe('projects.createProject', () => {
  beforeEach(async () => {
    await setupTestDatabase()
  })

  afterEach(async () => {
    await cleanupTestDatabase()
  })

  it('creates project in database', async () => {
    const caller = await createCaller({ user: testUser })
    
    const result = await caller.projects.createProject({
      name: 'Test Project',
      repository: 'https://github.com/test/repo',
      language: 'TypeScript',
      userEmail: testUser.email
    })
    
    expect(result.project.id).toBeDefined()
    expect(result.project.name).toBe('Test Project')
    
    // Verify in database
    const dbProject = await db.query.projects.findFirst({
      where: eq(projects.id, result.project.id)
    })
    expect(dbProject).toBeDefined()
  })

  it('throws error for duplicate repository', async () => {
    const caller = await createCaller({ user: testUser })
    
    await caller.projects.createProject({
      name: 'Project 1',
      repository: 'https://github.com/test/repo',
      userEmail: testUser.email
    })
    
    await expect(
      caller.projects.createProject({
        name: 'Project 2',
        repository: 'https://github.com/test/repo',
        userEmail: testUser.email
      })
    ).rejects.toThrow('Project with this repository already exists')
  })
})
```

### E2E Tests

**Purpose**: Test complete user flows

**Tools**: Playwright

**Examples**:

```typescript
import { test, expect } from '@playwright/test'

test.describe('Project Management', () => {
  test('user can create a project', async ({ page }) => {
    // Navigate to login
    await page.goto('/login')
    
    // Login with GitHub (mock)
    await page.click('text=Sign in with GitHub')
    
    // Navigate to projects
    await page.goto('/dashboard/projects')
    
    // Click new project
    await page.click('text=New Project')
    
    // Fill form
    await page.fill('[name="name"]', 'E2E Test Project')
    await page.fill('[name="repository"]', 'https://github.com/test/e2e-repo')
    await page.selectOption('[name="language"]', 'TypeScript')
    
    // Submit
    await page.click('button:has-text("Create Project")')
    
    // Verify success
    await expect(page).toHaveURL(/\/dashboard\/projects\/proj_/)
    await expect(page.locator('h1')).toHaveText('E2E Test Project')
  })
})
```

## 📐 Testing Patterns

### Arrange-Act-Assert (AAA)

```typescript
describe('MyComponent', () => {
  it('does something', () => {
    // Arrange: Set up test data and conditions
    const mockData = { id: '123', name: 'Test' }
    render(<MyComponent data={mockData} />)
    
    // Act: Perform the action
    fireEvent.click(screen.getByRole('button'))
    
    // Assert: Verify the result
    expect(screen.getByText('Success')).toBeInTheDocument()
  })
})
```

### Test Fixtures

```typescript
// test/fixtures/project.ts
export const createTestProject = (overrides = {}) => ({
  id: 'proj_test_123',
  name: 'Test Project',
  repository: 'https://github.com/test/repo',
  language: 'TypeScript',
  framework: 'React',
  userId: 'user_test_123',
  createdAt: new Date(),
  updatedAt: new Date(),
  ...overrides
})

// Usage
describe('ProjectCard', () => {
  it('renders project', () => {
    const project = createTestProject({ name: 'Custom Name' })
    render(<ProjectCard project={project} />)
    expect(screen.getByText('Custom Name')).toBeInTheDocument()
  })
})
```

### Mocking

```typescript
// Mock tRPC client
import { vi } from 'vitest'

const mockTrpc = {
  projects: {
    list: {
      useQuery: vi.fn(() => ({
        data: { projects: [createTestProject()] },
        isLoading: false
      }))
    }
  }
}

vi.mock('~/lib/trpc', () => ({
  trpc: mockTrpc
}))
```

### Test Utilities

```typescript
// test/utils.tsx
import { render } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

export function renderWithProviders(ui: React.ReactElement) {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false }
    }
  })

  return render(
    <QueryClientProvider client={queryClient}>
      {ui}
    </QueryClientProvider>
  )
}
```

## ✅ Best Practices

### Do's

✅ **Test behavior, not implementation**

```typescript
// ✅ Good: Test what user sees
expect(screen.getByText('Project created')).toBeInTheDocument()

// ❌ Bad: Test implementation details
expect(component.state.isCreated).toBe(true)
```

✅ **Use descriptive test names**

```typescript
// ✅ Good
it('displays error message when project name is empty', () => {})

// ❌ Bad
it('test 1', () => {})
```

✅ **Test edge cases**

```typescript
describe('calculatePassRate', () => {
  it('handles normal case', () => {})
  it('handles zero passed tests', () => {})
  it('handles zero total tests', () => {})
  it('handles 100% pass rate', () => {})
})
```

✅ **Keep tests independent**

```typescript
// Each test should work standalone
describe('Projects', () => {
  beforeEach(() => {
    // Reset state for each test
    cleanup()
  })

  it('creates project', () => {
    // Independent test
  })

  it('deletes project', () => {
    // Also independent
  })
})
```

✅ **Use meaningful assertions**

```typescript
// ✅ Good: Specific assertion
expect(project.name).toBe('Expected Name')

// ❌ Bad: Generic assertion
expect(project).toBeTruthy()
```

### Don'ts

❌ **Don't test framework code**

```typescript
// ❌ Bad: Testing React itself
it('useState returns state and setter', () => {
  const [state, setState] = useState(0)
  expect(typeof setState).toBe('function')
})
```

❌ **Don't use brittle selectors**

```tsx
// ❌ Bad: Fragile selector
screen.getByClassName('btn-primary')

// ✅ Good: Semantic selector
screen.getByRole('button', { name: 'Create Project' })
```

❌ **Don't have hidden dependencies**

```typescript
// ❌ Bad: Depends on previous test
let projectId

it('creates project', () => {
  projectId = createProject()
})

it('deletes project', () => {
  deleteProject(projectId) // Breaks if first test skipped
})

// ✅ Good: Independent
it('deletes project', () => {
  const projectId = createProject()
  deleteProject(projectId)
})
```

## 📊 Coverage Goals

### Target Coverage

```
Overall: 80%+
Unit Tests: 90%+
Integration Tests: 70%+
Critical Paths: 100%
```

### Check Coverage

```bash
# Run tests with coverage
pnpm test --coverage

# View HTML report
open coverage/index.html
```

### Coverage Configuration

```typescript
// vitest.config.ts
export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: [
        'node_modules/',
        'test/',
        '**/*.test.ts',
        '**/*.spec.ts',
        '**/types.ts'
      ],
      thresholds: {
        lines: 80,
        functions: 80,
        branches: 80,
        statements: 80
      }
    }
  }
})
```

## 🚀 Running Tests

### Commands

```bash
# Run all tests
pnpm test

# Run specific file
pnpm test projects.test.ts

# Run in watch mode
pnpm test --watch

# Run with UI
pnpm test --ui

# Run coverage
pnpm test --coverage

# Run E2E tests
pnpm test:e2e
```

### CI Pipeline

```yaml
# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v3
      
      - run: pnpm install
      - run: pnpm type:check
      - run: pnpm lint
      - run: pnpm test --coverage
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
```

## 🔗 Related Documentation

- **Code Style**: [Code style guide](./code-style.md)
- **Testing Backend**: [Backend testing](../backend/testing-backend.md)
- **Testing Frontend**: [Frontend testing](../frontend/testing-frontend.md)

---

*Contributing section complete! Continue with [API Reference](../api-reference/README.md) →*
