diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 9383ee21eea3..d9ce085042c6 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -73,6 +73,7 @@ export class Vitest { this.pool = undefined this.coverageProvider = undefined this.runningPromise = undefined + this.projectsTestFiles.clear() const resolved = resolveConfig(this.mode, options, server.config) @@ -591,7 +592,15 @@ export class Vitest { const onAdd = async (id: string) => { id = slash(id) updateLastChanged(id) - if (await this.isTargetFile(id)) { + + const matchingProjects: WorkspaceProject[] = [] + await Promise.all(this.projects.map(async (project) => { + if (await this.isTargetFile(id, project)) + matchingProjects.push(project) + })) + + if (matchingProjects.length > 0) { + this.projectsTestFiles.set(id, new Set(matchingProjects)) this.changedTests.add(id) this.scheduleRerun([id]) } @@ -738,13 +747,13 @@ export class Vitest { return files } - private async isTargetFile(id: string, source?: string): Promise { - const relativeId = relative(this.config.dir || this.config.root, id) - if (mm.isMatch(relativeId, this.config.exclude)) + private async isTargetFile(id: string, project: WorkspaceProject, source?: string): Promise { + const relativeId = relative(project.config.dir || project.config.root, id) + if (mm.isMatch(relativeId, project.config.exclude)) return false - if (mm.isMatch(relativeId, this.config.include)) + if (mm.isMatch(relativeId, project.config.include)) return true - if (this.config.includeSource?.length && mm.isMatch(relativeId, this.config.includeSource)) { + if (project.config.includeSource?.length && mm.isMatch(relativeId, project.config.includeSource)) { source = source || await fs.readFile(id, 'utf-8') return this.isInSourceTestFile(source) } diff --git a/test/watch/test/file-watching.test.ts b/test/watch/test/file-watching.test.ts index 1d01e0ac2330..d40a97e8aafc 100644 --- a/test/watch/test/file-watching.test.ts +++ b/test/watch/test/file-watching.test.ts @@ -1,4 +1,4 @@ -import { readFileSync, writeFileSync } from 'node:fs' +import { readFileSync, rmSync, writeFileSync } from 'node:fs' import { afterEach, describe, test } from 'vitest' import { startWatchMode } from './utils' @@ -12,6 +12,8 @@ const testFileContent = readFileSync(testFile, 'utf-8') const configFile = 'fixtures/vitest.config.ts' const configFileContent = readFileSync(configFile, 'utf-8') +const cleanups: (() => void)[] = [] + function editFile(fileContent: string) { return `// Modified by file-watching.test.ts ${fileContent} @@ -23,6 +25,7 @@ afterEach(() => { writeFileSync(sourceFile, sourceFileContent, 'utf8') writeFileSync(testFile, testFileContent, 'utf8') writeFileSync(configFile, configFileContent, 'utf8') + cleanups.splice(0).forEach(cleanup => cleanup()) }) test('editing source file triggers re-run', async () => { @@ -64,6 +67,26 @@ test('editing config file reloads new changes', async () => { await vitest.waitForOutput('ok 2') }) +test('adding a new test file triggers re-run', async () => { + const vitest = await startWatchMode() + + const testFile = 'fixtures/new-dynamic.test.ts' + const testFileContent = ` +import { expect, test } from "vitest"; + +test("dynamic test case", () => { + console.log("Running added dynamic test") + expect(true).toBeTruthy() +}) +` + 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') +}) + 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') diff --git a/test/watch/test/workspaces.test.ts b/test/watch/test/workspaces.test.ts index 5f6698d998ee..ca89245ea84e 100644 --- a/test/watch/test/workspaces.test.ts +++ b/test/watch/test/workspaces.test.ts @@ -1,6 +1,6 @@ import { fileURLToPath } from 'node:url' -import { readFileSync, writeFileSync } from 'node:fs' -import { afterAll, it } from 'vitest' +import { readFileSync, rmSync, writeFileSync } from 'node:fs' +import { afterAll, afterEach, expect, it } from 'vitest' import { dirname, resolve } from 'pathe' import { startWatchMode } from './utils' @@ -8,6 +8,7 @@ const file = fileURLToPath(import.meta.url) const dir = dirname(file) const root = resolve(dir, '..', '..', 'workspaces') const config = resolve(root, 'vitest.config.ts') +const cleanups: (() => void)[] = [] const srcMathFile = resolve(root, 'src', 'math.ts') const specSpace2File = resolve(root, 'space_2', 'test', 'node.spec.ts') @@ -15,6 +16,15 @@ const specSpace2File = resolve(root, 'space_2', 'test', 'node.spec.ts') const srcMathContent = readFileSync(srcMathFile, 'utf-8') const specSpace2Content = readFileSync(specSpace2File, 'utf-8') +const dynamicTestContent = `// Dynamic test added by test/watch/test/workspaces.test.ts +import { expect, test } from "vitest"; + +test("dynamic test case", () => { + console.log("Running added dynamic test") + expect(true).toBeTruthy() +}) +` + function startVitest() { return startWatchMode( { cwd: root, env: { TEST_WATCH: 'true' } }, @@ -27,6 +37,10 @@ function startVitest() { ) } +afterEach(() => { + cleanups.splice(0).forEach(cleanup => cleanup()) +}) + afterAll(() => { writeFileSync(srcMathFile, srcMathContent, 'utf8') writeFileSync(specSpace2File, specSpace2Content, 'utf8') @@ -65,3 +79,46 @@ it('filters by test name inside a workspace', async () => { await vitest.waitForOutput('Test name pattern: /2 x 2 = 4/') await vitest.waitForOutput('Test Files 1 passed') }) + +it('adding a new test file matching core project config triggers re-run', async () => { + const vitest = await startVitest() + + const testFile = resolve(root, 'space_2', 'test', 'new-dynamic.test.ts') + + 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') + + // Wait for tests to end + await vitest.waitForOutput('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|') +}) + +it('adding a new test file matching project specific config triggers re-run', async () => { + const vitest = await startVitest() + + const testFile = resolve(root, 'space_3', 'new-dynamic.space-3-test.ts') + 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') + + // Wait for tests to end + await vitest.waitForOutput('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|') +})