398 lines
9.6 KiB
Markdown
398 lines
9.6 KiB
Markdown
# Testing Patterns
|
|
|
|
**Analysis Date:** 2026-03-16
|
|
|
|
## Test Framework
|
|
|
|
**Runner:**
|
|
- Not configured - No test framework installed
|
|
- No test files in `src/` directory
|
|
- No testing scripts in `package.json`
|
|
- No `vitest.config.ts`, `jest.config.ts`, or similar configuration
|
|
|
|
**Assertion Library:**
|
|
- Not installed - No testing framework active
|
|
|
|
**Run Commands:**
|
|
- No test commands available
|
|
- `npm run dev` - Development server
|
|
- `npm run build` - Production build
|
|
- `npm run lint` - ESLint only
|
|
- `npm run preview` - Preview built assets
|
|
|
|
**Status:** Testing infrastructure not yet implemented.
|
|
|
|
## Test File Organization
|
|
|
|
**Location:**
|
|
- Not applicable - no test files exist
|
|
- Suggested pattern: Co-located tests
|
|
- Recommendation: Place `ComponentName.test.tsx` alongside `ComponentName.tsx`
|
|
- Recommendation: Place `hookName.test.ts` alongside `hookName.ts`
|
|
|
|
**Naming:**
|
|
- `.test.ts` or `.test.tsx` suffix preferred for consistency with industry standard
|
|
|
|
**Structure:**
|
|
- Suggested directory pattern:
|
|
```
|
|
src/
|
|
├── components/
|
|
│ ├── QuickAddPicker.tsx
|
|
│ ├── QuickAddPicker.test.tsx
|
|
│ └── ui/
|
|
│ ├── button.tsx
|
|
│ └── button.test.tsx
|
|
├── hooks/
|
|
│ ├── useAuth.ts
|
|
│ ├── useAuth.test.ts
|
|
│ └── ...
|
|
└── lib/
|
|
├── utils.ts
|
|
├── utils.test.ts
|
|
└── ...
|
|
```
|
|
|
|
## Test Structure
|
|
|
|
**Suite Organization:**
|
|
- Not yet implemented
|
|
- Recommended framework: Vitest (lightweight, modern, TypeScript-first)
|
|
- Example pattern to implement:
|
|
```typescript
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
import { useAuth } from '@/hooks/useAuth'
|
|
|
|
describe('useAuth', () => {
|
|
beforeEach(() => {
|
|
// Setup
|
|
})
|
|
|
|
afterEach(() => {
|
|
// Cleanup
|
|
})
|
|
|
|
it('should load session on mount', () => {
|
|
// Test implementation
|
|
})
|
|
})
|
|
```
|
|
|
|
**Patterns to establish:**
|
|
- One top-level `describe` per hook/component
|
|
- Nested `describe` blocks for related test groups
|
|
- Each test file focuses on single module
|
|
- Use of `beforeEach` for setup, `afterEach` for cleanup
|
|
|
|
## Mocking
|
|
|
|
**Framework:**
|
|
- Not yet configured
|
|
- Recommended: Vitest with `vi` module for mocking
|
|
- Alternative: Mock Service Worker (MSW) for API mocking
|
|
|
|
**Patterns to implement:**
|
|
|
|
**Supabase Client Mocking:**
|
|
```typescript
|
|
vi.mock('@/lib/supabase', () => ({
|
|
supabase: {
|
|
auth: {
|
|
getSession: vi.fn(),
|
|
getUser: vi.fn(),
|
|
onAuthStateChange: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signIn: vi.fn(),
|
|
signOut: vi.fn(),
|
|
},
|
|
from: vi.fn(),
|
|
},
|
|
}))
|
|
```
|
|
|
|
**React Query Mocking:**
|
|
```typescript
|
|
vi.mock('@tanstack/react-query', () => ({
|
|
useQuery: vi.fn(),
|
|
useMutation: vi.fn(),
|
|
useQueryClient: vi.fn(),
|
|
}))
|
|
```
|
|
|
|
**What to Mock:**
|
|
- External API calls (Supabase queries/mutations)
|
|
- React Query hooks (`useQuery`, `useMutation`)
|
|
- Toast notifications (`sonner`)
|
|
- Browser APIs (window, localStorage when needed)
|
|
- i18next translation function
|
|
|
|
**What NOT to Mock:**
|
|
- Internal hook logic
|
|
- Component rendering
|
|
- State management patterns
|
|
- User interaction handlers (test actual behavior)
|
|
|
|
## Fixtures and Factories
|
|
|
|
**Test Data:**
|
|
- Not yet established
|
|
- Recommended location: `src/__tests__/fixtures/` or `src/__tests__/factories/`
|
|
|
|
**Recommended pattern:**
|
|
```typescript
|
|
// src/__tests__/factories/category.factory.ts
|
|
import type { Category } from '@/lib/types'
|
|
|
|
export function createCategory(overrides?: Partial<Category>): Category {
|
|
return {
|
|
id: crypto.randomUUID(),
|
|
user_id: 'test-user-id',
|
|
name: 'Test Category',
|
|
type: 'bill',
|
|
icon: null,
|
|
sort_order: 0,
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
...overrides,
|
|
}
|
|
}
|
|
```
|
|
|
|
**Location:**
|
|
- Suggested: `src/__tests__/factories/` for factory functions
|
|
- Suggested: `src/__tests__/fixtures/` for static test data
|
|
- Alternative: Inline factories in test files for simple cases
|
|
|
|
## Coverage
|
|
|
|
**Requirements:**
|
|
- Not enforced - No coverage configuration
|
|
- Recommended minimum: 70% for new code
|
|
- Critical paths: hooks and mutation handlers (highest priority)
|
|
|
|
**View Coverage:**
|
|
- Once test framework installed, run:
|
|
```bash
|
|
npm run test:coverage
|
|
```
|
|
|
|
**Recommended coverage config (vitest.config.ts):**
|
|
```typescript
|
|
export default defineConfig({
|
|
test: {
|
|
coverage: {
|
|
provider: 'v8',
|
|
reporter: ['text', 'html', 'json'],
|
|
exclude: [
|
|
'node_modules/',
|
|
'src/__tests__/',
|
|
],
|
|
},
|
|
},
|
|
})
|
|
```
|
|
|
|
## Test Types
|
|
|
|
**Unit Tests:**
|
|
- Scope: Individual functions, hooks, components in isolation
|
|
- Approach: Test pure logic without external dependencies
|
|
- Priority: Utility functions (`cn()`, `formatCurrency()`), custom hooks
|
|
- Example targets:
|
|
- `useBudgets()` query/mutation logic
|
|
- `useAuth()` session management
|
|
- `formatCurrency()` number formatting
|
|
- Validation logic in components
|
|
|
|
**Integration Tests:**
|
|
- Scope: Multiple modules working together (e.g., hook + component)
|
|
- Approach: Mock Supabase, test hook + component interaction
|
|
- Priority: Complex components like `QuickAddPicker` with multiple state changes
|
|
- Example: Component flow - open popover → select item → open dialog → save
|
|
|
|
**E2E Tests:**
|
|
- Framework: Not used
|
|
- Recommended: Playwright or Cypress for future implementation
|
|
- Focus areas: Full user workflows (login → create budget → add items)
|
|
|
|
## Common Patterns
|
|
|
|
**Async Testing:**
|
|
- Recommended approach with Vitest:
|
|
```typescript
|
|
it('should fetch categories', async () => {
|
|
const { result } = renderHook(() => useCategories())
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.categories).toHaveLength(1)
|
|
})
|
|
})
|
|
```
|
|
|
|
- Alternative with MSW:
|
|
```typescript
|
|
it('should handle API error', async () => {
|
|
server.use(
|
|
http.get('/api/categories', () => {
|
|
return HttpResponse.error()
|
|
})
|
|
)
|
|
|
|
const { result } = renderHook(() => useCategories())
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false)
|
|
})
|
|
})
|
|
```
|
|
|
|
**Error Testing:**
|
|
- Test error states and error handling:
|
|
```typescript
|
|
it('should throw on auth error', async () => {
|
|
const mockSupabase = vi.mocked(supabase)
|
|
mockSupabase.auth.signIn.mockRejectedValueOnce(
|
|
new Error('Invalid credentials')
|
|
)
|
|
|
|
const { result } = renderHook(() => useAuth())
|
|
|
|
await expect(result.current.signIn('test@test.com', 'wrong')).rejects.toThrow()
|
|
})
|
|
```
|
|
|
|
- Test error UI display:
|
|
```typescript
|
|
it('should display error message on login failure', async () => {
|
|
render(<LoginPage />)
|
|
|
|
const input = screen.getByRole('textbox', { name: /email/i })
|
|
await userEvent.type(input, 'test@test.com')
|
|
|
|
await userEvent.click(screen.getByRole('button', { name: /sign in/i }))
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/invalid credentials/i)).toBeInTheDocument()
|
|
})
|
|
})
|
|
```
|
|
|
|
## React Testing Library Patterns
|
|
|
|
**When to implement:**
|
|
- Recommended alongside unit tests for components
|
|
- Use `@testing-library/react` for component testing
|
|
- Use `@testing-library/user-event` for user interactions
|
|
|
|
**Component test example structure:**
|
|
```typescript
|
|
import { render, screen, waitFor } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import QuickAddPicker from '@/components/QuickAddPicker'
|
|
|
|
describe('QuickAddPicker', () => {
|
|
const mockBudgetId = 'test-budget-id'
|
|
|
|
it('should open popover on button click', async () => {
|
|
const user = userEvent.setup()
|
|
render(<QuickAddPicker budgetId={mockBudgetId} />)
|
|
|
|
const button = screen.getByRole('button', { name: /quick add/i })
|
|
await user.click(button)
|
|
|
|
expect(screen.getByRole('listbox')).toBeInTheDocument()
|
|
})
|
|
})
|
|
```
|
|
|
|
## Setup and Configuration (Future)
|
|
|
|
**Recommended `vitest.config.ts`:**
|
|
```typescript
|
|
import { defineConfig } from 'vitest/config'
|
|
import react from '@vitejs/plugin-react'
|
|
import path from 'path'
|
|
|
|
export default defineConfig({
|
|
plugins: [react()],
|
|
test: {
|
|
globals: true,
|
|
environment: 'jsdom',
|
|
setupFiles: ['./src/__tests__/setup.ts'],
|
|
coverage: {
|
|
provider: 'v8',
|
|
reporter: ['text', 'html'],
|
|
include: ['src/**/*.{ts,tsx}'],
|
|
exclude: ['src/**/*.test.{ts,tsx}', 'src/**/*.d.ts'],
|
|
},
|
|
},
|
|
resolve: {
|
|
alias: {
|
|
'@': path.resolve(__dirname, './src'),
|
|
},
|
|
},
|
|
})
|
|
```
|
|
|
|
**Recommended `src/__tests__/setup.ts`:**
|
|
```typescript
|
|
import { beforeAll, afterEach, afterAll, vi } from 'vitest'
|
|
import { setupServer } from 'msw/node'
|
|
import { expect, afterEach as vitestAfterEach } from 'vitest'
|
|
|
|
// Mock MSW server setup (if using MSW)
|
|
export const server = setupServer()
|
|
|
|
beforeAll(() => {
|
|
server.listen({ onUnhandledRequest: 'error' })
|
|
})
|
|
|
|
afterEach(() => {
|
|
server.resetHandlers()
|
|
})
|
|
|
|
afterAll(() => {
|
|
server.close()
|
|
})
|
|
```
|
|
|
|
**Package additions needed:**
|
|
```json
|
|
{
|
|
"devDependencies": {
|
|
"vitest": "^1.0.0",
|
|
"@testing-library/react": "^14.0.0",
|
|
"@testing-library/user-event": "^14.0.0",
|
|
"@testing-library/jest-dom": "^6.0.0",
|
|
"jsdom": "^23.0.0",
|
|
"msw": "^2.0.0"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Critical Test Priorities
|
|
|
|
**High Priority (Core Functionality):**
|
|
1. Hook mutations (`useBudgets.createBudget`, `useBudgets.generateFromTemplate`)
|
|
2. Authentication flow (`useAuth.signIn`, `useAuth.signOut`)
|
|
3. Complex component state (`QuickAddPicker` dialog flow)
|
|
4. Validation logic (form field checks)
|
|
|
|
**Medium Priority (Data Access):**
|
|
1. Category queries and filtering
|
|
2. Budget item CRUD operations
|
|
3. Template copying logic
|
|
4. Sorting and ordering
|
|
|
|
**Lower Priority (UI/Display):**
|
|
1. Component rendering
|
|
2. Conditional displays
|
|
3. Icon rendering
|
|
4. Theme switching
|
|
|
|
---
|
|
|
|
*Testing analysis: 2026-03-16*
|
|
|
|
**Note:** No test framework currently installed. This document provides guidance for future test implementation. Recommend prioritizing Vitest as lightweight TypeScript-native test framework complementing Vite build tooling already in use.
|