From 1889065d19cdfb945a0108b7d0a38df4af160746 Mon Sep 17 00:00:00 2001 From: Raul Macarie Date: Thu, 18 Jun 2026 10:25:52 +0200 Subject: [PATCH 1/5] feat(vitest): create `vi.when()` (#10174) --- .vitepress/config.ts | 4 + api/expect.md | 32 ++++ api/vi.md | 128 +++++++++++++ guide/learn/mock-functions.md | 4 + guide/mocking/functions.md | 4 + guide/recipes/conditional-mocking.md | 270 +++++++++++++++++++++++++++ 6 files changed, 442 insertions(+) create mode 100644 guide/recipes/conditional-mocking.md diff --git a/.vitepress/config.ts b/.vitepress/config.ts index c42839e96..b608d7b55 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -1047,6 +1047,10 @@ export default ({ mode }: { mode: string }) => { text: 'Auto-Cleanup with `using`', link: '/guide/recipes/explicit-resources', }, + { + text: 'Conditional Mocking with `vi.when`', + link: '/guide/recipes/conditional-mocking', + }, { text: 'Per-File Isolation Settings', link: '/guide/recipes/disable-isolation', diff --git a/api/expect.md b/api/expect.md index d2603aa80..ae98bdddf 100644 --- a/api/expect.md +++ b/api/expect.md @@ -1453,6 +1453,38 @@ test('spy function returns bananas on second call', async () => { }) ``` +## toHaveBeenExhausted 5.0.0 {#tohavebeenexhausted} + +- **Type:** `() => void` + +This assertion checks that every behavior registered on a [`vi.when`](/api/vi#vi-when) chain has been consumed. A behavior is considered exhausted when it has been called the number of times specified by its `times` option, or at least once for behaviors that apply indefinitely. + +Requires a `When` chain returned by `vi.when` to be passed to `expect`. + +```ts +import { expect, test, vi } from 'vitest' + +test('all behaviors were consumed', () => { + const spy = vi.fn() + const w = vi.when(spy) + .calledWith(1) + .thenReturnOnce('once') + .calledWith(2) + .thenReturn('always') + + expect(w).not.toHaveBeenExhausted() + + spy(1) // consumes the `thenReturnOnce` behavior + spy(2) // satisfies the `thenReturn` behavior (called at least once) + + expect(w).toHaveBeenExhausted() +}) +``` + +::: warning +A `When` chain with no registered behaviors is never considered exhausted. `toHaveBeenExhausted` only passes when at least one `calledWith` with an associated action (`then*`) has been registered and every registered behavior has been fully consumed. +::: + ## called 4.1.0 {#called} - **Type:** `Assertion` (property, not a method) diff --git a/api/vi.md b/api/vi.md index 656a199a1..3d84ea781 100644 --- a/api/vi.md +++ b/api/vi.md @@ -797,6 +797,134 @@ globalThis.IntersectionObserver === undefined IntersectionObserver === undefined ``` +### vi.when 5.0.0 {#vi-when} + +```ts +interface WhenOptions { + onUnmatched?: 'throw' | 'passthrough' | ((...args: unknown[]) => unknown) +} + +interface BehaviorOptions { + times?: number +} + +function when(spy: Mock, options?: WhenOptions): When +``` + +Defines per-argument behaviors on a spy, replacing its implementation for the duration of the `when` chain. + +Call `.calledWith(...args)` on the returned object to specify which call arguments to match, then chain one or more `then*` methods to declare what the spy should return, throw, or resolve when invoked with those arguments. Arguments are matched with deep equality and support asymmetric matchers such as `expect.any()`. + +```ts +const spy = vi.fn() + +vi.when(spy) + .calledWith(1) + .thenReturn('one') + .calledWith(2) + .thenReturn('two') + +expect(spy(1)).toBe('one') +expect(spy(2)).toBe('two') +``` + +Available `then*` methods: + +| Method | Description | +|--------|-------------| +| `thenReturn(value, options?)` | Returns `value`. | +| `thenReturnOnce(value)` | Returns `value` once, then falls back. | +| `thenThrow(error, options?)` | Throws `error`. | +| `thenThrowOnce(error)` | Throws `error` once, then falls back. | +| `thenResolve(value, options?)` | Returns a resolved `Promise` with `value`. | +| `thenResolveOnce(value)` | Resolves once, then falls back. | +| `thenReject(error, options?)` | Returns a rejected `Promise` with `error`. | +| `thenRejectOnce(error)` | Rejects once, then falls back. | + +The optional `times` option limits how many times a behavior applies before being exhausted. Behaviors registered for the same arguments are consumed last-in-first-out: the most recently registered behavior is tried first, and once exhausted, earlier ones act as fallbacks. + +```ts +const spy = vi.fn<(key: string) => string>() + +vi.when(spy) + .calledWith('theme') + .thenReturn('light') // fallback, applies indefinitely + .thenReturn('dark', { times: 2 }) // applied first for the next 2 calls + +expect(spy('theme')).toBe('dark') +expect(spy('theme')).toBe('dark') +expect(spy('theme')).toBe('light') // falls back +``` + +When called with arguments that match no registered behavior, the spy falls through to its original implementation by default. Use the `onUnmatched` option to change this: + +- `'passthrough'` (**default**): delegates to the spy's original implementation +- `'throw'`: throws an error listing the unmatched arguments +- a function: called with the unmatched arguments; its return value is used + +```ts +const spy = vi.fn<(id: number) => string>() + +vi.when(spy, { onUnmatched: 'throw' }) + .calledWith(1) + .thenReturn('Alice') + +expect(spy(1)).toBe('Alice') +expect(() => spy(99)).toThrow() // no behavior defined for 99 +``` + +The `When` object returned by `vi.when` supports the [`toHaveBeenExhausted` assertion](/api/expect#tohavebeenexhausted), which passes once every registered behavior has been consumed. + +```ts +const spy = vi.fn() +const w = vi.when(spy) + .calledWith(1) + .thenReturnOnce('once') + .calledWith(2) + .thenReturn('always') + +expect(w).not.toHaveBeenExhausted() + +spy(1) // consumes the `thenReturnOnce` behavior +spy(2) // satisfies `thenReturn` (called at least once) + +expect(w).toHaveBeenExhausted() +``` + +::: tip +In environments that support [Explicit Resource Management](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Resource_management), you can use `using` instead of `const` to automatically restore the spy's original implementation when the containing block exits: + +```ts +const spy = vi.fn(() => 'original') + +{ + using w = vi.when(spy) + .calledWith('hello') + .thenReturn('mocked') + + expect(spy('hello')).toBe('mocked') +} // ← spy's original implementation is restored here + +expect(spy('hello')).toBe('original') +``` +::: + +### vi.isWhenChain 5.0.0 {#vi-iswhenchain} + +```ts +function isWhenChain(input: object): input is When +``` + +Returns `true` if the given value is a `When` chain created by [`vi.when`](#vi-when). If you are using TypeScript, it will also narrow down its type. + +```ts +const spy = vi.fn() +const w = vi.when(spy).calledWith(1).thenReturn(0) + +expect(vi.isWhenChain(w)).toBe(true) +expect(vi.isWhenChain(spy)).toBe(false) +``` + ## Fake Timers This sections describes how to work with [fake timers](/guide/mocking/timers). diff --git a/guide/learn/mock-functions.md b/guide/learn/mock-functions.md index 2892ff7b2..bc20ad135 100644 --- a/guide/learn/mock-functions.md +++ b/guide/learn/mock-functions.md @@ -72,6 +72,10 @@ test('mock async return values', async () => { }) ``` +::: tip +`mockReturnValue` always returns the same value regardless of the arguments the mock receives. If you need argument-specific return values, [`vi.when`](/api/vi#vi-when) lets you attach different behaviors for different argument combinations without writing your own `if/else` logic. See the [Conditional Mocking](/guide/recipes/conditional-mocking) recipe for details. +::: + ## Mock Implementation Sometimes you need more than a fixed return value. You want the mock to actually do something with its arguments. [`mockImplementation`](/api/mock#mockimplementation) lets you provide a full replacement function: diff --git a/guide/mocking/functions.md b/guide/mocking/functions.md index 58c729911..a2530cb8f 100644 --- a/guide/mocking/functions.md +++ b/guide/mocking/functions.md @@ -8,6 +8,10 @@ If you need to pass down a custom function implementation as an argument or crea Both `vi.spyOn` and `vi.fn` share the same methods. +::: tip +If you need a mock to return different values depending on the arguments it receives, [`vi.when()`](/api/vi#vi-when) lets you define argument-specific behaviors without writing your own `if/else` logic. See the [Conditional Mocking](/guide/recipes/conditional-mocking) recipe for details. +::: + ## Example ```js diff --git a/guide/recipes/conditional-mocking.md b/guide/recipes/conditional-mocking.md new file mode 100644 index 000000000..0fb8e190d --- /dev/null +++ b/guide/recipes/conditional-mocking.md @@ -0,0 +1,270 @@ +--- +title: Conditional Mocking with vi.when | Recipes +--- + +# Conditional Mocking with `vi.when` + +::: tip Prerequisites +This recipe assumes you already have some familiarity with [mocking](/guide/mocking) in Vitest. +::: + +When a mock needs to return different values depending on the arguments it receives, [`mockReturnValue`](/api/mock#mockreturnvalue) doesn't help because it always returns the same value. The standard approach would be to use [`mockImplementation`](/api/mock#mockimplementation) with a `switch` or a series of `if/else` statements: + +```ts +db.findById.mockImplementation((id) => { + if (id === 1) { + return Promise.resolve({ id: 1, name: 'Ella' }) + } + + if (id === 2) { + return Promise.resolve({ id: 2, name: 'Gracie' }) + } + + return Promise.resolve(undefined) +}) +``` + +This works, but it becomes tedious because you have to write the argument-matching logic yourself. This is something that Vitest can handle for you when using the [`vi.when`](/api/vi#vi-when) 5.0.0 API. + +## Pattern + +`vi.when` takes a spy and lets you define argument-specific behaviors. + +Call `.calledWith(...args)` to declare which arguments to match. This creates a _behavior_. + +Then attach an _action_ by calling a `then*` method. The action determines what happens when the behavior matches. + +Multiple behaviors can be chained on the same spy: + +```ts +import { test, vi } from 'vitest' +import { getUserById } from './user.ts' + +test('returns user data', async () => { + const db = { findById: vi.fn() } + + vi.when(db.findById) + .calledWith(1) + .thenResolve({ id: 1, name: 'Ella' }) + .calledWith(2) + .thenResolve({ id: 2, name: 'Gracie' }) + + await expect(getUserById(db, 1)).resolves.toEqual({ name: 'Ella' }) + await expect(getUserById(db, 2)).resolves.toEqual({ name: 'Gracie' }) +}) +``` + +The same approach works across all mock outcome types. Here is the full set of actions and their equivalents: + +| Action | Equivalent to | Equivalent code | +|---|---|---| +| `thenReturn(value)` | `mockReturnValue(value)` | `return value` | +| `thenThrow(error)` | `mockThrow(error)` | `throw error` | +| `thenResolve(value)` | `mockResolvedValue(value)` | `return Promise.resolve(value)` | +| `thenReject(error)` | `mockRejectedValue(error)` | `return Promise.reject(error)` | + +## Stacking actions + +A single behavior can have multiple actions attached to it. When the behavior matches, actions are _consumed_ in **last-in-first-out** order: the most recently registered action runs first. Once that action has been consumed, Vitest falls back to the previous one. Use the `times` option to limit how many calls an action handles before falling through to the next action. An action with no `times` limit runs indefinitely. + +Because actions are evaluated in reverse registration order, indefinite actions should be registered first so that later finite actions can temporarily override them. + +```ts +import { test, vi } from 'vitest' +import { readConfig } from './config.ts' + +test('retries after an initial failure', async () => { + const fetchInstance = vi.fn<() => Promise>() + + vi.when(fetchInstance) + .calledWith('/data/config.json') + .thenResolve(new Response('{ debug: true }')) + // ↳ indefinite fallback + .thenReject(new Error('network error'), { times: 1 }) + // ↳ applied first and consumed after one call + + await expect(readConfig(fetchInstance)).resolves.toEqual({ debug: true }) + + expect(fetchInstance).toHaveBeenCalledTimes(2) +}) +``` + +For convenience, `then*Once` shorthands are available and equivalent to `{ times: 1 }`: `thenReturnOnce`, `thenResolveOnce`, `thenThrowOnce`, `thenRejectOnce`. + +## Asymmetric matchers + +`calledWith` supports [asymmetric matchers](/guide/learn/matchers#asymmetric-matchers). This is useful when you care about the shape or type of an argument rather than its exact value: + +```ts +test('sends email to each recipient', () => { + vi.when(sendEmail) + .calledWith(expect.stringContaining('@')) + .thenReturn({ ok: true, message: 'sent via external relay' }) +}) +``` + +Behaviors, unlike actions, are matched in **first-in-first-out** order. The first behavior whose arguments match the call wins, just like a chain of `if/else` statements. Specific matchers must therefore be registered before broad ones. + +```ts +test('sends email to each recipient', () => { + vi.when(sendEmail) + .calledWith(expect.stringContaining('@internal.example.com')) + .thenReturn({ ok: true, message: 'sent via internal relay' }) + .calledWith(expect.stringContaining('@')) + .thenReturn({ ok: true, message: 'sent via external relay' }) +}) +``` + +::: warning Behavior Merging +When registering a new behavior, Vitest checks existing behaviors in registration order. If the new arguments already match an existing behavior, the new action is merged into that behavior instead of creating a new one. + +This is especially important with broad asymmetric matchers: + +```ts +vi.when(getRole) + .calledWith(expect.any(String)) + .thenReturn('user') + .calledWith('admin@example.com') + .thenReturnOnce('admin') +``` + +Because the second registration is merged into the existing behavior, the `'admin'` action is not scoped to `'admin@example.com'`. Instead, it becomes the next action for the entire `expect.any(String)` behavior. The resulting behavior acts as if it had been written like this: + +```ts +vi.when(getRole) + .calledWith(expect.any(String)) + .thenReturn('user') + .thenReturnOnce('admin') +``` + +As a result, the first call with any string returns `'admin'`, while later calls return `'user'`: + +```ts +expect(getRole('user@example.com')).toBe('admin') +expect(getRole('user@example.com')).toBe('user') +``` +::: + +## Handling unmatched calls + +By default, when the spy is called with arguments that match no registered behavior, it falls back to the spy's original implementation. If the spy has no original implementation, it returns `undefined`. + +There are three ways to handle this differently: + +1. [throwing an error](#onunmatched-throw); +1. [running a custom function](#onunmatched-fn); +1. [using asymmetric matchers as catch-all behaviors](#asymmetric-matcher-as-catch-all). + +### `onUnmatched: 'throw'` + +Pass `{ onUnmatched: 'throw' }` to throw whenever the spy is called with unregistered arguments: + +```ts +vi.when(db.findById, { onUnmatched: 'throw' }) + .calledWith(1) + .thenResolve({ id: 1, name: 'Ella' }) + +await expect(db.findById(1)).resolves.toMatchObject({ name: 'Ella' }) +await expect(db.findById(3)).rejects.toThrow( + 'vi.when: no behavior defined when called with [3]', +) +``` + +The error message includes the unmatched arguments. The error type and message are fixed and cannot be customized. + +### `onUnmatched: fn` + +Pass a function to handle unmatched calls with custom logic, for example when a shared mock needs a different fallback per test. + +```ts +const db = { findById: vi.fn() } + +test('returns a placeholder for unknown ids', async () => { + vi.when( + db.findById, + { onUnmatched: id => Promise.resolve({ id, name: `User ${id}` }) } + ) + .calledWith(1) + .thenResolve({ id: 1, name: 'Ella' }) + + await expect(db.findById(1)).resolves.toMatchObject({ name: 'Ella' }) + await expect(db.findById(42)).resolves.toMatchObject({ name: 'User 42' }) +}) +``` + +The function is called with the same arguments as the spy and its return value is used directly as the spy's result. If it throws or returns a rejected promise, that error propagates to the caller just as it would from any action. + +### Asymmetric matcher as catch-all + +Registering a broad `calledWith` last acts as a fallback for calls that do not match any earlier, more specific behavior. The fallback behavior can return a specific value, resolve or reject a promise, or throw a typed error. + +```ts +vi.when(db.findById) + .calledWith(1) + .thenResolve({ id: 1, name: 'Ella' }) + .calledWith(2) + .thenResolve({ id: 2, name: 'Gracie' }) + .calledWith(expect.any(Number)) + .thenReject(new Error('user not found')) +``` + +## Asserting that all behaviors were called + +To check that all registered behaviors were actually matched and their actions consumed, the object returned by `vi.when` supports the [`toHaveBeenExhausted`](/api/expect#tohavebeenexhausted) assertion: + +```ts +test('loads both users', async () => { + const db = { findById: vi.fn() } + + const w = vi.when(db.findById) + .calledWith(1) + .thenResolveOnce({ id: 1, name: 'Ella' }) + .calledWith(2) + .thenResolveOnce({ id: 2, name: 'Gracie' }) + + await loadDashboard(db) + + expect(w).toHaveBeenExhausted() +}) +``` + +In this example, if `loadDashboard` only calls `findById(1)`, the test fails with a message listing the behaviors that were never matched: + +``` +AssertionError: expected all behaviors to have been exhausted, but some remain: + + calledWith(2) + ✗ thenReturn({ id: 2, name: 'Gracie' }) never called +``` + +::: warning Caveat +A `vi.when` chain with no behaviors is never considered exhausted. The same applies to a bare `.calledWith()` with no `then*` action attached. Both will always cause `toHaveBeenExhausted` to fail. + +Indefinite actions (no `times` limit) satisfy exhaustion checks after being used at least once. The actions keep responding after that, but the assertion is satisfied. +::: + +## Automatic cleanup with `using` + +`vi.when` supports the [Explicit Resource Management](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Resource_management) protocol. + +Declare the chain with `using` to scope behaviors to the current block and restore the spy automatically when execution leaves it. + +```ts +const spy = vi.fn(() => 'original') + +test('with mocked behavior', () => { + using w = vi.when(spy).calledWith('hello').thenReturn('mocked') + expect(spy('hello')).toBe('mocked') +}) // ← restored here + +test('without mocked behavior', () => { + expect(spy('hello')).toBe('original') +}) +``` + +## See also + +- [`vi.when`](/api/vi#vi-when) +- [`toHaveBeenExhausted`](/api/expect#tohavebeenexhausted) +- [`vi.isWhenChain`](/api/vi#vi-iswhenchain) +- [Auto-Cleanup with `using`](/guide/recipes/explicit-resources) From 390a1bdb8ccef049b6d4b6f9b709aafa5367d079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 18 Jun 2026 14:04:27 +0200 Subject: [PATCH 2/5] docs: add Deno to install instructions (#10614) --- guide/index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/guide/index.md b/guide/index.md index 5e58c8d84..f8ee42ca6 100644 --- a/guide/index.md +++ b/guide/index.md @@ -36,6 +36,9 @@ pnpm add -D vitest ```bash [bun] bun add -D vitest ``` +```bash [deno] +deno add -D vitest +``` ::: :::tip From d2a96ea59ddf6280e75e8a04b41c6ca18cb10ff4 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 19 Jun 2026 17:21:51 +0900 Subject: [PATCH 3/5] fix(ui)!: harden UI API access (#10583) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Codex --- guide/migration.md | 9 +++++++++ guide/ui.md | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/guide/migration.md b/guide/migration.md index 077640c36..30a0f9707 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -43,6 +43,15 @@ test('sort', async ({ bench }) => { // [!code ++] - **`benchmark.outputJson` config and the `--outputJson` CLI flag** are removed. Use `--reporter=json --outputFile=` to capture benchmark results; the JSON reporter now includes a `benchmarks` field on each test case. - **`Vitest` instance `mode` property** is now always `'test'`. The previous `'benchmark'` value is no longer used; benchmarks run inside a dedicated project of the same `Vitest` instance. +### Vitest UI Requires an Authenticated URL + +Vitest UI now requires token authentication for the HTML page and API access. The `/__vitest__/` URL will show an error until the browser is authenticated. To authenticate, open the URL with a token printed by Vitest, as shown below. Once authenticated, the direct `/__vitest__/` URL will work correctly. + +```bash +vitest --ui +# UI started at http://localhost:51204/__vitest__/?token=... +``` + ### Removed `test.sequential`, `describe.sequential`, and `sequential` Options Vitest 5.0 removes the deprecated `test.sequential`, `describe.sequential`, and `sequential` test options. Use `concurrent: false` when you need a test or suite to opt out of inherited or globally configured concurrency. diff --git a/guide/ui.md b/guide/ui.md index 4dfa0b4a0..43fafa3f6 100644 --- a/guide/ui.md +++ b/guide/ui.md @@ -18,6 +18,10 @@ vitest --ui Then you can visit the Vitest UI at `http://localhost:51204/__vitest__/` +::: tip +Vitest UI access is protected. If the direct URL shows an error, open the URL with a token printed by Vitest in the terminal, for example `http://localhost:51204/__vitest__/?token=...`. +::: + ::: warning The UI is interactive and requires a running Vite server, so make sure to run Vitest in `watch` mode (the default). Alternatively, you can generate a static HTML report that looks identical to the Vitest UI by specifying `html` in config's `reporters` option. ::: From 83c20df03e0fda4bbcdebc819938fcb1ecab8778 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 19 Jun 2026 18:24:43 +0900 Subject: [PATCH 4/5] feat(ui)!: change html reporter default output to `.vitest` (#10620) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> --- config/outputfile.md | 2 +- guide/migration.md | 8 ++++++++ guide/reporters.md | 4 ++-- guide/ui.md | 8 ++++---- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/config/outputfile.md b/config/outputfile.md index fc87b029a..eab1b0c1d 100644 --- a/config/outputfile.md +++ b/config/outputfile.md @@ -8,5 +8,5 @@ outline: deep - **Type:** `string | Record` - **CLI:** `--outputFile=`, `--outputFile.json=./path` -Write test results to a file when the `--reporter=json`, `--reporter=html` or `--reporter=junit` option is also specified. +Write test results to a file when the `--reporter=json` or `--reporter=junit` option is also specified. By providing an object instead of a string you can define individual outputs when using multiple reporters. diff --git a/guide/migration.md b/guide/migration.md index 30a0f9707..73b91fb56 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -174,6 +174,14 @@ Vitest no longer serves the browser orchestrator UI from a bare `/__vitest_test_ If you manually opened the browser preview by copying the Vite server URL or visiting `/__vitest_test__/` directly, use the URL opened or printed by Vitest instead. +### Generated Reports and Artifacts Use the `.vitest` Directory + +Vitest now uses a single `.vitest` directory at the project root as the shared artifact root, so one `.vitest` entry in `.gitignore` is enough. Defaults that moved this major: + +- **Attachments** ([`attachmentsDir`](/config/attachmentsdir)): `.vitest-attachements/` → `.vitest/attachments/` +- **Blob reporter** and `--merge-reports`: `.vitest-reports/blob-*.json` → `.vitest/blob/blob-*.json` +- **HTML reporter** ([`html`](/guide/reporters#html-reporter)): `html/index.html` → `.vitest/index.html`, and its option changed from `outputFile` (a file) to `outputDir` (a directory) + ## Migrating to Vitest 4.0 {#vitest-4} ::: warning Prerequisites diff --git a/guide/reporters.md b/guide/reporters.md index e060753cd..cdb6a0be5 100644 --- a/guide/reporters.md +++ b/guide/reporters.md @@ -60,7 +60,7 @@ export default defineConfig({ ## Reporter Output -By default, Vitest's reporters will print their output to the terminal. When using the `json`, `html` or `junit` reporters, you can instead write your tests' output to a file by including an `outputFile` [configuration option](/config/outputfile) either in your Vite configuration file or via CLI. +By default, Vitest's reporters will print their output to the terminal. When using the `json` or `junit` reporters, you can instead write your tests' output to a file by including an `outputFile` [configuration option](/config/outputfile) either in your Vite configuration file or via CLI. The `html` reporter writes a report directory instead; see its [`outputDir`](#html-reporter) option. :::code-group ```bash [CLI] @@ -506,7 +506,7 @@ export default defineConfig({ Generates an HTML file to view test results through an interactive [GUI](/guide/ui). After the file has been generated, Vitest will keep a local development server running and provide a link to view the report in a browser. -Output file can be specified using the [`outputFile`](/config/outputfile) configuration option. If no `outputFile` option is provided, a new HTML file will be created. +The report artifact root can be specified using the reporter's `outputDir` option. The report entry is written to `/index.html` and the UI assets files live under `/ui/`. By default `outputDir` is `.vitest`, the shared Vitest artifact directory, so attachments (`.vitest/attachments`) and coverage (`.vitest/coverage`) are reused without being copied. :::code-group ```bash [CLI] diff --git a/guide/ui.md b/guide/ui.md index 43fafa3f6..4643681c4 100644 --- a/guide/ui.md +++ b/guide/ui.md @@ -51,10 +51,10 @@ If you still want to see how your tests are running in real time in the terminal To preview your HTML report, you can use the [vite preview](https://vitejs.dev/guide/cli.html#vite-preview) command: ```sh -npx vite preview --outDir ./html +npx vite preview --outDir .vitest ``` -You can configure output with [`outputFile`](/config/outputfile) config option. You need to specify `.html` path there. For example, `./html/index.html` is the default value. +You can configure the output location with the HTML reporter's `outputDir` option. It points to the report artifact root, and the report entry is written to `/index.html`. The default value is `.vitest`, the shared Vitest artifact directory. ::: If you need a portable report that can be opened or shared as one file, see [`singleFile`](/guide/reporters#html-reporter) in the HTML reporter documentation. @@ -67,7 +67,7 @@ To view the HTML report from CI, for example in GitHub Actions, upload the outpu id: upload-report with: name: vitest-report - path: html/ + path: .vitest/ - name: Viewer link in summary run: echo "[View HTML report](https://viewer.vitest.dev/?url=${{ steps.upload-report.outputs.artifact-url }})" >> $GITHUB_STEP_SUMMARY @@ -80,7 +80,7 @@ When you use `singleFile: true`, you can upload the report as a single file and ```yaml - uses: actions/upload-artifact@v7 with: - path: html/index.html + path: .vitest/index.html archive: false ``` ::: From ddd21787d1f08c59383ee68f02101e0588254151 Mon Sep 17 00:00:00 2001 From: noise Date: Sat, 20 Jun 2026 11:31:24 +0800 Subject: [PATCH 5/5] docs(cn): dissolve the conflict --- api/expect.md | 2 +- api/vi.md | 2 +- config/outputfile.md | 7 +------ guide/index.md | 3 --- guide/learn/mock-functions.md | 8 ++------ guide/migration.md | 11 ++--------- guide/mocking/functions.md | 8 ++------ guide/recipes/conditional-mocking.md | 1 + guide/reporters.md | 12 ++---------- guide/ui.md | 6 +----- 10 files changed, 13 insertions(+), 47 deletions(-) diff --git a/api/expect.md b/api/expect.md index 7de346d67..dac621f2f 100644 --- a/api/expect.md +++ b/api/expect.md @@ -1454,7 +1454,7 @@ test('spy function returns bananas on second call', async () => { expect(sell).toHaveNthResolvedWith(2, { product: 'bananas' }) }) ``` - + ## toHaveBeenExhausted 5.0.0 {#tohavebeenexhausted} - **Type:** `() => void` diff --git a/api/vi.md b/api/vi.md index 333e48a8d..b8719a415 100644 --- a/api/vi.md +++ b/api/vi.md @@ -800,7 +800,7 @@ globalThis.IntersectionObserver === undefined // 抛出 ReferenceError,因为变量未定义 IntersectionObserver === undefined ``` - + ### vi.when 5.0.0 {#vi-when} ```ts diff --git a/config/outputfile.md b/config/outputfile.md index 1d2260757..62780b0d4 100644 --- a/config/outputfile.md +++ b/config/outputfile.md @@ -8,9 +8,4 @@ outline: deep - **类型:** `string | Record` - **命令行终端:** `--outputFile=`, `--outputFile.json=./path` -<<<<<<< HEAD -当指定 `--reporter=json`、`--reporter=html` 或 `--reporter=junit` 时,将测试结果写入文件。通过提供对象而非字符串,你可以在使用多个报告器时定义各自的输出配置。 -======= -Write test results to a file when the `--reporter=json` or `--reporter=junit` option is also specified. -By providing an object instead of a string you can define individual outputs when using multiple reporters. ->>>>>>> 83c20df03e0fda4bbcdebc819938fcb1ecab8778 +当指定 `--reporter=json` 或 `--reporter=junit` 时,将测试结果写入文件。通过提供对象而非字符串,你可以在使用多个报告器时定义各自的输出配置。 diff --git a/guide/index.md b/guide/index.md index f71196011..3e71a1211 100644 --- a/guide/index.md +++ b/guide/index.md @@ -38,13 +38,10 @@ pnpm add -D vitest ```bash [bun] bun add -D vitest ``` -<<<<<<< HEAD -======= ```bash [deno] deno add -D vitest ``` ->>>>>>> 83c20df03e0fda4bbcdebc819938fcb1ecab8778 ::: :::tip diff --git a/guide/learn/mock-functions.md b/guide/learn/mock-functions.md index 54ad99379..0f3c1f8ee 100644 --- a/guide/learn/mock-functions.md +++ b/guide/learn/mock-functions.md @@ -71,16 +71,12 @@ test('mock async return values', async () => { await expect(fetchUser()).rejects.toThrow('Not found') }) ``` - -<<<<<<< HEAD -## 模拟实现 {#mock-implementation} -======= + ::: tip `mockReturnValue` always returns the same value regardless of the arguments the mock receives. If you need argument-specific return values, [`vi.when`](/api/vi#vi-when) lets you attach different behaviors for different argument combinations without writing your own `if/else` logic. See the [Conditional Mocking](/guide/recipes/conditional-mocking) recipe for details. ::: -## Mock Implementation ->>>>>>> 83c20df03e0fda4bbcdebc819938fcb1ecab8778 +## 模拟实现 {#mock-implementation} 有时你需要的不只是一个固定的返回值。而是希望模拟函数能根据传入的参数真正执行一些逻辑。[`mockImplementation`](/api/mock#mockimplementation) 允许你提供一个完整的替代函数: diff --git a/guide/migration.md b/guide/migration.md index bc35035ee..324eae032 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -43,9 +43,6 @@ test('sort', async ({ bench }) => { // [!code ++] - **`benchmark.outputJson` config and the `--outputJson` CLI flag** are removed. Use `--reporter=json --outputFile=` to capture benchmark results; the JSON reporter now includes a `benchmarks` field on each test case. - **`Vitest` instance `mode` property** is now always `'test'`. The previous `'benchmark'` value is no longer used; benchmarks run inside a dedicated project of the same `Vitest` instance. -<<<<<<< HEAD -### 移除 `test.sequential`, `describe.sequential`, 和 `sequential` 选项 {##removed-test-sequential-describe-sequential-and-sequential-options} -======= ### Vitest UI Requires an Authenticated URL Vitest UI now requires token authentication for the HTML page and API access. The `/__vitest__/` URL will show an error until the browser is authenticated. To authenticate, open the URL with a token printed by Vitest, as shown below. Once authenticated, the direct `/__vitest__/` URL will work correctly. @@ -56,7 +53,7 @@ vitest --ui ``` ### Removed `test.sequential`, `describe.sequential`, and `sequential` Options ->>>>>>> 83c20df03e0fda4bbcdebc819938fcb1ecab8778 +### 移除 `test.sequential`, `describe.sequential`, 和 `sequential` 选项 {##removed-test-sequential-describe-sequential-and-sequential-options} Vitest 5.0 移除了已弃用的 `test.sequential`、`describe.sequential` 和 `sequential` 选项。当你需要让某个测试或测试套件不再沿用继承来的并发设置,或退出全局配置的并发时,请使用 `concurrent: false`。 @@ -178,9 +175,6 @@ Vitest no longer serves the browser orchestrator UI from a bare `/__vitest_test_ If you manually opened the browser preview by copying the Vite server URL or visiting `/__vitest_test__/` directly, use the URL opened or printed by Vitest instead. -<<<<<<< HEAD -## 迁移至 Vitest 4.0 {#vitest-4} -======= ### Generated Reports and Artifacts Use the `.vitest` Directory Vitest now uses a single `.vitest` directory at the project root as the shared artifact root, so one `.vitest` entry in `.gitignore` is enough. Defaults that moved this major: @@ -189,8 +183,7 @@ Vitest now uses a single `.vitest` directory at the project root as the shared a - **Blob reporter** and `--merge-reports`: `.vitest-reports/blob-*.json` → `.vitest/blob/blob-*.json` - **HTML reporter** ([`html`](/guide/reporters#html-reporter)): `html/index.html` → `.vitest/index.html`, and its option changed from `outputFile` (a file) to `outputDir` (a directory) -## Migrating to Vitest 4.0 {#vitest-4} ->>>>>>> 83c20df03e0fda4bbcdebc819938fcb1ecab8778 +## 迁移至 Vitest 4.0 {#vitest-4} ::: warning 前提条件 Vitest 4.0 要求 **Vite >= 6.0.0** 和 **Node.js >= 20.0.0**。 diff --git a/guide/mocking/functions.md b/guide/mocking/functions.md index 5d69eb350..d4670fe25 100644 --- a/guide/mocking/functions.md +++ b/guide/mocking/functions.md @@ -7,16 +7,12 @@ 如果你需要传递自定义函数实现作为参数或创建新的模拟实体,你可以使用 [`vi.fn()`](/api/vi#vi-fn) 来创建一个模拟函数。 `vi.spyOn` 和 `vi.fn` 都共享相同的方法。 - -<<<<<<< HEAD -## 示例 {#example} -======= + ::: tip If you need a mock to return different values depending on the arguments it receives, [`vi.when()`](/api/vi#vi-when) lets you define argument-specific behaviors without writing your own `if/else` logic. See the [Conditional Mocking](/guide/recipes/conditional-mocking) recipe for details. ::: -## Example ->>>>>>> 83c20df03e0fda4bbcdebc819938fcb1ecab8778 +## 示例 {#example} ```js import { afterEach, describe, expect, it, vi } from 'vitest' diff --git a/guide/recipes/conditional-mocking.md b/guide/recipes/conditional-mocking.md index 0fb8e190d..7d8c68dd0 100644 --- a/guide/recipes/conditional-mocking.md +++ b/guide/recipes/conditional-mocking.md @@ -2,6 +2,7 @@ title: Conditional Mocking with vi.when | Recipes --- + # Conditional Mocking with `vi.when` ::: tip Prerequisites diff --git a/guide/reporters.md b/guide/reporters.md index 45b59b73a..ab2a21b6a 100644 --- a/guide/reporters.md +++ b/guide/reporters.md @@ -57,11 +57,7 @@ export default defineConfig({ ## 报告器输出 {#reporter-output} -<<<<<<< HEAD -默认情况下,Vitest 的报告器会将输出打印到终端。当使用 `json`、`html` 或 `junit` 报告器时,你可以在 Vite 配置文件中或通过 CLI 加入 `outputFile` [配置选项](/config/outputfile),将测试输出写入文件。 -======= -By default, Vitest's reporters will print their output to the terminal. When using the `json` or `junit` reporters, you can instead write your tests' output to a file by including an `outputFile` [configuration option](/config/outputfile) either in your Vite configuration file or via CLI. The `html` reporter writes a report directory instead; see its [`outputDir`](#html-reporter) option. ->>>>>>> 83c20df03e0fda4bbcdebc819938fcb1ecab8778 +默认情况下,Vitest 的报告器会将输出打印到终端。当使用 `json` 或 `junit` 报告器时,你可以在 Vite 配置文件中或通过 CLI 加入 `outputFile` [配置选项](/config/outputfile),将测试输出写入文件。 :::code-group @@ -522,12 +518,8 @@ export default defineConfig({ ### HTML 报告器 {#html-reporter} 生成 HTML 文件,通过交互式 [GUI](/guide/ui) 查看测试结果。文件生成后,Vitest 将保持本地开发服务器运行,并提供一个链接,以便在浏览器中查看报告。 - -<<<<<<< HEAD -可使用 [`outputFile`](/config/outputfile) 配置选项指定输出文件。如果没有提供 `outputFile` 选项,则会创建一个新的 HTML 文件。 -======= + The report artifact root can be specified using the reporter's `outputDir` option. The report entry is written to `/index.html` and the UI assets files live under `/ui/`. By default `outputDir` is `.vitest`, the shared Vitest artifact directory, so attachments (`.vitest/attachments`) and coverage (`.vitest/coverage`) are reused without being copied. ->>>>>>> 83c20df03e0fda4bbcdebc819938fcb1ecab8778 :::code-group diff --git a/guide/ui.md b/guide/ui.md index 436249a07..62281ade0 100644 --- a/guide/ui.md +++ b/guide/ui.md @@ -53,12 +53,8 @@ export default defineConfig({ ```sh npx vite preview --outDir .vitest ``` - -<<<<<<< HEAD -你可以使用 [`outputFile`](/config/outputfile) 配置选项配置输出。你需要在那里指定 `.html` 路径。例如,`./html/index.html` 是默认值。 -======= + You can configure the output location with the HTML reporter's `outputDir` option. It points to the report artifact root, and the report entry is written to `/index.html`. The default value is `.vitest`, the shared Vitest artifact directory. ->>>>>>> 83c20df03e0fda4bbcdebc819938fcb1ecab8778 ::: If you need a portable report that can be opened or shared as one file, see [`singleFile`](/guide/reporters#html-reporter) in the HTML reporter documentation.