Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ If you are using Vite and have a `vite.config` file, Vitest will read it to matc
- Pass `--config` option to CLI, e.g. `vitest --config ./path/to/vitest.config.ts`
- Use `process.env.VITEST` or `mode` property on `defineConfig` (will be set to `test` if not overridden with `--mode`) to conditionally apply different configuration in `vite.config.ts`. Note that like any other environment variable, `VITEST` is also exposed on `import.meta.env` in your tests

When an explicit `--config` option is not provided, Vitest looks for `vitest.config.{ts,mts,cts,js,mjs,cjs}` first and `vite.config.{ts,mts,cts,js,mjs,cjs}` second in the project [`root`](/config/root). If no config file is found, Vitest will run without one.

To configure `vitest` itself, add `test` property in your Vite config. You'll also need to add a reference to Vitest types using a [triple slash command](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) at the top of your config file, if you are importing `defineConfig` from `vite` itself.

If you are not using `vite`, add `defineConfig` imported from `vitest/config` to your config file:
Expand Down
4 changes: 2 additions & 2 deletions docs/guide/browser/why.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ To achieve the highest level of confidence in our tests, it's crucial to test in

When using Vitest browser, it is important to consider the following drawbacks:

### Early Development
### Not a Drop-In Replacement

The browser mode feature of Vitest is still in its early stages of development. As such, it may not yet be fully optimized, and there may be some bugs or issues that have not yet been ironed out. It is recommended that users augment their Vitest browser experience with a standalone browser-side test runner like WebdriverIO, Cypress or Playwright.
The browser mode feature of Vitest does not completely replace standalone end-to-end test runners. It is recommended that users augment their Vitest browser experience with a standalone browser-side test runner like WebdriverIO, Cypress or Playwright.

### Longer Initialization

