From e7db97ff0f197213443dc3a6c2f54a06a794fd93 Mon Sep 17 00:00:00 2001 From: noise Date: Wed, 12 Nov 2025 20:33:41 +0800 Subject: [PATCH 01/50] !docs: recovery /guide/migration --- guide/migration.md | 711 ++++++++++++++++++++++++++------------------- 1 file changed, 416 insertions(+), 295 deletions(-) diff --git a/guide/migration.md b/guide/migration.md index 2ce929a1..feafbcc6 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -5,409 +5,522 @@ outline: deep # 迁移指南 {#migration-guide} -## 迁移到 Vitest 4.0 {#vitest-4} +## 迁移到 Vitest 3.0 {#vitest-3} -### V8 Code Coverage Major Changes {#v8-code-coverage-major-changes} +### 测试选项作为第三个参数 {#test-options-as-a-third-argument} -Vitest 的 V8 覆盖率提供器现在使用了更精准的结果映射逻辑,从 Vitest v3 升级后,你可能会看到覆盖率报告的内容有变化。 +Vitest 3.0 如果我们将一个对象作为第三个参数传递给 `test` 或 `describe` 函数,会打印一条警告: -之前 Vitest 使用 [`v8-to-istanbul`](https://github.com/istanbuljs/v8-to-istanbul) 将 V8 覆盖率结果映射到源码文件,但这种方式不够准确,报告中常常会出现误报。现在我们开发了基于 AST 分析的新方法,使 V8 报告的准确度与 `@vitest/coverage-istanbul` 一致。 +```ts +test('validation works', () => { + // ... +}, { retry: 3 }) // [!code --] -- 覆盖率忽略提示已更新,详见 [覆盖率 | 忽略代码](/guide/coverage.html#ignoring-code)。 -- 已移除 `coverage.ignoreEmptyLines` 选项。没有可执行代码的行将不再出现在报告中。 -- 已移除 `coverage.experimentalAstAwareRemapping` 选项。此功能现已默认启用,并成为唯一的映射方式。 -- 现在 V8 提供器也支持 `coverage.ignoreClassMethods`。 +test('validation works', { retry: 3 }, () => { // [!code ++] + // ... +}) +``` -### 移除 `coverage.all` 和 `coverage.extensions` 选项 {#removed-options-coverage-all-and-coverage-extensions} +下一个主要版本将在第三个参数是对象时抛出错误。请注意,超时时间(timeout number)并没有被弃用: -在之前的版本中,Vitest 会默认把所有未覆盖的文件包含到报告中。这是因为 `coverage.all` 默认为 `true`,`coverage.include` 默认为 `**`。这样设计是因为测试工具无法准确判断用户源码所在位置。 +```ts +test('validation works', () => { + // ... +}, 1000) // Ok ✅ +``` -然而,这导致 Vitest 覆盖率工具会处理很多意料之外的文件(例如压缩 JS 文件),造成报告生成速度很慢甚至卡死。在 Vitest v4 中,我们彻底移除了 `coverage.all`,并将默认行为改为**只在报告中包含被测试覆盖的文件**。 +### `browser.name` 和 `browser.providerOptions` 已弃用 {#browser-name-and-browser-provideroptions-are-deprecated} -在升级到 v4 后,推荐在配置中显式指定 `coverage.include`,并视需要配合使用 `coverage.exclude` 进行排除。 +[`browser.name`](/guide/browser/config#browser-name) 和 [`browser.providerOptions`](/guide/browser/config#browser-provideroptions) 都将在 Vitest 4 中删除。请使用新的 [`browser.instances`](/guide/browser/config#browser-instances) 选项来代替它们: -```ts [vitest.config.ts] +```ts export default defineConfig({ test: { - coverage: { - // 包含匹配此模式的被覆盖和未覆盖文件: - include: ['packages/**/src/**.{js,jsx,ts,tsx}'], // [!code ++] + browser: { + name: 'chromium', // [!code --] + providerOptions: { // [!code --] + launch: { devtools: true }, // [!code --] + }, // [!code --] + instances: [ // [!code ++] + { // [!code ++] + browser: 'chromium', // [!code ++] + launch: { devtools: true }, // [!code ++] + }, // [!code ++] + ], // [!code ++] + }, + }, +}) +``` - // 对上述 include 匹配到的文件应用排除规则: - exclude: ['**/some-pattern/**'], // [!code ++] +使用新的 `browser.instances` 字段,我们还可以指定多个浏览器配置。 - // 以下选项已移除 - all: true, // [!code --] - extensions: ['js', 'ts'], // [!code --] - } - } -}) +### 现在 `spy.mockReset` 恢复原始实现 {#spy-mockreset-now-restores-the-original-implementation} + +之前没有好的方法在不重新应用 spy 的情况下将其重置为原始实现。现在,`spy.mockReset` 会将实现函数重置为原始函数,而不是假的 noop(空操作)。 + +```ts +const foo = { + bar: () => 'Hello, world!' +} + +vi.spyOn(foo, 'bar').mockImplementation(() => 'Hello, mock!') + +foo.bar() // 'Hello, mock!' + +foo.bar.mockReset() + +foo.bar() // undefined [!code --] +foo.bar() // 'Hello, world!' [!code ++] ``` -如果未定义 `coverage.include`,报告将只包含测试运行中被加载的文件: +### `vi.spyOn` 如果方法已被模拟,则复用模拟对象 {#vi-spyon-reuses-mock-if-method-is-already-mocked} -```ts [vitest.config.ts] -export default defineConfig({ - test: { - coverage: { - // 未设置 include,只包含运行时加载的文件 - include: undefined, // [!code ++] +之前,Vitest 在监视对象时总会分配一个新的 spy。这会导致 `mockRestore` 出现错误,因为它会将 spy 恢复到之前的 spy,而不是原始函数: - // 匹配此模式的已加载文件将被排除: - exclude: ['**/some-pattern/**'], // [!code ++] - } - } -}) +```ts +vi.spyOn(fooService, 'foo').mockImplementation(() => 'bar') +vi.spyOn(fooService, 'foo').mockImplementation(() => 'bar') +vi.restoreAllMocks() +vi.isMockFunction(fooService.foo) // true [!code --] +vi.isMockFunction(fooService.foo) // false [!code ++] ``` -更多示例请参考: -- [覆盖率报告中的文件包含与排除](/guide/coverage.html#including-and-excluding-files-from-coverage-report) -- [性能分析 | 代码覆盖率](/guide/profiling-test-performance.html#code-coverage) 了解调试覆盖率生成的方法 +### Fake Timers 默认值 {#fake-timers-defaults} -### `spyOn` and `fn` 支持构造函数 {#spyon-and-fn-support-constructors} +Vitest 不再提供默认的 `fakeTimers.toFake` 选项。现在,如果存在任何与定时器相关的 API(除 `nextTick` 外),Vitest 都会对其进行模拟。具体来说,当调用 `vi.useFakeTimers` 时,`performance.now()` 也会被模拟。 -在之前版本中,如果你对构造函数使用 `vi.spyOn`,会收到类似 `Constructor requires 'new'` 的错误。从 Vitest 4 开始,所有用 `new` 调用的 mock 都会正确创建实例,而不是调用 `mock.apply`。这意味着 mock 实现必须使用 `function` 或 `class` 关键字,例如: +```ts +vi.useFakeTimers() -```ts {12-14,16-20} -const cart = { - Apples: class Apples { - getApples() { - return 42 - } - } -} +performance.now() // 原始的 [!code --] +performance.now() // 假的 [!code ++] +``` -const Spy = vi.spyOn(cart, 'Apples') - .mockImplementation(() => ({ getApples: () => 0 })) // [!code --] - // 使用 function 关键字 - .mockImplementation(function () { - this.getApples = () => 0 - }) - // 使用自定义 class - .mockImplementation(class MockApples { - getApples() { - return 0 - } - }) +你可以通过在调用 `vi.useFakeTimers` 时或在全局配置中指定定时器来恢复到之前的行为: -const mock = new Spy() +```ts +export default defineConfig({ + test: { + fakeTimers: { + toFake: [ // [!code ++] + 'setTimeout', // [!code ++] + 'clearTimeout', // [!code ++] + 'setInterval', // [!code ++] + 'clearInterval', // [!code ++] + 'setImmediate', // [!code ++] + 'clearImmediate', // [!code ++] + 'Date', // [!code ++] + ] // [!code ++] + }, + }, +}) ``` -请注意,如果此时使用箭头函数,调用 mock 时会报 [` is not a constructor` 错误](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Not_a_constructor)。 +### 更严格的错误相等性检查 {#more-strict-error-equality} -### Mock 的变更 {#changes-to-mocking} +现在,Vitest 在通过 `toEqual` 或 `toThrowError` 比较错误时会检查更多的属性。Vitest 会比较 `name`、`message`、`cause` 和 `AggregateError.errors`。对于 `Error.cause`,比较是不对称进行的: -Vitest 4 除新增构造函数支持外,还重构了 mock 的创建机制,一举修复多年累积的模块模拟顽疾;尤其在类与 spy 交互时,行为更易预测、不再烧脑。 +```ts +expect(new Error('hi', { cause: 'x' })).toEqual(new Error('hi')) // ✅ +expect(new Error('hi')).toEqual(new Error('hi', { cause: 'x' })) // ❌ +``` -- `vi.fn().getMockName()` 现默认返回 `vi.fn()`,而不再附带 `spy`。这一改动会使快照中的 mock 名称从 `[MockFunction spy]` 简化为 `[MockFunction]`;而 `vi.spyOn` 创建的 spy 仍沿用原始名称,便于调试。 -- `vi.restoreAllMocks` 现已缩小作用范围:仅还原由 `vi.spyOn` 手动创建的 spy ,不再触及自动 mock ,亦不会重置其内部状态(对应配置项 [`restoreMocks`](/config/#restoremocks) 同步更新)。`.mockRestore` 仍按原行为重置实现并清空状态。 -- 现对 mock 调用 `vi.spyOn` 时,返回的仍是原 mock,而非新建 spy。 -- 自动 mock 的实例方法已正确隔离,但仍与原型共享底层状态;除非方法已自定义 mock 实现,否则修改原型实现会同步影响所有实例。此外,调用 `.mockReset` 不再破坏此继承关系。 +除了检查更多的属性外,Vitest 现在还会比较错误原型。例如,如果抛出的是 `TypeError`,相等性检查应该引用 `TypeError`,而不是 `Error`: ```ts -import { AutoMockedClass } from './example.js' -const instance1 = new AutoMockedClass() -const instance2 = new AutoMockedClass() +expect(() => { + throw new TypeError('type error') +}) + .toThrowError(new Error('type error')) // [!code --] + .toThrowError(new TypeError('type error')) // [!code ++] +``` -instance1.method.mockReturnValue(42) +更多详情请参见 PR:[#5876](https://github.com/vitest-dev/vitest/pull/5876)。 -expect(instance1.method()).toBe(42) -expect(instance2.method()).toBe(undefined) +### 在 Vite 6 中,默认不会解析 `module` 的条件导出 {#module-condition-export-is-not-resolved-by-default-on-vite-6} -expect(AutoMockedClass.prototype.method).toHaveBeenCalledTimes(2) +Vite 6 allows more flexible [`resolve.conditions`](https://vite.dev/config/shared-options#resolve-conditions) options and Vitest configures it to exclude `module` conditional export by default. +See also [Vite 6 migration guide](https://v6.vite.dev/guide/migration.html#default-value-for-resolve-conditions) for the detail of Vite side changes. -instance1.method.mockReset() -AutoMockedClass.prototype.method.mockReturnValue(100) +### `Custom` 类型已被弃用 API {#custom-type-is-deprecated} -expect(instance1.method()).toBe(100) -expect(instance2.method()).toBe(100) +`Custom` 类型现在等同于 `Test` 类型。需要注意一下,Vitest 在 2.1 版本中更新了公共类型,并将导出的名称更改为 `RunnerCustomCase` 和 `RunnerTestCase`。 -expect(AutoMockedClass.prototype.method).toHaveBeenCalledTimes(4) +```ts +import { + RunnerCustomCase, // [!code --] + RunnerTestCase, // [!code ++] +} from 'vitest' ``` -- 自动 mock 方法一经生成即不可还原,手动 `.mockRestore` 无效;`spy: true` 的自动 mock 模块行为保持不变。 -- 自动 mock 的 getter 不再执行原始逻辑,默认返回 `undefined`;如需继续监听并改写,请使用 `vi.spyOn(object, name, 'get')`。 -- 执行 `vi.fn(implementation).mockReset()` 后,`.getMockImplementation()` 现可正确返回原 mock 实现。 -- `vi.fn().mock.invocationCallOrder` 现以 `1` 起始,与 Jest 保持一致。 -### 带文件名过滤器的独立模式 {#standalone-mode-with-filename-filter} +如果我们正在使用 `getCurrentSuite().custom()`,返回的任务的 `type` 现在等于 `'test'`。`Custom` 类型将在 Vitest 4 中被移除。 -为了提升用户体验,当 [`--standalone`](/guide/cli#standalone) 与文件名过滤器一起使用时,Vitest 现在会直接开始运行匹配到的文件。 +### `WorkspaceSpec` 类型已被弃用 API {#the-workspacespec-type-is-no-longer-used} -```sh -# In Vitest v3 and below this command would ignore "math.test.ts" filename filter. -# In Vitest v4 the math.test.ts will run automatically. -$ vitest --standalone math.test.ts -``` +在公共 API 中,此类型之前用于自定义 [sequencers](/config/#sequence-sequencer)。请迁移到 [`TestSpecification`](/advanced/api/test-specification)。 -这允许用户为独立模式创建可复用的 `package.json`。 +### 现在 `onTestFinished` 和 `onTestFailed` 接收上下文参数 {#ontestfinished-and-ontestfailed-now-receive-a-context} -::: code-group -```json [package.json] -{ - "scripts": { - "test:dev": "vitest --standalone" - } -} -``` -```bash [CLI] -# Start Vitest in standalone mode, without running any files on start -$ pnpm run test:dev +[`onTestFinished`](/api/#ontestfinished) 和 [`onTestFailed`](/api/#ontestfailed) 钩子之前接收测试结果作为第一个参数。现在,它们像 `beforeEach` 和 `afterEach` 一样,接收一个测试上下文。 -# Run math.test.ts immediately -$ pnpm run test:dev math.test.ts -``` -::: +### Snapshot API 变更API {#changes-to-the-snapshot-api} -### Replacing `vite-node` with [Module Runner](https://vite.dev/guide/api-environment-runtimes.html#modulerunner) {#replacing-vite-node-with-module-runner} +`@vitest/snapshot` 中的公共 Snapshot API 已更改,以支持在单次运行中处理多个状态。更多详情请参见 PR:[#6817](https://github.com/vitest-dev/vitest/pull/6817)。 -Module Runner 已取代 `vite-node`,直接内嵌于 Vite, Vitest 亦移除 SSR 封装,直接调用。主要变更如下: +请注意,这些更改仅影响直接使用 Snapshot API 的开发者。`.toMatchSnapshot` API 没有任何变化。 -- 环境变量:`VITE_NODE_DEPS_MODULE_DIRECTORIES` → `VITEST_MODULE_DIRECTORIES` -- 注入字段:`__vitest_executor` → `moduleRunner`([`ModuleRunner`](https://vite.dev/guide/api-environment-runtimes.html#modulerunner) 实例) -- 移除内部入口 `vitest/execute` -- 自定义环境用 `viteEnvironment` 取代 `transformMode`;未指定时,Vitest 以环境名匹配 [`server.environments`](https://vite.dev/guide/api-environment-instances.html) -- 依赖列表剔除 `vite-node` -- `deps.optimizer.web` 重命名为 [`deps.optimizer.client`](/config/#deps-optimizer-client),并支持自定义环境名 +### 修改 `resolveConfig` 类型签名 API {#changes-to-resolveconfig-type-signature} -Vite 已提供外部化机制,但为降低破坏性,仍保留旧方案;[`server.deps`](/config/#server-deps) 可继续用于包的内联/外部化。 +[`resolveConfig`](/advanced/api/#resolveconfig) 现在更加有用。它不再接受已经解析的 Vite 配置,而是接受用户配置并返回解析后的配置。 -未使用上述高级功能者,升级无感知。 +此函数不用于内部,仅作为公共 API 暴露。 -### `workspace` is Replaced with `projects` {#workspace-is-replaced-with-projects} +### 已清理 `vitest/reporters` 类型定义 API {#cleaned-up-vitest-reporters-types} -The `workspace` configuration option was renamed to [`projects`](/guide/projects) in Vitest 3.2. They are functionally the same, except you cannot specify another file as the source of your workspace (previously you could specify a file that would export an array of projects). Migrating to `projects` is easy, just move the code from `vitest.workspace.js` to `vitest.config.ts`: +`vitest/reporters` 入口现在仅导出报告器实现和选项类型。如果您需要访问 `TestCase`、`TestSuite` 以及其他与任务相关的类型,请另外从 `vitest/node` 中导入它们。 -::: code-group -```ts [vitest.config.js] -import { defineConfig } from 'vitest/config' +### 即使覆盖了 `coverage.excludes` 配置,代码覆盖率仍会忽略测试文件。 {#coverage-ignores-test-files-even-when-coverage-excludes-is-overwritten} +不再可以通过覆盖 `coverage.excludes` 来将测试文件包含在覆盖率报告中。测试文件现在总是被排除。 + +## 迁移到 Vitest 2.0 {#vitest-2} + +### 默认数据池为 `forks` {#default-pool-is-forks} + +为了提高稳定性,Vitest 2.0 将 `pool` 的默认配置改为 `'fork'`。您可以在 [PR](https://github.com/vitest-dev/vitest/pull/5047)中阅读完整的动机。 + +如果使用了 `poolOptions` 而未指定一个 `pool`,则可能需要更新配置: + +```ts export default defineConfig({ test: { - workspace: './vitest.workspace.js', // [!code --] - projects: [ // [!code ++] - './packages/*', // [!code ++] - { // [!code ++] - test: { // [!code ++] - name: 'unit', // [!code ++] - }, // [!code ++] + poolOptions: { + threads: { // [!code --] + singleThread: true, // [!code --] + }, // [!code --] + forks: { // [!code ++] + singleFork: true, // [!code ++] }, // [!code ++] - ] // [!code ++] + } } }) ``` -```ts [vitest.workspace.js] -import { defineWorkspace } from 'vitest/config' // [!code --] -export default defineWorkspace([ // [!code --] - './packages/*', // [!code --] - { // [!code --] - test: { // [!code --] - name: 'unit', // [!code --] - }, // [!code --] - } // [!code --] -]) // [!code --] -``` -::: +### 钩子函数在堆栈中运行 {#hooks-are-running-in-a-stack} -### Browser Provider Rework {#browser-provider-rework} +在 Vitest 2.0 之前,所有钩子函数都是并行运行的。 在 2.0 中,所有钩子都是串行运行的。 除此之外,`afterAll`/`afterEach` 以相反的顺序运行。 -In Vitest 4.0, the browser provider now accepts an object instead of a string (`'playwright'`, `'webdriverio'`). The `preview` is no longer a default. This makes it simpler to work with custom options and doesn't require adding `/// { - await page.getByRole('button').click() +### 移除 `watchExclude` 选项 {#removal-of-the-watchexclude-option} + +Vitest 使用 Vite 的监视器。您可以将排除项添加到 `server.watch.ignored`: + +```ts +export default defineConfig({ + server: { // [!code ++] + watch: { // [!code ++] + ignored: ['!node_modules/examplejs'] // [!code ++] + } // [!code ++] + } // [!code ++] }) ``` -The modules are identical, so doing a simple "Find and Replace" should be sufficient. +### 移除 `--segfault-retry` {#segfault-retry-removed} -If you were using the `@vitest/browser/utils` module, you can now import those utilities from `vitest/browser` as well: +默认程序池更改后,不再需要此选项。如果遇到分离故障错误,请尝试切换到`'forks'`池。如果问题仍然存在,请重现问题并打开一个新问题。 + +### 删除套件任务中的空任务 {#empty-task-in-suite-tasks-removed} + +这是对高级[task API](/advanced/runner#your-task-function)的更改。以前,遍历 `.suite`最终会导致使用空的内部套件,而不是文件任务。 + +这使得 `.suite`成为可选项;如果任务是在顶层定义的,则不会有 suite。您可以回退到 `.file`属性,该属性现在存在于所有任务中(包括文件任务本身,因此要小心不要陷入无休止的递归)。 + +这一更改还删除了 `expect.getState().currentTestName` 中的文件,并使 `expect.getState().testPath` 成为必填项。 + +### `task.meta` 已添加到 JSON 报告器中 {#task-meta-is-added-to-the-json-reporter} + +JSON 报告器现在会为每个断言结果打印 `task.meta` 。 + +### 简化的模拟函数通用类型 (e.g. `vi.fn`, `Mock`) {#simplified-generic-types-of-mock-functions-e-g-vi-fn-t-mock-t} + +以前 `vi.fn` 分别接受参数和返回值的两个泛型。现在改为直接接受一个函数类型 `vi.fn` 以简化用法。 ```ts -import { getElementError } from '@vitest/browser/utils' // [!code --] -import { utils } from 'vitest/browser' // [!code ++] -const { getElementError } = utils // [!code ++] +import type { Mock } from 'vitest' +import { vi } from 'vitest' + +const add = (x: number, y: number): number => x + y + +// using vi.fn +const mockAdd = vi.fn, ReturnType>() // [!code --] +const mockAdd = vi.fn() // [!code ++] + +// using Mock +const mockAdd: Mock, ReturnType> = vi.fn() // [!code --] +const mockAdd: Mock = vi.fn() // [!code ++] ``` -::: warning -Both `@vitest/browser/context` and `@vitest/browser/utils` work at runtime during the transition period, but they will be removed in a future release. -::: +### 访问已解析 `mock.results` {#accessing-resolved-mock-results} + +之前,Vitest 会在函数返回 Promise 时解析 `mock.results` 的值。现在,增加了一个独立的 [`mock.settledResults`](/api/mock#mock-settledresults) 属性,仅在返回的 Promise 被解析或拒绝时填充。 -### Reporter Updates {#reporter-updates} +```ts +const fn = vi.fn().mockResolvedValueOnce('result') +await fn() + +const result = fn.mock.results[0] // 'result' [!code --] +const result = fn.mock.results[0] // 'Promise' [!code ++] -Reporter APIs `onCollected`, `onSpecsCollected`, `onPathsCollected`, `onTaskUpdate` and `onFinished` were removed. See [`Reporters API`](/advanced/api/reporters) for new alternatives. The new APIs were introduced in Vitest `v3.0.0`. +const settledResult = fn.mock.settledResults[0] // 'result' +``` -The `basic` reporter was removed as it is equal to: +通过这一更改,我们还引入了新的 [`toHaveResolved*`](/api/expect#tohaveresolved) 匹配器,类似于 `toHaveReturned`,以便如果您之前使用过 `toHaveReturned`,迁移会更加容易: ```ts -export default defineConfig({ - test: { - reporters: [ - ['default', { summary: false }] - ] +const fn = vi.fn().mockResolvedValueOnce('result') +await fn() + +expect(fn).toHaveReturned('result') // [!code --] +expect(fn).toHaveResolved('result') // [!code ++] +``` + +### 浏览器模式 {#browser-mode} + +Vitest 浏览器模式在测试周期内发生了很多变化。您可以在[GitHub discussion](https://github.com/vitest-dev/vitest/discussions/5828)上阅读我们关于浏览器模式的理念。 + +大多数改动都是附加的,但也有一些小的突破性改动: + +- `none` provider 更名为 `preview` [#5842](https://github.com/vitest-dev/vitest/pull/5826) +- `preview` provider 现在是默认的 [#5842](https://github.com/vitest-dev/vitest/pull/5826) +- `indexScripts` 更名为 `orchestratorScripts` [#5842](https://github.com/vitest-dev/vitest/pull/5842) + +### 删除已弃用的选项 {#deprecated-options-removed} + +一些已弃用的选项已被删除: + +- `vitest typecheck` 命令 - 使用 `vitest --typecheck` 代替 +- `VITEST_JUNIT_CLASSNAME` 和 `VITEST_JUNIT_SUITE_NAME` 环境变量(改用 reporter 选项) +- 检查 `c8` 覆盖率(使用 coverage-v8 代替) +- 从 `vitest` 导出 `SnapshotEnvironment` - 改为从 `vitest/snapshot` 导入 +- 删除 `SpyInstance` 改用 `MockInstance` + +## 迁移到 Vitest 1.0 {#migrating-to-vitest-1-0} + +### 最低要求 {#minimum-requirements} + +Vitest 1.0 需要 Vite 5.0 和 Node.js 18 或更高版本。 + +所有 `@vitest/*` 子软件包都需要 Vitest 1.0 版本。 + +### Snapshots 更新 [#3961](https://github.com/vitest-dev/vitest/pull/3961) {#snapshots-update-3961} + +快照中的引号不再转义,即使字符串只有一行,所有快照也都使用回车引号 (`)。 + +1. 引号不再转义: + +```diff +expect({ foo: 'bar' }).toMatchInlineSnapshot(` + Object { +- \\"foo\\": \\"bar\\", ++ "foo": "bar", } -}) +`) ``` -The [`verbose`](/guide/reporters#verbose-reporter) reporter now prints test cases as a flat list. To revert to the previous behaviour, use `--reporter=tree`: +2. 单行快照现在使用"`"引号,而不是"'": -```ts +```diff +- expect('some string').toMatchInlineSnapshot('"some string"') ++ expect('some string').toMatchInlineSnapshot(`"some string"`) +``` + +对 `@vitest/snapshot` 也有[修改](https://github.com/vitest-dev/vitest/pull/4076)。如果不直接使用它,则无需做任何更改。 + +- 我们不再需要扩展 `SnapshotClient` 以覆盖 `equalityCheck` 方法:只需在启动实例时将其作为 `isEqual` 传递即可。 +- `client.setTest` 更名为 `client.startCurrentRun` +- `client.resetCurrent` 更名为 `client.finishCurrentRun` 。 + +### Pools 标准化 [#4172](https://github.com/vitest-dev/vitest/pull/4172) {#pools-are-standardized-4172} + +我们删除了大量配置选项,以便根据需要配置运行程序。如果你已经使用了 `--threads` 或其他相关标记,请查看迁移示例。 + +- `--threads` 现在是 `--pool=threads` +- `--no-threads` 现在是 `--pool=forks` +- `--single-thread` 现在是 `--poolOptions.threads.singleThread` +- `--experimental-vm-threads` 现在是 `--pool=vmThreads` +- `--experimental-vm-worker-memory-limit` 现在是 `--poolOptions.vmThreads.memoryLimit` +- `--isolate` 现在是 `--poolOptions..isolate` 和 `browser.isolate` +- `test.maxThreads` 现在是 `test.poolOptions..maxThreads` +- `test.minThreads` 现在是 `test.poolOptions..minThreads` +- `test.useAtomics` 现在是 `test.poolOptions..useAtomics` +- `test.poolMatchGlobs.child_process` 现在是 `test.poolMatchGlobs.forks` +- `test.poolMatchGlobs.experimentalVmThreads` 现在是 `test.poolMatchGlobs.vmThreads` + +```diff +{ + scripts: { +- "test": "vitest --no-threads" + // For identical behaviour: ++ "test": "vitest --pool forks --poolOptions.forks.singleFork" + // Or multi parallel forks: ++ "test": "vitest --pool forks" + + } +} +``` + +```diff +{ + scripts: { +- "test": "vitest --experimental-vm-threads" ++ "test": "vitest --pool vmThreads" + } +} +``` + +```diff +{ + scripts: { +- "test": "vitest --isolate false" ++ "test": "vitest --poolOptions.threads.isolate false" + } +} +``` + +```diff +{ + scripts: { +- "test": "vitest --no-threads --isolate false" ++ "test": "vitest --pool forks --poolOptions.forks.isolate false" + } +} +``` + +### Coverage 的变化 [#4265](https://github.com/vitest-dev/vitest/pull/4265), [#4442](https://github.com/vitest-dev/vitest/pull/4442) {#changes-to-coverage-4265-4442} + +选项 `coverage.all` 现在默认启用。这意味着,所有符合 `coverage.include` 模式的项目文件都将被处理,即使它们未被执行。 + +更改了覆盖阈值 API 的形状,现在它支持使用 glob 模式为特定文件指定阈值: + +```diff export default defineConfig({ test: { - reporters: ['verbose'], // [!code --] - reporters: ['tree'], // [!code ++] + coverage: { +- perFile: true, +- thresholdAutoUpdate: true, +- 100: true, +- lines: 100, +- functions: 100, +- branches: 100, +- statements: 100, ++ thresholds: { ++ perFile: true, ++ autoUpdate: true, ++ 100: true, ++ lines: 100, ++ functions: 100, ++ branches: 100, ++ statements: 100, ++ } + } } }) ``` -### Snapshots using custom elements print the shadow root {#snapshots-using-custom-elements-print-the-shadow-root} - -In Vitest 4.0 snapshots that include custom elements will print the shadow root contents. To restore the previous behavior, set the [`printShadowRoot` option](/config/#snapshotformat) to `false`. - -```js{15-22} -// before Vite 4.0 -exports[`custom element with shadow root 1`] = ` -" -
- -
-" -` - -// after Vite 4.0 -exports[`custom element with shadow root 1`] = ` -" -
- - #shadow-root - - hello - - -
-" -` -``` - -### Deprecated APIs are Removed {#deprecated-apis-are-removed} - -Vitest 4.0 移除了以下废弃的配置项: - -- `poolMatchGlobs` 配置项。请使用 [`projects`](/guide/projects) 代替。 -- `environmentMatchGlobs` 配置项。请使用 [`projects`](/guide/projects) 代替。 -- `deps.external`、`deps.inline`、`deps.fallbackCJS` 配置项。请改用 `server.deps.external`、`server.deps.inline` 或 `server.deps.fallbackCJS`。 -- `browser.testerScripts` 配置项。请使用 [`browser.testerHtmlPath`](/guide/browser/config#browser-testerhtmlpath) 代替。 -- `minWorkers` 配置项。只有 `maxWorkers` 会对测试运行方式产生影响,因此我们正在移除这个公共选项。 -- Vitest 不再支持将测试选项作为第三个参数提供给 `test` 和 `describe`。请改用第二个参数。 +### Mock 类型 [#4400](https://github.com/vitest-dev/vitest/pull/4400) {#mock-types-4400} -```ts -test('example', () => { /* ... */ }, { retry: 2 }) // [!code --] -test('example', { retry: 2 }, () => { /* ... */ }) // [!code ++] -``` +删除了一些类型,改用 Jest 风格的 "Mock "命名。 -同时,所有弃用类型被一次性清理,彻底解决误引 `@types/node` 的问题([#5481](https://github.com/vitest-dev/vitest/issues/5481)、[#6141](https://github.com/vitest-dev/vitest/issues/6141))。 +```diff +- import { EnhancedSpy, SpyInstance } from 'vitest' ++ import { MockInstance } from 'vitest' +``` -## 从 Jest 迁移 {#jest} +::: warning +`SpyInstance` 已被弃用,取而代之的是 `MockInstance` ,并会在下一个主要版本中移除。 +::: -Vitest 的 API 设计兼容 Jest,旨在使从 Jest 迁移尽可能简单。尽管如此,你仍可能遇到以下差异: +### 模拟计时器 [#3925](https://github.com/vitest-dev/vitest/pull/3925) {#timer-mocks-3925} -### 默认是否启用全局变量 {#globals-as-a-default} +`vi.useFakeTimers()` 不再自动模拟 [`process.nextTick`](https://nodejs.org/api/process.html#processnexttickcallback-args) 。 +仍然可以通过使用 `vi.useFakeTimers({ toFake: ['nextTick'] })` 明确指定来模拟 `process.nextTick`。 -Jest 默认启用其 [globals API](https://jestjs.io/docs/api)。Vitest 默认不启用。你可以通过配置项 [globals](/config/#globals) 启用全局变量,或者修改代码直接从 `vitest` 模块导入所需 API。 +但是,在使用 `--pool=forks` 时,无法模拟 `process.nextTick` 。如果需要模拟 `process.nextTick` ,请使用不同的 `--pool` 选项。 -如果选择不启用全局变量,注意常用库如 [`testing-library`](https://testing-library.com/) 将不会自动执行 DOM 的 [清理](https://testing-library.com/docs/svelte-testing-library/api/#cleanup)。 +## 从 Jest 迁移 {#jest} -### `mock.mockReset` +Vitest 设计了与 Jest 兼容的 API ,方便你从 Jest 的迁移尽可能简单。尽管做出了这些努力,你仍然可能会遇到以下差异: -Jest 的 [`mockReset`](https://jestjs.io/docs/mock-function-api#mockfnmockreset) 会将 mock 实现替换为空函数,返回 `undefined`。 +### 全局变量作为默认值 {#globals-as-a-default} -Vitest 的 [`mockReset`](/api/mock#mockreset) 会将 mock 实现重置为最初的实现。也就是说,使用 `vi.fn(impl)` 创建的 mock,`mockReset` 会将实现重置为 `impl`。 +Jest 默认启用[全局 API](https://jestjs.io/zh-Hans/docs/api)。然而 Vitest 没有。你既可以通过 [`globals` 配置选项](/config/#globals)启用全局 API,也可以通过更新你的代码以便使用来自 `vitest` 模块的导入。 -### `mock.mock` 是持久的 {#mock-mock-is-persistent} +如果你决定禁用全局 API,请注意像 [`testing-library`](https://testing-library.com/) 这样的通用库不会自动运行 DOM [cleanup](https://testing-library.com/docs/svelte-testing-library/api/#cleanup)。 -Jest 调用 `.mockClear` 后会重建 mock 状态,只能以 getter 方式访问; Vitest 则保留持久引用,可直接复用。 +### `spy.mockReset` {#spy-mockreset} -```ts -const mock = vi.fn() -const state = mock.mock -mock.mockClear() +Jest 的 [`mockReset`](https://jestjs.io/docs/mock-function-api#mockfnmockreset) 方法会将模拟函数的实现替换为一个返回 `undefined` 的空函数。 -expect(state).toBe(mock.mock) // fails in Jest -``` +而 Vitest 的 [`mockReset`](/api/mock#mockreset) 方法会将模拟函数的实现重置为其原始实现。 +也就是说,通过 `vi.fn(impl)` 创建的模拟,使用 `mockReset` 将会把模拟的实现重置回 `impl`。 -### 模块 Mock {#module-mocks} +### 模拟模块 {#module-mocks} -在 Jest 中,mock 模块时工厂函数返回值即为默认导出。在 Vitest 中,工厂函数需返回包含所有导出的对象。例如,以下 Jest 代码需要改写为: +在 Jest 中模拟一个模块时,工厂参数的返回值是默认导出。在 Vitest 中,工厂参数必须返回一个明确定义了每个导出的对象。例如,下面的 `jest.mock` 必须更新如下: ```ts jest.mock('./some-path', () => 'hello') // [!code --] -vi.mock('./some-path', () => ({ // [!code ++] +vi.mock('./some-path', () => ({ + // [!code ++] default: 'hello', // [!code ++] })) // [!code ++] ``` -更多细节请参考 [`vi.mock` API](/api/vi#vi-mock)。 +有关更深入的详细描述,请参阅 [`vi.mock` api section](/api/#vi-mock)。 -### 自动 Mock 行为 {#auto-mocking-behaviour} +### 自动模拟行为 {#auto-mocking-behaviour} -与 Jest 不同,Vitest 仅在调用 `vi.mock()` 时加载 `/__mocks__` 中的模块。如果你需要像 Jest 一样在每个测试中自动 mock,可以在 [`setupFiles`](/config/#setupfiles) 中调用 mock。 +区别于 Jest,在 `/__mocks__` 中的模拟模块只有在 `vi.mock()` 被调用时才会加载。如果你需要它们像在 Jest 中一样,在每个测试中都被模拟,你可以在 [`setupFiles`](/config/#setupfiles) 中模拟它们。 -### 导入被 Mock 包的原始模块 {#importing-the-original-of-a-mocked-package} +### 导入模拟包的原始版本 {#importing-the-original-of-a-mocked-package} -如果只部分 mock 一个包,之前可能用 Jest 的 `requireActual`,Vitest 中应使用 `vi.importActual`: +如果你只需要模拟一个 package 的部分功能,你可能之前使用了 Jest 的 `requireActual` 函数。在 Vitest 中,你应该将这些调用替换为 `vi.importActual`。 ```ts const { cloneDeep } = jest.requireActual('lodash/cloneDeep') // [!code --] const { cloneDeep } = await vi.importActual('lodash/cloneDeep') // [!code ++] ``` -### 扩展 Mock 到外部库 {#extends-mocking-to-external-libraries} +### 将模拟扩展到外部库 {#extends-mocking-to-external-libraries} -Jest 默认会扩展 mock 到使用相同模块的外部库。Vitest 需要显式告知要 mock 的第三方库,使其成为源码的一部分,方法是使用 [server.deps.inline](https://vitest.dev/config/#server-deps-inline): +在 Jest 的默认情况下,当模拟一个模块并希望将此模拟扩展到使用相同模块的其他外部库时,您应该明确告知您希望模拟哪个第三方库,这样外部库就会成为您源代码的一部分,方法是使用 [server.deps.inline](https://vitest.dev/config/#server-deps-inline). ``` server.deps.inline: ["lib-name"] ``` -### `expect.getState().currentTestName` +### expect.getState().currentTestName {#expect-getstate-currenttestname} -Vitest 的测试名使用 `>` 符号连接,方便区分测试与套件,而 Jest 使用空格 (` `)。 +Vitest 的 `test` 名称用 `>` 符号连接,以便于区分测试和套件,而 Jest 则使用空格 (` `)。 ```diff - `${describeTitle} ${testTitle}` @@ -416,56 +529,59 @@ Vitest 的测试名使用 `>` 符号连接,方便区分测试与套件,而 J ### 环境变量 {#envs} -与 Jest 类似,Vitest 会将未设置时的 `NODE_ENV` 设为 `test`。Vitest 还有对应 `JEST_WORKER_ID` 的 `VITEST_POOL_ID`(小于等于 `maxThreads`),如果依赖此值,需重命名。Vitest 还暴露 `VITEST_WORKER_ID`,表示唯一的运行中 worker ID,受 `maxThreads` 不影响,随 worker 创建递增。 +与 Jest 类似,如果未提前设置,Vitest 会将 `NODE_ENV` 设置为 `"test"`。 Vitest 还提供了与 `JEST_WORKER_ID` 对应的 `VITEST_POOL_ID` (该值始终大于等于 `maxThreads`),如果你依赖它请记得重命名。此外,Vitest 还会暴露 `VITEST_WORKER_ID` 作为运行中 worker的唯一标识 —— 此数值不受 `maxThreads`限制,并且随着 worker 创建递增。 -### 替换属性 {#replace-property} +### 属性替换 {#replace-property} -如果想修改对象,Jest 使用 [replaceProperty API](https://jestjs.io/docs/jest-object#jestreplacepropertyobject-propertykey-value),Vitest 可使用 [`vi.stubEnv`](/api/#vi-stubenv) 或 [`vi.spyOn`](/api/vi#vi-spyon) 达成相同效果。 +如果你想修改测试环境,你会在 Jest 中使用 [replaceProperty API](https://jestjs.io/docs/jest-object#jestreplacepropertyobject-propertykey-value),你可以使用 [vi.stubEnv](/api/vi#vi-stubenv) 或者 [`vi.spyOn`](/api/vi#vi-spyon) 也可以在 Vitest 中执行此操作。 -### Done 回调 {#done-callback} - -Vitest 不支持回调式测试声明。你可以改写为使用 `async`/`await` 函数,或使用 Promise 来模拟回调风格。 +### Done 回调函数 {#done-callback} +从 Vitest v0.10.0 开始,声明测试的回调样式被弃用。建议改用 `async`/`await` 函数,或者使用 Promise 回调风格。 -### Hooks {#hooks} +### 钩子 {#hooks} -Vitest 中 `beforeAll`/`beforeEach` 钩子可返回 [清理函数](/api/#setup-and-teardown)。因此,如果钩子返回非 `undefined` 或 `null`,可能需改写: +`beforeAll`/`beforeEach` 钩子可能在 Vitest 的 [teardown 函数](/api/#setup-and-teardown)中返回。因此,如果它们返回的不是 `undefined` 或 `null`,你可能需要重写你的钩子声明: ```ts beforeEach(() => setActivePinia(createTestingPinia())) // [!code --] -beforeEach(() => { setActivePinia(createTestingPinia()) }) // [!code ++] +beforeEach(() => { + setActivePinia(createTestingPinia()) +}) // [!code ++] ``` -在 Jest 中钩子是顺序执行的(一个接一个)。默认情况下,Vitest 在栈中运行钩子。要使用 Jest 的行为,请更新 [`sequence.hooks`](/config/#sequence-hooks) 选项: +在 Jest 中,钩子是按顺序调用的(一个接一个)。默认情况下,Vitest 并行运行钩子。要使用 Jest 的行为,请更新 [`sequence.hooks`](/config/#sequence-hooks) 选项: ```ts export default defineConfig({ test: { - sequence: { // [!code ++] + sequence: { + // [!code ++] hooks: 'list', // [!code ++] - } // [!code ++] - } + }, // [!code ++] + }, }) ``` ### 类型 {#types} -Vitest 没有 Jest 的 `jest` 命名空间,需直接从 `vitest` 导入类型: +Vitest 没有等效于 `jest` 的命名空间,因此你需要直接从 `Vitest` 导入类型: ```ts -let fn: jest.Mock<(name: string) => number> // [!code --] -import type { Mock } from 'vitest' // [!code ++] +// [!code --] +// [!code --] +import type { Mock } from 'vitest' +let fn: jest.Mock<(name: string) => number> // [!code ++] let fn: Mock<(name: string) => number> // [!code ++] ``` -### 定时器 {#timers} - -Vitest 不支持 Jest 的遗留定时器。 +### Timers {#timers} +Vitest 不支持 Jest的 传统计时器。 -### 超时 {#timeout} +### 定时器 {#timeout} -如果使用了 `jest.setTimeout`,需迁移为 `vi.setConfig`: +如果你之前在测试中使用了 jest.setTimeout ,那么你需要迁移到 Vitest 中的`vi.setConfig` : ```ts jest.setTimeout(5_000) // [!code --] @@ -474,16 +590,21 @@ vi.setConfig({ testTimeout: 5_000 }) // [!code ++] ### Vue 快照 {#vue-snapshots} -这不是 Jest 特有的功能,但如果你之前在 vue-cli 预设中使用 Jest,你需要安装 [`jest-serializer-vue`](https://github.com/eddyerburgh/jest-serializer-vue) 包,并在 [`snapshotSerializers`](/config/#snapshotserializers) 中指定它: - -```js [vitest.config.js] -import { defineConfig } from 'vitest/config' +如果你以前在 vue-cli preset 中使用 Jest,那么这不是一个 Jest 独有的新特性。你可能需要安装 [`jest-serializer-vue`](https://github.com/eddyerburgh/jest-serializer-vue) 包,然后在 [setupFiles](/config/#setupfiles) 中配置: +:::code-group +```js [vite.config.js] +import { defineConfig } from 'vite' export default defineConfig({ test: { - snapshotSerializers: ['jest-serializer-vue'] - } + setupFiles: ['./tests/unit/setup.js'], + }, }) ``` +```js [tests/unit/setup.js] +import vueSnapshotSerializer from 'jest-serializer-vue' +expect.addSnapshotSerializer(vueSnapshotSerializer) +``` +::: -否则快照中会出现大量转义的 `"` 字符。 +否则你的快照将出现大量的 `"` 字符。 From 9eb63977099320cd6c5c252a5b543a5d8d2fe172 Mon Sep 17 00:00:00 2001 From: noise Date: Thu, 13 Nov 2025 00:26:50 +0800 Subject: [PATCH 02/50] !docs: recovery /guide/coverage --- .vitepress/config.ts | 4 +- guide/coverage.md | 213 ++++++------------------------------------- 2 files changed, 32 insertions(+), 185 deletions(-) diff --git a/.vitepress/config.ts b/.vitepress/config.ts index f5e1e610..750977bc 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -659,8 +659,8 @@ function guide(): DefaultTheme.SidebarItem[] { collapsed: false, items: [ { - text: '迁移到 Vitest 4.0', - link: '/guide/migration#vitest-4', + text: '迁移到 Vitest 3.0', + link: '/guide/migration#vitest-3', }, { text: '从 Jest 迁移', diff --git a/guide/coverage.md b/guide/coverage.md index ea19b509..47ab5c5d 100644 --- a/guide/coverage.md +++ b/guide/coverage.md @@ -135,14 +135,15 @@ globalThis.__VITEST_COVERAGE__[filename] = coverage // [!code ++] ## 覆盖率配置指南 {#coverage-setup} -::: tip -你可以在 [覆盖率配置参考](/config/#coverage) 中查看所有可用的覆盖率选项。 +:::tip +建议始终在配置文件中定义 [`coverage.include`](/config/#coverage-include)。 +这有助于 Vitest 减少 [`coverage.all`](/config/#coverage-all) 选择的文件数量。 ::: -如果想要在测试中开启覆盖率统计,可以在命令行里加上 `--coverage` 参数,或者在 `vitest.config.ts` 文件里将 `coverage.enabled` 设置为 `true` : +要在启用的情况下进行测试,你可以在 CLI 中传递 `--coverage` 标志。 +默认情况下, 将使用 `['text', 'html', 'clover', 'json']` 作为测试报告器。 -::: code-group -```json [package.json] +```json { "scripts": { "test": "vitest", @@ -150,91 +151,21 @@ globalThis.__VITEST_COVERAGE__[filename] = coverage // [!code ++] } } ``` -```ts [vitest.config.ts] -import { defineConfig } from 'vitest/config' -export default defineConfig({ - test: { - coverage: { - enabled: true - }, - }, -}) -``` -::: - -## 在覆盖率报告中设置需要统计或忽略的文件 {#including-and-excluding-files-from-coverage-report} - -你可以通过设置 [`coverage.include`](/config/#coverage-include) 和 [`coverage.exclude`](/config/#coverage-exclude) 来决定覆盖率报告中展示哪些文件。 +要对其进行配置,需要在配置文件中设置 `test.coverage` 选项: -Vitest 默认只统计测试中实际导入的文件。如果希望报告里也包含那些未被测试覆盖到的文件,需要在 [`coverage.include`](/config/#coverage-include) 中配置一个能匹配你源代码文件的模式: - -::: code-group -```ts [vitest.config.ts] {6} +```ts +// vitest.config.ts import { defineConfig } from 'vitest/config' export default defineConfig({ test: { coverage: { - include: ['src/**.{ts,tsx}'] + reporter: ['text', 'json', 'html'], }, }, }) ``` -```sh [Covered Files] -├── src -│ ├── components -│ │ └── counter.tsx # [!code ++] -│ ├── mock-data -│ │ ├── products.json # [!code error] -│ │ └── users.json # [!code error] -│ └── utils -│ ├── formatters.ts # [!code ++] -│ ├── time.ts # [!code ++] -│ └── users.ts # [!code ++] -├── test -│ └── utils.test.ts # [!code error] -│ -├── package.json # [!code error] -├── tsup.config.ts # [!code error] -└── vitest.config.ts # [!code error] -``` -::: - -如果你想从覆盖率中排除已经被 `coverage.include` 匹配到的部分文件,可以通过额外配置 [`coverage.exclude`](/config/#coverage-exclude) 来实现: - -::: code-group -```ts [vitest.config.ts] {7} -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - coverage: { - include: ['src/**.{ts,tsx}'], - exclude: ['**/utils/users.ts'] - }, - }, -}) -``` -```sh [Covered Files] -├── src -│ ├── components -│ │ └── counter.tsx # [!code ++] -│ ├── mock-data -│ │ ├── products.json # [!code error] -│ │ └── users.json # [!code error] -│ └── utils -│ ├── formatters.ts # [!code ++] -│ ├── time.ts # [!code ++] -│ └── users.ts # [!code error] -├── test -│ └── utils.test.ts # [!code error] -│ -├── package.json # [!code error] -├── tsup.config.ts # [!code error] -└── vitest.config.ts # [!code error] -``` -::: ## 自定义覆盖率的报告器 {#custom-coverage-reporter} @@ -334,14 +265,31 @@ export default CustomCoverageProviderModule 请参阅类型定义查看有关详细信息。 +## 更改默认覆盖文件夹位置 {#changing-the-default-coverage-folder-location} + +运行覆盖率报告时,会在项目的根目录中创建一个 `coverage` 文件夹。 如果你想将它移动到不同的目录,请使用 `vite.config.js` 文件中的 `test.coverage.reportsDirectory` 属性。 + +```js +import { defineConfig } from 'vite' + +export default defineConfig({ + test: { + coverage: { + reportsDirectory: './tests/unit/coverage', + }, + }, +}) +``` + ## 代码忽略 {#ignoring-code} 两个覆盖率提供商都有自己的方法来忽略覆盖率报告中的代码: - [`v8`](https://github.com/AriPerkkio/ast-v8-to-istanbul?tab=readme-ov-file#ignoring-code) - [`istanbul`](https://github.com/istanbuljs/nyc#parsing-hints-ignoring-lines) +- 启用 [`experimentalAstAwareRemapping: true`](/config/#coverage-experimentalAstAwareRemapping) `v8` 的覆盖率工具,详见 [ast-v8-to-istanbul | 代码忽略](https://github.com/AriPerkkio/ast-v8-to-istanbul?tab=readme-ov-file#ignoring-code) -使用 TypeScript 时,源代码使用 `esbuild` 进行转译,这会从源代码中删除所有注释([esbuild#516](https://github.com/evanw/esbuild/issues/516))。 +使用 TypeScript 时,源码使用 `esbuild` 进行转译,这会从源码中删除所有注释([esbuild#516](https://github.com/evanw/esbuild/issues/516))。 被视为[合法注释](https://esbuild.github.io/api/#legal-comments)的注释将被保留。 你可以在忽略提示里加入 `@preserve` 关键字。 @@ -357,110 +305,9 @@ if (condition) { if (condition) { ``` -### 示例 {#examples} - -::: code-group - -```ts [if else] -/* v8 ignore if -- @preserve */ -if (parameter) { // [!code error] - console.log('Ignored') // [!code error] -} // [!code error] -else { - console.log('Included') -} - -/* v8 ignore else -- @preserve */ -if (parameter) { - console.log('Included') -} -else { // [!code error] - console.log('Ignored') // [!code error] -} // [!code error] -``` +## 其他选项 {#other-options} -```ts [next node] -/* v8 ignore next -- @preserve */ -console.log('Ignored') // [!code error] -console.log('Included') - -/* v8 ignore next -- @preserve */ -function ignored() { // [!code error] - console.log('all') // [!code error] - // [!code error] - console.log('lines') // [!code error] - // [!code error] - console.log('are') // [!code error] - // [!code error] - console.log('ignored') // [!code error] -} // [!code error] - -/* v8 ignore next -- @preserve */ -class Ignored { // [!code error] - ignored() {} // [!code error] - alsoIgnored() {} // [!code error] -} // [!code error] - -/* v8 ignore next -- @preserve */ -condition // [!code error] - ? console.log('ignored') // [!code error] - : console.log('also ignored') // [!code error] -``` - -```ts [try catch] -/* v8 ignore next -- @preserve */ -try { // [!code error] - console.log('Ignored') // [!code error] -} // [!code error] -catch (error) { // [!code error] - console.log('Ignored') // [!code error] -} // [!code error] - -try { - console.log('Included') -} -catch (error) { - /* v8 ignore next -- @preserve */ - console.log('Ignored') // [!code error] - /* v8 ignore next -- @preserve */ - console.log('Ignored') // [!code error] -} - -// 由于 esbuild 不支持,需要使用 rolldown-vite。 -// 参阅 https://vite.dev/guide/rolldown.html#how-to-try-rolldown -try { - console.log('Included') -} -catch (error) /* v8 ignore next */ { // [!code error] - console.log('Ignored') // [!code error] -} // [!code error] -``` - -```ts [switch case] -switch (type) { - case 1: - return 'Included' - - /* v8 ignore next -- @preserve */ - case 2: // [!code error] - return 'Ignored' // [!code error] - - case 3: - return 'Included' - - /* v8 ignore next -- @preserve */ - default: // [!code error] - return 'Ignored' // [!code error] -} -``` - -```ts [whole file] -/* v8 ignore file -- @preserve */ -export function ignored() { // [!code error] - return 'Whole file is ignored'// [!code error] -}// [!code error] -``` -::: +要查看有关覆盖率的所有可配置选项,请参见 [覆盖率配置参考](https://cn.vitest.dev/config/#coverage)。 ## 覆盖率性能 {#coverage-performance} From 3f797e9ccf81e1280ae374dfde9eb595c902ce46 Mon Sep 17 00:00:00 2001 From: noise Date: Thu, 13 Nov 2025 01:13:34 +0800 Subject: [PATCH 03/50] !docs: recovery /guide/reporters --- guide/reporters.md | 68 ++++++++++++---------------------------------- 1 file changed, 17 insertions(+), 51 deletions(-) diff --git a/guide/reporters.md b/guide/reporters.md index 65436754..5a58c018 100644 --- a/guide/reporters.md +++ b/guide/reporters.md @@ -142,67 +142,31 @@ export default defineConfig({ Duration 1.26s (transform 35ms, setup 1ms, collect 90ms, tests 1.47s, environment 0ms, prepare 267ms) ``` -如果只有一个测试文件在运行,Vitest 将输出该文件的完整测试树,类似于 [`tree`](#tree-reporter) 报告器。如果文件中至少有一个测试失败,default 报告器也会打印测试树。 +### 基础报告器 {#basic-reporter} -```bash -✓ __tests__/file1.test.ts (2) 725ms - ✓ first test file (2) 725ms - ✓ 2 + 2 should equal 4 - ✓ 4 - 2 should equal 2 - - Test Files 1 passed (1) - Tests 2 passed (2) - Start at 12:34:32 - Duration 1.26s (transform 35ms, setup 1ms, collect 90ms, tests 1.47s, environment 0ms, prepare 267ms) -``` - -### 详细报告器 {#verbose-reporter} - -详细报告器会在每个测试用例完成后打印出来。它不会单独报告套件或文件。如果启用了 `--includeTaskLocation`,它还会在输出中包含每个测试的位置。与 `default` 报告器类似,你可以通过配置报告器来禁用摘要。 - -除此之外,`verbose` 报告器会立即打印测试错误消息。完整的测试错误会在测试运行结束时报告。 - -这是唯一一个在测试未失败时报告[注解](/guide/test-annotations)的终端报告器。 +`basic` 报告器等同于没有 `summary` 的 `default` 报告器。 :::code-group ```bash [CLI] -npx vitest --reporter=verbose +npx vitest --reporter=basic ``` ```ts [vitest.config.ts] export default defineConfig({ test: { - reporters: [ - ['verbose', { summary: false }] - ] + reporters: ['basic'], }, }) ``` ::: -Example output: +使用基础报告器的输出示例: ```bash -✓ __tests__/file1.test.ts > first test file > 2 + 2 should equal 4 1ms -✓ __tests__/file1.test.ts > first test file > 4 - 2 should equal 2 1ms -✓ __tests__/file2.test.ts > second test file > 1 + 1 should equal 2 1ms -✓ __tests__/file2.test.ts > second test file > 2 - 1 should equal 1 1ms - - Test Files 2 passed (2) - Tests 4 passed (4) - Start at 12:34:32 - Duration 1.26s (transform 35ms, setup 1ms, collect 90ms, tests 1.47s, environment 0ms, prepare 267ms) -``` - -一个带有 `--includeTaskLocation` 参数的示例: - -```bash -✓ __tests__/file1.test.ts:2:1 > first test file > 2 + 2 should equal 4 1ms -✓ __tests__/file1.test.ts:3:1 > first test file > 4 - 2 should equal 2 1ms -✓ __tests__/file2.test.ts:2:1 > second test file > 1 + 1 should equal 2 1ms -✓ __tests__/file2.test.ts:3:1 > second test file > 2 - 1 should equal 1 1ms +✓ __tests__/file1.test.ts (2) 725ms +✓ __tests__/file2.test.ts (2) 746ms Test Files 2 passed (2) Tests 4 passed (4) @@ -210,24 +174,26 @@ Example output: Duration 1.26s (transform 35ms, setup 1ms, collect 90ms, tests 1.47s, environment 0ms, prepare 267ms) ``` -### 树状报告器 {#tree-reporter} +### 详细报告器 {#verbose-reporter} -树状报告器与 `default` 报告器相同,但它还会在套件完成后显示每个单独的测试。与 `default` 报告器类似,你可以通过配置报告器来禁用摘要。 +详细报告器与 `default` 报告器相同,但它还会在测试套件完成后显示每个单独的测试。它还会显示当前正在运行且耗时超过 [`slowTestThreshold`](/config/#slowtestthreshold) 的测试。与 `default` 报告器类似,我们可以通过配置报告器来禁用摘要。 :::code-group + ```bash [CLI] -npx vitest --reporter=tree +npx vitest --reporter=verbose ``` ```ts [vitest.config.ts] export default defineConfig({ test: { reporters: [ - ['tree', { summary: false }] + ['verbose', { summary: false }] ] }, }) ``` + ::: 使用默认 `slowTestThreshold: 300` 的情况下,测试进行中的示例输出: @@ -268,7 +234,7 @@ export default defineConfig({ ### Dot 报告器 {#dot-reporter} -每当一个测试完成时,就会打印一个点,以最小化输出量,同时让你看到所有执行过的测试。只有当测试失败时才会显示详细信息,并在最后提供套件的汇总。 +为每个已完成的测试打印一个点,以提供最少的输出,并显示所有已运行的测试。只提供失败测试的详细信息,以及套件的基本报告摘要。 :::code-group @@ -417,7 +383,7 @@ JSON 报告示例: ``` ::: info -自Vitest 3起,如果启用了代码覆盖率功能,JSON 报告器会在 `coverageMap` 中包含覆盖率信息。 +自 Vitest 3 起,如果启用了代码覆盖率功能,JSON 报告器会在 `coverageMap` 中包含覆盖率信息。 ::: ### HTML 报告器 {#html-reporter} @@ -448,7 +414,7 @@ export default defineConfig({ ### TAP 报告器 {#tap-reporter} -按照 [Test Anything Protocol](https://testanything.org/) (TAP)输出报告。 +按照 [Test Anything Protocol](https://testanything.org/) (TAP) 输出报告。 :::code-group @@ -491,7 +457,7 @@ not ok 1 - __tests__/test-file-1.test.ts # time=14.00ms { ### TAP 扁平报告器 {#tap-flat-reporter} -输出 TAP 扁平报告。与 `TAP Reporter` 一样,测试结果的格式遵循 TAP 标准,但测试套件的格式是扁平列表,而不是嵌套层次结构。 +输出 TAP 扁平报告。与 `TAP` 报告器一样,测试结果的格式遵循 TAP 标准,但测试套件的格式是扁平列表,而不是嵌套层次结构。 :::code-group From 8e703f673a5b79dcc9a2efb56bc9307f46adf7f2 Mon Sep 17 00:00:00 2001 From: noise Date: Thu, 13 Nov 2025 01:37:07 +0800 Subject: [PATCH 04/50] !docs: recovery /guide/profiling-test-performance --- guide/profiling-test-performance.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guide/profiling-test-performance.md b/guide/profiling-test-performance.md index 0e1c3287..639fe939 100644 --- a/guide/profiling-test-performance.md +++ b/guide/profiling-test-performance.md @@ -83,9 +83,9 @@ export default defineConfig({ ``` ::: -测试运行后,应该会生成 `test-runner-profile/*.cpuprofile` 和 `test-runner-profile/*.heapprofile` 文件。想要知道如何分析这些文件,可以仔细查看[「 检查分析记录 / Inspecting profiling records 」](#inspecting-profiling-records)。 +测试运行后,应该会生成 `test-runner-profile/*.cpuprofile` 和 `test-runner-profile/*.heapprofile` 文件。想要知道如何分析这些文件,可以仔细查看 [性能分析记录](#inspecting-profiling-records)。 -也可以看看[性能分析 | 示例](https://github.com/vitest-dev/vitest/tree/main/examples/profiling)。 +也可以看看 [性能分析 | 示例](https://github.com/vitest-dev/vitest/tree/main/examples/profiling) 。 ## 主线程 {#main-thread} From b1011c3333c64e265e52097bcd0a9750527eb20f Mon Sep 17 00:00:00 2001 From: noise Date: Thu, 13 Nov 2025 11:10:49 +0800 Subject: [PATCH 05/50] !docs: recovery /guide/index --- guide/index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/guide/index.md b/guide/index.md index 91927ec2..61d070ac 100644 --- a/guide/index.md +++ b/guide/index.md @@ -66,7 +66,7 @@ test('adds 1 + 2 to equal 3', () => { ``` ::: tip 提示 -一般情况下,执行测试的文件名中必须包含 ".test." 或 ".spec." 。 +一般情况下,执行测试的文件名中必须包含 `.test.` 或 `.spec.` 。 ::: 接下来,为了执行测试,请将以下部分添加到你的 `package.json` 文件中: @@ -120,7 +120,7 @@ export default defineConfig({ ``` ::: tip 提示 -即使你自己不使用 Vite,Vitest 的转换管道也严重依赖它。因此,你还可以配置[Vite 文档](https://cn.vitejs.dev/config/)中描述的任何属性。 +即使你自己不使用 Vite,Vitest 的转换管道也严重依赖它。因此,你还可以配置 [Vite 文档](https://cn.vitejs.dev/config/) 中描述的任何属性。 ::: 如果你已经在使用 Vite,请在 Vite 配置中添加 `test` 属性。你还需要使用 [三斜杠指令](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) 在你的配置文件的顶部引用。 @@ -168,7 +168,7 @@ export default defineConfig({ ## 多项目支持 {#projects-support} -通过 [测试项目](/guide/projects) 功能,你可以在同一个项目里运行多套不同的配置。只需在 vitest.config 文件中列出对应的文件和文件夹,即可定义各个项目。 +通过 [测试项目](/guide/projects) 功能,你可以在同一个项目里运行多套不同的配置。只需在 `vitest.config` 文件中列出对应的文件和文件夹,即可定义各个项目。 ```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' @@ -221,7 +221,7 @@ export default defineConfig({ 要在不监视文件更改的情况下运行一次测试,请使用 `vitest run`。 你还可以指定其他 CLI 选项,例如 `--port` 或 `--https`。 有关 CLI 选项的完整列表,可以在你的项目中运行 `npx vitest --help`。 -了解更多有关 [命令行界面](/guide/cli) 的更多信息 +了解更多有关 [命令行界面](/guide/cli) 的信息 ## 自动安装依赖项 {#automatic-dependency-installation} @@ -233,7 +233,7 @@ export default defineConfig({ [从 VS Code 插件市场进行安装](https://marketplace.visualstudio.com/items?itemName=vitest.explorer) -了解更多有关 [IDE 插件](/guide/ide) 的更多信息 +了解更多有关 [IDE 插件](/guide/ide) 的信息 ## 示例 {#examples} From 1e8df5f9dcba7c20d28aa059f8319f3b25f9e71e Mon Sep 17 00:00:00 2001 From: noise Date: Sat, 15 Nov 2025 22:22:34 +0800 Subject: [PATCH 06/50] !docs: recovery /guide/features --- .vitepress/components/FeaturesList.vue | 6 +++--- .vitepress/config.ts | 4 ++-- guide/features.md | 17 +++++++++-------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.vitepress/components/FeaturesList.vue b/.vitepress/components/FeaturesList.vue index dd43d850..fa581170 100644 --- a/.vitepress/components/FeaturesList.vue +++ b/.vitepress/components/FeaturesList.vue @@ -21,7 +21,7 @@ 支持基准测试 支持套件和测试的过滤、超时、并发配置 - 支持 Projects / Workspace + 支持 Projects Jest 快照功能 @@ -37,8 +37,8 @@ 使用 - jsdom 或 - happy-dom + happy-dom 或 + jsdom 模拟 DOM 浏览器模式:在浏览器中运行组件测试 diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 750977bc..f2ecccc1 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -567,11 +567,11 @@ function guide(): DefaultTheme.SidebarItem[] { link: '/guide/reporters', }, { - text: '测试覆盖率', + text: '覆盖率', link: '/guide/coverage', }, { - text: '测试快照', + text: '快照', link: '/guide/snapshot', }, { diff --git a/guide/features.md b/guide/features.md index 739d5675..b17db28b 100644 --- a/guide/features.md +++ b/guide/features.md @@ -7,14 +7,15 @@ outline: deep +通过视频了解如何编写你的第一个测试 + ## 一套配置可以运用在多种环境 {#shared-config-between-test-dev-and-build}
-通过视频了解如何编写你的第一个测试 与 Vite 的配置、转换器、解析器和插件通用,将会使用应用中的相同配置来运行测试。 -了解更多信息 [配置 Vitest](/guide/#配置-vitest) +了解更多信息 [配置 Vitest](/guide/#configuring-vitest) 。 ## 监听模式(watch mode) {#watch-mode} @@ -45,7 +46,7 @@ Vitest 还隔离了每个测试文件的运行环境,因此一个文件中的 Vitest 提供了许多缩小测试范围的方法,以便在开发过程中加快速度并集中精力。 -了解更多信息 [测试筛选](/guide/filtering) +了解更多信息 [测试筛选](/guide/filtering) 。 ## 同时运行多个测试 {#running-tests-concurrently} @@ -116,7 +117,7 @@ it('renders correctly', () => { 注意,如果你正在使用添加匹配器的第三方库,将 [`test.globals`](/config/#globals) 设置为 `true` 将提供更好的兼容性。 -## 对象模拟(Mocking) {#mocking} +## 对象模拟 (Mocking) {#mocking} 内置 [Tinyspy](https://github.com/tinylibs/tinyspy) 用于在 `vi` 对象上使用 `jest` 兼容的 API 进行对象模拟。 @@ -157,7 +158,7 @@ export default defineConfig({ }) ``` -了解更多信息 [模拟对象](/guide/mocking) +了解更多信息 [模拟对象](/guide/mocking) 。 ## 测试覆盖率 {#coverage} @@ -187,11 +188,11 @@ export default defineConfig({ }) ``` -了解更多信息 [测试覆盖率](/guide/coverage) +了解更多信息 [覆盖率](/guide/coverage) 。 ## 源码内联测试 {#in-source-testing} -Vitest 还提供了一种方式,可以运行与你的代码实现放在一起的测试,类似 [Rust's 模块测试](https://doc.rust-lang.org/book/ch11-03-test-organization.html#the-tests-module-and-cfgtest). +Vitest 还提供了一种方式,可以运行与你的代码实现放在一起的测试,类似 [Rust 模块测试](https://doc.rust-lang.org/book/ch11-03-test-organization.html#the-tests-module-and-cfgtest). 这使得测试与实现共享相同的闭包,并且能够在不导出的情况下针对私有状态进行测试。同时,它也使开发更加接近反馈循环。 @@ -211,7 +212,7 @@ if (import.meta.vitest) { } ``` -了解更多信息 [源码内联测试](/guide/in-source) +了解更多信息 [源码内联测试](/guide/in-source) 。 ## 基准测试 实验性 {#benchmarking} From 939aa8960ad21633296cc25b39e26ca49ce8ae93 Mon Sep 17 00:00:00 2001 From: noise Date: Sat, 15 Nov 2025 23:50:37 +0800 Subject: [PATCH 07/50] !docs: recovery /api/index --- api/index.md | 125 +++++++++++++++++++-------------------------------- 1 file changed, 46 insertions(+), 79 deletions(-) diff --git a/api/index.md b/api/index.md index fb1ad821..466632cd 100644 --- a/api/index.md +++ b/api/index.md @@ -35,7 +35,7 @@ interface TestOptions { 当测试函数返回一个 promise 时,运行器会等待它解析结束收集异步的结果。如果 promise 被拒绝,测试就会失败。 ::: tip -在 Jest 中,`TestFunction` 也可以是 `(done: DoneCallback) => void` 类型。如果使用这种形式,测试将在调用 `done` 之前不会结束。也可以使用 `async` 函数来实现相同的效果,请参阅[迁移指南中的回调完成部分](/guide/migration#回调完成)。 +在 Jest 中,`TestFunction` 也可以是 `(done: DoneCallback) => void` 类型。如果使用这种形式,测试将在调用 `done` 之前不会结束。也可以使用 `async` 函数来实现相同的效果,请参阅 [迁移指南中的回调完成部分](/guide/migration.html#done-callback)。 ::: @@ -108,7 +108,7 @@ test('heavy test', { skip: true, timeout: 10_000 }, () => { `test` 定义了一组相关的期望。 它接收测试名称和保存测试期望的函数。 -或者,我们可以提供超时(以毫秒为单位)来指定终止前等待的时间。 默认为 5 秒,可以通过 [testTimeout](/config/#testtimeout) 进行全局配置 +或者,我们可以提供超时(以毫秒为单位)来指定终止前等待的时间。 默认为 5 秒,可以通过 [testTimeout](/config/#testtimeout) 进行全局配置。 ```ts import { expect, test } from 'vitest' @@ -123,7 +123,7 @@ test('should work as expected', () => { - **类型:** `>(fixtures: Fixtures): TestAPI` - **别名:** `it.extend` -使用 `test.extend` 来使用自定义的 fixtures 扩展测试上下文。这将返回一个新的 `test`,它也是可扩展的,因此可以根据需要扩展更多的 fixtures 或覆盖现有的 fixtures。有关更多信息,请参阅[扩展测试上下文](/guide/test-context.html#test-extend)。 +使用 `test.extend` 来使用自定义的 fixtures 扩展测试上下文。这将返回一个新的 `test`,它也是可扩展的,因此可以根据需要扩展更多的 fixtures 或覆盖现有的 fixtures。有关更多信息,请参阅 [扩展测试上下文](/guide/test-context.html#test-extend)。 ```ts import { expect, test } from 'vitest' @@ -176,7 +176,7 @@ test('skipped test', (context) => { }) ``` -自 Vitest 3.1 起,如果你无法提前确定是否跳过,可以把条件直接作为第一个参数传给 `skip 方法: +自 Vitest 3.1 起,如果你无法提前确定是否跳过,可以把条件直接作为第一个参数传给 `skip` 方法: ```ts import { assert, test } from 'vitest' @@ -206,7 +206,7 @@ test.skipIf(isDev)('prod only test', () => { ``` ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### test.runIf @@ -227,7 +227,7 @@ test.runIf(isDev)('dev only test', () => { ``` ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### test.only @@ -248,7 +248,7 @@ test.only('test', () => { }) ``` -有时,只运行某个文件中的 "测试",而忽略整个 测试套件 中的所有其他测试是非常有用的,因为这些测试会污染输出。 +有时,只运行某个文件中的 "测试",而忽略整个测试套件中的所有其他测试是非常有用的,因为这些测试会污染输出。 为此,请使用包含相关测试的特定文件运行 `vitest`。 @@ -289,7 +289,7 @@ test.only.concurrent(/* ... */) // or test.concurrent.only(/* ... */) test.todo.concurrent(/* ... */) // or test.concurrent.todo(/* ... */) ``` -运行并发测试时,快照和断言必须使用本地[测试上下文](/guide/test-context.md)中的 `expect`,以确保检测到正确的测试。 +运行并发测试时,快照和断言必须使用本地 [测试上下文](/guide/test-context.md) 中的 `expect`,以确保检测到正确的测试。 ```ts test.concurrent('test 1', async ({ expect }) => { @@ -301,12 +301,13 @@ test.concurrent('test 2', async ({ expect }) => { ``` ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### test.sequential - **类型:** `(name: string | Function, fn: TestFunction, timeout?: number) => void` +- **别名:** `it.sequential` `test.sequential` 标记一个测试为顺序测试。如果要在 `describe.concurrent` 中或使用 `--sequence.concurrent` 命令选项按顺序运行测试,这一点非常有用。 @@ -314,35 +315,19 @@ test.concurrent('test 2', async ({ expect }) => { import { describe, test } from 'vitest' // 使用配置选项 `{ sequence: { concurrent: true } }` -test('concurrent test 1', async () => { - /* ... */ -}) -test('concurrent test 2', async () => { - /* ... */ -}) +test('concurrent test 1', async () => { /* ... */ }) +test('concurrent test 2', async () => { /* ... */ }) -test.sequential('sequential test 1', async () => { - /* ... */ -}) -test.sequential('sequential test 2', async () => { - /* ... */ -}) +test.sequential('sequential test 1', async () => { /* ... */ }) +test.sequential('sequential test 2', async () => { /* ... */ }) // 在并发套件中 describe.concurrent('suite', () => { - test('concurrent test 1', async () => { - /* ... */ - }) - test('concurrent test 2', async () => { - /* ... */ - }) + test('concurrent test 1', async () => { /* ... */ }) + test('concurrent test 2', async () => { /* ... */ }) - test.sequential('sequential test 1', async () => { - /* ... */ - }) - test.sequential('sequential test 2', async () => { - /* ... */ - }) + test.sequential('sequential test 1', async () => { /* ... */ }) + test.sequential('sequential test 2', async () => { /* ... */ }) }) ``` @@ -360,7 +345,6 @@ test.todo('unimplemented test') ### test.fails -- **类型:** `(name: string | Function, fn: TestFunction, timeout?: number) => void` - **别名:** `it.fails` 使用 `test.fails` 明确表示断言将失败。 @@ -377,7 +361,7 @@ test.fails('fail test', async () => { ``` ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### test.each @@ -487,7 +471,7 @@ Vitest 使用 chai `format` 方法处理 `$values`。如果数值太短,可以 ::: ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### test.for @@ -504,8 +488,8 @@ test.each([ [1, 1, 2], [1, 2, 3], [2, 1, 3], -])('add(%i, %i) -> %i', (a, b, expected) => { // [!code --] +])('add(%i, %i) -> %i', (a, b, expected) => { expect(a + b).toBe(expected) }) @@ -514,8 +498,8 @@ test.for([ [1, 1, 2], [1, 2, 3], [2, 1, 3], -])('add(%i, %i) -> %i', ([a, b, expected]) => { // [!code ++] +])('add(%i, %i) -> %i', ([a, b, expected]) => { expect(a + b).toBe(expected) }) ``` @@ -856,6 +840,7 @@ describe('numberToCurrency', () => { ### describe.skip - **类型:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void` +- **别名:** `suite.skip` 在套件中使用 `describe.skip` 可避免运行特定的 describe 块。 @@ -873,6 +858,7 @@ describe.skip('skipped suite', () => { ### describe.skipIf - **类型:** `(condition: any) => void` +- **别名:** `suite.skipIf` 在某些情况下,可能会在不同的环境下多次运行套件,其中一些测试套件可能是特定于环境的。可以使用 `describe.skipIf` 来跳过条件为真时的套件,而不是使用 `if` 来封装套件。 @@ -893,6 +879,7 @@ describe.skipIf(isDev)('prod only test suite', () => { ### describe.runIf - **类型:** `(condition: any) => void` +- **别名:** `suite.runIf` 与 [describe.skipIf](#describe-skipif) 相反。 @@ -931,11 +918,9 @@ describe('other suite', () => { }) ``` -为了做到这一点,请使用包含相关测试的特定文件来运行 `vitest`。 - 有时,只运行某个文件中的测试套件,而忽略整个测试套件中的所有其他测试是非常有用的,因为这些测试会污染输出。 -要做到这一点,请在包含相关测试的特定文件中运行 `vitest`。 +为了做到这一点,请使用包含相关测试的特定文件来运行 `vitest`。 ``` # vitest interesting.test.ts @@ -978,7 +963,7 @@ describe.only.concurrent(/* ... */) // 或 describe.concurrent.only(/* ... */) describe.todo.concurrent(/* ... */) // 或 describe.concurrent.todo(/* ... */) ``` -运行并发测试时,快照和断言必须使用本地[测试上下文](/guide/test-context.md)中的 `expect` ,以确保检测到正确的测试。 +运行并发测试时,快照和断言必须使用本地 [测试上下文](/guide/test-context.md) 中的 `expect` ,以确保检测到正确的测试。 ```ts describe.concurrent('suite', () => { @@ -992,12 +977,13 @@ describe.concurrent('suite', () => { ``` ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### describe.sequential - **类型:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void` +- **别名:** `suite.sequential` 测试套件中的 `describe.sequential` 会将每个测试标记为顺序测试。如果需要在 `describe.concurrent` 中或使用 `--sequence.concurrent` 命令选项按顺序运行测试,这一点非常有用。 @@ -1005,20 +991,12 @@ describe.concurrent('suite', () => { import { describe, test } from 'vitest' describe.concurrent('suite', () => { - test('concurrent test 1', async () => { - /* ... */ - }) - test('concurrent test 2', async () => { - /* ... */ - }) + test('concurrent test 1', async () => { /* ... */ }) + test('concurrent test 2', async () => { /* ... */ }) describe.sequential('', () => { - test('sequential test 1', async () => { - /* ... */ - }) - test('sequential test 2', async () => { - /* ... */ - }) + test('sequential test 1', async () => { /* ... */ }) + test('sequential test 2', async () => { /* ... */ }) }) }) ``` @@ -1026,6 +1004,7 @@ describe.concurrent('suite', () => { ### describe.shuffle - **类型:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void` +- **别名:** `suite.shuffle` Vitest 通过 CLI 标志 [`--sequence.shuffle`](/guide/cli) 或配置选项 [`sequence.shuffle`](/config/#sequence-shuffle),提供了一种以随机顺序运行所有测试的方法,但如果只想让测试套件的一部分以随机顺序运行测试,可以用这个标志来标记它。 @@ -1034,48 +1013,35 @@ import { describe, test } from 'vitest' // 或 `describe('suite', { shuffle: true }, ...)` describe.shuffle('suite', () => { - test('random test 1', async () => { - /* ... */ - }) - test('random test 2', async () => { - /* ... */ - }) - test('random test 3', async () => { - /* ... */ - }) + test('random test 1', async () => { /* ... */ }) + test('random test 2', async () => { /* ... */ }) + test('random test 3', async () => { /* ... */ }) // `shuffle` 是继承的 describe('still random', () => { - test('random 4.1', async () => { - /* ... */ - }) - test('random 4.2', async () => { - /* ... */ - }) + test('random 4.1', async () => { /* ... */ }) + test('random 4.2', async () => { /* ... */ }) }) // 禁用内部的 shuffle describe('not random', { shuffle: false }, () => { - test('in order 5.1', async () => { - /* ... */ - }) - test('in order 5.2', async () => { - /* ... */ - }) + test('in order 5.1', async () => { /* ... */ }) + test('in order 5.2', async () => { /* ... */ }) }) }) // 顺序取决于配置中的 `sequence.seed` 选项(默认为 `Date.now()`) ``` -`.skip`、`.only`和`.todo`适用于随机测试套件。 +`.skip`、 `.only` 和 `.todo` 适用于随机测试套件。 ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### describe.todo - **类型:** `(name: string | Function) => void` +- **别名:** `suite.todo` 使用 `describe.todo` 来暂存待以后实施的套件。测试报告中会显示一个条目,这样就能知道还有多少测试需要执行。 @@ -1087,6 +1053,7 @@ describe.todo('unimplemented suite') ### describe.each - **类型:** `(cases: ReadonlyArray, ...args: any[]): (name: string | Function, fn: (...args: T[]) => void, options?: number | TestOptions) => void` +- **别名:** `suite.each` ::: tip 虽然 `describe.each` 是为了兼容 Jest 提供的, @@ -1140,7 +1107,7 @@ describe.each` ``` ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### describe.for From 3c2087b22d21411c97a6a6181ee2e23c44681bec Mon Sep 17 00:00:00 2001 From: noise Date: Sun, 16 Nov 2025 13:15:18 +0800 Subject: [PATCH 08/50] !docs: recovery /api/mock --- .vitepress/config.ts | 2 +- api/mock.md | 55 +++++++------------------------------------- 2 files changed, 9 insertions(+), 48 deletions(-) diff --git a/.vitepress/config.ts b/.vitepress/config.ts index f2ecccc1..980e4042 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -692,7 +692,7 @@ function api(): DefaultTheme.SidebarItem[] { link: '/api/', }, { - text: 'Mocks', + text: 'Mock Functions', link: '/api/mock', }, { diff --git a/api/mock.md b/api/mock.md index 036d9cea..70f93cec 100644 --- a/api/mock.md +++ b/api/mock.md @@ -1,6 +1,6 @@ -# Mocks +# Mock Functions -用 `vi.fn` 即可创建 mock 函数或类,并全程记录其调用情况;若想监控已存在对象上的某个属性,则改用 `vi.spyOn`。 +我们可以使用 `vi.fn` 方法创建一个 mock 函数来跟踪其执行情况。如果要跟踪已创建对象上的方法,可以使用 `vi.spyOn` 方法: ```js import { vi } from 'vitest' @@ -18,10 +18,10 @@ market.getApples() getApplesSpy.mock.calls.length === 1 ``` -要验证 mock 的行为,请通过 [`expect`](/api/expect) 调用类似 [`toHaveBeenCalled`](/api/expect#tohavebeencalled) 的断言方法;以下 API 参考汇总了所有可用来操控 mock 的属性和方法。 +我们应该在 [`expect`](/api/expect) 上使用 mock 断言(例如 [`toHaveBeenCalled`](/api/expect#tohavebeencalled) )来断言 mock 结果。在这里我们介绍了用于操作 mock 行为的可用属性和方法。 ::: tip -The custom function implementation in the types below is marked with a generic ``. +下列类型中的自定义函数实现通过泛型 `` 标记。 ::: ## getMockImplementation @@ -42,7 +42,7 @@ function getMockImplementation(): T | undefined function getMockName(): string ``` -此方法返回由 `.mockName(name)` 为 mock 指定的名称。`vi.fn()` 创建的替身默认返回 `'vi.fn()'`; `vi.spyOn` 生成的 spy 则沿用被监视方法的原始名称。 +使用它来返回使用 `.mockName(name)` 方法分配给 mock 对象的名称。默认情况下,它将返回 `vi.fn()`。 ## mockClear @@ -215,7 +215,7 @@ await asyncMock() // 抛出 Error<'Async error'> function mockReset(): Mock ``` -该方法会先执行与 [`mockClear`](#mockClear) 相同的清理,再重置 mock 的实现,并一并清除所有一次性(once)设定。 +执行与 `mockClear` 相同的操作,并将内部实现设置为空函数(调用时返回 undefined)。这也会重置所有 “once” 实现。它对于将 mock 完全重置为其默认状态很有用。 注意: @@ -378,37 +378,6 @@ fn.mock.calls ] ``` -:::warning 对象按引用存储。 -请注意,Vitest 在 `mock` 状态的所有属性中始终按引用保存对象。一旦你的代码修改了这些属性,诸如 [`.toHaveBeenCalledWith`](/api/expect#tohavebeencalledwith) 之类的断言便可能无法通过: - -```ts -const argument = { - value: 0, -} -const fn = vi.fn() -fn(argument) // { value: 0 } - -argument.value = 10 - -expect(fn).toHaveBeenCalledWith({ value: 0 }) // [!code --] - -// 相等性检查是针对原始参数进行的, -// 但是,该参数的属性在调用和断言之间发生了更改。 -expect(fn).toHaveBeenCalledWith({ value: 10 }) // [!code ++] -``` - -此时,可先自行克隆该参数: - -```ts{6} -const calledArguments = [] -const fn = vi.fn((arg) => { - calledArguments.push(structuredClone(arg)) -}) - -expect(calledArguments[0]).toEqual({ value: 0 }) -``` -::: - ## mock.lastCall ```ts @@ -491,11 +460,6 @@ fn.mock.results ## mock.settledResults ```ts -interface MockSettledResultIncomplete { - type: 'incomplete' - value: undefined -} - interface MockSettledResultFulfilled { type: 'fulfilled' value: T @@ -509,16 +473,13 @@ interface MockSettledResultRejected { export type MockSettledResult = | MockSettledResultFulfilled | MockSettledResultRejected - | MockSettledResultIncomplete const settledResults: MockSettledResult>>[] ``` -该数组按顺序记录了函数每次被调用后最终兑现或拒绝的值。 - -若函数返回的是非 Promise ,实际值会原封不动地保留,但状态仍被标记为 `fulfilled` 或 `rejected`。 +包含函数中 `resolved` 或 `rejected` 的所有值的数组。 -在结果出来前,对应的 `settledResult` 类型始终为 `incomplete`。 +如果函数从未 resolved 或 rejected ,则此数组将为空。 ```js const fn = vi.fn().mockResolvedValueOnce('result') From 54ec728c92f14a772d96ec2b1927d9f400fc5eed Mon Sep 17 00:00:00 2001 From: noise Date: Sun, 16 Nov 2025 21:43:05 +0800 Subject: [PATCH 09/50] !docs: recovery /api/vi --- api/vi.md | 346 ++++++++++++------------------------------------------ 1 file changed, 75 insertions(+), 271 deletions(-) diff --git a/api/vi.md b/api/vi.md index a2860833..337f5a6e 100644 --- a/api/vi.md +++ b/api/vi.md @@ -4,33 +4,20 @@ outline: deep # Vi -Vitest 通过其 `vi` 辅助工具提供实用功能来帮助你。可以全局访问它(当启用 [globals 配置](/config/#globals) 时),也可以直接从 `vitest` 中导入: +Vitest 通过 `vi` 工具函数提供实用功能。可以全局访问它(当启用 [globals 配置](/config/#globals) 时),也可以直接从 `vitest` 中导入: ```js import { vi } from 'vitest' ``` -## 模拟模块 {#mock-modules} +## Mock Modules 本节介绍在 [模拟模块](/guide/mocking#modules) 时可以使用的 API。请注意,Vitest 不支持模拟使用 `require()` 导入的模块。 ### vi.mock -```ts -interface MockOptions { - spy?: boolean -} - -interface MockFactory { - (importOriginal: () => T): unknown -} - -function mock(path: string, factory?: MockOptions | MockFactory): void -function mock( - module: Promise, - factory?: MockOptions | MockFactory -): void -``` +- **类型**: `(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void` +- **类型**: `(path: Promise, factory?: MockOptions | ((importOriginal: () => T) => T | Promise)) => void` 用另一个模块替换提供的 `path` 中的所有导入模块。我们可以在路径内使用配置的 Vite 别名。对 `vi.mock` 的调用是悬挂式的,因此在何处调用并不重要。它总是在所有导入之前执行。如果需要在其作用域之外引用某些变量,可以在 [`vi.hoisted`](/api/vi#vi-hoisted)中定义它们,并在 `vi.mock` 中引用它们。 @@ -44,6 +31,8 @@ Vitest 不会模拟 [setup file](/config/#setupfiles) 中导入的模块,因 如果定义了 `factory` 函数,所有导入都将返回其结果。Vitest 只调用一次 factory,并缓存所有后续导入的结果,直到 [`vi.unmock`](#vi-unmock) 或 [`vi.doUnmock`](#vi-dounmock) 被调用。 +与 `jest` 不同, 该工厂函数是可以异步的。你可以通过 [`vi.importActual`](#vi-importactual) 或传入工厂函数作为首个参数的工具方法,在内部获取原始模块。 + 我们还可以提供一个具有 `spy` 属性的对象,而不是工厂函数。如果 `spy` 为 `true`,则 Vitest 将照常自动模拟模块,但不会覆盖导出的实现。如果我们只想断言导出的方法已被另一种方法正确调用,这将非常有用。 ```ts @@ -59,7 +48,6 @@ expect(result).toBe(3) expect(calculator).toHaveBeenCalledWith(1, 2) expect(calculator).toHaveReturned(3) ``` - Vitest 还在 `vi.mock` 和 `vi.doMock` 方法中支持 module promise 而非字符串,以获得更好的集成开发环境支持。当文件被移动时,路径会被更新,`importOriginal` 也会自动继承类型。使用此签名还将强制工厂返回类型与原始模块兼容(但每次导出都是可选的)。 ```ts @@ -119,7 +107,7 @@ vi.mock('./path/to/module.js', () => { return { default: { myDefaultKey: vi.fn() }, namedExport: vi.fn(), - // ... + // etc... } }) ``` @@ -143,11 +131,11 @@ vi.mock('./path/to/module.js', () => { 如果在没有提供工厂或选项的测试文件中调用 `vi.mock` ,它会在 `__mocks__` 文件夹中找到一个文件作为模块使用: ```ts [increment.test.js] +import { vi } from 'vitest' + // axios 是 `__mocks__/axios.js` 默认导出项 import axios from 'axios' -import { vi } from 'vitest' - // increment 是 `src/__mocks__/increment.js` 具名导出 import { increment } from '../increment.js' @@ -162,20 +150,12 @@ axios.get(`/apples/${increment(1)}`) 请注意,如果不调用 `vi.mock` ,模块**不会**被自动模拟。要复制 Jest 的自动锁定行为,可以在 [`setupFiles`](/config/#setupfiles) 中为每个所需的模块调用 `vi.mock` 。 ::: -如果没有提供 `__mocks__` 文件夹或未提供工厂函数,Vitest 将导入原始模块并自动模拟其所有导出。有关应用的规则,请参阅[算法](/guide/mocking/modules#automocking-algorithm)。 +如果没有提供 `__mocks__` 文件夹或工厂,Vitest 将导入原始模块并自动模拟其所有输出。有关应用的规则,请参阅 [模块](/mocking.html#automocking-algorithm)。 ### vi.doMock -```ts -function doMock( - path: string, - factory?: MockOptions | MockFactory -): void -function doMock( - module: Promise, - factory?: MockOptions | MockFactory -): void -``` +- **类型**: `(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void` +- **类型**: `(path: Promise, factory?: MockOptions | ((importOriginal: () => T) => T | Promise)) => void` 与 [`vi.mock`](#vi-mock) 相同,但它不会被移动到文件顶部,因此我们可以引用全局文件作用域中的变量。模块的下一个 [动态导入](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) 将被模拟。 @@ -224,16 +204,8 @@ test('importing the next module imports mocked one', async () => { ### vi.mocked -```ts -function mocked( - object: T, - deep?: boolean -): MaybeMockedDeep -function mocked( - object: T, - options?: { partial?: boolean, deep?: boolean } -): MaybePartiallyMockedDeep -``` +- **类型**: `(obj: T, deep?: boolean) => MaybeMockedDeep` +- **类型**: `(obj: T, options?: { partial?: boolean; deep?: boolean }) => MaybePartiallyMockedDeep` TypeScript 的类型助手。只返回传入的对象。 @@ -270,9 +242,7 @@ test('mock return value with only partially correct typing', async () => { ### vi.importActual -```ts -function importActual(path: string): Promise -``` +- **类型**: `(path: string) => Promise` 导入模块,绕过模块是否应被模拟的所有检查。如果我们想部分模拟模块,这一点很有用。 @@ -286,25 +256,19 @@ vi.mock('./example.js', async () => { ### vi.importMock -```ts -function importMock(path: string): Promise> -``` +- **类型**: `(path: string) => Promise>` -导入模块并模拟其所有属性(包括嵌套属性)。遵循与 [`vi.mock`](#vi-mock) 相同的规则。有关应用的规则,请参阅[算法](/guide/mocking/modules#automocking-algorithm)。 +导入模块并模拟其所有属性(包括嵌套属性)。遵循与 [`vi.mock`](#vi-mock) 相同的规则。有关应用的规则,请参阅 [模块](/mocking.html#automocking-algorithm)。 ### vi.unmock -```ts -function unmock(path: string | Promise): void -``` +- **类型**: `(path: string | Promise) => void` 从模拟注册表中删除模块。所有导入调用都将返回原始模块,即使该模块之前已被模拟。该调用会被移动到文件顶端,因此只会解除在 `setupFiles` 中定义的模块。 ### vi.doUnmock -```ts -function doUnmock(path: string | Promise): void -``` +- **类型**: `(path: string | Promise) => void` 与 [`vi.unmock`](#vi-unmock) 相同,但不会移动到文件顶端。下一次导入模块时,将导入原始模块而非 mock。这不会解除先前导入的模块。 @@ -343,9 +307,7 @@ unmockedIncrement(30) === 31 ### vi.resetModules -```ts -function resetModules(): Vitest -``` +- **类型**: `() => Vitest` 通过清除所有模块的缓存来重置模块注册表。这样就可以在重新导入模块时对模块进行重新评估。顶层导入无法重新评估。这可能有助于隔离测试之间存在本地状态冲突的模块。 @@ -376,10 +338,6 @@ test('module has old state', async () => { ### vi.dynamicImportSettled -```ts -function dynamicImportSettled(): Promise -``` - 等待加载所有导入模块。如果有同步调用开始导入一个模块,而如果不这样做就无法等待,那么它就很有用。 ```ts @@ -411,9 +369,7 @@ test('operations are resolved', async () => { ### vi.fn -```ts -function fn(fn?: Procedure | Constructable): Mock -``` +- **类型:** `(fn?: Function) => Mock` 创建函数的监视程序,但也可以不创建监视程序。每次调用函数时,它都会存储调用参数、返回值和实例。此外,我们还可以使用 [methods](/api/mock) 操纵它的行为。 如果没有给出函数,调用 mock 时将返回 `undefined`。 @@ -433,32 +389,17 @@ expect(res).toBe(5) expect(getApples).toHaveNthReturnedWith(2, 5) ``` -`vi.fn` 同样支持传入 class 作为参数: - -```ts -const Cart = vi.fn( - class { - get = () => 0 - } -) - -const cart = new Cart() -expect(Cart).toHaveBeenCalled() -``` - ### vi.mockObject 3.2.0 -```ts -function mockObject(value: T): MaybeMockedDeep -``` +- **Type:** `(value: T) => MaybeMockedDeep` -它与 `vi.mock()` 模拟模块相同,深层模拟给定对象的属性和方法。详见[自动模拟](/guide/mocking.html#automocking-algorithm)。 +它与 `vi.mock()` 模拟模块相同,深层模拟给定对象的属性和方法。详见 [自动模拟](/guide/mocking.html#automocking-algorithm)。 ```ts const original = { simple: () => 'value', nested: { - method: () => 'real', + method: () => 'real' }, prop: 'foo', } @@ -475,69 +416,30 @@ expect(mocked.simple()).toBe('mocked') expect(mocked.nested.method()).toBe('mocked nested') ``` -就像 `vi.mock()` 一样,可以传递 `{ spy: true }` 作为第二个参数,以保持函数实现: - -```ts -const spied = vi.mockObject(original, { spy: true }) -expect(spied.simple()).toBe('value') -expect(spied.simple).toHaveBeenCalled() -expect(spied.simple.mock.results[0]).toEqual({ - type: 'return', - value: 'value', -}) -``` - ### vi.isMockFunction -```ts -function isMockFunction(fn: unknown): asserts fn is Mock -``` +- **类型:** `(fn: Function) => boolean` 检查给定参数是否为 mock 函数。如果使用的是 TypeScript ,它还会缩小参数类型的范围。 ### vi.clearAllMocks -```ts -function clearAllMocks(): Vitest -``` - 对所有 spies 调用 [`.mockClear()`](/api/mock#mockclear)。 这将清除模拟的历史记录,但不影响模拟的实现。 ### vi.resetAllMocks -```ts -function resetAllMocks(): Vitest -``` - 对所有 spies 调用 [`.mockReset()`](/api/mock#mockreset)。 这将清除模拟的历史记录,并将每个模拟的实现重置为其原始状态。 ### vi.restoreAllMocks -```ts -function restoreAllMocks(): Vitest -``` - -该方法会一次性恢复所有由 [`vi.spyOn`](#vi-spyon) 创建的 spy 的原始实现。 - -一旦完成还原,即可重新对其进行监视。 - -::: warning -该方法同样不会触及 [automocking](/guide/mocking/modules#mocking-a-module) 期间生成的任何 mock。 - -注意:与 [`mock.mockRestore`](/api/mock#mockrestore) 不同,`vi.restoreAllMocks` 既不会清空调用历史,也不会重置 mock 的实现。 -::: +对所有 spies 调用 [`.mockRestore()`](/api/mock#mockrestore)。 +这将清除模拟的历史记录,恢复所有原始模拟实现,并恢复被监视对象的原始描述符。 ### vi.spyOn -```ts -function spyOn( - object: T, - key: K, - accessor?: 'get' | 'set' -): Mock -``` +- **类型:** `(object: T, method: K, accessType?: 'get' | 'set') => MockInstance` 创建与 [`vi.fn()`](#vi-fn) 类似的对象的方法或 getter/setter 的监听(spy) 。它会返回一个 [mock 函数](/api/mock) 。 @@ -556,36 +458,6 @@ expect(spy).toHaveBeenCalled() expect(spy).toHaveReturnedWith(1) ``` -若被监视的方法为类定义,则 mock 实现必须使用 `function` 或 `class` 关键字。 - -```ts {12-14,16-20} -const cart = { - Apples: class Apples { - getApples() { - return 42 - } - }, -} - -const spy = vi - .spyOn(cart, 'Apples') - .mockImplementation(() => ({ getApples: () => 0 })) // [!code --] - // 使用函数关键字 - .mockImplementation(function () { - this.getApples = () => 0 - }) - // 使用自定义类 - .mockImplementation( - class MockApples { - getApples() { - return 0 - } - } - ) -``` - -如果传入箭头函数, mock 被调用时将抛出 [` is not a constructor` 错误](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Not_a_constructor)。 - ::: tip 若运行环境支持 [显式资源管理](https://github.com/tc39/proposal-explicit-resource-management) ,可将 `const` 替换为 `using`。离开当前块级作用域时,系统会自动对被 mock 的函数调用 `mockRestore`,特别适用于已打 spy 的方法。 @@ -595,13 +467,12 @@ it('calls console.log', () => { debug('message') expect(spy).toHaveBeenCalled() }) -// console.log 在此处还原 +// console.log is restored here ``` - ::: ::: tip -在每个测试后,于 [`afterEach`](/api/#aftereach) 中调用 [`vi.restoreAllMocks`](#vi-restoreallmocks) 或开启配置项 [`test.restoreMocks`](/config/#restoreMocks),即可将所有方法还原为原始实现。此操作会恢复其 [对象描述符](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty),除非重新对其进行 spy ,否则无法再次修改方法实现。 +你可以在 [`afterEach`](/api/#aftereach)(或启用 [`test.restoreMocks`](/config/#restoreMocks) )中调用 [`vi.restoreAllMocks`](#vi-restoreallmocks) ,将所有方法还原为原始实现。这将还原原始的 [object descriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) ,因此无法更改方法的实现: ```ts const cart = { @@ -614,7 +485,7 @@ console.log(cart.getApples()) // 10 vi.restoreAllMocks() console.log(cart.getApples()) // 42 spy.mockReturnValue(10) -console.log(cart.getApples()) // 仍然为 42! +console.log(cart.getApples()) // still 42! ``` ::: @@ -638,12 +509,7 @@ expect(calculator).toHaveReturned(3) ### vi.stubEnv {#vi-stubenv} -```ts -function stubEnv( - name: T, - value: T extends 'PROD' | 'DEV' | 'SSR' ? boolean : string | undefined -): Vitest -``` +- **类型:** `(name: T, value: T extends "PROD" | "DEV" | "SSR" ? boolean : string | undefined) => Vitest` 更改 `process.env` 和 `import.meta.env` 中环境变量的值。我们可以调用 `vi.unstubAllEnvs` 恢复其值。 @@ -678,9 +544,7 @@ import.meta.env.MODE = 'test' ### vi.unstubAllEnvs {#vi-unstuballenvs} -```ts -function unstubAllEnvs(): Vitest -``` +- **类型:** `() => Vitest` 恢复通过 `vi.stubEnv` 更改的所有 `import.meta.env` 和 `process.env` 值。首次调用时,Vitest 会记住并保存原始值,直到再次调用 `unstubAllEnvs`。 @@ -709,9 +573,7 @@ import.meta.env.NODE_ENV === 'development' ### vi.stubGlobal -```ts -function stubGlobal(name: string | number | symbol, value: unknown): Vitest -``` +- **类型:** `(name: string | number | symbol, value: unknown) => Vitest` 更改全局变量的值。我们可以调用 `vi.unstubAllGlobals` 恢复其原始值。 @@ -729,7 +591,7 @@ window.innerWidth === 100 ``` :::tip -我们也可以通过简单地将其赋值给 `globalThis` 或 `window`(如果你正在使用 `jsdom` 或 `happy-dom` 环境)来更改该值,但无法使用 `vi.unstubAllGlobals` 恢复原始值: +我们也可以通过简单地将其赋值给 `globalThis` 或 `window`(如果我们使用的是 `jsdom` 或 `happy-dom` 环境)来更改该值,但无法使用 `vi.unstubAllGlobals` 恢复原始值: ```ts globalThis.innerWidth = 100 @@ -741,11 +603,9 @@ window.innerWidth = 100 ### vi.unstubAllGlobals {#vi-unstuballglobals} -```ts -function unstubAllGlobals(): Vitest -``` +- **类型:** `() => Vitest` -恢复 `globalThis` / `global`(和 `window` / `top` / `self` / `parent `,如果我们使用的是 `jsdom` 或 `happy-dom` 环境)上所有被 `vi.stubGlobal` 更改过的全局值。第一次调用时,Vitest 会记住并保存原始值,直到再次调用 `unstubAllGlobals`。 +恢复 `globalThis`/`global`(和 `window`/`top`/`self`/`parent`,如果我们使用的是 `jsdom` 或 `happy-dom` 环境)上所有被 `vi.stubGlobal` 更改过的全局值。第一次调用时,Vitest 会记住并保存原始值,直到再次调用 `unstubAllGlobals`。 ```ts import { vi } from 'vitest' @@ -772,13 +632,11 @@ IntersectionObserver === undefined ## Fake Timers -本节介绍如何使用 [fake timers](/guide/mocking/timers) 。 +本节介绍如何使用 [模拟计时器](/guide/mocking.html#timers)。 ### vi.advanceTimersByTime -```ts -function advanceTimersByTime(ms: number): Vitest -``` +- **类型:** `(ms: number) => Vitest` 该方法将调用每个启动的定时器,直到超过指定的毫秒数或队列为空(以先到者为准)。 @@ -795,9 +653,7 @@ vi.advanceTimersByTime(150) ### vi.advanceTimersByTimeAsync -```ts -function advanceTimersByTimeAsync(ms: number): Promise -``` +- **类型:** `(ms: number) => Promise` 该方法将调用每个已启动的定时器,直到超过指定的毫秒数或队列为空(以先到者为准)。这将包括异步设置的计时器。 @@ -814,9 +670,7 @@ await vi.advanceTimersByTimeAsync(150) ### vi.advanceTimersToNextTimer -```ts -function advanceTimersToNextTimer(): Vitest -``` +- **类型:** `() => Vitest` 将调用下一个可用的定时器。在每次调用定时器之间进行断言非常有用。我们可以调用它来管理自己的定时器。 @@ -831,9 +685,7 @@ vi.advanceTimersToNextTimer() // 输出: 1 ### vi.advanceTimersToNextTimerAsync -```ts -function advanceTimersToNextTimerAsync(): Promise -``` +- **类型:** `() => Promise` 如果定时器是异步设置的,则会调用下一个可用的定时器并等待解决。在每次调用定时器之间进行断言非常有用。 @@ -848,11 +700,9 @@ await vi.advanceTimersToNextTimerAsync() // log: 2 await vi.advanceTimersToNextTimerAsync() // log: 3 ``` -### vi.advanceTimersToNextFrame +### vi.advanceTimersToNextFrame 2.1.0 {#vi-advancetimerstonextframe} -```ts -function advanceTimersToNextFrame(): Vitest -``` +- **类型:** `() => Vitest` 与 [`vi.advanceTimersByTime`](https://vitest.dev/api/vi#vi-advancetimersbytime) 类似,但会将计时器推进当前使用 `requestAnimationFrame` 安排的回调执行所需的毫秒数。 @@ -870,49 +720,35 @@ expect(frameRendered).toBe(true) ### vi.getTimerCount -```ts -function getTimerCount(): number -``` +- **类型:** `() => number` 获取等待计时器的数量。 ### vi.clearAllTimers -```ts -function clearAllTimers(): void -``` - -立即取消所有已排程的计时器,使其不再执行。 +删除所有计划运行的计时器。这些定时器今后将不再运行。 ### vi.getMockedSystemTime -```ts -function getMockedSystemTime(): Date | null -``` +- **类型**: `() => Date | null` 返回模拟的当前日期。如果没有模拟日期,该方法将返回 `null`。 ### vi.getRealSystemTime -```ts -function getRealSystemTime(): number -``` +- **类型**: `() => number` 使用 `vi.useFakeTimers` 时,会模拟 `Date.now` 调用。如果需要以毫秒为单位获取实时时间,可以调用此函数。 ### vi.runAllTicks -```ts -function runAllTicks(): Vitest -``` +- **类型:** `() => Vitest` 调用由 `process.nextTick` 排在队列中的每个微任务。这也将运行所有自己安排的微任务。 ### vi.runAllTimers -```ts -function runAllTimers(): Vitest -``` +- **类型:** `() => Vitest` 该方法将调用每个已经启动的定时器,直到定时器队列为空。这意味着在 `runAllTimers` 期间调用的每个定时器都会被触发。如果时间间隔为无限,则会在尝试 10000 次后触发(可使用 [`fakeTimers.loopLimit`](/config/#faketimers-looplimit) 进行配置)。 @@ -935,9 +771,7 @@ vi.runAllTimers() ### vi.runAllTimersAsync -```ts -function runAllTimersAsync(): Promise -``` +- **类型:** `() => Promise` 该方法将异步调用每个已启动的定时器,直到定时器队列为空。这意味着在 `runAllTimersAsync` 期间调用的每个定时器都会被触发,即使是异步定时器。如果我们有一个无限的时间间隔、 会在尝试 10000 次后抛出(可使用 [`fakeTimers.loopLimit`](/config/#faketimers-looplimit) )。 @@ -954,9 +788,7 @@ await vi.runAllTimersAsync() ### vi.runOnlyPendingTimers -```ts -function runOnlyPendingTimers(): Vitest -``` +- **类型:** `() => Vitest` 此方法将调用 [`vi.useFakeTimers`](#vii-usefaketimers) 调用后启动的所有计时器。它不会调用在调用期间启动的任何计时器。 @@ -971,9 +803,7 @@ vi.runOnlyPendingTimers() ### vi.runOnlyPendingTimersAsync -```ts -function runOnlyPendingTimersAsync(): Promise -``` +- **类型:** `() => Promise` 此方法将异步调用 [`vi.useFakeTimers`](#vi-usefaketimers) 调用后启动的每个定时器,即使是异步定时器。它不会触发任何在调用期间启动的定时器。 @@ -1000,13 +830,11 @@ await vi.runOnlyPendingTimersAsync() ### vi.setSystemTime -```ts -function setSystemTime(date: string | number | Date): Vitest -``` +- **类型**: `(date: string | number | Date) => void` 如果启用了伪计时器,此方法将模拟用户更改系统时钟(将影响与日期相关的 API,如 `hrtime` 、`performance.now` 或 `new Date()` ),但不会触发任何计时器。如果未启用假定时器,该方法将仅模拟 `Date.*` 调用。 -如果我们需要测试任何依赖于当前日期的内容 -- 例如在代码中调用 [luxon](https://github.com/moment/luxon/) --则非常有用。 +适用于需要测试依赖当前日期的场景,例如代码中的 [Luxon](https://github.com/moment/luxon/) 库调用。 接受与 `Date` 相同的字符串和数字参数。 @@ -1023,9 +851,7 @@ vi.useRealTimers() ### vi.useFakeTimers -```ts -function useFakeTimers(config?: FakeTimerInstallOpts): Vitest -``` +- **类型:** `(config?: FakeTimerInstallOpts) => Vitest` 要启用模拟定时器,需要调用此方法。在调用 [`vi.useRealTimers()`](#vi-userealtimers) 之前,它将封装所有对定时器的进一步调用(如 `setTimeout` 、`setInterval` 、`clearTimeout` 、`clearInterval` 、`setImmediate` 、`clearImmediate` 和 `Date`)。 @@ -1040,32 +866,23 @@ function useFakeTimers(config?: FakeTimerInstallOpts): Vitest ### vi.isFakeTimers {#vi-isfaketimers} -```ts -function isFakeTimers(): boolean -``` +- **类型:** `() => boolean` -如果启用了假计时器,则返回 `true` 。 +如果启用了模拟计时器,则返回 `true` 。 ### vi.useRealTimers -```ts -function useRealTimers(): Vitest -``` +- **类型:** `() => Vitest` 当定时器用完后,我们可以调用此方法将模拟的计时器返回到其原始实现。之前调度的计时器都将被丢弃。 -## 辅助函数{#miscellaneous} +## Miscellaneous Vitest 提供的一组有用的辅助函数。 ### vi.waitFor {#vi-waitfor} -```ts -function waitFor( - callback: WaitForCallback, - options?: number | WaitForOptions -): Promise -``` +- **类型:** `(callback: WaitForCallback, options?: number | WaitForOptions) => Promise` 等待回调成功执行。如果回调抛出错误或返回拒绝的承诺,它将继续等待,直到成功或超时。 @@ -1126,20 +943,15 @@ test('Element exists in a DOM', async () => { }) ``` -一旦通过 `vi.useFakeTimers` 启用假计时器,`vi.waitFor` 将在每次轮询时自动调用 `vi.advanceTimersByTime(interval)` 推进时间。 +如果使用了 `vi.useFakeTimers` , `vi.waitFor` 会在每次检查回调中自动调用 `vi.advanceTimersByTime(interval)` 。 -### vi.waitUntil {#vi-waituntil} +### vi.waitUntil -```ts -function waitUntil( - callback: WaitUntilCallback, - options?: number | WaitUntilOptions -): Promise -``` +- **类型:** `(callback: WaitUntilCallback, options?: number | WaitUntilOptions) => Promise` -与 `vi.waitFor` 类似,但若回调抛出错误会立即中断并给出报错;若回调返回假值,则持续轮询直至返回真值。适用于“先等某物出现再行动”的场景。 +这与 `vi.waitFor` 类似,但如果回调抛出任何错误,执行将立即中断并收到一条错误信息。如果回调返回虚假值(falsy) ,下一次检查将继续,直到返回真实值(truthy) 。这在需要等待某项内容存在后再执行下一步时非常有用。 -下面的示例,我们可以使用 `vi.waitUntil` 等待元素出现在页面上,然后再对该元素进行操作。 +请看下面的示例。我们可以使用 `vi.waitUntil` 等待元素出现在页面上,然后对元素进行操作。 ```ts import { expect, test, vi } from 'vitest' @@ -1149,17 +961,15 @@ test('Element render correctly', async () => { timeout: 500, // 默认为 1000 interval: 20, // 默认为 50 }) - expect(element).toBeInstanceOf(HTMLElement) + + // 对元素执行操作 + expect(element.querySelector('.element-child')).toBeTruthy() }) ``` -如果使用了 `vi.useFakeTimers` , `vi.waitFor` 会在每次检查回调中自动调用 `vi.advanceTimersByTime(interval)` 。 - ### vi.hoisted {#vi-hoisted} -```ts -function hoisted(factory: () => T): T -``` +- **类型**: `(factory: () => T) => T` ES 模块中的所有静态 `import` 语句都被提升到文件顶部,因此在导入之前定义的任何代码都将在导入评估之后执行。 @@ -1187,9 +997,7 @@ import { value } from './some/module.js' ```ts import { value } from './some/module.js' -vi.hoisted(() => { - value -}) // 抛出一个错误 // [!code warning] +vi.hoisted(() => { value }) // 抛出一个错误 // [!code warning] ``` 此代码将产生错误: @@ -1238,9 +1046,7 @@ const json = await vi.hoisted(async () => { ### vi.setConfig -```ts -function setConfig(config: RuntimeOptions): void -``` +- **类型**: `RuntimeConfig` 更新当前测试文件的配置。此方法只会影响当前测试文件的配置选项: @@ -1253,20 +1059,18 @@ vi.setConfig({ restoreMocks: true, fakeTimers: { now: new Date(2021, 11, 19), - // 支持完整对象 + // supports the whole object }, maxConcurrency: 10, sequence: { hooks: 'stack', - // 仅支持 "sequence.hooks" + // supports only "sequence.hooks" }, }) ``` ### vi.resetConfig -```ts -function resetConfig(): void -``` +- **类型**: `RuntimeConfig` 如果之前调用过 [`vi.setConfig`](#vi-setconfig) ,则会将配置重置为原始状态。 From 463b30121b0df0367b0013726500ad9788d69cbd Mon Sep 17 00:00:00 2001 From: noise Date: Sun, 16 Nov 2025 21:49:28 +0800 Subject: [PATCH 10/50] !docs: recovery /api/vi --- api/vi.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/vi.md b/api/vi.md index 337f5a6e..fd28be9c 100644 --- a/api/vi.md +++ b/api/vi.md @@ -363,7 +363,7 @@ test('operations are resolved', async () => { 该方法还将在导入解析后等待下一个 `setTimeout` 跟他挂钩,因此所有同步操作都应在解析时完成。 ::: -## 模拟函数和对象 +## 模拟函数和对象 {#mocking-functions-and-objects} 本节介绍如何使用 [method mock](/api/mock) 替换环境变量和全局变量。 @@ -1059,12 +1059,12 @@ vi.setConfig({ restoreMocks: true, fakeTimers: { now: new Date(2021, 11, 19), - // supports the whole object + // 支持完整对象 }, maxConcurrency: 10, sequence: { hooks: 'stack', - // supports only "sequence.hooks" + // 仅支持 "sequence.hooks" }, }) ``` From 723b7d9b01244f2cd1348190621942af3019d4a6 Mon Sep 17 00:00:00 2001 From: noise Date: Tue, 18 Nov 2025 23:39:45 +0800 Subject: [PATCH 11/50] !docs: recovery /guide/filtering --- guide/filtering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/filtering.md b/guide/filtering.md index e6b9b858..be82a69d 100644 --- a/guide/filtering.md +++ b/guide/filtering.md @@ -4,7 +4,7 @@ title: 测试筛选 | 指南 # 测试筛选 {#test-filtering} -用于测试套件(suite)和测试的筛选(filtering)、超时(timeouts)、并发(concurrent)。 +筛选、超时和测试套件的并发。 ## CLI {#cli} From 2ec45b4074a4f3bb2825b41d6d1b564f499b2a80 Mon Sep 17 00:00:00 2001 From: noise Date: Tue, 18 Nov 2025 23:55:46 +0800 Subject: [PATCH 12/50] !docs: recovery /guide/projects --- guide/projects.md | 51 ++--------------------------------------------- 1 file changed, 2 insertions(+), 49 deletions(-) diff --git a/guide/projects.md b/guide/projects.md index 1c234a04..56086c92 100644 --- a/guide/projects.md +++ b/guide/projects.md @@ -30,7 +30,7 @@ export default defineConfig({ }) ``` -项目配置可以是内联配置、文件或指向项目的 glob 模式。例如,如果你有一个名为 `packages` 的文件夹包含多个项目,可以在根 Vitest 配置中定义一个数组: +项目配置可以是内联配置、文件或指向项目的 glob 模式。例如,如果你有一个名为 `packages` 的文件夹包含多个项目,可以在 Vitest 配置文件中定义一个数组: ```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' @@ -42,54 +42,7 @@ export default defineConfig({ }) ``` -Vitest 会将 `packages` 中的每个文件夹视为独立项目,即使其中没有配置文件。如果 glob 模式匹配到文件,它将验证文件名是否以 `vitest.config`/`vite.config` 开头,或匹配 `(vite|vitest).*.config.*` 模式,以确保它是 Vitest 配置文件。例如,以下配置文件是有效的: - -- `vitest.config.ts` -- `vite.config.js` -- `vitest.unit.config.ts` -- `vite.e2e.config.js` -- `vitest.config.unit.js` -- `vite.config.e2e.js` - -要排除文件夹和文件,你可以使用否定模式: - -```ts [vitest.config.ts] -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - // 包含 "packages" 所有子文件夹,并排除 "excluded" 文件夹 - projects: [ - 'packages/*', - '!packages/excluded' - ], - }, -}) -``` - -如果你有一个嵌套结构,其中某些文件夹需要成为项目,但其他文件夹有自己的子文件夹,你必须使用括号来避免匹配父文件夹: - -```ts [vitest.config.ts] -import { defineConfig } from 'vitest/config' - -// 举例来说,像下面这样创建项目: -// packages/a -// packages/b -// packages/business/c -// packages/business/d -// 注意:"packages/business" 并不是一个项目 - -export default defineConfig({ - test: { - projects: [ - // 匹配 "packages" 目录下除 "business" 所有子文件夹 - 'packages/!(business)', - // 匹配 "packages/business" 下所有子文件夹 - 'packages/business/*', - ], - }, -}) -``` +Vitest 会将 `packages` 中的每个文件夹视为独立项目,即使其中没有配置文件。如果该 glob 模式匹配到 _任意文件_,它将被视为 Vitest 配置,即使文件名中没有包含 `vitest` 或文件扩展名不常见。 ::: warning Vitest 不会将根目录的 `vitest.config` 文件视为项目,除非在配置中显式指定。因此,根配置只会影响全局选项,如 `reporters` 和 `coverage`。但 Vitest 总会执行根配置文件中指定的某些插件钩子,如 `apply`、`config`、`configResolved` 或 `configureServer`。Vitest 也会使用相同的插件执行全局设置和自定义覆盖提供者。 From 763a739c0f1c95fef6415db4155123fb295939bc Mon Sep 17 00:00:00 2001 From: noise Date: Wed, 19 Nov 2025 00:35:40 +0800 Subject: [PATCH 13/50] !docs: recovery /guide/snapshot --- guide/snapshot.md | 49 ++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/guide/snapshot.md b/guide/snapshot.md index eee9cfff..576e4f16 100644 --- a/guide/snapshot.md +++ b/guide/snapshot.md @@ -4,10 +4,10 @@ title: 测试快照 | 指南 # 测试快照 {#snapshot} -当你希望确保函数的输出不会意外更改时,快照测试是一个非常有用的工具。 - 通过 Vue School 的视频学习快照 +当你希望确保函数的输出不会意外更改时,快照测试是一个非常有用的工具。 + 使用快照时,Vitest 将获取给定值的快照,将其比较时将参考存储在测试旁边的快照文件。如果两个快照不匹配,则测试将失败:要么更改是意外的,要么参考快照需要更新到测试结果的新版本。 ## 使用快照 {#use-snapshots} @@ -39,7 +39,7 @@ exports['toUpperCase 1'] = '"FOOBAR"' 在异步并发测试中使用快照时,由于 JavaScript 的限制,你需要使用 [测试环境](/guide/test-context) 中的 `expect` 来确保检测到正确的测试。 ::: -如同前文,你可以使用 [`toMatchInlineSnapshot()`](/api/#tomatchinlinesnapshot) 将内联快照存储在测试文件中。 +同样,你可以使用 [`toMatchInlineSnapshot()`](/api/#tomatchinlinesnapshot) 将内联快照存储在测试文件中。 ```ts import { expect, it } from 'vitest' @@ -63,12 +63,12 @@ it('toUpperCase', () => { 这允许你直接查看期望输出,而无需跨不同的文件跳转。 -## 更新快照 {#updating-snapshots} - ::: warning -在异步并发测试中使用快照时,由于 JavaScript 的限制,你需要使用 [测试环境](/guide/test-context) 中的 `expect` 来确保检测到正确的测试。 +在异步并发测试中使用快照时,必须使用本地的 [Test Context](/guide/test-context) 中的 `expect` 方法,以确保正确检测到对应的测试。 ::: +## 更新快照 {#updating-snapshots} + 当接收到的值与快照不匹配时,测试将失败,并显示它们之间的差异。当需要更改快照时,你可能希望从当前状态更新快照。 在监听(watch)模式下, 你可以在终端中键入 `u` 键直接更新失败的快照。 @@ -81,7 +81,7 @@ vitest -u ## 文件快照 {#file-snapshots} -调用 `toMatchSnapshot()` 时,我们将所有快照存储在格式化的快照文件中。这意味着我们需要转义快照字符串中的一些字符(即双引号 `"` 和反引号 `\``)。同时,你可能会丢失快照内容的语法突出显示(如果它们是某种语言)。 +调用 `toMatchSnapshot()` 时,我们将所有快照存储在格式化的快照文件中。这意味着我们需要转义快照字符串中的一些字符(即双引号 `"` 和反引号 `` ` ``)。同时,你可能会丢失快照内容的语法突出显示(如果它们是某种语言)。 为了改善这种情况,我们引入 [`toMatchFileSnapshot()`](/api/expect#tomatchfilesnapshot) 以在文件中显式快照。这允许你为快照文件分配任何文件扩展名,并使它们更具可读性。 @@ -96,33 +96,38 @@ it('render basic', async () => { 它将与 `./test/basic.output.html` 的内容进行比较。并且可以用 `--update` 标志写回。 -## 图像快照 {#visual-snapshots} +## 图像快照 {#image-snapshots} -对于 UI 组件和页面的视觉回归测试,Vitest 通过[浏览器模式](/guide/browser/)提供了内置支持,使用 [`toMatchScreenshot()`](/guide/browser/assertion-api#tomatchscreenshot-experimental) 断言: +快照图像也可以使用 [`jest-image-snapshot`](https://github.com/americanexpress/jest-image-snapshot)。 -```ts -import { expect, test } from 'vitest' -import { page } from 'vitest/browser' +```bash +npm i -D jest-image-snapshot +``` -test('button looks correct', async () => { - const button = page.getByRole('button') - await expect(button).toMatchScreenshot('primary-button') +```ts +test('image snapshot', () => { + expect(readFileSync('./test/stubs/input-image.png')) + .toMatchImageSnapshot() }) ``` -它会捕获屏幕截图并与参考图像进行比较,以检测意外的视觉变化。在[视觉回归测试指南](/guide/browser/visual-regression-testing)中了解更多内容。 - ## 自定义序列化程序 {#custom-serializer} -你可以添加自己的逻辑来修改快照的序列化方式。像 Jest 一样,Vitest 为内置的 JavaScript 类型、HTML 元素、ImmutableJS 和 React 元素提供了默认的序列化程序。 +你可以添加自己的逻辑来修改快照的序列化方式。像 Jest 一样,Vitest 默认有内置的 JavaScript 类型、HTML 元素、ImmutableJS 和 React 元素提供了默认的序列化程序。 可以使用 [`expect.addSnapshotSerializer`](/api/expect#expect-addsnapshotserializer) 添加自定义序列器。 ```ts expect.addSnapshotSerializer({ serialize(val, config, indentation, depth, refs, printer) { - // `printer` is a function that serializes a value using existing plugins. - return `Pretty foo: ${printer(val.foo, config, indentation, depth, refs)}` + // `printer` 是一个通过现有插件对值进行序列化的函数。 + return `Pretty foo: ${printer( + val.foo, + config, + indentation, + depth, + refs + )}` }, test(val) { return val && Object.prototype.hasOwnProperty.call(val, 'foo') @@ -130,7 +135,7 @@ expect.addSnapshotSerializer({ }) ``` -我们还支持 [snapshotSerializers](/config/#snapshotserializers) 选项来隐式添加自定义序列化器。 +我们还支持 [snapshotSerializers](/config/#snapshotserializers) 选项,可以隐式添加自定义序列化器。 ```ts [path/to/custom-serializer.ts] import { SnapshotSerializer } from 'vitest' @@ -156,7 +161,7 @@ export default defineConfig({ }) ``` -如下所示的测试添加后: +添加类似的测试后: ```ts test('foo snapshot test', () => { From 0c652230120b936facd5aa5e11630d7602157ff2 Mon Sep 17 00:00:00 2001 From: noise Date: Wed, 19 Nov 2025 01:00:46 +0800 Subject: [PATCH 14/50] !docs: recovery /guide/coverage --- guide/coverage.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/guide/coverage.md b/guide/coverage.md index 47ab5c5d..8f4597aa 100644 --- a/guide/coverage.md +++ b/guide/coverage.md @@ -49,7 +49,7 @@ npm i -D @vitest/coverage-istanbul 这让用户在享受 V8 覆盖率高速执行的同时,也能获得 Istanbul 覆盖率的高准确度。 ::: -Vitest 默认采用 'v8' 作为覆盖率提供器。 +Vitest 默认采用 `v8` 作为覆盖率提供器。 此提供器依赖于基于 [V8 引擎](https://v8.dev/) 的 JavaScript 运行环境,比如 NodeJS、Deno,或者 Google Chrome 等 Chromium 内核的浏览器。 覆盖率收集是在程序运行时完成的,通过 [`node:inspector`](https://nodejs.org/api/inspector.html) 模块以及浏览器中的 [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/) 协议 与 V8 交互即可实现。这样,用户的源码可以直接被执行,而不需要事先进行插桩处理。 @@ -265,7 +265,7 @@ export default CustomCoverageProviderModule 请参阅类型定义查看有关详细信息。 -## 更改默认覆盖文件夹位置 {#changing-the-default-coverage-folder-location} +## 更改默认覆盖率报告文件夹位置 {#changing-the-default-coverage-folder-location} 运行覆盖率报告时,会在项目的根目录中创建一个 `coverage` 文件夹。 如果你想将它移动到不同的目录,请使用 `vite.config.js` 文件中的 `test.coverage.reportsDirectory` 属性。 @@ -311,13 +311,13 @@ if (condition) { ## 覆盖率性能 {#coverage-performance} -如果你的项目中代码覆盖率生成较慢,请参阅[性能测试分析 | 代码覆盖率](/guide/profiling-test-performance.html#code-coverage)。 +如果你的项目中代码覆盖率生成较慢,请参阅 [性能测试分析 | 代码覆盖率](/guide/profiling-test-performance.html#code-coverage)。 -## Vitest UI {#vitest-ui} +## UI 模式 {#vitest-ui} -我们可以在 [Vitest UI](/guide/ui) 中查看你的覆盖率报告。 +我们可以在 [UI 模式](/guide/ui) 中查看你的覆盖率报告。 -Vitest UI 会在以下情况下启用覆盖率报告: +UI 模式 会在以下情况下启用覆盖率报告: - 显式启用覆盖率报告:在配置文件中设置 `coverage.enabled=true` ,或运行 Vitest 时添加 `--coverage.enabled=true` 标志。 - 添加 HTML 报告器:将 `html` 添加到 `coverage.reporter` 列表中,我们还可以启用 `subdir` 选项,将覆盖率报告放在子目录中。 From dae59a29ddff77d1148ee2ddae0cfac64b004341 Mon Sep 17 00:00:00 2001 From: noise Date: Wed, 19 Nov 2025 01:05:40 +0800 Subject: [PATCH 15/50] !docs: recovery /guide/ui --- guide/ui.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/ui.md b/guide/ui.md index ff2552f8..3936397a 100644 --- a/guide/ui.md +++ b/guide/ui.md @@ -37,7 +37,7 @@ export default defineConfig({ }) ``` -你可以在 Vitest UI 中查看覆盖率报告:查看 [Vitest UI 覆盖率](/guide/coverage#vitest-ui) 了解更多详情。 +你可以在 Vitest UI 中查看覆盖率报告:查看 [覆盖率 | UI 模式](/guide/coverage#vitest-ui) 了解更多详情。 ::: warning 如果你仍想在终端中实时查看测试的运行情况,请不要忘记将 `default` 报告器添加到 `reporters` 选项:`['default', 'html']`。 From bc56adedc17421d6020e90832f9abc51bb8de0ff Mon Sep 17 00:00:00 2001 From: noise Date: Wed, 19 Nov 2025 02:17:02 +0800 Subject: [PATCH 16/50] !docs: recovery /guide/cli --- .vitepress/config.ts | 2 +- .vitepress/scripts/cli-generator.ts | 2 +- guide/browser/config.md | 14 +- guide/cli-generated.md | 552 ++++++++++++++-------------- guide/cli.md | 4 +- 5 files changed, 294 insertions(+), 280 deletions(-) diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 980e4042..d5e07c8a 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -622,7 +622,7 @@ function guide(): DefaultTheme.SidebarItem[] { link: '/guide/testing-types', }, { - text: 'UI模式', + text: 'UI 模式', link: '/guide/ui', }, { diff --git a/.vitepress/scripts/cli-generator.ts b/.vitepress/scripts/cli-generator.ts index bea3ade8..642b9aba 100644 --- a/.vitepress/scripts/cli-generator.ts +++ b/.vitepress/scripts/cli-generator.ts @@ -77,7 +77,7 @@ const template = options.map((option) => { const title = option.title const cli = option.cli const config = skipConfig.has(title) ? '' : `[${title}](${title.includes('browser.') ? '/guide/browser/config' : '/config/'}#${title.toLowerCase().replace(/\./g, '-')})` - return `### ${title}\n\n- **CLI:** ${cli}\n${config ? `- **Config:** ${config}\n` : ''}\n${option.description}\n` + return `### ${title}\n\n- **命令行终端:** ${cli}\n${config ? `- **配置:** ${config}\n` : ''}\n${option.description}\n` }).join('\n') writeFileSync(cliTablePath, template, 'utf-8') diff --git a/guide/browser/config.md b/guide/browser/config.md index f5830437..381e8275 100644 --- a/guide/browser/config.md +++ b/guide/browser/config.md @@ -40,7 +40,7 @@ export default defineConfig({ - **类型:** `boolean` - **默认值:** `false` -- **CLI:** `--browser`, `--browser.enabled=false` +- **命令行终端:** `--browser`, `--browser.enabled=false` 默认情况下在浏览器中运行所有测试。请注意,`--browser` 仅在我们至少有一个 [`browser.instances`](#browser-instances) 项时有效。 @@ -95,7 +95,7 @@ export default defineConfig({ - **类型:** `boolean` - **默认值:** `process.env.CI` -- **CLI:** `--browser.headless`, `--browser.headless=false` +- **命令行终端:** `--browser.headless`, `--browser.headless=false` 在 `headless` 模式下运行浏览器。如果我们在 CI 中运行 Vitest,则默认启用此模式。 @@ -103,7 +103,7 @@ export default defineConfig({ - **类型:** `boolean` - **默认值:** `true` -- **CLI:** `--browser.isolate`, `--browser.isolate=false` +- **命令行终端:** `--browser.isolate`, `--browser.isolate=false` 在单独的 iframe 中运行每个测试。 @@ -117,7 +117,7 @@ HTML 入口点的路径。可以是相对于项目根目录的路径。此文件 - **类型:** `number | { port?, strictPort?, host? }` - **默认值:** `63315` -- **CLI:** `--browser.api=63315`, `--browser.api.port=1234, --browser.api.host=example.com` +- **命令行终端:** `--browser.api=63315`, `--browser.api.port=1234, --browser.api.host=example.com` 配置为浏览器提供代码的 Vite 服务器的选项。不影响 [`test.api`](#api) 选项。默认情况下,Vitest 分配端口 `63315` 以避免与开发服务器冲突,允许我们同时运行两者。 @@ -125,7 +125,7 @@ HTML 入口点的路径。可以是相对于项目根目录的路径。此文件 - **类型:** `BrowserProviderOption` - **默认值:** `'preview'` -- **CLI:** `--browser.provider=playwright` +- **命令行终端:** `--browser.provider=playwright` 提供者工厂的返回值。你可以从 `@vitest/browser-` 导入工厂函数,或者创建自己的提供者: @@ -206,7 +206,7 @@ export interface BrowserProvider { - **类型:** `boolean` - **默认值:** `!isCI` -- **CLI:** `--browser.ui=false` +- **命令行终端:** `--browser.ui=false` 是否应将 Vitest UI 注入页面。默认情况下,在开发期间注入 UI iframe。 @@ -303,7 +303,7 @@ export interface BrowserScript { ## browser.trace - **类型:** `'on' | 'off' | 'on-first-retry' | 'on-all-retries' | 'retain-on-failure' | object` -- **CLI:** `--browser.trace=on`, `--browser.trace=retain-on-failure` +- **命令行终端:** `--browser.trace=on`, `--browser.trace=retain-on-failure` - **Default:** `'off'` Capture a trace of your browser test runs. You can preview traces with [Playwright Trace Viewer](https://trace.playwright.dev/). diff --git a/guide/cli-generated.md b/guide/cli-generated.md index 9ca5fc3c..4cb93af6 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -1,904 +1,918 @@ ### root -- **CLI:** `-r, --root ` -- **Config:** [root](/config/#root) +- **命令行终端:** `-r, --root ` +- **配置:** [root](/config/#root) 根路径 ### config -- **CLI:** `-c, --config ` +- **命令行终端:** `-c, --config ` 配置文件的路径 ### update -- **CLI:** `-u, --update` -- **Config:** [update](/config/#update) +- **命令行终端:** `-u, --update` +- **配置:** [update](/config/#update) 更新快照 ### watch -- **CLI:** `-w, --watch` -- **Config:** [watch](/config/#watch) +- **命令行终端:** `-w, --watch` +- **配置:** [watch](/config/#watch) 启用观察模式 ### testNamePattern -- **CLI:** `-t, --testNamePattern ` -- **Config:** [testNamePattern](/config/#testnamepattern) +- **命令行终端:** `-t, --testNamePattern ` +- **配置:** [testNamePattern](/config/#testnamepattern) 使用符合指定 regexp 模式的运行测试 ### dir -- **CLI:** `--dir ` -- **Config:** [dir](/config/#dir) +- **命令行终端:** `--dir ` +- **配置:** [dir](/config/#dir) 扫描测试文件的基本目录 ### ui -- **CLI:** `--ui` -- **Config:** [ui](/config/#ui) +- **命令行终端:** `--ui` +- **配置:** [ui](/config/#ui) -启用UI +启用 UI 模式 ### open -- **CLI:** `--open` -- **Config:** [open](/config/#open) +- **命令行终端:** `--open` +- **配置:** [open](/config/#open) 自动打开用户界面(默认值:`!process.env.CI`)。 ### api.port -- **CLI:** `--api.port [port]` +- **命令行终端:** `--api.port [port]` 指定服务器端口。注意,如果端口已被使用,Vite 会自动尝试下一个可用端口,因此这可能不是服务器最终监听的实际端口。如果为 `true`,将设置为`51204` ### api.host -- **CLI:** `--api.host [host]` +- **命令行终端:** `--api.host [host]` 指定服务器应该监听哪些 IP 地址。设为 `0.0.0.0` 或 `true` 则监听所有地址,包括局域网地址和公共地址 ### api.strictPort -- **CLI:** `--api.strictPort` +- **命令行终端:** `--api.strictPort` 设置为 true 时,如果端口已被使用,则退出,而不是自动尝试下一个可用端口 ### silent -- **CLI:** `--silent [value]` -- **Config:** [silent](/config/#silent) +- **命令行终端:** `--silent [value]` +- **配置:** [silent](/config/#silent) 测试的静默控制台输出。使用 `'passed-only'` 仅查看失败测试的日志。 ### hideSkippedTests -- **CLI:** `--hideSkippedTests` +- **命令行终端:** `--hideSkippedTests` 隐藏跳过测试的日志 ### reporters -- **CLI:** `--reporter ` -- **Config:** [reporters](/config/#reporters) +- **命令行终端:** `--reporter ` +- **配置:** [reporters](/config/#reporters) 指定报告器(default、blob、verbose、dot、json、tap、tap-flat、junit、tree、hanging-process、github-actions) ### outputFile -- **CLI:** `--outputFile ` -- **Config:** [outputFile](/config/#outputfile) +- **命令行终端:** `--outputFile ` +- **配置:** [outputFile](/config/#outputfile) -如果还指定了支持报告程序,则将测试结果写入文件,使用 cac 的点符号表示多个报告程序的单个输出结果 (比如: --outputFile.tap=./tap.txt) +如果还指定了支持报告程序,则将测试结果写入文件,使用 cac 的点符号表示多个报告程序的单个输出结果 (比如: `--outputFile.tap=./tap.txt`) + +### coverage.all + +- **命令行终端:** `--coverage.all` +- **配置:** [coverage.all](/config/#coverage-all) + +是否将所有文件,包括未测试的文件,都纳入报告中。 ### coverage.provider -- **CLI:** `--coverage.provider ` -- **Config:** [coverage.provider](/config/#coverage-provider) +- **命令行终端:** `--coverage.provider ` +- **配置:** [coverage.provider](/config/#coverage-provider) 选择覆盖范围采集工具, 可用值为: "v8", "istanbul" and "custom" ### coverage.enabled -- **CLI:** `--coverage.enabled` -- **Config:** [coverage.enabled](/config/#coverage-enabled) +- **命令行终端:** `--coverage.enabled` +- **配置:** [coverage.enabled](/config/#coverage-enabled) 启用覆盖范围收集。可使用 `--coverage` CLI 选项覆盖(默认值:`false`)。 ### coverage.include -- **CLI:** `--coverage.include ` -- **Config:** [coverage.include](/config/#coverage-include) +- **命令行终端:** `--coverage.include ` +- **配置:** [coverage.include](/config/#coverage-include) -作为通配符模式包含在覆盖率中的文件。在使用多个模式时可以指定多次。默认情况下,只包含被测试覆盖的文件。 +覆盖范围中要包含的文件。使用多个扩展名时,可指定多次(默认值:`**`)。 ### coverage.exclude -- **CLI:** `--coverage.exclude ` -- **Config:** [coverage.exclude](/config/#coverage-exclude) +- **命令行终端:** `--coverage.exclude ` +- **配置:** [coverage.exclude](/config/#coverage-exclude) + +覆盖范围中要排除的文件。使用多个扩展名时,可指定多次(默认情况下: 访问 [`coverage.exclude`](https://vitest.dev/config/#coverage-exclude) + +### coverage.extension -覆盖范围中要排除的文件。使用多个扩展名时,可指定多次。 +- **命令行终端:** `--coverage.extension ` +- **配置:** [coverage.extension](/config/#coverage-extension) + +包含在覆盖范围内的扩展名。使用多个扩展名时,可指定多次 (默认值: `[".js", ".cjs", ".mjs", ".ts", ".mts", ".tsx", ".jsx", ".vue", ".svelte"]`) ### coverage.clean -- **CLI:** `--coverage.clean` -- **Config:** [coverage.clean](/config/#coverage-clean) +- **命令行终端:** `--coverage.clean` +- **配置:** [coverage.clean](/config/#coverage-clean) 运行测试前清除覆盖结果(默认值:true) ### coverage.cleanOnRerun -- **CLI:** `--coverage.cleanOnRerun` -- **Config:** [coverage.cleanOnRerun](/config/#coverage-cleanonrerun) +- **命令行终端:** `--coverage.cleanOnRerun` +- **配置:** [coverage.cleanOnRerun](/config/#coverage-cleanonrerun) 重新运行监视时清理覆盖率报告(默认值:true) ### coverage.reportsDirectory -- **CLI:** `--coverage.reportsDirectory ` -- **Config:** [coverage.reportsDirectory](/config/#coverage-reportsdirectory) +- **命令行终端:** `--coverage.reportsDirectory ` +- **配置:** [coverage.reportsDirectory](/config/#coverage-reportsdirectory) 将覆盖率报告写入的目录(默认值: ./coverage) ### coverage.reporter -- **CLI:** `--coverage.reporter ` -- **Config:** [coverage.reporter](/config/#coverage-reporter) +- **命令行终端:** `--coverage.reporter ` +- **配置:** [coverage.reporter](/config/#coverage-reporter) 使用的报告。更多信息请访问 [`coverage.reporter`](https://vitest.dev/config/#coverage-reporter)。 (默认值: `["text", "html", "clover", "json"]`) ### coverage.reportOnFailure -- **CLI:** `--coverage.reportOnFailure` -- **Config:** [coverage.reportOnFailure](/config/#coverage-reportonfailure) +- **命令行终端:** `--coverage.reportOnFailure` +- **配置:** [coverage.reportOnFailure](/config/#coverage-reportonfailure) 即使测试失败也能生成覆盖率报告 (默认值: `false`) ### coverage.allowExternal -- **CLI:** `--coverage.allowExternal` -- **Config:** [coverage.allowExternal](/config/#coverage-allowexternal) +- **命令行终端:** `--coverage.allowExternal` +- **配置:** [coverage.allowExternal](/config/#coverage-allowexternal) 收集项目根目录外文件的覆盖范围(默认值:`false`) ### coverage.skipFull -- **CLI:** `--coverage.skipFull` -- **Config:** [coverage.skipFull](/config/#coverage-skipfull) +- **命令行终端:** `--coverage.skipFull` +- **配置:** [coverage.skipFull](/config/#coverage-skipfull) 不显示语句、分支和函数覆盖率为 100% 的文件(默认值:`false`) ### coverage.thresholds.100 -- **CLI:** `--coverage.thresholds.100` -- **Config:** [coverage.thresholds.100](/config/#coverage-thresholds-100) +- **命令行终端:** `--coverage.thresholds.100` +- **配置:** [coverage.thresholds.100](/config/#coverage-thresholds-100) 将所有覆盖率阈值设置为 100 的快捷方式(默认值:`false`) ### coverage.thresholds.perFile -- **CLI:** `--coverage.thresholds.perFile` -- **Config:** [coverage.thresholds.perFile](/config/#coverage-thresholds-perfile) +- **命令行终端:** `--coverage.thresholds.perFile` +- **配置:** [coverage.thresholds.perFile](/config/#coverage-thresholds-perfile) -查每个文件的阈值。 `--coverage.thresholds.lines`, `--coverage.thresholds.functions`, `--coverage.thresholds.branches`, `--coverage.thresholds.statements` 为实际阈值(默认值:`false`) +检查每个文件的阈值。 `--coverage.thresholds.lines`, `--coverage.thresholds.functions`, `--coverage.thresholds.branches`, `--coverage.thresholds.statements` 为实际阈值(默认值:`false`) ### coverage.thresholds.autoUpdate -- **CLI:** `--coverage.thresholds.autoUpdate ` -- **Config:** [coverage.thresholds.autoUpdate](/config/#coverage-thresholds-autoupdate) +- **命令行终端:** `--coverage.thresholds.autoUpdate ` +- **配置:** [coverage.thresholds.autoUpdate](/config/#coverage-thresholds-autoupdate) -更新阈值: 当当前覆盖率高于配置的阈值时,将 "lines"、"functions"、"branches"和 "statements"更新到配置文件(默认值:`false`) +更新阈值: 当前覆盖率高于配置的阈值时,将 "lines"、"functions"、"branches"和 "statements"更新到配置文件(默认值:`false`) ### coverage.thresholds.lines -- **CLI:** `--coverage.thresholds.lines ` +- **命令行终端:** `--coverage.thresholds.lines ` 针对代码行的覆盖度阈值设定,请访问 [istanbuljs](https://github.com/istanbuljs/nyc#coverage-thresholds) 了解更多信息。此选项不适用于自定义 providers ### coverage.thresholds.functions -- **CLI:** `--coverage.thresholds.functions ` +- **命令行终端:** `--coverage.thresholds.functions ` -针对函数的覆盖度阈值设定,请访问 [istanbuljs](https://github.com/istanbuljs/nyc#coverage-thresholds) 了解更多信息。 此选项不适用于自定义 providers +针对函数的覆盖度阈值设定,请访问 [istanbuljs](https://github.com/istanbuljs/nyc#coverage-thresholds) 了解更多信息。此选项不适用于自定义 providers ### coverage.thresholds.branches -- **CLI:** `--coverage.thresholds.branches ` +- **命令行终端:** `--coverage.thresholds.branches ` -针对 branches 的覆盖度阈值设定,请访问 [istanbuljs](https://github.com/istanbuljs/nyc#coverage-thresholds) 了解更多信息。 此选项不适用于自定义 providers +针对 branches 的覆盖度阈值设定,请访问 [istanbuljs](https://github.com/istanbuljs/nyc#coverage-thresholds) 了解更多信息。此选项不适用于自定义 providers ### coverage.thresholds.statements -- **CLI:** `--coverage.thresholds.statements ` +- **命令行终端:** `--coverage.thresholds.statements ` -针对 statements 的覆盖度阈值设定,请访问 [istanbuljs](https://github.com/istanbuljs/nyc#coverage-thresholds) 了解更多信息。 此选项不适用于自定义 providers +针对 statements 的覆盖度阈值设定,请访问 [istanbuljs](https://github.com/istanbuljs/nyc#coverage-thresholds) 了解更多信息。此选项不适用于自定义 providers ### coverage.ignoreClassMethods -- **CLI:** `--coverage.ignoreClassMethods ` -- **Config:** [coverage.ignoreClassMethods](/config/#coverage-ignoreclassmethods) +- **命令行终端:** `--coverage.ignoreClassMethods ` +- **配置:** [coverage.ignoreClassMethods](/config/#coverage-ignoreclassmethods) 覆盖时要忽略的类方法名称数组。更多信息请访问 [istanbuljs](https://github.com/istanbuljs/nyc#ignoring-methods) 。该选项仅适用于 istanbul providers(默认值:`[]`)。 ### coverage.processingConcurrency -- **CLI:** `--coverage.processingConcurrency ` -- **Config:** [coverage.processingConcurrency](/config/#coverage-processingconcurrency) +- **命令行终端:** `--coverage.processingConcurrency ` +- **配置:** [coverage.processingConcurrency](/config/#coverage-processingconcurrency) 处理覆盖率结果时使用的并发限制。 (默认最小值介于 20 和 CPU 数量之间) ### coverage.customProviderModule -- **CLI:** `--coverage.customProviderModule ` -- **Config:** [coverage.customProviderModule](/config/#coverage-customprovidermodule) +- **命令行终端:** `--coverage.customProviderModule ` +- **配置:** [coverage.customProviderModule](/config/#coverage-customprovidermodule) -指定自定义覆盖范围提供程序模块的模块名称或路径。 请访问[自定义 providers 覆盖范围](https://vitest.dev/guide/coverage#custom-coverage-provider) 了解更多信息。 此选项仅适用于自定义 providers +指定自定义覆盖范围提供程序模块的模块名称或路径。 请访问 [自定义 providers 覆盖范围](/guide/coverage#custom-coverage-provider) 了解更多信息。此选项仅适用于自定义 providers ### coverage.watermarks.statements -- **CLI:** `--coverage.watermarks.statements ` +- **命令行终端:** `--coverage.watermarks.statements ` High and low watermarks for statements in the format of `,` ### coverage.watermarks.lines -- **CLI:** `--coverage.watermarks.lines ` +- **命令行终端:** `--coverage.watermarks.lines ` High and low watermarks for lines in the format of `,` ### coverage.watermarks.branches -- **CLI:** `--coverage.watermarks.branches ` +- **命令行终端:** `--coverage.watermarks.branches ` High and low watermarks for branches in the format of `,` ### coverage.watermarks.functions -- **CLI:** `--coverage.watermarks.functions ` +- **命令行终端:** `--coverage.watermarks.functions ` High and low watermarks for functions in the format of `,` ### mode -- **CLI:** `--mode ` -- **Config:** [mode](/config/#mode) +- **命令行终端:** `--mode ` +- **配置:** [mode](/config/#mode) 覆盖 Vite 模式 (默认值: `test` 或 `benchmark`) +### workspace + +- **命令行终端:** `--workspace ` +- **配置:** [workspace](/config/#workspace) + +(已废弃)工作区配置文件的路径 + ### isolate -- **CLI:** `--isolate` -- **Config:** [isolate](/config/#isolate) +- **命令行终端:** `--isolate` +- **配置:** [isolate](/config/#isolate) 隔离运行每个测试文件。要禁用隔离, 使用 `--no-isolate` (默认值: `true`) ### globals -- **CLI:** `--globals` -- **Config:** [globals](/config/#globals) +- **命令行终端:** `--globals` +- **配置:** [globals](/config/#globals) 全局注入 ### dom -- **CLI:** `--dom` +- **命令行终端:** `--dom` 使用 happy-dom 模拟浏览器 API ### browser.enabled -- **CLI:** `--browser.enabled` -- **Config:** [browser.enabled](/guide/browser/config#browser-enabled) +- **命令行终端:** `--browser.enabled` +- **配置:** [browser.enabled](/guide/browser/config#browser-enabled) 在浏览器中运行测试。 相当于 `--browser.enabled` (默认值: `false`) ### browser.name -- **CLI:** `--browser.name ` -- **Config:** [browser.name](/guide/browser/config#browser-name) +- **命令行终端:** `--browser.name ` +- **配置:** [browser.name](/guide/browser/config#browser-name) 在特定浏览器中运行所有测试。某些浏览器仅适用于特定提供商(请参阅 `--browser.provider` )。访问 [`browser.name`](https://vitest.dev/guide/browser/config/#browser-name) 了解更多信息 ### browser.headless -- **CLI:** `--browser.headless` -- **Config:** [browser.headless](/guide/browser/config#browser-headless) +- **命令行终端:** `--browser.headless` +- **配置:** [browser.headless](/guide/browser/config#browser-headless) 在无头模式下运行浏览器(即不打开图形用户界面)。如果在 CI 中运行 Vitest,默认情况下将启用无头模式 (默认值: `process.env.CI`) ### browser.api.port -- **CLI:** `--browser.api.port [port]` -- **Config:** [browser.api.port](/guide/browser/config#browser-api-port) +- **命令行终端:** `--browser.api.port [port]` +- **配置:** [browser.api.port](/guide/browser/config#browser-api-port) 指定服务器端口。注意,如果端口已被使用,Vite 会自动尝试下一个可用端口,因此这可能不是服务器最终监听的实际端口。如果为 `true`,将设置为 `63315` ### browser.api.host -- **CLI:** `--browser.api.host [host]` -- **Config:** [browser.api.host](/guide/browser/config#browser-api-host) +- **命令行终端:** `--browser.api.host [host]` +- **配置:** [browser.api.host](/guide/browser/config#browser-api-host) 指定服务器应该监听哪些 IP 地址。设为 `0.0.0.0` 或 `true` 则监听所有地址,包括局域网地址和公共地址 ### browser.api.strictPort -- **CLI:** `--browser.api.strictPort` -- **Config:** [browser.api.strictPort](/guide/browser/config#browser-api-strictport) +- **命令行终端:** `--browser.api.strictPort` +- **配置:** [browser.api.strictPort](/guide/browser/config#browser-api-strictport) 设置为 true 时,如果端口已被使用,则退出,而不是自动尝试下一个可用端口 ### browser.provider -- **CLI:** `--browser.provider ` -- **Config:** [browser.provider](/guide/browser/config#browser-provider) +- **命令行终端:** `--browser.provider ` +- **配置:** [browser.provider](/guide/browser/config#browser-provider) -指定执行浏览器测试时所使用的提供程序。部分浏览器仅在特定的提供程序下可用。可选值有 "webdriverio"、"playwright"、"preview",也可以填写自定义提供程序的路径。更多信息请查看 [`browser.provider`](https://vitest.dev/guide/browser/config.html#browser-provider)(默认值为 "preview")。 +指定执行浏览器测试时所使用的提供程序。部分浏览器仅在特定的提供程序下可用。可选值有 "webdriverio"、"playwright"、"preview",也可以填写自定义提供程序的路径。更多信息请查看 [`browser.provider`](https://vitest.dev/guide/browser/config.html#browser-provider)(默认值为 "preview") + +### browser.providerOptions + +- **命令行终端:** `--browser.providerOptions ` +- **配置:** [browser.providerOptions](/guide/browser/config#browser-provideroptions) + +传递给浏览器提供程序的选项。更多信息请访问 [`browser.providerOptions`](https://vitest.dev/config/#browser-provideroptions) ### browser.isolate -- **CLI:** `--browser.isolate` -- **Config:** [browser.isolate](/guide/browser/config#browser-isolate) +- **命令行终端:** `--browser.isolate` +- **配置:** [browser.isolate](/guide/browser/config#browser-isolate) 隔离运行每个浏览器测试文件。要禁用隔离请使用 `--browser.isolate=false` (默认值: `true`) ### browser.ui -- **CLI:** `--browser.ui` -- **Config:** [browser.ui](/guide/browser/config#browser-ui) +- **命令行终端:** `--browser.ui` +- **配置:** [browser.ui](/guide/browser/config#browser-ui) -运行测试时显示 Vitest UI(默认值: `!process.env.CI`) +运行测试时显示 Vitest UI (默认值: `!process.env.CI`) ### browser.fileParallelism -- **CLI:** `--browser.fileParallelism` -- **Config:** [browser.fileParallelism](/guide/browser/config#browser-fileparallelism) +- **命令行终端:** `--browser.fileParallelism` +- **配置:** [browser.fileParallelism](/guide/browser/config#browser-fileparallelism) -浏览器测试文件是否应并行运行。使用 `--browser.fileParallelism=false` 可禁用 (默认值: `true`) +浏览器测试文件是否应并行运行。使用 `--browser.fileParallelism=false` 进行禁用 (默认值: `true`) ### browser.connectTimeout -- **CLI:** `--browser.connectTimeout ` -- **Config:** [browser.connectTimeout](/guide/browser/config#browser-connecttimeout) - -If connection to the browser takes longer, the test suite will fail (default: `60_000`) - -### browser.trackUnhandledErrors - -- **CLI:** `--browser.trackUnhandledErrors` -- **Config:** [browser.trackUnhandledErrors](/guide/browser/config#browser-trackunhandlederrors) - -控制 Vitest 是否捕获未捕获的异常以便报告(默认:`true`)。 - -### browser.trace - -- **CLI:** `--browser.trace ` -- **Config:** [browser.trace](/guide/browser/config#browser-trace) +- **命令行终端:** `--browser.connectTimeout ` +- **配置:** [browser.connectTimeout](/guide/browser/config#browser-connecttimeout) -Enable trace view mode. Supported: "on", "off", "on-first-retry", "on-all-retries", "retain-on-failure". +如果连接浏览器时间超时,测试套件将失败 (默认值: `60_000`) ### pool -- **CLI:** `--pool ` -- **Config:** [pool](/config/#pool) +- **命令行终端:** `--pool ` +- **配置:** [pool](/config/#pool) 如果未在浏览器中运行,则指定 pool (默认值: `threads`) ### poolOptions.threads.isolate -- **CLI:** `--poolOptions.threads.isolate` -- **Config:** [poolOptions.threads.isolate](/config/#pooloptions-threads-isolate) +- **命令行终端:** `--poolOptions.threads.isolate` +- **配置:** [poolOptions.threads.isolate](/config/#pooloptions-threads-isolate) 在线程池中隔离测试 (默认值: `true`) ### poolOptions.threads.singleThread -- **CLI:** `--poolOptions.threads.singleThread` -- **Config:** [poolOptions.threads.singleThread](/config/#pooloptions-threads-singlethread) +- **命令行终端:** `--poolOptions.threads.singleThread` +- **配置:** [poolOptions.threads.singleThread](/config/#pooloptions-threads-singlethread) 在单线程内运行测试 (默认值: `false`) ### poolOptions.threads.maxThreads -- **CLI:** `--poolOptions.threads.maxThreads ` -- **Config:** [poolOptions.threads.maxThreads](/config/#pooloptions-threads-maxthreads) +- **命令行终端:** `--poolOptions.threads.maxThreads ` +- **配置:** [poolOptions.threads.maxThreads](/config/#pooloptions-threads-maxthreads) 运行测试的最大线程数或百分比 ### poolOptions.threads.useAtomics -- **CLI:** `--poolOptions.threads.useAtomics` -- **Config:** [poolOptions.threads.useAtomics](/config/#pooloptions-threads-useatomics) +- **命令行终端:** `--poolOptions.threads.useAtomics` +- **配置:** [poolOptions.threads.useAtomics](/config/#pooloptions-threads-useatomics) 使用 Atomics 同步线程。这在某些情况下可以提高性能,但在较旧的 Node 版本中可能会导致 segfault。 (默认值: `false`) ### poolOptions.vmThreads.isolate -- **CLI:** `--poolOptions.vmThreads.isolate` -- **Config:** [poolOptions.vmThreads.isolate](/config/#pooloptions-vmthreads-isolate) +- **命令行终端:** `--poolOptions.vmThreads.isolate` +- **配置:** [poolOptions.vmThreads.isolate](/config/#pooloptions-vmthreads-isolate) 在线程池中隔离测试 (默认值: `true`) ### poolOptions.vmThreads.singleThread -- **CLI:** `--poolOptions.vmThreads.singleThread` -- **Config:** [poolOptions.vmThreads.singleThread](/config/#pooloptions-vmthreads-singlethread) +- **命令行终端:** `--poolOptions.vmThreads.singleThread` +- **配置:** [poolOptions.vmThreads.singleThread](/config/#pooloptions-vmthreads-singlethread) 在单线程内运行测试(默认值:`false`) ### poolOptions.vmThreads.maxThreads -- **CLI:** `--poolOptions.vmThreads.maxThreads ` -- **Config:** [poolOptions.vmThreads.maxThreads](/config/#pooloptions-vmthreads-maxthreads) +- **命令行终端:** `--poolOptions.vmThreads.maxThreads ` +- **配置:** [poolOptions.vmThreads.maxThreads](/config/#pooloptions-vmthreads-maxthreads) 运行测试的最大线程数或百分比 ### poolOptions.vmThreads.useAtomics -- **CLI:** `--poolOptions.vmThreads.useAtomics` -- **Config:** [poolOptions.vmThreads.useAtomics](/config/#pooloptions-vmthreads-useatomics) +- **命令行终端:** `--poolOptions.vmThreads.useAtomics` +- **配置:** [poolOptions.vmThreads.useAtomics](/config/#pooloptions-vmthreads-useatomics) 使用 Atomics 同步线程。这在某些情况下可以提高性能,但在较旧的 Node 版本中可能会导致 segfault。 (默认值: `false`) ### poolOptions.vmThreads.memoryLimit -- **CLI:** `--poolOptions.vmThreads.memoryLimit ` -- **Config:** [poolOptions.vmThreads.memoryLimit](/config/#pooloptions-vmthreads-memorylimit) +- **命令行终端:** `--poolOptions.vmThreads.memoryLimit ` +- **配置:** [poolOptions.vmThreads.memoryLimit](/config/#pooloptions-vmthreads-memorylimit) 虚拟机线程池的内存限制。如果发现内存泄漏,请尝试调整该值。 ### poolOptions.forks.isolate -- **CLI:** `--poolOptions.forks.isolate` -- **Config:** [poolOptions.forks.isolate](/config/#pooloptions-forks-isolate) +- **命令行终端:** `--poolOptions.forks.isolate` +- **配置:** [poolOptions.forks.isolate](/config/#pooloptions-forks-isolate) 在 forks pool 中隔离测试 (默认值: `true`) ### poolOptions.forks.singleFork -- **CLI:** `--poolOptions.forks.singleFork` -- **Config:** [poolOptions.forks.singleFork](/config/#pooloptions-forks-singlefork) +- **命令行终端:** `--poolOptions.forks.singleFork` +- **配置:** [poolOptions.forks.singleFork](/config/#pooloptions-forks-singlefork) 单个子进程内运行测试 (default: `false`) ### poolOptions.forks.maxForks -- **CLI:** `--poolOptions.forks.maxForks ` -- **Config:** [poolOptions.forks.maxForks](/config/#pooloptions-forks-maxforks) +- **命令行终端:** `--poolOptions.forks.maxForks ` +- **配置:** [poolOptions.forks.maxForks](/config/#pooloptions-forks-maxforks) 运行测试的最大进程数 ### poolOptions.vmForks.isolate -- **CLI:** `--poolOptions.vmForks.isolate` -- **Config:** [poolOptions.vmForks.isolate](/config/#pooloptions-vmforks-isolate) +- **命令行终端:** `--poolOptions.vmForks.isolate` +- **配置:** [poolOptions.vmForks.isolate](/config/#pooloptions-vmforks-isolate) 在 forks pool 中隔离测试 (default: `true`) ### poolOptions.vmForks.singleFork -- **CLI:** `--poolOptions.vmForks.singleFork` -- **Config:** [poolOptions.vmForks.singleFork](/config/#pooloptions-vmforks-singlefork) +- **命令行终端:** `--poolOptions.vmForks.singleFork` +- **配置:** [poolOptions.vmForks.singleFork](/config/#pooloptions-vmforks-singlefork) 在单个子进程内运行测试 (default: `false`) ### poolOptions.vmForks.maxForks -- **CLI:** `--poolOptions.vmForks.maxForks ` -- **Config:** [poolOptions.vmForks.maxForks](/config/#pooloptions-vmforks-maxforks) +- **命令行终端:** `--poolOptions.vmForks.maxForks ` +- **配置:** [poolOptions.vmForks.maxForks](/config/#pooloptions-vmforks-maxforks) 运行测试的最大进程数 ### poolOptions.vmForks.memoryLimit -- **CLI:** `--poolOptions.vmForks.memoryLimit ` -- **Config:** [poolOptions.vmForks.memoryLimit](/config/#pooloptions-vmforks-memorylimit) +- **命令行终端:** `--poolOptions.vmForks.memoryLimit ` +- **配置:** [poolOptions.vmForks.memoryLimit](/config/#pooloptions-vmforks-memorylimit) VM forks pool 的内存限制。如果你观察到内存泄漏问题,可以尝试调整这个值。 ### fileParallelism -- **CLI:** `--fileParallelism` -- **Config:** [fileParallelism](/config/#fileparallelism) +- **命令行终端:** `--fileParallelism` +- **配置:** [fileParallelism](/config/#fileparallelism) 是否所有测试文件都应并行运行. 使用 `--no-file-parallelism` 去禁用 (默认值: `true`) ### maxWorkers -- **CLI:** `--maxWorkers ` -- **Config:** [maxWorkers](/config/#maxworkers) +- **命令行终端:** `--maxWorkers ` +- **配置:** [maxWorkers](/config/#maxworkers) 同时并发执行测试任务的最大线程数或百分比 ### environment -- **CLI:** `--environment ` -- **Config:** [environment](/config/#environment) +- **命令行终端:** `--environment ` +- **配置:** [environment](/config/#environment) 如果不在浏览器中运行,则指定运行环境 (默认值: `node`) ### passWithNoTests -- **CLI:** `--passWithNoTests` -- **Config:** [passWithNoTests](/config/#passwithnotests) +- **命令行终端:** `--passWithNoTests` +- **配置:** [passWithNoTests](/config/#passwithnotests) 未发现测试时通过 ### logHeapUsage -- **CLI:** `--logHeapUsage` -- **Config:** [logHeapUsage](/config/#logheapusage) +- **命令行终端:** `--logHeapUsage` +- **配置:** [logHeapUsage](/config/#logheapusage) 在节点中运行时,显示每个测试的堆大小 ### allowOnly -- **CLI:** `--allowOnly` -- **Config:** [allowOnly](/config/#allowonly) +- **命令行终端:** `--allowOnly` +- **配置:** [allowOnly](/config/#allowonly) 允许执行那些被标记为"only"的测试用例或测试套件 (默认值: `!process.env.CI`) ### dangerouslyIgnoreUnhandledErrors -- **CLI:** `--dangerouslyIgnoreUnhandledErrors` -- **Config:** [dangerouslyIgnoreUnhandledErrors](/config/#dangerouslyignoreunhandlederrors) +- **命令行终端:** `--dangerouslyIgnoreUnhandledErrors` +- **配置:** [dangerouslyIgnoreUnhandledErrors](/config/#dangerouslyignoreunhandlederrors) 忽略任何未处理的错误 ### sequence.shuffle.files -- **CLI:** `--sequence.shuffle.files` -- **Config:** [sequence.shuffle.files](/config/#sequence-shuffle-files) +- **命令行终端:** `--sequence.shuffle.files` +- **配置:** [sequence.shuffle.files](/config/#sequence-shuffle-files) 以随机顺序运行文件。如果启用此选项,长时间运行的测试将不会提前开始。 (默认值: `false`) ### sequence.shuffle.tests -- **CLI:** `--sequence.shuffle.tests` -- **Config:** [sequence.shuffle.tests](/config/#sequence-shuffle-tests) +- **命令行终端:** `--sequence.shuffle.tests` +- **配置:** [sequence.shuffle.tests](/config/#sequence-shuffle-tests) 以随机方式运行测试(默认值:`false`) ### sequence.concurrent -- **CLI:** `--sequence.concurrent` -- **Config:** [sequence.concurrent](/config/#sequence-concurrent) +- **命令行终端:** `--sequence.concurrent` +- **配置:** [sequence.concurrent](/config/#sequence-concurrent) 使测试并行运行(默认值:`false`) ### sequence.seed -- **CLI:** `--sequence.seed ` -- **Config:** [sequence.seed](/config/#sequence-seed) +- **命令行终端:** `--sequence.seed ` +- **配置:** [sequence.seed](/config/#sequence-seed) 设置随机化种子。如果 --sequence.shuffle(随机序列)是`false`,则此选项无效。 t 通过 ["Random Seed" page](https://en.wikipedia.org/wiki/Random_seed) 查看更多信息 ### sequence.hooks -- **CLI:** `--sequence.hooks ` -- **Config:** [sequence.hooks](/config/#sequence-hooks) +- **命令行终端:** `--sequence.hooks ` +- **配置:** [sequence.hooks](/config/#sequence-hooks) 更改钩子的执行顺序。 可接受的值有: "stack", "list" and "parallel". 通过 [`sequence.hooks`](https://vitest.dev/config/#sequence-hooks) 查看更多信息 (默认值: `"parallel"`) ### sequence.setupFiles -- **CLI:** `--sequence.setupFiles ` -- **Config:** [sequence.setupFiles](/config/#sequence-setupfiles) +- **命令行终端:** `--sequence.setupFiles ` +- **配置:** [sequence.setupFiles](/config/#sequence-setupfiles) 更改设置文件的执行顺序。可接受的值有 "list" 和 "parallel"。如果设置为"list",将按照定义的顺序运行设置文件。如果设置为 "parallel",将并行运行设置文件(默认值:`"parallel"`)。 ### inspect -- **CLI:** `--inspect [[host:]port]` -- **Config:** [inspect](/config/#inspect) +- **命令行终端:** `--inspect [[host:]port]` +- **配置:** [inspect](/config/#inspect) 启用 Node.js 检查器(默认值:`127.0.0.1:9229`) ### inspectBrk -- **CLI:** `--inspectBrk [[host:]port]` -- **Config:** [inspectBrk](/config/#inspectbrk) +- **命令行终端:** `--inspectBrk [[host:]port]` +- **配置:** [inspectBrk](/config/#inspectbrk) 启用 Node.js 检查器并在测试开始前中断 ### testTimeout -- **CLI:** `--testTimeout ` -- **Config:** [testTimeout](/config/#testtimeout) +- **命令行终端:** `--testTimeout ` +- **配置:** [testTimeout](/config/#testtimeout) 测试的默认超时(毫秒)(默认值:`5000`)。使用 `0` 完全禁用超时。 ### hookTimeout -- **CLI:** `--hookTimeout ` -- **Config:** [hookTimeout](/config/#hooktimeout) +- **命令行终端:** `--hookTimeout ` +- **配置:** [hookTimeout](/config/#hooktimeout) 默认钩子超时(以毫秒为单位)(默认值:`10000`)。使用 `0` 完全禁用超时。 ### bail -- **CLI:** `--bail ` -- **Config:** [bail](/config/#bail) +- **命令行终端:** `--bail ` +- **配置:** [bail](/config/#bail) 当指定数量的测试失败时停止测试执行(默认值:`0`) ### retry -- **CLI:** `--retry ` -- **Config:** [retry](/config/#retry) +- **命令行终端:** `--retry ` +- **配置:** [retry](/config/#retry) 如果测试失败,重试特定次数(默认值: `0`)。 ### diff.aAnnotation -- **CLI:** `--diff.aAnnotation ` -- **Config:** [diff.aAnnotation](/config/#diff-aannotation) +- **命令行终端:** `--diff.aAnnotation ` +- **配置:** [diff.aAnnotation](/config/#diff-aannotation) Annotation for expected lines (default: `Expected`) ### diff.aIndicator -- **CLI:** `--diff.aIndicator ` -- **Config:** [diff.aIndicator](/config/#diff-aindicator) +- **命令行终端:** `--diff.aIndicator ` +- **配置:** [diff.aIndicator](/config/#diff-aindicator) Indicator for expected lines (default: `-`) ### diff.bAnnotation -- **CLI:** `--diff.bAnnotation ` -- **Config:** [diff.bAnnotation](/config/#diff-bannotation) +- **命令行终端:** `--diff.bAnnotation ` +- **配置:** [diff.bAnnotation](/config/#diff-bannotation) Annotation for received lines (default: `Received`) ### diff.bIndicator -- **CLI:** `--diff.bIndicator ` -- **Config:** [diff.bIndicator](/config/#diff-bindicator) +- **命令行终端:** `--diff.bIndicator ` +- **配置:** [diff.bIndicator](/config/#diff-bindicator) Indicator for received lines (default: `+`) ### diff.commonIndicator -- **CLI:** `--diff.commonIndicator ` -- **Config:** [diff.commonIndicator](/config/#diff-commonindicator) +- **命令行终端:** `--diff.commonIndicator ` +- **配置:** [diff.commonIndicator](/config/#diff-commonindicator) Indicator for common lines (default: ` `) ### diff.contextLines -- **CLI:** `--diff.contextLines ` -- **Config:** [diff.contextLines](/config/#diff-contextlines) +- **命令行终端:** `--diff.contextLines ` +- **配置:** [diff.contextLines](/config/#diff-contextlines) Number of lines of context to show around each change (default: `5`) ### diff.emptyFirstOrLastLinePlaceholder -- **CLI:** `--diff.emptyFirstOrLastLinePlaceholder ` -- **Config:** [diff.emptyFirstOrLastLinePlaceholder](/config/#diff-emptyfirstorlastlineplaceholder) +- **命令行终端:** `--diff.emptyFirstOrLastLinePlaceholder ` +- **配置:** [diff.emptyFirstOrLastLinePlaceholder](/config/#diff-emptyfirstorlastlineplaceholder) Placeholder for an empty first or last line (default: `""`) ### diff.expand -- **CLI:** `--diff.expand` -- **Config:** [diff.expand](/config/#diff-expand) +- **命令行终端:** `--diff.expand` +- **配置:** [diff.expand](/config/#diff-expand) Expand all common lines (default: `true`) ### diff.includeChangeCounts -- **CLI:** `--diff.includeChangeCounts` -- **Config:** [diff.includeChangeCounts](/config/#diff-includechangecounts) +- **命令行终端:** `--diff.includeChangeCounts` +- **配置:** [diff.includeChangeCounts](/config/#diff-includechangecounts) Include comparison counts in diff output (default: `false`) ### diff.omitAnnotationLines -- **CLI:** `--diff.omitAnnotationLines` -- **Config:** [diff.omitAnnotationLines](/config/#diff-omitannotationlines) +- **命令行终端:** `--diff.omitAnnotationLines` +- **配置:** [diff.omitAnnotationLines](/config/#diff-omitannotationlines) Omit annotation lines from the output (default: `false`) ### diff.printBasicPrototype -- **CLI:** `--diff.printBasicPrototype` -- **Config:** [diff.printBasicPrototype](/config/#diff-printbasicprototype) +- **命令行终端:** `--diff.printBasicPrototype` +- **配置:** [diff.printBasicPrototype](/config/#diff-printbasicprototype) Print basic prototype Object and Array (default: `true`) ### diff.maxDepth -- **CLI:** `--diff.maxDepth ` -- **Config:** [diff.maxDepth](/config/#diff-maxdepth) +- **命令行终端:** `--diff.maxDepth ` +- **配置:** [diff.maxDepth](/config/#diff-maxdepth) Limit the depth to recurse when printing nested objects (default: `20`) ### diff.truncateThreshold -- **CLI:** `--diff.truncateThreshold ` -- **Config:** [diff.truncateThreshold](/config/#diff-truncatethreshold) +- **命令行终端:** `--diff.truncateThreshold ` +- **配置:** [diff.truncateThreshold](/config/#diff-truncatethreshold) Number of lines to show before and after each change (default: `0`) ### diff.truncateAnnotation -- **CLI:** `--diff.truncateAnnotation ` -- **Config:** [diff.truncateAnnotation](/config/#diff-truncateannotation) +- **命令行终端:** `--diff.truncateAnnotation ` +- **配置:** [diff.truncateAnnotation](/config/#diff-truncateannotation) Annotation for truncated lines (default: `... Diff result is truncated`) ### exclude -- **CLI:** `--exclude ` -- **Config:** [exclude](/config/#exclude) +- **命令行终端:** `--exclude ` +- **配置:** [exclude](/config/#exclude) 测试中排除的其他文件路径匹配模式 ### expandSnapshotDiff -- **CLI:** `--expandSnapshotDiff` -- **Config:** [expandSnapshotDiff](/config/#expandsnapshotdiff) +- **命令行终端:** `--expandSnapshotDiff` +- **配置:** [expandSnapshotDiff](/config/#expandsnapshotdiff) 快照失败时显示完整差异 ### disableConsoleIntercept -- **CLI:** `--disableConsoleIntercept` -- **Config:** [disableConsoleIntercept](/config/#disableconsoleintercept) +- **命令行终端:** `--disableConsoleIntercept` +- **配置:** [disableConsoleIntercept](/config/#disableconsoleintercept) 禁用自动拦截控制台日志(默认值:`false`) ### typecheck.enabled -- **CLI:** `--typecheck.enabled` -- **Config:** [typecheck.enabled](/config/#typecheck-enabled) +- **命令行终端:** `--typecheck.enabled` +- **配置:** [typecheck.enabled](/config/#typecheck-enabled) 在测试的同时启用类型检查(默认值:`false`) ### typecheck.only -- **CLI:** `--typecheck.only` -- **Config:** [typecheck.only](/config/#typecheck-only) +- **命令行终端:** `--typecheck.only` +- **配置:** [typecheck.only](/config/#typecheck-only) 仅运行类型检查测试。这将自动启用类型检查(默认值:`false`) ### typecheck.checker -- **CLI:** `--typecheck.checker ` -- **Config:** [typecheck.checker](/config/#typecheck-checker) +- **命令行终端:** `--typecheck.checker ` +- **配置:** [typecheck.checker](/config/#typecheck-checker) 指定要使用的类型检查器。可用值为 "tsc"和 "vue-tsc "以及一个可执行文件的路径(默认值:`tsc`) ### typecheck.allowJs -- **CLI:** `--typecheck.allowJs` -- **Config:** [typecheck.allowJs](/config/#typecheck-allowjs) +- **命令行终端:** `--typecheck.allowJs` +- **配置:** [typecheck.allowJs](/config/#typecheck-allowjs) 允许对 JavaScript 文件进行类型检查。默认值取自 tsconfig.json ### typecheck.ignoreSourceErrors -- **CLI:** `--typecheck.ignoreSourceErrors` -- **Config:** [typecheck.ignoreSourceErrors](/config/#typecheck-ignoresourceerrors) +- **命令行终端:** `--typecheck.ignoreSourceErrors` +- **配置:** [typecheck.ignoreSourceErrors](/config/#typecheck-ignoresourceerrors) 忽略源文件中的类型错误 ### typecheck.tsconfig -- **CLI:** `--typecheck.tsconfig ` -- **Config:** [typecheck.tsconfig](/config/#typecheck-tsconfig) +- **命令行终端:** `--typecheck.tsconfig ` +- **配置:** [typecheck.tsconfig](/config/#typecheck-tsconfig) 自定义 tsconfig 文件的路径 ### typecheck.spawnTimeout -- **CLI:** `--typecheck.spawnTimeout
}> - - - ) - - // Initially working - await expect.element(getByText('Component working fine')).toBeInTheDocument() - - // Trigger error - rerender( - Something went wrong}> - - - ) - - // Error boundary should catch it - await expect.element(getByText('Something went wrong')).toBeInTheDocument() -}) -``` - -### 测试可访问性 {#testing-accessibility} - -```tsx -test('Modal component is accessible', async () => { - const { getByRole, getByLabelText } = render( - - - - ) - - // Test focus management - modal should receive focus when opened - // This is crucial for screen reader users to know a modal opened - const modal = getByRole('dialog') - await expect.element(modal).toHaveFocus() - - // Test ARIA attributes - these provide semantic information to screen readers - await expect.element(modal).toHaveAttribute('aria-labelledby') // Links to title element - await expect.element(modal).toHaveAttribute('aria-modal', 'true') // Indicates modal behavior - - // Test keyboard navigation - Escape key should close modal - // This is required by ARIA authoring practices - await userEvent.keyboard('{Escape}') - // expect.element auto-retries until modal is removed - await expect.element(modal).not.toBeInTheDocument() - - // Test focus trap - tab navigation should cycle within modal - // This prevents users from tabbing to content behind the modal - const firstInput = getByLabelText(/username/i) - const lastButton = getByRole('button', { name: /save/i }) - - // Use click to focus on the first input, then test tab navigation - await firstInput.click() - await userEvent.keyboard('{Shift>}{Tab}{/Shift}') // Shift+Tab goes backwards - await expect.element(lastButton).toHaveFocus() // Should wrap to last element -}) -``` - -## 调试组件测试 {#debugging-component-tests} - -### 1. 使用浏览器开发者工具 {#_1-use-browser-dev-tools} - -浏览器模式在真实浏览器中运行测试,让你可以使用完整的开发者工具。当测试失败时,你可以: - -- **在测试执行期间打开浏览器开发者工具**(按F12或右键点击→检查) -- **在测试代码或组件代码中设置断点** -- **检查DOM**以查看实际渲染的输出 -- **检查控制台错误**以查找JavaScript错误或警告 -- **监控网络请求**以调试API调用 - -对于有头模式调试,可以在浏览器配置中临时添加`headless: false`。 - -### 2. 添加调试语句 {#_2-add-debug-statements} - -使用策略性日志记录来理解测试失败: - -```tsx -test('debug form validation', async () => { - render() - - const submitButton = page.getByRole('button', { name: /submit/i }) - await submitButton.click() - - // Debug: Check if element exists with different query - const errorElement = page.getByText('Email is required') - console.log('Error element found:', errorElement.length) - - await expect.element(errorElement).toBeInTheDocument() -}) -``` - -### 3. 检查渲染输出 {#_3-inspect-rendered-output} - -当组件未按预期渲染时,请系统性地进行调查: - -**使用Vitest的浏览器UI:** -- 在启用浏览器模式的情况下运行测试 -- 打开终端中显示的浏览器URL以查看测试运行情况 -- 可视化检查有助于识别CSS问题、布局问题或缺失元素 - -**测试元素查询:** -```tsx -// Debug why elements can't be found -const button = page.getByRole('button', { name: /submit/i }) -console.log('Button count:', button.length) // Should be 1 - -// Try alternative queries if the first one fails -if (button.length === 0) { - console.log('All buttons:', page.getByRole('button').length) - console.log('By test ID:', page.getByTestId('submit-btn').length) -} -``` - -### 4. 验证选择器 {#_4-verify-selectors} - -选择器问题是测试失败的常见原因。请系统性地调试它们: - -**检查可访问名称:** -```tsx -// If getByRole fails, check what roles/names are available -const buttons = page.getByRole('button').all() -for (const button of buttons) { - // Use element() to get the DOM element and access native properties - const element = button.element() - const accessibleName = element.getAttribute('aria-label') || element.textContent - console.log(`Button: "${accessibleName}"`) -} -``` - -**测试不同的查询策略:** -```tsx -// Multiple ways to find the same element using .or for auto-retrying -const submitButton = page.getByRole('button', { name: /submit/i }) // By accessible name - .or(page.getByTestId('submit-button')) // By test ID - .or(page.getByText('Submit')) // By exact text -// Note: Vitest doesn't have page.locator(), use specific getBy* methods instead -``` - -**常见的选择器调试模式:** -```tsx -test('debug element queries', async () => { - render() - - // Check if element is visible and enabled - const emailInput = page.getByLabelText(/email/i) - await expect.element(emailInput).toBeVisible() // Will show if element is visible and print DOM if not -}) -``` - -### 5. 调试异步问题 {#_5-debugging-async-issues} - -组件测试经常涉及时机问题: - -```tsx -test('debug async component behavior', async () => { - render() - - // expect.element will automatically retry and show helpful error messages - await expect.element(page.getByText('John Doe')).toBeInTheDocument() -}) -``` - -## 从其他测试框架迁移 {#migration-from-other-testing-frameworks} - -### 从 Jest + Testing Library 迁移 {#from-jest-testing-library} - -大多数 Jest + Testing Library 测试只需少量更改即可工作: - -```ts -// Before (Jest) -import { render, screen } from '@testing-library/react' // [!code --] - -// After (Vitest) -import { render } from 'vitest-browser-react' // [!code ++] -``` - -### 主要差异 {#key-differences} - -- 使用 `await expect.element()` 而不是 `expect()` 进行 DOM 断言 -- 使用 `vitest/browser` 进行用户交互而不是 `@testing-library/user-event` -- 浏览器模式提供真实的浏览器环境以进行准确的测试 - -## 了解更多 {#learn-more} - -- [浏览器模式文档](/guide/browser/) -- [断言API](/guide/browser/assertion-api) -- [交互性API](/guide/browser/interactivity-api) -- [示例仓库](https://github.com/vitest-tests/browser-examples) diff --git a/guide/browser/trace-view.md b/guide/browser/trace-view.md deleted file mode 100644 index d78db55d..00000000 --- a/guide/browser/trace-view.md +++ /dev/null @@ -1,74 +0,0 @@ -# Trace View - -Vitest Browser Mode supports generating Playwright's [trace files](https://playwright.dev/docs/trace-viewer#viewing-remote-traces). To enable tracing, you need to set the [`trace`](/guide/browser/config#browser-trace) option in the `test.browser` configuration. - -::: warning -Generating trace files is only available when using the [Playwright provider](/guide/browser/playwright). -::: - -::: code-group -```ts [vitest.config.js] -import { defineConfig } from 'vitest/config' -import { playwright } from '@vitest/browser-playwright' - -export default defineConfig({ - test: { - browser: { - provider: playwright(), - trace: 'on', - }, - }, -}) -``` -```bash [CLI] -vitest --browser.trace=on -``` -::: - -By default, Vitest will generate a trace file for each test. You can also configure it to only generate traces on test failures by setting `trace` to `'on-first-retry'`, `'on-all-retries'` or `'retain-on-failure'`. The files will be saved in `__traces__` folder next to your test files. The name of the trace includes the project name, the test name, the [`repeats` count and `retry` count](/api/#test-api-reference): - -``` -chromium-my-test-0-0.trace.zip -^^^^^^^^ project name - ^^^^^^ test name - ^ repeat count - ^ retry count -``` - -To change the output directory, you can set the `tracesDir` option in the `test.browser.trace` configuration. This way all traces will be stored in the same directory, grouped by the test file. - -```ts [vitest.config.js] -import { defineConfig } from 'vitest/config' -import { playwright } from '@vitest/browser-playwright' - -export default defineConfig({ - test: { - browser: { - provider: playwright(), - trace: { - mode: 'on', - // the path is relative to the root of the project - tracesDir: './playwright-traces', - }, - }, - }, -}) -``` - -The traces are available in reporters as [annotations](/guide/test-annotations). For example, in the HTML reporter, you can find the link to the trace file in the test details. - -## Preview - -To open the trace file, you can use the Playwright Trace Viewer. Run the following command in your terminal: - -```bash -npx playwright show-trace "path-to-trace-file" -``` - -This will start the Trace Viewer and load the specified trace file. - -Alternatively, you can open the Trace Viewer in your browser at https://trace.playwright.dev and upload the trace file there. - -## Limitations - -At the moment, Vitest cannot populate the "Sources" tab in the Trace Viewer. This means that while you can see the actions and screenshots captured during the test, you won't be able to view the source code of your tests directly within the Trace Viewer. You will need to refer back to your code editor to see the test implementation. diff --git a/guide/browser/visual-regression-testing.md b/guide/browser/visual-regression-testing.md deleted file mode 100644 index 4037932c..00000000 --- a/guide/browser/visual-regression-testing.md +++ /dev/null @@ -1,692 +0,0 @@ ---- -title: 可视化回归测试 -outline: [2, 3] ---- - -# 可视化回归测试 {#visual-regression-testing} - -Vitest 原生支持可视化回归测试。它会自动截取 UI 组件或页面的截图,并与基准图像对比,以捕捉那些非预期的视觉变化。 - -与只验证功能逻辑的功能测试不同,可视化测试能发现样式异常、布局偏移和渲染错误——这些问题如果没有细致的人工检查,往往会被忽略。 - -## 为什么需要可视化回归测试? {#why-visual-regression-testing} - -视觉 bug 不会报错,但它们的外观已经改变。这正是可视化测试的意义所在: - -- 按钮依然能提交表单,但颜色却变成了亮粉色 -- 文本在桌面端显示正常,在移动端却被挤压变形 -- 功能没问题,可两个容器已跑出视口 -- 精心的 CSS 重构完成了,却破坏了某个无人测试的页面布局 - -可视化回归测试是 UI 的安全网,确保这些变化在进入生产环境之前就被自动发现并处理。 - -## 快速入门 {#getting-started} - -::: warning 浏览器渲染差异 -可视化回归测试对运行环境非常敏感,不同机器生成的截图可能存在差异,常见原因包括: - -- 字体渲染差异(最常见,Windows、macOS、Linux 各不相同) -- GPU 驱动与硬件加速 -- 是否使用无头模式 -- 浏览器版本与设置 -- ……甚至偶发的系统差异 - -因此,Vitest 会在截图文件名中添加浏览器和平台信息(如 `button-chromium-darwin.png`),避免不同环境的截图互相覆盖。 - -要获得稳定结果,应使用相同的测试环境。**推荐**采用云端服务(如 [Azure App Testing](https://azure.microsoft.com/en-us/products/playwright-testing))或基于 [Docker containers](https://playwright.dev/docs/docker) 的环境。 -::: - -在 Vitest 中,可通过 [`toMatchScreenshot` assertion](/guide/browser/assertion-api.html#tomatchscreenshot) 断言运行可视化回归测试: - -```ts -import { expect, test } from 'vitest' -import { page } from 'vitest/browser' - -test('hero section looks correct', async () => { - // ...the rest of the test - - // capture and compare screenshot - await expect(page.getByTestId('hero')).toMatchScreenshot('hero-section') -}) -``` - -### 创建基准截图 {#creating-references} - -首次运行可视化测试时, Vitest 会生成一张基准( baseline )截图,并提示如下错误信息使测试失败: - -``` -expect(element).toMatchScreenshot() - -No existing reference screenshot found; a new one was created. Review it before running tests again. - -Reference screenshot: - tests/__screenshots__/hero.test.ts/hero-section-chromium-darwin.png -``` - -确认截图正确后再次运行测试,Vitest 会将后续结果与该基准图比较。 - -::: tip -基准截图存放在测试文件所在目录下的 `__screenshots__` 文件夹中, -**请务必提交到版本库**。 -::: - -### 截图组织方式 {#screenshot-organization} - -Vitest 默认将截图按以下结构保存: - -``` -. -├── __screenshots__ -│ └── test-file.test.ts -│ ├── test-name-chromium-darwin.png -│ ├── test-name-firefox-linux.png -│ └── test-name-webkit-win32.png -└── test-file.test.ts -``` - -文件名由三部分组成: -- **测试名**:来自 `toMatchScreenshot()` 的第一个参数,或自动根据测试用例名生成 -- **浏览器名**:`chrome`、`chromium`、`firefox`、`webkit` -- **平台**:如 `aix`、`darwin`、`linux`、`win32` 等 - -这种命名方式可避免不同环境生成的截图互相覆盖。 - -### 更新基准截图 {#updating-references} - -当你有意修改 UI 时,需要更新基准截图: - -```bash -$ vitest --update -``` - -提交前务必核对更新后的截图,确保改动符合预期。 - -## 配置可视化测试 {#configuring-visual-tests} - -### 全局配置 {#global-configuration} - -可在 [Vitest 配置文件](/guide/browser/config#browser-expect-tomatchscreenshot) 中设定可视化回归测试的默认规则: - -```ts [vitest.config.ts] -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - browser: { - expect: { - toMatchScreenshot: { - comparatorName: 'pixelmatch', - comparatorOptions: { - // 0-1, how different can colors be? - threshold: 0.2, - // 1% of pixels can differ - allowedMismatchedPixelRatio: 0.01, - }, - }, - }, - }, - }, -}) -``` - -### 单测试配置 {#per-test-configuration} - -若某个测试需要不同的比较标准,可在调用时覆盖全局设置: - -```ts -await expect(element).toMatchScreenshot('button-hover', { - comparatorName: 'pixelmatch', - comparatorOptions: { - // more lax comparison for text-heavy elements - allowedMismatchedPixelRatio: 0.1, - }, -}) -``` - -## 最佳实践 {#best-practices} - -### 聚焦测试目标元素 {#test-specific-elements} - -除非确实需要测试整个页面,否则应优先只对目标组件截图,这能显著减少因页面其他部分变化而造成的误报。 - -```ts -// ❌ Captures entire page; prone to unrelated changes -await expect(page).toMatchScreenshot() - -// ✅ Captures only the component under test -await expect(page.getByTestId('product-card')).toMatchScreenshot() -``` - -### 处理动态内容 {#handle-dynamic-content} - -测试中,如果页面包含诸如时间戳、用户信息或随机值等动态内容,往往会导致结果不一致而造成测试失败。 -解决方法有两种:一是模拟这些动态数据的生成源; -二是在使用 Playwright 进行截图时,在 `screenshotOptions` 中启用 -[`mask` 选项](https://playwright.dev/docs/api/class-page#page-screenshot-option-mask), -将这些动态区域遮盖,从而确保测试结果的稳定性。 - -```ts -await expect(page.getByTestId('profile')).toMatchScreenshot({ - screenshotOptions: { - mask: [page.getByTestId('last-seen')], - }, -}) -``` - -### 禁用所有动画 {#disable-animations} - -动画效果往往会导致测试结果出现波动。为避免这种情况, -可以在测试执行过程中注入一段自定义的 CSS 样式代码,用于禁用所有动画,从而提升测试的稳定性。 - -```css -*, *::before, *::after { - animation-duration: 0s !important; - animation-delay: 0s !important; - transition-duration: 0s !important; - transition-delay: 0s !important; -} -``` - -::: tip -在使用 Playwright 作为测试工具时,若执行断言操作,动画会被自动禁用。 -具体而言,`screenshotOptions` 配置中的 `animations` 选项会默认设为 `"disabled"`,从而确保截图与测试结果的稳定一致。 -::: - -### 设置合理的阈值 {#set-appropriate-thresholds} - -在视觉回归测试中,阈值调整是一项需要权衡的工作——它取决于页面内容、测试环境、 -应用所能容忍的差异范围,且可能因具体测试而有所不同。 - -Vitest 并未为像素差异设定默认阈值,这需要由用户根据实际需求来决定。 -官方建议使用 `allowedMismatchedPixelRatio`,让阈值按截图的整体尺寸比例计算,而非依赖固定像素数量。 - -当 `allowedMismatchedPixelRatio` 与 `allowedMismatchedPixels` 同时设置时, -Vitest 会优先采用二者中限制更严格的那一个,以确保测试结果的准确性与一致性。 - -### 保持统一的视口大小 {#set-consistent-viewport-sizes} - -浏览器实例的默认窗口尺寸可能存在差异,这会影响视觉回归测试的稳定性。为避免由于尺寸不一致而产生的截图偏差, -建议在测试脚本或浏览器实例配置中显式指定一个固定的视口大小,从而确保测试结果的可重复性与一致性。 - -```ts -await page.viewport(1280, 720) -``` - -```ts [vitest.config.ts] -import { playwright } from '@vitest/browser-playwright' -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - browser: { - enabled: true, - provider: playwright(), - instances: [ - { - browser: 'chromium', - viewport: { width: 1280, height: 720 }, - }, - ], - }, - }, -}) -``` - -### 使用 Git LFS 管理基准截图 {#use-git-lfs} - -对于规模较大的视觉回归测试套件,建议将基准截图文件存储在 -[Git LFS](https://github.com/git-lfs/git-lfs?tab=readme-ov-file) 中。 -这样既能避免仓库体积膨胀,又能高效管理和传输这些大尺寸文件,提升团队协作效率。 - -## 调试视觉测试失败 {#debugging-failed-tests} - -当视觉回归测试未能通过时, Vitest 会生成三张关键截图,帮助你分析问题所在: - -1. **参考截图( Reference screenshot )**:测试期望的基准图像 -2. **实际截图( Actual screenshot )**:测试运行过程中截取的画面 -3. **差异图( Diff image )**:用高亮标记出参考图与实际图的差异(有时可能不会生成) - -在调试时,你会在输出中看到类似如下的文件列表或路径信息: - -``` -expect(element).toMatchScreenshot() - -Screenshot does not match the stored reference. -245 pixels (ratio 0.03) differ. - -Reference screenshot: - tests/__screenshots__/button.test.ts/button-chromium-darwin.png - -Actual screenshot: - tests/.vitest-attachments/button.test.ts/button-chromium-darwin-actual.png - -Diff image: - tests/.vitest-attachments/button.test.ts/button-chromium-darwin-diff.png -``` - -### 如何解读差异图 {#understanding-the-diff-image} - -- **红色像素**:表示参考截图与实际截图之间存在显著差异的区域 -- **黄色像素**:由抗锯齿处理带来的细微差异(仅在未忽略抗锯齿时可见) -- **透明或原始图像部分**:表示两张截图在该区域完全一致 - -:::tip -如果差异图几乎被红色覆盖,说明测试结果与预期严重不符,需要重点排查。 -若只是文字边缘零星出现少量红点,可能只是渲染细节差异,此时适当提高阈值即可解决。 -::: - -## 常见问题与解决方案 {#common-issues-and-solutions} - -### 字体渲染引发的误报 {#false-positives-from-font-rendering} - -由于不同操作系统在字体可用性与渲染方式上差异明显,视觉回归测试中可能会出现“误报”现象。为降低这种风险,可以考虑以下做法: - -- 使用 Web 字体,并在测试执行前等待字体完全加载; - - ```ts - // wait for fonts to load - await document.fonts.ready - - // continue with your tests - ``` - -- 对包含大量文字的区域适当提高像素差异的比较阈值,以减少因字体渲染细微差别导致的误报; - - ```ts - await expect(page.getByTestId('article-summary')).toMatchScreenshot({ - comparatorName: 'pixelmatch', - comparatorOptions: { - // 10% of the pixels are allowed to change - allowedMismatchedPixelRatio: 0.1, - }, - }) - ``` - -- 使用云端服务或容器化测试环境,确保字体渲染效果在各次测试中保持一致,从而减少系统差异带来的影响; - -### 测试不稳定或截图尺寸不一致 {#flaky-tests-or-different-screenshot-sizes} - -如果测试结果出现随机通过或失败,或者在不同运行中生成的截图尺寸不一致,可以采取以下措施: - -- 确保页面所有内容均已加载完成,包括加载指示器与动画; -- 明确设置固定的视口大小,例如:`await page.viewport(1920, 1080)`; -- 检查页面在视口临界尺寸下的响应式布局表现; -- 排查是否存在非预期的动画或过渡效果干扰截图结果; -- 对体积较大的截图适当延长测试的超时时间; -- 使用云端服务或容器化环境,确保字体渲染、浏览器配置等保持一致。 - -## 团队版视觉回归测试方案 {#visual-regression-testing-for-teams} - -视觉回归测试对环境的稳定性要求极高,而本地开发机并不适合担当这一角色。 - -在团队协作中,常见的三种方案是: - -1. **自托管运行器**:部署过程复杂,日常维护工作量大; -2. **GitHub Actions**:对开源项目免费,可与任何测试框架或服务集成; -3. **云服务**:如 [Microsoft Playwright Testing](https://azure.microsoft.com/en-us/products/playwright-testing),专为解决视觉测试环境一致性问题而构建。 - -我们将重点介绍第 2 和第 3 种方案,因为它们能最快投入使用。 - -主要权衡点在于: - -- **GitHub Actions**:视觉测试只能在持续集成(CI)环境中运行,开发者无法直接在本地执行; -- **Microsoft 云服务**:可在任意环境运行,但需额外付费,并且仅支持 Playwright。 - -:::: tabs key:vrt-for-teams -=== GitHub Actions - -要点在于,将视觉回归测试与常规测试分离运行。 -否则,你可能会因截图差异引发的失败日志而浪费数小时进行排查。 - -#### 测试组织建议 {#organizing-your-tests} - -首先,应将视觉回归测试与其他测试隔离管理。 -建议单独建立一个 `visual` 文件夹(或根据项目结构选择更合适的目录名称)来存放这些测试用例,以便维护与执行。 - -```json [package.json] -{ - "scripts": { - "test:unit": "vitest --exclude tests/visual/*.test.ts", - "test:visual": "vitest tests/visual/*.test.ts" - } -} -``` - -这样,开发者就能在本地运行 `npm run test:unit` ,而无需受到视觉回归测试的影响; -视觉测试则放在环境一致的持续集成( CI )平台中运行,以确保结果稳定可靠。 - -::: tip 抉择 -不喜欢用 glob 匹配模式?那你也可以创建独立的 [测试项目](/guide/projects),并通过以下方式来运行它们: - -- `vitest --project unit` -- `vitest --project visual` -::: - -#### 持续集成( CI )环境配置 {#ci-setup} - -在 CI 环境中运行视觉回归测试时,需要确保浏览器已正确安装。至于如何安装,则取决于你所使用的 CI 服务提供商及其运行环境。 - -::: tabs key:provider -== Playwright - -[Playwright](https://npmjs.com/package/playwright) 能让浏览器安装与管理变得非常简单。 -你只需固定所用的 Playwright 版本,并在运行测试之前加入以下命令或脚本: - -```yaml [.github/workflows/ci.yml] -# ...the rest of the workflow -- name: Install Playwright Browsers - run: npx --no playwright install --with-deps --only-shell -``` - -== WebdriverIO - -[WebdriverIO](https://www.npmjs.com/package/webdriverio) 要求用户自行准备浏览器环境。不过, -[ @browser-actions ](https://github.com/browser-actions) 团队已经为此提供了方便的解决方案, -帮你轻松完成浏览器的安装与配置。 - -```yaml [.github/workflows/ci.yml] -# ...the rest of the workflow -- uses: browser-actions/setup-chrome@v1 - with: - chrome-version: 120 -``` - -::: - -最后,运行你的视觉回归测试: - -```yaml [.github/workflows/ci.yml] -# ...the rest of the workflow -# ...browser setup -- name: Visual Regression Testing - run: npm run test:visual -``` - -#### 更新工作流程 {#the-update-workflow} - -关键点来了——切勿在每一次 Pull Request 中都自动更新截图, -*(那只会带来混乱)*。更稳妥的方式,是建立一个手动触发的工作流程, -让开发者在有意更改 UI 时主动运行,从而更新基准截图。 - -该工作流程具备以下特性: -- 仅在功能分支上运行,确保主分支安全不受影响; -- 自动将触发流程的开发者署名为共同作者; -- 阻止同一分支上的并发执行,避免冲突与资源浪费; -- 生成一份清晰美观的执行摘要,便于快速查看结果。 - - **当基准截图发生变动时**,系统会列出所有具体的变化项,方便开发者快速了解差异。 - - Action summary after updates - Action summary after updates - - - **当没有任何变化时**,系统同样会明确提示,让你一目了然。 - - Action summary after no updates - Action summary after no updates - -::: tip -这只是实现的其中一种方式。 -有些团队倾向于在 Pull Request 中添加特定评论(如 `/update-screenshots`)来触发更新, -也有团队通过添加标签来完成这一操作。 -你可以根据自身的开发流程进行调整。 - -关键在于,必须建立一种可控的机制来更新基准截图, -以避免不必要的混乱和错误。 -::: - -```yaml [.github/workflows/update-screenshots.yml] -name: Update Visual Regression Screenshots - -on: - workflow_dispatch: # manual trigger only - -env: - AUTHOR_NAME: 'github-actions[bot]' - AUTHOR_EMAIL: '41898282+github-actions[bot]@users.noreply.github.com' - COMMIT_MESSAGE: | - test: update visual regression screenshots - - Co-authored-by: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> - -jobs: - update-screenshots: - runs-on: ubuntu-24.04 - - # safety first: don't run on main - if: github.ref_name != github.event.repository.default_branch - - # one at a time per branch - concurrency: - group: visual-regression-screenshots@${{ github.ref_name }} - cancel-in-progress: true - - permissions: - contents: write # needs to push changes - - steps: - - name: Checkout selected branch - uses: actions/checkout@v4 - with: - ref: ${{ github.ref_name }} - # use PAT if triggering other workflows - # token: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure Git - run: | - git config --global user.name "${{ env.AUTHOR_NAME }}" - git config --global user.email "${{ env.AUTHOR_EMAIL }}" - - # your setup steps here (node, pnpm, whatever) - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 24 - - - name: Install dependencies - run: npm ci - - - name: Install Playwright Browsers - run: npx --no playwright install --with-deps --only-shell - - # the magic happens below 🪄 - - name: Update Visual Regression Screenshots - run: npm run test:visual --update - - # check what changed - - name: Check for changes - id: check_changes - run: | - CHANGED_FILES=$(git status --porcelain | awk '{print $2}') - if [ "${CHANGED_FILES:+x}" ]; then - echo "changes=true" >> $GITHUB_OUTPUT - echo "Changes detected" - - # save the list for the summary - echo "changed_files<> $GITHUB_OUTPUT - echo "$CHANGED_FILES" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - echo "changed_count=$(echo "$CHANGED_FILES" | wc -l)" >> $GITHUB_OUTPUT - else - echo "changes=false" >> $GITHUB_OUTPUT - echo "No changes detected" - fi - - # commit if there are changes - - name: Commit changes - if: steps.check_changes.outputs.changes == 'true' - run: | - git add -A - git commit -m "${{ env.COMMIT_MESSAGE }}" - - - name: Push changes - if: steps.check_changes.outputs.changes == 'true' - run: git push origin ${{ github.ref_name }} - - # pretty summary for humans - - name: Summary - run: | - if [[ "${{ steps.check_changes.outputs.changes }}" == "true" ]]; then - echo "### 📸 Visual Regression Screenshots Updated" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Successfully updated **${{ steps.check_changes.outputs.changed_count }}** screenshot(s) on \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "#### Changed Files:" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "${{ steps.check_changes.outputs.changed_files }}" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "✅ The updated screenshots have been committed and pushed. Your visual regression baseline is now up to date!" >> $GITHUB_STEP_SUMMARY - else - echo "### ℹ️ No Screenshot Updates Required" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "The visual regression test command ran successfully but no screenshots needed updating." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "All screenshots are already up to date! 🎉" >> $GITHUB_STEP_SUMMARY - fi -``` - -=== Azure App Testing - -你的测试依旧在本地运行,只是将浏览器托管到云端执行。 -这基于 Playwright 的远程浏览器功能,但所有云端基础设施均由 Microsoft 负责维护与管理。 - -#### 测试组织建议 - -为控制成本,应将视觉回归测试与其他测试分离管理, -并确保只有那些实际需要截取页面截图的用例才会调用该服务。 - -最为简洁高效的做法,是使用 [Test Projects](/guide/projects) 功能来隔离这些测试。 - - -```ts [vitest.config.ts] -import { env } from 'node:process' -import { defineConfig } from 'vitest/config' -import { playwright } from '@vitest/browser-playwright' - -export default defineConfig({ - // ...global Vite config - tests: { - // ...global Vitest config - projects: [ - { - extends: true, - test: { - name: 'unit', - include: ['tests/**/*.test.ts'], - // regular config, can use local browsers - }, - }, - { - extends: true, - test: { - name: 'visual', - // or you could use a different suffix, e.g.,: `tests/**/*.visual.ts?(x)` - include: ['visual-regression-tests/**/*.test.ts?(x)'], - browser: { - enabled: true, - provider: playwright({ - connectOptions: { - wsEndpoint: `${env.PLAYWRIGHT_SERVICE_URL}?${new URLSearchParams({ - 'api-version': '2025-09-01', - os: 'linux', // always use Linux for consistency - // helps identifying runs in the service's dashboard - runName: `Vitest ${env.CI ? 'CI' : 'local'} run @${new Date().toISOString()}`, - })}`, - exposeNetwork: '', - headers: { - Authorization: `Bearer ${env.PLAYWRIGHT_SERVICE_ACCESS_TOKEN}`, - }, - timeout: 30_000, - } - }), - headless: true, - instances: [ - { - browser: 'chromium', - viewport: { width: 2560, height: 1440 }, - }, - ], - }, - }, - }, - ], - }, -}) -``` - -该服务会提供两个关键环境变量: - -- `PLAYWRIGHT_SERVICE_URL`:指示 Playwright 连接的服务器地址 -- `PLAYWRIGHT_SERVICE_ACCESS_TOKEN`:你的身份验证令牌 - - - -Follow the [official guide to create a Playwright Workspace](https://learn.microsoft.com/en-us/azure/app-testing/playwright-workspaces/quickstart-run-end-to-end-tests?tabs=playwrightcli&pivots=playwright-test-runner#create-a-workspace). - -Once your workspace is created, configure Vitest to use it: - -1. **Set the endpoint URL**: following the [official guide](https://learn.microsoft.com/en-us/azure/app-testing/playwright-workspaces/quickstart-run-end-to-end-tests?tabs=playwrightcli&pivots=playwright-test-runner#configure-the-browser-endpoint), retrieve the URL and set it as the `PLAYWRIGHT_SERVICE_URL` environment variable. -2. **Enable token authentication**: [enable access tokens](https://learn.microsoft.com/en-us/azure/app-testing/playwright-workspaces/how-to-manage-authentication?pivots=playwright-test-runner#enable-authentication-using-access-tokens) for your workspace, then [generate a token](https://learn.microsoft.com/en-us/azure/app-testing/playwright-workspaces/how-to-manage-access-tokens#generate-a-workspace-access-token) and set it as the `PLAYWRIGHT_SERVICE_ACCESS_TOKEN` environment variable. - -::: danger 令牌务必保密! -切勿将 `PLAYWRIGHT_SERVICE_ACCESS_TOKEN` 提交到代码仓库。 -任何获取到该令牌的人都可能在你的账户上产生高额费用。 -在本地开发时,应通过环境变量引用令牌;在 CI 中,应将其存放于安全的密钥管理中。 -::: - -然后,将 `test` 脚本按如下方式拆分运行: - -```json [package.json] -{ - "scripts": { - "test:visual": "vitest --project visual", - "test:unit": "vitest --project unit" - } -} -``` - -#### 运行测试 - -```bash -# Local development -npm run test:unit # free, runs locally -npm run test:visual # uses cloud browsers - -# Update screenshots -npm run test:visual -- --update -``` - -这种方式的最大优势在于“开箱即用”: - -- **截图结果一致**:所有人共享相同的云端浏览器环境,避免环境差异; -- **支持本地执行**:开发者可在本地直接运行并更新视觉回归测试; -- **按量计费**:仅有视觉测试会消耗服务分钟数,成本可控; -- **零运维负担**:无需配置 Docker 或复杂的工作流,几乎不需额外维护。 - -#### 持续集成( CI )环境配置 - -在 CI 平台中,将所需的密钥添加到环境变量或机密配置中: - -```yaml -env: - PLAYWRIGHT_SERVICE_URL: ${{ vars.PLAYWRIGHT_SERVICE_URL }} - PLAYWRIGHT_SERVICE_ACCESS_TOKEN: ${{ secrets.PLAYWRIGHT_SERVICE_ACCESS_TOKEN }} -``` - -接下来,你只需像往常一样运行测试,其余的由服务全权负责处理。 - -:::: - -### 该选哪一个? {#so-which-one} - -两种方案都可行,关键在于团队最在意的痛点是什么。 - -如果你的团队已经深度依赖 GitHub 生态,那么 **GitHub Actions** 几乎是无可替代的选择——对开源项目免费、 -支持任意浏览器服务商、并且可完全掌控执行流程。 - -缺点在于:当有人在本地生成的截图与 CI 环境的基准不一致时,就会出现那句熟悉的“在我机器上没问题”。 - -如果团队需要在本地执行视觉回归测试,那么云服务或许更适合。 -这种方式特别适合有设计师参与审核,或开发者希望在推送代码前发现并修复问题的团队, -能够跳过“推送—等待—检查—修改—再推送”的繁琐循环。 - -如果依然犹豫,不妨先从 GitHub Actions 开始;等到本地测试成为痛点时,再引入云服务也不迟。 From 8be2149e55e23659536be472d382a4ad2f2f89a3 Mon Sep 17 00:00:00 2001 From: noise Date: Mon, 1 Dec 2025 02:57:14 +0800 Subject: [PATCH 48/50] fix: dead link --- api/vi.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/vi.md b/api/vi.md index fd28be9c..221a80fe 100644 --- a/api/vi.md +++ b/api/vi.md @@ -150,7 +150,7 @@ axios.get(`/apples/${increment(1)}`) 请注意,如果不调用 `vi.mock` ,模块**不会**被自动模拟。要复制 Jest 的自动锁定行为,可以在 [`setupFiles`](/config/#setupfiles) 中为每个所需的模块调用 `vi.mock` 。 ::: -如果没有提供 `__mocks__` 文件夹或工厂,Vitest 将导入原始模块并自动模拟其所有输出。有关应用的规则,请参阅 [模块](/mocking.html#automocking-algorithm)。 +如果没有提供 `__mocks__` 文件夹或工厂,Vitest 将导入原始模块并自动模拟其所有输出。有关应用的规则,请参阅 [模块](/guide/mocking#automocking-algorithm)。 ### vi.doMock @@ -258,7 +258,7 @@ vi.mock('./example.js', async () => { - **类型**: `(path: string) => Promise>` -导入模块并模拟其所有属性(包括嵌套属性)。遵循与 [`vi.mock`](#vi-mock) 相同的规则。有关应用的规则,请参阅 [模块](/mocking.html#automocking-algorithm)。 +导入模块并模拟其所有属性(包括嵌套属性)。遵循与 [`vi.mock`](#vi-mock) 相同的规则。有关应用的规则,请参阅 [模块](/guide/mocking#automocking-algorithm)。 ### vi.unmock @@ -393,7 +393,7 @@ expect(getApples).toHaveNthReturnedWith(2, 5) - **Type:** `(value: T) => MaybeMockedDeep` -它与 `vi.mock()` 模拟模块相同,深层模拟给定对象的属性和方法。详见 [自动模拟](/guide/mocking.html#automocking-algorithm)。 +它与 `vi.mock()` 模拟模块相同,深层模拟给定对象的属性和方法。详见 [自动模拟](/guide/mocking#automocking-algorithm)。 ```ts const original = { @@ -632,7 +632,7 @@ IntersectionObserver === undefined ## Fake Timers -本节介绍如何使用 [模拟计时器](/guide/mocking.html#timers)。 +本节介绍如何使用 [模拟计时器](/guide/mocking#timers)。 ### vi.advanceTimersByTime From d12062aa1114951a15b1f9f2e8862fe2f5934b15 Mon Sep 17 00:00:00 2001 From: noise Date: Mon, 1 Dec 2025 03:02:44 +0800 Subject: [PATCH 49/50] fix: dead link --- config/index.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/config/index.md b/config/index.md index 9d37cbc3..f512be33 100644 --- a/config/index.md +++ b/config/index.md @@ -2530,20 +2530,10 @@ Expand all common lines. - **命令行终端:** `--workspace=./file.js` - **默认值:** `vitest.{workspace,projects}.{js,ts,json}` close to the config file or root -相对于[root](#root) 的 [workspace](/guide/workspace) 配置文件的路径。 +相对于[root](#root) 的 [workspace](/guide/projects) 配置文件的路径。 自 Vitest 3 起,您也可以在根配置中直接定义 `workspace` 数组。若手动在配置中声明了 workspace 参数,Vitest 将忽略项目根目录下的 `vitest.workspace` 文件。 -### workspace - -::: danger 弃用 -此选项已弃用,将在下一个主要版本中移除。请改用 [`projects`](#projects)。 -::: - -- **类型:** `string | TestProjectConfiguration` -- **命令行终端:** `--workspace=./file.js` -- **默认值:** 配置文件或根目录附近的 `vitest.{workspace,projects}.{js,ts,json}` 文件 - 相对于 [root](#root) 的 [workspace](/guide/projects) 配置文件路径。 从 Vitest 3 起,您也可以在根配置中定义 workspace 数组。如果手动在配置中定义了 `workspace`,Vitest 将忽略根目录下的 `vitest.workspace` 文件。 From 47b871ef6b38f1713a2a58f2168ccf7610c952d5 Mon Sep 17 00:00:00 2001 From: noise Date: Mon, 1 Dec 2025 03:08:22 +0800 Subject: [PATCH 50/50] fix: /config/ workspace options --- config/index.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/config/index.md b/config/index.md index f512be33..11e1e275 100644 --- a/config/index.md +++ b/config/index.md @@ -2528,16 +2528,12 @@ Expand all common lines. - **类型:** `string | TestProjectConfiguration` - **命令行终端:** `--workspace=./file.js` -- **默认值:** `vitest.{workspace,projects}.{js,ts,json}` close to the config file or root +- **默认值:** 配置文件或根目录附近的 `vitest.{workspace,projects}.{js,ts,json}` 相对于[root](#root) 的 [workspace](/guide/projects) 配置文件的路径。 自 Vitest 3 起,您也可以在根配置中直接定义 `workspace` 数组。若手动在配置中声明了 workspace 参数,Vitest 将忽略项目根目录下的 `vitest.workspace` 文件。 -相对于 [root](#root) 的 [workspace](/guide/projects) 配置文件路径。 - -从 Vitest 3 起,您也可以在根配置中定义 workspace 数组。如果手动在配置中定义了 `workspace`,Vitest 将忽略根目录下的 `vitest.workspace` 文件。 - ### projects - **类型:** `TestProjectConfiguration[]`