Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: watch mode #2925

Merged
merged 1 commit into from
Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions test/watch/fixtures/example.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { expect, test } from 'vitest'

import { getHelloWorld } from './example'

test('getHello', async () => {
expect(getHelloWorld()).toBe('Hello world')
})
3 changes: 3 additions & 0 deletions test/watch/fixtures/example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function getHelloWorld() {
return 'Hello world'
}
7 changes: 7 additions & 0 deletions test/watch/fixtures/math.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { expect, test } from 'vitest'

import { sum } from './math'

test('sum', () => {
expect(sum(1, 2)).toBe(3)
})
3 changes: 3 additions & 0 deletions test/watch/fixtures/math.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function sum(a: number, b: number) {
return a + b
}
15 changes: 15 additions & 0 deletions test/watch/fixtures/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { defineConfig } from 'vitest/config'

// Patch stdin on the process so that we can fake it to seem like a real interactive terminal and pass the TTY checks
process.stdin.isTTY = true
process.stdin.setRawMode = () => process.stdin

export default defineConfig({
test: {
watch: true,
outputTruncateLength: 999,

// This configuration is edited by tests
reporters: 'verbose',
},
})
13 changes: 13 additions & 0 deletions test/watch/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "@vitest/test-watch",
"private": true,
"scripts": {
"test": "vitest"
},
"devDependencies": {
"execa": "^7.0.0",
"strip-ansi": "^7.0.1",
"vite": "latest",
"vitest": "workspace:*"
}
}
65 changes: 65 additions & 0 deletions test/watch/test/file-watching.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { readFileSync, writeFileSync } from 'fs'
import { afterEach, expect, test } from 'vitest'

import { startWatchMode, waitFor } from './utils'

const EDIT_COMMENT = '// Modified by file-watching.test.ts\n\n'

const sourceFile = 'fixtures/math.ts'
const sourceFileContent = readFileSync(sourceFile, 'utf-8')

const testFile = 'fixtures/math.test.ts'
const testFileContent = readFileSync(testFile, 'utf-8')

const configFile = 'fixtures/vitest.config.ts'
const configFileContent = readFileSync(configFile, 'utf-8')

afterEach(() => {
writeFileSync(sourceFile, sourceFileContent, 'utf8')
writeFileSync(testFile, testFileContent, 'utf8')
writeFileSync(configFile, configFileContent, 'utf8')
})

test('editing source file triggers re-run', async () => {
const vitest = await startWatchMode()

writeFileSync(sourceFile, `${EDIT_COMMENT}${sourceFileContent}`, 'utf8')

await waitFor(() => {
expect(vitest.getOutput()).toContain('RERUN math.ts')
expect(vitest.getOutput()).toContain('1 passed')
})
})

test('editing test file triggers re-run', async () => {
const vitest = await startWatchMode()

writeFileSync(testFile, `${EDIT_COMMENT}${testFileContent}`, 'utf8')

await waitFor(() => {
expect(vitest.getOutput()).toMatch('RERUN math.test.ts')
expect(vitest.getOutput()).toMatch('1 passed')
})
})

test('editing config file triggers re-run', async () => {
const vitest = await startWatchMode()

writeFileSync(configFile, `${EDIT_COMMENT}${configFileContent}`, 'utf8')

await waitFor(() => {
expect(vitest.getOutput()).toMatch('Restarting due to config changes')
expect(vitest.getOutput()).toMatch('2 passed')
})
})

test('editing config file reloads new changes', async () => {
const vitest = await startWatchMode()

writeFileSync(configFile, configFileContent.replace('reporters: \'verbose\'', 'reporters: \'tap\''), 'utf8')

await waitFor(() => {
expect(vitest.getOutput()).toMatch('TAP version')
expect(vitest.getOutput()).toMatch('ok 2')
})
})
45 changes: 45 additions & 0 deletions test/watch/test/stdin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { expect, test } from 'vitest'

import { startWatchMode, waitFor } from './utils'

test('quit watch mode', async () => {
const vitest = await startWatchMode()

vitest.write('q')

await vitest.isDone
})

test('filter by filename', async () => {
const vitest = await startWatchMode()

vitest.write('p')

await waitFor(() => {
expect(vitest.getOutput()).toMatch('Input filename pattern')
})

vitest.write('math\n')

await waitFor(() => {
expect(vitest.getOutput()).toMatch('Filename pattern: math')
expect(vitest.getOutput()).toMatch('1 passed')
})
})

test('filter by test name', async () => {
const vitest = await startWatchMode()

vitest.write('t')

await waitFor(() => {
expect(vitest.getOutput()).toMatch('Input test name pattern')
})

vitest.write('sum\n')

await waitFor(() => {
expect(vitest.getOutput()).toMatch('Test name pattern: /sum/')
expect(vitest.getOutput()).toMatch('1 passed')
})
})
62 changes: 62 additions & 0 deletions test/watch/test/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { afterEach, expect } from 'vitest'
import { execa } from 'execa'
import stripAnsi from 'strip-ansi'

export async function startWatchMode() {
const subprocess = execa('vitest', ['--root', 'fixtures'])

let setDone: (value?: unknown) => void
const isDone = new Promise(resolve => (setDone = resolve))

const vitest = {
output: '',
isDone,
write(text: string) {
this.resetOutput()
subprocess.stdin!.write(text)
},
getOutput() {
return this.output
},
resetOutput() {
this.output = ''
},
}

subprocess.stdout!.on('data', (data) => {
vitest.output += stripAnsi(data.toString())
})

subprocess.on('exit', () => setDone())

// Manually stop the processes so that each test don't have to do this themselves
afterEach(async () => {
if (subprocess.exitCode === null)
subprocess.kill()

await vitest.isDone
})

// Wait for initial test run to complete
await waitFor(() => {
expect(vitest.getOutput()).toMatch('Waiting for file changes')
})
vitest.resetOutput()

return vitest
}

export async function waitFor(method: () => unknown, retries = 100): Promise<void> {
try {
method()
}
catch (error) {
if (retries === 0) {
console.error(error)
throw error
}

await new Promise(resolve => setTimeout(resolve, 250))
return waitFor(method, retries - 1)
}
}
14 changes: 14 additions & 0 deletions test/watch/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
reporters: 'verbose',
include: ['test/**/*.test.*'],

// For Windows CI mostly
testTimeout: 30_000,

// Test cases may have side effects, e.g. files under fixtures/ are modified on the fly to trigger file watchers
singleThread: true,
},
})