Skip to content

Commit 7b2c81b

Browse files
authoredApr 14, 2023
fix(watch): run test files when added to filesystem (#3189)
1 parent ba3d133 commit 7b2c81b

File tree

6 files changed

+115
-25
lines changed

6 files changed

+115
-25
lines changed
 

‎packages/vitest/src/node/core.ts

+10-18
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export class Vitest {
7373
this.pool = undefined
7474
this.coverageProvider = undefined
7575
this.runningPromise = undefined
76+
this.projectsTestFiles.clear()
7677

7778
const resolved = resolveConfig(this.mode, options, server.config)
7879

@@ -589,7 +590,15 @@ export class Vitest {
589590
const onAdd = async (id: string) => {
590591
id = slash(id)
591592
updateLastChanged(id)
592-
if (await this.isTargetFile(id)) {
593+
594+
const matchingProjects: WorkspaceProject[] = []
595+
await Promise.all(this.projects.map(async (project) => {
596+
if (await project.isTargetFile(id))
597+
matchingProjects.push(project)
598+
}))
599+
600+
if (matchingProjects.length > 0) {
601+
this.projectsTestFiles.set(id, new Set(matchingProjects))
593602
this.changedTests.add(id)
594603
this.scheduleRerun([id])
595604
}
@@ -739,28 +748,11 @@ export class Vitest {
739748
return files
740749
}
741750

742-
private async isTargetFile(id: string, source?: string): Promise<boolean> {
743-
const relativeId = relative(this.config.dir || this.config.root, id)
744-
if (mm.isMatch(relativeId, this.config.exclude))
745-
return false
746-
if (mm.isMatch(relativeId, this.config.include))
747-
return true
748-
if (this.config.includeSource?.length && mm.isMatch(relativeId, this.config.includeSource)) {
749-
source = source || await fs.readFile(id, 'utf-8')
750-
return this.isInSourceTestFile(source)
751-
}
752-
return false
753-
}
754-
755751
// The server needs to be running for communication
756752
shouldKeepServer() {
757753
return !!this.config?.watch
758754
}
759755

760-
isInSourceTestFile(code: string) {
761-
return code.includes('import.meta.vitest')
762-
}
763-
764756
onServerRestart(fn: OnServerRestartHandler) {
765757
this._onRestartListeners.push(fn)
766758
}

‎packages/vitest/src/node/workspace.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { promises as fs } from 'node:fs'
22
import fg from 'fast-glob'
3-
import { dirname, resolve, toNamespacedPath } from 'pathe'
3+
import mm from 'micromatch'
4+
import { dirname, relative, resolve, toNamespacedPath } from 'pathe'
45
import { createServer } from 'vite'
56
import type { ViteDevServer, InlineConfig as ViteInlineConfig } from 'vite'
67
import { ViteNodeRunner } from 'vite-node/client'
@@ -108,7 +109,7 @@ export class WorkspaceProject {
108109
await Promise.all(files.map(async (file) => {
109110
try {
110111
const code = await fs.readFile(file, 'utf-8')
111-
if (this.ctx.isInSourceTestFile(code))
112+
if (this.isInSourceTestFile(code))
112113
testFiles.push(file)
113114
}
114115
catch {
@@ -131,6 +132,23 @@ export class WorkspaceProject {
131132
return fg(include, globOptions)
132133
}
133134

135+
async isTargetFile(id: string, source?: string): Promise<boolean> {
136+
const relativeId = relative(this.config.dir || this.config.root, id)
137+
if (mm.isMatch(relativeId, this.config.exclude))
138+
return false
139+
if (mm.isMatch(relativeId, this.config.include))
140+
return true
141+
if (this.config.includeSource?.length && mm.isMatch(relativeId, this.config.includeSource)) {
142+
source = source || await fs.readFile(id, 'utf-8')
143+
return this.isInSourceTestFile(source)
144+
}
145+
return false
146+
}
147+
148+
isInSourceTestFile(code: string) {
149+
return code.includes('import.meta.vitest')
150+
}
151+
134152
filterFiles(testFiles: string[], filters: string[] = []) {
135153
if (filters.length && process.platform === 'win32')
136154
filters = filters.map(f => toNamespacedPath(f))

‎test/watch/test/file-watching.test.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { readFileSync, writeFileSync } from 'node:fs'
1+
import { readFileSync, rmSync, writeFileSync } from 'node:fs'
22
import { afterEach, describe, test } from 'vitest'
33

44
import { startWatchMode } from './utils'
@@ -12,6 +12,8 @@ const testFileContent = readFileSync(testFile, 'utf-8')
1212
const configFile = 'fixtures/vitest.config.ts'
1313
const configFileContent = readFileSync(configFile, 'utf-8')
1414

15+
const cleanups: (() => void)[] = []
16+
1517
function editFile(fileContent: string) {
1618
return `// Modified by file-watching.test.ts
1719
${fileContent}
@@ -23,6 +25,7 @@ afterEach(() => {
2325
writeFileSync(sourceFile, sourceFileContent, 'utf8')
2426
writeFileSync(testFile, testFileContent, 'utf8')
2527
writeFileSync(configFile, configFileContent, 'utf8')
28+
cleanups.splice(0).forEach(cleanup => cleanup())
2629
})
2730

2831
test('editing source file triggers re-run', async () => {
@@ -64,6 +67,26 @@ test('editing config file reloads new changes', async () => {
6467
await vitest.waitForOutput('ok 2')
6568
})
6669

70+
test('adding a new test file triggers re-run', async () => {
71+
const vitest = await startWatchMode()
72+
73+
const testFile = 'fixtures/new-dynamic.test.ts'
74+
const testFileContent = `
75+
import { expect, test } from "vitest";
76+
77+
test("dynamic test case", () => {
78+
console.log("Running added dynamic test")
79+
expect(true).toBeTruthy()
80+
})
81+
`
82+
cleanups.push(() => rmSync(testFile))
83+
writeFileSync(testFile, testFileContent, 'utf-8')
84+
85+
await vitest.waitForOutput('Running added dynamic test')
86+
await vitest.waitForOutput('RERUN ../new-dynamic.test.ts')
87+
await vitest.waitForOutput('1 passed')
88+
})
89+
6790
describe('browser', () => {
6891
test.runIf((process.platform !== 'win32'))('editing source file triggers re-run', async () => {
6992
const vitest = await startWatchMode('--browser.enabled', '--browser.headless', '--browser.name=chrome')

‎test/watch/test/workspaces.test.ts

+60-3
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
import { fileURLToPath } from 'node:url'
2-
import { readFileSync, writeFileSync } from 'node:fs'
3-
import { afterAll, it } from 'vitest'
2+
import { readFileSync, rmSync, writeFileSync } from 'node:fs'
3+
import { afterAll, afterEach, expect, it } from 'vitest'
44
import { dirname, resolve } from 'pathe'
55
import { startWatchMode } from './utils'
66

77
const file = fileURLToPath(import.meta.url)
88
const dir = dirname(file)
99
const root = resolve(dir, '..', '..', 'workspaces')
1010
const config = resolve(root, 'vitest.config.ts')
11+
const cleanups: (() => void)[] = []
1112

1213
const srcMathFile = resolve(root, 'src', 'math.ts')
1314
const specSpace2File = resolve(root, 'space_2', 'test', 'node.spec.ts')
1415

1516
const srcMathContent = readFileSync(srcMathFile, 'utf-8')
1617
const specSpace2Content = readFileSync(specSpace2File, 'utf-8')
1718

19+
const dynamicTestContent = `// Dynamic test added by test/watch/test/workspaces.test.ts
20+
import { expect, test } from "vitest";
21+
22+
test("dynamic test case", () => {
23+
console.log("Running added dynamic test")
24+
expect(true).toBeTruthy()
25+
})
26+
`
27+
1828
function startVitest() {
1929
return startWatchMode(
2030
{ cwd: root, env: { TEST_WATCH: 'true' } },
@@ -27,6 +37,10 @@ function startVitest() {
2737
)
2838
}
2939

40+
afterEach(() => {
41+
cleanups.splice(0).forEach(cleanup => cleanup())
42+
})
43+
3044
afterAll(() => {
3145
writeFileSync(srcMathFile, srcMathContent, 'utf8')
3246
writeFileSync(specSpace2File, specSpace2Content, 'utf8')
@@ -48,7 +62,7 @@ it('editing a file that is imported in different workspaces reruns both files',
4862
writeFileSync(srcMathFile, `${srcMathContent}\n`, 'utf8')
4963

5064
await vitest.waitForOutput('RERUN src/math.ts')
51-
await vitest.waitForOutput('|space_3| math.space-test.ts')
65+
await vitest.waitForOutput('|space_3| math.space-3-test.ts')
5266
await vitest.waitForOutput('|space_1| test/math.spec.ts')
5367
await vitest.waitForOutput('Test Files 2 passed')
5468
})
@@ -65,3 +79,46 @@ it('filters by test name inside a workspace', async () => {
6579
await vitest.waitForOutput('Test name pattern: /2 x 2 = 4/')
6680
await vitest.waitForOutput('Test Files 1 passed')
6781
})
82+
83+
it('adding a new test file matching core project config triggers re-run', async () => {
84+
const vitest = await startVitest()
85+
86+
const testFile = resolve(root, 'space_2', 'test', 'new-dynamic.test.ts')
87+
88+
cleanups.push(() => rmSync(testFile))
89+
writeFileSync(testFile, dynamicTestContent, 'utf-8')
90+
91+
await vitest.waitForOutput('Running added dynamic test')
92+
await vitest.waitForOutput('RERUN space_2/test/new-dynamic.test.ts')
93+
await vitest.waitForOutput('|space_2| test/new-dynamic.test.ts')
94+
95+
// Wait for tests to end
96+
await vitest.waitForOutput('Waiting for file changes')
97+
98+
// Test case should not be run by other projects
99+
expect(vitest.output).not.include('|space_1|')
100+
expect(vitest.output).not.include('|space_3|')
101+
expect(vitest.output).not.include('|node|')
102+
expect(vitest.output).not.include('|happy-dom|')
103+
})
104+
105+
it('adding a new test file matching project specific config triggers re-run', async () => {
106+
const vitest = await startVitest()
107+
108+
const testFile = resolve(root, 'space_3', 'new-dynamic.space-3-test.ts')
109+
cleanups.push(() => rmSync(testFile))
110+
writeFileSync(testFile, dynamicTestContent, 'utf-8')
111+
112+
await vitest.waitForOutput('Running added dynamic test')
113+
await vitest.waitForOutput('RERUN space_3/new-dynamic.space-3-test.ts')
114+
await vitest.waitForOutput('|space_3| new-dynamic.space-3-test.ts')
115+
116+
// Wait for tests to end
117+
await vitest.waitForOutput('Waiting for file changes')
118+
119+
// Test case should not be run by other projects
120+
expect(vitest.output).not.include('|space_1|')
121+
expect(vitest.output).not.include('|space_2|')
122+
expect(vitest.output).not.include('|node|')
123+
expect(vitest.output).not.include('|happy-dom|')
124+
})

‎test/workspaces/space_3/vitest.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { defineProject } from 'vitest/config'
22

33
export default defineProject({
44
test: {
5-
include: ['**/*.space-test.ts'],
5+
include: ['**/*.space-3-test.ts'],
66
name: 'space_3',
77
environment: 'node',
88
},

0 commit comments

Comments
 (0)
Please sign in to comment.