web/docs/test.md
This document is the single source of truth for automated frontend tests under web/ and packages/dify-ui/. Tests should protect product behavior and make refactoring safer. They are not a file-by-file completion exercise.
Write or update a test when a change affects a stable, observable contract:
Do not add a test only because:
useState, useEffect, useMemo, or useCallback.For visual-only changes, verify the real UI at representative widths and states. Use browser, screenshot, Storybook, or end-to-end coverage when the risk justifies automation.
Coverage is a diagnostic signal, not a quality target. This guide defines no required percentage and reviewers should not request tests solely to increase coverage. Use a report to find suspicious gaps, then decide whether each gap represents a product risk worth protecting.
Use the smallest boundary that proves the product contract without coupling the test to implementation:
happy-dom cannot represent faithfully.packages/dify-ui/README.md for the Storybook and Vitest boundary of Dify UI primitives.Test the behavior owner. Barrel exports, pass-through wrappers, and purely presentational children do not need separate tests when the owning feature already proves their contract.
null, undefined, or extreme values without a reachable scenario.Prefer selectors in this order:
getByRole with an accessible name.getByLabelText for labeled form controls.within in React Testing Library or locator chaining in Browser Mode.getByText, getByPlaceholderText, or other user-visible queries when appropriate.getByTestId only for boundaries with no useful DOM semantics, such as canvas output, editor shims, or mocked non-visual integrations.If an interactive control cannot be found semantically, first check whether the production markup needs a real button, link, label, landmark, or accessible name.
userEvent.setup() instance inside the test. Use fireEvent only when the low-level event itself is the contract..element() only for DOM APIs that locators do not expose.queryBy* for absence and findBy* for asynchronously appearing elements. In Browser Mode, use expect.element for eventual assertions.Use real components within the owning feature by default, especially when primitive semantics, context wiring, or integration behavior matters. Mock only where isolation improves the signal:
Mocks must preserve the public contract needed by the test. Do not replace Dify UI or legacy base primitives with semantically inaccurate stubs just to make a test easier.
web/__mocks__/ only when multiple suites genuinely share it.findBy*, and waitFor.findBy* for an element that appears asynchronously and waitFor for an eventually true external assertion.web/vitest.setup.ts already runs Testing Library cleanup and resets Zustand stores after each test.vi.clearAllMocks() in beforeEach when a suite relies on mock call history. Do not use afterEach to prepare the next test.web/ run in happy-dom through web/vite.config.ts and load web/vitest.setup.ts.packages/dify-ui/ use separate Vitest Browser Mode projects: unit specs load the package styles through vitest.setup.ts, while Storybook tests run stories through @storybook/addon-vitest.__tests__/ directory. Existing colocated utility and hook specs may follow their owning module's convention. Cross-feature integration specs belong in web/__tests__/.react-i18next mock is loaded globally. Use createReactI18nextMock from web/test/i18n-mock only when a test needs custom translations.nuqs behavior, use the helpers in web/test/nuqs-testing.tsx and assert URL updates. Mock nuqs only when URL synchronization is explicitly outside the test contract.When working across several files, order the work by dependency and verify each coherent slice before continuing. Do not create one test file per source file by default.
Run from web/:
# Focused spec or directory
vp test run path/to/spec-or-directory
# All web tests
vp test run
# Watch mode
vp test watch path/to/spec
# Diagnostic coverage report; not an acceptance target
vp test run --coverage path/to/spec-or-directory