Expand Down
9 changes: 9 additions & 0 deletions docs/guide/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ await expect.element(banner).toMatchTextContent(/error/i) // [!code ++]
await expect.element(banner).toHaveTextContent('Error!')
```

### Config Files Are Not Looked Up From Parent Directories

Vitest no longer searches parent directories for config files. If you previously relied on running `vitest` from a subdirectory while using a config file from a parent directory, pass the config explicitly and scope test discovery with `--dir`. For example,

```bash
$ cd subdir && vitest # [!code --]
$ cd subdir && vitest --config ../vitest.config.ts # [!code ++]
```

## Migrating to Vitest 4.0 {#vitest-4}

::: warning Prerequisites
Expand Down
17 changes: 0 additions & 17 deletions packages/vitest/LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,23 +316,6 @@ Repository: egoist/cac

---------------------------------------

## empathic
License: MIT
By: Luke Edwards
Repository: lukeed/empathic

> MIT License
>
> Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

---------------------------------------

## flatted
License: ISC
By: Andrea Giammarchi
Expand Down
1 change: 0 additions & 1 deletion packages/vitest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@
"acorn-walk": "catalog:",
"birpc": "catalog:",
"cac": "catalog:",
"empathic": "^2.0.0",
"flatted": "catalog:",
"happy-dom": "^20.8.3",
"jsdom": "^27.4.0",
Expand Down
7 changes: 2 additions & 5 deletions packages/vitest/src/create/browser/creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import { existsSync, readFileSync } from 'node:fs'
import { writeFile } from 'node:fs/promises'
import { dirname, relative, resolve } from 'node:path'
import { detectPackageManager, installPackage } from '@antfu/install-pkg'
import * as find from 'empathic/find'
import prompt from 'prompts'
import { x } from 'tinyexec'
import c from 'tinyrainbow'
import { configFiles } from '../../constants'
import { findConfigFile } from '../../node/config/resolveConfig'
import { generateExampleFiles } from './examples'

// eslint-disable-next-line no-console
Expand Down Expand Up @@ -424,9 +423,7 @@ export async function create(): Promise<void> {
dependenciesToInstall.filter(pkg => !dependencies[pkg]),
)

const rootConfig = find.any(configFiles, {
cwd: process.cwd(),
})
const rootConfig = findConfigFile(process.cwd())

let scriptCommand = 'vitest'

Expand Down
15 changes: 15 additions & 0 deletions packages/vitest/src/node/config/resolveConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
} from '../types/config'
import type { CoverageOptions, CoverageReporterWithOptions } from '../types/coverage'
import crypto from 'node:crypto'
import { existsSync, statSync } from 'node:fs'
import { pathToFileURL } from 'node:url'
import { slash, toArray } from '@vitest/utils/helpers'
import { resolveModule } from 'local-pkg'
Expand Down Expand Up @@ -45,6 +46,15 @@ function resolvePath(path: string, root: string) {
)
}

export function findConfigFile(root: string): string | undefined {
for (const configFile of configFiles) {
const configPath = resolve(root, configFile)
if (existsSync(configPath)) {
return configPath
}
}
}

function parseInspector(inspect: string | undefined | boolean | number) {
if (typeof inspect === 'boolean' || inspect === undefined) {
return {}
Expand Down Expand Up @@ -178,6 +188,11 @@ export function resolveConfig(
root: viteConfig.root,
} as any as ResolvedConfig

const rootStats = statSync(resolved.root, { throwIfNoEntry: false })
if (!rootStats?.isDirectory()) {
throw new Error(`Root path does not exist or is not a directory: ${resolved.root}`)
}

resolved.mode ??= viteConfig.mode ?? 'test'

if (resolved.retry && typeof resolved.retry === 'object' && typeof resolved.retry.condition === 'function') {
Expand Down
5 changes: 2 additions & 3 deletions packages/vitest/src/node/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import type { VitestOptions } from './core'
import type { VitestRunMode } from './types/config'
import { resolve } from 'node:path'
import { deepClone, slash } from '@vitest/utils/helpers'
import * as find from 'empathic/find'
import { resolveModule } from 'local-pkg'
import { mergeConfig } from 'vite'
import { configFiles } from '../constants'
import { findConfigFile } from './config/resolveConfig'
import { Vitest } from './core'
import { VitestPlugin } from './plugins'
import { createViteServer } from './vite'
Expand Down Expand Up @@ -56,7 +55,7 @@ export async function createVitest(
? false
: options.config
? (resolveModule(options.config, { paths: [root] }) ?? resolve(root, options.config))
: find.any(configFiles, { cwd: root })
: findConfigFile(root)

options.config = configPath

Expand Down
6 changes: 2 additions & 4 deletions packages/vitest/src/node/plugins/publicConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import type {
} from 'vite'
import type { ResolvedConfig, UserConfig } from '../types/config'
import { deepClone, slash } from '@vitest/utils/helpers'
import * as find from 'empathic/find'
import { resolve } from 'pathe'
import { mergeConfig, resolveConfig as resolveViteConfig } from 'vite'
import { configFiles } from '../../constants'
import { resolveConfig as resolveVitestConfig } from '../config/resolveConfig'
import { findConfigFile, resolveConfig as resolveVitestConfig } from '../config/resolveConfig'
import { Vitest } from '../core'
import { VitestPlugin } from './index'

Expand All @@ -24,7 +22,7 @@ export async function resolveConfig(
? false
: options.config
? resolve(root, options.config)
: find.any(configFiles, { cwd: root })
: findConfigFile(root)
options.config = configPath

const vitest = new Vitest(deepClone(options))
Expand Down
3 changes: 0 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 12 additions & 4 deletions test/coverage-test/test/include-exclude.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { BaseCoverageProvider, CoverageOptions } from 'vitest/node'
import { mkdirSync, rmSync } from 'node:fs'
import { join, resolve, sep } from 'node:path'
import { Writable } from 'node:stream'
import { expect, onTestFinished, test } from 'vitest'
Expand Down Expand Up @@ -145,15 +146,22 @@ test('files outside project when allowExternal: true', async () => {
})

test('files with almost matching name, outside project when allowExternal: false', async () => {
const parent = resolve(process.cwd(), `vitest-test-${crypto.randomUUID()}`)
const root = resolve(parent, 'something')
mkdirSync(root, { recursive: true })
onTestFinished(() => {
rmSync(parent, { recursive: true, force: true })
})

const isIncluded = await init({
include: ['**/*.ts'],
root: './something/',
root,
allowExternal: false,
})

expect(isIncluded(resolve(process.cwd(), './something/src/one.ts'))).toBe(true)
expect(isIncluded(resolve(process.cwd(), './not-something/src/two.ts'))).toBe(false)
expect(isIncluded(resolve(process.cwd(), './something-else/src/three.ts'))).toBe(false)
expect(isIncluded(resolve(parent, './something/src/one.ts'))).toBe(true)
expect(isIncluded(resolve(parent, './not-something/src/two.ts'))).toBe(false)
expect(isIncluded(resolve(parent, './something-else/src/three.ts'))).toBe(false)
})

async function init(options: Partial<CoverageOptions> & { testInclude?: string[]; root?: string }) {
Expand Down
70 changes: 68 additions & 2 deletions test/e2e/test/cli-config.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { resolve } from 'pathe'
import { expect, it, test } from 'vitest'
import { createVitest } from 'vitest/node'

import { runVitest } from '../../test-utils'
import { runVitest, useFS } from '../../test-utils'

test('can pass down the config as a module', async () => {
const vitest = await createVitest('test', {
Expand Down Expand Up @@ -62,3 +61,70 @@ it('correctly inherit from the cli', async () => {
})
expect(config.testNamePattern?.test('math')).toBe(true)
})

it('fails when resolved root directory does not exist', async () => {
const parent = resolve(process.cwd(), `vitest-test-${crypto.randomUUID()}`)
useFS(parent, {
'vitest.config.ts': `
throw new Error("parent config file should not be loaded")
`,
})
const root = resolve(parent, 'missing-dir')
const result = await runVitest({ root }, undefined, { fails: true })
expect(result.stderr).toContain('Error: Root path does not exist or is not a directory')
expect(result.stderr).not.toContain('parent config file should not be loaded')
expect(result.thrown).toBe(true)
})

it('does not lookup config from parent directory', async () => {
const parent = resolve(process.cwd(), `vitest-test-${crypto.randomUUID()}`)
useFS(parent, {
'vitest.config.ts': `
throw new Error("parent config file should not be loaded")
`,
'good-dir/basic.test.ts': `
test('ok', () => {})
`,
})
const root = resolve(parent, 'good-dir')
const result = await runVitest({ root, globals: true })
expect(result.stderr).toMatchInlineSnapshot(`""`)
expect(result.errorTree()).toMatchInlineSnapshot(`
{
"basic.test.ts": {
"ok": "passed",
},
}
`)
})

it('loads explicit config from parent directory', async () => {
const parent = resolve(process.cwd(), `vitest-test-${crypto.randomUUID()}`)
useFS(parent, {
'vitest.config.ts': `
export default {
test: {
globals: true,
},
}
`,
'dir1/file1.test.ts': `
test('ok', () => {})
`,
'dir2/file2.test.ts': `
test('ok', () => {})
`,
})
const result = await runVitest({
root: resolve(parent, 'dir1'),
config: resolve(parent, 'vitest.config.ts'),
})
expect(result.stderr).toMatchInlineSnapshot(`""`)
expect(result.errorTree()).toMatchInlineSnapshot(`
{
"file1.test.ts": {
"ok": "passed",
},
}
`)
})
1 change: 1 addition & 0 deletions test/e2e/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export default defineConfig({
server: {
watch: {
ignored: [
'**/vitest-test-*/**',
'**/fixtures/browser-multiple/**/*',
'**/fixtures/browser-init/**/*',
'**/package.json',
Expand Down
Loading