home / skills / deve1993 / quickfy-website / component-tester
/.claude/skills-main/skills-main/new_skills/component-tester
npx playbooks add skill deve1993/quickfy-website --skill component-testerReview the files below or copy the command above to add this skill to your agents.
---
name: component-tester
description: Write, run, and analyze component tests using Vitest and React Testing Library with coverage analysis and accessibility validation
allowed-tools:
- Read
- Write
- Edit
- Bash
- Glob
- Grep
- Task
---
# Component Tester
Expert skill for testing UI component libraries with Vitest and React Testing Library. Specializes in writing comprehensive tests, analyzing coverage, validating accessibility, and ensuring component quality.
## Core Capabilities
### 1. Test Writing
- Unit tests for individual components
- Integration tests for component interactions
- Snapshot tests for visual regression
- Accessibility tests (a11y)
- User interaction tests (clicks, typing, keyboard nav)
- Async behavior testing (loading states, data fetching)
- Edge case and error state testing
### 2. Testing Tools Mastery
- **Vitest**: Fast test runner with native ESM support
- **React Testing Library**: User-centric testing
- **@testing-library/user-event**: Realistic user interactions
- **@testing-library/jest-dom**: Custom matchers
- **@axe-core/react**: Accessibility testing
- **msw**: API mocking
### 3. Test Patterns
- Arrange-Act-Assert (AAA) pattern
- Test fixtures and factories
- Custom render functions
- Reusable test utilities
- Mock management
- Test data builders
### 4. Coverage Analysis
- Line coverage metrics
- Branch coverage analysis
- Function coverage tracking
- Statement coverage
- Identify untested code paths
- Generate coverage reports (HTML, LCOV, JSON)
### 5. Accessibility Validation
- Screen reader compatibility
- Keyboard navigation testing
- ARIA attribute validation
- Color contrast checks
- Focus management tests
- Semantic HTML validation
### 6. Component Quality Checks
- PropTypes validation
- TypeScript type checking
- Performance testing
- Memory leak detection
- Render efficiency
- Bundle size impact
## Workflow
### Phase 1: Test Planning
1. **Analyze Component**
- Understand component behavior
- Identify user interactions
- List edge cases and error states
- Determine accessibility requirements
- Note performance considerations
2. **Define Test Strategy**
- What to test (user behavior, not implementation)
- Which interactions to cover
- Which edge cases matter
- What NOT to test (implementation details)
3. **Set Up Test Environment**
- Create test file
- Import necessary utilities
- Set up custom render function
- Prepare mocks and fixtures
### Phase 2: Writing Tests
1. **Basic Rendering Tests**
- Component renders without errors
- Default props render correctly
- Required props are handled
2. **User Interaction Tests**
- Click events work
- Keyboard navigation functions
- Form inputs update correctly
- Hover/focus states trigger
3. **State Management Tests**
- Internal state updates correctly
- Controlled component behavior
- Uncontrolled component behavior
- State persistence
4. **Accessibility Tests**
- ARIA roles and labels present
- Keyboard navigation works
- Screen reader announcements
- Focus management correct
5. **Edge Case Tests**
- Empty states
- Loading states
- Error states
- Boundary values
- Invalid inputs
### Phase 3: Running & Analysis
1. **Execute Tests**
- Run test suite
- Watch mode for development
- Generate coverage report
- Check for failures
2. **Analyze Coverage**
- Review coverage percentages
- Identify untested code
- Prioritize missing tests
- Set coverage thresholds
3. **Review Results**
- Fix failing tests
- Improve test quality
- Refactor when needed
- Document findings
## Testing Patterns & Examples
### Custom Render Function
```typescript
// test-utils.tsx
import { render, RenderOptions } from '@testing-library/react'
import { ReactElement } from 'react'
import { ThemeProvider } from './theme-provider'
interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
theme?: 'light' | 'dark'
}
function customRender(
ui: ReactElement,
{ theme = 'light', ...options }: CustomRenderOptions = {}
) {
function Wrapper({ children }: { children: React.ReactNode }) {
return <ThemeProvider theme={theme}>{children}</ThemeProvider>
}
return render(ui, { wrapper: Wrapper, ...options })
}
export * from '@testing-library/react'
export { customRender as render }
```
### Basic Component Test
```typescript
// Button.test.tsx
import { render, screen } from './test-utils'
import { userEvent } from '@testing-library/user-event'
import { Button } from './Button'
describe('Button', () => {
it('renders with text', () => {
render(<Button>Click me</Button>)
expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument()
})
it('calls onClick when clicked', async () => {
const handleClick = vi.fn()
const user = userEvent.setup()
render(<Button onClick={handleClick}>Click me</Button>)
await user.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
it('is disabled when disabled prop is true', () => {
render(<Button disabled>Click me</Button>)
expect(screen.getByRole('button')).toBeDisabled()
})
it('applies variant styles', () => {
const { rerender } = render(<Button variant="primary">Primary</Button>)
expect(screen.getByRole('button')).toHaveClass('btn-primary')
rerender(<Button variant="secondary">Secondary</Button>)
expect(screen.getByRole('button')).toHaveClass('btn-secondary')
})
})
```
### Async Testing
```typescript
// DataFetcher.test.tsx
import { render, screen, waitFor } from './test-utils'
import { DataFetcher } from './DataFetcher'
import { server } from './mocks/server'
import { rest } from 'msw'
describe('DataFetcher', () => {
it('displays loading state initially', () => {
render(<DataFetcher url="/api/data" />)
expect(screen.getByText(/loading/i)).toBeInTheDocument()
})
it('displays data after successful fetch', async () => {
render(<DataFetcher url="/api/data" />)
await waitFor(() => {
expect(screen.getByText(/data loaded/i)).toBeInTheDocument()
})
})
it('displays error on failed fetch', async () => {
server.use(
rest.get('/api/data', (req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: 'Server error' }))
})
)
render(<DataFetcher url="/api/data" />)
await waitFor(() => {
expect(screen.getByText(/error/i)).toBeInTheDocument()
})
})
})
```
### User Interaction Testing
```typescript
// Form.test.tsx
import { render, screen } from './test-utils'
import { userEvent } from '@testing-library/user-event'
import { Form } from './Form'
describe('Form', () => {
it('submits form with entered data', async () => {
const handleSubmit = vi.fn()
const user = userEvent.setup()
render(<Form onSubmit={handleSubmit} />)
// Type in input
await user.type(screen.getByLabelText(/username/i), 'john_doe')
// Select option
await user.selectOptions(screen.getByLabelText(/role/i), 'admin')
// Check checkbox
await user.click(screen.getByLabelText(/agree to terms/i))
// Submit form
await user.click(screen.getByRole('button', { name: /submit/i }))
expect(handleSubmit).toHaveBeenCalledWith({
username: 'john_doe',
role: 'admin',
agreedToTerms: true,
})
})
it('validates required fields', async () => {
const user = userEvent.setup()
render(<Form />)
await user.click(screen.getByRole('button', { name: /submit/i }))
expect(screen.getByText(/username is required/i)).toBeInTheDocument()
})
})
```
### Keyboard Navigation Testing
```typescript
// Menu.test.tsx
import { render, screen } from './test-utils'
import { userEvent } from '@testing-library/user-event'
import { Menu } from './Menu'
describe('Menu keyboard navigation', () => {
it('opens menu with Enter key', async () => {
const user = userEvent.setup()
render(<Menu />)
const trigger = screen.getByRole('button', { name: /open menu/i })
await user.tab() // Focus trigger
await user.keyboard('{Enter}')
expect(screen.getByRole('menu')).toBeInTheDocument()
})
it('navigates items with arrow keys', async () => {
const user = userEvent.setup()
render(<Menu defaultOpen />)
const items = screen.getAllByRole('menuitem')
await user.tab() // Focus first item
expect(items[0]).toHaveFocus()
await user.keyboard('{ArrowDown}')
expect(items[1]).toHaveFocus()
await user.keyboard('{ArrowUp}')
expect(items[0]).toHaveFocus()
})
it('closes menu with Escape key', async () => {
const user = userEvent.setup()
render(<Menu defaultOpen />)
expect(screen.getByRole('menu')).toBeInTheDocument()
await user.keyboard('{Escape}')
expect(screen.queryByRole('menu')).not.toBeInTheDocument()
})
})
```
### Accessibility Testing
```typescript
// Button.a11y.test.tsx
import { render } from './test-utils'
import { axe, toHaveNoViolations } from 'jest-axe'
import { Button } from './Button'
expect.extend(toHaveNoViolations)
describe('Button accessibility', () => {
it('has no accessibility violations', async () => {
const { container } = render(<Button>Click me</Button>)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
it('has correct ARIA label when icon-only', async () => {
const { container } = render(
<Button aria-label="Close dialog">
<XIcon />
</Button>
)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
})
```
### Snapshot Testing
```typescript
// Card.test.tsx
import { render } from './test-utils'
import { Card } from './Card'
describe('Card snapshots', () => {
it('matches snapshot with default props', () => {
const { container } = render(
<Card>
<Card.Header>Title</Card.Header>
<Card.Content>Content</Card.Content>
</Card>
)
expect(container.firstChild).toMatchSnapshot()
})
it('matches snapshot with all variants', () => {
const variants = ['default', 'bordered', 'elevated'] as const
variants.forEach((variant) => {
const { container } = render(
<Card variant={variant}>
<Card.Content>Content</Card.Content>
</Card>
)
expect(container.firstChild).toMatchSnapshot()
})
})
})
```
### Mock Management
```typescript
// setupTests.ts
import { beforeAll, afterEach, afterAll } from 'vitest'
import { server } from './mocks/server'
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
// mocks/server.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
// mocks/handlers.ts
import { rest } from 'msw'
export const handlers = [
rest.get('/api/data', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({ data: 'Mock data' })
)
}),
]
```
## Test Configuration
### Vitest Config
```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/test/setup.ts',
css: true,
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
exclude: [
'node_modules/',
'src/test/',
'**/*.d.ts',
'**/*.config.*',
'**/mockData',
'dist/',
],
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
},
},
})
```
### Setup File
```typescript
// src/test/setup.ts
import '@testing-library/jest-dom'
import { cleanup } from '@testing-library/react'
import { afterEach } from 'vitest'
// Cleanup after each test
afterEach(() => {
cleanup()
})
// Mock window.matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
})
// Mock IntersectionObserver
global.IntersectionObserver = class IntersectionObserver {
constructor() {}
disconnect() {}
observe() {}
takeRecords() {
return []
}
unobserve() {}
} as any
```
## Testing Best Practices
### What to Test
✅ **DO Test:**
- User-visible behavior
- Accessibility features
- User interactions (clicks, typing, keyboard nav)
- Different prop combinations
- Edge cases and error states
- Loading and async states
- Integration between components
❌ **DON'T Test:**
- Implementation details
- Internal state directly
- Styling (use visual regression tools instead)
- Third-party libraries
- Framework internals
### Writing Effective Tests
1. **Use Queries in Priority Order**
```typescript
// 1. Accessible to all (best)
getByRole('button', { name: /submit/i })
getByLabelText(/username/i)
getByPlaceholderText(/enter email/i)
getByText(/welcome/i)
// 2. Semantic (good)
getByAltText(/profile picture/i)
getByTitle(/tooltip/i)
// 3. Test IDs (last resort)
getByTestId('submit-button')
```
2. **Wait for Async Changes**
```typescript
// Use waitFor for assertions
await waitFor(() => {
expect(screen.getByText(/data loaded/i)).toBeInTheDocument()
})
// Use findBy for queries (combines getBy + waitFor)
expect(await screen.findByText(/data loaded/i)).toBeInTheDocument()
```
3. **User-Event Over FireEvent**
```typescript
// Prefer userEvent (more realistic)
await user.click(button)
await user.type(input, 'hello')
// Avoid fireEvent when possible
fireEvent.click(button) // Less realistic
```
4. **Descriptive Test Names**
```typescript
// Good
it('displays error message when email is invalid', () => {})
it('disables submit button while form is submitting', () => {})
// Bad
it('works', () => {})
it('test button', () => {})
```
5. **Arrange-Act-Assert Pattern**
```typescript
it('increments counter on click', async () => {
// Arrange
const user = userEvent.setup()
render(<Counter />)
// Act
await user.click(screen.getByRole('button', { name: /increment/i }))
// Assert
expect(screen.getByText('Count: 1')).toBeInTheDocument()
})
```
### Coverage Best Practices
1. **Set Realistic Thresholds**
- Aim for 80%+ coverage
- 100% coverage doesn't mean bug-free
- Focus on critical paths
2. **Ignore Appropriate Files**
- Config files
- Type definitions
- Test utilities
- Generated code
- Mock data
3. **Review Coverage Reports**
- Look for untested branches
- Identify missed edge cases
- Check critical code paths
## Running Tests
### Common Commands
```bash
# Run all tests
npm test
# Watch mode
npm test -- --watch
# Run specific file
npm test Button.test.tsx
# Run with coverage
npm test -- --coverage
# Run in UI mode
npm test -- --ui
# Run only changed files
npm test -- --changed
# Update snapshots
npm test -- -u
```
### CI/CD Integration
```yaml
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm test -- --coverage
- uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
```
## Common Testing Scenarios
### Testing Controlled Components
```typescript
it('calls onChange when value changes', async () => {
const handleChange = vi.fn()
const user = userEvent.setup()
const { rerender } = render(
<Input value="" onChange={handleChange} />
)
await user.type(screen.getByRole('textbox'), 'hello')
expect(handleChange).toHaveBeenCalledTimes(5)
expect(handleChange).toHaveBeenLastCalledWith('hello')
})
```
### Testing Compound Components
```typescript
it('activates tab when clicked', async () => {
const user = userEvent.setup()
render(
<Tabs defaultValue="tab1">
<TabsList>
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
</TabsList>
<TabsContent value="tab1">Content 1</TabsContent>
<TabsContent value="tab2">Content 2</TabsContent>
</Tabs>
)
expect(screen.getByText('Content 1')).toBeInTheDocument()
expect(screen.queryByText('Content 2')).not.toBeInTheDocument()
await user.click(screen.getByRole('tab', { name: /tab 2/i }))
expect(screen.queryByText('Content 1')).not.toBeInTheDocument()
expect(screen.getByText('Content 2')).toBeInTheDocument()
})
```
### Testing Context Providers
```typescript
it('provides theme context to children', () => {
render(
<ThemeProvider theme="dark">
<ComponentUsingTheme />
</ThemeProvider>
)
expect(screen.getByTestId('theme-display')).toHaveTextContent('dark')
})
```
### Testing Custom Hooks
```typescript
// useCounter.test.ts
import { renderHook, act } from '@testing-library/react'
import { useCounter } from './useCounter'
describe('useCounter', () => {
it('increments counter', () => {
const { result } = renderHook(() => useCounter())
expect(result.current.count).toBe(0)
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})
})
```
## Troubleshooting
### Common Issues
**Act Warnings**
```typescript
// Wrap state updates in act()
await act(async () => {
await someAsyncFunction()
})
```
**Query Not Found**
```typescript
// Use queryBy for assertions about absence
expect(screen.queryByText(/not here/i)).not.toBeInTheDocument()
// Use waitFor for async elements
await waitFor(() => {
expect(screen.getByText(/async content/i)).toBeInTheDocument()
})
```
**Timer Issues**
```typescript
// Use fake timers when needed
vi.useFakeTimers()
act(() => {
vi.advanceTimersByTime(1000)
})
vi.useRealTimers()
```
## MCP Integration
### Seamless UI Testing Server Integration
This skill automatically integrates with the **UI Testing MCP Server** when available, providing enhanced testing capabilities beyond unit tests.
#### Automatic Behavior
When you test a component, the system intelligently decides which tests to run:
```typescript
// You say: "test Button component"
// System automatically:
1. ✅ Unit Tests (Vitest) - Always
└── Test logic, props, state, events
2. ✅ Visual Regression (MCP) - If component renders UI
└── Screenshot, compare with baseline, detect changes
3. ✅ Accessibility Audit (MCP) - Always
└── WCAG compliance, keyboard nav, ARIA validation
4. ✅ Performance Check (MCP) - If configured
└── Render time, bundle size impact
5. 📊 Unified Report
└── All results in one comprehensive report
```
#### Enhanced Test Commands
**Standard Test** (Quick)
```bash
"test Button"
→ Unit + Visual + A11y (5-10 seconds)
```
**Full Test Suite** (Complete)
```bash
"full test Button"
→ Unit + Visual + E2E + A11y + Performance (30-60 seconds)
```
**Visual Only**
```bash
"screenshot Button all variants"
→ Captures all visual variants
```
**Performance Only**
```bash
"profile Button"
→ Performance metrics only
```
**Accessibility Only**
```bash
"audit Button accessibility"
→ Complete WCAG audit
```
#### Integration Example
```typescript
// Button.test.tsx (Vitest)
describe('Button', () => {
// Standard unit tests
it('calls onClick when clicked', async () => {
const handleClick = vi.fn()
const user = userEvent.setup()
render(<Button onClick={handleClick}>Click me</Button>)
await user.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
// MCP integration happens automatically when you run:
// "test Button" in Claude Code
//
// Behind the scenes:
// 1. Vitest runs unit tests above
// 2. MCP captures visual screenshot
// 3. MCP runs accessibility audit
// 4. Results combined in single report
})
```
#### Configuration
Enable/disable MCP features in `.claude/mcp-config.json`:
```json
{
"component-tester": {
"mcpIntegration": {
"enabled": true,
"autoVisual": true, // Auto visual test for UI components
"autoAccessibility": true, // Always run a11y tests
"autoPerformance": false, // Only when explicitly requested
"threshold": "fast" // Only fast tests on auto-run
}
}
}
```
#### Setup MCP Server
If not already set up:
```bash
# Generate and setup UI Testing MCP Server
cd your-project
npx @ui-testing/setup-mcp-server
# Or manually with mcp-server-generator skill
"Generate UI Testing MCP server for this project"
```
#### Benefits
**Without MCP Integration:**
- ✅ Unit tests only
- ⚠️ Manual visual checks
- ⚠️ Manual a11y testing
- ⚠️ Manual performance profiling
**With MCP Integration:**
- ✅ Unit tests (Vitest)
- ✅ Automated visual regression
- ✅ Automated accessibility audits
- ✅ Automated performance checks
- ✅ Single unified report
- ✅ CI/CD ready
#### Workflow Example
**Development Workflow:**
```bash
# While developing Button component
1. "dev Button"
→ Opens live preview with hot reload
→ Real-time a11y feedback
→ Performance metrics shown
2. Make changes to Button.tsx
→ Hot reload updates preview
→ A11y issues highlighted
→ Performance tracked
3. "test Button"
→ Quick test suite runs
→ Report: "✅ All passed"
4. Ready to commit!
```
**Pre-Release Workflow:**
```bash
# Before releasing new version
1. "full test Button all variants"
→ Tests all variants (primary, secondary, outline, ghost)
→ Tests all themes (light, dark)
→ Tests all sizes (sm, md, lg)
→ Tests all states (default, hover, active, disabled)
2. Comprehensive report generated
→ Unit tests: 15/15 passed
→ Visual regression: No changes detected
→ Accessibility: WCAG AA compliant
→ Performance: 45ms render (within budget)
3. Ready for release! ✅
```
## When to Use This Skill
Activate this skill when you need to:
- Write unit tests for components
- Create integration tests
- Set up test infrastructure
- Analyze test coverage
- Fix failing tests
- Improve test quality
- Add accessibility tests
- Test user interactions
- Mock API calls
- Set up MSW handlers
- Configure Vitest
- Debug test issues
- **Run automated visual regression tests** (with MCP)
- **Perform accessibility audits** (with MCP)
- **Profile component performance** (with MCP)
- **Generate comprehensive test reports** (with MCP)
## Output Format
When writing tests, provide:
1. **Complete Test Suite**: All test cases for the component
2. **Coverage Report**: What's tested and what's not
3. **Setup Instructions**: Any configuration needed
4. **Mock Data**: Test fixtures if required
5. **Accessibility Notes**: A11y test results
6. **Next Steps**: Recommendations for additional tests
7. **MCP Integration Status**: Whether MCP enhancements are active
8. **Visual Regression Results**: If MCP server available
9. **Performance Metrics**: If MCP server available
Always write tests that are maintainable, readable, and focused on user behavior. When MCP integration is available, leverage it for comprehensive automated testing beyond unit tests.