# 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 ` 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() 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() // Assert expect(screen.getByText('Test')).toBeInTheDocument() }) }) describe('User Interactions', () => { it('should handle click events', () => { const handleClick = jest.fn() render() fireEvent.click(screen.getByRole('button')) expect(handleClick).toHaveBeenCalledTimes(1) }) }) describe('Edge Cases', () => { it('should handle null data', () => { render() 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) => , })) ``` ## 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() expect(screen.queryByText('Content')).not.toBeInTheDocument() rerender() 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.