From 19cfa88e2b36d8c5e75fb95f49df052f2ca46d6a Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 28 Oct 2024 14:38:03 +0100 Subject: [PATCH 1/4] feat(vitest): add `onWatcherRerun` method to global setup context --- docs/config/index.md | 12 ++++++++++++ packages/vitest/src/node/core.ts | 18 +++++++++++++++--- packages/vitest/src/node/globalSetup.ts | 3 ++- packages/vitest/src/node/workspace.ts | 1 + packages/vitest/src/public/node.ts | 5 ++++- packages/vitest/src/types/general.ts | 2 +- 6 files changed, 35 insertions(+), 6 deletions(-) diff --git a/docs/config/index.md b/docs/config/index.md index 1fc760dc40bc..93dbdd978283 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -1089,6 +1089,18 @@ inject('wsPort') === 3000 ``` ::: +Since Vitest 2.2.0, you can define a custom callback function to be called when Vitest reruns tests. If the function is asynchronous, the runner will wait for it to complete before executing the tests. + +```ts +import type { GlobalSetupContext } from 'vitest/node' + +export default function setup({ onWatcherRerun }: GlobalSetupContext) { + onWatcherRerun(async () => { + await restartDb() + }) +} +``` + ### forceRerunTriggers - **Type**: `string[]` diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 763fec4c468a..b3c996e31ff4 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -3,7 +3,7 @@ import type { Writable } from 'node:stream' import type { ViteDevServer } from 'vite' import type { defineWorkspace } from 'vitest/config' import type { SerializedCoverageConfig } from '../runtime/config' -import type { ArgumentsType, OnServerRestartHandler, ProvidedContext, UserConsoleLog } from '../types/general' +import type { ArgumentsType, OnServerRestartHandler, OnWatcherRerunHandler, ProvidedContext, UserConsoleLog } from '../types/general' import type { ProcessPool, WorkspaceSpec } from './pool' import type { TestSpecification } from './spec' import type { ResolvedConfig, UserConfig, VitestRunMode } from './types/config' @@ -102,6 +102,7 @@ export class Vitest { private _onClose: (() => Awaited)[] = [] private _onSetServer: OnServerRestartHandler[] = [] private _onCancelListeners: ((reason: CancelReason) => Promise | void)[] = [] + private _onUserWatcherRerun: OnWatcherRerunHandler[] = [] async setServer(options: UserConfig, server: ViteDevServer, cliOptions: UserConfig) { this.unregisterWatcher?.() @@ -113,6 +114,7 @@ export class Vitest { this.coverageProvider = undefined this.runningPromise = undefined this._cachedSpecs.clear() + this._onUserWatcherRerun = [] const resolved = resolveConfig(this.mode, options, server.config, this.logger) @@ -691,7 +693,10 @@ export class Vitest { files = files.filter(file => filteredFiles.some(f => f[1] === file)) } - await this.report('onWatcherRerun', files, trigger) + await Promise.all([ + this.report('onWatcherRerun', files, trigger), + ...this._onUserWatcherRerun.map(fn => fn(files)), + ]) await this.runFiles(files.flatMap(file => this.getProjectsByTestFile(file)), allTestsRun) await this.report('onWatcherStart', this.state.getFiles(files)) @@ -813,7 +818,10 @@ export class Vitest { const triggerIds = new Set(triggerId.map(id => relative(this.config.root, id))) const triggerLabel = Array.from(triggerIds).join(', ') - await this.report('onWatcherRerun', files, triggerLabel) + await Promise.all([ + this.report('onWatcherRerun', files, triggerLabel), + ...this._onUserWatcherRerun.map(fn => fn(files)), + ]) await this.runFiles(files.flatMap(file => this.getProjectsByTestFile(file)), false) @@ -1150,4 +1158,8 @@ export class Vitest { onClose(fn: () => void) { this._onClose.push(fn) } + + onWatcherRerun(fn: OnWatcherRerunHandler): void { + this._onUserWatcherRerun.push(fn) + } } diff --git a/packages/vitest/src/node/globalSetup.ts b/packages/vitest/src/node/globalSetup.ts index caf1d4e0820f..10804003c434 100644 --- a/packages/vitest/src/node/globalSetup.ts +++ b/packages/vitest/src/node/globalSetup.ts @@ -1,5 +1,5 @@ import type { ViteNodeRunner } from 'vite-node/client' -import type { ProvidedContext } from '../types/general' +import type { OnWatcherRerunHandler, ProvidedContext } from '../types/general' import type { ResolvedConfig } from './types/config' import { toArray } from '@vitest/utils' @@ -9,6 +9,7 @@ export interface GlobalSetupContext { key: T, value: ProvidedContext[T] ) => void + onWatcherRerun: (cb: OnWatcherRerunHandler) => void } export interface GlobalSetupFile { diff --git a/packages/vitest/src/node/workspace.ts b/packages/vitest/src/node/workspace.ts index 38135d1ff6c9..8f815e4c1513 100644 --- a/packages/vitest/src/node/workspace.ts +++ b/packages/vitest/src/node/workspace.ts @@ -170,6 +170,7 @@ export class WorkspaceProject { const teardown = await globalSetupFile.setup?.({ provide: (key, value) => this.provide(key, value), config: this.config, + onWatcherRerun: cb => this.ctx.onWatcherRerun(cb), }) if (teardown == null || !!globalSetupFile.teardown) { continue diff --git a/packages/vitest/src/public/node.ts b/packages/vitest/src/public/node.ts index ce7b67697811..6de6d2832199 100644 --- a/packages/vitest/src/public/node.ts +++ b/packages/vitest/src/public/node.ts @@ -126,7 +126,10 @@ export type { TscErrorInfo as TypeCheckErrorInfo, } from '../typecheck/types' -export type { OnServerRestartHandler } from '../types/general' +export type { + OnServerRestartHandler, + OnWatcherRerunHandler, +} from '../types/general' export { createDebugger } from '../utils/debugger' diff --git a/packages/vitest/src/types/general.ts b/packages/vitest/src/types/general.ts index 99cba19ca50b..0666bfaad5e5 100644 --- a/packages/vitest/src/types/general.ts +++ b/packages/vitest/src/types/general.ts @@ -46,5 +46,5 @@ export interface ModuleGraphData { } export type OnServerRestartHandler = (reason?: string) => Promise | void - +export type OnWatcherRerunHandler = (testFiles: string[]) => Promise | void export interface ProvidedContext {} From fd36f678be8931f27a6995ffe95721c3c4a8887a Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 28 Oct 2024 15:53:42 +0100 Subject: [PATCH 2/4] test: add test for the global setup --- test/watch/fixtures/global-setup.ts | 15 ++++++++++++ test/watch/fixtures/vitest.config.ts | 4 ++++ test/watch/test/global-setup-rerun.test.ts | 27 ++++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 test/watch/fixtures/global-setup.ts create mode 100644 test/watch/test/global-setup-rerun.test.ts diff --git a/test/watch/fixtures/global-setup.ts b/test/watch/fixtures/global-setup.ts new file mode 100644 index 000000000000..087062f73f56 --- /dev/null +++ b/test/watch/fixtures/global-setup.ts @@ -0,0 +1,15 @@ +import { GlobalSetupContext } from 'vitest/node'; + +const calls: string[] = []; + +(globalThis as any).__CALLS = calls + +export default ({ onWatcherRerun }: GlobalSetupContext) => { + calls.push('start') + onWatcherRerun(() => { + calls.push('rerun') + }) + return () => { + calls.push('end') + } +} diff --git a/test/watch/fixtures/vitest.config.ts b/test/watch/fixtures/vitest.config.ts index 184e5719a9d6..864dd0acabc5 100644 --- a/test/watch/fixtures/vitest.config.ts +++ b/test/watch/fixtures/vitest.config.ts @@ -18,5 +18,9 @@ export default defineConfig({ forceRerunTriggers: [ '**/force-watch/**', ], + + globalSetup: process.env.TEST_GLOBAL_SETUP + ? './global-setup.ts' + : undefined, }, }) diff --git a/test/watch/test/global-setup-rerun.test.ts b/test/watch/test/global-setup-rerun.test.ts new file mode 100644 index 000000000000..232e60ca97c8 --- /dev/null +++ b/test/watch/test/global-setup-rerun.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from 'vitest' +import { editFile, runVitest } from '../../test-utils' + +const testFile = 'fixtures/math.test.ts' + +test('global setup calls hooks correctly', async () => { + process.env.TEST_GLOBAL_SETUP = 'true' + const { vitest, ctx } = await runVitest({ + root: 'fixtures', + watch: true, + include: ['math.test.ts'], + }) + + await vitest.waitForStdout('Waiting for file changes') + + const calls = (globalThis as any).__CALLS as string[] + expect(calls).toEqual(['start']) + + editFile(testFile, testFileContent => `${testFileContent}\n\n`) + + await vitest.waitForStdout('RERUN') + expect(calls).toEqual(['start', 'rerun']) + + await ctx?.close() + + expect(calls).toEqual(['start', 'rerun', 'end']) +}) From a34b4f58d7cd3ffb3978da3554a30c3fa7485625 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Tue, 29 Oct 2024 14:50:23 +0100 Subject: [PATCH 3/4] test: add a test with a manual rerun --- test/watch/test/global-setup-rerun.test.ts | 25 +++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/test/watch/test/global-setup-rerun.test.ts b/test/watch/test/global-setup-rerun.test.ts index 232e60ca97c8..f386157fadf2 100644 --- a/test/watch/test/global-setup-rerun.test.ts +++ b/test/watch/test/global-setup-rerun.test.ts @@ -3,7 +3,7 @@ import { editFile, runVitest } from '../../test-utils' const testFile = 'fixtures/math.test.ts' -test('global setup calls hooks correctly', async () => { +test('global setup calls hooks correctly when file changes', async () => { process.env.TEST_GLOBAL_SETUP = 'true' const { vitest, ctx } = await runVitest({ root: 'fixtures', @@ -25,3 +25,26 @@ test('global setup calls hooks correctly', async () => { expect(calls).toEqual(['start', 'rerun', 'end']) }) + +test('global setup calls hooks correctly with a manual rerun', async () => { + process.env.TEST_GLOBAL_SETUP = 'true' + const { vitest, ctx } = await runVitest({ + root: 'fixtures', + watch: true, + include: ['math.test.ts'], + }) + + await vitest.waitForStdout('Waiting for file changes') + + const calls = (globalThis as any).__CALLS as string[] + expect(calls).toEqual(['start']) + + vitest.write('r') + + await vitest.waitForStdout('RERUN') + expect(calls).toEqual(['start', 'rerun']) + + await ctx?.close() + + expect(calls).toEqual(['start', 'rerun', 'end']) +}) From 510e64a39c3a946317b4c8f1afc3a5de954341c4 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Tue, 5 Nov 2024 12:35:02 +0100 Subject: [PATCH 4/4] refactor: rename onWatcherRerun to onTestsRerun --- docs/config/index.md | 4 ++-- packages/vitest/src/node/core.ts | 14 +++++++------- packages/vitest/src/node/globalSetup.ts | 4 ++-- packages/vitest/src/node/workspace.ts | 2 +- packages/vitest/src/public/node.ts | 2 +- packages/vitest/src/types/general.ts | 2 +- test/watch/fixtures/global-setup.ts | 4 ++-- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/config/index.md b/docs/config/index.md index 93dbdd978283..efb37d8e2ade 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -1094,8 +1094,8 @@ Since Vitest 2.2.0, you can define a custom callback function to be called when ```ts import type { GlobalSetupContext } from 'vitest/node' -export default function setup({ onWatcherRerun }: GlobalSetupContext) { - onWatcherRerun(async () => { +export default function setup({ onTestsRerun }: GlobalSetupContext) { + onTestsRerun(async () => { await restartDb() }) } diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index b3c996e31ff4..ffeed278d2f9 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -3,7 +3,7 @@ import type { Writable } from 'node:stream' import type { ViteDevServer } from 'vite' import type { defineWorkspace } from 'vitest/config' import type { SerializedCoverageConfig } from '../runtime/config' -import type { ArgumentsType, OnServerRestartHandler, OnWatcherRerunHandler, ProvidedContext, UserConsoleLog } from '../types/general' +import type { ArgumentsType, OnServerRestartHandler, OnTestsRerunHandler, ProvidedContext, UserConsoleLog } from '../types/general' import type { ProcessPool, WorkspaceSpec } from './pool' import type { TestSpecification } from './spec' import type { ResolvedConfig, UserConfig, VitestRunMode } from './types/config' @@ -102,7 +102,7 @@ export class Vitest { private _onClose: (() => Awaited)[] = [] private _onSetServer: OnServerRestartHandler[] = [] private _onCancelListeners: ((reason: CancelReason) => Promise | void)[] = [] - private _onUserWatcherRerun: OnWatcherRerunHandler[] = [] + private _onUserTestsRerun: OnTestsRerunHandler[] = [] async setServer(options: UserConfig, server: ViteDevServer, cliOptions: UserConfig) { this.unregisterWatcher?.() @@ -114,7 +114,7 @@ export class Vitest { this.coverageProvider = undefined this.runningPromise = undefined this._cachedSpecs.clear() - this._onUserWatcherRerun = [] + this._onUserTestsRerun = [] const resolved = resolveConfig(this.mode, options, server.config, this.logger) @@ -695,7 +695,7 @@ export class Vitest { await Promise.all([ this.report('onWatcherRerun', files, trigger), - ...this._onUserWatcherRerun.map(fn => fn(files)), + ...this._onUserTestsRerun.map(fn => fn(files)), ]) await this.runFiles(files.flatMap(file => this.getProjectsByTestFile(file)), allTestsRun) @@ -820,7 +820,7 @@ export class Vitest { const triggerLabel = Array.from(triggerIds).join(', ') await Promise.all([ this.report('onWatcherRerun', files, triggerLabel), - ...this._onUserWatcherRerun.map(fn => fn(files)), + ...this._onUserTestsRerun.map(fn => fn(files)), ]) await this.runFiles(files.flatMap(file => this.getProjectsByTestFile(file)), false) @@ -1159,7 +1159,7 @@ export class Vitest { this._onClose.push(fn) } - onWatcherRerun(fn: OnWatcherRerunHandler): void { - this._onUserWatcherRerun.push(fn) + onTestsRerun(fn: OnTestsRerunHandler): void { + this._onUserTestsRerun.push(fn) } } diff --git a/packages/vitest/src/node/globalSetup.ts b/packages/vitest/src/node/globalSetup.ts index 10804003c434..33d17d8aadfe 100644 --- a/packages/vitest/src/node/globalSetup.ts +++ b/packages/vitest/src/node/globalSetup.ts @@ -1,5 +1,5 @@ import type { ViteNodeRunner } from 'vite-node/client' -import type { OnWatcherRerunHandler, ProvidedContext } from '../types/general' +import type { OnTestsRerunHandler, ProvidedContext } from '../types/general' import type { ResolvedConfig } from './types/config' import { toArray } from '@vitest/utils' @@ -9,7 +9,7 @@ export interface GlobalSetupContext { key: T, value: ProvidedContext[T] ) => void - onWatcherRerun: (cb: OnWatcherRerunHandler) => void + onTestsRerun: (cb: OnTestsRerunHandler) => void } export interface GlobalSetupFile { diff --git a/packages/vitest/src/node/workspace.ts b/packages/vitest/src/node/workspace.ts index 8f815e4c1513..6857b67c006e 100644 --- a/packages/vitest/src/node/workspace.ts +++ b/packages/vitest/src/node/workspace.ts @@ -170,7 +170,7 @@ export class WorkspaceProject { const teardown = await globalSetupFile.setup?.({ provide: (key, value) => this.provide(key, value), config: this.config, - onWatcherRerun: cb => this.ctx.onWatcherRerun(cb), + onTestsRerun: cb => this.ctx.onTestsRerun(cb), }) if (teardown == null || !!globalSetupFile.teardown) { continue diff --git a/packages/vitest/src/public/node.ts b/packages/vitest/src/public/node.ts index 6de6d2832199..e46a3911134e 100644 --- a/packages/vitest/src/public/node.ts +++ b/packages/vitest/src/public/node.ts @@ -128,7 +128,7 @@ export type { export type { OnServerRestartHandler, - OnWatcherRerunHandler, + OnTestsRerunHandler, } from '../types/general' export { createDebugger } from '../utils/debugger' diff --git a/packages/vitest/src/types/general.ts b/packages/vitest/src/types/general.ts index 0666bfaad5e5..2d46006f0da4 100644 --- a/packages/vitest/src/types/general.ts +++ b/packages/vitest/src/types/general.ts @@ -46,5 +46,5 @@ export interface ModuleGraphData { } export type OnServerRestartHandler = (reason?: string) => Promise | void -export type OnWatcherRerunHandler = (testFiles: string[]) => Promise | void +export type OnTestsRerunHandler = (testFiles: string[]) => Promise | void export interface ProvidedContext {} diff --git a/test/watch/fixtures/global-setup.ts b/test/watch/fixtures/global-setup.ts index 087062f73f56..b86537e952dc 100644 --- a/test/watch/fixtures/global-setup.ts +++ b/test/watch/fixtures/global-setup.ts @@ -4,9 +4,9 @@ const calls: string[] = []; (globalThis as any).__CALLS = calls -export default ({ onWatcherRerun }: GlobalSetupContext) => { +export default ({ onTestsRerun }: GlobalSetupContext) => { calls.push('start') - onWatcherRerun(() => { + onTestsRerun(() => { calls.push('rerun') }) return () => {