diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab6d310e36c5..6213b72d0ecf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1404,9 +1404,6 @@ importers: '@vitest/browser': specifier: workspace:* version: link:../../packages/browser - execa: - specifier: ^7.0.0 - version: 7.1.1 vite: specifier: ^4.2.1 version: 4.2.1(@types/node@18.16.3) @@ -1425,9 +1422,9 @@ importers: test/benchmark: devDependencies: - execa: - specifier: ^6.1.0 - version: 6.1.0 + vitest: + specifier: workspace:* + version: link:../../packages/vitest test/browser: devDependencies: @@ -1482,12 +1479,6 @@ importers: test/config: devDependencies: - execa: - specifier: ^7.0.0 - version: 7.0.0 - strip-ansi: - specifier: ^7.0.1 - version: 7.0.1 vite: specifier: ^4.2.1 version: 4.2.1(@types/node@18.16.3) @@ -1603,9 +1594,6 @@ importers: test/fails: devDependencies: - execa: - specifier: ^6.1.0 - version: 6.1.0 jsdom: specifier: ^21.0.0 version: 21.0.0 @@ -1630,9 +1618,6 @@ importers: test/global-setup-fail: devDependencies: - execa: - specifier: ^6.1.0 - version: 6.1.0 vitest: specifier: workspace:* version: link:../../packages/vitest @@ -1668,24 +1653,21 @@ importers: test/related: devDependencies: - execa: - specifier: ^6.1.0 - version: 6.1.0 vitest: specifier: workspace:* version: link:../../packages/vitest test/reporters: devDependencies: - execa: - specifier: ^6.1.0 - version: 6.1.0 flatted: specifier: ^3.2.7 version: 3.2.7 pkg-reporter: specifier: ./reportPkg/ version: link:reportPkg + strip-ansi: + specifier: ^7.0.1 + version: 7.0.1 vitest: specifier: workspace:* version: link:../../packages/vitest @@ -1707,18 +1689,12 @@ importers: test/setup: devDependencies: - execa: - specifier: ^6.1.0 - version: 6.1.0 vitest: specifier: workspace:* version: link:../../packages/vitest test/shard: devDependencies: - execa: - specifier: ^6.1.0 - version: 6.1.0 vitest: specifier: workspace:* version: link:../../packages/vitest @@ -1736,10 +1712,22 @@ importers: version: link:../../packages/vitest test/stacktraces: + devDependencies: + vitest: + specifier: workspace:* + version: link:../../packages/vitest + + test/test-utils: devDependencies: execa: - specifier: ^6.1.0 - version: 6.1.0 + specifier: ^7.1.1 + version: 7.1.1 + strip-ansi: + specifier: ^7.0.1 + version: 7.0.1 + vite: + specifier: ^4.2.1 + version: 4.2.1(@types/node@18.16.3) vitest: specifier: workspace:* version: link:../../packages/vitest @@ -1750,9 +1738,6 @@ importers: specifier: workspace:* version: link:../../packages/vitest devDependencies: - execa: - specifier: ^6.1.0 - version: 6.1.0 typescript: specifier: ^4.8.4 version: 4.8.4 @@ -1801,12 +1786,6 @@ importers: '@vitest/browser': specifier: workspace:* version: link:../../packages/browser - execa: - specifier: ^7.0.0 - version: 7.0.0 - strip-ansi: - specifier: ^7.0.1 - version: 7.0.1 vite: specifier: ^4.2.1 version: 4.2.1(@types/node@18.16.3) diff --git a/test/bail/package.json b/test/bail/package.json index 694a5df093dc..db99ec6ac37f 100644 --- a/test/bail/package.json +++ b/test/bail/package.json @@ -6,7 +6,6 @@ }, "devDependencies": { "@vitest/browser": "workspace:*", - "execa": "^7.0.0", "vite": "latest", "vitest": "workspace:*", "webdriverio": "latest" diff --git a/test/bail/test/bail.test.ts b/test/bail/test/bail.test.ts index f42293710a14..48234281fa37 100644 --- a/test/bail/test/bail.test.ts +++ b/test/bail/test/bail.test.ts @@ -1,34 +1,26 @@ -import { expect, test } from 'vitest' -import { execa } from 'execa' +import { type UserConfig, expect, test } from 'vitest' -const configs: string[][] = [] -const pools = [['--threads', 'true'], ['--threads', 'false'], ['--single-thread']] +import { runVitest } from '../../test-utils' + +const configs: UserConfig[] = [] +const pools: UserConfig[] = [{ threads: true }, { threads: false }, { singleThread: true }] if (process.platform !== 'win32') - pools.push(['--browser']) - -for (const isolate of ['true', 'false']) { - for (const pool of pools) { - configs.push([ - '--bail', - '1', - '--isolate', - isolate, - ...pool, - ]) - } + pools.push({ browser: { enabled: true, name: 'chrome' } }) + +for (const isolate of [true, false]) { + for (const pool of pools) + configs.push({ isolate, ...pool }) } for (const config of configs) { - test(`should bail with "${config.join(' ')}"`, async () => { - const { exitCode, stdout } = await execa('vitest', [ - '--no-color', - '--root', - 'fixtures', + test(`should bail with "${JSON.stringify(config)}"`, async () => { + process.env.THREADS = config?.threads ? 'true' : 'false' + + const { exitCode, stdout } = await runVitest({ + root: './fixtures', + bail: 1, ...config, - ], { - reject: false, - env: { THREADS: config.join(' ').includes('--threads true') ? 'true' : 'false' }, }) expect(exitCode).toBe(1) diff --git a/test/benchmark/package.json b/test/benchmark/package.json index 16b0a4d04e74..a266eee9bf37 100644 --- a/test/benchmark/package.json +++ b/test/benchmark/package.json @@ -4,10 +4,9 @@ "scripts": { "test": "node test.mjs", "bench:json": "vitest bench --reporter=json", - "bench": "vitest bench", - "coverage": "vitest run --coverage" + "bench": "vitest bench" }, "devDependencies": { - "execa": "^6.1.0" + "vitest": "workspace:*" } } diff --git a/test/benchmark/test.mjs b/test/benchmark/test.mjs index 95e0054d815f..470c43426e8e 100644 --- a/test/benchmark/test.mjs +++ b/test/benchmark/test.mjs @@ -1,19 +1,14 @@ +import { existsSync, rmSync } from 'node:fs' import { readFile } from 'node:fs/promises' -import { execa } from 'execa' +import { startVitest } from 'vitest/node' -let error -await execa('npx', ['vitest', 'bench', 'base.bench', 'mode.bench', 'only.bench'], { - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, -}) - .catch((e) => { - error = e - }) +if (existsSync('./bench.json')) + rmSync('./bench.json') -if (error) { +try { + await startVitest('benchmark', ['base.bench', 'mode.bench', 'only.bench']) +} +catch (error) { console.error(error) process.exit(1) } diff --git a/test/benchmark/vitest.config.ts b/test/benchmark/vitest.config.ts index 261935cfc4fa..790c4797b8e3 100644 --- a/test/benchmark/vitest.config.ts +++ b/test/benchmark/vitest.config.ts @@ -19,7 +19,7 @@ export default defineConfig({ onWatcherRerun: noop, onServerRestart: noop, onUserConsoleLog: noop, - }, 'default'], + }], }, }, }) diff --git a/test/config/package.json b/test/config/package.json index 56c95551587d..949e36ea2baa 100644 --- a/test/config/package.json +++ b/test/config/package.json @@ -5,8 +5,6 @@ "test": "vitest run" }, "devDependencies": { - "execa": "^7.0.0", - "strip-ansi": "^7.0.1", "vite": "latest", "vitest": "workspace:*" } diff --git a/test/config/test/failures.test.ts b/test/config/test/failures.test.ts index 0dcfcd0f9032..0ad3abf50ed9 100644 --- a/test/config/test/failures.test.ts +++ b/test/config/test/failures.test.ts @@ -1,73 +1,82 @@ import { expect, test } from 'vitest' +import type { UserConfig } from 'vitest/config' import { version } from 'vitest/package.json' -import { runVitest } from './utils' +import * as testUtils from '../../test-utils' + +function runVitest(config: NonNullable & { shard?: any }) { + return testUtils.runVitest(config, ['fixtures/test/']) +} + +function runVitestCli(...cliArgs: string[]) { + return testUtils.runVitestCli('run', 'fixtures/test/', ...cliArgs) +} test('shard cannot be used with watch mode', async () => { - const { error } = await runVitest('watch', ['--shard', '1/2']) + const { stderr } = await runVitest({ watch: true, shard: '1/2' }) - expect(error).toMatch('Error: You cannot use --shard option with enabled watch') + expect(stderr).toMatch('Error: You cannot use --shard option with enabled watch') }) test('shard must be positive number', async () => { - const { error } = await runVitest('run', ['--shard', '"-1"']) + const { stderr } = await runVitest({ shard: '-1' }) - expect(error).toMatch('Error: --shard must be a positive number') + expect(stderr).toMatch('Error: --shard must be a positive number') }) test('shard index must be smaller than count', async () => { - const { error } = await runVitest('run', ['--shard', '2/1']) + const { stderr } = await runVitest({ shard: '2/1' }) - expect(error).toMatch('Error: --shard must be a positive number less then ') + expect(stderr).toMatch('Error: --shard must be a positive number less then ') }) test('inspect requires changing threads or singleThread', async () => { - const { error } = await runVitest('run', ['--inspect']) + const { stderr } = await runVitest({ inspect: true }) - expect(error).toMatch('Error: You cannot use --inspect without "threads: false" or "singleThread: true"') + expect(stderr).toMatch('Error: You cannot use --inspect without "threads: false" or "singleThread: true"') }) test('inspect cannot be used with threads', async () => { - const { error } = await runVitest('run', ['--inspect', '--threads', 'true']) + const { stderr } = await runVitest({ inspect: true, threads: true }) - expect(error).toMatch('Error: You cannot use --inspect without "threads: false" or "singleThread: true"') + expect(stderr).toMatch('Error: You cannot use --inspect without "threads: false" or "singleThread: true"') }) test('inspect-brk cannot be used with threads', async () => { - const { error } = await runVitest('run', ['--inspect-brk', '--threads', 'true']) + const { stderr } = await runVitest({ inspectBrk: true, threads: true }) - expect(error).toMatch('Error: You cannot use --inspect-brk without "threads: false" or "singleThread: true"') + expect(stderr).toMatch('Error: You cannot use --inspect-brk without "threads: false" or "singleThread: true"') }) test('c8 coverage provider cannot be used with browser', async () => { - const { error } = await runVitest('run', ['--coverage.enabled', '--browser']) + const { stderr } = await runVitest({ coverage: { enabled: true }, browser: { enabled: true, name: 'chrome' } }) + + expect(stderr).toMatch('Error: @vitest/coverage-c8 does not work with --browser. Use @vitest/coverage-istanbul instead') +}) - expect(error).toMatch('Error: @vitest/coverage-c8 does not work with --browser. Use @vitest/coverage-istanbul instead') +test('version number is printed when coverage provider fails to load', async () => { + const { stderr, stdout } = await runVitest({ + coverage: { + enabled: true, + provider: 'custom', + customProviderModule: './non-existing-module.ts', + }, + }) + + expect(stdout).toMatch(`RUN v${version}`) + expect(stderr).toMatch('Error: Failed to load custom CoverageProviderModule from ./non-existing-module.ts') }) test('boolean coverage flag without dot notation, with more dot notation options', async () => { - const { error } = await runVitest('run', ['--coverage', '--coverage.reporter', 'text']) + const { stderr } = await runVitestCli('--coverage', '--coverage.reporter', 'text') - expect(error).toMatch('Error: A boolean argument "--coverage" was used with dot notation arguments "--coverage.reporter".') - expect(error).toMatch('Please specify the "--coverage" argument with dot notation as well: "--coverage.enabled"') + expect(stderr).toMatch('Error: A boolean argument "--coverage" was used with dot notation arguments "--coverage.reporter".') + expect(stderr).toMatch('Please specify the "--coverage" argument with dot notation as well: "--coverage.enabled"') }) test('boolean browser flag without dot notation, with more dot notation options', async () => { - const { error } = await runVitest('run', ['--browser', '--browser.name', 'chrome']) - - expect(error).toMatch('Error: A boolean argument "--browser" was used with dot notation arguments "--browser.name".') - expect(error).toMatch('Please specify the "--browser" argument with dot notation as well: "--browser.enabled"') -}) + const { stderr } = await runVitestCli('run', '--browser', '--browser.name', 'chrome') -test('version number is printed when coverage provider fails to load', async () => { - const { error, output } = await runVitest('run', [ - '--coverage.enabled', - '--coverage.provider', - 'custom', - '--coverage.customProviderModule', - './non-existing-module.ts', - ]) - - expect(output).toMatch(`RUN v${version}`) - expect(error).toMatch('Error: Failed to load custom CoverageProviderModule from ./non-existing-module.ts') + expect(stderr).toMatch('Error: A boolean argument "--browser" was used with dot notation arguments "--browser.name".') + expect(stderr).toMatch('Please specify the "--browser" argument with dot notation as well: "--browser.enabled"') }) diff --git a/test/config/test/utils.ts b/test/config/test/utils.ts deleted file mode 100644 index 1dc0ccabd2e5..000000000000 --- a/test/config/test/utils.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { execa } from 'execa' -import stripAnsi from 'strip-ansi' - -export async function runVitest(mode: 'run' | 'watch', cliArguments: string[]) { - const subprocess = execa('vitest', [mode, 'fixtures/test/', ...cliArguments]) - let error = '' - let output = '' - - subprocess.stdout?.on('data', (data) => { - output += stripAnsi(data.toString()) - }) - - subprocess.stderr?.on('data', (data) => { - error += stripAnsi(data.toString()) - }) - - await new Promise(resolve => subprocess.on('exit', resolve)) - - return { output, error } -} diff --git a/test/config/vitest.config.ts b/test/config/vitest.config.ts index 8d362f825f92..0fb0f723dc01 100644 --- a/test/config/vitest.config.ts +++ b/test/config/vitest.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { include: ['test/**.test.ts'], + reporters: ['verbose'], testTimeout: 60_000, chaiConfig: { truncateThreshold: 999, diff --git a/test/fails/package.json b/test/fails/package.json index 7c39c13b224b..fe036ef4e339 100644 --- a/test/fails/package.json +++ b/test/fails/package.json @@ -6,7 +6,6 @@ "coverage": "vitest run --coverage" }, "devDependencies": { - "execa": "^6.1.0", "jsdom": "^21.0.0", "vitest": "workspace:*" } diff --git a/test/fails/test/runner.test.ts b/test/fails/test/runner.test.ts index 7cd5ebbc59cb..54ae49740be1 100644 --- a/test/fails/test/runner.test.ts +++ b/test/fails/test/runner.test.ts @@ -1,33 +1,19 @@ import { resolve } from 'pathe' import fg from 'fast-glob' -import { execa } from 'execa' import { describe, expect, it } from 'vitest' +import { runVitest } from '../../test-utils' + describe('should fails', async () => { const root = resolve(__dirname, '../fixtures') const files = await fg('**/*.test.ts', { cwd: root, dot: true }) for (const file of files) { it(file, async () => { - // in Windows child_process is very unstable, we skip testing it - if (process.platform === 'win32' && process.env.CI) - return - - let error: any - await execa('npx', ['vitest', 'run', file], { - cwd: root, - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - }) - .catch((e) => { - error = e - }) + const { stderr } = await runVitest({ root }, [file]) - expect(error).toBeTruthy() - const msg = String(error) + expect(stderr).toBeTruthy() + const msg = String(stderr) .split(/\n/g) .reverse() .filter(i => i.includes('Error: ') && !i.includes('Command failed') && !i.includes('stackStr') && !i.includes('at runTest')) diff --git a/test/filters/test/testname-pattern.test.ts b/test/filters/test/testname-pattern.test.ts index 9aaa7b0cea12..6a134c6c1615 100644 --- a/test/filters/test/testname-pattern.test.ts +++ b/test/filters/test/testname-pattern.test.ts @@ -1,50 +1,32 @@ import { resolve } from 'pathe' import { expect, test } from 'vitest' -import type { File } from 'vitest' -import { startVitest } from 'vitest/node' + +import { runVitest } from '../../test-utils' test('match by partial pattern', async () => { - const output = await runVitest('example') + const { stdout } = await runVitest({ root: './fixtures' }, ['example']) - expect(output).toMatchInlineSnapshot('"pass: test/example.test.ts"') + expect(stdout).toMatch('✓ test/example.test.ts > this will pass') + expect(stdout).toMatch('Test Files 1 passed (1)') + expect(stdout).not.toMatch('test/filters.test.ts') }) test('match by full test file name', async () => { const filename = resolve('./fixtures/test/example.test.ts') - const output = await runVitest(filename) + const { stdout } = await runVitest({ root: './fixtures' }, [filename]) - expect(output).toMatchInlineSnapshot('"pass: test/example.test.ts"') + expect(stdout).toMatch('✓ test/example.test.ts > this will pass') + expect(stdout).toMatch('Test Files 1 passed (1)') + expect(stdout).not.toMatch('test/filters.test.ts') }) test('match by pattern that also matches current working directory', async () => { const filter = 'filters' expect(process.cwd()).toMatch(filter) - const output = await runVitest(filter) - expect(output).toMatchInlineSnapshot('"pass: test/filters.test.ts"') -}) + const { stdout } = await runVitest({ root: './fixtures' }, [filter]) -async function runVitest(...cliArgs: string[]) { - let resolve: (value: string) => void - const promise = new Promise((_resolve) => { - resolve = _resolve - }) - - await startVitest('test', cliArgs, { - root: './fixtures', - watch: false, - reporters: [{ - onFinished(files?: File[], errors?: unknown[]) { - if (errors?.length) - resolve(`Error: ${JSON.stringify(errors, null, 2)}`) - - if (files) - resolve(files.map(file => `${file.result?.state}: ${file.name}`).join('\n')) - else - resolve('No files') - }, - }], - }) - - return promise -} + expect(stdout).toMatch('✓ test/filters.test.ts > this will pass') + expect(stdout).toMatch('Test Files 1 passed (1)') + expect(stdout).not.toMatch('test/example.test.ts') +}) diff --git a/test/global-setup-fail/fixtures/vitest.config.ts b/test/global-setup-fail/fixtures/vitest.config.ts index 8b5713f83ba3..119e478a0181 100644 --- a/test/global-setup-fail/fixtures/vitest.config.ts +++ b/test/global-setup-fail/fixtures/vitest.config.ts @@ -1,10 +1,15 @@ +import { resolve } from 'node:path' +import { fileURLToPath } from 'node:url' import { defineConfig } from 'vite' +const __filename = fileURLToPath(import.meta.url) +const __dirname = resolve(__filename, '..') + export default defineConfig({ test: { globals: true, globalSetup: [ - './globalSetup/error.js', + resolve(__dirname, './globalSetup/error.js'), ], }, }) diff --git a/test/global-setup-fail/package.json b/test/global-setup-fail/package.json index 8cad578a4425..9244af279411 100644 --- a/test/global-setup-fail/package.json +++ b/test/global-setup-fail/package.json @@ -6,7 +6,6 @@ "test": "vitest" }, "devDependencies": { - "execa": "^6.1.0", "vitest": "workspace:*" } } diff --git a/test/global-setup-fail/test/runner.test.ts b/test/global-setup-fail/test/runner.test.ts index b2c8f4b432e6..dcc797007e85 100644 --- a/test/global-setup-fail/test/runner.test.ts +++ b/test/global-setup-fail/test/runner.test.ts @@ -1,28 +1,14 @@ import { resolve } from 'pathe' -import { execa } from 'execa' import { expect, it } from 'vitest' -it('should fail', async () => { - // in Windows child_process is very unstable, we skip testing it - if (process.platform === 'win32' && process.env.CI) - return +import { runVitest } from '../../test-utils' +it('should fail', async () => { const root = resolve(__dirname, '../fixtures') - let error: any - await execa('npx', ['vitest'], { - cwd: root, - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - }) - .catch((e) => { - error = e - }) + const { stderr } = await runVitest({ root }) - expect(error).toBeTruthy() - const msg = String(error) + expect(stderr).toBeTruthy() + const msg = String(stderr) .split(/\n/g) .reverse() .find(i => i.includes('Error: ')) diff --git a/test/related/package.json b/test/related/package.json index 1d0653ebf985..aa799f30df73 100644 --- a/test/related/package.json +++ b/test/related/package.json @@ -2,12 +2,11 @@ "name": "@vitest/test-related", "private": true, "scripts": { - "test": "nr test:related && nr test:rerun", + "test": "pnpm test:related && pnpm test:rerun", "test:related": "vitest related src/sourceA.ts --globals --watch=false", "test:rerun": "vitest run rerun" }, "devDependencies": { - "execa": "^6.1.0", "vitest": "workspace:*" } } diff --git a/test/related/tests/force-rerun.test.ts b/test/related/tests/force-rerun.test.ts index 808fc90432e9..79f373f3e4bd 100644 --- a/test/related/tests/force-rerun.test.ts +++ b/test/related/tests/force-rerun.test.ts @@ -1,9 +1,14 @@ import { unlink, writeFile } from 'node:fs' import { beforeEach, describe, expect, it } from 'vitest' -import { execa } from 'execa' + +import { runVitest } from '../../test-utils' async function run() { - return await execa('vitest', ['run', '--changed', '--config', 'force-rerun.vitest.config.ts']) + return runVitest({ + include: ['tests/related.test.ts'], + forceRerunTriggers: ['**/rerun.temp/**'], + changed: true, + }) } const fileName = 'rerun.temp' diff --git a/test/reporters/package.json b/test/reporters/package.json index 497f184ffe54..a4d56a9937ea 100644 --- a/test/reporters/package.json +++ b/test/reporters/package.json @@ -2,12 +2,12 @@ "name": "@vitest/test-reporters", "private": true, "scripts": { - "test": "vitest run" + "test": "NO_COLOR=1 vitest run" }, "devDependencies": { - "execa": "^6.1.0", "flatted": "^3.2.7", "pkg-reporter": "./reportPkg/", + "strip-ansi": "^7.0.1", "vitest": "workspace:*", "vitest-sonar-reporter": "0.3.3" } diff --git a/test/reporters/tests/custom-reporter.spec.ts b/test/reporters/tests/custom-reporter.spec.ts index e3598aa361b5..f4adfc55dd66 100644 --- a/test/reporters/tests/custom-reporter.spec.ts +++ b/test/reporters/tests/custom-reporter.spec.ts @@ -1,24 +1,17 @@ -import { execa } from 'execa' import { resolve } from 'pathe' import { describe, expect, test } from 'vitest' +import TestReporter from '../src/custom-reporter' +import { runVitest, runVitestCli } from '../../test-utils' + const customTsReporterPath = resolve(__dirname, '../src/custom-reporter.ts') const customJSReporterPath = resolve(__dirname, '../src/custom-reporter.js') +const root = resolve(__dirname, '..') async function run(...runOptions: string[]): Promise { - const root = resolve(__dirname, '..') - - const { stdout } = await execa('npx', ['vitest', 'run', ...runOptions], { - cwd: root, - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - windowsHide: false, - }) + const vitest = await runVitestCli({ cwd: root, windowsHide: false }, 'run', ...runOptions) - return stdout + return vitest.stdout } async function runWithRetry(...runOptions: string[]) { @@ -35,15 +28,15 @@ async function runWithRetry(...runOptions: string[]) { } } -describe.concurrent('custom reporters', () => { +describe('custom reporters', () => { // On Windows and macOS child_process is very unstable, we skip testing it as the functionality is tested on Linux if ((process.platform === 'win32' || process.platform === 'darwin') && process.env.CI) return test.skip('skip on windows') const TIMEOUT = 60_000 - test('custom reporter instances defined in configuration works', async () => { - const stdout = await runWithRetry('--config', 'custom-reporter.vitest.config.ts') + test('custom reporter instances works', async () => { + const { stdout } = await runVitest({ root, reporters: [new TestReporter()], include: ['tests/reporters.spec.ts'] }) expect(stdout).includes('hello from custom reporter') }, TIMEOUT) @@ -53,12 +46,17 @@ describe.concurrent('custom reporters', () => { }, TIMEOUT) test('package.json dependencies reporter instances defined in configuration works', async () => { - const stdout = await runWithRetry('--config', 'deps-reporter.vitest.config.ts') + const { stdout } = await runVitest({ + root, + include: ['tests/reporters.spec.ts'], + reporters: ['pkg-reporter', 'vitest-sonar-reporter'], + outputFile: './sonar-config.xml', + }) expect(stdout).includes('hello from package reporter') }, TIMEOUT) test('a path to a custom reporter defined in configuration works', async () => { - const stdout = await runWithRetry('--config', 'custom-reporter-path.vitest.config.ts', '--reporter', customJSReporterPath) + const { stdout } = await runVitest({ root, reporters: customJSReporterPath, include: ['tests/reporters.spec.ts'] }) expect(stdout).includes('hello from custom reporter') }, TIMEOUT) diff --git a/test/reporters/tests/html.test.ts b/test/reporters/tests/html.test.ts index be05c6524642..90bcb74b5b30 100644 --- a/test/reporters/tests/html.test.ts +++ b/test/reporters/tests/html.test.ts @@ -1,27 +1,21 @@ import fs from 'node:fs' import zlib from 'node:zlib' import { resolve } from 'pathe' -import { execa } from 'execa' import { describe, expect, it } from 'vitest' import { parse } from 'flatted' +import stripAnsi from 'strip-ansi' -const skip = (process.platform === 'win32' || process.platform === 'darwin') && process.env.CI +import { runVitest } from '../../test-utils' -describe.skipIf(skip)('html reporter', async () => { +describe('html reporter', async () => { const vitestRoot = resolve(__dirname, '../../..') const root = resolve(__dirname, '../fixtures') it('resolves to "passing" status for test file "all-passing-or-skipped"', async () => { const [expected, testFile, basePath] = ['passing', 'all-passing-or-skipped', 'html/all-passing-or-skipped'] - await execa('npx', ['vitest', 'run', testFile, '--reporter=html', `--outputFile=${basePath}/index.html`], { - cwd: root, - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - stdio: 'inherit', - }).catch(e => e) + + await runVitest({ reporters: 'html', outputFile: `${basePath}/index.html`, root }, [testFile]) + const metaJsonGzipeed = fs.readFileSync(resolve(root, `${basePath}/html.meta.json.gz`)) const metaJson = zlib.gunzipSync(metaJsonGzipeed).toString('utf-8') const indexHtml = fs.readFileSync(resolve(root, `${basePath}/index.html`), { encoding: 'utf-8' }) @@ -47,15 +41,9 @@ describe.skipIf(skip)('html reporter', async () => { it('resolves to "failing" status for test file "json-fail"', async () => { const [expected, testFile, basePath] = ['failing', 'json-fail', 'html/fail'] - await execa('npx', ['vitest', 'run', testFile, '--reporter=html', `--outputFile=${basePath}/index.html`], { - cwd: root, - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - stdio: 'inherit', - }).catch(e => e) + + await runVitest({ reporters: 'html', outputFile: `${basePath}/index.html`, root }, [testFile]) + const metaJsonGzipped = fs.readFileSync(resolve(root, `${basePath}/html.meta.json.gz`)) const metaJson = zlib.gunzipSync(metaJsonGzipped).toString('utf-8') const indexHtml = fs.readFileSync(resolve(root, `${basePath}/index.html`), { encoding: 'utf-8' }) @@ -83,7 +71,7 @@ describe.skipIf(skip)('html reporter', async () => { expect(task.logs).toHaveLength(1) task.logs[0].taskId = 0 task.logs[0].time = 0 - expect(resultJson).toMatchSnapshot(`tests are ${expected}`) + expect(resultJson).toMatchSnapshot(`tests are ${stripAnsi(expected)}`) expect(indexHtml).toMatch('window.METADATA_PATH="html.meta.json.gz"') }, 120000) }) diff --git a/test/reporters/tests/json.test.ts b/test/reporters/tests/json.test.ts index 217e56c30546..e6bdcc820cbc 100644 --- a/test/reporters/tests/json.test.ts +++ b/test/reporters/tests/json.test.ts @@ -1,22 +1,13 @@ import { resolve } from 'pathe' -import { execa } from 'execa' import { describe, expect, it } from 'vitest' +import { runVitest } from '../../test-utils' + describe('json reporter', async () => { const root = resolve(__dirname, '../fixtures') - const skip = (process.platform === 'win32' || process.platform === 'darwin') && process.env.CI - - it.skipIf(skip)('generates correct report', async () => { - const { stdout } = await execa('npx', ['vitest', 'run', 'json-fail', '--reporter=json'], { - cwd: root, - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - stdio: 'pipe', - }).catch(e => e) + it('generates correct report', async () => { + const { stdout } = await runVitest({ reporters: 'json', root }, ['json-fail']) const data = JSON.parse(stdout) @@ -28,20 +19,12 @@ describe('json reporter', async () => { expect(result).toMatchSnapshot() }, 40000) - it.skipIf(skip).each([ + it.each([ ['passed', 'all-passing-or-skipped'], ['passed', 'all-skipped'], ['failed', 'some-failing'], ])('resolves to "%s" status for test file "%s"', async (expected, file) => { - const { stdout } = await execa('npx', ['vitest', 'run', file, '--reporter=json'], { - cwd: root, - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - stdio: 'pipe', - }).catch(e => e) + const { stdout } = await runVitest({ reporters: 'json', root }, [file]) const data = JSON.parse(stdout) diff --git a/test/setup/package.json b/test/setup/package.json index 8f59d466bbb7..4a07af4100d2 100644 --- a/test/setup/package.json +++ b/test/setup/package.json @@ -5,7 +5,6 @@ "test": "vitest" }, "devDependencies": { - "execa": "^6.1.0", "vitest": "workspace:*" } } diff --git a/test/setup/setup.vitest.config.ts b/test/setup/setup.vitest.config.ts deleted file mode 100644 index 84c7a1e6f7ac..000000000000 --- a/test/setup/setup.vitest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - include: ['tests/empty-setup.test.ts'], - setupFiles: ['setupFiles/empty-setup.ts'], - }, -}) diff --git a/test/setup/tests/setup-files.test.ts b/test/setup/tests/setup-files.test.ts index f8e30d453825..4146e2b9b44c 100644 --- a/test/setup/tests/setup-files.test.ts +++ b/test/setup/tests/setup-files.test.ts @@ -1,9 +1,14 @@ import { promises as fs } from 'node:fs' import { afterEach, describe, expect, it } from 'vitest' -import { execa } from 'execa' + +import { runVitest } from '../../test-utils' async function run() { - return await execa('vitest', ['run', '--changed', '--config', 'setup.vitest.config.ts']) + return await runVitest({ + include: ['tests/empty-setup.test.ts'], + setupFiles: ['setupFiles/empty-setup.ts'], + changed: true, + }) } describe('setup files with forceRerunTrigger', () => { @@ -13,6 +18,7 @@ describe('setup files with forceRerunTrigger', () => { await fs.writeFile(file, '', 'utf-8') }) + // Note that this test will fail locally if you have uncommitted changes it('should run no tests if setup file is not changed', async () => { const { stdout } = await run() expect(stdout).toContain('No test files found, exiting with code 0') diff --git a/test/shard/package.json b/test/shard/package.json index 8a28b5035add..f6609954fca2 100644 --- a/test/shard/package.json +++ b/test/shard/package.json @@ -5,7 +5,6 @@ "test": "vitest run shard-test.test.ts" }, "devDependencies": { - "execa": "^6.1.0", "vitest": "workspace:*" } } diff --git a/test/shard/shard-test.test.ts b/test/shard/shard-test.test.ts index d23e0d2056dc..0cd167f7689c 100644 --- a/test/shard/shard-test.test.ts +++ b/test/shard/shard-test.test.ts @@ -1,10 +1,10 @@ -import { expect, test } from 'vitest' +import { type UserConfig, expect, test } from 'vitest' import { basename } from 'pathe' -import { execa } from 'execa' -async function runVitest(args: string[]) { - const { stdout } = await execa('vitest', ['--run', '--dir', './test', ...args]) - return stdout +import * as testUtils from '../test-utils' + +function runVitest(config: UserConfig) { + return testUtils.runVitest({ ...config, dir: './test' }) } function parsePaths(stdout: string) { @@ -16,7 +16,7 @@ function parsePaths(stdout: string) { } test('--shard=1/1', async () => { - const stdout = await runVitest(['--shard=1/1']) + const { stdout } = await runVitest({ shard: '1/1' }) const paths = parsePaths(stdout) @@ -24,7 +24,7 @@ test('--shard=1/1', async () => { }) test('--shard=1/2', async () => { - const stdout = await runVitest(['--shard=1/2']) + const { stdout } = await runVitest({ shard: '1/2' }) const paths = parsePaths(stdout) @@ -32,7 +32,7 @@ test('--shard=1/2', async () => { }) test('--shard=2/2', async () => { - const stdout = await runVitest(['--shard=2/2']) + const { stdout } = await runVitest({ shard: '2/2' }) const paths = parsePaths(stdout) @@ -40,7 +40,7 @@ test('--shard=2/2', async () => { }) test('--shard=4/4', async () => { - const stdout = await runVitest(['--shard=4/4']) + const { stdout } = await runVitest({ shard: '4/4' }) const paths = parsePaths(stdout) diff --git a/test/stacktraces/package.json b/test/stacktraces/package.json index 818202bc1bc0..94844f2d9d3e 100644 --- a/test/stacktraces/package.json +++ b/test/stacktraces/package.json @@ -1,5 +1,5 @@ { - "name": "@vitest/test-fails", + "name": "@vitest/test-stacktraces", "type": "module", "private": true, "scripts": { @@ -7,7 +7,6 @@ "coverage": "vitest run --coverage" }, "devDependencies": { - "execa": "^6.1.0", "vitest": "workspace:*" } } diff --git a/test/stacktraces/test/__snapshots__/runner.test.ts.snap b/test/stacktraces/test/__snapshots__/runner.test.ts.snap index 6cba882358e8..ad776b8d4fa4 100644 --- a/test/stacktraces/test/__snapshots__/runner.test.ts.snap +++ b/test/stacktraces/test/__snapshots__/runner.test.ts.snap @@ -15,6 +15,7 @@ ReferenceError: bar is not defined ❯ error-in-deps.test.js:5:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ + " `; @@ -66,6 +67,7 @@ exports[`stacktraces should respect sourcemaps > error-in-deps.test.js > error-i " ❯ error-in-deps.test.js:5:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ + " `; diff --git a/test/stacktraces/test/runner.test.ts b/test/stacktraces/test/runner.test.ts index f1c36ef63753..69feb285138a 100644 --- a/test/stacktraces/test/runner.test.ts +++ b/test/stacktraces/test/runner.test.ts @@ -1,28 +1,19 @@ import { resolve } from 'pathe' import fg from 'fast-glob' -import { execa } from 'execa' import { describe, expect, it } from 'vitest' +import { runVitest } from '../../test-utils' + +// To prevent the warnining coming up in snapshots +process.setMaxListeners(20) + describe('stacktraces should respect sourcemaps', async () => { const root = resolve(__dirname, '../fixtures') const files = await fg('*.test.*', { cwd: root }) for (const file of files) { it(file, async () => { - // in Windows child_process is very unstable, we skip testing it - if (process.platform === 'win32' && process.env.CI) - return - - const { stderr } = await execa('npx', ['vitest', 'run', file], { - cwd: root, - reject: false, - stdio: 'pipe', - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - }) + const { stderr } = await runVitest({ root }, [file]) expect(stderr).toBeTruthy() const lines = String(stderr).split(/\n/g) @@ -39,20 +30,7 @@ describe('stacktraces should pick error frame if present', async () => { for (const file of files) { it(file, async () => { - // in Windows child_process is very unstable, we skip testing it - if (process.platform === 'win32' && process.env.CI) - return - - const { stderr } = await execa('npx', ['vitest', 'run', file], { - cwd: root, - reject: false, - stdio: 'pipe', - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - }) + const { stderr } = await runVitest({ root }, [file]) expect(stderr).toBeTruthy() const lines = String(stderr).split(/\n/g) @@ -66,21 +44,9 @@ describe('stacktraces should pick error frame if present', async () => { describe('stacktrace should print error frame source file correctly', async () => { const root = resolve(__dirname, '../fixtures') const testFile = resolve(root, './error-in-deps.test.js') - it('error-in-deps', async () => { - // in Windows child_process is very unstable, we skip testing it - if (process.platform === 'win32' && process.env.CI) - return - const { stderr } = await execa('npx', ['vitest', 'run', testFile], { - cwd: root, - reject: false, - stdio: 'pipe', - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - }) + it('error-in-deps', async () => { + const { stderr } = await runVitest({ root }, [testFile]) // expect to print framestack of foo.js expect(stderr).toMatchSnapshot('error-in-deps') diff --git a/test/test-utils/index.ts b/test/test-utils/index.ts new file mode 100644 index 000000000000..dadc05360c40 --- /dev/null +++ b/test/test-utils/index.ts @@ -0,0 +1,186 @@ +import { Console } from 'node:console' +import { Writable } from 'node:stream' +import { type UserConfig, type VitestRunMode, afterEach } from 'vitest' +import { startVitest } from 'vitest/node' +import { type Options, execa } from 'execa' +import stripAnsi from 'strip-ansi' + +export async function runVitest(config: UserConfig, cliFilters: string[] = [], mode: VitestRunMode = 'test') { + // Reset possible previous runs + process.exitCode = 0 + + // Prevent possible process.exit() calls, e.g. from --browser + const exit = process.exit + process.exit = (() => { }) as never + + const { getLogs, restore } = captureLogs() + + try { + await startVitest(mode, cliFilters, { + watch: false, + reporters: ['verbose'], + ...config, + }) + } + catch (e: any) { + return { + stderr: `${getLogs().stderr}\n${e.message}`, + stdout: getLogs().stdout, + exitCode: process.exitCode, + } + } + finally { + restore() + } + + const exitCode = process.exitCode + process.exitCode = 0 + process.exit = exit + + return { ...getLogs(), exitCode } +} + +function captureLogs() { + const stdout: string[] = [] + const stderr: string[] = [] + + const streams = { + stdout: new Writable({ + write(chunk, _, callback) { + stdout.push(chunk.toString()) + callback() + }, + }), + stderr: new Writable({ + write(chunk, _, callback) { + stderr.push(chunk.toString()) + callback() + }, + }), + } + + const originalConsole = globalThis.console + globalThis.console = new Console(streams) + + const originalStdoutWrite = process.stdout.write + process.stdout.write = streams.stdout.write.bind(streams.stdout) as any + + const originalStderrWrite = process.stderr.write + process.stderr.write = streams.stderr.write.bind(streams.stderr) as any + + return { + restore: () => { + globalThis.console = originalConsole + process.stdout.write = originalStdoutWrite + process.stderr.write = originalStderrWrite + }, + getLogs() { + return { + stdout: stripAnsi(stdout.join('')), + stderr: stripAnsi(stderr.join('')), + } + }, + } +} + +export async function runVitestCli(_options?: Options | string, ...args: string[]) { + let options = _options + + if (typeof _options === 'string') { + args.unshift(_options) + options = undefined + } + + const subprocess = execa('vitest', args, options as Options) + + let setDone: (value?: unknown) => void + const isDone = new Promise(resolve => (setDone = resolve)) + + const vitest = { + stdout: '', + stderr: '', + stdoutListeners: [] as (() => void)[], + stderrListeners: [] as (() => void)[], + isDone, + write(text: string) { + this.resetOutput() + subprocess.stdin!.write(text) + }, + waitForStdout(expected: string) { + return new Promise((resolve, reject) => { + if (this.stdout.includes(expected)) + return resolve() + + const timeout = setTimeout(() => { + reject(new Error(`Timeout when waiting for output "${expected}".\nReceived:\n${this.stdout}`)) + }, process.env.CI ? 20_000 : 4_000) + + const listener = () => { + if (this.stdout.includes(expected)) { + if (timeout) + clearTimeout(timeout) + + resolve() + } + } + + this.stdoutListeners.push(listener) + }) + }, + waitForStderr(expected: string) { + return new Promise((resolve, reject) => { + if (this.stderr.includes(expected)) + return resolve() + + const timeout = setTimeout(() => { + reject(new Error(`Timeout when waiting for error "${expected}".\nReceived:\n${this.stderr}`)) + }, process.env.CI ? 20_000 : 4_000) + + const listener = () => { + if (this.stderr.includes(expected)) { + if (timeout) + clearTimeout(timeout) + + resolve() + } + } + + this.stderrListeners.push(listener) + }) + }, + resetOutput() { + this.stdout = '' + this.stderr = '' + }, + } + + subprocess.stdout!.on('data', (data) => { + vitest.stdout += stripAnsi(data.toString()) + vitest.stdoutListeners.forEach(fn => fn()) + }) + + subprocess.stderr!.on('data', (data) => { + vitest.stderr += stripAnsi(data.toString()) + vitest.stderrListeners.forEach(fn => fn()) + }) + + subprocess.on('exit', () => setDone()) + + // Manually stop the processes so that each test don't have to do this themselves + afterEach(async () => { + if (subprocess.exitCode === null) + subprocess.kill() + + await vitest.isDone + }) + + if (args.includes('--watch')) { // Wait for initial test run to complete + await vitest.waitForStdout('Waiting for file changes') + vitest.resetOutput() + } + else { + await vitest.isDone + } + + return vitest +} diff --git a/test/test-utils/package.json b/test/test-utils/package.json new file mode 100644 index 000000000000..a9b39f620758 --- /dev/null +++ b/test/test-utils/package.json @@ -0,0 +1,13 @@ +{ + "name": "@vitest/test-utils", + "private": true, + "scripts": { + "test": "echo \"No tests\"" + }, + "devDependencies": { + "execa": "^7.1.1", + "strip-ansi": "^7.0.1", + "vite": "latest", + "vitest": "workspace:*" + } +} diff --git a/test/typescript/package.json b/test/typescript/package.json index 7873d6655cd9..e1557eb2329b 100644 --- a/test/typescript/package.json +++ b/test/typescript/package.json @@ -8,7 +8,6 @@ "vitest": "workspace:*" }, "devDependencies": { - "execa": "^6.1.0", "typescript": "^4.8.4", "vue-tsc": "^0.40.13" } diff --git a/test/typescript/test/runner.test.ts b/test/typescript/test/runner.test.ts index a23d836c4fab..e5a49460c45a 100644 --- a/test/typescript/test/runner.test.ts +++ b/test/typescript/test/runner.test.ts @@ -1,30 +1,22 @@ import { resolve } from 'pathe' import fg from 'fast-glob' -import { execa } from 'execa' import { describe, expect, it } from 'vitest' +import { runVitest, runVitestCli } from '../../test-utils' + describe('should fail', async () => { const root = resolve(__dirname, '../failing') const files = await fg('*.test-d.*', { cwd: root }) it('typecheck files', async () => { - const { stderr } = await execa('npx', [ - 'vitest', - 'typecheck', - '--run', - '--dir', - resolve(__dirname, '..', './failing'), - '--config', - resolve(__dirname, './vitest.config.ts'), - ], { - cwd: root, - reject: false, - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', + const { stderr } = await runVitest({ + root, + dir: './failing', + typecheck: { + allowJs: true, + include: ['**/*.test-d.*'], }, - }) + }, [], 'typecheck') expect(stderr).toBeTruthy() const lines = String(stderr).split(/\n/g) @@ -50,23 +42,15 @@ describe('should fail', async () => { }, 30_000) it('typecheks with custom tsconfig', async () => { - const { stderr } = await execa('npx', [ - 'vitest', + const { stderr } = await runVitestCli( + { cwd: root, env: { ...process.env, CI: 'true' } }, 'typecheck', '--run', '--dir', resolve(__dirname, '..', './failing'), '--config', resolve(__dirname, './vitest.custom.config.ts'), - ], { - cwd: root, - reject: false, - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - }) + ) expect(stderr).toBeTruthy() const lines = String(stderr).split(/\n/g) diff --git a/test/typescript/test/vitest.config.ts b/test/typescript/test/vitest.config.ts deleted file mode 100644 index 9c971afea0b4..000000000000 --- a/test/typescript/test/vitest.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - typecheck: { - allowJs: true, - include: ['**/*.test-d.*'], - }, - }, -}) diff --git a/test/ui/test/html-report.spec.ts b/test/ui/test/html-report.spec.ts index 05baab68b530..7b2d32de86bd 100644 --- a/test/ui/test/html-report.spec.ts +++ b/test/ui/test/html-report.spec.ts @@ -1,22 +1,16 @@ import { resolve } from 'node:path' import { beforeAll, describe, expect, it } from 'vitest' -import { execaCommandSync } from 'execa' import { browserErrors, isWindows, page, ports, startServerCommand, untilUpdated } from '../setup' +import { runVitest } from '../../test-utils' + const root = resolve(__dirname, '../fixtures') const port = ports.report // TODO: fix flakyness on windows describe.skipIf(isWindows)('html report', () => { beforeAll(async () => { - execaCommandSync('npx vitest run --reporter=html --outputFile=html/index.html', { - cwd: root, - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - }) + await runVitest({ root, reporters: 'html', outputFile: 'html/index.html' }) const exit = await startServerCommand( root, diff --git a/test/watch/package.json b/test/watch/package.json index 4c676c03a9e3..680318133e7f 100644 --- a/test/watch/package.json +++ b/test/watch/package.json @@ -6,8 +6,6 @@ }, "devDependencies": { "@vitest/browser": "workspace:*", - "execa": "^7.0.0", - "strip-ansi": "^7.0.1", "vite": "latest", "vitest": "workspace:*", "webdriverio": "latest" diff --git a/test/watch/test/file-watching.test.ts b/test/watch/test/file-watching.test.ts index d40a97e8aafc..ce311e2d2b86 100644 --- a/test/watch/test/file-watching.test.ts +++ b/test/watch/test/file-watching.test.ts @@ -1,7 +1,7 @@ import { readFileSync, rmSync, writeFileSync } from 'node:fs' import { afterEach, describe, test } from 'vitest' -import { startWatchMode } from './utils' +import { runVitestCli } from '../../test-utils' const sourceFile = 'fixtures/math.ts' const sourceFileContent = readFileSync(sourceFile, 'utf-8') @@ -12,6 +12,7 @@ const testFileContent = readFileSync(testFile, 'utf-8') const configFile = 'fixtures/vitest.config.ts' const configFileContent = readFileSync(configFile, 'utf-8') +const cliArgs = ['--root', 'fixtures', '--watch'] const cleanups: (() => void)[] = [] function editFile(fileContent: string) { @@ -29,46 +30,46 @@ afterEach(() => { }) test('editing source file triggers re-run', async () => { - const vitest = await startWatchMode() + const vitest = await runVitestCli(...cliArgs) writeFileSync(sourceFile, editFile(sourceFileContent), 'utf8') - await vitest.waitForOutput('New code running') - await vitest.waitForOutput('RERUN ../math.ts') - await vitest.waitForOutput('1 passed') + await vitest.waitForStdout('New code running') + await vitest.waitForStdout('RERUN ../math.ts') + await vitest.waitForStdout('1 passed') }) test('editing test file triggers re-run', async () => { - const vitest = await startWatchMode() + const vitest = await runVitestCli(...cliArgs) writeFileSync(testFile, editFile(testFileContent), 'utf8') - await vitest.waitForOutput('New code running') - await vitest.waitForOutput('RERUN ../math.test.ts') - await vitest.waitForOutput('1 passed') + await vitest.waitForStdout('New code running') + await vitest.waitForStdout('RERUN ../math.test.ts') + await vitest.waitForStdout('1 passed') }) test('editing config file triggers re-run', async () => { - const vitest = await startWatchMode() + const vitest = await runVitestCli(...cliArgs) writeFileSync(configFile, editFile(configFileContent), 'utf8') - await vitest.waitForOutput('New code running') - await vitest.waitForOutput('Restarting due to config changes') - await vitest.waitForOutput('2 passed') + await vitest.waitForStdout('New code running') + await vitest.waitForStdout('Restarting due to config changes') + await vitest.waitForStdout('2 passed') }) test('editing config file reloads new changes', async () => { - const vitest = await startWatchMode() + const vitest = await runVitestCli(...cliArgs) writeFileSync(configFile, configFileContent.replace('reporters: \'verbose\'', 'reporters: \'tap\''), 'utf8') - await vitest.waitForOutput('TAP version') - await vitest.waitForOutput('ok 2') + await vitest.waitForStdout('TAP version') + await vitest.waitForStdout('ok 2') }) test('adding a new test file triggers re-run', async () => { - const vitest = await startWatchMode() + const vitest = await runVitestCli(...cliArgs) const testFile = 'fixtures/new-dynamic.test.ts' const testFileContent = ` @@ -82,20 +83,20 @@ test("dynamic test case", () => { cleanups.push(() => rmSync(testFile)) writeFileSync(testFile, testFileContent, 'utf-8') - await vitest.waitForOutput('Running added dynamic test') - await vitest.waitForOutput('RERUN ../new-dynamic.test.ts') - await vitest.waitForOutput('1 passed') + await vitest.waitForStdout('Running added dynamic test') + await vitest.waitForStdout('RERUN ../new-dynamic.test.ts') + await vitest.waitForStdout('1 passed') }) describe('browser', () => { test.runIf((process.platform !== 'win32'))('editing source file triggers re-run', async () => { - const vitest = await startWatchMode('--browser.enabled', '--browser.headless', '--browser.name=chrome') + const vitest = await runVitestCli(...cliArgs, '--browser.enabled', '--browser.headless', '--browser.name=chrome') writeFileSync(sourceFile, editFile(sourceFileContent), 'utf8') - await vitest.waitForOutput('New code running') - await vitest.waitForOutput('RERUN ../math.ts') - await vitest.waitForOutput('1 passed') + await vitest.waitForStdout('New code running') + await vitest.waitForStdout('RERUN ../math.ts') + await vitest.waitForStdout('1 passed') vitest.write('q') }) diff --git a/test/watch/test/stdin.test.ts b/test/watch/test/stdin.test.ts index 958c442e86a0..af91599ec4f2 100644 --- a/test/watch/test/stdin.test.ts +++ b/test/watch/test/stdin.test.ts @@ -1,8 +1,9 @@ import { rmSync, writeFileSync } from 'node:fs' import { afterEach, expect, test } from 'vitest' -import { startWatchMode } from './utils' +import { runVitestCli } from '../../test-utils' +const cliArgs = ['--root', 'fixtures', '--watch'] const cleanups: (() => void)[] = [] afterEach(() => { @@ -10,7 +11,7 @@ afterEach(() => { }) test('quit watch mode', async () => { - const vitest = await startWatchMode() + const vitest = await runVitestCli(...cliArgs) vitest.write('q') @@ -18,42 +19,42 @@ test('quit watch mode', async () => { }) test('rerun current pattern tests', async () => { - const vitest = await startWatchMode('-t', 'sum') + const vitest = await runVitestCli(...cliArgs, '-t', 'sum') vitest.write('r') - await vitest.waitForOutput('Test name pattern: /sum/') - await vitest.waitForOutput('1 passed') + await vitest.waitForStdout('Test name pattern: /sum/') + await vitest.waitForStdout('1 passed') }) test('filter by filename', async () => { - const vitest = await startWatchMode() + const vitest = await runVitestCli(...cliArgs) vitest.write('p') - await vitest.waitForOutput('Input filename pattern') + await vitest.waitForStdout('Input filename pattern') vitest.write('math\n') - await vitest.waitForOutput('Filename pattern: math') - await vitest.waitForOutput('1 passed') + await vitest.waitForStdout('Filename pattern: math') + await vitest.waitForStdout('1 passed') }) test('filter by test name', async () => { - const vitest = await startWatchMode() + const vitest = await runVitestCli(...cliArgs) vitest.write('t') - await vitest.waitForOutput('Input test name pattern') + await vitest.waitForStdout('Input test name pattern') vitest.write('sum\n') - await vitest.waitForOutput('Test name pattern: /sum/') - await vitest.waitForOutput('1 passed') + await vitest.waitForStdout('Test name pattern: /sum/') + await vitest.waitForStdout('1 passed') }) test('cancel test run', async () => { - const vitest = await startWatchMode() + const vitest = await runVitestCli(...cliArgs) const testPath = 'fixtures/cancel.test.ts' const testCase = `// Dynamic test case @@ -78,13 +79,13 @@ test('2 - test that is cancelled', async () => { writeFileSync(testPath, testCase, 'utf8') // Test case is running, cancel it - await vitest.waitForOutput('[cancel-test]: test') + await vitest.waitForStdout('[cancel-test]: test') vitest.write('c') // Test hooks should still be called - await vitest.waitForOutput('CANCELLED') - await vitest.waitForOutput('[cancel-test]: afterAll') - await vitest.waitForOutput('[cancel-test]: afterEach') + await vitest.waitForStdout('CANCELLED') + await vitest.waitForStdout('[cancel-test]: afterAll') + await vitest.waitForStdout('[cancel-test]: afterEach') - expect(vitest.output).not.include('[cancel-test]: should not run') + expect(vitest.stdout).not.include('[cancel-test]: should not run') }) diff --git a/test/watch/test/stdout.test.ts b/test/watch/test/stdout.test.ts index 761d46f71a88..6110bf26a850 100644 --- a/test/watch/test/stdout.test.ts +++ b/test/watch/test/stdout.test.ts @@ -1,7 +1,7 @@ import { readFileSync, writeFileSync } from 'node:fs' import { afterEach, test } from 'vitest' -import { startWatchMode } from './utils' +import { runVitestCli } from '../../test-utils' const testFile = 'fixtures/math.test.ts' const testFileContent = readFileSync(testFile, 'utf-8') @@ -11,7 +11,7 @@ afterEach(() => { }) test('console.log is visible on test re-run', async () => { - const vitest = await startWatchMode() + const vitest = await runVitestCli('--root', 'fixtures', '--watch') const testCase = ` test('test with logging', () => { console.log('First') @@ -23,8 +23,8 @@ test('test with logging', () => { writeFileSync(testFile, `${testFileContent}${testCase}`, 'utf8') - await vitest.waitForOutput('stdout | math.test.ts > test with logging') - await vitest.waitForOutput('First') - await vitest.waitForOutput('Second') - await vitest.waitForOutput('Third') + await vitest.waitForStdout('stdout | math.test.ts > test with logging') + await vitest.waitForStdout('First') + await vitest.waitForStdout('Second') + await vitest.waitForStdout('Third') }) diff --git a/test/watch/test/utils.ts b/test/watch/test/utils.ts deleted file mode 100644 index 4f3b12954daf..000000000000 --- a/test/watch/test/utils.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { afterEach } from 'vitest' -import { type Options, execa } from 'execa' -import stripAnsi from 'strip-ansi' - -export async function startWatchMode(options?: Options | string, ...args: string[]) { - if (typeof options === 'string') - args.unshift(options) - const argsWithRoot = args.includes('--root') ? args : ['--root', 'fixtures', ...args] - const subprocess = execa('vitest', argsWithRoot, typeof options === 'string' ? undefined : options) - - let setDone: (value?: unknown) => void - const isDone = new Promise(resolve => (setDone = resolve)) - - const vitest = { - output: '', - listeners: [] as (() => void)[], - isDone, - write(text: string) { - this.resetOutput() - subprocess.stdin!.write(text) - }, - waitForOutput(expected: string) { - return new Promise((resolve, reject) => { - if (this.output.includes(expected)) - return resolve() - - const timeout = setTimeout(() => { - reject(new Error(`Timeout when waiting for output "${expected}".\nReceived:\n${this.output}`)) - }, process.env.CI ? 20_000 : 4_000) - - const listener = () => { - if (this.output.includes(expected)) { - if (timeout) - clearTimeout(timeout) - - resolve() - } - } - - this.listeners.push(listener) - }) - }, - resetOutput() { - this.output = '' - }, - } - - subprocess.stdout!.on('data', (data) => { - vitest.output += stripAnsi(data.toString()) - vitest.listeners.forEach(fn => fn()) - }) - - subprocess.on('exit', () => setDone()) - - // Manually stop the processes so that each test don't have to do this themselves - afterEach(async () => { - if (subprocess.exitCode === null) - subprocess.kill() - - await vitest.isDone - }) - - // Wait for initial test run to complete - await vitest.waitForOutput('Waiting for file changes') - vitest.resetOutput() - - return vitest -} diff --git a/test/watch/test/workspaces.test.ts b/test/watch/test/workspaces.test.ts index ca89245ea84e..285734037ff8 100644 --- a/test/watch/test/workspaces.test.ts +++ b/test/watch/test/workspaces.test.ts @@ -2,7 +2,8 @@ import { fileURLToPath } from 'node:url' import { readFileSync, rmSync, writeFileSync } from 'node:fs' import { afterAll, afterEach, expect, it } from 'vitest' import { dirname, resolve } from 'pathe' -import { startWatchMode } from './utils' + +import { runVitestCli } from '../../test-utils' const file = fileURLToPath(import.meta.url) const dir = dirname(file) @@ -26,7 +27,7 @@ test("dynamic test case", () => { ` function startVitest() { - return startWatchMode( + return runVitestCli( { cwd: root, env: { TEST_WATCH: 'true' } }, '--root', root, @@ -51,9 +52,9 @@ it('editing a test file in a suite with workspaces reruns test', async () => { writeFileSync(specSpace2File, `${specSpace2Content}\n`, 'utf8') - await vitest.waitForOutput('RERUN space_2/test/node.spec.ts x1') - await vitest.waitForOutput('|space_2| test/node.spec.ts') - await vitest.waitForOutput('Test Files 1 passed') + await vitest.waitForStdout('RERUN space_2/test/node.spec.ts x1') + await vitest.waitForStdout('|space_2| test/node.spec.ts') + await vitest.waitForStdout('Test Files 1 passed') }) it('editing a file that is imported in different workspaces reruns both files', async () => { @@ -61,10 +62,10 @@ it('editing a file that is imported in different workspaces reruns both files', writeFileSync(srcMathFile, `${srcMathContent}\n`, 'utf8') - await vitest.waitForOutput('RERUN src/math.ts') - await vitest.waitForOutput('|space_3| math.space-3-test.ts') - await vitest.waitForOutput('|space_1| test/math.spec.ts') - await vitest.waitForOutput('Test Files 2 passed') + await vitest.waitForStdout('RERUN src/math.ts') + await vitest.waitForStdout('|space_3| math.space-3-test.ts') + await vitest.waitForStdout('|space_1| test/math.spec.ts') + await vitest.waitForStdout('Test Files 2 passed') }) it('filters by test name inside a workspace', async () => { @@ -72,12 +73,12 @@ it('filters by test name inside a workspace', async () => { vitest.write('t') - await vitest.waitForOutput('Input test name pattern') + await vitest.waitForStdout('Input test name pattern') vitest.write('2 x 2 = 4\n') - await vitest.waitForOutput('Test name pattern: /2 x 2 = 4/') - await vitest.waitForOutput('Test Files 1 passed') + await vitest.waitForStdout('Test name pattern: /2 x 2 = 4/') + await vitest.waitForStdout('Test Files 1 passed') }) it('adding a new test file matching core project config triggers re-run', async () => { @@ -88,18 +89,18 @@ it('adding a new test file matching core project config triggers re-run', async cleanups.push(() => rmSync(testFile)) writeFileSync(testFile, dynamicTestContent, 'utf-8') - await vitest.waitForOutput('Running added dynamic test') - await vitest.waitForOutput('RERUN space_2/test/new-dynamic.test.ts') - await vitest.waitForOutput('|space_2| test/new-dynamic.test.ts') + await vitest.waitForStdout('Running added dynamic test') + await vitest.waitForStdout('RERUN space_2/test/new-dynamic.test.ts') + await vitest.waitForStdout('|space_2| test/new-dynamic.test.ts') // Wait for tests to end - await vitest.waitForOutput('Waiting for file changes') + await vitest.waitForStdout('Waiting for file changes') // Test case should not be run by other projects - expect(vitest.output).not.include('|space_1|') - expect(vitest.output).not.include('|space_3|') - expect(vitest.output).not.include('|node|') - expect(vitest.output).not.include('|happy-dom|') + expect(vitest.stdout).not.include('|space_1|') + expect(vitest.stdout).not.include('|space_3|') + expect(vitest.stdout).not.include('|node|') + expect(vitest.stdout).not.include('|happy-dom|') }) it('adding a new test file matching project specific config triggers re-run', async () => { @@ -109,16 +110,16 @@ it('adding a new test file matching project specific config triggers re-run', as cleanups.push(() => rmSync(testFile)) writeFileSync(testFile, dynamicTestContent, 'utf-8') - await vitest.waitForOutput('Running added dynamic test') - await vitest.waitForOutput('RERUN space_3/new-dynamic.space-3-test.ts') - await vitest.waitForOutput('|space_3| new-dynamic.space-3-test.ts') + await vitest.waitForStdout('Running added dynamic test') + await vitest.waitForStdout('RERUN space_3/new-dynamic.space-3-test.ts') + await vitest.waitForStdout('|space_3| new-dynamic.space-3-test.ts') // Wait for tests to end - await vitest.waitForOutput('Waiting for file changes') + await vitest.waitForStdout('Waiting for file changes') // Test case should not be run by other projects - expect(vitest.output).not.include('|space_1|') - expect(vitest.output).not.include('|space_2|') - expect(vitest.output).not.include('|node|') - expect(vitest.output).not.include('|happy-dom|') + expect(vitest.stdout).not.include('|space_1|') + expect(vitest.stdout).not.include('|space_2|') + expect(vitest.stdout).not.include('|node|') + expect(vitest.stdout).not.include('|happy-dom|') })