mirror of
https://github.com/langgenius/dify.git
synced 2026-09-05 08:48:10 +08:00
docs(web): update dev guide (#33815)
This commit is contained in:
parent
b0566b4193
commit
ec8ff89dc1
5
.github/workflows/autofix.yml
vendored
5
.github/workflows/autofix.yml
vendored
@ -94,11 +94,6 @@ jobs:
|
|||||||
find . -name "*.py" -type f -exec sed -i.bak -E 's/"([^"]+)" \| None/Optional["\1"]/g; s/'"'"'([^'"'"']+)'"'"' \| None/Optional['"'"'\1'"'"']/g' {} \;
|
find . -name "*.py" -type f -exec sed -i.bak -E 's/"([^"]+)" \| None/Optional["\1"]/g; s/'"'"'([^'"'"']+)'"'"' \| None/Optional['"'"'\1'"'"']/g' {} \;
|
||||||
find . -name "*.py.bak" -type f -delete
|
find . -name "*.py.bak" -type f -delete
|
||||||
|
|
||||||
# mdformat breaks YAML front matter in markdown files. Add --exclude for directories containing YAML front matter.
|
|
||||||
- name: mdformat
|
|
||||||
run: |
|
|
||||||
uvx --python 3.13 mdformat . --exclude ".agents/skills/**"
|
|
||||||
|
|
||||||
- name: Setup web environment
|
- name: Setup web environment
|
||||||
if: steps.web-changes.outputs.any_changed == 'true'
|
if: steps.web-changes.outputs.any_changed == 'true'
|
||||||
uses: ./.github/actions/setup-web
|
uses: ./.github/actions/setup-web
|
||||||
|
|||||||
@ -6,19 +6,23 @@ NEXT_PUBLIC_EDITION=SELF_HOSTED
|
|||||||
NEXT_PUBLIC_BASE_PATH=
|
NEXT_PUBLIC_BASE_PATH=
|
||||||
# The base URL of console application, refers to the Console base URL of WEB service if console domain is
|
# The base URL of console application, refers to the Console base URL of WEB service if console domain is
|
||||||
# different from api or web app domain.
|
# different from api or web app domain.
|
||||||
# example: http://cloud.dify.ai/console/api
|
# example: https://cloud.dify.ai/console/api
|
||||||
NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api
|
NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api
|
||||||
# The URL for Web APP, refers to the Web App base URL of WEB service if web app domain is different from
|
# The URL for Web APP, refers to the Web App base URL of WEB service if web app domain is different from
|
||||||
# console or api domain.
|
# console or api domain.
|
||||||
# example: http://udify.app/api
|
# example: https://udify.app/api
|
||||||
NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api
|
NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api
|
||||||
# Dev-only Hono proxy targets. The frontend keeps requesting http://localhost:5001 directly.
|
# When the frontend and backend run on different subdomains, set NEXT_PUBLIC_COOKIE_DOMAIN=1.
|
||||||
|
NEXT_PUBLIC_COOKIE_DOMAIN=
|
||||||
|
|
||||||
|
# Dev-only Hono proxy targets.
|
||||||
|
# The frontend keeps requesting http://localhost:5001 directly,
|
||||||
|
# the proxy server will forward the request to the target server,
|
||||||
|
# so that you don't need to run a separate backend server and use online API in development.
|
||||||
HONO_PROXY_HOST=127.0.0.1
|
HONO_PROXY_HOST=127.0.0.1
|
||||||
HONO_PROXY_PORT=5001
|
HONO_PROXY_PORT=5001
|
||||||
HONO_CONSOLE_API_PROXY_TARGET=
|
HONO_CONSOLE_API_PROXY_TARGET=
|
||||||
HONO_PUBLIC_API_PROXY_TARGET=
|
HONO_PUBLIC_API_PROXY_TARGET=
|
||||||
# When the frontend and backend run on different subdomains, set NEXT_PUBLIC_COOKIE_DOMAIN=1.
|
|
||||||
NEXT_PUBLIC_COOKIE_DOMAIN=
|
|
||||||
|
|
||||||
# The API PREFIX for MARKETPLACE
|
# The API PREFIX for MARKETPLACE
|
||||||
NEXT_PUBLIC_MARKETPLACE_API_PREFIX=https://marketplace.dify.ai/api/v1
|
NEXT_PUBLIC_MARKETPLACE_API_PREFIX=https://marketplace.dify.ai/api/v1
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Dify Frontend
|
# Dify Frontend
|
||||||
|
|
||||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
This is a [Next.js] project, but you can dev with [vinext].
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@ -8,8 +8,11 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next
|
|||||||
|
|
||||||
Before starting the web frontend service, please make sure the following environment is ready.
|
Before starting the web frontend service, please make sure the following environment is ready.
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org)
|
- [Node.js]
|
||||||
- [pnpm](https://pnpm.io)
|
- [pnpm]
|
||||||
|
|
||||||
|
You can also use [Vite+] with the corresponding `vp` commands.
|
||||||
|
For example, use `vp install` instead of `pnpm install` and `vp test` instead of `pnpm run test`.
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> It is recommended to install and enable Corepack to manage package manager versions automatically:
|
> It is recommended to install and enable Corepack to manage package manager versions automatically:
|
||||||
@ -19,7 +22,7 @@ Before starting the web frontend service, please make sure the following environ
|
|||||||
> corepack enable
|
> corepack enable
|
||||||
> ```
|
> ```
|
||||||
>
|
>
|
||||||
> Learn more: [Corepack](https://github.com/nodejs/corepack#readme)
|
> Learn more: [Corepack]
|
||||||
|
|
||||||
First, install the dependencies:
|
First, install the dependencies:
|
||||||
|
|
||||||
@ -27,31 +30,14 @@ First, install the dependencies:
|
|||||||
pnpm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, configure the environment variables. Create a file named `.env.local` in the current directory and copy the contents from `.env.example`. Modify the values of these environment variables according to your requirements:
|
Then, configure the environment variables.
|
||||||
|
Create a file named `.env.local` in the current directory and copy the contents from `.env.example`.
|
||||||
|
Modify the values of these environment variables according to your requirements:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env.local
|
cp .env.example .env.local
|
||||||
```
|
```
|
||||||
|
|
||||||
```txt
|
|
||||||
# For production release, change this to PRODUCTION
|
|
||||||
NEXT_PUBLIC_DEPLOY_ENV=DEVELOPMENT
|
|
||||||
# The deployment edition, SELF_HOSTED
|
|
||||||
NEXT_PUBLIC_EDITION=SELF_HOSTED
|
|
||||||
# The base URL of console application, refers to the Console base URL of WEB service if console domain is
|
|
||||||
# different from api or web app domain.
|
|
||||||
# example: http://cloud.dify.ai/console/api
|
|
||||||
NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api
|
|
||||||
NEXT_PUBLIC_COOKIE_DOMAIN=
|
|
||||||
# The URL for Web APP, refers to the Web App base URL of WEB service if web app domain is different from
|
|
||||||
# console or api domain.
|
|
||||||
# example: http://udify.app/api
|
|
||||||
NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api
|
|
||||||
|
|
||||||
# SENTRY
|
|
||||||
NEXT_PUBLIC_SENTRY_DSN=
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
>
|
>
|
||||||
> 1. When the frontend and backend run on different subdomains, set NEXT_PUBLIC_COOKIE_DOMAIN=1. The frontend and backend must be under the same top-level domain in order to share authentication cookies.
|
> 1. When the frontend and backend run on different subdomains, set NEXT_PUBLIC_COOKIE_DOMAIN=1. The frontend and backend must be under the same top-level domain in order to share authentication cookies.
|
||||||
@ -61,11 +47,16 @@ Finally, run the development server:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run dev
|
pnpm run dev
|
||||||
|
# or if you are using vinext which provides a better development experience
|
||||||
|
pnpm run dev:vinext
|
||||||
|
# (optional) start the dev proxy server so that you can use online API in development
|
||||||
|
pnpm run dev:proxy
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
Open <http://localhost:3000> with your browser to see the result.
|
||||||
|
|
||||||
You can start editing the file under folder `app`. The page auto-updates as you edit the file.
|
You can start editing the file under folder `app`.
|
||||||
|
The page auto-updates as you edit the file.
|
||||||
|
|
||||||
## Deploy
|
## Deploy
|
||||||
|
|
||||||
@ -91,7 +82,7 @@ pnpm run start --port=3001 --host=0.0.0.0
|
|||||||
|
|
||||||
## Storybook
|
## Storybook
|
||||||
|
|
||||||
This project uses [Storybook](https://storybook.js.org/) for UI component development.
|
This project uses [Storybook] for UI component development.
|
||||||
|
|
||||||
To start the storybook server, run:
|
To start the storybook server, run:
|
||||||
|
|
||||||
@ -99,19 +90,24 @@ To start the storybook server, run:
|
|||||||
pnpm storybook
|
pnpm storybook
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:6006](http://localhost:6006) with your browser to see the result.
|
Open <http://localhost:6006> with your browser to see the result.
|
||||||
|
|
||||||
## Lint Code
|
## Lint Code
|
||||||
|
|
||||||
If your IDE is VSCode, rename `web/.vscode/settings.example.json` to `web/.vscode/settings.json` for lint code setting.
|
If your IDE is VSCode, rename `web/.vscode/settings.example.json` to `web/.vscode/settings.json` for lint code setting.
|
||||||
|
|
||||||
Then follow the [Lint Documentation](./docs/lint.md) to lint the code.
|
Then follow the [Lint Documentation] to lint the code.
|
||||||
|
|
||||||
## Test
|
## Test
|
||||||
|
|
||||||
We use [Vitest](https://vitest.dev/) and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) for Unit Testing.
|
We use [Vitest] and [React Testing Library] for Unit Testing.
|
||||||
|
|
||||||
**📖 Complete Testing Guide**: See [web/testing/testing.md](./testing/testing.md) for detailed testing specifications, best practices, and examples.
|
**📖 Complete Testing Guide**: See [web/docs/test.md] for detailed testing specifications, best practices, and examples.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> As we are using Vite+, the `vitest` command is not available.
|
||||||
|
> Please make sure to run tests with `vp` commands.
|
||||||
|
> For example, use `npx vp test` instead of `npx vitest`.
|
||||||
|
|
||||||
Run test:
|
Run test:
|
||||||
|
|
||||||
@ -119,12 +115,17 @@ Run test:
|
|||||||
pnpm test
|
pnpm test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Our test is not fully stable yet, and we are actively working on improving it.
|
||||||
|
> If you encounter test failures only in CI but not locally, please feel free to ignore them and report the issue to us.
|
||||||
|
> You can try to re-run the test in CI, and it may pass successfully.
|
||||||
|
|
||||||
### Example Code
|
### Example Code
|
||||||
|
|
||||||
If you are not familiar with writing tests, refer to:
|
If you are not familiar with writing tests, refer to:
|
||||||
|
|
||||||
- [classnames.spec.ts](./utils/classnames.spec.ts) - Utility function test example
|
- [classnames.spec.ts] - Utility function test example
|
||||||
- [index.spec.tsx](./app/components/base/button/index.spec.tsx) - Component test example
|
- [index.spec.tsx] - Component test example
|
||||||
|
|
||||||
### Analyze Component Complexity
|
### Analyze Component Complexity
|
||||||
|
|
||||||
@ -134,7 +135,7 @@ Before writing tests, use the script to analyze component complexity:
|
|||||||
pnpm analyze-component app/components/your-component/index.tsx
|
pnpm analyze-component app/components/your-component/index.tsx
|
||||||
```
|
```
|
||||||
|
|
||||||
This will help you determine the testing strategy. See [web/testing/testing.md](./testing/testing.md) for details.
|
This will help you determine the testing strategy. See [web/testing/testing.md] for details.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@ -142,4 +143,19 @@ Visit <https://docs.dify.ai> to view the full documentation.
|
|||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
The Dify community can be found on [Discord community](https://discord.gg/5AEfbxcd9k), where you can ask questions, voice ideas, and share your projects.
|
The Dify community can be found on [Discord community], where you can ask questions, voice ideas, and share your projects.
|
||||||
|
|
||||||
|
[Corepack]: https://github.com/nodejs/corepack#readme
|
||||||
|
[Discord community]: https://discord.gg/5AEfbxcd9k
|
||||||
|
[Lint Documentation]: ./docs/lint.md
|
||||||
|
[Next.js]: https://nextjs.org
|
||||||
|
[Node.js]: https://nodejs.org
|
||||||
|
[React Testing Library]: https://testing-library.com/docs/react-testing-library/intro
|
||||||
|
[Storybook]: https://storybook.js.org
|
||||||
|
[Vite+]: https://viteplus.dev
|
||||||
|
[Vitest]: https://vitest.dev
|
||||||
|
[classnames.spec.ts]: ./utils/classnames.spec.ts
|
||||||
|
[index.spec.tsx]: ./app/components/base/button/index.spec.tsx
|
||||||
|
[pnpm]: https://pnpm.io
|
||||||
|
[vinext]: https://github.com/cloudflare/vinext
|
||||||
|
[web/docs/test.md]: ./docs/test.md
|
||||||
|
|||||||
@ -12,25 +12,44 @@ We use ESLint and Typescript to maintain code quality and consistency across the
|
|||||||
pnpm eslint [options] file.js [file.js] [dir]
|
pnpm eslint [options] file.js [file.js] [dir]
|
||||||
```
|
```
|
||||||
|
|
||||||
**`--cache`**: Caches lint results for faster subsequent runs. Keep this enabled by default; only disable when you encounter unexpected lint results.
|
**`--cache`**: Caches lint results for faster subsequent runs.
|
||||||
|
Keep this enabled by default; only disable when you encounter unexpected lint results.
|
||||||
|
|
||||||
**`--concurrency`**: Enables multi-threaded linting. Use `--concurrency=auto` or experiment with specific numbers to find the optimal setting for your machine. Keep this enabled when linting multiple files.
|
**`--concurrency`**: Enables multi-threaded linting.
|
||||||
|
Use `--concurrency=auto` or experiment with specific numbers to find the optimal setting for your machine.
|
||||||
|
Keep this enabled when linting multiple files.
|
||||||
|
|
||||||
- [ESLint multi-thread linting blog post](https://eslint.org/blog/2025/08/multithread-linting/)
|
- [ESLint multi-thread linting blog post]
|
||||||
|
|
||||||
**`--fix`**: Automatically fixes auto-fixable rule violations. Always review the diff before committing to ensure no unintended changes.
|
**`--fix`**: Automatically fixes auto-fixable rule violations.
|
||||||
|
Keep this enabled so that you do not have to care about auto-fixable errors (e.g., formatting issues) and can focus on more important errors.
|
||||||
|
Always review the diff before committing to ensure no unintended changes.
|
||||||
|
|
||||||
**`--quiet`**: Suppresses warnings and only shows errors. Useful when you want to reduce noise from existing issues.
|
**`--quiet`**: Suppresses warnings and only shows errors.
|
||||||
|
Useful when you want to reduce noise from existing warnings.
|
||||||
|
|
||||||
**`--suppress-all`**: Temporarily suppresses error-level violations and records them, allowing CI to pass. Treat this as an escape hatch—fix these errors when time permits.
|
**`--suppress-all`**: Temporarily suppresses error-level violations and records them, allowing CI to pass.
|
||||||
|
Treat this as an escape hatch—fix these errors when time permits.
|
||||||
|
|
||||||
**`--prune-suppressions`**: Removes outdated suppressions after you've fixed the underlying errors.
|
**`--prune-suppressions`**: Removes outdated suppressions after you've fixed the underlying errors.
|
||||||
|
|
||||||
- [ESLint bulk suppressions blog post](https://eslint.org/blog/2025/04/introducing-bulk-suppressions/)
|
- [ESLint bulk suppressions blog post]
|
||||||
|
|
||||||
|
### The Auto-Fix Workflow and Suppression Strategy
|
||||||
|
|
||||||
|
To streamline your development process, we recommend configuring your editor to automatically fix lint errors on save.
|
||||||
|
As a fallback, any remaining autofixable errors will be corrected automatically when you commit.
|
||||||
|
To prevent workflow disruptions, these commit hooks are intentionally bypassed when you are merging branches, rebasing, or cherry-picking.
|
||||||
|
|
||||||
|
Additionally, we currently track many existing legacy errors in eslint-suppressions.json.
|
||||||
|
You do not need to spend time manually pruning these suppressions (we already append `--pass-on-unpruned-suppressions` in the commit hook);
|
||||||
|
once you open a Pull Request, the CI pipeline will automatically handle the cleanup for you.
|
||||||
|
|
||||||
### Type-Aware Linting
|
### Type-Aware Linting
|
||||||
|
|
||||||
Some ESLint rules require type information, such as [no-leaked-conditional-rendering](https://www.eslint-react.xyz/docs/rules/no-leaked-conditional-rendering). However, [typed linting via typescript-eslint](https://typescript-eslint.io/getting-started/typed-linting) is too slow for practical use, so we use [TSSLint](https://github.com/johnsoncodehk/tsslint) instead.
|
Some ESLint rules require type information, such as [no-leaked-conditional-rendering].
|
||||||
|
However, [typed linting via typescript-eslint] is too slow for practical use.
|
||||||
|
So we use [TSSLint] instead.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm lint:tss
|
pnpm lint:tss
|
||||||
@ -43,7 +62,7 @@ This command lints the entire project and is intended for final verification bef
|
|||||||
If a new rule causes many existing code errors or automatic fixes generate too many diffs, do not use the `--fix` option for automatic fixes.
|
If a new rule causes many existing code errors or automatic fixes generate too many diffs, do not use the `--fix` option for automatic fixes.
|
||||||
You can introduce the rule first, then use the `--suppress-all` option to temporarily suppress these errors, and gradually fix them in subsequent changes.
|
You can introduce the rule first, then use the `--suppress-all` option to temporarily suppress these errors, and gradually fix them in subsequent changes.
|
||||||
|
|
||||||
For overlay migration policy and cleanup phases, see [Overlay Migration Guide](./overlay-migration.md).
|
For overlay migration policy and cleanup phases, see [Overlay Migration Guide].
|
||||||
|
|
||||||
## Type Check
|
## Type Check
|
||||||
|
|
||||||
@ -55,4 +74,12 @@ However, it can be useful to run the TypeScript 7 command-line (tsgo) to type ch
|
|||||||
pnpm type-check:tsgo
|
pnpm type-check:tsgo
|
||||||
```
|
```
|
||||||
|
|
||||||
Prefer using `tsgo` for type checking as it is significantly faster than the standard TypeScript compiler. Only fall back to `pnpm type-check` (which uses `tsc`) if you encounter unexpected results.
|
Prefer using `tsgo` for type checking as it is significantly faster than the standard TypeScript compiler.
|
||||||
|
Only fall back to `pnpm type-check` (which uses `tsc`) if you encounter unexpected results.
|
||||||
|
|
||||||
|
[ESLint bulk suppressions blog post]: https://eslint.org/blog/2025/04/introducing-bulk-suppressions
|
||||||
|
[ESLint multi-thread linting blog post]: https://eslint.org/blog/2025/08/multithread-linting
|
||||||
|
[Overlay Migration Guide]: ./overlay-migration.md
|
||||||
|
[TSSLint]: https://github.com/johnsoncodehk/tsslint
|
||||||
|
[no-leaked-conditional-rendering]: https://www.eslint-react.xyz/docs/rules/no-leaked-conditional-rendering
|
||||||
|
[typed linting via typescript-eslint]: https://typescript-eslint.io/getting-started/typed-linting
|
||||||
|
|||||||
@ -23,7 +23,7 @@ This document tracks the migration away from legacy overlay APIs.
|
|||||||
- `@/app/components/base/ui/alert-dialog`
|
- `@/app/components/base/ui/alert-dialog`
|
||||||
- `@/app/components/base/ui/select`
|
- `@/app/components/base/ui/select`
|
||||||
- `@/app/components/base/ui/toast`
|
- `@/app/components/base/ui/toast`
|
||||||
- Tracking issue: https://github.com/langgenius/dify/issues/32767
|
- Tracking issue: <https://github.com/langgenius/dify/issues/32767>
|
||||||
|
|
||||||
## ESLint policy
|
## ESLint policy
|
||||||
|
|
||||||
@ -72,14 +72,14 @@ All new overlay primitives in `base/ui/` share a single z-index value:
|
|||||||
During the migration period, legacy and new overlays coexist. Legacy overlays
|
During the migration period, legacy and new overlays coexist. Legacy overlays
|
||||||
portal to `document.body` with explicit z-index values:
|
portal to `document.body` with explicit z-index values:
|
||||||
|
|
||||||
| Layer | z-index | Components |
|
| Layer | z-index | Components |
|
||||||
|-------|---------|------------|
|
| --------------------------------- | ---------------- | -------------------------------------------- |
|
||||||
| Legacy Drawer | `z-[30]` | `base/drawer` |
|
| Legacy Drawer | `z-[30]` | `base/drawer` |
|
||||||
| Legacy Modal | `z-[60]` | `base/modal` (default) |
|
| Legacy Modal | `z-[60]` | `base/modal` (default) |
|
||||||
| Legacy PortalToFollowElem callers | up to `z-[1001]` | various business components |
|
| Legacy PortalToFollowElem callers | up to `z-[1001]` | various business components |
|
||||||
| **New UI primitives** | **`z-[1002]`** | `base/ui/*` (Popover, Dialog, Tooltip, etc.) |
|
| **New UI primitives** | **`z-[1002]`** | `base/ui/*` (Popover, Dialog, Tooltip, etc.) |
|
||||||
| Legacy Modal (highPriority) | `z-[1100]` | `base/modal` (`highPriority={true}`) |
|
| Legacy Modal (highPriority) | `z-[1100]` | `base/modal` (`highPriority={true}`) |
|
||||||
| Toast (legacy + new) | `z-[1101]` | `base/toast`, `base/ui/toast` |
|
| Toast (legacy + new) | `z-[1101]` | `base/toast`, `base/ui/toast` |
|
||||||
|
|
||||||
`z-[1002]` sits above all common legacy overlays, so new primitives always
|
`z-[1002]` sits above all common legacy overlays, so new primitives always
|
||||||
render on top without needing per-call-site z-index hacks. Among themselves,
|
render on top without needing per-call-site z-index hacks. Among themselves,
|
||||||
|
|||||||
@ -119,13 +119,11 @@ When assigned to test a **directory/path** (not just a single file), follow thes
|
|||||||
Choose based on directory complexity:
|
Choose based on directory complexity:
|
||||||
|
|
||||||
1. **Single spec file (Integration approach)** - Preferred for related components
|
1. **Single spec file (Integration approach)** - Preferred for related components
|
||||||
|
|
||||||
- Minimize mocking - use real project components
|
- Minimize mocking - use real project components
|
||||||
- Test actual integration between components
|
- Test actual integration between components
|
||||||
- Only mock: API calls, complex context providers, third-party libs
|
- Only mock: API calls, complex context providers, third-party libs
|
||||||
|
|
||||||
1. **Multiple spec files (Unit approach)** - For complex directories
|
1. **Multiple spec files (Unit approach)** - For complex directories
|
||||||
|
|
||||||
- One spec file per component/hook/utility
|
- One spec file per component/hook/utility
|
||||||
- More isolated testing
|
- More isolated testing
|
||||||
- Useful when components are independent
|
- Useful when components are independent
|
||||||
@ -139,7 +137,7 @@ When using a single spec file:
|
|||||||
- ❌ **DO NOT mock** base components (`@/app/components/base/*`)
|
- ❌ **DO NOT mock** base components (`@/app/components/base/*`)
|
||||||
- ❌ **DO NOT mock** sibling/child components in the same directory
|
- ❌ **DO NOT mock** sibling/child components in the same directory
|
||||||
|
|
||||||
> See [Example Structure](#example-structure) for correct import/mock patterns.
|
> See [Example Structure] for correct import/mock patterns.
|
||||||
|
|
||||||
## Testing Components with Dedicated Dependencies
|
## Testing Components with Dedicated Dependencies
|
||||||
|
|
||||||
@ -185,8 +183,8 @@ Treat component state as part of the public behavior: confirm the initial render
|
|||||||
- ✅ When creating lightweight provider stubs, mirror the real default values and surface helper builders (for example `createMockWorkflowContext`).
|
- ✅ When creating lightweight provider stubs, mirror the real default values and surface helper builders (for example `createMockWorkflowContext`).
|
||||||
- ✅ Reset shared stores (React context, Zustand, TanStack Query cache) between tests to avoid leaking state. Prefer helper factory functions over module-level singletons in specs.
|
- ✅ Reset shared stores (React context, Zustand, TanStack Query cache) between tests to avoid leaking state. Prefer helper factory functions over module-level singletons in specs.
|
||||||
- ✅ For hooks that read from context, use `renderHook` with a custom wrapper that supplies required providers.
|
- ✅ For hooks that read from context, use `renderHook` with a custom wrapper that supplies required providers.
|
||||||
- ✅ **Use factory functions for mock data**: Import actual types and create factory functions with complete defaults (see [Test Data Builders](#9-test-data-builders-anti-hardcoding) section).
|
- ✅ **Use factory functions for mock data**: Import actual types and create factory functions with complete defaults (see [Test Data Builders] section).
|
||||||
- ✅ If it's need to mock some common context provider used across many components (for example, `ProviderContext`), put it in __mocks__/context(for example, `__mocks__/context/provider-context`). To dynamically control the mock behavior (for example, toggling plan type), use module-level variables to track state and change them(for example, `context/provider-context-mock.spec.tsx`).
|
- ✅ If it's need to mock some common context provider used across many components (for example, `ProviderContext`), put it in **mocks**/context(for example, `__mocks__/context/provider-context`). To dynamically control the mock behavior (for example, toggling plan type), use module-level variables to track state and change them(for example, `context/provider-context-mock.spec.tsx`).
|
||||||
- ✅ Use factory functions to create mock data with TypeScript types. This ensures type safety and makes tests more maintainable.
|
- ✅ Use factory functions to create mock data with TypeScript types. This ensures type safety and makes tests more maintainable.
|
||||||
|
|
||||||
**Rules**:
|
**Rules**:
|
||||||
@ -363,7 +361,6 @@ describe('ComponentName', () => {
|
|||||||
1. **i18n**: Uses global mock in `web/vitest.setup.ts` (auto-loaded by Vitest setup)
|
1. **i18n**: Uses global mock in `web/vitest.setup.ts` (auto-loaded by Vitest setup)
|
||||||
|
|
||||||
The global mock provides:
|
The global mock provides:
|
||||||
|
|
||||||
- `useTranslation` - returns translation keys with namespace prefix
|
- `useTranslation` - returns translation keys with namespace prefix
|
||||||
- `Trans` component - renders i18nKey and components
|
- `Trans` component - renders i18nKey and components
|
||||||
- `useMixedTranslation` (from `@/app/components/plugins/marketplace/hooks`)
|
- `useMixedTranslation` (from `@/app/components/plugins/marketplace/hooks`)
|
||||||
@ -533,16 +530,25 @@ const element = await screen.findByText('Async Content')
|
|||||||
|
|
||||||
Test examples in the project:
|
Test examples in the project:
|
||||||
|
|
||||||
- [classnames.spec.ts](../utils/classnames.spec.ts) - Utility function tests
|
- [classnames.spec.ts] - Utility function tests
|
||||||
- [index.spec.tsx](../app/components/base/button/index.spec.tsx) - Component tests
|
- [index.spec.tsx] - Component tests
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
- [Vitest Documentation](https://vitest.dev/guide/)
|
- [Vitest Documentation]
|
||||||
- [React Testing Library Documentation](https://testing-library.com/docs/react-testing-library/intro/)
|
- [React Testing Library Documentation]
|
||||||
- [Testing Library Best Practices](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library)
|
- [Testing Library Best Practices]
|
||||||
- [Vitest Mocking Guide](https://vitest.dev/guide/mocking.html)
|
- [Vitest Mocking Guide]
|
||||||
|
|
||||||
______________________________________________________________________
|
---
|
||||||
|
|
||||||
**Remember**: Writing tests is not just about coverage, but ensuring code quality and maintainability. Good tests should be clear, concise, and meaningful.
|
**Remember**: Writing tests is not just about coverage, but ensuring code quality and maintainability. Good tests should be clear, concise, and meaningful.
|
||||||
|
|
||||||
|
[Example Structure]: #example-structure
|
||||||
|
[React Testing Library Documentation]: https://testing-library.com/docs/react-testing-library/intro
|
||||||
|
[Test Data Builders]: #9-test-data-builders-anti-hardcoding
|
||||||
|
[Testing Library Best Practices]: https://kentcdodds.com/blog/common-mistakes-with-react-testing-library
|
||||||
|
[Vitest Documentation]: https://vitest.dev/guide
|
||||||
|
[Vitest Mocking Guide]: https://vitest.dev/guide/mocking.html
|
||||||
|
[classnames.spec.ts]: ../utils/classnames.spec.ts
|
||||||
|
[index.spec.tsx]: ../app/components/base/button/index.spec.tsx
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
import antfu, { GLOB_TESTS, GLOB_TS, GLOB_TSX, isInEditorEnv, isInGitHooksOrLintStaged } from '@antfu/eslint-config'
|
|
||||||
|
import antfu, { GLOB_MARKDOWN, GLOB_TESTS, GLOB_TS, GLOB_TSX, isInEditorEnv, isInGitHooksOrLintStaged } from '@antfu/eslint-config'
|
||||||
import pluginQuery from '@tanstack/eslint-plugin-query'
|
import pluginQuery from '@tanstack/eslint-plugin-query'
|
||||||
|
import md from 'eslint-markdown'
|
||||||
import tailwindcss from 'eslint-plugin-better-tailwindcss'
|
import tailwindcss from 'eslint-plugin-better-tailwindcss'
|
||||||
import hyoban from 'eslint-plugin-hyoban'
|
import hyoban from 'eslint-plugin-hyoban'
|
||||||
|
import markdownPreferences from 'eslint-plugin-markdown-preferences'
|
||||||
import sonar from 'eslint-plugin-sonarjs'
|
import sonar from 'eslint-plugin-sonarjs'
|
||||||
import storybook from 'eslint-plugin-storybook'
|
import storybook from 'eslint-plugin-storybook'
|
||||||
import {
|
import {
|
||||||
@ -56,6 +59,26 @@ export default antfu(
|
|||||||
},
|
},
|
||||||
e18e: false,
|
e18e: false,
|
||||||
},
|
},
|
||||||
|
markdownPreferences.configs.standard,
|
||||||
|
{
|
||||||
|
files: [GLOB_MARKDOWN],
|
||||||
|
plugins: { md },
|
||||||
|
rules: {
|
||||||
|
'md/no-url-trailing-slash': 'error',
|
||||||
|
'markdown-preferences/prefer-link-reference-definitions': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
minLinks: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'markdown-preferences/ordered-list-marker-sequence': [
|
||||||
|
'error',
|
||||||
|
{ increment: 'never' },
|
||||||
|
],
|
||||||
|
'markdown-preferences/definitions-last': 'error',
|
||||||
|
'markdown-preferences/sort-definitions': 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
'node/prefer-global/process': 'off',
|
'node/prefer-global/process': 'off',
|
||||||
|
|||||||
@ -41,7 +41,7 @@ cd web/i18n
|
|||||||
cp -r en-US id-ID
|
cp -r en-US id-ID
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Modify the translation `.json` files in the new folder. Keep keys flat (for example, `dialog.title`).
|
1. Modify the translation `.json` files in the new folder. Keep keys flat (for example, `dialog.title`).
|
||||||
|
|
||||||
1. Add the new language to the `languages.ts` file.
|
1. Add the new language to the `languages.ts` file.
|
||||||
|
|
||||||
@ -143,10 +143,10 @@ export const languages = [
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Don't forget to mark the supported field as `true` if the language is supported.
|
1. Don't forget to mark the supported field as `true` if the language is supported.
|
||||||
|
|
||||||
1. Sometimes you might need to do some changes in the server side. Please change this file as well. 👇
|
1. Sometimes you might need to do some changes in the server side. Please change this file as well. 👇
|
||||||
https://github.com/langgenius/dify/blob/61e4bbabaf2758354db4073cbea09fdd21a5bec1/api/constants/languages.py#L5
|
<https://github.com/langgenius/dify/blob/61e4bbabaf2758354db4073cbea09fdd21a5bec1/api/constants/languages.py#L5>
|
||||||
|
|
||||||
> Note: `I18nText` type is automatically derived from `LanguagesSupported`, so you don't need to manually add types.
|
> Note: `I18nText` type is automatically derived from `LanguagesSupported`, so you don't need to manually add types.
|
||||||
|
|
||||||
|
|||||||
@ -216,8 +216,10 @@
|
|||||||
"autoprefixer": "10.4.27",
|
"autoprefixer": "10.4.27",
|
||||||
"code-inspector-plugin": "1.4.4",
|
"code-inspector-plugin": "1.4.4",
|
||||||
"eslint": "10.0.3",
|
"eslint": "10.0.3",
|
||||||
|
"eslint-markdown": "0.6.0",
|
||||||
"eslint-plugin-better-tailwindcss": "4.3.2",
|
"eslint-plugin-better-tailwindcss": "4.3.2",
|
||||||
"eslint-plugin-hyoban": "0.14.1",
|
"eslint-plugin-hyoban": "0.14.1",
|
||||||
|
"eslint-plugin-markdown-preferences": "0.40.3",
|
||||||
"eslint-plugin-react-hooks": "7.0.1",
|
"eslint-plugin-react-hooks": "7.0.1",
|
||||||
"eslint-plugin-react-refresh": "0.5.2",
|
"eslint-plugin-react-refresh": "0.5.2",
|
||||||
"eslint-plugin-sonarjs": "4.0.2",
|
"eslint-plugin-sonarjs": "4.0.2",
|
||||||
|
|||||||
52
web/pnpm-lock.yaml
generated
52
web/pnpm-lock.yaml
generated
@ -530,12 +530,18 @@ importers:
|
|||||||
eslint:
|
eslint:
|
||||||
specifier: 10.0.3
|
specifier: 10.0.3
|
||||||
version: 10.0.3(jiti@1.21.7)
|
version: 10.0.3(jiti@1.21.7)
|
||||||
|
eslint-markdown:
|
||||||
|
specifier: 0.6.0
|
||||||
|
version: 0.6.0(eslint@10.0.3(jiti@1.21.7))
|
||||||
eslint-plugin-better-tailwindcss:
|
eslint-plugin-better-tailwindcss:
|
||||||
specifier: 4.3.2
|
specifier: 4.3.2
|
||||||
version: 4.3.2(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.17.0))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3)
|
version: 4.3.2(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.17.0))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3)
|
||||||
eslint-plugin-hyoban:
|
eslint-plugin-hyoban:
|
||||||
specifier: 0.14.1
|
specifier: 0.14.1
|
||||||
version: 0.14.1(eslint@10.0.3(jiti@1.21.7))
|
version: 0.14.1(eslint@10.0.3(jiti@1.21.7))
|
||||||
|
eslint-plugin-markdown-preferences:
|
||||||
|
specifier: 0.40.3
|
||||||
|
version: 0.40.3(@eslint/markdown@7.5.1)(eslint@10.0.3(jiti@1.21.7))
|
||||||
eslint-plugin-react-hooks:
|
eslint-plugin-react-hooks:
|
||||||
specifier: 7.0.1
|
specifier: 7.0.1
|
||||||
version: 7.0.1(eslint@10.0.3(jiti@1.21.7))
|
version: 7.0.1(eslint@10.0.3(jiti@1.21.7))
|
||||||
@ -4763,6 +4769,15 @@ packages:
|
|||||||
'@eslint/json':
|
'@eslint/json':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
eslint-markdown@0.6.0:
|
||||||
|
resolution: {integrity: sha512-NrgfiNto5IJrW1F/Akf2hJYoJTCbXoClOUvtUMDgoqmQNH0VRihNvFh+MFay4E0HV2eozfgxsLSGxnndtRJA8w==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^9.31.0 || ^10.0.0-rc.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
eslint:
|
||||||
|
optional: true
|
||||||
|
|
||||||
eslint-merge-processors@2.0.0:
|
eslint-merge-processors@2.0.0:
|
||||||
resolution: {integrity: sha512-sUuhSf3IrJdGooquEUB5TNpGNpBoQccbnaLHsb1XkBLUPPqCNivCpY05ZcpCOiV9uHwO2yxXEWVczVclzMxYlA==}
|
resolution: {integrity: sha512-sUuhSf3IrJdGooquEUB5TNpGNpBoQccbnaLHsb1XkBLUPPqCNivCpY05ZcpCOiV9uHwO2yxXEWVczVclzMxYlA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -4828,6 +4843,13 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '>=9.38.0'
|
eslint: '>=9.38.0'
|
||||||
|
|
||||||
|
eslint-plugin-markdown-preferences@0.40.3:
|
||||||
|
resolution: {integrity: sha512-R3CCAEFwnnYXukTdtvdsamGjbTgVs9UZKqMKhNeWNXzFtOP1Frc89bgbd56lJUN7ASaxgvzc5fUpKvDCOTtDpg==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
'@eslint/markdown': ^7.4.0
|
||||||
|
eslint: '>=9.0.0'
|
||||||
|
|
||||||
eslint-plugin-n@17.24.0:
|
eslint-plugin-n@17.24.0:
|
||||||
resolution: {integrity: sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==}
|
resolution: {integrity: sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@ -12142,6 +12164,16 @@ snapshots:
|
|||||||
esquery: 1.7.0
|
esquery: 1.7.0
|
||||||
jsonc-eslint-parser: 3.1.0
|
jsonc-eslint-parser: 3.1.0
|
||||||
|
|
||||||
|
eslint-markdown@0.6.0(eslint@10.0.3(jiti@1.21.7)):
|
||||||
|
dependencies:
|
||||||
|
'@eslint/markdown': 7.5.1
|
||||||
|
micromark-util-normalize-identifier: 2.0.1
|
||||||
|
parse5: 8.0.0
|
||||||
|
optionalDependencies:
|
||||||
|
eslint: 10.0.3(jiti@1.21.7)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
eslint-merge-processors@2.0.0(eslint@10.0.3(jiti@1.21.7)):
|
eslint-merge-processors@2.0.0(eslint@10.0.3(jiti@1.21.7)):
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 10.0.3(jiti@1.21.7)
|
eslint: 10.0.3(jiti@1.21.7)
|
||||||
@ -12233,6 +12265,26 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@eslint/json'
|
- '@eslint/json'
|
||||||
|
|
||||||
|
eslint-plugin-markdown-preferences@0.40.3(@eslint/markdown@7.5.1)(eslint@10.0.3(jiti@1.21.7)):
|
||||||
|
dependencies:
|
||||||
|
'@eslint/markdown': 7.5.1
|
||||||
|
diff-sequences: 29.6.3
|
||||||
|
emoji-regex-xs: 2.0.1
|
||||||
|
eslint: 10.0.3(jiti@1.21.7)
|
||||||
|
mdast-util-from-markdown: 2.0.3
|
||||||
|
mdast-util-frontmatter: 2.0.1
|
||||||
|
mdast-util-gfm: 3.1.0
|
||||||
|
mdast-util-math: 3.0.0
|
||||||
|
micromark-extension-frontmatter: 2.0.0
|
||||||
|
micromark-extension-gfm: 3.0.0
|
||||||
|
micromark-extension-math: 3.1.0
|
||||||
|
micromark-factory-space: 2.0.1
|
||||||
|
micromark-util-character: 2.1.1
|
||||||
|
micromark-util-symbol: 2.0.1
|
||||||
|
string-width: 8.2.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
eslint-plugin-n@17.24.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3):
|
eslint-plugin-n@17.24.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@1.21.7))
|
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@1.21.7))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user