diff --git a/docs/config/index.md b/docs/config/index.md index 3a4778238644..30a72215a532 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/browser/why.md b/docs/guide/browser/why.md index 24e3d04154ef..70ff8aabfe94 100644 --- a/docs/guide/browser/why.md +++ b/docs/guide/browser/why.md @@ -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 diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 4d8ebbe3ec73..1bc18c2497d3 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 10f97d9b504c..50346dafef37 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 196f32912db9..71ab72e578a9 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 9854d43d942b..ef39f1069307 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 1d2b43bd5d02..472be270a0fb 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 e102e934cceb..f7dea48496fd 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 e7c4201761a4..592e2a1a629b 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 add6fdc6b6b3..2771528b269e 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 67d4411ef8a5..e48f7c0ab6a3 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 d0f3c98338b3..1f228d428497 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 5f94d0c8d8a8..83f4bef0c37c 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',