mirror of https://github.com/langgenius/dify.git
323 lines
10 KiB
Markdown
323 lines
10 KiB
Markdown
---
|
|
name: frontend-testing
|
|
description: Generate Jest + React Testing Library tests for Dify frontend components, hooks, and utilities. Triggers on testing, spec files, coverage, Jest, RTL, unit tests, integration tests, or write/review test requests.
|
|
---
|
|
|
|
# Dify Frontend Testing Skill
|
|
|
|
This skill enables Claude to generate high-quality, comprehensive frontend tests for the Dify project following established conventions and best practices.
|
|
|
|
> **⚠️ Authoritative Source**: This skill is derived from `web/testing/testing.md`. When in doubt, always refer to that document as the canonical specification.
|
|
|
|
## When to Apply This Skill
|
|
|
|
Apply this skill when the user:
|
|
|
|
- Asks to **write tests** for a component, hook, or utility
|
|
- Asks to **review existing tests** for completeness
|
|
- Mentions **Jest**, **React Testing Library**, **RTL**, or **spec files**
|
|
- Requests **test coverage** improvement
|
|
- Uses `pnpm analyze-component` output as context
|
|
- Mentions **testing**, **unit tests**, or **integration tests** for frontend code
|
|
- Wants to understand **testing patterns** in the Dify codebase
|
|
|
|
**Do NOT apply** when:
|
|
|
|
- User is asking about backend/API tests (Python/pytest)
|
|
- User is asking about E2E tests (Playwright/Cypress)
|
|
- User is only asking conceptual questions without code context
|
|
|
|
## Quick Reference
|
|
|
|
### Tech Stack
|
|
|
|
| Tool | Version | Purpose |
|
|
|------|---------|---------|
|
|
| Jest | 29.7 | Test runner |
|
|
| React Testing Library | 16.0 | Component testing |
|
|
| happy-dom | - | Test environment |
|
|
| nock | 14.0 | HTTP mocking |
|
|
| TypeScript | 5.x | Type safety |
|
|
|
|
### Key Commands
|
|
|
|
```bash
|
|
# Run all tests
|
|
pnpm test
|
|
|
|
# Watch mode
|
|
pnpm test -- --watch
|
|
|
|
# Run specific file
|
|
pnpm test -- path/to/file.spec.tsx
|
|
|
|
# Generate coverage report
|
|
pnpm test -- --coverage
|
|
|
|
# Analyze component complexity
|
|
pnpm analyze-component <path>
|
|
|
|
# Review existing test
|
|
pnpm analyze-component <path> --review
|
|
```
|
|
|
|
### File Naming
|
|
|
|
- Test files: `ComponentName.spec.tsx` (same directory as component)
|
|
- Integration tests: `web/__tests__/` directory
|
|
|
|
## Test Structure Template
|
|
|
|
```typescript
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
import Component from './index'
|
|
|
|
// ✅ Import real project components (DO NOT mock these)
|
|
// import Loading from '@/app/components/base/loading'
|
|
// import { ChildComponent } from './child-component'
|
|
|
|
// ✅ Mock external dependencies only
|
|
jest.mock('@/service/api')
|
|
jest.mock('next/navigation', () => ({
|
|
useRouter: () => ({ push: jest.fn() }),
|
|
usePathname: () => '/test',
|
|
}))
|
|
|
|
// Shared state for mocks (if needed)
|
|
let mockSharedState = false
|
|
|
|
describe('ComponentName', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks() // ✅ Reset mocks BEFORE each test
|
|
mockSharedState = false // ✅ Reset shared state
|
|
})
|
|
|
|
// Rendering tests (REQUIRED)
|
|
describe('Rendering', () => {
|
|
it('should render without crashing', () => {
|
|
// Arrange
|
|
const props = { title: 'Test' }
|
|
|
|
// Act
|
|
render(<Component {...props} />)
|
|
|
|
// Assert
|
|
expect(screen.getByText('Test')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// Props tests (REQUIRED)
|
|
describe('Props', () => {
|
|
it('should apply custom className', () => {
|
|
render(<Component className="custom" />)
|
|
expect(screen.getByRole('button')).toHaveClass('custom')
|
|
})
|
|
})
|
|
|
|
// User Interactions
|
|
describe('User Interactions', () => {
|
|
it('should handle click events', () => {
|
|
const handleClick = jest.fn()
|
|
render(<Component onClick={handleClick} />)
|
|
|
|
fireEvent.click(screen.getByRole('button'))
|
|
|
|
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
// Edge Cases (REQUIRED)
|
|
describe('Edge Cases', () => {
|
|
it('should handle null data', () => {
|
|
render(<Component data={null} />)
|
|
expect(screen.getByText(/no data/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should handle empty array', () => {
|
|
render(<Component items={[]} />)
|
|
expect(screen.getByText(/empty/i)).toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|
|
```
|
|
|
|
## Testing Workflow (CRITICAL)
|
|
|
|
### ⚠️ Incremental Approach Required
|
|
|
|
**NEVER generate all test files at once.** For complex components or multi-file directories:
|
|
|
|
1. **Analyze & Plan**: List all files, order by complexity (simple → complex)
|
|
1. **Process ONE at a time**: Write test → Run test → Fix if needed → Next
|
|
1. **Verify before proceeding**: Do NOT continue to next file until current passes
|
|
|
|
```
|
|
For each file:
|
|
┌────────────────────────────────────────┐
|
|
│ 1. Write test │
|
|
│ 2. Run: pnpm test -- <file>.spec.tsx │
|
|
│ 3. PASS? → Mark complete, next file │
|
|
│ FAIL? → Fix first, then continue │
|
|
└────────────────────────────────────────┘
|
|
```
|
|
|
|
### Complexity-Based Order
|
|
|
|
Process in this order for multi-file testing:
|
|
|
|
1. 🟢 Utility functions (simplest)
|
|
1. 🟢 Custom hooks
|
|
1. 🟡 Simple components (presentational)
|
|
1. 🟡 Medium components (state, effects)
|
|
1. 🔴 Complex components (API, routing)
|
|
1. 🔴 Integration tests (index files - last)
|
|
|
|
### When to Refactor First
|
|
|
|
- **Complexity > 50**: Break into smaller pieces before testing
|
|
- **500+ lines**: Consider splitting before testing
|
|
- **Many dependencies**: Extract logic into hooks first
|
|
|
|
> 📖 See `references/workflow.md` for complete workflow details and todo list format.
|
|
|
|
## Testing Strategy
|
|
|
|
### Path-Level Testing (Directory Testing)
|
|
|
|
When assigned to test a directory/path, test **ALL content** within that path:
|
|
|
|
- Test all components, hooks, utilities in the directory (not just `index` file)
|
|
- Use incremental approach: one file at a time, verify each before proceeding
|
|
- Goal: 100% coverage of ALL files in the directory
|
|
|
|
### Integration Testing First
|
|
|
|
**Prefer integration testing** when writing tests for a directory:
|
|
|
|
- ✅ **Import real project components** directly (including base components and siblings)
|
|
- ✅ **Only mock**: API services (`@/service/*`), `next/navigation`, complex context providers
|
|
- ❌ **DO NOT mock** base components (`@/app/components/base/*`)
|
|
- ❌ **DO NOT mock** sibling/child components in the same directory
|
|
|
|
> See [Test Structure Template](#test-structure-template) for correct import/mock patterns.
|
|
|
|
## Core Principles
|
|
|
|
### 1. AAA Pattern (Arrange-Act-Assert)
|
|
|
|
Every test should clearly separate:
|
|
|
|
- **Arrange**: Setup test data and render component
|
|
- **Act**: Perform user actions
|
|
- **Assert**: Verify expected outcomes
|
|
|
|
### 2. Black-Box Testing
|
|
|
|
- Test observable behavior, not implementation details
|
|
- Use semantic queries (getByRole, getByLabelText)
|
|
- Avoid testing internal state directly
|
|
- **Prefer pattern matching over hardcoded strings** in assertions:
|
|
|
|
```typescript
|
|
// ❌ Avoid: hardcoded text assertions
|
|
expect(screen.getByText('Loading...')).toBeInTheDocument()
|
|
|
|
// ✅ Better: role-based queries
|
|
expect(screen.getByRole('status')).toBeInTheDocument()
|
|
|
|
// ✅ Better: pattern matching
|
|
expect(screen.getByText(/loading/i)).toBeInTheDocument()
|
|
```
|
|
|
|
### 3. Single Behavior Per Test
|
|
|
|
Each test verifies ONE user-observable behavior:
|
|
|
|
```typescript
|
|
// ✅ Good: One behavior
|
|
it('should disable button when loading', () => {
|
|
render(<Button loading />)
|
|
expect(screen.getByRole('button')).toBeDisabled()
|
|
})
|
|
|
|
// ❌ Bad: Multiple behaviors
|
|
it('should handle loading state', () => {
|
|
render(<Button loading />)
|
|
expect(screen.getByRole('button')).toBeDisabled()
|
|
expect(screen.getByText('Loading...')).toBeInTheDocument()
|
|
expect(screen.getByRole('button')).toHaveClass('loading')
|
|
})
|
|
```
|
|
|
|
### 4. Semantic Naming
|
|
|
|
Use `should <behavior> when <condition>`:
|
|
|
|
```typescript
|
|
it('should show error message when validation fails')
|
|
it('should call onSubmit when form is valid')
|
|
it('should disable input when isReadOnly is true')
|
|
```
|
|
|
|
## Required Test Scenarios
|
|
|
|
### Always Required (All Components)
|
|
|
|
1. **Rendering**: Component renders without crashing
|
|
1. **Props**: Required props, optional props, default values
|
|
1. **Edge Cases**: null, undefined, empty values, boundary conditions
|
|
|
|
### Conditional (When Present)
|
|
|
|
| Feature | Test Focus |
|
|
|---------|-----------|
|
|
| `useState` | Initial state, transitions, cleanup |
|
|
| `useEffect` | Execution, dependencies, cleanup |
|
|
| Event handlers | All onClick, onChange, onSubmit, keyboard |
|
|
| API calls | Loading, success, error states |
|
|
| Routing | Navigation, params, query strings |
|
|
| `useCallback`/`useMemo` | Referential equality |
|
|
| Context | Provider values, consumer behavior |
|
|
| Forms | Validation, submission, error display |
|
|
|
|
## Coverage Goals (Per File)
|
|
|
|
For each test file generated, aim for:
|
|
|
|
- ✅ **100%** function coverage
|
|
- ✅ **100%** statement coverage
|
|
- ✅ **>95%** branch coverage
|
|
- ✅ **>95%** line coverage
|
|
|
|
> **Note**: For multi-file directories, process one file at a time with full coverage each. See `references/workflow.md`.
|
|
|
|
## Detailed Guides
|
|
|
|
For more detailed information, refer to:
|
|
|
|
- `references/workflow.md` - **Incremental testing workflow** (MUST READ for multi-file testing)
|
|
- `references/mocking.md` - Mock patterns and best practices
|
|
- `references/async-testing.md` - Async operations and API calls
|
|
- `references/domain-components.md` - Workflow, Dataset, Configuration testing
|
|
- `references/common-patterns.md` - Frequently used testing patterns
|
|
- `references/checklist.md` - Test generation checklist and validation steps
|
|
|
|
## Authoritative References
|
|
|
|
### Primary Specification (MUST follow)
|
|
|
|
- **`web/testing/testing.md`** - The canonical testing specification. This skill is derived from this document.
|
|
|
|
### Reference Examples in Codebase
|
|
|
|
- `web/utils/classnames.spec.ts` - Utility function tests
|
|
- `web/app/components/base/button/index.spec.tsx` - Component tests
|
|
- `web/__mocks__/provider-context.ts` - Mock factory example
|
|
|
|
### Project Configuration
|
|
|
|
- `web/jest.config.ts` - Jest configuration
|
|
- `web/jest.setup.ts` - Test environment setup
|
|
- `web/testing/analyze-component.js` - Component analysis tool
|
|
- `web/__mocks__/react-i18next.ts` - Shared i18n mock (auto-loaded by Jest, no explicit mock needed; override locally only for custom translations)
|