From e26e066c7759d43c3b819e3bb9d3c905e7b6c459 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 13 Nov 2024 17:01:32 +0100 Subject: [PATCH] feat(vitest): add `onTestsRerun` method to global setup context (#6803) --- 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 +- test/watch/fixtures/global-setup.ts | 15 +++++++ test/watch/fixtures/vitest.config.ts | 4 ++ test/watch/test/global-setup-rerun.test.ts | 50 ++++++++++++++++++++++ 9 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 test/watch/fixtures/global-setup.ts create mode 100644 test/watch/test/global-setup-rerun.test.ts diff --git a/docs/config/index.md b/docs/config/index.md index a0f9152d09d2..7a441d0687f0 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({ onTestsRerun }: GlobalSetupContext) { + onTestsRerun(async () => { + await restartDb() + }) +} +``` + ### forceRerunTriggers - **Type**: `string[]` diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 74fcceda3874..f10eb087b8a0 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, 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' @@ -104,6 +104,7 @@ export class Vitest { private _onClose: (() => Awaited)[] = [] private _onSetServer: OnServerRestartHandler[] = [] private _onCancelListeners: ((reason: CancelReason) => Promise | void)[] = [] + private _onUserTestsRerun: OnTestsRerunHandler[] = [] async setServer(options: UserConfig, server: ViteDevServer, cliOptions: UserConfig) { this.unregisterWatcher?.() @@ -119,6 +120,7 @@ export class Vitest { this.coverageProvider = undefined this.runningPromise = undefined this._cachedSpecs.clear() + this._onUserTestsRerun = [] const resolved = resolveConfig(this.mode, options, server.config, this.logger) @@ -695,7 +697,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._onUserTestsRerun.map(fn => fn(files)), + ]) await this.runFiles(files.flatMap(file => this.getProjectsByTestFile(file)), allTestsRun) await this.report('onWatcherStart', this.state.getFiles(files)) @@ -817,7 +822,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._onUserTestsRerun.map(fn => fn(files)), + ]) await this.runFiles(files.flatMap(file => this.getProjectsByTestFile(file)), false) @@ -1154,4 +1162,8 @@ export class Vitest { onClose(fn: () => void) { this._onClose.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 caf1d4e0820f..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 { ProvidedContext } from '../types/general' +import type { OnTestsRerunHandler, 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 + onTestsRerun: (cb: OnTestsRerunHandler) => void } export interface GlobalSetupFile { diff --git a/packages/vitest/src/node/workspace.ts b/packages/vitest/src/node/workspace.ts index 76e87a144f63..fea8e499818f 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, + 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 ce7b67697811..e46a3911134e 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, + 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 99cba19ca50b..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 OnTestsRerunHandler = (testFiles: string[]) => Promise | void export interface ProvidedContext {} diff --git a/test/watch/fixtures/global-setup.ts b/test/watch/fixtures/global-setup.ts new file mode 100644 index 000000000000..b86537e952dc --- /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 ({ onTestsRerun }: GlobalSetupContext) => { + calls.push('start') + onTestsRerun(() => { + 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..f386157fadf2 --- /dev/null +++ b/test/watch/test/global-setup-rerun.test.ts @@ -0,0 +1,50 @@ +import { expect, test } from 'vitest' +import { editFile, runVitest } from '../../test-utils' + +const testFile = 'fixtures/math.test.ts' + +test('global setup calls hooks correctly when file changes', 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']) +}) + +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']) +})