update readme with test section

This commit is contained in:
Kalidou Diagne
2025-08-07 23:14:49 +01:00
parent 384981d2c3
commit f4d5765bcf
3 changed files with 29 additions and 564 deletions

View File

@@ -65,3 +65,32 @@ Start the app
- Tailwind CSS
- Icons from [Lucide](https://lucide.dev)
- Tailwind CSS class sorting, merging and linting.
## Testing
Quick commands:
```bash
# Run all tests (CI mode)
yarn test:run
# Watch mode (dev)
yarn test:watch
# UI runner
yarn test:ui
# Coverage report
yarn test:coverage
# Validate setup (sanity checks)
yarn test:validation
```
Notes:
- Tests live in `tests/` with utilities in `tests/test-utils.tsx`.
- Mocks are under `tests/mocks/` (Next components, browser APIs, external libs).
- Use the custom render from `@/tests/test-utils` to get providers.
- Path alias `@/` points to project root.
- jsdom environment is preconfigured.

View File

@@ -1,473 +0,0 @@
# Testing Guide
This document provides comprehensive guidance for testing in this Next.js 14 project.
## 🚀 Quick Start
### Running Tests
```bash
# Run all tests
yarn test
# Run tests in watch mode (recommended for development)
yarn test:watch
# Run tests with UI (visual test runner)
yarn test:ui
# Run tests once (CI mode)
yarn test:run
# Run tests with coverage report
yarn test:coverage
# Validate test setup
yarn test:validation
```
### Writing Your First Test
```typescript
import { render, screen } from '@/tests/test-utils'
import { MyComponent } from '@/components/MyComponent'
describe('MyComponent', () => {
it('renders correctly', () => {
render(<MyComponent title="Hello World" />)
expect(screen.getByText('Hello World')).toBeInTheDocument()
})
})
```
## 🏗️ Test Structure
### File Organization
```
tests/
├── setup.ts # Global test setup
├── test-utils.tsx # Custom render functions & utilities
├── mocks/ # Mock implementations
│ ├── index.ts
│ ├── next-components.ts # Next.js component mocks
│ ├── external-libraries.ts # Third-party library mocks
│ └── browser-apis.ts # Browser API mocks
├── examples/ # Example test files
│ ├── Button.test.tsx
│ ├── Input.test.tsx
│ ├── AppLink.test.tsx
│ ├── GlobalProvider.test.tsx
│ ├── api-route.test.ts
│ └── hooks.test.tsx
└── validation.test.ts # Setup validation tests
```
### Naming Conventions
- **Test files**: `ComponentName.test.tsx` or `functionName.test.ts`
- **Test descriptions**: Use descriptive names that explain the behavior
- **Test IDs**: Use `data-testid` for elements that need specific targeting
## 🧰 Testing Utilities
### Custom Render Function
Always use the custom render function from `@/tests/test-utils`:
```typescript
import { render, screen } from '@/tests/test-utils'
// This automatically wraps your component with all necessary providers:
// - GlobalProvider (with QueryClient)
// - ProjectsProvider
// - ThemeProvider
```
### Provider-Specific Rendering
For more control over which providers to include:
```typescript
import { renderWithProviders } from '@/tests/test-utils'
renderWithProviders(<Component />, {
withGlobal: true, // Include GlobalProvider
withProjects: false, // Exclude ProjectsProvider
withTheme: true, // Include ThemeProvider
})
```
### Helper Functions
```typescript
import {
waitForLoadingToFinish,
mockMatchMedia,
mockLocalStorage,
resetAllMocks
} from '@/tests/test-utils'
// Wait for async operations
await waitForLoadingToFinish()
// Mock media queries
mockMatchMedia(true) // true = dark mode preference
// Mock localStorage with methods
const storage = mockLocalStorage()
storage.getItem.mockReturnValue('stored-value')
// Reset all mocks between tests
resetAllMocks()
```
## 🎯 Testing Patterns
### Component Testing
#### Basic Component
```typescript
describe('Button', () => {
it('renders with text', () => {
render(<Button>Click me</Button>)
expect(screen.getByRole('button')).toHaveTextContent('Click me')
})
it('handles click events', () => {
const handleClick = vi.fn()
render(<Button onClick={handleClick}>Click me</Button>)
fireEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
})
```
#### Component with Props
```typescript
describe('Card', () => {
const defaultProps = {
title: 'Test Title',
content: 'Test content',
variant: 'default' as const,
}
it('renders with required props', () => {
render(<Card {...defaultProps} />)
expect(screen.getByText('Test Title')).toBeInTheDocument()
expect(screen.getByText('Test content')).toBeInTheDocument()
})
it('applies variant styles', () => {
render(<Card {...defaultProps} variant="highlighted" />)
const card = screen.getByRole('article') // or appropriate role
expect(card).toHaveClass('highlighted-variant-class')
})
})
```
#### Client Components
```typescript
describe('InteractiveComponent', () => {
it('updates state on user interaction', () => {
render(<InteractiveComponent />)
const input = screen.getByRole('textbox')
fireEvent.change(input, { target: { value: 'new value' } })
expect(input).toHaveValue('new value')
})
})
```
### Provider Testing
```typescript
describe('ThemeProvider', () => {
it('provides theme context to children', () => {
const TestComponent = () => {
const { theme } = useTheme()
return <div data-testid="theme">{theme}</div>
}
render(
<ThemeProvider>
<TestComponent />
</ThemeProvider>
)
expect(screen.getByTestId('theme')).toHaveTextContent('light')
})
})
```
### Hook Testing
```typescript
describe('useCounter', () => {
it('increments count', () => {
const { result } = renderHook(() => useCounter())
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})
})
```
### API Route Testing
```typescript
describe('API Routes', () => {
it('returns valid response', async () => {
const request = new NextRequest('http://localhost/api/test?param=value')
const response = await GET(request)
const data = await response.json()
expect(response.status).toBe(200)
expect(data).toHaveProperty('result')
})
})
```
## 🔧 Configuration
### Vitest Config Highlights
- **Environment**: jsdom for DOM testing
- **Setup files**: Automatic mock setup
- **Coverage**: v8 provider with HTML reports
- **Path aliases**: `@/` mapped to project root
- **CSS support**: Enabled for styling tests
### Mocks Included
#### Next.js Components
-`next/image` → Simple `<img>` tag
-`next/link` → Simple `<a>` tag
-`next/script` → Simple `<script>` tag
-`next/font/google` → Font objects with variables
-`next/navigation` → Router hooks and functions
#### Browser APIs
-`localStorage` / `sessionStorage`
-`window.matchMedia`
-`window.scrollTo`
-`requestAnimationFrame`
-`ResizeObserver`
-`IntersectionObserver`
#### External Libraries
-`framer-motion` → Simple div wrappers
-`react-slick` → Simple div with test ID
-`algoliasearch` → Mock search client
-`fuse.js` → Mock fuzzy search
-`prismjs` → Mock syntax highlighter
-`gsap` → Mock animation library
## 🎨 Best Practices
### Test Structure
```typescript
describe('Component/Feature Name', () => {
// Setup
beforeEach(() => {
// Reset mocks, clear localStorage, etc.
})
// Group related tests
describe('when user is authenticated', () => {
// Tests for authenticated state
})
describe('when user is not authenticated', () => {
// Tests for unauthenticated state
})
// Edge cases
describe('error handling', () => {
// Error scenarios
})
})
```
### Test Descriptions
- ✅ "renders loading state while fetching data"
- ✅ "shows error message when API call fails"
- ✅ "filters projects when tag is selected"
- ❌ "it works"
- ❌ "test component"
### Assertions
```typescript
// Prefer semantic queries
screen.getByRole('button', { name: /submit/i })
screen.getByLabelText(/email address/i)
screen.getByText(/welcome back/i)
// Use specific matchers
expect(element).toBeInTheDocument()
expect(element).toHaveClass('active')
expect(element).toHaveAttribute('aria-expanded', 'true')
// Test behavior, not implementation
fireEvent.click(button) // Good
wrapper.instance().handleClick() // Bad - testing implementation
```
### Async Testing
```typescript
// Use waitFor for async updates
await waitFor(() => {
expect(screen.getByText('Data loaded')).toBeInTheDocument()
})
// Use findBy queries (built-in waiting)
const element = await screen.findByText('Async content')
// Wrap state updates in act()
await act(async () => {
result.current.fetchData()
})
```
## 🐛 Troubleshooting
### Common Issues
#### "TextEncoder is not defined"
This is handled by the jsdom environment. If you see this error, ensure your test file is being run with the correct environment.
#### "window is not defined"
Make sure you're using our test setup which configures jsdom. Check that your test imports from `@/tests/test-utils`.
#### "Cannot find module '@/components/...'"
Path aliases are configured in `vitest.config.ts`. Ensure the path exists and the import is correct.
#### React Query Errors
Use our custom render function which includes QueryClient setup, or wrap your component manually:
```typescript
const queryClient = createTestQueryClient()
render(
<QueryClientProvider client={queryClient}>
<YourComponent />
</QueryClientProvider>
)
```
#### Mock Not Working
Ensure mocks are imported at the top of your test file and that you're calling `resetAllMocks()` in `beforeEach` if needed.
### Debug Tips
```typescript
// Debug render output
render(<Component />)
screen.debug() // Prints DOM to console
// Debug specific element
screen.debug(screen.getByRole('button'))
// Find elements during development
screen.logTestingPlaygroundURL() // Opens Testing Playground
```
## 📊 Coverage
Generate coverage reports:
```bash
yarn test:coverage
```
Coverage files are generated in the `coverage/` directory:
- `coverage/index.html` - Visual coverage report
- `coverage/lcov.info` - LCOV format for CI tools
### Coverage Thresholds
Configure in `vitest.config.ts`:
```typescript
coverage: {
thresholds: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
}
```
## 🔄 CI Integration
### GitHub Actions Example
```yaml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'yarn'
- run: yarn install --frozen-lockfile
- run: yarn test:run
- run: yarn test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
```
### Pre-commit Testing
Tests automatically run on staged files via lint-staged:
```json
{
"lint-staged": {
"**/*.{js,ts,tsx}": [
"eslint --fix",
"prettier --write",
"vitest run --passWithNoTests"
]
}
}
```
## 📚 Resources
- [Vitest Documentation](https://vitest.dev/)
- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/)
- [Jest DOM Matchers](https://github.com/testing-library/jest-dom)
- [Testing Playground](https://testing-playground.com/)
- [Common Testing Mistakes](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library)
---
## 🎯 Ready to Test!
Your testing environment is fully configured and ready. Start by running the validation test:
```bash
yarn test:validation
```
Then explore the example tests in `tests/examples/` to understand the patterns, and start writing tests for your components!
Happy testing! 🧪✨

View File

@@ -1,91 +0,0 @@
# Test Suite Fixes - Complete Resolution Summary
## 🎯 **Final Status: ALL TESTS PASSING ✅**
**Result: 68/68 tests passing (100% success rate)**
## ✅ **Issues Fixed**
### 1. **React Import Error (RESOLVED)**
- **Error**: `ReferenceError: React is not defined` in hooks tests
- **Solution**: Added `import React from 'react'` to `tests/examples/hooks.test.tsx`
- **Impact**: Fixed all hook-related test failures
### 2. **AppLink Component Tests (RESOLVED)**
- **Error 1**: Expected `rel="noopener noreferrer"` but component uses `rel="noreferrer noopener nofollow"`
- **Error 2**: Expected nested content's parent to have href, but component wraps children in div
- **Solution**: Updated test assertions to match actual component behavior:
- Fixed `rel` attribute expectation to include `nofollow`
- Updated parent element targeting to use `screen.getByRole('link')`
### 3. **Button Component Tests (RESOLVED)**
- **Error**: Test expected non-existent `loading` prop functionality
- **Solution**: Replaced with proper `disabled` state test that matches actual component API
### 4. **Vitest Deprecation Warning (RESOLVED)**
- **Warning**: `"deps.inline" is deprecated`
- **Solution**: Updated `vitest.config.mjs` to use `server.deps.inline` instead of `deps.inline`
## 🔧 **Files Modified**
1. **`tests/examples/hooks.test.tsx`**
- Added React import
- Replaced project-specific hook tests with generic example hooks
2. **`tests/examples/AppLink.test.tsx`**
- Fixed `rel` attribute assertion
- Updated href checking logic
3. **`tests/examples/Button.test.tsx`**
- Replaced `loading` prop test with `disabled` prop test
4. **`vitest.config.mjs`**
- Updated deprecated `deps.inline` to `server.deps.inline`
## 📊 **Test Results Summary**
```
✅ API Routes: 7/7 tests passing
✅ Custom Hooks: 12/12 tests passing
✅ Validation: 19/19 tests passing
✅ Input Component: 9/9 tests passing
✅ Global Provider: 7/7 tests passing
✅ AppLink Component: 5/5 tests passing
✅ Button Component: 9/9 tests passing
Total: 68/68 tests passing (100%)
```
## 🚀 **Environment Status**
**All systems operational:**
- ✅ Vitest: Working perfectly
- ✅ React Testing Library: Ready
- ✅ TypeScript: Full support
- ✅ Custom test utilities: Functional
- ✅ Jest DOM matchers: Working
- ✅ Mocks: All systems ready
- ✅ Provider wrappers: Functional
- ✅ CSS/styling: Supported
- ✅ Async support: Working
- ✅ Next.js mocks: Ready
- ✅ Browser API mocks: Ready
**No deprecation warnings or errors remaining.**
## 📝 **Notes**
- **Error logs in GlobalProvider tests**: These are expected - they show the provider attempting to fetch projects data, which demonstrates real integration testing
- **Navigation warnings in AppLink tests**: These are expected JSDOM limitations and don't affect test functionality
- **passHref warning**: This is expected from the Next.js Link mock and doesn't affect functionality
## 🎉 **Ready for Production**
The test environment is now **fully operational** and ready for:
- ✅ Component testing
- ✅ Hook testing
- ✅ API route testing
- ✅ Integration testing
- ✅ CI/CD integration
Use `yarn test` to start testing!