Skip to content

Commit

Permalink
fix(watch): run test files when added to filesystem (#3189)
Browse files Browse the repository at this point in the history
  • Loading branch information
AriPerkkio committed Apr 14, 2023
1 parent ba3d133 commit 7b2c81b
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 25 deletions.
28 changes: 10 additions & 18 deletions packages/vitest/src/node/core.ts
Expand Up @@ -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)

Expand Down Expand Up @@ -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])
}
Expand Down Expand Up @@ -739,28 +748,11 @@ export class Vitest {
return files
}

private async isTargetFile(id: string, source?: string): Promise<boolean> {
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)
}
Expand Down
22 changes: 20 additions & 2 deletions 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'
Expand Down Expand Up @@ -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 {
Expand All @@ -131,6 +132,23 @@ export class WorkspaceProject {
return fg(include, globOptions)
}

async isTargetFile(id: string, source?: string): Promise<boolean> {
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))
Expand Down
25 changes: 24 additions & 1 deletion 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'
Expand All @@ -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}
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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')
Expand Down
63 changes: 60 additions & 3 deletions test/watch/test/workspaces.test.ts
@@ -1,20 +1,30 @@
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'

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')

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' } },
Expand All @@ -27,6 +37,10 @@ function startVitest() {
)
}

afterEach(() => {
cleanups.splice(0).forEach(cleanup => cleanup())
})

afterAll(() => {
writeFileSync(srcMathFile, srcMathContent, 'utf8')
writeFileSync(specSpace2File, specSpace2Content, 'utf8')
Expand All @@ -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')
})
Expand All @@ -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|')
})
File renamed without changes.
2 changes: 1 addition & 1 deletion test/workspaces/space_3/vitest.config.ts
Expand Up @@ -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',
},
Expand Down

0 comments on commit 7b2c81b

Please sign in to comment.