mirror of https://github.com/langgenius/dify.git
docs: update frontend testing guidelines and add comprehensive testing documentation in TESTING.md
This commit is contained in:
parent
1bb42447cf
commit
009744b71a
226
.cursorrules
226
.cursorrules
|
|
@ -1,193 +1,69 @@
|
|||
# Cursor Rules for Dify Project - Test Generation
|
||||
# Cursor Rules for Dify Project
|
||||
|
||||
## When Generating Tests
|
||||
## Frontend Testing Guidelines
|
||||
|
||||
Follow these rules when asked to generate tests for React components:
|
||||
> **📖 Complete Testing Documentation**: For detailed testing specifications and guidelines, see [`web/scripts/TESTING.md`](./web/scripts/TESTING.md)
|
||||
|
||||
### Tech Stack
|
||||
- Next.js 15 + React 19 + TypeScript
|
||||
- Jest 29.7 + React Testing Library 16.0
|
||||
- Test environment: @happy-dom/jest-environment
|
||||
- File naming: `ComponentName.spec.tsx` (same directory)
|
||||
When generating tests for React components:
|
||||
|
||||
### Component Complexity Guidelines
|
||||
### Key Requirements
|
||||
|
||||
#### 🔴 Very Complex Components (Complexity > 50)
|
||||
- **Split before testing**: Break component into smaller pieces
|
||||
- **Integration tests**: Test complex workflows end-to-end
|
||||
- **Data-driven tests**: Use `test.each()` for multiple scenarios
|
||||
- **Performance benchmarks**: Add performance tests for critical paths
|
||||
1. **Tech Stack**: Next.js 15 + React 19 + TypeScript + Jest + React Testing Library
|
||||
2. **File Naming**: `ComponentName.spec.tsx` (same directory as component)
|
||||
3. **Code Style**:
|
||||
- Use `fireEvent` (not `userEvent`)
|
||||
- AAA pattern (Arrange → Act → Assert)
|
||||
- Test names: `"should [behavior] when [condition]"`
|
||||
- No `any` types
|
||||
- Cleanup after each test: `afterEach(() => jest.clearAllMocks())`
|
||||
|
||||
#### ⚠️ Complex Components (Complexity 30-50)
|
||||
- **Multiple describe blocks**: Group related test cases
|
||||
- **Integration scenarios**: Test feature combinations
|
||||
- **Organized structure**: Keep tests maintainable
|
||||
### Required Tests (All Components)
|
||||
|
||||
#### 📏 Large Components (500+ lines)
|
||||
- **Consider refactoring**: Split into smaller components if possible
|
||||
- **Section testing**: Test major sections separately
|
||||
- **Helper functions**: Reduce test complexity with utilities
|
||||
|
||||
### Test Scenarios
|
||||
|
||||
Apply based on component features:
|
||||
|
||||
#### 1. Rendering Tests (REQUIRED for all)
|
||||
```typescript
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(<Component />)
|
||||
expect(screen.getByRole('...')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### 2. Props Testing (REQUIRED for all)
|
||||
- Test each prop variation
|
||||
- Test required vs optional props
|
||||
- Test default values
|
||||
- Test prop type validation
|
||||
|
||||
#### 3. State Management
|
||||
|
||||
**State Only (useState):**
|
||||
- Test initial state values
|
||||
- Test all state transitions
|
||||
- Test state reset/cleanup scenarios
|
||||
|
||||
**Effects Only (useEffect):**
|
||||
- Test effect execution conditions
|
||||
- Verify dependencies array correctness
|
||||
- Test cleanup on unmount
|
||||
|
||||
**State + Effects Combined:**
|
||||
- Test state initialization and updates
|
||||
- Test useEffect dependencies array
|
||||
- Test cleanup functions (return from useEffect)
|
||||
- Use `waitFor()` for async state changes
|
||||
|
||||
#### 4. Performance Optimization
|
||||
|
||||
**useCallback:**
|
||||
- Verify callbacks maintain referential equality
|
||||
- Test callback dependencies
|
||||
- Ensure re-renders don't recreate functions unnecessarily
|
||||
|
||||
**useMemo:**
|
||||
- Test memoization dependencies
|
||||
- Ensure expensive computations are cached
|
||||
- Verify memo recomputation conditions
|
||||
|
||||
#### 5. Event Handlers
|
||||
- Test all onClick, onChange, onSubmit handlers
|
||||
- Test keyboard events (Enter, Escape, Tab, etc.)
|
||||
- Verify event.preventDefault() calls if needed
|
||||
- Test event bubbling/propagation
|
||||
- Use `fireEvent` (not userEvent)
|
||||
|
||||
#### 6. API Calls & Async Operations
|
||||
- Mock all API calls using `jest.mock`
|
||||
- Test retry logic if applicable
|
||||
- Verify error handling and user feedback
|
||||
- Use `waitFor()` for async operations
|
||||
|
||||
#### 7. Next.js Routing
|
||||
- Mock useRouter, usePathname, useSearchParams
|
||||
- Test navigation behavior and parameters
|
||||
- Test query string handling
|
||||
- Verify route guards/redirects if any
|
||||
- Test URL parameter updates
|
||||
|
||||
#### 8. Edge Cases (REQUIRED for all)
|
||||
- Test null/undefined/empty values
|
||||
- Test boundary conditions
|
||||
- Test error states
|
||||
- Test loading states
|
||||
- Test unexpected inputs
|
||||
|
||||
#### 9. Accessibility (Optional)
|
||||
- Test keyboard navigation
|
||||
- Verify ARIA attributes
|
||||
- Test focus management
|
||||
- Ensure screen reader compatibility
|
||||
|
||||
#### 10. Snapshots (use sparingly)
|
||||
- Only for stable UI (icons, badges, static layouts)
|
||||
- Snapshot small sections only
|
||||
- Prefer explicit assertions over snapshots
|
||||
- Update snapshots intentionally, not automatically
|
||||
|
||||
**Note**: Dify is a desktop app. NO responsive/mobile testing.
|
||||
|
||||
### Code Style
|
||||
|
||||
- Use `fireEvent` not `userEvent`
|
||||
- AAA pattern: Arrange → Act → Assert
|
||||
- Descriptive test names: "should [behavior] when [condition]"
|
||||
- TypeScript: No `any` types
|
||||
- Cleanup: `afterEach(() => jest.clearAllMocks())`
|
||||
|
||||
### Dify-Specific Components
|
||||
|
||||
#### General
|
||||
1. **i18n**: Always return key: `t: (key) => key`
|
||||
2. **Toast**: Mock `@/app/components/base/toast`
|
||||
3. **Forms**: Test validation thoroughly
|
||||
|
||||
#### Workflow Components (`workflow/`)
|
||||
- **Node configuration**: Test all node configuration options
|
||||
- **Data validation**: Verify input/output validation rules
|
||||
- **Variable passing**: Test data flow between nodes
|
||||
- **Edge connections**: Test graph structure and connections
|
||||
- **Error handling**: Verify handling of invalid configurations
|
||||
- **Integration**: Test complete workflow execution paths
|
||||
|
||||
#### Dataset Components (`dataset/`)
|
||||
- **File upload**: Test file upload and validation
|
||||
- **File types**: Verify supported format handling
|
||||
- **Pagination**: Test data loading and pagination
|
||||
- **Search & filtering**: Test query functionality
|
||||
- **Data format handling**: Test various data formats
|
||||
- **Error states**: Test upload failures and invalid data
|
||||
|
||||
#### Configuration Components (`app/configuration`, `config/`)
|
||||
- **Form validation**: Test all validation rules thoroughly
|
||||
- **Save/reset functionality**: Test data persistence
|
||||
- **Required vs optional fields**: Verify field validation
|
||||
- **Configuration persistence**: Test state preservation
|
||||
- **Error feedback**: Verify user error messages
|
||||
- **Default values**: Test initial configuration state
|
||||
|
||||
### Test Strategy Quick Reference
|
||||
|
||||
**Always Test:**
|
||||
- ✅ Rendering without crashing
|
||||
- ✅ Renders without crashing
|
||||
- ✅ Props (required, optional, defaults)
|
||||
- ✅ Edge cases (null, undefined, empty)
|
||||
- ✅ Edge cases (null, undefined, empty values)
|
||||
|
||||
### Conditional Tests (When Present in Component)
|
||||
|
||||
**Test When Present:**
|
||||
- 🔄 **useState** → State initialization, transitions, cleanup
|
||||
- ⚡ **useEffect** → Execution, dependencies, cleanup
|
||||
- 🎯 **Event handlers** → All onClick, onChange, onSubmit, keyboard events
|
||||
- 🌐 **API calls** → Loading, success, error states
|
||||
- 🔀 **Routing** → Navigation, params, query strings
|
||||
- 🎯 **Event Handlers** → onClick, onChange, onSubmit, keyboard events
|
||||
- 🌐 **API Calls** → Loading, success, error states
|
||||
- 🔀 **Next.js Routing** → Navigation, params, query strings
|
||||
- 🚀 **useCallback/useMemo** → Referential equality, dependencies
|
||||
- ⚙️ **Workflow** → Node config, data flow, validation
|
||||
- 📚 **Dataset** → Upload, pagination, search
|
||||
- 🎛️ **Configuration** → Form validation, persistence
|
||||
- ⚙️ **Workflow Components** → Node config, data flow, validation
|
||||
- 📚 **Dataset Components** → Upload, pagination, search
|
||||
- 🎛️ **Configuration Components** → Form validation, persistence
|
||||
|
||||
**Complex Components (30+):**
|
||||
- Group tests in multiple `describe` blocks
|
||||
- Test integration scenarios
|
||||
- Consider splitting component before testing
|
||||
### Component Complexity Strategy
|
||||
|
||||
### Coverage Target
|
||||
- **Complexity > 50**: Refactor before testing, use integration tests and `test.each()`
|
||||
- **Complexity 30-50**: Use multiple `describe` blocks to group tests
|
||||
- **500+ lines**: Consider splitting component
|
||||
|
||||
### Coverage Goals
|
||||
|
||||
Aim for 100% coverage:
|
||||
- Line: >95%
|
||||
- Branch: >95%
|
||||
- Function: 100%
|
||||
- Statement: 100%
|
||||
- Line coverage: >95%
|
||||
- Branch coverage: >95%
|
||||
- Function coverage: 100%
|
||||
- Statement coverage: 100%
|
||||
|
||||
Generate comprehensive tests covering ALL code paths and scenarios.
|
||||
### Dify-Specific Mocks
|
||||
|
||||
```typescript
|
||||
|
||||
// Toast
|
||||
jest.mock('@/app/components/base/toast')
|
||||
|
||||
// Next.js Router
|
||||
jest.mock('next/navigation', () => ({
|
||||
useRouter: jest.fn(),
|
||||
usePathname: jest.fn(),
|
||||
useSearchParams: jest.fn(),
|
||||
}))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Important**: The above is a quick reference only. When generating tests, strictly follow the complete specifications in [`web/scripts/TESTING.md`](./web/scripts/TESTING.md), including detailed examples, best practices, and FAQs.
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ How we prioritize:
|
|||
|
||||
For setting up the frontend service, please refer to our comprehensive [guide](https://github.com/langgenius/dify/blob/main/web/README.md) in the `web/README.md` file. This document provides detailed instructions to help you set up the frontend environment properly.
|
||||
|
||||
**Testing**: All React components must have comprehensive test coverage. See [web/scripts/TESTING.md](https://github.com/langgenius/dify/blob/main/web/scripts/TESTING.md) for detailed testing guidelines.
|
||||
|
||||
#### Backend
|
||||
|
||||
For setting up the backend service, kindly refer to our detailed [instructions](https://github.com/langgenius/dify/blob/main/api/README.md) in the `api/README.md` file. This document contains step-by-step guidance to help you get the backend up and running smoothly.
|
||||
|
|
|
|||
|
|
@ -93,20 +93,26 @@ If your IDE is VSCode, rename `web/.vscode/settings.example.json` to `web/.vscod
|
|||
|
||||
## Test
|
||||
|
||||
We start to use [Jest](https://jestjs.io/) and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) for Unit Testing.
|
||||
We use [Jest](https://jestjs.io/) and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) for Unit Testing.
|
||||
|
||||
You can create a test file with a suffix of `.spec` beside the file that to be tested. For example, if you want to test a file named `util.ts`. The test file name should be `util.spec.ts`.
|
||||
**📖 Complete Testing Guide**: See [scripts/TESTING.md](./scripts/TESTING.md) for detailed testing specifications, best practices, and examples.
|
||||
|
||||
Run test:
|
||||
### Example Code
|
||||
|
||||
If you are not familiar with writing tests, refer to:
|
||||
|
||||
- [classnames.spec.ts](./utils/classnames.spec.ts) - Utility function test example
|
||||
- [index.spec.tsx](./app/components/base/button/index.spec.tsx) - Component test example
|
||||
|
||||
### Analyze Component Complexity
|
||||
|
||||
Before writing tests, use the script to analyze component complexity:
|
||||
|
||||
```bash
|
||||
pnpm run test
|
||||
pnpm analyze-component app/components/your-component/index.tsx
|
||||
```
|
||||
|
||||
If you are not familiar with writing tests, here is some code to refer to:
|
||||
|
||||
- [classnames.spec.ts](./utils/classnames.spec.ts)
|
||||
- [index.spec.tsx](./app/components/base/button/index.spec.tsx)
|
||||
This will help you determine the testing strategy. See [scripts/TESTING.md](./scripts/TESTING.md) for details.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Frontend development utility scripts.
|
|||
|
||||
---
|
||||
|
||||
## 🚀 Generate Tests (Using Cursor AI)
|
||||
## 🚀 Generate Tests (Using AI Assistants)
|
||||
|
||||
### Quick Start
|
||||
|
||||
|
|
@ -18,11 +18,14 @@ Frontend development utility scripts.
|
|||
# 1. Analyze component
|
||||
pnpm test:gen app/components/base/button/index.tsx
|
||||
|
||||
# Output: Component analysis + Cursor prompt (auto-copied)
|
||||
# Output: Component analysis + AI prompt (auto-copied to clipboard)
|
||||
|
||||
# 2. In Cursor: Cmd+L → Cmd+V → Enter → Apply
|
||||
# 2. Paste in your AI assistant:
|
||||
# - Cursor: Cmd+L (Chat) or Cmd+I (Composer) → Cmd+V → Enter
|
||||
# - GitHub Copilot Chat: Cmd+I → Cmd+V → Enter
|
||||
# - Claude/ChatGPT: Paste the prompt directly
|
||||
|
||||
# 3. Verify
|
||||
# 3. Apply the generated test and verify
|
||||
pnpm test app/components/base/button/index.spec.tsx
|
||||
```
|
||||
|
||||
|
|
@ -37,19 +40,19 @@ pnpm test app/components/base/button/index.spec.tsx
|
|||
Script analyzes and scores components:
|
||||
|
||||
- **0-10**: 🟢 Simple (5-10 min to test)
|
||||
- **11-30Menu 🟡 Medium (15-30 min to test)
|
||||
- **31-50Menu 🟠 Complex (30-60 min to test)
|
||||
- **11-30**: 🟡 Medium (15-30 min to test)
|
||||
- **31-50**: 🟠 Complex (30-60 min to test)
|
||||
- **51+**: 🔴 Very Complex (60+ min, consider refactoring)
|
||||
|
||||
### Test Scenarios (11 types)
|
||||
### Test Scenarios
|
||||
|
||||
Defined in `.cursorrules`:
|
||||
Defined in `TESTING.md`:
|
||||
|
||||
**Must test**: Rendering, Props, Edge Cases
|
||||
**CommonMenuInteractions, Accessibility, i18n, Async
|
||||
**OptionalMenuState, Security, Performance, Snapshots
|
||||
**Conditional**: State, Effects, Events, API calls, Routing
|
||||
**Optional**: Accessibility, Performance, Snapshots
|
||||
|
||||
Cursor AI auto-selects scenarios based on component features.
|
||||
AI assistant auto-selects scenarios based on component features.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -58,10 +61,12 @@ Cursor AI auto-selects scenarios based on component features.
|
|||
```bash
|
||||
# New component
|
||||
pnpm test:gen app/components/new-feature/index.tsx
|
||||
# → Cursor → Apply → Done
|
||||
# → Paste in AI assistant → Apply → Done
|
||||
|
||||
# Or even simpler in Cursor
|
||||
# Cmd+I → "Generate test" → Apply
|
||||
# Quick shortcuts:
|
||||
# Cursor users: Cmd+I → "Generate test for [file]" → Apply
|
||||
# Copilot users: Cmd+I → Paste prompt → Accept
|
||||
# Others: Copy prompt → Paste in your AI tool
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -80,10 +85,24 @@ pnpm type-check # Type check
|
|||
|
||||
## 🎯 Customize
|
||||
|
||||
Edit `.cursorrules` to modify test standards for your team.
|
||||
Edit testing standards for your team:
|
||||
|
||||
```bash
|
||||
# Complete testing guide (for all team members)
|
||||
code web/scripts/TESTING.md
|
||||
|
||||
# Quick reference for Cursor users
|
||||
code .cursorrules
|
||||
git commit -m "docs: update test rules"
|
||||
|
||||
# Commit your changes
|
||||
git commit -m "docs: update test standards"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
- **Testing Guide**: [TESTING.md](./TESTING.md) - Complete testing specifications
|
||||
- **Quick Reference**: [.cursorrules](../../.cursorrules) - For Cursor users
|
||||
- **Examples**: [classnames.spec.ts](../utils/classnames.spec.ts), [button/index.spec.tsx](../app/components/base/button/index.spec.tsx)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,459 @@
|
|||
# Frontend Testing Guide
|
||||
|
||||
This document is the complete testing specification for the Dify frontend project.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Framework**: Next.js 15 + React 19 + TypeScript
|
||||
- **Testing Tools**: Jest 29.7 + React Testing Library 16.0
|
||||
- **Test Environment**: @happy-dom/jest-environment
|
||||
- **File Naming**: `ComponentName.spec.tsx` (same directory as component)
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pnpm test
|
||||
|
||||
# Watch mode
|
||||
pnpm test -- --watch
|
||||
|
||||
# Generate coverage report
|
||||
pnpm test -- --coverage
|
||||
|
||||
# Run specific file
|
||||
pnpm test -- path/to/file.spec.tsx
|
||||
```
|
||||
|
||||
## Component Complexity Guidelines
|
||||
|
||||
Use `pnpm analyze-component <path>` to analyze component complexity and adopt different testing strategies based on the results.
|
||||
|
||||
### 🔴 Very Complex Components (Complexity > 50)
|
||||
|
||||
- **Refactor first**: Break component into smaller pieces
|
||||
- **Integration tests**: Test complex workflows end-to-end
|
||||
- **Data-driven tests**: Use `test.each()` for multiple scenarios
|
||||
- **Performance benchmarks**: Add performance tests for critical paths
|
||||
|
||||
### ⚠️ Complex Components (Complexity 30-50)
|
||||
|
||||
- **Multiple describe blocks**: Group related test cases
|
||||
- **Integration scenarios**: Test feature combinations
|
||||
- **Organized structure**: Keep tests maintainable
|
||||
|
||||
### 📏 Large Components (500+ lines)
|
||||
|
||||
- **Consider refactoring**: Split into smaller components if possible
|
||||
- **Section testing**: Test major sections separately
|
||||
- **Helper functions**: Reduce test complexity with utilities
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
Apply the following test scenarios based on component features:
|
||||
|
||||
### 1. Rendering Tests (REQUIRED - All Components)
|
||||
|
||||
```typescript
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(<Component />)
|
||||
expect(screen.getByRole('...')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Key Points**:
|
||||
- Verify component renders properly
|
||||
- Check key elements exist
|
||||
- Use semantic queries (getByRole, getByLabelText)
|
||||
|
||||
### 2. Props Testing (REQUIRED - All Components)
|
||||
|
||||
**Must Test**:
|
||||
- ✅ Different values for each prop
|
||||
- ✅ Required vs optional props
|
||||
- ✅ Default values
|
||||
- ✅ Props type validation
|
||||
|
||||
### 3. State Management
|
||||
|
||||
#### useState
|
||||
|
||||
When testing state-related components:
|
||||
- ✅ Test initial state values
|
||||
- ✅ Test all state transitions
|
||||
- ✅ Test state reset/cleanup scenarios
|
||||
|
||||
#### useEffect
|
||||
|
||||
When testing side effects:
|
||||
- ✅ Test effect execution conditions
|
||||
- ✅ Verify dependencies array correctness
|
||||
- ✅ Test cleanup function on unmount
|
||||
|
||||
#### useState + useEffect Combined
|
||||
|
||||
- ✅ Test state initialization and updates
|
||||
- ✅ Test useEffect dependencies array
|
||||
- ✅ Test cleanup functions (useEffect return value)
|
||||
- ✅ Use `waitFor()` for async state changes
|
||||
|
||||
### 4. Performance Optimization
|
||||
|
||||
#### useCallback
|
||||
|
||||
- ✅ Verify callbacks maintain referential equality
|
||||
- ✅ Test callback dependencies
|
||||
- ✅ Ensure re-renders don't recreate functions unnecessarily
|
||||
|
||||
#### useMemo
|
||||
|
||||
- ✅ Test memoization dependencies
|
||||
- ✅ Ensure expensive computations are cached
|
||||
- ✅ Verify memo recomputation conditions
|
||||
|
||||
### 5. Event Handlers
|
||||
|
||||
**Must Test**:
|
||||
- ✅ All onClick, onChange, onSubmit handlers
|
||||
- ✅ Keyboard events (Enter, Escape, Tab, etc.)
|
||||
- ✅ Verify event.preventDefault() calls (if needed)
|
||||
- ✅ Test event bubbling/propagation
|
||||
|
||||
**Note**: Use `fireEvent` (not `userEvent`)
|
||||
|
||||
### 6. API Calls and Async Operations
|
||||
|
||||
**Must Test**:
|
||||
- ✅ Mock all API calls using `jest.mock`
|
||||
- ✅ Test retry logic (if applicable)
|
||||
- ✅ Verify error handling and user feedback
|
||||
- ✅ Use `waitFor()` for async operations
|
||||
|
||||
### 7. Next.js Routing
|
||||
|
||||
**Must Test**:
|
||||
- ✅ Mock useRouter, usePathname, useSearchParams
|
||||
- ✅ Test navigation behavior and parameters
|
||||
- ✅ Test query string handling
|
||||
- ✅ Verify route guards/redirects (if any)
|
||||
- ✅ Test URL parameter updates
|
||||
|
||||
### 8. Edge Cases (REQUIRED - All Components)
|
||||
|
||||
**Must Test**:
|
||||
- ✅ null/undefined/empty values
|
||||
- ✅ Boundary conditions
|
||||
- ✅ Error states
|
||||
- ✅ Loading states
|
||||
- ✅ Unexpected inputs
|
||||
|
||||
### 9. Accessibility Testing (Optional)
|
||||
|
||||
- Test keyboard navigation
|
||||
- Verify ARIA attributes
|
||||
- Test focus management
|
||||
- Ensure screen reader compatibility
|
||||
|
||||
### 10. Snapshot Testing (Use Sparingly)
|
||||
|
||||
**Only Use For**:
|
||||
- ✅ Stable UI (icons, badges, static layouts)
|
||||
- ✅ Snapshot small sections only
|
||||
- ✅ Prefer explicit assertions over snapshots
|
||||
- ✅ Update snapshots intentionally, not automatically
|
||||
|
||||
**Note**: Dify is a desktop application. **No need for** responsive/mobile testing.
|
||||
|
||||
## Code Style
|
||||
|
||||
### Basic Guidelines
|
||||
|
||||
- ✅ Use `fireEvent` instead of `userEvent`
|
||||
- ✅ AAA pattern: Arrange (setup) → Act (execute) → Assert (verify)
|
||||
- ✅ Descriptive test names: `"should [behavior] when [condition]"`
|
||||
- ✅ TypeScript: No `any` types
|
||||
- ✅ Cleanup: `afterEach(() => jest.clearAllMocks())`
|
||||
|
||||
### Example Structure
|
||||
|
||||
```typescript
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
||||
import Component from './index'
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('@/service/api')
|
||||
|
||||
describe('ComponentName', () => {
|
||||
// Cleanup after each test
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
// Arrange
|
||||
const props = { title: 'Test' }
|
||||
|
||||
// Act
|
||||
render(<Component {...props} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('Test')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle null data', () => {
|
||||
render(<Component data={null} />)
|
||||
expect(screen.getByText(/no data/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Dify-Specific Components
|
||||
|
||||
### General
|
||||
|
||||
1. **i18n**: Always return key
|
||||
```typescript
|
||||
jest.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}))
|
||||
```
|
||||
|
||||
2. **Toast**: Mock toast component
|
||||
```typescript
|
||||
jest.mock('@/app/components/base/toast', () => ({
|
||||
notify: jest.fn(),
|
||||
}))
|
||||
```
|
||||
|
||||
3. **Forms**: Test validation logic thoroughly
|
||||
|
||||
### Workflow Components (`workflow/`)
|
||||
|
||||
**Must Test**:
|
||||
- ⚙️ **Node configuration**: Test all node configuration options
|
||||
- ✔️ **Data validation**: Verify input/output validation rules
|
||||
- 🔄 **Variable passing**: Test data flow between nodes
|
||||
- 🔗 **Edge connections**: Test graph structure and connections
|
||||
- ❌ **Error handling**: Verify invalid configuration handling
|
||||
- 🧪 **Integration**: Test complete workflow execution paths
|
||||
|
||||
### Dataset Components (`dataset/`)
|
||||
|
||||
**Must Test**:
|
||||
- 📤 **File upload**: Test file upload and validation
|
||||
- 📄 **File types**: Verify supported format handling
|
||||
- 📃 **Pagination**: Test data loading and pagination
|
||||
- 🔍 **Search & filtering**: Test query functionality
|
||||
- 📊 **Data format handling**: Test various data formats
|
||||
- ⚠️ **Error states**: Test upload failures and invalid data
|
||||
|
||||
### Configuration Components (`app/configuration`, `config/`)
|
||||
|
||||
**Must Test**:
|
||||
- ✅ **Form validation**: Test all validation rules thoroughly
|
||||
- 💾 **Save/reset functionality**: Test data persistence
|
||||
- 🔒 **Required vs optional fields**: Verify field validation
|
||||
- 📌 **Configuration persistence**: Test state preservation
|
||||
- 💬 **Error feedback**: Verify user error messages
|
||||
- 🎯 **Default values**: Test initial configuration state
|
||||
|
||||
## Testing Strategy Quick Reference
|
||||
|
||||
### Required (All Components)
|
||||
|
||||
- ✅ Renders without crashing
|
||||
- ✅ Props (required, optional, defaults)
|
||||
- ✅ Edge cases (null, undefined, empty values)
|
||||
|
||||
### Conditional (When Present in Component)
|
||||
|
||||
- 🔄 **useState** → State initialization, transitions, cleanup
|
||||
- ⚡ **useEffect** → Execution, dependencies, cleanup
|
||||
- 🎯 **Event Handlers** → All onClick, onChange, onSubmit, keyboard events
|
||||
- 🌐 **API Calls** → Loading, success, error states
|
||||
- 🔀 **Routing** → Navigation, params, query strings
|
||||
- 🚀 **useCallback/useMemo** → Referential equality, dependencies
|
||||
- ⚙️ **Workflow** → Node config, data flow, validation
|
||||
- 📚 **Dataset** → Upload, pagination, search
|
||||
- 🎛️ **Configuration** → Form validation, persistence
|
||||
|
||||
### Complex Components (Complexity 30+)
|
||||
|
||||
- Group tests in multiple `describe` blocks
|
||||
- Test integration scenarios
|
||||
- Consider splitting component before testing
|
||||
|
||||
## Coverage Goals
|
||||
|
||||
Aim for 100% coverage:
|
||||
- **Line coverage**: >95%
|
||||
- **Branch coverage**: >95%
|
||||
- **Function coverage**: 100%
|
||||
- **Statement coverage**: 100%
|
||||
|
||||
Generate comprehensive tests covering **all** code paths and scenarios.
|
||||
|
||||
## Common Mock Patterns
|
||||
|
||||
### Mock Hooks
|
||||
|
||||
```typescript
|
||||
// Mock useState
|
||||
const mockSetState = jest.fn()
|
||||
jest.spyOn(React, 'useState').mockImplementation((init) => [init, mockSetState])
|
||||
|
||||
// Mock useContext
|
||||
jest.spyOn(React, 'useContext').mockReturnValue({ user: mockUser })
|
||||
```
|
||||
|
||||
### Mock Modules
|
||||
|
||||
```typescript
|
||||
// Mock entire module
|
||||
jest.mock('@/utils/api', () => ({
|
||||
get: jest.fn(),
|
||||
post: jest.fn(),
|
||||
}))
|
||||
|
||||
// Mock partial module
|
||||
jest.mock('@/utils/helpers', () => ({
|
||||
...jest.requireActual('@/utils/helpers'),
|
||||
specificFunction: jest.fn(),
|
||||
}))
|
||||
```
|
||||
|
||||
### Mock Next.js
|
||||
|
||||
```typescript
|
||||
// useRouter
|
||||
jest.mock('next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: jest.fn(),
|
||||
replace: jest.fn(),
|
||||
prefetch: jest.fn(),
|
||||
}),
|
||||
usePathname: () => '/current-path',
|
||||
useSearchParams: () => new URLSearchParams(),
|
||||
}))
|
||||
|
||||
// next/image
|
||||
jest.mock('next/image', () => ({
|
||||
__esModule: true,
|
||||
default: (props: any) => <img {...props} />,
|
||||
}))
|
||||
```
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
### View Rendered DOM
|
||||
|
||||
```typescript
|
||||
import { screen } from '@testing-library/react'
|
||||
|
||||
// Print entire DOM
|
||||
screen.debug()
|
||||
|
||||
// Print specific element
|
||||
screen.debug(screen.getByRole('button'))
|
||||
```
|
||||
|
||||
### Finding Elements
|
||||
|
||||
Priority order (recommended top to bottom):
|
||||
1. `getByRole` - Most recommended, follows accessibility standards
|
||||
2. `getByLabelText` - Form fields
|
||||
3. `getByPlaceholderText` - Only when no label
|
||||
4. `getByText` - Non-interactive elements
|
||||
5. `getByDisplayValue` - Current form value
|
||||
6. `getByAltText` - Images
|
||||
7. `getByTitle` - Last choice
|
||||
8. `getByTestId` - Only as last resort
|
||||
|
||||
### Async Debugging
|
||||
|
||||
```typescript
|
||||
// Wait for element to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Loaded')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Wait for element to disappear
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Loading')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Find async element
|
||||
const element = await screen.findByText('Async Content')
|
||||
```
|
||||
|
||||
## Reference Examples
|
||||
|
||||
Test examples in the project:
|
||||
- [classnames.spec.ts](../utils/classnames.spec.ts) - Utility function tests
|
||||
- [index.spec.tsx](../app/components/base/button/index.spec.tsx) - Component tests
|
||||
|
||||
## Resources
|
||||
|
||||
- [Jest Documentation](https://jestjs.io/docs/getting-started)
|
||||
- [React Testing Library Documentation](https://testing-library.com/docs/react-testing-library/intro/)
|
||||
- [Testing Library Best Practices](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library)
|
||||
- [Jest Mock Functions](https://jestjs.io/docs/mock-functions)
|
||||
|
||||
## FAQ
|
||||
|
||||
### Q: When to use `getBy` vs `queryBy` vs `findBy`?
|
||||
|
||||
- `getBy*`: Expect element to exist, throws error if not found
|
||||
- `queryBy*`: Element may not exist, returns null if not found
|
||||
- `findBy*`: Async query, returns Promise
|
||||
|
||||
### Q: How to test conditional rendering?
|
||||
|
||||
```typescript
|
||||
it('should conditionally render content', () => {
|
||||
const { rerender } = render(<Component show={false} />)
|
||||
expect(screen.queryByText('Content')).not.toBeInTheDocument()
|
||||
|
||||
rerender(<Component show={true} />)
|
||||
expect(screen.getByText('Content')).toBeInTheDocument()
|
||||
})
|
||||
```
|
||||
|
||||
### Q: How to test custom hooks?
|
||||
|
||||
```typescript
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
|
||||
it('should update counter', () => {
|
||||
const { result } = renderHook(() => useCounter())
|
||||
|
||||
act(() => {
|
||||
result.current.increment()
|
||||
})
|
||||
|
||||
expect(result.current.count).toBe(1)
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Remember**: Writing tests is not just about coverage, but ensuring code quality and maintainability. Good tests should be clear, concise, and meaningful.
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Component Analyzer for Cursor AI
|
||||
* Component Analyzer for Test Generation
|
||||
*
|
||||
* Analyzes a component and generates a structured prompt for Cursor AI.
|
||||
* Copy the output and paste into Cursor Chat (Cmd+L) or Composer (Cmd+I).
|
||||
* Analyzes a component and generates a structured prompt for AI assistants.
|
||||
* Works with Cursor, GitHub Copilot, and other AI coding tools.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/analyze-component.js <component-path>
|
||||
|
|
@ -11,6 +11,8 @@
|
|||
* Examples:
|
||||
* node scripts/analyze-component.js app/components/base/button/index.tsx
|
||||
* node scripts/analyze-component.js app/components/workflow/nodes/llm/panel.tsx
|
||||
*
|
||||
* For complete testing guidelines, see: web/scripts/TESTING.md
|
||||
*/
|
||||
|
||||
const fs = require('node:fs')
|
||||
|
|
@ -272,16 +274,16 @@ class ComponentAnalyzer {
|
|||
}
|
||||
|
||||
// ============================================================================
|
||||
// Prompt Builder for Cursor
|
||||
// Prompt Builder for AI Assistants
|
||||
// ============================================================================
|
||||
|
||||
class CursorPromptBuilder {
|
||||
class TestPromptBuilder {
|
||||
build(analysis, _sourceCode) {
|
||||
const testPath = analysis.path.replace(/\.tsx?$/, '.spec.tsx')
|
||||
|
||||
return `
|
||||
╔════════════════════════════════════════════════════════════════════════════╗
|
||||
║ 📋 GENERATE TEST FOR DIFY COMPONENT ║
|
||||
║ 📋 GENERATE TEST FOR DIFY COMPONENT ║
|
||||
╚════════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
📍 Component: ${analysis.name}
|
||||
|
|
@ -307,7 +309,7 @@ Features Detected:
|
|||
${analysis.hasAPI ? '✓' : '✗'} API calls
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📝 TASK FOR CURSOR AI:
|
||||
📝 TASK:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Please generate a comprehensive test file for this component at:
|
||||
|
|
@ -316,16 +318,18 @@ Please generate a comprehensive test file for this component at:
|
|||
The component is located at:
|
||||
${analysis.path}
|
||||
|
||||
Follow the testing guidelines in .cursorrules file.
|
||||
Follow the testing guidelines in:
|
||||
- web/scripts/TESTING.md (complete testing guide)
|
||||
- .cursorrules (quick reference for Cursor users)
|
||||
|
||||
${this.getSpecificGuidelines(analysis)}
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📋 COPY THIS TO CURSOR:
|
||||
📋 PROMPT FOR AI ASSISTANT (COPY THIS TO YOUR AI ASSISTANT):
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Generate a comprehensive test file for @${analysis.path} following the project's testing guidelines in .cursorrules.
|
||||
Generate a comprehensive test file for @${analysis.path} following the project's testing guidelines in web/scripts/TESTING.md.
|
||||
|
||||
Including but not limited to:
|
||||
${this.buildFocusPoints(analysis)}
|
||||
|
|
@ -514,8 +518,13 @@ Examples:
|
|||
node scripts/analyze-component.js app/components/base/button/index.tsx
|
||||
node scripts/analyze-component.js app/components/workflow/nodes/llm/panel.tsx
|
||||
|
||||
This tool analyzes your component and generates a prompt for Cursor AI.
|
||||
Copy the output and use it in Cursor Chat (Cmd+L) or Composer (Cmd+I).
|
||||
This tool analyzes your component and generates a prompt for AI assistants.
|
||||
Copy the output and use it with:
|
||||
- Cursor (Cmd+L for Chat, Cmd+I for Composer)
|
||||
- GitHub Copilot Chat (Cmd+I)
|
||||
- Claude, ChatGPT, or any other AI coding tool
|
||||
|
||||
For complete testing guidelines, see: web/scripts/TESTING.md
|
||||
`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
|
@ -587,8 +596,8 @@ This component is too complex to test effectively. Please consider:
|
|||
process.exit(0)
|
||||
}
|
||||
|
||||
// Build prompt for Cursor
|
||||
const builder = new CursorPromptBuilder()
|
||||
// Build prompt for AI assistant
|
||||
const builder = new TestPromptBuilder()
|
||||
const prompt = builder.build(analysis, sourceCode)
|
||||
|
||||
// Output
|
||||
|
|
@ -619,8 +628,13 @@ This component is too complex to test effectively. Please consider:
|
|||
encoding: 'utf-8',
|
||||
})
|
||||
|
||||
if (result.status === 0)
|
||||
console.log('\n📋 Prompt copied to clipboard! Paste it in Cursor Chat (Cmd+L).\n')
|
||||
if (result.status === 0) {
|
||||
console.log('\n📋 Prompt copied to clipboard!')
|
||||
console.log(' Paste it in your AI assistant:')
|
||||
console.log(' - Cursor: Cmd+L (Chat) or Cmd+I (Composer)')
|
||||
console.log(' - GitHub Copilot Chat: Cmd+I')
|
||||
console.log(' - Or any other AI coding tool\n')
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// pbcopy failed, but don't break the script
|
||||
|
|
|
|||
Loading…
Reference in New Issue