# 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 { 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() 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() 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.