feat: deprecate workspace in favor of projects (#7923)

Co-authored-by: Ari Perkkiö <ari.perkkio@gmail.com>
This commit is contained in:
Vladimir 2025-05-05 18:49:26 +02:00 committed by GitHub
parent 5f50495843
commit 41beb261e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
92 changed files with 720 additions and 713 deletions

View File

@ -43,7 +43,7 @@ Next generation testing framework powered by Vite.
- Components testing ([Vue](https://github.com/vitest-tests/browser-examples/tree/main/examples/vue), [React](https://github.com/vitest-tests/browser-examples/tree/main/examples/react), [Svelte](https://github.com/vitest-tests/browser-examples/tree/main/examples/svelte), [Lit](./examples/lit), [Marko](https://github.com/marko-js/examples/tree/master/examples/library-ts))
- Workers multi-threading via [Tinypool](https://github.com/tinylibs/tinypool) (a lightweight fork of [Piscina](https://github.com/piscinajs/piscina))
- Benchmarking support with [Tinybench](https://github.com/tinylibs/tinybench)
- [Workspace](https://vitest.dev/guide/workspace) support
- [Projects](https://vitest.dev/guide/projects) support
- [expect-type](https://github.com/mmkal/expect-type) for type-level testing
- ESM first, top level await
- Out-of-box TypeScript / JSX support

View File

@ -13,7 +13,7 @@
<ListItem>Workers multi-threading via <a target="_blank" href="https://github.com/tinylibs/tinypool" rel="noopener noreferrer">Tinypool</a></ListItem>
<ListItem>Benchmarking support with <a target="_blank" href="https://github.com/tinylibs/tinybench" rel="noopener noreferrer">Tinybench</a></ListItem>
<ListItem>Filtering, timeouts, concurrent for suite and tests</ListItem>
<ListItem><a href="/guide/workspace">Workspace</a> support</ListItem>
<ListItem><a href="/guide/projects">Projects</a> support</ListItem>
<ListItem>
<a href="/guide/snapshot">
Jest-compatible Snapshot

View File

@ -477,8 +477,8 @@ function guide(): DefaultTheme.SidebarItem[] {
link: '/guide/filtering',
},
{
text: 'Workspace',
link: '/guide/workspace',
text: 'Test Projects',
link: '/guide/projects',
},
{
text: 'Reporters',

View File

@ -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.
:::

View File

@ -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')

View File

@ -4,10 +4,8 @@ title: TestProject
# TestProject <Version>3.0.0</Version> {#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',
},
```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',
},
},
],
},
{
// 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

View File

@ -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

View File

@ -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).

View File

@ -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
/**

View File

@ -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'

View File

@ -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 <NonProjectOption /> 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 <NonProjectOption /> 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<NonProjectOption /> {#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<NonProjectOption /> {#projects}
- **Type:** `TestProjectConfiguration[]`
- **Default:** `[]`
An array of [projects](/guide/projects).
### isolate
- **Type:** `boolean`

View File

@ -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.

View File

@ -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/).
:::
<img alt="Vitest UI" img-light src="/ui-browser-1-light.png">
@ -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

View File

@ -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

View File

@ -291,7 +291,7 @@ Override Vite mode (default: `test` or `benchmark`)
- **CLI:** `--workspace <path>`
- **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 <name>`
- **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

View File

@ -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

View File

@ -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

View File

@ -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.
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:
::: tip NAMING
Please note that this feature is named `workspace`, not `workspaces` (without an "s" at the end).
:::
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 <Version>3.0.0</Version>]
```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 <Version>3.0.0</Version>]
```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 <Version>3.0.0</Version>]
```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 <Version>3.0.0</Version>]
```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

View File

@ -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'
```ts [vitest.config.ts]
import { defineConfig } from 'vitest/config'
export default defineWorkspace([
{
test: {
name: 'project-new',
},
},
{
test: {
name: 'project-full',
provide: {
url: '/full',
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',
},
},
},
],
},
])
})
```
:::

View File

@ -1,5 +1,5 @@
{
"name": "@vitest/example-workspace",
"name": "@vitest/example-projects",
"type": "module",
"private": true,
"license": "MIT",

View File

@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import React from 'react'
import { expect, test } from 'vitest'
import Link from '../components/Link.jsx'
import Link from '../components/Link.js'
test('Link changes the state when hovered', async () => {
render(

View File

@ -1,6 +1,6 @@
import type { FastifyInstance } from 'fastify'
import Fastify from 'fastify'
import { usersData } from '../mockData'
import { usersData } from '../mockData.js'
const app: FastifyInstance = Fastify({
logger: process.env.NODE_ENV === 'development',

View File

@ -1,4 +1,4 @@
import app from './app'
import app from './app.js'
async function start() {
try {

View File

@ -1,8 +1,8 @@
import supertest from 'supertest'
import { afterAll, expect, test } from 'vitest'
import { usersData } from '../mockData'
import app from '../src/app'
import { usersData } from '../mockData.js'
import app from '../src/app.js'
test('with HTTP injection', async () => {
const response = await app.inject({

View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
projects: ['packages/*'],
},
})

View File

@ -1,5 +0,0 @@
import { defineWorkspace } from 'vitest/config'
export default defineWorkspace([
'packages/*',
])

View File

@ -25,6 +25,16 @@ from = "/config/file"
to = "/config/"
status = 301
[[redirects]]
from = "/guide/workspace"
to = "/guide/projects"
status = 301
[[redirects]]
from = "/guide/workspace.html"
to = "/guide/projects"
status = 301
[[headers]]
for = "/manifest.webmanifest"

View File

@ -332,7 +332,7 @@ export const cliOptionsConfig: VitestCLIOptions = {
argument: '<name>',
},
workspace: {
description: 'Path to a workspace configuration file',
description: '[deprecated] Path to a workspace configuration file',
argument: '<path>',
normalize: true,
},
@ -863,6 +863,7 @@ export const cliOptionsConfig: VitestCLIOptions = {
json: null,
provide: null,
filesOnly: null,
projects: null,
watchTriggerPatterns: null,
}

View File

@ -15,7 +15,7 @@ import { promises as fs } from 'node:fs'
import { getTasks, hasFailed } from '@vitest/runner/utils'
import { SnapshotManager } from '@vitest/snapshot/manager'
import { noop, toArray } from '@vitest/utils'
import { dirname, join, normalize, relative } from 'pathe'
import { dirname, join, normalize, relative, resolve } from 'pathe'
import { ViteNodeRunner } from 'vite-node/client'
import { ViteNodeServer } from 'vite-node/server'
import { version } from '../../package.json' with { type: 'json' }
@ -33,6 +33,7 @@ import { Logger } from './logger'
import { VitestPackageInstaller } from './packageInstaller'
import { createPool } from './pool'
import { TestProject } from './project'
import { getDefaultTestProject, resolveBrowserProjects, resolveProjects } from './projects/resolveProjects'
import { BlobReporter, readBlobs } from './reporters/blob'
import { HangingProcessReporter } from './reporters/hanging-process'
import { createBenchmarkReporters, createReporters } from './reporters/utils'
@ -40,7 +41,6 @@ import { VitestSpecifications } from './specifications'
import { StateManager } from './state'
import { TestRun } from './test-run'
import { VitestWatcher } from './watcher'
import { getDefaultTestProject, resolveBrowserWorkspace, resolveWorkspace } from './workspace/resolveWorkspace'
const WATCHER_DEBOUNCE = 100
@ -274,7 +274,7 @@ export class Vitest {
}
catch { }
const projects = await this.resolveWorkspace(cliOptions)
const projects = await this.resolveProjects(cliOptions)
this.resolvedProjects = projects
this.projects = projects
@ -325,15 +325,15 @@ export class Vitest {
*/
private injectTestProject = async (config: TestProjectConfiguration | TestProjectConfiguration[]): Promise<TestProject[]> => {
const currentNames = new Set(this.projects.map(p => p.name))
const workspace = await resolveWorkspace(
const projects = await resolveProjects(
this,
this._options,
undefined,
Array.isArray(config) ? config : [config],
currentNames,
)
this.projects.push(...workspace)
return workspace
this.projects.push(...projects)
return projects
}
/**
@ -423,11 +423,30 @@ export class Vitest {
return join(configDir, workspaceConfigName)
}
private async resolveWorkspace(cliOptions: UserConfig): Promise<TestProject[]> {
private async resolveProjects(cliOptions: UserConfig): Promise<TestProject[]> {
const names = new Set<string>()
if (this.config.projects) {
if (typeof this.config.workspace !== 'undefined') {
this.logger.warn(
'Both `config.projects` and `config.workspace` are defined. Ignoring the `workspace` option.',
)
}
return resolveProjects(
this,
cliOptions,
undefined,
this.config.projects,
names,
)
}
if (Array.isArray(this.config.workspace)) {
return resolveWorkspace(
this.logger.deprecate(
'The `workspace` option is deprecated and will be removed in the next major. To hide this warning, rename `workspace` option to `projects`.',
)
return resolveProjects(
this,
cliOptions,
undefined,
@ -448,9 +467,17 @@ export class Vitest {
if (!project) {
return []
}
return resolveBrowserWorkspace(this, new Set([project.name]), [project])
return resolveBrowserProjects(this, new Set([project.name]), [project])
}
const configFile = this.vite.config.configFile
? resolve(this.vite.config.root, this.vite.config.configFile)
: 'the root config file'
this.logger.deprecate(
`The workspace file is deprecated and will be removed in the next major. Please, use the \`projects\` field in ${configFile} instead.`,
)
const workspaceModule = await this.import<{
default: ReturnType<typeof defineWorkspace>
}>(workspaceConfigPath)
@ -459,7 +486,7 @@ export class Vitest {
throw new TypeError(`Workspace config file "${workspaceConfigPath}" must export a default array of project paths.`)
}
return resolveWorkspace(
return resolveProjects(
this,
cliOptions,
workspaceConfigPath,

View File

@ -110,6 +110,10 @@ export class Logger {
printError(err, this.ctx, this, options)
}
deprecate(message: string): void {
this.log(c.bold(c.bgYellow(' DEPRECATED ')), c.yellow(message))
}
clearHighlightCache(filename?: string): void {
if (filename) {
this._highlights.delete(filename)

View File

@ -70,7 +70,7 @@ export function getFilePoolName(project: TestProject, file: string): Pool {
for (const [glob, pool] of project.config.poolMatchGlobs) {
if ((pool as Pool) === 'browser') {
throw new Error(
'Since Vitest 0.31.0 "browser" pool is not supported in "poolMatchGlobs". You can create a workspace to run some of your tests in browser in parallel. Read more: https://vitest.dev/guide/workspace',
'Since Vitest 0.31.0 "browser" pool is not supported in "poolMatchGlobs". You can create a project to run some of your tests in browser in parallel. Read more: https://vitest.dev/guide/projects',
)
}
if (mm.isMatch(file, glob, { cwd: project.config.root })) {

View File

@ -14,17 +14,17 @@ import { VitestFilteredOutProjectError } from '../errors'
import { initializeProject, TestProject } from '../project'
import { withLabel } from '../reporters/renderers/utils'
export async function resolveWorkspace(
export async function resolveProjects(
vitest: Vitest,
cliOptions: UserConfig,
workspaceConfigPath: string | undefined,
workspaceDefinition: TestProjectConfiguration[],
projectsDefinition: TestProjectConfiguration[],
names: Set<string>,
): Promise<TestProject[]> {
const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs(
vitest,
workspaceConfigPath,
workspaceDefinition,
projectsDefinition,
)
// cli options that affect the project config,
@ -109,7 +109,7 @@ export async function resolveWorkspace(
[
'No projects were found. Make sure your configuration is correct. ',
vitest.config.project.length ? `The filter matched no projects: ${vitest.config.project.join(', ')}. ` : '',
`The workspace: ${JSON.stringify(workspaceDefinition, null, 4)}.`,
`The projects definition: ${JSON.stringify(projectsDefinition, null, 4)}.`,
].join(''),
)
}
@ -135,7 +135,7 @@ export async function resolveWorkspace(
if (errors.length) {
throw new AggregateError(
errors,
'Failed to initialize projects. There were errors during workspace setup. See below for more details.',
'Failed to initialize projects. There were errors during projects setup. See below for more details.',
)
}
@ -157,16 +157,16 @@ export async function resolveWorkspace(
' is not unique.',
duplicate?.vite.config.configFile ? ` The project is already defined by "${relative(vitest.config.root, duplicate.vite.config.configFile)}".` : '',
filesError,
'All projects in a workspace should have unique names. Make sure your configuration is correct.',
'All projects should have unique names. Make sure your configuration is correct.',
].join(''))
}
names.add(name)
}
return resolveBrowserWorkspace(vitest, names, resolvedProjects)
return resolveBrowserProjects(vitest, names, resolvedProjects)
}
export async function resolveBrowserWorkspace(
export async function resolveBrowserProjects(
vitest: Vitest,
names: Set<string>,
resolvedProjects: TestProject[],
@ -184,7 +184,7 @@ export async function resolveBrowserWorkspace(
browser,
name: project.name ? `${project.name} (${browser})` : browser,
})
console.warn(
vitest.logger.warn(
withLabel(
'yellow',
'Vitest',
@ -237,7 +237,7 @@ export async function resolveBrowserWorkspace(
[
`Cannot define a nested project for a ${browser} browser. The project name "${name}" was already defined. `,
'If you have multiple instances for the same browser, make sure to define a custom "name". ',
'All projects in a workspace should have unique names. Make sure your configuration is correct.',
'All projects should have unique names. Make sure your configuration is correct.',
].join(''),
)
}
@ -322,21 +322,21 @@ function cloneConfig(project: TestProject, { browser, ...config }: BrowserInstan
async function resolveTestProjectConfigs(
vitest: Vitest,
workspaceConfigPath: string | undefined,
workspaceDefinition: TestProjectConfiguration[],
projectsDefinition: TestProjectConfiguration[],
) {
// project configurations that were specified directly
const projectsOptions: (UserWorkspaceConfig & { extends?: true | string })[] = []
// custom config files that were specified directly or resolved from a directory
const workspaceConfigFiles: string[] = []
const projectsConfigFiles: string[] = []
// custom glob matches that should be resolved as directories or config files
const workspaceGlobMatches: string[] = []
const projectsGlobMatches: string[] = []
// directories that don't have a config file inside, but should be treated as projects
const nonConfigProjectDirectories: string[] = []
for (const definition of workspaceDefinition) {
for (const definition of projectsDefinition) {
if (typeof definition === 'string') {
const stringOption = definition.replace('<rootDir>', vitest.config.root)
// if the string doesn't contain a glob, we can resolve it directly
@ -345,23 +345,23 @@ async function resolveTestProjectConfigs(
const file = resolve(vitest.config.root, stringOption)
if (!existsSync(file)) {
const relativeWorkSpaceConfigPath = workspaceConfigPath
const relativeWorkspaceConfigPath = workspaceConfigPath
? relative(vitest.config.root, workspaceConfigPath)
: undefined
const note = workspaceConfigPath ? `Workspace config file "${relativeWorkSpaceConfigPath}"` : 'Inline workspace'
const note = workspaceConfigPath ? `Workspace config file "${relativeWorkspaceConfigPath}"` : 'Projects definition'
throw new Error(`${note} references a non-existing file or a directory: ${file}`)
}
const stats = await fs.stat(file)
// user can specify a config file directly
if (stats.isFile()) {
workspaceConfigFiles.push(file)
projectsConfigFiles.push(file)
}
// user can specify a directory that should be used as a project
else if (stats.isDirectory()) {
const configFile = await resolveDirectoryConfig(file)
if (configFile) {
workspaceConfigFiles.push(configFile)
projectsConfigFiles.push(configFile)
}
else {
const directory = file[file.length - 1] === '/' ? file : `${file}/`
@ -376,7 +376,7 @@ async function resolveTestProjectConfigs(
// if the string is a glob pattern, resolve it later
// ['./packages/*']
else {
workspaceGlobMatches.push(stringOption)
projectsGlobMatches.push(stringOption)
}
}
// if the config is inlined, we can resolve it immediately
@ -394,7 +394,7 @@ async function resolveTestProjectConfigs(
}
}
if (workspaceGlobMatches.length) {
if (projectsGlobMatches.length) {
const globOptions: GlobOptions = {
absolute: true,
dot: true,
@ -410,27 +410,27 @@ async function resolveTestProjectConfigs(
],
}
const workspacesFs = await glob(workspaceGlobMatches, globOptions)
const projectsFs = await glob(projectsGlobMatches, globOptions)
await Promise.all(workspacesFs.map(async (path) => {
await Promise.all(projectsFs.map(async (path) => {
// directories are allowed with a glob like `packages/*`
// in this case every directory is treated as a project
if (path.endsWith('/')) {
const configFile = await resolveDirectoryConfig(path)
if (configFile) {
workspaceConfigFiles.push(configFile)
projectsConfigFiles.push(configFile)
}
else {
nonConfigProjectDirectories.push(path)
}
}
else {
workspaceConfigFiles.push(path)
projectsConfigFiles.push(path)
}
}))
}
const projectConfigFiles = Array.from(new Set(workspaceConfigFiles))
const projectConfigFiles = Array.from(new Set(projectsConfigFiles))
return {
projectConfigs: projectsOptions,

View File

@ -1,6 +1,5 @@
import type { File } from '@vitest/runner'
import type { Vitest } from '../core'
import c from 'tinyrainbow'
import { BaseReporter } from './base'
export class BasicReporter extends BaseReporter {
@ -12,11 +11,11 @@ export class BasicReporter extends BaseReporter {
onInit(ctx: Vitest): void {
super.onInit(ctx)
ctx.logger.log(c.bold(c.bgYellow(' DEPRECATED ')), c.yellow(
ctx.logger.deprecate(
`'basic' reporter is deprecated and will be removed in Vitest v3.\n`
+ `Remove 'basic' from 'reporters' option. To match 'basic' reporter 100%, use configuration:\n${
JSON.stringify({ test: { reporters: [['default', { summary: false }]] } }, null, 2)}`,
))
)
}
reportSummary(files: File[], errors: unknown[]): void {

View File

@ -320,7 +320,7 @@ export interface InlineConfig {
*
* Format: [glob, environment-name]
*
* @deprecated use [`workspace`](https://vitest.dev/config/#environmentmatchglobs) instead
* @deprecated use [`projects`](https://vitest.dev/config/#projects) instead
* @default []
* @example [
* // all tests in tests/dom will run in jsdom
@ -377,7 +377,7 @@ export interface InlineConfig {
*
* Format: [glob, pool-name]
*
* @deprecated use [`workspace`](https://vitest.dev/config/#poolmatchglobs) instead
* @deprecated use [`projects`](https://vitest.dev/config/#projects) instead
* @default []
* @example [
* // all tests in "forks" directory will run using "poolOptions.forks" API
@ -388,8 +388,14 @@ export interface InlineConfig {
*/
poolMatchGlobs?: [string, Exclude<Pool, 'browser'>][]
/**
* Options for projects
*/
projects?: TestProjectConfiguration[]
/**
* Path to a workspace configuration file
* @deprecated use `projects` instead
*/
workspace?: string | TestProjectConfiguration[]

View File

@ -66,6 +66,9 @@ export function defineProject(config: UserProjectConfigExport): UserProjectConfi
return config
}
/**
* @deprecated use the `projects` field in the root config instead
*/
export function defineWorkspace(config: TestProjectConfiguration[]): TestProjectConfiguration[] {
return config
}

76
pnpm-lock.yaml generated
View File

@ -382,25 +382,7 @@ importers:
specifier: workspace:*
version: link:../../packages/vitest
examples/typecheck:
devDependencies:
'@types/node':
specifier: ^20.17.30
version: 20.17.30
'@vitest/ui':
specifier: workspace:*
version: link:../../packages/ui
typescript:
specifier: ^5.8.3
version: 5.8.3
vite:
specifier: ^6.2.0
version: 6.2.0(@types/node@20.17.30)(jiti@2.4.2)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vitest:
specifier: workspace:*
version: link:../../packages/vitest
examples/workspace:
examples/projects:
devDependencies:
'@testing-library/jest-dom':
specifier: ^6.6.3
@ -442,6 +424,24 @@ importers:
specifier: workspace:*
version: link:../../packages/vitest
examples/typecheck:
devDependencies:
'@types/node':
specifier: ^20.17.30
version: 20.17.30
'@vitest/ui':
specifier: workspace:*
version: link:../../packages/ui
typescript:
specifier: ^5.8.3
version: 5.8.3
vite:
specifier: ^6.2.0
version: 6.2.0(@types/node@20.17.30)(jiti@2.4.2)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vitest:
specifier: workspace:*
version: link:../../packages/vitest
packages/browser:
dependencies:
'@testing-library/dom':
@ -5476,9 +5476,6 @@ packages:
resolution: {integrity: sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
decimal.js@10.4.3:
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
decimal.js@10.5.0:
resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
@ -8768,10 +8765,6 @@ packages:
tr46@1.0.1:
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
tr46@5.0.0:
resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
engines: {node: '>=18'}
tr46@5.1.0:
resolution: {integrity: sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==}
engines: {node: '>=18'}
@ -9342,10 +9335,6 @@ packages:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
engines: {node: '>=18'}
whatwg-url@14.1.0:
resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==}
engines: {node: '>=18'}
whatwg-url@14.2.0:
resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
engines: {node: '>=18'}
@ -11891,24 +11880,24 @@ snapshots:
'@types/babel__core@7.20.5':
dependencies:
'@babel/parser': 7.26.2
'@babel/types': 7.26.0
'@babel/parser': 7.27.0
'@babel/types': 7.27.0
'@types/babel__generator': 7.6.6
'@types/babel__template': 7.4.3
'@types/babel__traverse': 7.20.3
'@types/babel__generator@7.6.6':
dependencies:
'@babel/types': 7.26.0
'@babel/types': 7.27.0
'@types/babel__template@7.4.3':
dependencies:
'@babel/parser': 7.26.2
'@babel/types': 7.26.0
'@babel/parser': 7.27.0
'@babel/types': 7.27.0
'@types/babel__traverse@7.20.3':
dependencies:
'@babel/types': 7.26.0
'@babel/types': 7.27.0
'@types/braces@3.0.1': {}
@ -13652,8 +13641,6 @@ snapshots:
decamelize@6.0.0: {}
decimal.js@10.4.3: {}
decimal.js@10.5.0: {}
decode-bmp@0.2.1:
@ -15435,7 +15422,7 @@ snapshots:
dependencies:
cssstyle: 4.2.1
data-urls: 5.0.0
decimal.js: 10.4.3
decimal.js: 10.5.0
form-data: 4.0.1
html-encoding-sniffer: 4.0.0
http-proxy-agent: 7.0.2
@ -15451,7 +15438,7 @@ snapshots:
webidl-conversions: 7.0.0
whatwg-encoding: 3.1.1
whatwg-mimetype: 4.0.0
whatwg-url: 14.1.0
whatwg-url: 14.2.0
ws: 8.18.1
xml-name-validator: 5.0.0
transitivePeerDependencies:
@ -17679,10 +17666,6 @@ snapshots:
dependencies:
punycode: 2.3.1
tr46@5.0.0:
dependencies:
punycode: 2.3.1
tr46@5.1.0:
dependencies:
punycode: 2.3.1
@ -18389,11 +18372,6 @@ snapshots:
whatwg-mimetype@4.0.0: {}
whatwg-url@14.1.0:
dependencies:
tr46: 5.0.0
webidl-conversions: 7.0.0
whatwg-url@14.2.0:
dependencies:
tr46: 5.1.0

View File

@ -5,7 +5,7 @@ export default defineConfig({
test: {
watch: false,
workspace: [
projects: [
{
test: {
name: "Browser in workspace",

View File

@ -13,10 +13,10 @@ it('can manipulate files', async () => {
catch (err) {
expect(err.message).toMatch(`ENOENT: no such file or directory, open`)
if (server.platform === 'win32') {
expect(err.message).toMatch('test\\browser\\test\\test.txt')
expect(err.message).toMatch('test\\browser\\test.txt')
}
else {
expect(err.message).toMatch('test/browser/test/test.txt')
expect(err.message).toMatch('test/browser/test.txt')
}
}
@ -34,10 +34,10 @@ it('can manipulate files', async () => {
catch (err) {
expect(err.message).toMatch(`ENOENT: no such file or directory, open`)
if (server.platform === 'win32') {
expect(err.message).toMatch('test\\browser\\test\\test.txt')
expect(err.message).toMatch('test\\browser\\test.txt')
}
else {
expect(err.message).toMatch('test/browser/test/test.txt')
expect(err.message).toMatch('test/browser/test.txt')
}
}
})

View File

@ -0,0 +1,37 @@
import { resolve } from 'pathe';
import { defineConfig, defineWorkspace } from 'vitest/config';
export default defineConfig({
test: {
projects: [
{
cacheDir: resolve(import.meta.dirname, 'basic-1'),
test: {
name: 'basic-1',
dir: import.meta.dirname,
include: ['./basic.test.js'],
browser: {
enabled: true,
instances: [{ browser: 'chromium' }],
provider: 'playwright',
headless: true,
}
}
},
{
cacheDir: resolve(import.meta.dirname, 'basic-2'),
test: {
name: 'basic-2',
dir: import.meta.dirname,
include: ['./basic.test.js'],
browser: {
enabled: true,
instances: [{ browser: 'chromium' }],
provider: 'playwright',
headless: true,
}
}
},
],
},
})

View File

@ -1,33 +0,0 @@
import { resolve } from 'pathe';
import { defineWorkspace } from 'vitest/config';
export default defineWorkspace([
{
cacheDir: resolve(import.meta.dirname, 'basic-1'),
test: {
name: 'basic-1',
dir: import.meta.dirname,
include: ['./basic.test.js'],
browser: {
enabled: true,
instances: [{ browser: 'chromium' }],
provider: 'playwright',
headless: true,
}
}
},
{
cacheDir: resolve(import.meta.dirname, 'basic-2'),
test: {
name: 'basic-2',
dir: import.meta.dirname,
include: ['./basic.test.js'],
browser: {
enabled: true,
instances: [{ browser: 'chromium' }],
provider: 'playwright',
headless: true,
}
}
},
])

View File

@ -3,7 +3,7 @@ import "@vitest/test-dep-linked/ts";
export default defineConfig({
test: {
workspace: [
projects: [
"browser/vitest.config.ts",
"node/vitest.config.ts",
],

View File

@ -8,7 +8,7 @@ export default defineConfig({
array: [1, 2, 3],
},
},
workspace: [
projects: [
{
extends: true,
test: {

View File

@ -1 +1,9 @@
export default {}
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
projects: [
"packages/*/vitest.config.mjs",
]
},
})

View File

@ -1,3 +0,0 @@
export default [
"packages/*/vitest.config.mjs",
];

View File

@ -6,14 +6,12 @@ import { runVitest } from '../../test-utils'
it('automatically assigns the port', async () => {
const root = resolve(import.meta.dirname, '../fixtures/browser-multiple')
const workspace = resolve(import.meta.dirname, '../fixtures/browser-multiple/vitest.workspace.ts')
const spy = vi.spyOn(console, 'log')
onTestFinished(() => spy.mockRestore())
let ctx: Vitest
let urls: (string | undefined)[] = []
const { stderr } = await runVitest({
root,
workspace,
dir: root,
watch: false,
reporters: [

View File

@ -1,5 +1,25 @@
import { defineConfig } from 'vitest/config'
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = resolve(__filename, '..')
export default defineConfig({
test: {},
test: {
projects: [
{
test: {
name: 'Project #1',
root: resolve(__dirname, './project'),
},
},
{
test: {
name: 'Project #2',
root: resolve(__dirname, './project'),
},
},
]
},
})

View File

@ -1,21 +0,0 @@
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { defineWorkspace } from 'vitest/config'
const __filename = fileURLToPath(import.meta.url)
const __dirname = resolve(__filename, '..')
export default defineWorkspace([
{
test: {
name: 'Project #1',
root: resolve(__dirname, './project'),
},
},
{
test: {
name: 'Project #2',
root: resolve(__dirname, './project'),
},
},
])

View File

@ -2,7 +2,7 @@ import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
workspace: [
projects: [
{
test: {
name: 'unit',

View File

@ -1,3 +1,9 @@
import { defineConfig } from "vitest/config";
export default defineConfig({})
export default defineConfig({
test: {
projects: [
'packages/*'
]
}
})

View File

@ -1,3 +0,0 @@
export default [
'packages/*'
]

View File

@ -11,5 +11,21 @@ export default defineConfig({
basename(path) + extension
);
},
projects: [
{
extends: './vitest.config.ts',
test: {
name: 'project1',
root: import.meta.dirname,
}
},
{
extends: './vitest.config.ts',
test: {
name: 'project2',
root: import.meta.dirname,
}
}
],
},
});

View File

@ -1,18 +0,0 @@
import { defineWorkspace } from 'vitest/config'
export default defineWorkspace([
{
extends: './vitest.config.ts',
test: {
name: 'project1',
root: import.meta.dirname,
}
},
{
extends: './vitest.config.ts',
test: {
name: 'project2',
root: import.meta.dirname,
}
}
])

View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
projects: ['projects'],
},
})

View File

@ -1 +0,0 @@
export default ['projects']

View File

@ -17,7 +17,7 @@ export default defineConfig({
},
],
test: {
workspace: [
projects: [
{
extends: true,
test: {

View File

@ -0,0 +1,11 @@
import { defineConfig } from 'vitest/config'
import depAsJs from "./dep.js"
export default defineConfig({
test: {
projects: [
"./packages/*",
...depAsJs,
],
}
})

View File

@ -1,6 +0,0 @@
import depAsJs from "./dep.js"
export default [
"./packages/*",
...depAsJs,
]

View File

@ -0,0 +1,10 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
projects: [
'./vitest1.config.js',
'./vitest2.config.js',
],
}
})

View File

@ -1,6 +0,0 @@
import { defineWorkspace } from 'vitest/config'
export default defineWorkspace([
'./vitest1.config.js',
'./vitest2.config.js',
])

View File

@ -0,0 +1,18 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
projects: [
{
test: {
name: 'test',
},
},
{
test: {
name: 'test',
},
},
]
}
})

View File

@ -1,14 +0,0 @@
import { defineWorkspace } from 'vitest/config'
export default defineWorkspace([
{
test: {
name: 'test',
},
},
{
test: {
name: 'test',
},
},
])

View File

@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
projects: [
'./vitest.config.js'
],
},
})

View File

@ -1,5 +0,0 @@
import { defineWorkspace } from 'vitest/config'
export default defineWorkspace([
'./vitest.config.js'
])

View File

@ -0,0 +1,10 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
projects: [
'packages/*',
'!packages/b'
],
},
})

View File

@ -1,4 +0,0 @@
export default [
'packages/*',
'!packages/b'
]

View File

@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
projects: [
'./test/*.config.*.ts',
],
},
})

View File

@ -1,3 +0,0 @@
export default [
'./test/*.config.*.ts'
]

View File

@ -0,0 +1,10 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
projects: [
'projects/*',
'apps/*'
],
},
})

View File

@ -1,4 +0,0 @@
export default [
'projects/*',
'apps/*'
]

View File

@ -68,7 +68,7 @@ test('filters projects with a wildcard', async () => {
test('assignes names as browsers in a custom project', async () => {
const { projects } = await vitest({
workspace: [
projects: [
{
test: {
name: 'custom',
@ -207,7 +207,7 @@ test('coverage provider v8 works correctly in browser mode if instances are filt
test('coverage provider v8 works correctly in workspaced browser mode if instances are filtered', async () => {
const { projects } = await vitest({
project: 'browser (chromium)',
workspace: [
projects: [
{
test: {
name: 'browser',
@ -236,7 +236,7 @@ test('coverage provider v8 works correctly in workspaced browser mode if instanc
test('filter for the global browser project includes all browser instances', async () => {
const { projects } = await vitest({
project: 'myproject',
workspace: [
projects: [
{
test: {
name: 'myproject',
@ -275,7 +275,7 @@ test('can enable browser-cli options for multi-project workspace', async () => {
},
},
{
workspace: [
projects: [
{
test: {
name: 'unit',
@ -373,7 +373,7 @@ function getCliConfig(options: UserConfig, cli: string[], fs: TestFsStructure =
describe('[e2e] workspace configs are affected by the CLI options', () => {
test('UI is not enabled by default in headless config', async () => {
const vitest = await getCliConfig({
workspace: [
projects: [
{
test: {
name: 'unit',
@ -426,7 +426,7 @@ describe('[e2e] workspace configs are affected by the CLI options', () => {
test('CLI options correctly override inline workspace options', async () => {
const vitest = await getCliConfig({
workspace: [
projects: [
{
test: {
name: 'unit',
@ -481,7 +481,7 @@ describe('[e2e] workspace configs are affected by the CLI options', () => {
test('CLI options correctly override config file workspace options', async () => {
const vitest = await getCliConfig(
{
workspace: [
projects: [
{
test: {
name: 'unit',

View File

@ -30,7 +30,7 @@ test('can change global configuration', async () => {
test('can change the project and the global configurations', async () => {
const v = await vitest({}, {
workspace: [
projects: [
{
plugins: [
{
@ -59,7 +59,7 @@ test('plugin is not called if the project is filtered out', async () => {
const { projects } = await vitest({
project: 'project-2',
}, {
workspace: [
projects: [
{
test: {
name: 'project-1',
@ -113,7 +113,7 @@ test('injected plugin is filtered by the --project filter', async () => {
let newWorkspace: TestProject[] = []
const { projects } = await vitest({
project: 'project-1',
workspace: [
projects: [
{
test: {
name: 'project-1',
@ -143,7 +143,7 @@ test('injected plugin is not filtered by the --project filter when it\'s overrid
let newWorkspace: TestProject[] = []
const { projects } = await vitest({
project: 'project-1',
workspace: [
projects: [
{
test: {
name: 'project-1',
@ -174,7 +174,7 @@ test('injected plugin is not filtered by the --project filter when it\'s overrid
test('adding a plugin with existing name throws and error', async () => {
await expect(() => vitest({
workspace: [
projects: [
{
test: {
name: 'project-1',
@ -194,10 +194,10 @@ test('adding a plugin with existing name throws and error', async () => {
},
],
}),
).rejects.toThrowError('Project name "project-1" is not unique. All projects in a workspace should have unique names. Make sure your configuration is correct.')
).rejects.toThrowError('Project name "project-1" is not unique. All projects should have unique names. Make sure your configuration is correct.')
await expect(() => vitest({
workspace: [
projects: [
{
plugins: [
{
@ -219,10 +219,10 @@ test('adding a plugin with existing name throws and error', async () => {
},
],
}),
).rejects.toThrowError('Project name "project-1" is not unique. All projects in a workspace should have unique names. Make sure your configuration is correct.')
).rejects.toThrowError('Project name "project-1" is not unique. All projects should have unique names. Make sure your configuration is correct.')
await expect(() => vitest({
workspace: [
projects: [
{
plugins: [
{
@ -246,5 +246,5 @@ test('adding a plugin with existing name throws and error', async () => {
},
],
}),
).rejects.toThrowError('Project name "project-1" is not unique. All projects in a workspace should have unique names. Make sure your configuration is correct.')
).rejects.toThrowError('Project name "project-1" is not unique. All projects should have unique names. Make sure your configuration is correct.')
})

View File

@ -476,7 +476,7 @@ test('browser.instances throws an error if no custom name is provided', async ()
},
},
})
expect(stderr).toMatch('Cannot define a nested project for a firefox browser. The project name "firefox" was already defined. If you have multiple instances for the same browser, make sure to define a custom "name". All projects in a workspace should have unique names. Make sure your configuration is correct.')
expect(stderr).toMatch('Cannot define a nested project for a firefox browser. The project name "firefox" was already defined. If you have multiple instances for the same browser, make sure to define a custom "name". All projects should have unique names. Make sure your configuration is correct.')
})
test('browser.instances throws an error if no custom name is provided, but the config name is inherited', async () => {
@ -491,12 +491,12 @@ test('browser.instances throws an error if no custom name is provided, but the c
],
},
})
expect(stderr).toMatch('Cannot define a nested project for a firefox browser. The project name "custom (firefox)" was already defined. If you have multiple instances for the same browser, make sure to define a custom "name". All projects in a workspace should have unique names. Make sure your configuration is correct.')
expect(stderr).toMatch('Cannot define a nested project for a firefox browser. The project name "custom (firefox)" was already defined. If you have multiple instances for the same browser, make sure to define a custom "name". All projects should have unique names. Make sure your configuration is correct.')
})
test('throws an error if name conflicts with a workspace name', async () => {
const { stderr } = await runVitest({
workspace: [
projects: [
{ test: { name: '1 (firefox)' } },
{
test: {
@ -511,7 +511,7 @@ test('throws an error if name conflicts with a workspace name', async () => {
},
],
})
expect(stderr).toMatch('Cannot define a nested project for a firefox browser. The project name "1 (firefox)" was already defined. If you have multiple instances for the same browser, make sure to define a custom "name". All projects in a workspace should have unique names. Make sure your configuration is correct.')
expect(stderr).toMatch('Cannot define a nested project for a firefox browser. The project name "1 (firefox)" was already defined. If you have multiple instances for the same browser, make sure to define a custom "name". All projects should have unique names. Make sure your configuration is correct.')
})
test('throws an error if several browsers are headed in nonTTY mode', async () => {

View File

@ -15,7 +15,6 @@ it('correctly runs workspace tests when workspace config path is specified', asy
it('runs the workspace if there are several vitest config files', async () => {
const { stderr, stdout } = await runVitest({
root: 'fixtures/workspace/several-configs',
workspace: './fixtures/workspace/several-configs/vitest.workspace.ts',
})
expect(stderr).toBe('')
expect(stdout).toContain('workspace/several-configs')
@ -28,7 +27,6 @@ it('runs the workspace if there are several vitest config files', async () => {
it('correctly resolves workspace projects with a several folder globs', async () => {
const { stderr, stdout } = await runVitest({
root: 'fixtures/workspace/several-folders',
workspace: './fixtures/workspace/several-folders/vitest.workspace.ts',
})
expect(stderr).toBe('')
expect(stdout).toContain('test - a')
@ -38,7 +36,6 @@ it('correctly resolves workspace projects with a several folder globs', async ()
it('supports glob negation pattern', async () => {
const { stderr, stdout } = await runVitest({
root: 'fixtures/workspace/negated',
workspace: './fixtures/workspace/negated/vitest.workspace.ts',
})
expect(stderr).toBe('')
expect(stdout).toContain('test - a')
@ -49,7 +46,6 @@ it('supports glob negation pattern', async () => {
it('fails if project names are identical with a nice error message', async () => {
const { stderr } = await runVitest({
root: 'fixtures/workspace/invalid-duplicate-configs',
workspace: './fixtures/workspace/invalid-duplicate-configs/vitest.workspace.ts',
}, [], 'test', {}, { fails: true })
expect(stderr).toContain(
`Project name "test" from "vitest2.config.js" is not unique. The project is already defined by "vitest1.config.js".
@ -58,34 +54,31 @@ Your config matched these files:
- vitest1.config.js
- vitest2.config.js
All projects in a workspace should have unique names. Make sure your configuration is correct.`,
All projects should have unique names. Make sure your configuration is correct.`,
)
})
it('fails if project names are identical inside the inline config', async () => {
const { stderr } = await runVitest({
root: 'fixtures/workspace/invalid-duplicate-inline',
workspace: './fixtures/workspace/invalid-duplicate-inline/vitest.workspace.ts',
}, [], 'test', {}, { fails: true })
expect(stderr).toContain(
'Project name "test" is not unique. All projects in a workspace should have unique names. Make sure your configuration is correct.',
'Project name "test" is not unique. All projects should have unique names. Make sure your configuration is correct.',
)
})
it('fails if referenced file doesnt exist', async () => {
const { stderr } = await runVitest({
root: 'fixtures/workspace/invalid-non-existing-config',
workspace: './fixtures/workspace/invalid-non-existing-config/vitest.workspace.ts',
}, [], 'test', {}, { fails: true })
expect(stderr).toContain(
`Workspace config file "vitest.workspace.ts" references a non-existing file or a directory: ${resolve('fixtures/workspace/invalid-non-existing-config/vitest.config.js')}`,
`Projects definition references a non-existing file or a directory: ${resolve('fixtures/workspace/invalid-non-existing-config/vitest.config.js')}`,
)
})
it('vite import analysis is applied when loading workspace config', async () => {
const { stderr, stdout } = await runVitest({
root: 'fixtures/workspace/config-import-analysis',
workspace: './fixtures/workspace/config-import-analysis/vitest.workspace.ts',
})
expect(stderr).toBe('')
expect(stdout).toContain('test - a')
@ -97,7 +90,7 @@ it('can define inline workspace config programmatically', async () => {
env: {
TEST_ROOT: '1',
},
workspace: [
projects: [
{
extends: true,
test: {
@ -137,9 +130,9 @@ it('correctly inherits the root config', async () => {
it('fails if workspace is empty', async () => {
const { stderr } = await runVitest({
workspace: [],
projects: [],
})
expect(stderr).toContain('No projects were found. Make sure your configuration is correct. The workspace: [].')
expect(stderr).toContain('No projects were found. Make sure your configuration is correct. The projects definition: [].')
})
it('fails if workspace is filtered by the project', async () => {
@ -147,11 +140,11 @@ it('fails if workspace is filtered by the project', async () => {
project: 'non-existing',
root: 'fixtures/workspace/config-empty',
config: './vitest.config.js',
workspace: [
projects: [
'./vitest.config.js',
],
})
expect(stderr).toContain(`No projects were found. Make sure your configuration is correct. The filter matched no projects: non-existing. The workspace: [
expect(stderr).toContain(`No projects were found. Make sure your configuration is correct. The filter matched no projects: non-existing. The projects definition: [
"./vitest.config.js"
].`)
})

View File

@ -1,3 +1,5 @@
import type { LabelColor } from 'vitest'
import type { Pool } from 'vitest/node'
import { basename, dirname, join, resolve } from 'pathe'
import { defaultExclude, defineConfig } from 'vitest/config'
@ -135,5 +137,20 @@ export default defineConfig({
return false
}
},
projects: [
project('threads', 'red'),
project('forks', 'green'),
project('vmThreads', 'blue'),
],
},
})
function project(pool: Pool, color: LabelColor) {
return {
extends: './vite.config.ts',
test: {
name: { label: pool, color },
pool,
},
}
}

View File

@ -1,25 +0,0 @@
import { defineWorkspace } from 'vitest/config'
export default defineWorkspace([
{
extends: './vite.config.ts',
test: {
name: { label: 'threads', color: 'red' },
pool: 'threads',
},
},
{
extends: './vite.config.ts',
test: {
name: { label: 'forks', color: 'green' },
pool: 'forks',
},
},
{
extends: './vite.config.ts',
test: {
name: { label: 'vmThreads', color: 'blue' },
pool: 'vmThreads',
},
},
])

View File

@ -1,3 +1,7 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({})
export default defineConfig({
test: {
projects: ['space-1', 'space-2'],
},
})

View File

@ -1,3 +0,0 @@
import { defineWorkspace } from "vitest/config";
export default defineWorkspace(['space-1', 'space-2'])

View File

@ -0,0 +1,7 @@
import { defineConfig, defineWorkspace } from 'vitest/config'
export default defineConfig({
test: {
projects: ['packages/*'],
},
})

View File

@ -1,5 +0,0 @@
import { defineWorkspace } from 'vitest/config'
export default defineWorkspace([
'packages/*',
])

View File

@ -23,7 +23,6 @@ test('--update works for workspace project', async () => {
const { stdout, exitCode } = await runVitest({
update: true,
root: 'test/fixtures/workspace',
workspace: 'vitest.workspace.ts',
})
expect.soft(stdout).include('Snapshots 1 updated')
expect.soft(exitCode).toBe(0)

View File

@ -10,7 +10,7 @@ test('reruns tests when config changes', async () => {
export default {
test: {
workspace: [
projects: [
'./project-1',
'./project-2',
],

View File

@ -11,5 +11,28 @@ export default defineConfig({
reporters: ['default', 'json'],
outputFile: './results.json',
globalSetup: './globalTest.ts',
projects: [
'./space_*/*.config.ts',
{
test: {
name: 'space_browser_inline',
root: './space_browser_inline',
browser: {
enabled: true,
instances: [{ browser: process.env.BROWSER || 'chromium' }],
headless: true,
provider: process.env.PROVIDER || 'playwright',
},
alias: {
'test-alias-from-vitest': new URL('./space_browser_inline/test-alias-to.ts', import.meta.url).pathname,
},
},
resolve: {
alias: {
'test-alias-from-vite': new URL('./space_browser_inline/test-alias-to.ts', import.meta.url).pathname,
},
},
},
],
},
})

View File

@ -1,25 +0,0 @@
import { defineWorkspace } from 'vitest/config'
export default defineWorkspace([
'./space_*/*.config.ts',
{
test: {
name: 'space_browser_inline',
root: './space_browser_inline',
browser: {
enabled: true,
instances: [{ browser: process.env.BROWSER || 'chromium' }],
headless: true,
provider: process.env.PROVIDER || 'playwright',
},
alias: {
'test-alias-from-vitest': new URL('./space_browser_inline/test-alias-to.ts', import.meta.url).pathname,
},
},
resolve: {
alias: {
'test-alias-from-vite': new URL('./space_browser_inline/test-alias-to.ts', import.meta.url).pathname,
},
},
},
])

View File

@ -5,7 +5,7 @@ export default defineConfig({
{
name: 'throw-error',
config() {
throw new Error('This file should not initiate a workspace project.')
throw new Error('This file should not initiate a test project.')
},
},
],

View File

@ -1,3 +1,6 @@
import type { Vite } from 'vitest/node'
import remapping from '@ampproject/remapping'
import MagicString from 'magic-string'
import { defineConfig } from 'vitest/config'
import { cwdPlugin } from './cwdPlugin.js'
@ -19,5 +22,161 @@ export default defineConfig({
provide: {
globalConfigValue: true,
},
projects: [
'space_2',
'./space_*/vitest.config.ts',
'./space_1/*.config.ts',
async () => ({
test: {
name: 'happy-dom',
root: './space_shared',
environment: 'happy-dom',
setupFiles: ['./setup.jsdom.ts'],
provide: {
providedConfigValue: 'actual config value',
},
},
}),
Promise.resolve({
test: {
name: 'node',
root: './space_shared',
environment: 'node',
setupFiles: ['./setup.node.ts'],
},
}),
// Projects testing pool and poolOptions
{
test: {
name: 'Threads pool',
include: [
'./space-pools/threads.test.ts',
'./space-pools/multi-worker.test.ts',
'./space-pools/isolate.test.ts',
],
pool: 'threads',
},
},
{
test: {
name: 'Single thread pool',
include: [
'./space-pools/threads.test.ts',
'./space-pools/single-worker.test.ts',
],
pool: 'threads',
poolOptions: { threads: { singleThread: true } },
},
},
{
test: {
name: 'Non-isolated thread pool #1',
include: [
'./space-pools/threads.test.ts',
'./space-pools/no-isolate.test.ts',
],
pool: 'threads',
poolOptions: { threads: { isolate: false } },
},
},
{
test: {
name: 'Non-isolated thread pool #2',
include: [
'./space-pools/threads.test.ts',
'./space-pools/no-isolate.test.ts',
],
pool: 'threads',
isolate: false,
},
},
{
test: {
name: 'Forks pool',
include: [
'./space-pools/forks.test.ts',
'./space-pools/multi-worker.test.ts',
'./space-pools/isolate.test.ts',
],
pool: 'forks',
},
},
{
test: {
name: 'Single fork pool',
include: [
'./space-pools/forks.test.ts',
'./space-pools/single-worker.test.ts',
],
pool: 'forks',
poolOptions: { forks: { singleFork: true } },
},
},
{
test: {
name: 'Non-isolated fork pool #1',
include: [
'./space-pools/forks.test.ts',
'./space-pools/no-isolate.test.ts',
],
pool: 'forks',
poolOptions: { forks: { isolate: false } },
},
},
{
test: {
name: 'Non-isolated fork pool #2',
include: [
'./space-pools/forks.test.ts',
'./space-pools/no-isolate.test.ts',
],
pool: 'forks',
isolate: false,
},
},
// These two projects run on same environment but still transform
// a single file differently due to Vite plugins
{
plugins: [customPlugin(0)],
test: {
name: 'Project with custom plugin #1',
environment: 'node',
include: ['./space-multi-transform/test/project-1.test.ts'],
},
},
{
plugins: [customPlugin(15)],
test: {
name: 'Project with custom plugin #2',
environment: 'node',
include: ['./space-multi-transform/test/project-2.test.ts'],
},
},
],
},
})
function customPlugin(offset: number): Vite.Plugin {
return {
name: 'vitest-custom-multi-transform',
enforce: 'pre',
transform(code, id) {
if (id.includes('space-multi-transform/src/multi-transform.ts')) {
const padding = '\n*****'.repeat(offset)
const transformed = new MagicString(code)
transformed.replace('\'default-padding\'', `\`${padding}\``)
const map = remapping(
[transformed.generateMap({ hires: true }), this.getCombinedSourcemap() as any],
() => null,
) as any
return { code: transformed.toString(), map }
}
},
}
}

View File

@ -1,160 +0,0 @@
import type { Plugin } from 'vite'
import remapping from '@ampproject/remapping'
import MagicString from 'magic-string'
import { defineWorkspace } from 'vitest/config'
export default defineWorkspace([
'space_2',
'./space_*/vitest.config.ts',
'./space_1/*.config.ts',
async () => ({
test: {
name: 'happy-dom',
root: './space_shared',
environment: 'happy-dom',
setupFiles: ['./setup.jsdom.ts'],
provide: {
providedConfigValue: 'actual config value',
},
},
}),
Promise.resolve({
test: {
name: 'node',
root: './space_shared',
environment: 'node',
setupFiles: ['./setup.node.ts'],
},
}),
// Projects testing pool and poolOptions
{
test: {
name: 'Threads pool',
include: [
'./space-pools/threads.test.ts',
'./space-pools/multi-worker.test.ts',
'./space-pools/isolate.test.ts',
],
pool: 'threads',
},
},
{
test: {
name: 'Single thread pool',
include: [
'./space-pools/threads.test.ts',
'./space-pools/single-worker.test.ts',
],
pool: 'threads',
poolOptions: { threads: { singleThread: true } },
},
},
{
test: {
name: 'Non-isolated thread pool #1',
include: [
'./space-pools/threads.test.ts',
'./space-pools/no-isolate.test.ts',
],
pool: 'threads',
poolOptions: { threads: { isolate: false } },
},
},
{
test: {
name: 'Non-isolated thread pool #2',
include: [
'./space-pools/threads.test.ts',
'./space-pools/no-isolate.test.ts',
],
pool: 'threads',
isolate: false,
},
},
{
test: {
name: 'Forks pool',
include: [
'./space-pools/forks.test.ts',
'./space-pools/multi-worker.test.ts',
'./space-pools/isolate.test.ts',
],
pool: 'forks',
},
},
{
test: {
name: 'Single fork pool',
include: [
'./space-pools/forks.test.ts',
'./space-pools/single-worker.test.ts',
],
pool: 'forks',
poolOptions: { forks: { singleFork: true } },
},
},
{
test: {
name: 'Non-isolated fork pool #1',
include: [
'./space-pools/forks.test.ts',
'./space-pools/no-isolate.test.ts',
],
pool: 'forks',
poolOptions: { forks: { isolate: false } },
},
},
{
test: {
name: 'Non-isolated fork pool #2',
include: [
'./space-pools/forks.test.ts',
'./space-pools/no-isolate.test.ts',
],
pool: 'forks',
isolate: false,
},
},
// These two projects run on same environment but still transform
// a single file differently due to Vite plugins
{
plugins: [customPlugin(0)],
test: {
name: 'Project with custom plugin #1',
environment: 'node',
include: ['./space-multi-transform/test/project-1.test.ts'],
},
},
{
plugins: [customPlugin(15)],
test: {
name: 'Project with custom plugin #2',
environment: 'node',
include: ['./space-multi-transform/test/project-2.test.ts'],
},
},
])
function customPlugin(offset: number): Plugin {
return {
name: 'vitest-custom-multi-transform',
enforce: 'pre',
transform(code, id) {
if (id.includes('space-multi-transform/src/multi-transform.ts')) {
const padding = '\n*****'.repeat(offset)
const transformed = new MagicString(code)
transformed.replace('\'default-padding\'', `\`${padding}\``)
const map = remapping(
[transformed.generateMap({ hires: true }), this.getCombinedSourcemap() as any],
() => null,
) as any
return { code: transformed.toString(), map }
}
},
}
}