- Add 115+ unit, component, and E2E tests - Add Vitest configuration with coverage thresholds - Add validation schema tests (validations.test.ts) - Add sanitizer utility tests (sanitizer.test.ts) - Add WhatsApp service tests (whatsapp.test.ts) - Add component tests for UnifiedTypography and UnifiedSurface - Add E2E tests for admin auth and public pages - Add testing documentation (docs/TESTING.md) - Add sanitizer and WhatsApp utilities - Add centralized validation schemas - Refactor state management (admin/public separation) - Fix security issues (OTP via POST, session password validation) - Update AGENTS.md with testing guidelines Test Coverage: 50%+ target achieved All tests passing: 115/115 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
12 KiB
Testing Guide - Desa Darmasaba
Overview
This document provides comprehensive testing guidelines for the Desa Darmasaba project. The project uses a multi-layered testing strategy including unit tests, component tests, and end-to-end (E2E) tests.
Testing Stack
| Layer | Tool | Purpose |
|---|---|---|
| Unit Tests | Vitest | Testing utility functions, validation schemas, services |
| Component Tests | Vitest + React Testing Library | Testing React components in isolation |
| E2E Tests | Playwright | Testing complete user flows in real browsers |
| API Mocking | MSW (Mock Service Worker) | Mocking API responses for unit/component tests |
Test Structure
__tests__/
├── api/ # API integration tests
│ └── fileStorage.test.ts
├── components/ # Component tests
│ └── admin/
│ ├── UnifiedTypography.test.tsx
│ └── UnifiedSurface.test.tsx
├── e2e/ # End-to-end tests
│ ├── admin/
│ │ └── auth.spec.ts
│ └── public/
│ └── pages.spec.ts
├── lib/ # Unit tests for utilities
│ ├── validations.test.ts
│ ├── sanitizer.test.ts
│ └── whatsapp.test.ts
├── mocks/ # MSW mocks for API
│ ├── handlers.ts
│ └── server.ts
└── setup.ts # Test setup and configuration
Running Tests
All Tests
bun run test
Unit Tests Only
bun run test:api
E2E Tests Only
bun run test:e2e
Tests with Coverage
bun run test:api --coverage
Run Specific Test File
bunx vitest run __tests__/lib/validations.test.ts
Run Tests in Watch Mode
bunx vitest
Run E2E Tests with UI
bun run test:e2e --ui
Writing Tests
Unit Tests (Vitest)
Unit tests should test pure functions, validation schemas, and utilities in isolation.
// __tests__/lib/example.test.ts
import { describe, it, expect } from 'vitest';
import { exampleFunction } from '@/lib/example';
describe('exampleFunction', () => {
it('should return expected value for valid input', () => {
const result = exampleFunction('valid-input');
expect(result).toBe('expected-output');
});
it('should handle edge cases', () => {
expect(() => exampleFunction('')).toThrow();
expect(() => exampleFunction(null)).toThrow();
});
});
Component Tests (React Testing Library)
Component tests should test React components in isolation with mocked dependencies.
// __tests__/components/Example.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { MantineProvider, createTheme } from '@mantine/core';
import { ExampleComponent } from '@/components/Example';
function renderWithMantine(ui: React.ReactElement) {
const theme = createTheme();
return render(ui, {
wrapper: ({ children }) => (
<MantineProvider theme={theme}>{children}</MantineProvider>
),
});
}
describe('ExampleComponent', () => {
it('should render with props', () => {
renderWithMantine(<ExampleComponent title="Test Title" />);
expect(screen.getByText('Test Title')).toBeInTheDocument();
});
it('should handle user interactions', async () => {
const onClick = vi.fn();
renderWithMantine(<ExampleComponent onClick={onClick} />);
fireEvent.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalledTimes(1);
});
});
E2E Tests (Playwright)
E2E tests should test complete user flows in a real browser environment.
// __tests__/e2e/example.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Feature Name', () => {
test.beforeEach(async ({ page }) => {
// Setup before each test
await page.goto('/starting-page');
});
test('should complete user flow', async ({ page }) => {
// Fill form
await page.fill('input[name="email"]', 'user@example.com');
await page.click('button[type="submit"]');
// Wait for navigation
await page.waitForURL('/success');
// Verify result
await expect(page.getByText('Success!')).toBeVisible();
});
test('should handle errors gracefully', async ({ page }) => {
// Submit invalid data
await page.click('button[type="submit"]');
// Verify error message
await expect(page.getByText('Validation error')).toBeVisible();
});
});
API Mocking (MSW)
Use MSW to mock API responses for unit and component tests.
// __tests__/mocks/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/example', () => {
return HttpResponse.json({
data: [{ id: '1', name: 'Item 1' }],
});
}),
http.post('/api/example', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({
data: { id: '2', ...body },
status: 201,
});
}),
];
Test Coverage Goals
Current coverage thresholds (configured in vitest.config.ts):
| Metric | Target |
|---|---|
| Branches | 50% |
| Functions | 50% |
| Lines | 50% |
| Statements | 50% |
Critical Files Priority
Focus testing efforts on these critical files first:
-
Validation & Security
src/lib/validations/index.tssrc/lib/sanitizer.tssrc/lib/whatsapp.tssrc/lib/session.ts
-
Core Utilities
src/lib/api-fetch.tssrc/lib/prisma.tssrc/utils/themeTokens.ts
-
Shared Components
src/components/admin/UnifiedTypography.tsxsrc/components/admin/UnifiedSurface.tsxsrc/components/admin/UnifiedCard.tsx
-
State Management
src/state/darkModeStore.tssrc/state/admin/*.tssrc/state/public/*.ts
-
API Routes
src/app/api/[[...slugs]]/_lib/auth/**src/app/api/[[...slugs]]/_lib/desa/**
Testing Conventions
Naming Conventions
- Unit/Component Tests:
*.test.tsor*.test.tsx - E2E Tests:
*.spec.ts - Test Files: Match source file name (e.g.,
sanitizer.ts→sanitizer.test.ts) - Test Directories: Mirror source structure under
__tests__/
Describe Blocks
Use nested describe blocks to organize tests logically:
describe('FeatureName', () => {
describe('functionName', () => {
describe('when valid input', () => {
it('should return expected result', () => {});
});
describe('when invalid input', () => {
it('should throw error', () => {});
});
});
});
Test Descriptions
- Use clear, descriptive test names
- Follow pattern:
should [expected behavior] when [condition] - Avoid vague descriptions like "works correctly"
Assertions
- Use specific matchers (
toBe,toEqual,toContain) - Test both success and failure cases
- Test edge cases (empty input, null, undefined, max values)
Setup and Teardown
describe('ComponentName', () => {
beforeEach(() => {
// Reset mocks, state
vi.clearAllMocks();
});
afterEach(() => {
// Cleanup
vi.restoreAllMocks();
});
// ... tests
});
Mocking Guidelines
Mock External Services
// Mock fetch API
global.fetch = vi.fn();
// Mock modules
vi.mock('@/lib/prisma', () => ({
default: {
berita: {
findMany: vi.fn(),
create: vi.fn(),
},
},
}));
Mock Environment Variables
const originalEnv = process.env;
beforeEach(() => {
process.env = {
...originalEnv,
TEST_VAR: 'test-value',
};
});
afterEach(() => {
process.env = originalEnv;
});
Mock Date/Time
const mockDate = new Date('2024-01-01T00:00:00Z');
vi.useFakeTimers();
vi.setSystemTime(mockDate);
// ... tests
vi.useRealTimers();
E2E Testing Best Practices
Test User Flows, Not Implementation
✅ Good:
test('user can login and view dashboard', async ({ page }) => {
await page.goto('/admin/login');
await page.fill('input[name="nomor"]', '08123456789');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/admin/dashboard');
});
❌ Bad:
test('login form submits to API', async ({ page }) => {
// Don't test internal implementation details
});
Use Data Attributes for Selectors
// In component
<button data-testid="submit-button">Submit</button>
// In test
await page.getByTestId('submit-button').click();
Handle Async Operations
// Wait for specific element
await page.waitForSelector('.loaded-content');
// Wait for navigation
await page.waitForNavigation();
// Wait for network request
await page.waitForResponse('/api/data');
Skip Tests Appropriately
// Skip in CI
test.skip(process.env.CI === 'true', 'Skip in CI environment');
// Skip with reason
test.skip(true, 'Feature not yet implemented');
// Conditional skip
test.skip(!hasValidCredentials, 'Requires valid credentials');
Continuous Integration
GitHub Actions Workflow
Tests run automatically on:
- Pull requests
- Push to main branch
- Manual trigger
Test Requirements
- All new features must include tests
- Bug fixes should include regression tests
- Coverage should not decrease significantly
Debugging Tests
Vitest Debug Mode
bunx vitest --reporter=verbose
Playwright Debug Mode
PWDEBUG=1 bun run test:e2e
Playwright Trace Viewer
bun run test:e2e --trace on
bunx playwright show-trace
Common Patterns
Testing Validation Schemas
describe('validationSchema', () => {
it('should accept valid data', () => {
const result = validationSchema.safeParse(validData);
expect(result.success).toBe(true);
});
it('should reject invalid data', () => {
const result = validationSchema.safeParse(invalidData);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.errors[0].message).toContain('error message');
}
});
});
Testing Async Functions
it('should fetch data successfully', async () => {
const result = await fetchData();
expect(result).toEqual(expectedData);
});
it('should handle errors', async () => {
await expect(asyncFunction()).rejects.toThrow('error message');
});
Testing Hooks
import { renderHook, act } from '@testing-library/react';
it('should update state', () => {
const { result } = renderHook(() => useCustomHook());
act(() => {
result.current.setValue('new value');
});
expect(result.current.value).toBe('new value');
});
Troubleshooting
Common Issues
Issue: Tests fail with "Cannot find module"
Solution: Check import paths, ensure @/ alias is configured in vitest.config.ts
Issue: Mantine components throw errors
Solution: Wrap components with MantineProvider in test setup
Issue: Tests fail in CI but pass locally Solution: Check for environment-specific code, use proper mocking
Issue: E2E tests timeout Solution: Increase timeout, check for async operations, use proper waits
Getting Help
- Check existing tests for patterns
- Review Vitest documentation: https://vitest.dev
- Review Playwright documentation: https://playwright.dev
- Review Testing Library documentation: https://testing-library.com
Resources
- Vitest Documentation
- Playwright Documentation
- React Testing Library
- MSW Documentation
- Testing JavaScript Course
Maintenance
Regular Tasks
- Update test dependencies monthly
- Review and update test coverage goals quarterly
- Remove deprecated test patterns
- Add tests for newly discovered edge cases
- Document common testing patterns
Deprecation Policy
When refactoring code:
- Keep existing tests passing
- Update tests to match new implementation
- Remove tests for removed functionality
- Update this documentation
Last Updated: March 9, 2026 Version: 1.0.0 Maintained By: Development Team