Files
gh-physics91-claude-vibe/skills/test-generator/SKILL.md
2025-11-30 08:47:23 +08:00

231 lines
5.7 KiB
Markdown

---
name: test-generator
description: |
WHEN: Test code generation, unit/integration/E2E test writing, component/hook/utility tests
WHAT: Framework detection + Jest/Vitest/RTL/Playwright templates + Happy Path/Edge/Error case tests
WHEN NOT: Coverage analysis → coverage-analyzer, Test quality review → code-reviewer
---
# Test Generator Skill
## Purpose
Automatically generates tests by detecting project test framework and applying appropriate patterns.
## When to Use
- Test generation requests
- New component/function needs tests
- Unit, E2E, integration test mentions
- Coverage improvement needed
## Framework Detection
### Test Runners
| Framework | Detection | package.json |
|-----------|-----------|--------------|
| Jest | `jest.config.*` | `jest` |
| Vitest | `vitest.config.*` | `vitest` |
| Playwright | `playwright.config.*` | `@playwright/test` |
| Cypress | `cypress.config.*` | `cypress` |
### Test Libraries
| Library | Purpose | package.json |
|---------|---------|--------------|
| RTL | React components | `@testing-library/react` |
| Vue Test Utils | Vue components | `@vue/test-utils` |
## Workflow
### Step 1: Detect Environment
```
**Runner**: Jest
**Library**: React Testing Library
**Config**: jest.config.js
**Test Dir**: __tests__/, *.test.tsx
```
### Step 2: Select Target
**AskUserQuestion:**
```
"Which code to test?"
Options:
- Specific file/component
- Auto-detect untested files
- Recently changed files
- Entire directory
```
### Step 3: Select Test Type
**AskUserQuestion:**
```
"What type of tests?"
Options:
- Unit Test
- Integration Test
- Component Test
- E2E Test (Playwright/Cypress)
```
### Step 4: Coverage Goal
**AskUserQuestion:**
```
"Coverage goal?"
Options:
- Happy Path only
- Happy Path + Edge Cases
- Full (including errors)
- Specify scenarios
```
## Test Templates
### React Component (Jest + RTL)
```typescript
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Component } from './Component'
describe('Component', () => {
const defaultProps = { /* ... */ }
const renderComponent = (props = {}) =>
render(<Component {...defaultProps} {...props} />)
describe('Rendering', () => {
it('renders with default state', () => {
renderComponent()
expect(screen.getByRole('button')).toBeInTheDocument()
})
})
describe('Interactions', () => {
it('calls callback on click', async () => {
const onClick = jest.fn()
renderComponent({ onClick })
await userEvent.click(screen.getByRole('button'))
expect(onClick).toHaveBeenCalledTimes(1)
})
})
describe('Edge Cases', () => {
it('handles empty data', () => {
renderComponent({ data: [] })
expect(screen.getByText('No data')).toBeInTheDocument()
})
})
})
```
### React Hook
```typescript
import { renderHook, act, waitFor } from '@testing-library/react'
import { useCustomHook } from './useCustomHook'
describe('useCustomHook', () => {
it('returns initial state', () => {
const { result } = renderHook(() => useCustomHook())
expect(result.current.value).toBe(initialValue)
})
it('updates state', () => {
const { result } = renderHook(() => useCustomHook())
act(() => { result.current.setValue('new') })
expect(result.current.value).toBe('new')
})
})
```
### Utility Function
```typescript
import { utilityFunction } from './utils'
describe('utilityFunction', () => {
it('processes valid input', () => {
expect(utilityFunction('valid')).toBe('expected')
})
describe('Edge Cases', () => {
it('handles empty string', () => {
expect(utilityFunction('')).toBe('')
})
it('handles null', () => {
expect(utilityFunction(null)).toBeNull()
})
})
describe('Errors', () => {
it('throws on invalid input', () => {
expect(() => utilityFunction(undefined)).toThrow()
})
})
})
```
### E2E (Playwright)
```typescript
import { test, expect } from '@playwright/test'
test.describe('User Flow: Login', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login')
})
test('logs in successfully', async ({ page }) => {
await page.getByLabel('Email').fill('user@example.com')
await page.getByLabel('Password').fill('password123')
await page.getByRole('button', { name: 'Login' }).click()
await expect(page).toHaveURL('/dashboard')
})
test('shows error on invalid credentials', async ({ page }) => {
await page.getByLabel('Email').fill('wrong@example.com')
await page.getByLabel('Password').fill('wrong')
await page.getByRole('button', { name: 'Login' }).click()
await expect(page.getByRole('alert')).toContainText('Login failed')
})
})
```
## Response Template
```
## Tests Generated
**Target**: src/components/Button.tsx
**Output**: src/components/__tests__/Button.test.tsx
**Runner**: Jest + RTL
### Test Cases
| Category | Test | Description |
|----------|------|-------------|
| Rendering | Default render | Renders correctly |
| Interaction | Click event | onClick callback |
| Edge Case | Long text | Overflow handling |
### Run
\`\`\`bash
npm test -- Button.test.tsx
npm test -- --coverage Button.test.tsx
\`\`\`
### Expected Coverage
- Lines: ~90%
- Branches: ~85%
- Functions: ~100%
```
## Best Practices
1. **AAA Pattern**: Arrange-Act-Assert
2. **Clear Names**: Expected behavior in test name
3. **Independence**: Each test runs independently
4. **Minimal Mocking**: Mock only when necessary
5. **Real User Behavior**: Prefer user-event
## Integration
- `/generate-tests` command
- `coverage-analyzer` skill
- `code-reviewer` skill
## Notes
- Follows existing test patterns if present
- Test file location matches project structure
- Mocking based on actual implementation analysis