[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

fix(watch): run test files when added to filesystem (#3189)

authored by

Ari Perkkiö and committed by
GitHub
(Apr 14, 2023, 5:19 PM +0200) 7b2c81bc ba3d1338

+126 -36
+24 -1
test/watch/test/file-watching.test.ts
··· 1 - import { readFileSync, writeFileSync } from 'node:fs' 1 + import { readFileSync, rmSync, writeFileSync } from 'node:fs' 2 2 import { afterEach, describe, test } from 'vitest' 3 3 4 4 import { startWatchMode } from './utils' ··· 12 12 const configFile = 'fixtures/vitest.config.ts' 13 13 const configFileContent = readFileSync(configFile, 'utf-8') 14 14 15 + const cleanups: (() => void)[] = [] 16 + 15 17 function editFile(fileContent: string) { 16 18 return `// Modified by file-watching.test.ts 17 19 ${fileContent} ··· 23 25 writeFileSync(sourceFile, sourceFileContent, 'utf8') 24 26 writeFileSync(testFile, testFileContent, 'utf8') 25 27 writeFileSync(configFile, configFileContent, 'utf8') 28 + cleanups.splice(0).forEach(cleanup => cleanup()) 26 29 }) 27 30 28 31 test('editing source file triggers re-run', async () => { ··· 62 65 63 66 await vitest.waitForOutput('TAP version') 64 67 await vitest.waitForOutput('ok 2') 68 + }) 69 + 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') 65 88 }) 66 89 67 90 describe('browser', () => {
+60 -3
test/watch/test/workspaces.test.ts
··· 1 1 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' 4 4 import { dirname, resolve } from 'pathe' 5 5 import { startWatchMode } from './utils' 6 6 ··· 8 8 const dir = dirname(file) 9 9 const root = resolve(dir, '..', '..', 'workspaces') 10 10 const config = resolve(root, 'vitest.config.ts') 11 + const cleanups: (() => void)[] = [] 11 12 12 13 const srcMathFile = resolve(root, 'src', 'math.ts') 13 14 const specSpace2File = resolve(root, 'space_2', 'test', 'node.spec.ts') 14 15 15 16 const srcMathContent = readFileSync(srcMathFile, 'utf-8') 16 17 const specSpace2Content = readFileSync(specSpace2File, 'utf-8') 18 + 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 + ` 17 27 18 28 function startVitest() { 19 29 return startWatchMode( ··· 26 36 '--no-coverage', 27 37 ) 28 38 } 39 + 40 + afterEach(() => { 41 + cleanups.splice(0).forEach(cleanup => cleanup()) 42 + }) 29 43 30 44 afterAll(() => { 31 45 writeFileSync(srcMathFile, srcMathContent, 'utf8') ··· 48 62 writeFileSync(srcMathFile, `${srcMathContent}\n`, 'utf8') 49 63 50 64 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') 52 66 await vitest.waitForOutput('|space_1| test/math.spec.ts') 53 67 await vitest.waitForOutput('Test Files 2 passed') 54 68 }) ··· 64 78 65 79 await vitest.waitForOutput('Test name pattern: /2 x 2 = 4/') 66 80 await vitest.waitForOutput('Test Files 1 passed') 81 + }) 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|') 67 124 })
+11
test/workspaces/space_3/math.space-3-test.ts
··· 1 + import { expect, test } from 'vitest' 2 + import { sum } from '../src/math' 3 + import { multiple } from './src/multiply' 4 + 5 + test('2 x 2 = 4', () => { 6 + expect(multiple(2, 2)).toBe(4) 7 + }) 8 + 9 + test('2 + 2 = 4', () => { 10 + expect(sum(2, 2)).toBe(4) 11 + })
-11
test/workspaces/space_3/math.space-test.ts
··· 1 - import { expect, test } from 'vitest' 2 - import { sum } from '../src/math' 3 - import { multiple } from './src/multiply' 4 - 5 - test('2 x 2 = 4', () => { 6 - expect(multiple(2, 2)).toBe(4) 7 - }) 8 - 9 - test('2 + 2 = 4', () => { 10 - expect(sum(2, 2)).toBe(4) 11 - })
+1 -1
test/workspaces/space_3/vitest.config.ts
··· 2 2 3 3 export default defineProject({ 4 4 test: { 5 - include: ['**/*.space-test.ts'], 5 + include: ['**/*.space-3-test.ts'], 6 6 name: 'space_3', 7 7 environment: 'node', 8 8 },
+10 -18
packages/vitest/src/node/core.ts
··· 73 73 this.pool = undefined 74 74 this.coverageProvider = undefined 75 75 this.runningPromise = undefined 76 + this.projectsTestFiles.clear() 76 77 77 78 const resolved = resolveConfig(this.mode, options, server.config) 78 79 ··· 589 590 const onAdd = async (id: string) => { 590 591 id = slash(id) 591 592 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)) 593 602 this.changedTests.add(id) 594 603 this.scheduleRerun([id]) 595 604 } ··· 739 748 return files 740 749 } 741 750 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 - 755 751 // The server needs to be running for communication 756 752 shouldKeepServer() { 757 753 return !!this.config?.watch 758 - } 759 - 760 - isInSourceTestFile(code: string) { 761 - return code.includes('import.meta.vitest') 762 754 } 763 755 764 756 onServerRestart(fn: OnServerRestartHandler) {
+20 -2
packages/vitest/src/node/workspace.ts
··· 1 1 import { promises as fs } from 'node:fs' 2 2 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' 4 5 import { createServer } from 'vite' 5 6 import type { ViteDevServer, InlineConfig as ViteInlineConfig } from 'vite' 6 7 import { ViteNodeRunner } from 'vite-node/client' ··· 108 109 await Promise.all(files.map(async (file) => { 109 110 try { 110 111 const code = await fs.readFile(file, 'utf-8') 111 - if (this.ctx.isInSourceTestFile(code)) 112 + if (this.isInSourceTestFile(code)) 112 113 testFiles.push(file) 113 114 } 114 115 catch { ··· 129 130 } 130 131 131 132 return fg(include, globOptions) 133 + } 134 + 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') 132 150 } 133 151 134 152 filterFiles(testFiles: string[], filters: string[] = []) {