From 6f3d705d7e8d9ec25035018d208d774597f8b4b2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 08:50:29 +0200 Subject: [PATCH 01/17] fix(deps): update all non-major dependencies (#7867) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index b9705376..fc1e1cf6 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "devDependencies": { "@iconify-json/carbon": "catalog:", "@iconify-json/logos": "catalog:", - "@shikijs/transformers": "^3.2.2", - "@shikijs/vitepress-twoslash": "^3.2.2", + "@shikijs/transformers": "^3.3.0", + "@shikijs/vitepress-twoslash": "^3.3.0", "@unocss/reset": "catalog:", "@vite-pwa/assets-generator": "^0.2.6", "@vite-pwa/vitepress": "^0.5.4", @@ -32,7 +32,7 @@ "unplugin-vue-components": "catalog:", "vite": "^5.2.8", "vite-plugin-pwa": "^0.21.2", - "vitepress": "2.0.0-alpha.4", + "vitepress": "2.0.0-alpha.5", "vitepress-plugin-group-icons": "^1.5.2", "vitepress-plugin-tabs": "^0.7.0", "workbox-window": "^7.3.0" From 2905288f8f2b37e0300d71b736c56baca466592e Mon Sep 17 00:00:00 2001 From: Elijah Kennedy Date: Thu, 24 Apr 2025 12:20:51 -0400 Subject: [PATCH 02/17] docs: fix typo (#7886) --- api/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/index.md b/api/index.md index c5bc14c9..e9dfc47d 100644 --- a/api/index.md +++ b/api/index.md @@ -167,7 +167,7 @@ test('skipped test', (context) => { }) ``` -Since Vitest 3.1, if the condition is unknonwn, you can provide it to the `skip` method as the first arguments: +Since Vitest 3.1, if the condition is unknown, you can provide it to the `skip` method as the first arguments: ```ts import { assert, test } from 'vitest' From 43966ff8cad5638e0705efdd4841ffceadf53bbb Mon Sep 17 00:00:00 2001 From: Vladimir Date: Fri, 2 May 2025 14:05:35 +0200 Subject: [PATCH 03/17] docs: deprecate old context augmentation and recommend test.extend (#7703) --- advanced/runner.md | 2 +- api/index.md | 10 ---- guide/cli-generated.md | 7 +++ guide/test-context.md | 112 ++++++++++++++++++++++++++++++----------- 4 files changed, 90 insertions(+), 41 deletions(-) diff --git a/advanced/runner.md b/advanced/runner.md index 0e4e2be5..9ff366a9 100644 --- a/advanced/runner.md +++ b/advanced/runner.md @@ -209,7 +209,7 @@ interface Test extends TaskBase { */ file: File /** - * Whether the task was skipped by calling `t.skip()`. + * Whether the task was skipped by calling `context.skip()`. */ pending?: boolean /** diff --git a/api/index.md b/api/index.md index e9dfc47d..cd34e03a 100644 --- a/api/index.md +++ b/api/index.md @@ -1279,16 +1279,6 @@ test('performs an organization query', async () => { ::: tip This hook is always called in reverse order and is not affected by [`sequence.hooks`](/config/#sequence-hooks) option. - - -Note that this hook is not called if test was skipped with a dynamic `ctx.skip()` call: - -```ts{2} -test('skipped dynamically', (t) => { - onTestFinished(() => {}) // not called - t.skip() -}) -``` ::: ### onTestFailed diff --git a/guide/cli-generated.md b/guide/cli-generated.md index f0572dc3..172c980c 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -761,6 +761,13 @@ Omit annotation lines from the output (default: `false`) Print basic prototype Object and Array (default: `true`) +### diff.maxDepth + +- **CLI:** `--diff.maxDepth ` +- **Config:** [diff.maxDepth](/config/#diff-maxdepth) + +Limit the depth to recurse when printing nested objects (default: `20`) + ### diff.truncateThreshold - **CLI:** `--diff.truncateThreshold ` diff --git a/guide/test-context.md b/guide/test-context.md index de171f9b..ba2c3be4 100644 --- a/guide/test-context.md +++ b/guide/test-context.md @@ -14,19 +14,19 @@ The first argument for each test callback is a test context. ```ts import { it } from 'vitest' -it('should work', (ctx) => { +it('should work', ({ task }) => { // prints name of the test - console.log(ctx.task.name) + console.log(task.name) }) ``` ## Built-in Test Context -#### `context.task` +#### `task` A readonly object containing metadata about the test. -#### `context.expect` +#### `expect` The `expect` API bound to the current test: @@ -52,7 +52,12 @@ it.concurrent('math is hard', ({ expect }) => { }) ``` -#### `context.skip` +#### `skip` + +```ts +function skip(note?: string): never +function skip(condition: boolean, note?: string): void +``` Skips subsequent test execution and marks test as skipped: @@ -65,6 +70,23 @@ it('math is hard', ({ skip }) => { }) ``` +Since Vitest 3.1, it accepts a boolean parameter to skip the test conditionally: + +```ts +it('math is hard', ({ skip, mind }) => { + skip(mind === 'foggy') + expect(2 + 2).toBe(5) +}) +``` + +#### `onTestFailed` + +The [`onTestFailed`](/api/#ontestfailed) hook bound to the current test. This API is useful if you are running tests concurrently and need to have a special handling only for this specific test. + +#### `onTestFinished` + +The [`onTestFinished`](/api/#ontestfailed) hook bound to the current test. This API is useful if you are running tests concurrently and need to have a special handling only for this specific test. + ## Extend Test Context Vitest provides two different ways to help you extend the test context. @@ -73,15 +95,15 @@ Vitest provides two different ways to help you extend the test context. Like [Playwright](https://playwright.dev/docs/api/class-test#test-extend), you can use this method to define your own `test` API with custom fixtures and reuse it anywhere. -For example, we first create `myTest` with two fixtures, `todos` and `archive`. +For example, we first create the `test` collector with two fixtures: `todos` and `archive`. ```ts [my-test.ts] -import { test } from 'vitest' +import { test as baseTest } from 'vitest' const todos = [] const archive = [] -export const myTest = test.extend({ +export const test = baseTest.extend({ todos: async ({}, use) => { // setup the fixture before each test function todos.push(1, 2, 3) @@ -100,16 +122,16 @@ Then we can import and use it. ```ts [my-test.test.ts] import { expect } from 'vitest' -import { myTest } from './my-test.js' +import { test } from './my-test.js' -myTest('add items to todos', ({ todos }) => { +test('add items to todos', ({ todos }) => { expect(todos.length).toBe(3) todos.push(4) expect(todos.length).toBe(4) }) -myTest('move items from todos to archive', ({ todos, archive }) => { +test('move items from todos to archive', ({ todos, archive }) => { expect(todos.length).toBe(3) expect(archive.length).toBe(0) @@ -119,10 +141,12 @@ myTest('move items from todos to archive', ({ todos, archive }) => { }) ``` -We can also add more fixtures or override existing fixtures by extending `myTest`. +We can also add more fixtures or override existing fixtures by extending our `test`. ```ts -export const myTest2 = myTest.extend({ +import { test as todosTest } from './my-test.js' + +export const test = todosTest.extend({ settings: { // ... } @@ -134,34 +158,35 @@ export const myTest2 = myTest.extend({ Vitest runner will smartly initialize your fixtures and inject them into the test context based on usage. ```ts -import { test } from 'vitest' +import { test as baseTest } from 'vitest' -async function todosFn({ task }, use) { - await use([1, 2, 3]) -} - -const myTest = test.extend({ - todos: todosFn, +const test = baseTest.extend<{ + todos: number[] + archive: number[] +}>({ + todos: async ({ task }, use) => { + await use([1, 2, 3]) + }, archive: [] }) -// todosFn will not run -myTest('', () => {}) -myTest('', ({ archive }) => {}) +// todos will not run +test('skip', () => {}) +test('skip', ({ archive }) => {}) -// todosFn will run -myTest('', ({ todos }) => {}) +// todos will run +test('run', ({ todos }) => {}) ``` ::: warning When using `test.extend()` with fixtures, you should always use the object destructuring pattern `{ todos }` to access context both in fixture function and test function. ```ts -myTest('context must be destructured', (context) => { // [!code --] +test('context must be destructured', (context) => { // [!code --] expect(context.todos.length).toBe(2) }) -myTest('context must be destructured', ({ todos }) => { // [!code ++] +test('context must be destructured', ({ todos }) => { // [!code ++] expect(todos.length).toBe(2) }) ``` @@ -316,19 +341,46 @@ interface MyFixtures { archive: number[] } -const myTest = test.extend({ +const test = baseTest.extend({ todos: [], archive: [] }) -myTest('types are defined correctly', ({ todos, archive }) => { +test('types are defined correctly', ({ todos, archive }) => { expectTypeOf(todos).toEqualTypeOf() expectTypeOf(archive).toEqualTypeOf() }) ``` +::: info Type Infering +Note that Vitest doesn't support infering the types when the `use` function is called. It is always preferable to pass down the whole context type as the generic type when `test.extend` is called: + +```ts +import { test as baseTest } from 'vitest' + +const test = baseTest.extend<{ + todos: number[] + schema: string +}>({ + todos: ({ schema }, use) => use([]), + schema: 'test' +}) + +test('types are correct', ({ + todos, // number[] + schema, // string +}) => { + // ... +}) +``` +::: + ### `beforeEach` and `afterEach` +::: danger Deprecated +This is an outdated way of extending context and it will not work when the `test` is extended with `test.extend`. +::: + The contexts are different for each test. You can access and extend them within the `beforeEach` and `afterEach` hooks. ```ts @@ -346,7 +398,7 @@ it('should work', ({ foo }) => { #### TypeScript -To provide property types for all your custom contexts, you can aggregate the `TestContext` type by adding +To provide property types for all your custom contexts, you can augment the `TestContext` type by adding ```ts declare module 'vitest' { From aecfe2cae8e0e667c2a312a18e2f58f0791a8836 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 5 May 2025 16:14:10 +0200 Subject: [PATCH 04/17] fix(browser): resolve FS commands relative to the project root (#7896) --- guide/browser/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/browser/commands.md b/guide/browser/commands.md index 2350a3bd..01478320 100644 --- a/guide/browser/commands.md +++ b/guide/browser/commands.md @@ -11,7 +11,7 @@ Command is a function that invokes another function on the server and passes dow ### Files Handling -You can use `readFile`, `writeFile` and `removeFile` API to handle files inside your browser tests. All paths are resolved relative to the test file even if they are called in a helper function located in another file. +You can use the `readFile`, `writeFile`, and `removeFile` APIs to handle files in your browser tests. Since Vitest 3.2, all paths are resolved relative to the [project](/guide/workspace) root (which is `process.cwd()`, unless overriden manually). Previously, paths were resolved relative to the test file. By default, Vitest uses `utf-8` encoding but you can override it with options. From 4f3f3a16c5430f7eac462009a2b7da4bb83b7481 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 5 May 2025 16:16:46 +0200 Subject: [PATCH 05/17] feat: provide `ctx.signal` (#7878) --- guide/test-context.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/guide/test-context.md b/guide/test-context.md index ba2c3be4..6e8ba9fd 100644 --- a/guide/test-context.md +++ b/guide/test-context.md @@ -79,6 +79,21 @@ it('math is hard', ({ skip, mind }) => { }) ``` +#### `context.signal` 3.2.0 {#context-signal} + +An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that can be aborted by Vitest. The signal is aborted in these situations: + +- Test times out +- User manually cancelled the test run with Ctrl+C +- [`vitest.cancelCurrentRun`](/advanced/api/vitest#cancelcurrentrun) was called programmatically +- Another test failed in parallel and the [`bail`](/config/#bail) flag is set + +```ts +it('stop request when test times out', async ({ signal }) => { + await fetch('/resource', { signal }) +}, 2000) +``` + #### `onTestFailed` The [`onTestFailed`](/api/#ontestfailed) hook bound to the current test. This API is useful if you are running tests concurrently and need to have a special handling only for this specific test. From 2fd4f42aa5acf80b710cce546d4a77d8d80a5312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Mon, 5 May 2025 17:17:56 +0300 Subject: [PATCH 06/17] feat(coverage): v8 experimental AST-aware remapping (#7736) --- config/index.md | 10 +++++++++- guide/coverage.md | 11 ++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/config/index.md b/config/index.md index 2dd8cf96..4378db5b 100644 --- a/config/index.md +++ b/config/index.md @@ -1642,7 +1642,7 @@ Sets thresholds to 100 for files matching the glob pattern. - **Available for providers:** `'v8'` - **CLI:** `--coverage.ignoreEmptyLines=` -Ignore empty lines, comments and other non-runtime code, e.g. Typescript types. +Ignore empty lines, comments and other non-runtime code, e.g. Typescript types. Requires `experimentalAstAwareRemapping: false`. This option works only if the used compiler removes comments and other non-runtime code from the transpiled code. By default Vite uses ESBuild which removes comments and Typescript types from `.ts`, `.tsx` and `.jsx` files. @@ -1666,6 +1666,14 @@ export default defineConfig({ }, }) ``` +#### coverage.experimentalAstAwareRemapping + +- **Type:** `boolean` +- **Default:** `false` +- **Available for providers:** `'v8'` +- **CLI:** `--coverage.experimentalAstAwareRemapping=` + +Remap coverage with experimental AST based analysis. Provides more accurate results compared to default mode. #### coverage.ignoreClassMethods diff --git a/guide/coverage.md b/guide/coverage.md index 3e0aff07..90ff19f7 100644 --- a/guide/coverage.md +++ b/guide/coverage.md @@ -190,24 +190,21 @@ Both coverage providers have their own ways how to ignore code from coverage rep - [`v8`](https://github.com/istanbuljs/v8-to-istanbul#ignoring-uncovered-lines) - [`ìstanbul`](https://github.com/istanbuljs/nyc#parsing-hints-ignoring-lines) +- `v8` with [`experimentalAstAwareRemapping: true`](https://vitest.dev/config/#coverage-experimentalAstAwareRemapping) see [ast-v8-to-istanbul | Ignoring code](https://github.com/AriPerkkio/ast-v8-to-istanbul?tab=readme-ov-file#ignoring-code) When using TypeScript the source codes are transpiled using `esbuild`, which strips all comments from the source codes ([esbuild#516](https://github.com/evanw/esbuild/issues/516)). Comments which are considered as [legal comments](https://esbuild.github.io/api/#legal-comments) are preserved. -For `istanbul` provider you can include a `@preserve` keyword in the ignore hint. +You can include a `@preserve` keyword in the ignore hint. Beware that these ignore hints may now be included in final production build as well. ```diff -/* istanbul ignore if */ +/* istanbul ignore if -- @preserve */ if (condition) { -``` - -For `v8` this does not cause any issues. You can use `v8 ignore` comments with Typescript as usual: - -```ts -/* v8 ignore next 3 */ +-/* v8 ignore if */ ++/* v8 ignore if -- @preserve */ if (condition) { ``` From 9a5874b9b1d55c75460b6d2151f7e6ad4d651839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Mon, 5 May 2025 17:21:24 +0300 Subject: [PATCH 07/17] feat: support custom colors for `test.name` (#7809) --- config/index.md | 6 ++++-- guide/workspace.md | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/config/index.md b/config/index.md index 4378db5b..e10481d2 100644 --- a/config/index.md +++ b/config/index.md @@ -146,9 +146,11 @@ When defined, Vitest will run all matched files with `import.meta.vitest` inside ### name -- **Type:** `string` +- **Type:** `string | { label: string, color?: LabelColor }` + +Assign a custom name to the test project or Vitest process. The name will be visible in the CLI and UI, and available in the Node.js API via [`project.name`](/advanced/api/test-project#name). -Assign a custom name to the test project or Vitest process. The name will be visible in the CLI and available in the Node.js API via [`project.name`](/advanced/api/test-project#name). +Color used by CLI and UI can be changed by providing an object with `color` property. ### server {#server} diff --git a/guide/workspace.md b/guide/workspace.md index d44f6bf8..1980bc0e 100644 --- a/guide/workspace.md +++ b/guide/workspace.md @@ -102,7 +102,8 @@ export default defineConfig({ { test: { include: ['tests/**/*.{node}.test.{ts,js}'], - name: 'node', + // color of the name label can be changed + name: { label: 'node', color: 'green' }, environment: 'node', } } From 8706c77553e1b8b147d509250a01a40381198524 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 5 May 2025 23:21:42 +0900 Subject: [PATCH 08/17] feat: add `vi.mockObject` to automock any object (#7761) Co-authored-by: Vladimir --- api/vi.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/api/vi.md b/api/vi.md index d3cc13a1..1a4772df 100644 --- a/api/vi.md +++ b/api/vi.md @@ -390,6 +390,33 @@ expect(res).toBe(5) expect(getApples).toHaveNthReturnedWith(2, 5) ``` +### vi.mockObject 3.2.0 + +- **Type:** `(value: T) => MaybeMockedDeep` + +Deeply mocks properties and methods of a given object in the same way as `vi.mock()` mocks module exports. See [automocking](/guide/mocking.html#automocking-algorithm) for the detail. + +```ts +const original = { + simple: () => 'value', + nested: { + method: () => 'real' + }, + prop: 'foo', +} + +const mocked = vi.mockObject(original) +expect(mocked.simple()).toBe(undefined) +expect(mocked.nested.method()).toBe(undefined) +expect(mocked.prop).toBe('foo') + +mocked.simple.mockReturnValue('mocked') +mocked.nested.method.mockReturnValue('mocked nested') + +expect(mocked.simple()).toBe('mocked') +expect(mocked.nested.method()).toBe('mocked nested') +``` + ### vi.isMockFunction - **Type:** `(fn: Function) => boolean` From 0b7a91cb4d346e5b1a83aaf9d717701a83df1070 Mon Sep 17 00:00:00 2001 From: Florian Schwalm <68847951+egfx-notifications@users.noreply.github.com> Date: Mon, 5 May 2025 16:29:15 +0200 Subject: [PATCH 09/17] feat(browser): implement `connect` option for `playwright` browser provider (#7915) Co-authored-by: Vladimir --- guide/browser/playwright.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/guide/browser/playwright.md b/guide/browser/playwright.md index 985e693b..2601fcc6 100644 --- a/guide/browser/playwright.md +++ b/guide/browser/playwright.md @@ -16,9 +16,9 @@ Alternatively, you can also add it to `compilerOptions.types` field in your `tsc } ``` -Vitest opens a single page to run all tests in the same file. You can configure the `launch` and `context` properties in `instances`: +Vitest opens a single page to run all tests in the same file. You can configure the `launch`, `connect` and `context` properties in `instances`: -```ts{9-10} [vitest.config.ts] +```ts{9-11} [vitest.config.ts] import { defineConfig } from 'vitest/config' export default defineConfig({ @@ -28,6 +28,7 @@ export default defineConfig({ { browser: 'firefox', launch: {}, + connect: {}, context: {}, }, ], @@ -65,6 +66,14 @@ Vitest will ignore `launch.headless` option. Instead, use [`test.browser.headles Note that Vitest will push debugging flags to `launch.args` if [`--inspect`](/guide/cli#inspect) is enabled. ::: +## connect 3.2.0 {#connect} + +These options are directly passed down to `playwright[browser].connect` command. You can read more about the command and available arguments in the [Playwright documentation](https://playwright.dev/docs/api/class-browsertype#browser-type-connect). + +::: warning +Since this command connects to an existing Playwright server, any `launch` options will be ignored. +::: + ## context Vitest creates a new context for every test file by calling [`browser.newContext()`](https://playwright.dev/docs/api/class-browsercontext). You can configure this behaviour by specifying [custom arguments](https://playwright.dev/docs/api/class-apirequest#api-request-new-context). From 5c801866893617f44ef9dc4e37bdb65b5730ccc5 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 5 May 2025 16:48:33 +0200 Subject: [PATCH 10/17] feat: introduce `watchTriggerPatterns` option (#7778) --- config/index.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/config/index.md b/config/index.md index e10481d2..291d2181 100644 --- a/config/index.md +++ b/config/index.md @@ -697,6 +697,36 @@ In interactive environments, this is the default, unless `--run` is specified ex In CI, or when run from a non-interactive shell, "watch" mode is not the default, but can be enabled explicitly with this flag. +### watchTriggerPatterns 3.2.0 {#watchtriggerpatterns} + +- **Type:** `WatcherTriggerPattern[]` + +Vitest reruns tests based on the module graph which is populated by static and dynamic `import` statements. However, if you are reading from the file system or fetching from a proxy, then Vitest cannot detect those dependencies. + +To correctly rerun those tests, you can define a regex pattern and a function that retuns a list of test files to run. + +```ts +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + watchTriggerPatterns: [ + { + pattern: /^src\/(mailers|templates)\/(.*)\.(ts|html|txt)$/, + testToRun: (match) => { + // relative to the root value + return `./api/tests/mailers/${match[2]}.test.ts` + }, + }, + ], + }, +}) +``` + +::: warning +Returned files should be either absolute or relative to the root. Note that this is a global option, and it cannot be used inside of [project](/guide/workspace) configs. +::: + ### root - **Type:** `string` From dd5596aefa502cad5095c747f6701999eda5d05f Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 5 May 2025 17:41:18 +0200 Subject: [PATCH 11/17] docs: fix example --- config/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/index.md b/config/index.md index 291d2181..2512097f 100644 --- a/config/index.md +++ b/config/index.md @@ -713,7 +713,7 @@ export default defineConfig({ watchTriggerPatterns: [ { pattern: /^src\/(mailers|templates)\/(.*)\.(ts|html|txt)$/, - testToRun: (match) => { + testToRun: (id, match) => { // relative to the root value return `./api/tests/mailers/${match[2]}.test.ts` }, From a40aa270ad3cd61fb1e96ef429617b5bf222aa1a Mon Sep 17 00:00:00 2001 From: Romain Hamel Date: Mon, 5 May 2025 18:38:49 +0200 Subject: [PATCH 12/17] docs: use `extends` instead of `configFile` in `injectTestProjects` (#7860) --- advanced/api/plugin.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/advanced/api/plugin.md b/advanced/api/plugin.md index f609d118..6943b550 100644 --- a/advanced/api/plugin.md +++ b/advanced/api/plugin.md @@ -93,10 +93,10 @@ This methods accepts a config glob pattern, a filepath to the config or an inlin ```ts // inject a single project with a custom alias const newProjects = await injectTestProjects({ - // you can inherit the current project config by referencing `configFile` + // you can inherit the current project config by referencing `extends` // note that you cannot have a project with the name that already exists, // so it's a good practice to define a custom name - configFile: project.vite.config.configFile, + extends: project.vite.config.configFile, test: { name: 'my-custom-alias', alias: { @@ -117,7 +117,7 @@ Note that this will only affect projects injected with [`injectTestProjects`](#i ::: ::: tip Referencing the Current Config -If you want to keep the user configuration, you can specify the `configFile` property. All other properties will be merged with the user defined config. +If you want to keep the user configuration, you can specify the `extends` property. All other properties will be merged with the user defined config. The project's `configFile` can be accessed in Vite's config: `project.vite.config.configFile`. From e906203e1d3e1d29225b5dec3a750300e619a966 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 5 May 2025 18:49:26 +0200 Subject: [PATCH 13/17] feat: deprecate `workspace` in favor of `projects` (#7923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ari Perkkiö --- .vitepress/components/FeaturesList.vue | 2 +- .vitepress/config.ts | 4 +- advanced/api/index.md | 2 +- advanced/api/plugin.md | 4 +- advanced/api/test-project.md | 46 +++++---- advanced/api/vitest.md | 10 +- advanced/pool.md | 10 +- advanced/runner.md | 2 +- blog/vitest-3.md | 2 +- config/index.md | 27 ++++-- guide/browser/commands.md | 2 +- guide/browser/index.md | 72 +++++++------- guide/browser/multiple-setups.md | 2 +- guide/cli-generated.md | 4 +- guide/environment.md | 2 +- guide/index.md | 10 +- guide/{workspace.md => projects.md} | 128 ++++++------------------- guide/test-context.md | 52 +++++----- 18 files changed, 163 insertions(+), 218 deletions(-) rename guide/{workspace.md => projects.md} (52%) diff --git a/.vitepress/components/FeaturesList.vue b/.vitepress/components/FeaturesList.vue index 81be1d95..d77ef623 100644 --- a/.vitepress/components/FeaturesList.vue +++ b/.vitepress/components/FeaturesList.vue @@ -13,7 +13,7 @@ Workers multi-threading via Tinypool Benchmarking support with Tinybench Filtering, timeouts, concurrent for suite and tests - Workspace support + Projects support Jest-compatible Snapshot diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 12f617a1..d1dfecc0 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -477,8 +477,8 @@ function guide(): DefaultTheme.SidebarItem[] { link: '/guide/filtering', }, { - text: 'Workspace', - link: '/guide/workspace', + text: 'Test Projects', + link: '/guide/projects', }, { text: 'Reporters', diff --git a/advanced/api/index.md b/advanced/api/index.md index 8e6ab4cd..b1e8b1b1 100644 --- a/advanced/api/index.md +++ b/advanced/api/index.md @@ -119,7 +119,7 @@ If you pass down the config to the `startVitest` or `createVitest` APIs, Vitest ::: ::: warning -The `resolveConfig` doesn't resolve the `workspace`. To resolve workspace configs, Vitest needs an established Vite server. +The `resolveConfig` doesn't resolve `projects`. To resolve projects configs, Vitest needs an established Vite server. Also note that `viteConfig.test` will not be fully resolved. If you need Vitest config, use `vitestConfig` instead. ::: diff --git a/advanced/api/plugin.md b/advanced/api/plugin.md index 6943b550..039f9a9e 100644 --- a/advanced/api/plugin.md +++ b/advanced/api/plugin.md @@ -53,7 +53,7 @@ Vitest re-exports all Vite type-only imports via a `Vite` namespace, which you c ``` ::: -Unlike [`reporter.onInit`](/advanced/api/reporters#oninit), this hooks runs early in Vitest lifecycle allowing you to make changes to configuration like `coverage` and `reporters`. A more notable change is that you can manipulate the global config from a [workspace project](/guide/workspace) if your plugin is defined in the project and not in the global config. +Unlike [`reporter.onInit`](/advanced/api/reporters#oninit), this hooks runs early in Vitest lifecycle allowing you to make changes to configuration like `coverage` and `reporters`. A more notable change is that you can manipulate the global config from a [test project](/guide/projects) if your plugin is defined in the project and not in the global config. ## Context @@ -107,7 +107,7 @@ const newProjects = await injectTestProjects({ ``` ::: warning Projects are Filtered -Vitest filters projects during the config resolution, so if the user defined a filter, injected project might not be resolved unless it [matches the filter](./vitest#matchesprojectfilter). You can update the filter via the `vitest.config.project` option to always include your workspace project: +Vitest filters projects during the config resolution, so if the user defined a filter, injected project might not be resolved unless it [matches the filter](./vitest#matchesprojectfilter). You can update the filter via the `vitest.config.project` option to always include your test project: ```ts vitest.config.project.push('my-project-name') diff --git a/advanced/api/test-project.md b/advanced/api/test-project.md index 9cfc7d39..d1a2f4f9 100644 --- a/advanced/api/test-project.md +++ b/advanced/api/test-project.md @@ -4,10 +4,8 @@ title: TestProject # TestProject 3.0.0 {#testproject} -- **Alias**: `WorkspaceProject` before 3.0.0 - ::: warning -This guide describes the advanced Node.js API. If you just want to create a workspace, follow the ["Workspace"](/guide/workspace) guide. +This guide describes the advanced Node.js API. If you just want to define projects, follow the ["Test Projects"](/guide/projects) guide. ::: ## name @@ -26,28 +24,34 @@ vitest.projects.map(p => p.name) === [ 'custom' ] ``` -```ts [vitest.workspace.js] -export default [ - './packages/server', // has package.json with "@pkg/server" - './utils', // doesn't have a package.json file - { - // doesn't customize the name - test: { - pool: 'threads', - }, - }, - { - // customized the name - test: { - name: 'custom', - }, +```ts [vitest.config.js] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + projects: [ + './packages/server', // has package.json with "@pkg/server" + './utils', // doesn't have a package.json file + { + // doesn't customize the name + test: { + pool: 'threads', + }, + }, + { + // customized the name + test: { + name: 'custom', + }, + }, + ], }, -] +}) ``` ::: ::: info -If the [root project](/advanced/api/vitest#getroottestproject) is not part of a user workspace, its `name` will not be resolved. +If the [root project](/advanced/api/vitest#getroottestproject) is not part of user projects, its `name` will not be resolved. ::: ## vitest @@ -279,7 +283,7 @@ dynamicExample !== staticExample // ✅ ::: ::: info -Internally, Vitest uses this method to import global setups, custom coverage providers, workspace file, and custom reporters, meaning all of them share the same module graph as long as they belong to the same Vite server. +Internally, Vitest uses this method to import global setups, custom coverage providers and custom reporters, meaning all of them share the same module graph as long as they belong to the same Vite server. ::: ## onTestsRerun diff --git a/advanced/api/vitest.md b/advanced/api/vitest.md index 5933d637..4db4337d 100644 --- a/advanced/api/vitest.md +++ b/advanced/api/vitest.md @@ -64,7 +64,7 @@ Benchmark mode calls `bench` functions and throws an error, when it encounters ` ## config -The root (or global) config. If workspace feature is enabled, projects will reference this as `globalConfig`. +The root (or global) config. If projects are defined, they will reference this as `globalConfig`. ::: warning This is Vitest config, it doesn't extend _Vite_ config. It only has resolved values from the `test` property. @@ -101,9 +101,9 @@ Cache manager that stores information about latest test results and test file st ## projects -An array of [test projects](/advanced/api/test-project) that belong to the user's workspace. If the user did not specify a custom workspace, the workspace will only have a [root project](#getrootproject). +An array of [test projects](/advanced/api/test-project) that belong to user's projects. If the user did not specify a them, this array will only contain a [root project](#getrootproject). -Vitest will ensure that there is always at least one project in the workspace. If the user specifies a non-existent `--project` name, Vitest will throw an error. +Vitest will ensure that there is always at least one project in this array. If the user specifies a non-existent `--project` name, Vitest will throw an error before this array is defined. ## getRootProject @@ -111,7 +111,7 @@ Vitest will ensure that there is always at least one project in the workspace. I function getRootProject(): TestProject ``` -This returns the root test project. The root project generally doesn't run any tests and is not included in `vitest.projects` unless the user explicitly includes the root config in their workspace, or the workspace is not defined at all. +This returns the root test project. The root project generally doesn't run any tests and is not included in `vitest.projects` unless the user explicitly includes the root config in their configuration, or projects are not defined at all. The primary goal of the root project is to setup the global config. In fact, `rootProject.config` references `rootProject.globalConfig` and `vitest.config` directly: @@ -433,7 +433,7 @@ dynamicExample !== staticExample // ✅ ::: ::: info -Internally, Vitest uses this method to import global setups, custom coverage providers, workspace file, and custom reporters, meaning all of them share the same module graph as long as they belong to the same Vite server. +Internally, Vitest uses this method to import global setups, custom coverage providers, and custom reporters, meaning all of them share the same module graph as long as they belong to the same Vite server. ::: ## close diff --git a/advanced/pool.md b/advanced/pool.md index 35b0cfee..0485164b 100644 --- a/advanced/pool.md +++ b/advanced/pool.md @@ -31,12 +31,12 @@ export default defineConfig({ }) ``` -If you need to run tests in different pools, use the [workspace](/guide/workspace) feature: +If you need to run tests in different pools, use the [`projects`](/guide/projects) feature: ```ts [vitest.config.ts] export default defineConfig({ test: { - workspace: [ + projects: [ { extends: true, test: { @@ -48,10 +48,6 @@ export default defineConfig({ }) ``` -::: info -The `workspace` field was introduced in Vitest 3. To define a workspace in [Vitest 2](https://v2.vitest.dev/), create a separate `vitest.workspace.ts` file. -::: - ## API The file specified in `pool` option should export a function (can be async) that accepts `Vitest` interface as its first option. This function needs to return an object matching `ProcessPool` interface: @@ -69,7 +65,7 @@ export interface ProcessPool { The function is called only once (unless the server config was updated), and it's generally a good idea to initialize everything you need for tests inside that function and reuse it when `runTests` is called. -Vitest calls `runTest` when new tests are scheduled to run. It will not call it if `files` is empty. The first argument is an array of [TestSpecifications](/advanced/api/test-specification). Files are sorted using [`sequencer`](/config/#sequence-sequencer) before `runTests` is called. It's possible (but unlikely) to have the same file twice, but it will always have a different project - this is implemented via [`vitest.workspace.ts`](/guide/workspace) configuration. +Vitest calls `runTest` when new tests are scheduled to run. It will not call it if `files` is empty. The first argument is an array of [TestSpecifications](/advanced/api/test-specification). Files are sorted using [`sequencer`](/config/#sequence-sequencer) before `runTests` is called. It's possible (but unlikely) to have the same file twice, but it will always have a different project - this is implemented via [`projects`](/guide/projects) configuration. Vitest will wait until `runTests` is executed before finishing a run (i.e., it will emit [`onFinished`](/advanced/reporters) only after `runTests` is resolved). diff --git a/advanced/runner.md b/advanced/runner.md index 9ff366a9..5fbd5a5f 100644 --- a/advanced/runner.md +++ b/advanced/runner.md @@ -164,7 +164,7 @@ interface File extends Suite { */ filepath: string /** - * The name of the workspace project the file belongs to. + * The name of the test project the file belongs to. */ projectName: string | undefined /** diff --git a/blog/vitest-3.md b/blog/vitest-3.md index 25c27a50..455f6589 100644 --- a/blog/vitest-3.md +++ b/blog/vitest-3.md @@ -72,7 +72,7 @@ You can follow the design process in [#7069](https://github.com/vitest-dev/vites ## Inline Workspace -Rejoice! No more separate files to define your [workspace](/guide/workspace) - specify an array of projects using the `workspace` field in your `vitest.config` file: +Rejoice! No more separate files to define your [workspace](/guide/projects) - specify an array of projects using the `workspace` field in your `vitest.config` file: ```jsx import { defineConfig } from 'vitest/config' diff --git a/config/index.md b/config/index.md index 2512097f..dfe225fc 100644 --- a/config/index.md +++ b/config/index.md @@ -106,7 +106,7 @@ export default defineConfig({ Since Vitest uses Vite config, you can also use any configuration option from [Vite](https://vitejs.dev/config/). For example, `define` to define global variables, or `resolve.alias` to define aliases - these options should be defined on the top level, _not_ within a `test` property. -Configuration options that are not supported inside a [workspace](/guide/workspace) project config have sign next to them. This means they can only be set in the root Vitest config. +Configuration options that are not supported inside a [project](/guide/projects) config have sign next to them. This means they can only be set in the root Vitest config. ::: ### include @@ -588,7 +588,7 @@ These options are passed down to `setup` method of current [`environment`](#envi - **Default:** `[]` ::: danger DEPRECATED -This API was deprecated in Vitest 3. Use [workspace](/guide/workspace) to define different configurations instead. +This API was deprecated in Vitest 3. Use [projects](/guide/projects) to define different configurations instead. ```ts export default defineConfig({ @@ -596,7 +596,7 @@ export default defineConfig({ environmentMatchGlobs: [ // [!code --] ['./*.jsdom.test.ts', 'jsdom'], // [!code --] ], // [!code --] - workspace: [ // [!code ++] + projects: [ // [!code ++] { // [!code ++] extends: true, // [!code ++] test: { // [!code ++] @@ -635,7 +635,7 @@ export default defineConfig({ - **Default:** `[]` ::: danger DEPRECATED -This API was deprecated in Vitest 3. Use [workspace](/guide/workspace) to define different configurations instead: +This API was deprecated in Vitest 3. Use [projects](/guide/projects) to define different configurations instead: ```ts export default defineConfig({ @@ -643,7 +643,7 @@ export default defineConfig({ poolMatchGlobs: [ // [!code --] ['./*.threads.test.ts', 'threads'], // [!code --] ], // [!code --] - workspace: [ // [!code ++] + projects: [ // [!code ++] { // [!code ++] test: { // [!code ++] extends: true, // [!code ++] @@ -724,7 +724,7 @@ export default defineConfig({ ``` ::: warning -Returned files should be either absolute or relative to the root. Note that this is a global option, and it cannot be used inside of [project](/guide/workspace) configs. +Returned files should be either absolute or relative to the root. Note that this is a global option, and it cannot be used inside of [project](/guide/projects) configs. ::: ### root @@ -2436,14 +2436,25 @@ Tells fake timers to clear "native" (i.e. not fake) timers by delegating to thei ### workspace {#workspace} -- **Type:** `string | TestProjectConfiguration` +::: danger DEPRECATED +This options is deprecated and will be removed in the next major. Please, use [`projects`](#projects) instead. +::: + +- **Type:** `string | TestProjectConfiguration[]` - **CLI:** `--workspace=./file.js` - **Default:** `vitest.{workspace,projects}.{js,ts,json}` close to the config file or root -Path to a [workspace](/guide/workspace) config file relative to [root](#root). +Path to a [workspace](/guide/projects) config file relative to [root](#root). Since Vitest 3, you can also define the workspace array in the root config. If the `workspace` is defined in the config manually, Vitest will ignore the `vitest.workspace` file in the root. +### projects {#projects} + +- **Type:** `TestProjectConfiguration[]` +- **Default:** `[]` + +An array of [projects](/guide/projects). + ### isolate - **Type:** `boolean` diff --git a/guide/browser/commands.md b/guide/browser/commands.md index 01478320..c28cff3d 100644 --- a/guide/browser/commands.md +++ b/guide/browser/commands.md @@ -11,7 +11,7 @@ Command is a function that invokes another function on the server and passes dow ### Files Handling -You can use the `readFile`, `writeFile`, and `removeFile` APIs to handle files in your browser tests. Since Vitest 3.2, all paths are resolved relative to the [project](/guide/workspace) root (which is `process.cwd()`, unless overriden manually). Previously, paths were resolved relative to the test file. +You can use the `readFile`, `writeFile`, and `removeFile` APIs to handle files in your browser tests. Since Vitest 3.2, all paths are resolved relative to the [project](/guide/projects) root (which is `process.cwd()`, unless overriden manually). Previously, paths were resolved relative to the test file. By default, Vitest uses `utf-8` encoding but you can override it with options. diff --git a/guide/browser/index.md b/guide/browser/index.md index cf1d5806..2ce7499b 100644 --- a/guide/browser/index.md +++ b/guide/browser/index.md @@ -8,7 +8,7 @@ outline: deep This page provides information about the experimental browser mode feature in the Vitest API, which allows you to run your tests in the browser natively, providing access to browser globals like window and document. This feature is currently under development, and APIs may change in the future. ::: tip -If you are looking for documentation for `expect`, `vi` or any general API like workspaces or type testing, refer to the ["Getting Started" guide](/guide/). +If you are looking for documentation for `expect`, `vi` or any general API like test projects or type testing, refer to the ["Getting Started" guide](/guide/). ::: Vitest UI @@ -209,44 +209,48 @@ export default defineConfig({ ``` ::: -If you need to run some tests using Node-based runner, you can define a [workspace](/guide/workspace) file with separate configurations for different testing strategies: +If you need to run some tests using Node-based runner, you can define a [`projects`](/guide/projects) option with separate configurations for different testing strategies: -{#workspace-config} +{#projects-config} -```ts [vitest.workspace.ts] -import { defineWorkspace } from 'vitest/config' +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' -export default defineWorkspace([ - { - test: { - // an example of file based convention, - // you don't have to follow it - include: [ - 'tests/unit/**/*.{test,spec}.ts', - 'tests/**/*.unit.{test,spec}.ts', - ], - name: 'unit', - environment: 'node', - }, - }, - { - test: { - // an example of file based convention, - // you don't have to follow it - include: [ - 'tests/browser/**/*.{test,spec}.ts', - 'tests/**/*.browser.{test,spec}.ts', - ], - name: 'browser', - browser: { - enabled: true, - instances: [ - { browser: 'chromium' }, - ], +export default defineConfig({ + test: { + projects: [ + { + test: { + // an example of file based convention, + // you don't have to follow it + include: [ + 'tests/unit/**/*.{test,spec}.ts', + 'tests/**/*.unit.{test,spec}.ts', + ], + name: 'unit', + environment: 'node', + }, }, - }, + { + test: { + // an example of file based convention, + // you don't have to follow it + include: [ + 'tests/browser/**/*.{test,spec}.ts', + 'tests/**/*.browser.{test,spec}.ts', + ], + name: 'browser', + browser: { + enabled: true, + instances: [ + { browser: 'chromium' }, + ], + }, + }, + }, + ], }, -]) +}) ``` ## Browser Option Types diff --git a/guide/browser/multiple-setups.md b/guide/browser/multiple-setups.md index 5d572ee8..811227f3 100644 --- a/guide/browser/multiple-setups.md +++ b/guide/browser/multiple-setups.md @@ -2,7 +2,7 @@ Since Vitest 3, you can specify several different browser setups using the new [`browser.instances`](/guide/browser/config#browser-instances) option. -The main advantage of using the `browser.instances` over the [workspace](/guide/workspace) is improved caching. Every project will use the same Vite server meaning the file transform and [dependency pre-bundling](https://vite.dev/guide/dep-pre-bundling.html) has to happen only once. +The main advantage of using the `browser.instances` over the [test projects](/guide/projects) is improved caching. Every project will use the same Vite server meaning the file transform and [dependency pre-bundling](https://vite.dev/guide/dep-pre-bundling.html) has to happen only once. ## Several Browsers diff --git a/guide/cli-generated.md b/guide/cli-generated.md index 172c980c..8db3e449 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -291,7 +291,7 @@ Override Vite mode (default: `test` or `benchmark`) - **CLI:** `--workspace ` - **Config:** [workspace](/config/#workspace) -Path to a workspace configuration file +[deprecated] Path to a workspace configuration file ### isolate @@ -360,7 +360,7 @@ Set to true to exit if port is already in use, instead of automatically trying t - **CLI:** `--browser.provider ` - **Config:** [browser.provider](/guide/browser/config#browser-provider) -Provider used to run browser tests. Some browsers are only available for specific providers. Can be "webdriverio", "playwright", "preview", or the path to a custom provider. Visit [`browser.provider`](https://vitest.dev/config/#browser-provider) for more information (default: `"preview"`) +Provider used to run browser tests. Some browsers are only available for specific providers. Can be "webdriverio", "playwright", "preview", or the path to a custom provider. Visit [`browser.provider`](https://vitest.dev/guide/browser/config.html#browser-provider) for more information (default: `"preview"`) ### browser.providerOptions diff --git a/guide/environment.md b/guide/environment.md index 878ed48c..53cf4e3d 100644 --- a/guide/environment.md +++ b/guide/environment.md @@ -22,7 +22,7 @@ The `require` of CSS and assets inside the external dependencies are resolved au ::: warning "Environments" exist only when running tests in Node.js. -`browser` is not considered an environment in Vitest. If you wish to run part of your tests using [Browser Mode](/guide/browser/), you can create a [workspace project](/guide/browser/#workspace-config). +`browser` is not considered an environment in Vitest. If you wish to run part of your tests using [Browser Mode](/guide/browser/), you can create a [test project](/guide/browser/#projects-config). ::: ## Environments for Specific Files diff --git a/guide/index.md b/guide/index.md index 5aee7d03..92bac46a 100644 --- a/guide/index.md +++ b/guide/index.md @@ -175,17 +175,17 @@ export default defineConfig({ However, we recommend using the same file for both Vite and Vitest, instead of creating two separate files. ::: -## Workspaces Support +## Projects Support -Run different project configurations inside the same project with [Vitest Workspaces](/guide/workspace). You can define a list of files and folders that define your workspace in `vitest.config` file. +Run different project configurations inside the same project with [Test Projects](/guide/projects). You can define a list of files and folders that define your projects in `vitest.config` file. ```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' export default defineConfig({ test: { - workspace: [ - // you can use a list of glob patterns to define your workspaces + projects: [ + // you can use a list of glob patterns to define your projects // Vitest expects a list of config files // or directories where there is a config file 'packages/*', @@ -261,7 +261,7 @@ Learn more about [IDE Integrations](/guide/ide) | `sveltekit` | [GitHub](https://github.com/vitest-dev/vitest/tree/main/examples/sveltekit) | [Play Online](https://stackblitz.com/fork/github/vitest-dev/vitest/tree/main/examples/sveltekit?initialPath=__vitest__/) | | `profiling` | [GitHub](https://github.com/vitest-dev/vitest/tree/main/examples/profiling) | Not Available | | `typecheck` | [GitHub](https://github.com/vitest-dev/vitest/tree/main/examples/typecheck) | [Play Online](https://stackblitz.com/fork/github/vitest-dev/vitest/tree/main/examples/typecheck?initialPath=__vitest__/) | -| `workspace` | [GitHub](https://github.com/vitest-dev/vitest/tree/main/examples/workspace) | [Play Online](https://stackblitz.com/fork/github/vitest-dev/vitest/tree/main/examples/workspace?initialPath=__vitest__/) | +| `projects` | [GitHub](https://github.com/vitest-dev/vitest/tree/main/examples/projects) | [Play Online](https://stackblitz.com/fork/github/vitest-dev/vitest/tree/main/examples/projects?initialPath=__vitest__/) | ## Projects using Vitest diff --git a/guide/workspace.md b/guide/projects.md similarity index 52% rename from guide/workspace.md rename to guide/projects.md index 1980bc0e..2f14d642 100644 --- a/guide/workspace.md +++ b/guide/projects.md @@ -1,92 +1,75 @@ --- -title: Workspace | Guide +title: Test Projects | Guide --- -# Workspace +# Test Projects ::: tip Sample Project -[GitHub](https://github.com/vitest-dev/vitest/tree/main/examples/workspace) - [Play Online](https://stackblitz.com/fork/github/vitest-dev/vitest/tree/main/examples/workspace?initialPath=__vitest__/) +[GitHub](https://github.com/vitest-dev/vitest/tree/main/examples/projects) - [Play Online](https://stackblitz.com/fork/github/vitest-dev/vitest/tree/main/examples/projects?initialPath=__vitest__/) ::: +::: warning +This feature is also known as a `workspace`. The `workspace` is deprecated since 3.2 and replaced with the `projects` configuration. They are functionally the same. +::: + Vitest provides a way to define multiple project configurations within a single Vitest process. This feature is particularly useful for monorepo setups but can also be used to run tests with different configurations, such as `resolve.alias`, `plugins`, or `test.browser` and more. -## Defining a Workspace +## Defining Projects -Since Vitest 3, you can define a workspace in your root [config](/config/). In this case, Vitest will ignore the `vitest.workspace` file in the root, if one exists. +You can define projects in your root [config](/config/): ```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' export default defineConfig({ test: { - workspace: ['packages/*'], + projects: ['packages/*'], }, }) ``` -If you are using an older version, a workspace must include `vitest.workspace` or `vitest.projects` file in its root directory (located in the same folder as your root configuration file or working directory if it doesn't exist). Note that `projects` is just an alias and does not change the behavior or semantics of this feature. Vitest supports `ts`, `js`, and `json` extensions for this file. - -::: tip NAMING -Please note that this feature is named `workspace`, not `workspaces` (without an "s" at the end). -::: +Project configurations are inlined configs, files, or glob patterns referencing your projects. For example, if you have a folder named `packages` that contains your projects, you can define an array in your root Vitest config: -A workspace is a list of inlined configs, files, or glob patterns referencing your projects. For example, if you have a folder named `packages` that contains your projects, you can either create a workspace file or define an array in the root config: - -:::code-group -```ts [vitest.config.ts 3.0.0] +```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' export default defineConfig({ test: { - workspace: ['packages/*'], + projects: ['packages/*'], }, }) ``` -```ts [vitest.workspace.ts] -export default [ - 'packages/*' -] -``` -::: -Vitest will treat every folder in `packages` as a separate project even if it doesn't have a config file inside. If this glob pattern matches any file it will be considered a Vitest config even if it doesn't have a `vitest` in its name. +Vitest will treat every folder in `packages` as a separate project even if it doesn't have a config file inside. If this glob pattern matches _any file_, it will be considered a Vitest config even if it doesn't have a `vitest` in its name or has an obscure file extension. ::: warning -Vitest does not treat the root `vitest.config` file as a workspace project unless it is explicitly specified in the workspace configuration. Consequently, the root configuration will only influence global options such as `reporters` and `coverage`. Note that Vitest will always run certain plugin hooks, like `apply`, `config`, `configResolved` or `configureServer`, specified in the root config file. Vitest also uses the same plugins to execute global setups, workspace files and custom coverage provider. +Vitest does not treat the root `vitest.config` file as a project unless it is explicitly specified in the configuration. Consequently, the root configuration will only influence global options such as `reporters` and `coverage`. Note that Vitest will always run certain plugin hooks, like `apply`, `config`, `configResolved` or `configureServer`, specified in the root config file. Vitest also uses the same plugins to execute global setups and custom coverage provider. ::: You can also reference projects with their config files: -:::code-group -```ts [vitest.config.ts 3.0.0] +```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' export default defineConfig({ test: { - workspace: ['packages/*/vitest.config.{e2e,unit}.ts'], + projects: ['packages/*/vitest.config.{e2e,unit}.ts'], }, }) ``` -```ts [vitest.workspace.ts] -export default [ - 'packages/*/vitest.config.{e2e,unit}.ts' -] -``` -::: This pattern will only include projects with a `vitest.config` file that contains `e2e` or `unit` before the extension. -You can also define projects using inline configuration. The workspace configuration supports both syntaxes simultaneously. +You can also define projects using inline configuration. The configuration supports both syntaxes simultaneously. -:::code-group -```ts [vitest.config.ts 3.0.0] +```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' export default defineConfig({ test: { - workspace: [ + projects: [ // matches every folder and file inside the `packages` folder 'packages/*', { @@ -111,47 +94,12 @@ export default defineConfig({ } }) ``` -```ts [vitest.workspace.ts] -import { defineWorkspace } from 'vitest/config' - -// defineWorkspace provides a nice type hinting DX -export default defineWorkspace([ - // matches every folder and file inside the `packages` folder - 'packages/*', - { - // add "extends" to merge two configs together - extends: './vite.config.js', - test: { - include: ['tests/**/*.{browser}.test.{ts,js}'], - // it is recommended to define a name when using inline configs - name: 'happy-dom', - environment: 'happy-dom', - } - }, - { - test: { - include: ['tests/**/*.{node}.test.{ts,js}'], - name: 'node', - environment: 'node', - } - } -]) -``` -::: ::: warning All projects must have unique names; otherwise, Vitest will throw an error. If a name is not provided in the inline configuration, Vitest will assign a number. For project configurations defined with glob syntax, Vitest will default to using the "name" property in the nearest `package.json` file or, if none exists, the folder name. ::: -If you do not use inline configurations, you can create a small JSON file in your root directory or just specify it in the root config: - -```json [vitest.workspace.json] -[ - "packages/*" -] -``` - -Workspace projects do not support all configuration properties. For better type safety, use the `defineProject` method instead of `defineConfig` within project configuration files: +Projects do not support all configuration properties. For better type safety, use the `defineProject` method instead of `defineConfig` within project configuration files: ```ts twoslash [packages/a/vitest.config.ts] // @errors: 2769 @@ -169,7 +117,7 @@ export default defineProject({ ## Running tests -To run tests inside the workspace, define a script in your root `package.json`: +To run tests, define a script in your root `package.json`: ```json [package.json] { @@ -233,7 +181,7 @@ bun run test --project e2e --project unit ## Configuration -None of the configuration options are inherited from the root-level config file, even if the workspace is defined inside that config and not in a separate `vitest.workspace` file. You can create a shared config file and merge it with the project config yourself: +None of the configuration options are inherited from the root-level config file. You can create a shared config file and merge it with the project config yourself: ```ts [packages/a/vitest.config.ts] import { defineProject, mergeConfig } from 'vitest/config' @@ -249,10 +197,9 @@ export default mergeConfig( ) ``` -Additionally, at the `defineWorkspace` level, you can use the `extends` option to inherit from your root-level configuration. All options will be merged. +Additionally, you can use the `extends` option to inherit from your root-level configuration. All options will be merged. -::: code-group -```ts [vitest.config.ts 3.0.0] +```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' @@ -260,7 +207,7 @@ export default defineConfig({ plugins: [react()], test: { pool: 'threads', - workspace: [ + projects: [ { // will inherit options from this config like plugins and pool extends: true, @@ -282,32 +229,11 @@ export default defineConfig({ }, }) ``` -```ts [vitest.workspace.ts] -import { defineWorkspace } from 'vitest/config' - -export default defineWorkspace([ - { - extends: './vitest.config.ts', - test: { - name: 'unit', - include: ['**/*.unit.test.ts'], - }, - }, - { - extends: './vitest.config.ts', - test: { - name: 'integration', - include: ['**/*.integration.test.ts'], - }, - }, -]) -``` -::: ::: danger Unsupported Options Some of the configuration options are not allowed in a project config. Most notably: -- `coverage`: coverage is done for the whole workspace +- `coverage`: coverage is done for the whole process - `reporters`: only root-level reporters can be supported - `resolveSnapshotPath`: only root-level resolver is respected - all other options that don't affect test runners diff --git a/guide/test-context.md b/guide/test-context.md index 6e8ba9fd..0594f10f 100644 --- a/guide/test-context.md +++ b/guide/test-context.md @@ -232,7 +232,7 @@ test('works correctly') #### Default fixture -Since Vitest 3, you can provide different values in different [projects](/guide/workspace). To enable this feature, pass down `{ injected: true }` to the options. If the key is not specified in the [project configuration](/config/#provide), then the default value will be used. +Since Vitest 3, you can provide different values in different [projects](/guide/projects). To enable this feature, pass down `{ injected: true }` to the options. If the key is not specified in the [project configuration](/config/#provide), then the default value will be used. :::code-group ```ts [fixtures.test.ts] @@ -253,32 +253,36 @@ test('works correctly', ({ url }) => { // url is "/empty" in "project-empty" }) ``` -```ts [vitest.workspace.ts] -import { defineWorkspace } from 'vitest/config' - -export default defineWorkspace([ - { - test: { - name: 'project-new', - }, - }, - { - test: { - name: 'project-full', - provide: { - url: '/full', +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + projects: [ + { + test: { + name: 'project-new', + }, }, - }, - }, - { - test: { - name: 'project-empty', - provide: { - url: '/empty', + { + test: { + name: 'project-full', + provide: { + url: '/full', + }, + }, }, - }, + { + test: { + name: 'project-empty', + provide: { + url: '/empty', + }, + }, + }, + ], }, -]) +}) ``` ::: From 69ccc6e112792a4942a8fb64a596675cfe439b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ky=E2=84=93e=20Hensel?= Date: Tue, 6 May 2025 13:11:55 +1000 Subject: [PATCH 14/17] chore(snapshots): rename `message` to `hint` in method signatures (#7919) --- api/expect.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api/expect.md b/api/expect.md index 57af519d..f8ecd2ac 100644 --- a/api/expect.md +++ b/api/expect.md @@ -771,7 +771,7 @@ test('throws on pineapples', async () => { ## toMatchSnapshot -- **Type:** `(shape?: Partial | string, message?: string) => void` +- **Type:** `(shape?: Partial | string, hint?: string) => void` This ensures that a value matches the most recent snapshot. @@ -803,7 +803,7 @@ test('matches snapshot', () => { ## toMatchInlineSnapshot -- **Type:** `(shape?: Partial | string, snapshot?: string, message?: string) => void` +- **Type:** `(shape?: Partial | string, snapshot?: string, hint?: string) => void` This ensures that a value matches the most recent snapshot. @@ -846,7 +846,7 @@ test('matches snapshot', () => { ## toMatchFileSnapshot {#tomatchfilesnapshot} -- **Type:** `(filepath: string, message?: string) => Promise` +- **Type:** `(filepath: string, hint?: string) => Promise` Compare or update the snapshot with the content of a file explicitly specified (instead of the `.snap` file). @@ -863,13 +863,13 @@ Note that since file system operation is async, you need to use `await` with `to ## toThrowErrorMatchingSnapshot -- **Type:** `(message?: string) => void` +- **Type:** `(hint?: string) => void` The same as [`toMatchSnapshot`](#tomatchsnapshot), but expects the same value as [`toThrowError`](#tothrowerror). ## toThrowErrorMatchingInlineSnapshot -- **Type:** `(snapshot?: string, message?: string) => void` +- **Type:** `(snapshot?: string, hint?: string) => void` The same as [`toMatchInlineSnapshot`](#tomatchinlinesnapshot), but expects the same value as [`toThrowError`](#tothrowerror). From 8f0fb8c9ddb50f0d831434335a31432689f601cc Mon Sep 17 00:00:00 2001 From: Tanguy Krotoff Date: Tue, 6 May 2025 13:22:18 +0200 Subject: [PATCH 15/17] docs: fix toEqualTypeOf in testing-types.md (#7938) --- guide/testing-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/testing-types.md b/guide/testing-types.md index 560bd69b..d16efb10 100644 --- a/guide/testing-types.md +++ b/guide/testing-types.md @@ -97,7 +97,7 @@ This is because the TypeScript compiler needs to infer the typearg for the `.toE const one = valueFromFunctionOne({ some: { complex: inputs } }) const two = valueFromFunctionTwo({ some: { other: inputs } }) -expectTypeOf(one).toEqualTypeof() +expectTypeOf(one).toEqualTypeOf() ``` If you find it hard working with `expectTypeOf` API and figuring out errors, you can always use more simple `assertType` API: From 823cf17af8015884d0db3c210346c58f03e48a5d Mon Sep 17 00:00:00 2001 From: berzi <32619123+berzi@users.noreply.github.com> Date: Sun, 11 May 2025 09:54:35 +0200 Subject: [PATCH 16/17] docs: minor prose improvements to it.for documentation (#7956) --- api/index.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/api/index.md b/api/index.md index cd34e03a..a7958bad 100644 --- a/api/index.md +++ b/api/index.md @@ -461,13 +461,13 @@ You cannot use this syntax when using Vitest as [type checker](/guide/testing-ty - **Alias:** `it.for` -Alternative of `test.each` to provide [`TestContext`](/guide/test-context). +Alternative to `test.each` to provide [`TestContext`](/guide/test-context). -The difference from `test.each` is how array case is provided in the arguments. -Other non array case (including template string usage) works exactly same. +The difference from `test.each` lies in how arrays are provided in the arguments. +Non-array arguments to `test.for` (including template string usage) work exactly the same as for `test.each`. ```ts -// `each` spreads array case +// `each` spreads arrays test.each([ [1, 1, 2], [1, 2, 3], @@ -476,7 +476,7 @@ test.each([ expect(a + b).toBe(expected) }) -// `for` doesn't spread array case +// `for` doesn't spread arrays (notice the square brackets around the arguments) test.for([ [1, 1, 2], [1, 2, 3], @@ -486,7 +486,7 @@ test.for([ }) ``` -2nd argument is [`TestContext`](/guide/test-context) and it can be used for concurrent snapshot, for example, +The 2nd argument is [`TestContext`](/guide/test-context) and can be used for concurrent snapshots, for example: ```ts test.concurrent.for([ @@ -502,9 +502,9 @@ test.concurrent.for([ - **Type:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` -`bench` defines a benchmark. In Vitest terms benchmark is a function that defines a series of operations. Vitest runs this function multiple times to display different performance results. +`bench` defines a benchmark. In Vitest terms, benchmark is a function that defines a series of operations. Vitest runs this function multiple times to display different performance results. -Vitest uses [`tinybench`](https://github.com/tinylibs/tinybench) library under the hood, inheriting all its options that can be used as a third argument. +Vitest uses the [`tinybench`](https://github.com/tinylibs/tinybench) library under the hood, inheriting all its options that can be used as a third argument. ```ts import { bench } from 'vitest' From 7eae51ccf951498de39ec8f8bc8ef2202e8cbe84 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 13 May 2025 19:28:56 +0200 Subject: [PATCH 17/17] fix: try to catch unhandled error outside of a test (#7968) --- .vitepress/components/FeaturesList.vue | 3 +- guide/features.md | 57 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/.vitepress/components/FeaturesList.vue b/.vitepress/components/FeaturesList.vue index d77ef623..a82fdd4f 100644 --- a/.vitepress/components/FeaturesList.vue +++ b/.vitepress/components/FeaturesList.vue @@ -26,7 +26,8 @@ Code coverage via v8 or istanbul Rust-like in-source testing Type Testing via expect-type - Sharding support + Sharding Support + Reporting Uncaught Errors diff --git a/guide/features.md b/guide/features.md index 38b02e87..0a5312d8 100644 --- a/guide/features.md +++ b/guide/features.md @@ -259,3 +259,60 @@ export default defineConfig(({ mode }) => ({ }, })) ``` + +## Unhandled Errors + +By default, Vitest catches and reports all [unhandled rejections](https://developer.mozilla.org/en-US/docs/Web/API/Window/unhandledrejection_event), [uncaught exceptions](https://nodejs.org/api/process.html#event-uncaughtexception) (in Node.js) and [error](https://developer.mozilla.org/en-US/docs/Web/API/Window/error_event) events (in the [browser](/guide/browser/)). + +You can disable this behaviour by catching them manually. Vitest assumes the callback is handled by you and won't report the error. + +::: code-group +```ts [setup.node.js] +// in Node.js +process.on('unhandledRejection', () => { + // your own handler +}) + +process.on('uncaughtException', () => { + // your own handler +}) +``` +```ts [setup.browser.js] +// in the browser +window.addEventListener('error', () => { + // your own handler +}) + +window.addEventListener('unhandledrejection', () => { + // your own handler +}) +``` +::: + +Alternatively, you can also ignore reported errors with a [`dangerouslyIgnoreUnhandledErrors`](/config/#dangerouslyignoreunhandlederrors) option. Vitest will still report them, but they won't affect the test result (exit code won't be changed). + +If you need to test that error was not caught, you can create a test that looks like this: + +```ts +test('my function throws uncaught error', async ({ onTestFinished }) => { + onTestFinished(() => { + // if the event was never called during the test, + // make sure it's removed before the next test starts + process.removeAllListeners('unhandledrejection') + }) + + return new Promise((resolve, reject) => { + process.once('unhandledrejection', (error) => { + try { + expect(error.message).toBe('my error') + resolve() + } + catch (error) { + reject(error) + } + }) + + callMyFunctionThatRejectsError() + }) +}) +```