diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 66f94571d..12f617a12 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -350,6 +350,10 @@ export default ({ mode }: { mode: string }) => {
},
],
},
+ {
+ text: 'Plugin API',
+ link: '/advanced/api/plugin',
+ },
{
text: 'Runner API',
link: '/advanced/runner',
diff --git a/docs/advanced/api/plugin.md b/docs/advanced/api/plugin.md
new file mode 100644
index 000000000..f609d1185
--- /dev/null
+++ b/docs/advanced/api/plugin.md
@@ -0,0 +1,125 @@
+---
+title: Plugin API
+outline: deep
+---
+
+# Plugin API 3.1.0 {#plugin-api}
+
+::: warning
+This is an advanced API. If you just want to [run tests](/guide/), you probably don't need this. It is primarily used by library authors.
+
+This guide assumes you know how to work with [Vite plugins](https://vite.dev/guide/api-plugin.html).
+:::
+
+Vitest supports an experimental `configureVitest` [plugin](https://vite.dev/guide/api-plugin.html) hook hook since version 3.1. Any feedback regarding this API is welcome in [GitHub](https://github.com/vitest-dev/vitest/discussions/7104).
+
+::: code-group
+```ts [only vitest]
+import type { Vite, VitestPluginContext } from 'vitest/node'
+
+export function plugin(): Vite.Plugin {
+ return {
+ name: 'vitest:my-plugin',
+ configureVitest(context: VitestPluginContext) {
+ // ...
+ }
+ }
+}
+```
+```ts [vite and vitest]
+///
+
+import type { Plugin } from 'vite'
+
+export function plugin(): Plugin {
+ return {
+ name: 'vitest:my-plugin',
+ transform() {
+ // ...
+ },
+ configureVitest(context) {
+ // ...
+ }
+ }
+}
+```
+:::
+
+::: tip TypeScript
+Vitest re-exports all Vite type-only imports via a `Vite` namespace, which you can use to keep your versions in sync. However, if you are writing a plugin for both Vite and Vitest, you can continue using the `Plugin` type from the `vite` entrypoint. Just make sure you have `vitest/config` referenced somewhere so that `configureVitest` is augmented correctly:
+
+```ts
+///
+```
+:::
+
+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.
+
+## Context
+
+### project
+
+The current [test project](./test-project) that the plugin belongs to.
+
+::: warning Browser Mode
+Note that if you are relying on a browser feature, the `project.browser` field is not set yet. Use [`reporter.onBrowserInit`](./reporters#onbrowserinit) event instead.
+:::
+
+### vitest
+
+The global [Vitest](./vitest) instance. You can change the global configuration by directly mutating the `vitest.config` property:
+
+```ts
+vitest.config.coverage.enabled = false
+vitest.config.reporters.push([['my-reporter', {}]])
+```
+
+::: warning Config is Resolved
+Note that Vitest already resolved the config, so some types might be different from the usual user configuration. This also means that some properties will not be resolved again, like `setupFile`. If you are adding new files, make sure to resolve it first.
+
+At this point reporters are not created yet, so modifying `vitest.reporters` will have no effect because it will be overwritten. If you need to inject your own reporter, modify the config instead.
+:::
+
+### injectTestProjects
+
+```ts
+function injectTestProjects(
+ config: TestProjectConfiguration | TestProjectConfiguration[]
+): Promise
+```
+
+This methods accepts a config glob pattern, a filepath to the config or an inline configuration. It returns an array of resolved [test projects](./test-project).
+
+```ts
+// inject a single project with a custom alias
+const newProjects = await injectTestProjects({
+ // you can inherit the current project config by referencing `configFile`
+ // 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,
+ test: {
+ name: 'my-custom-alias',
+ alias: {
+ customAlias: resolve('./custom-path.js'),
+ },
+ },
+})
+```
+
+::: 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:
+
+```ts
+vitest.config.project.push('my-project-name')
+```
+
+Note that this will only affect projects injected with [`injectTestProjects`](#injecttestprojects) method.
+:::
+
+::: 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.
+
+The project's `configFile` can be accessed in Vite's config: `project.vite.config.configFile`.
+
+Note that this will also inherit the `name` - Vitest doesn't allow multiple projects with the same name, so this will throw an error. Make sure you specified a different name. You can access the current name via the `project.name` property and all used names are available in the `vitest.projects` array.
+:::
diff --git a/docs/advanced/api/vitest.md b/docs/advanced/api/vitest.md
index 5043c56bb..5933d6371 100644
--- a/docs/advanced/api/vitest.md
+++ b/docs/advanced/api/vitest.md
@@ -518,3 +518,13 @@ vitest.onFilterWatchedSpecification(specification =>
```
Vitest can create different specifications for the same file depending on the `pool` or `locations` options, so do not rely on the reference. Vitest can also return cached specification from [`vitest.getModuleSpecifications`](#getmodulespecifications) - the cache is based on the `moduleId` and `pool`. Note that [`project.createSpecification`](/advanced/api/test-project#createspecification) always returns a new instance.
+
+## matchesProjectFilter 3.1.0 {#matchesprojectfilter}
+
+```ts
+function matchesProjectFilter(name: string): boolean
+```
+
+Check if the name matches the current [project filter](/guide/cli#project). If there is no project filter, this will always return `true`.
+
+It is not possible to programmatically change the `--project` CLI option.
diff --git a/packages/browser/src/node/pool.ts b/packages/browser/src/node/pool.ts
index be0ad6e60..86dcf7a8b 100644
--- a/packages/browser/src/node/pool.ts
+++ b/packages/browser/src/node/pool.ts
@@ -169,7 +169,7 @@ export function createBrowserPool(vitest: Vitest): ProcessPool {
async close() {
await Promise.all([...providers].map(provider => provider.close()))
providers.clear()
- vitest.resolvedProjects.forEach((project) => {
+ vitest.projects.forEach((project) => {
project.browser?.state.orchestrators.forEach((orchestrator) => {
orchestrator.$close()
})
diff --git a/packages/vitest/src/api/setup.ts b/packages/vitest/src/api/setup.ts
index eb32b41bc..9bc308324 100644
--- a/packages/vitest/src/api/setup.ts
+++ b/packages/vitest/src/api/setup.ts
@@ -88,7 +88,7 @@ export function setup(ctx: Vitest, _server?: ViteDevServer): void {
return ctx.getRootProject().serializedConfig
},
getResolvedProjectNames(): string[] {
- return ctx.resolvedProjects.map(p => p.name)
+ return ctx.projects.map(p => p.name)
},
async getTransformResult(projectName: string, id, browser = false) {
const project = ctx.getProjectByName(projectName)
diff --git a/packages/vitest/src/node/config/resolveConfig.ts b/packages/vitest/src/node/config/resolveConfig.ts
index 804f16591..4c319cc25 100644
--- a/packages/vitest/src/node/config/resolveConfig.ts
+++ b/packages/vitest/src/node/config/resolveConfig.ts
@@ -913,7 +913,7 @@ function isPlaywrightChromiumOnly(vitest: Vitest, config: ResolvedConfig) {
for (const instance of browser.instances) {
const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser)
// browser config is filtered out
- if (!vitest._matchesProjectFilter(name)) {
+ if (!vitest.matchesProjectFilter(name)) {
continue
}
if (instance.browser !== 'chromium') {
diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts
index 9d3b5e69b..d3b2820f6 100644
--- a/packages/vitest/src/node/core.ts
+++ b/packages/vitest/src/node/core.ts
@@ -7,7 +7,7 @@ import type { SerializedCoverageConfig } from '../runtime/config'
import type { ArgumentsType, ProvidedContext, UserConsoleLog } from '../types/general'
import type { ProcessPool, WorkspaceSpec } from './pool'
import type { TestSpecification } from './spec'
-import type { ResolvedConfig, UserConfig, VitestRunMode } from './types/config'
+import type { ResolvedConfig, TestProjectConfiguration, UserConfig, VitestRunMode } from './types/config'
import type { CoverageProvider } from './types/coverage'
import type { Reporter } from './types/reporter'
import type { TestRunResult } from './types/tests'
@@ -98,11 +98,10 @@ export class Vitest {
/** @internal */ _browserLastPort = defaultBrowserPort
/** @internal */ _browserSessions = new BrowserSessions()
/** @internal */ _options: UserConfig = {}
- /** @internal */ reporters: Reporter[] = undefined!
+ /** @internal */ reporters: Reporter[] = []
/** @internal */ vitenode: ViteNodeServer = undefined!
/** @internal */ runner: ViteNodeRunner = undefined!
/** @internal */ _testRun: TestRun = undefined!
- /** @internal */ _projectFilters: RegExp[] = []
private isFirstRun = true
private restartsCount = 0
@@ -216,7 +215,6 @@ export class Vitest {
this.specifications.clearCache()
this._onUserTestsRerun = []
- this._projectFilters = toArray(options.project || []).map(project => wildcardPatternToRegExp(project))
this._vite = server
const resolved = resolveConfig(this, options, server.config)
@@ -259,7 +257,7 @@ export class Vitest {
server.watcher.on('change', async (file) => {
file = normalize(file)
const isConfig = file === server.config.configFile
- || this.resolvedProjects.some(p => p.vite.config.configFile === file)
+ || this.projects.some(p => p.vite.config.configFile === file)
|| file === this._workspaceConfigPath
if (isConfig) {
await Promise.all(this._onRestartListeners.map(fn => fn('config')))
@@ -279,6 +277,16 @@ export class Vitest {
const projects = await this.resolveWorkspace(cliOptions)
this.resolvedProjects = projects
this.projects = projects
+
+ await Promise.all(projects.flatMap((project) => {
+ const hooks = project.vite.config.getSortedPluginHooks('configureVitest')
+ return hooks.map(hook => hook({
+ project,
+ vitest: this,
+ injectTestProjects: this.injectTestProject,
+ }))
+ }))
+
if (!this.projects.length) {
throw new Error(`No projects matched the filter "${toArray(resolved.project).join('", "')}".`)
}
@@ -297,6 +305,24 @@ export class Vitest {
await Promise.all(this._onSetServer.map(fn => fn()))
}
+ /**
+ * Inject new test projects into the workspace.
+ * @param config Glob, config path or a custom config options.
+ * @returns An array of new test projects. Can be empty if the name was filtered out.
+ */
+ private injectTestProject = async (config: TestProjectConfiguration | TestProjectConfiguration[]): Promise => {
+ const currentNames = new Set(this.projects.map(p => p.name))
+ const workspace = await resolveWorkspace(
+ this,
+ this._options,
+ undefined,
+ Array.isArray(config) ? config : [config],
+ currentNames,
+ )
+ this.projects.push(...workspace)
+ return workspace
+ }
+
/**
* Provide a value to the test context. This value will be available to all tests with `inject`.
*/
@@ -385,12 +411,15 @@ export class Vitest {
}
private async resolveWorkspace(cliOptions: UserConfig): Promise {
+ const names = new Set()
+
if (Array.isArray(this.config.workspace)) {
return resolveWorkspace(
this,
cliOptions,
undefined,
this.config.workspace,
+ names,
)
}
@@ -406,7 +435,7 @@ export class Vitest {
if (!project) {
return []
}
- return resolveBrowserWorkspace(this, new Set(), [project])
+ return resolveBrowserWorkspace(this, new Set([project.name]), [project])
}
const workspaceModule = await this.import<{
@@ -422,6 +451,7 @@ export class Vitest {
cliOptions,
workspaceConfigPath,
workspaceModule.default,
+ names,
)
}
@@ -861,11 +891,9 @@ export class Vitest {
async changeProjectName(pattern: string): Promise {
if (pattern === '') {
this.configOverride.project = undefined
- this._projectFilters = []
}
else {
this.configOverride.project = [pattern]
- this._projectFilters = [wildcardPatternToRegExp(pattern)]
}
await this.vite.restart()
@@ -1096,10 +1124,10 @@ export class Vitest {
await project._teardownGlobalSetup()
}
- const closePromises: unknown[] = this.resolvedProjects.map(w => w.close())
+ const closePromises: unknown[] = this.projects.map(w => w.close())
// close the core workspace server only once
// it's possible that it's not initialized at all because it's not running any tests
- if (this.coreWorkspaceProject && !this.resolvedProjects.includes(this.coreWorkspaceProject)) {
+ if (this.coreWorkspaceProject && !this.projects.includes(this.coreWorkspaceProject)) {
closePromises.push(this.coreWorkspaceProject.close().then(() => this._vite = undefined as any))
}
@@ -1136,7 +1164,7 @@ export class Vitest {
this.state.getProcessTimeoutCauses().forEach(cause => console.warn(cause))
if (!this.pool) {
- const runningServers = [this._vite, ...this.resolvedProjects.map(p => p._vite)].filter(Boolean).length
+ const runningServers = [this._vite, ...this.projects.map(p => p._vite)].filter(Boolean).length
if (runningServers === 1) {
console.warn('Tests closed successfully but something prevents Vite server from exiting')
@@ -1252,20 +1280,23 @@ export class Vitest {
/**
* Check if the project with a given name should be included.
- * @internal
*/
- _matchesProjectFilter(name: string): boolean {
+ matchesProjectFilter(name: string): boolean {
+ const projects = this._config?.project || this._options?.project
// no filters applied, any project can be included
- if (!this._projectFilters.length) {
+ if (!projects || !projects.length) {
return true
}
- return this._projectFilters.some(filter => filter.test(name))
+ return toArray(projects).some((project) => {
+ const regexp = wildcardPatternToRegExp(project)
+ return regexp.test(name)
+ })
}
}
function assert(condition: unknown, property: string, name: string = property): asserts condition {
if (!condition) {
- throw new Error(`The ${name} was not set. It means that \`vitest.${property}\` was called before the Vite server was established. Either await the Vitest promise or check that it is initialized with \`vitest.ready()\` before accessing \`vitest.${property}\`.`)
+ throw new Error(`The ${name} was not set. It means that \`vitest.${property}\` was called before the Vite server was established. Await the Vitest promise before accessing \`vitest.${property}\`.`)
}
}
diff --git a/packages/vitest/src/node/plugins/workspace.ts b/packages/vitest/src/node/plugins/workspace.ts
index 8b3395cb0..40f2cfba5 100644
--- a/packages/vitest/src/node/plugins/workspace.ts
+++ b/packages/vitest/src/node/plugins/workspace.ts
@@ -1,6 +1,6 @@
import type { UserConfig as ViteConfig, Plugin as VitePlugin } from 'vite'
import type { TestProject } from '../project'
-import type { ResolvedConfig, UserWorkspaceConfig } from '../types/config'
+import type { ResolvedConfig, TestProjectInlineConfiguration } from '../types/config'
import { existsSync, readFileSync } from 'node:fs'
import { deepMerge } from '@vitest/utils'
import { basename, dirname, relative, resolve } from 'pathe'
@@ -21,7 +21,7 @@ import {
} from './utils'
import { VitestProjectResolver } from './vitestResolver'
-interface WorkspaceOptions extends UserWorkspaceConfig {
+interface WorkspaceOptions extends TestProjectInlineConfiguration {
root?: string
workspacePath: string | number
}
@@ -85,7 +85,7 @@ export function WorkspaceVitestPlugin(
// if some of them match, they will later be filtered again by `resolveWorkspace`
if (filters.length) {
const hasProject = workspaceNames.some((name) => {
- return project.vitest._matchesProjectFilter(name)
+ return project.vitest.matchesProjectFilter(name)
})
if (!hasProject) {
throw new VitestFilteredOutProjectError()
diff --git a/packages/vitest/src/node/project.ts b/packages/vitest/src/node/project.ts
index 9fe135e72..3a41fb743 100644
--- a/packages/vitest/src/node/project.ts
+++ b/packages/vitest/src/node/project.ts
@@ -16,8 +16,8 @@ import type { ParentProjectBrowser, ProjectBrowser } from './types/browser'
import type {
ResolvedConfig,
SerializedConfig,
+ TestProjectInlineConfiguration,
UserConfig,
- UserWorkspaceConfig,
} from './types/config'
import { promises as fs, readFileSync } from 'node:fs'
import { rm } from 'node:fs/promises'
@@ -726,7 +726,7 @@ export interface SerializedTestProject {
context: ProvidedContext
}
-interface InitializeProjectOptions extends UserWorkspaceConfig {
+interface InitializeProjectOptions extends TestProjectInlineConfiguration {
configFile: string | false
}
diff --git a/packages/vitest/src/node/types/config.ts b/packages/vitest/src/node/types/config.ts
index 73956ee5a..62c12ddf3 100644
--- a/packages/vitest/src/node/types/config.ts
+++ b/packages/vitest/src/node/types/config.ts
@@ -1125,7 +1125,7 @@ export type UserProjectConfigExport =
| Promise
| UserProjectConfigFn
-export type TestProjectConfiguration = string | (UserProjectConfigExport & {
+export type TestProjectInlineConfiguration = (UserWorkspaceConfig & {
/**
* Relative path to the extendable config. All other options will be merged with this config.
* If `true`, the project will inherit all options from the root config.
@@ -1134,5 +1134,11 @@ export type TestProjectConfiguration = string | (UserProjectConfigExport & {
extends?: string | true
})
+export type TestProjectConfiguration =
+ string
+ | TestProjectInlineConfiguration
+ | Promise
+ | UserProjectConfigFn
+
/** @deprecated use `TestProjectConfiguration` instead */
export type WorkspaceProjectConfiguration = TestProjectConfiguration
diff --git a/packages/vitest/src/node/types/plugin.ts b/packages/vitest/src/node/types/plugin.ts
new file mode 100644
index 000000000..012084d84
--- /dev/null
+++ b/packages/vitest/src/node/types/plugin.ts
@@ -0,0 +1,9 @@
+import type { Vitest } from '../core'
+import type { TestProject } from '../project'
+import type { TestProjectConfiguration } from './config'
+
+export interface VitestPluginContext {
+ vitest: Vitest
+ project: TestProject
+ injectTestProjects: (config: TestProjectConfiguration | TestProjectConfiguration[]) => Promise
+}
diff --git a/packages/vitest/src/node/types/vite.ts b/packages/vitest/src/node/types/vite.ts
index 60d9b7764..3de960bce 100644
--- a/packages/vitest/src/node/types/vite.ts
+++ b/packages/vitest/src/node/types/vite.ts
@@ -1,4 +1,8 @@
+/* eslint-disable unused-imports/no-unused-vars */
+
+import type { HookHandler } from 'vite'
import type { InlineConfig } from './config'
+import type { VitestPluginContext } from './plugin'
type VitestInlineConfig = InlineConfig
@@ -9,6 +13,10 @@ declare module 'vite' {
*/
test?: VitestInlineConfig
}
+
+ interface Plugin {
+ configureVitest?: HookHandler<(context: VitestPluginContext) => void>
+ }
}
export {}
diff --git a/packages/vitest/src/node/workspace/resolveWorkspace.ts b/packages/vitest/src/node/workspace/resolveWorkspace.ts
index fb516a8b3..2d7ec50f2 100644
--- a/packages/vitest/src/node/workspace/resolveWorkspace.ts
+++ b/packages/vitest/src/node/workspace/resolveWorkspace.ts
@@ -19,6 +19,7 @@ export async function resolveWorkspace(
cliOptions: UserConfig,
workspaceConfigPath: string | undefined,
workspaceDefinition: TestProjectConfiguration[],
+ names: Set,
): Promise {
const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs(
vitest,
@@ -114,7 +115,6 @@ export async function resolveWorkspace(
}
const resolvedProjectsPromises = await Promise.allSettled(projectPromises)
- const names = new Set()
const errors: Error[] = []
const resolvedProjects: TestProject[] = []
@@ -201,11 +201,11 @@ export async function resolveBrowserWorkspace(
}
const originalName = project.config.name
// if original name is in the --project=name filter, keep all instances
- const filteredInstances = !vitest._projectFilters.length || vitest._matchesProjectFilter(originalName)
+ const filteredInstances = vitest.matchesProjectFilter(originalName)
? instances
: instances.filter((instance) => {
const newName = instance.name! // name is set in "workspace" plugin
- return vitest._matchesProjectFilter(newName)
+ return vitest.matchesProjectFilter(newName)
})
// every project was filtered out
@@ -460,7 +460,7 @@ export function getDefaultTestProject(vitest: Vitest): TestProject | null {
}
// check for the project name and browser names
const hasProjects = getPotentialProjectNames(project).some(p =>
- vitest._matchesProjectFilter(p),
+ vitest.matchesProjectFilter(p),
)
if (hasProjects) {
return project
diff --git a/packages/vitest/src/public/config.ts b/packages/vitest/src/public/config.ts
index fd906108c..6b7374d52 100644
--- a/packages/vitest/src/public/config.ts
+++ b/packages/vitest/src/public/config.ts
@@ -1,6 +1,13 @@
import type { ConfigEnv, UserConfig as ViteUserConfig } from 'vite'
-import type { TestProjectConfiguration, UserProjectConfigExport, UserProjectConfigFn, UserWorkspaceConfig, WorkspaceProjectConfiguration } from '../node/types/config'
+import type {
+ TestProjectConfiguration,
+ TestProjectInlineConfiguration,
+ UserProjectConfigExport,
+ UserProjectConfigFn,
+ UserWorkspaceConfig,
+ WorkspaceProjectConfiguration,
+} from '../node/types/config'
import '../node/types/vite'
export { extraInlineDeps } from '../constants'
@@ -20,7 +27,14 @@ export type { ConfigEnv, ViteUserConfig }
* @deprecated Use `ViteUserConfig` instead
*/
export type UserConfig = ViteUserConfig
-export type { TestProjectConfiguration, UserProjectConfigExport, UserProjectConfigFn, UserWorkspaceConfig, WorkspaceProjectConfiguration }
+export type {
+ TestProjectConfiguration,
+ TestProjectInlineConfiguration,
+ UserProjectConfigExport,
+ UserProjectConfigFn,
+ UserWorkspaceConfig,
+ WorkspaceProjectConfiguration,
+}
export type UserConfigFnObject = (env: ConfigEnv) => ViteUserConfig
export type UserConfigFnPromise = (env: ConfigEnv) => Promise
export type UserConfigFn = (
diff --git a/packages/vitest/src/public/node.ts b/packages/vitest/src/public/node.ts
index 716250bb3..b44ddd3fb 100644
--- a/packages/vitest/src/public/node.ts
+++ b/packages/vitest/src/public/node.ts
@@ -121,6 +121,7 @@ export type {
ResolvedCoverageOptions,
} from '../node/types/coverage'
+export type { VitestPluginContext } from '../node/types/plugin'
export type { TestRunResult } from '../node/types/tests'
/**
* @deprecated Use `TestModule` instead
diff --git a/test/config/test/configureVitest.test.ts b/test/config/test/configureVitest.test.ts
new file mode 100644
index 000000000..41bd27a4f
--- /dev/null
+++ b/test/config/test/configureVitest.test.ts
@@ -0,0 +1,250 @@
+import type { ViteUserConfig } from 'vitest/config'
+import type { TestProject, UserConfig, VitestOptions } from 'vitest/node'
+import { expect, onTestFinished, test } from 'vitest'
+import { createVitest } from 'vitest/node'
+
+async function vitest(cliOptions: UserConfig, configValue: UserConfig = {}, viteConfig: ViteUserConfig = {}, vitestOptions: VitestOptions = {}) {
+ const vitest = await createVitest('test', { ...cliOptions, watch: false }, { ...viteConfig, test: configValue as any }, vitestOptions)
+ onTestFinished(() => vitest.close())
+ return vitest
+}
+
+test('can change global configuration', async () => {
+ const v = await vitest({}, {}, {
+ plugins: [
+ {
+ name: 'test',
+ configureVitest({ vitest }) {
+ vitest.config.coverage.enabled = true
+ vitest.config.coverage.exclude = ['**/*']
+ vitest.config.setupFiles.push('test/setup.ts')
+ },
+ },
+ ],
+ })
+ expect(v.config.coverage.enabled).toBe(true)
+ expect(v.config.coverage.exclude).toEqual(['**/*'])
+ // setup is not resolved
+ expect(v.config.setupFiles).toEqual(['test/setup.ts'])
+})
+
+test('can change the project and the global configurations', async () => {
+ const v = await vitest({}, {
+ workspace: [
+ {
+ plugins: [
+ {
+ name: 'test',
+ configureVitest({ vitest, project }) {
+ vitest.config.setupFiles.push('test/setup.ts')
+ project.config.setupFiles.push('test/project-setup.ts')
+ },
+ },
+ ],
+ },
+ ],
+ })
+
+ expect(v.config.setupFiles).toEqual(['test/setup.ts'])
+ const rootProject = v.getRootProject()
+
+ expect(v.projects).toHaveLength(1)
+
+ const project = v.projects[0]
+ expect(project).not.toBe(rootProject)
+ expect(project.config.setupFiles).toEqual(['test/project-setup.ts'])
+})
+
+test('plugin is not called if the project is filtered out', async () => {
+ const { projects } = await vitest({
+ project: 'project-2',
+ }, {
+ workspace: [
+ {
+ test: {
+ name: 'project-1',
+ },
+ plugins: [
+ {
+ name: 'test',
+ configureVitest() {
+ expect.unreachable()
+ },
+ },
+ ],
+ },
+ {
+ test: {
+ name: 'project-2',
+ },
+ },
+ ],
+ })
+ expect(projects).toHaveLength(1)
+ expect(projects[0].name).toBe('project-2')
+})
+
+test('can inject the plugin', async () => {
+ let newWorkspace: TestProject[] = []
+ const v = await vitest({}, {}, {
+ plugins: [
+ {
+ name: 'test',
+ async configureVitest({ injectTestProjects }) {
+ newWorkspace = await injectTestProjects({
+ test: {
+ name: 'project-1',
+ },
+ })
+ },
+ },
+ ],
+ })
+ expect(v.projects).toHaveLength(2)
+ // the default project that called configureVitest
+ expect(v.projects[0].name).toBe('')
+ expect(v.projects[1].name).toBe('project-1')
+
+ expect(newWorkspace).toHaveLength(1)
+ expect(newWorkspace[0].name).toBe('project-1')
+})
+
+test('injected plugin is filtered by the --project filter', async () => {
+ let newWorkspace: TestProject[] = []
+ const { projects } = await vitest({
+ project: 'project-1',
+ workspace: [
+ {
+ test: {
+ name: 'project-1',
+ },
+ plugins: [
+ {
+ name: 'test',
+ async configureVitest({ injectTestProjects }) {
+ newWorkspace = await injectTestProjects({
+ test: {
+ name: 'project-2',
+ },
+ })
+ },
+ },
+ ],
+ },
+ ],
+ })
+ expect(projects).toHaveLength(1)
+ expect(projects[0].name).toBe('project-1')
+
+ expect(newWorkspace).toHaveLength(0)
+})
+
+test('injected plugin is not filtered by the --project filter when it\'s overriden', async () => {
+ let newWorkspace: TestProject[] = []
+ const { projects } = await vitest({
+ project: 'project-1',
+ workspace: [
+ {
+ test: {
+ name: 'project-1',
+ },
+ plugins: [
+ {
+ name: 'test',
+ async configureVitest({ vitest, injectTestProjects }) {
+ vitest.config.project.push('project-2')
+ newWorkspace = await injectTestProjects({
+ test: {
+ name: 'project-2',
+ },
+ })
+ },
+ },
+ ],
+ },
+ ],
+ })
+ expect(projects).toHaveLength(2)
+ expect(projects[0].name).toBe('project-1')
+ expect(projects[1].name).toBe('project-2')
+
+ expect(newWorkspace).toHaveLength(1)
+ expect(newWorkspace[0].name).toBe('project-2')
+})
+
+test('adding a plugin with existing name throws and error', async () => {
+ await expect(() => vitest({
+ workspace: [
+ {
+ test: {
+ name: 'project-1',
+ },
+ plugins: [
+ {
+ name: 'test',
+ async configureVitest({ injectTestProjects }) {
+ await injectTestProjects({
+ test: {
+ name: 'project-1',
+ },
+ })
+ },
+ },
+ ],
+ },
+ ],
+ }),
+ ).rejects.toThrowError('Project name "project-1" is not unique. All projects in a workspace should have unique names. Make sure your configuration is correct.')
+
+ await expect(() => vitest({
+ workspace: [
+ {
+ plugins: [
+ {
+ name: 'test',
+ async configureVitest({ injectTestProjects }) {
+ await injectTestProjects({
+ test: {
+ name: 'project-1',
+ },
+ })
+ await injectTestProjects({
+ test: {
+ name: 'project-1',
+ },
+ })
+ },
+ },
+ ],
+ },
+ ],
+ }),
+ ).rejects.toThrowError('Project name "project-1" is not unique. All projects in a workspace should have unique names. Make sure your configuration is correct.')
+
+ await expect(() => vitest({
+ workspace: [
+ {
+ plugins: [
+ {
+ name: 'test',
+ async configureVitest({ injectTestProjects }) {
+ await injectTestProjects([
+ {
+ test: {
+ name: 'project-1',
+ },
+ },
+ {
+ test: {
+ name: 'project-1',
+ },
+ },
+ ])
+ },
+ },
+ ],
+ },
+ ],
+ }),
+ ).rejects.toThrowError('Project name "project-1" is not unique. All projects in a workspace should have unique names. Make sure your configuration is correct.')
+})