From 7b2c81bcbf7e1076faa32cf721ba2df69e22251c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Fri, 14 Apr 2023 18:19:31 +0300 Subject: [PATCH] fix(watch): run test files when added to filesystem (#3189) --- packages/vitest/src/node/core.ts | 28 +++------ packages/vitest/src/node/workspace.ts | 22 ++++++- test/watch/test/file-watching.test.ts | 25 +++++++- test/watch/test/workspaces.test.ts | 63 ++++++++++++++++++- ...ath.space-test.ts => math.space-3-test.ts} | 0 test/workspaces/space_3/vitest.config.ts | 2 +- 6 files changed, 115 insertions(+), 25 deletions(-) rename test/workspaces/space_3/{math.space-test.ts => math.space-3-test.ts} (100%) diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 4c12f14e947f..f838fd585d08 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) @@ -589,7 +590,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 project.isTargetFile(id)) + matchingProjects.push(project) + })) + + if (matchingProjects.length > 0) { + this.projectsTestFiles.set(id, new Set(matchingProjects)) this.changedTests.add(id) this.scheduleRerun([id]) } @@ -739,28 +748,11 @@ 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)) - return false - if (mm.isMatch(relativeId, this.config.include)) - return true - if (this.config.includeSource?.length && mm.isMatch(relativeId, this.config.includeSource)) { - source = source || await fs.readFile(id, 'utf-8') - return this.isInSourceTestFile(source) - } - return false - } - // The server needs to be running for communication shouldKeepServer() { return !!this.config?.watch } - isInSourceTestFile(code: string) { - return code.includes('import.meta.vitest') - } - onServerRestart(fn: OnServerRestartHandler) { this._onRestartListeners.push(fn) } diff --git a/packages/vitest/src/node/workspace.ts b/packages/vitest/src/node/workspace.ts index 717f47ef19bf..c875fe298e59 100644 --- a/packages/vitest/src/node/workspace.ts +++ b/packages/vitest/src/node/workspace.ts @@ -1,6 +1,7 @@ import { promises as fs } from 'node:fs' import fg from 'fast-glob' -import { dirname, resolve, toNamespacedPath } from 'pathe' +import mm from 'micromatch' +import { dirname, relative, resolve, toNamespacedPath } from 'pathe' import { createServer } from 'vite' import type { ViteDevServer, InlineConfig as ViteInlineConfig } from 'vite' import { ViteNodeRunner } from 'vite-node/client' @@ -108,7 +109,7 @@ export class WorkspaceProject { await Promise.all(files.map(async (file) => { try { const code = await fs.readFile(file, 'utf-8') - if (this.ctx.isInSourceTestFile(code)) + if (this.isInSourceTestFile(code)) testFiles.push(file) } catch { @@ -131,6 +132,23 @@ export class WorkspaceProject { return fg(include, globOptions) } + async isTargetFile(id: string, source?: string): Promise { + const relativeId = relative(this.config.dir || this.config.root, id) + if (mm.isMatch(relativeId, this.config.exclude)) + return false + if (mm.isMatch(relativeId, this.config.include)) + return true + if (this.config.includeSource?.length && mm.isMatch(relativeId, this.config.includeSource)) { + source = source || await fs.readFile(id, 'utf-8') + return this.isInSourceTestFile(source) + } + return false + } + + isInSourceTestFile(code: string) { + return code.includes('import.meta.vitest') + } + filterFiles(testFiles: string[], filters: string[] = []) { if (filters.length && process.platform === 'win32') filters = filters.map(f => toNamespacedPath(f)) 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 a9bc61f9f749..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') @@ -48,7 +62,7 @@ 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-test.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') }) @@ -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|') +}) diff --git a/test/workspaces/space_3/math.space-test.ts b/test/workspaces/space_3/math.space-3-test.ts similarity index 100% rename from test/workspaces/space_3/math.space-test.ts rename to test/workspaces/space_3/math.space-3-test.ts diff --git a/test/workspaces/space_3/vitest.config.ts b/test/workspaces/space_3/vitest.config.ts index a36bb28d0a0b..1270e126a7c1 100644 --- a/test/workspaces/space_3/vitest.config.ts +++ b/test/workspaces/space_3/vitest.config.ts @@ -2,7 +2,7 @@ import { defineProject } from 'vitest/config' export default defineProject({ test: { - include: ['**/*.space-test.ts'], + include: ['**/*.space-3-test.ts'], name: 'space_3', environment: 'node', },