diff --git a/docs/config/index.md b/docs/config/index.md index 3a4778238..30a72215a 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -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: diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 4d8ebbe3e..1bc18c249 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -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 diff --git a/packages/vitest/LICENSE.md b/packages/vitest/LICENSE.md index 10f97d9b5..50346dafe 100644 --- a/packages/vitest/LICENSE.md +++ b/packages/vitest/LICENSE.md @@ -316,23 +316,6 @@ Repository: egoist/cac --------------------------------------- -## empathic -License: MIT -By: Luke Edwards -Repository: lukeed/empathic - -> MIT License -> -> Copyright (c) Luke Edwards (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 diff --git a/packages/vitest/package.json b/packages/vitest/package.json index 196f32912..71ab72e57 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -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", diff --git a/packages/vitest/src/create/browser/creator.ts b/packages/vitest/src/create/browser/creator.ts index 9854d43d9..ef39f1069 100644 --- a/packages/vitest/src/create/browser/creator.ts +++ b/packages/vitest/src/create/browser/creator.ts @@ -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 @@ -424,9 +423,7 @@ export async function create(): Promise { dependenciesToInstall.filter(pkg => !dependencies[pkg]), ) - const rootConfig = find.any(configFiles, { - cwd: process.cwd(), - }) + const rootConfig = findConfigFile(process.cwd()) let scriptCommand = 'vitest' diff --git a/packages/vitest/src/node/config/resolveConfig.ts b/packages/vitest/src/node/config/resolveConfig.ts index 1d2b43bd5..472be270a 100644 --- a/packages/vitest/src/node/config/resolveConfig.ts +++ b/packages/vitest/src/node/config/resolveConfig.ts @@ -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' @@ -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 {} @@ -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') { diff --git a/packages/vitest/src/node/create.ts b/packages/vitest/src/node/create.ts index e102e934c..f7dea4849 100644 --- a/packages/vitest/src/node/create.ts +++ b/packages/vitest/src/node/create.ts @@ -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' @@ -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 diff --git a/packages/vitest/src/node/plugins/publicConfig.ts b/packages/vitest/src/node/plugins/publicConfig.ts index e7c420176..592e2a1a6 100644 --- a/packages/vitest/src/node/plugins/publicConfig.ts +++ b/packages/vitest/src/node/plugins/publicConfig.ts @@ -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' @@ -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)) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index add6fdc6b..2771528b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1156,9 +1156,6 @@ importers: cac: specifier: 'catalog:' version: 6.7.14(patch_hash=a8f0f3517a47ce716ed90c0cfe6ae382ab763b021a664ada2a608477d0621588) - empathic: - specifier: ^2.0.0 - version: 2.0.0 flatted: specifier: 'catalog:' version: 3.4.2 diff --git a/test/coverage-test/test/include-exclude.unit.test.ts b/test/coverage-test/test/include-exclude.unit.test.ts index 67d4411ef..e48f7c0ab 100644 --- a/test/coverage-test/test/include-exclude.unit.test.ts +++ b/test/coverage-test/test/include-exclude.unit.test.ts @@ -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' @@ -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 & { testInclude?: string[]; root?: string }) { diff --git a/test/e2e/test/cli-config.test.ts b/test/e2e/test/cli-config.test.ts index d0f3c9833..1f228d428 100644 --- a/test/e2e/test/cli-config.test.ts +++ b/test/e2e/test/cli-config.test.ts @@ -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', { @@ -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", + }, + } + `) +}) diff --git a/test/e2e/vitest.config.ts b/test/e2e/vitest.config.ts index 5f94d0c8d..83f4bef0c 100644 --- a/test/e2e/vitest.config.ts +++ b/test/e2e/vitest.config.ts @@ -72,6 +72,7 @@ export default defineConfig({ server: { watch: { ignored: [ + '**/vitest-test-*/**', '**/fixtures/browser-multiple/**/*', '**/fixtures/browser-init/**/*', '**/package.json',