From 7bd11a9b32d413efa47a3262ce9c91606158f2fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Mon, 9 Jun 2025 13:57:56 +0300 Subject: [PATCH] fix(runner): fast sequential task updates missing (#8121) --- packages/runner/src/run.ts | 14 ++++++++++++-- test/reporters/tests/test-run.test.ts | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/runner/src/run.ts b/packages/runner/src/run.ts index f29429a22..8c746df74 100644 --- a/packages/runner/src/run.ts +++ b/packages/runner/src/run.ts @@ -17,7 +17,7 @@ import type { TestContext, WriteableTestContext, } from './types/tasks' -import { shuffle } from '@vitest/utils' +import { getSafeTimers, shuffle } from '@vitest/utils' import { processError } from '@vitest/utils/error' import { collectTests } from './collect' import { abortContextSignal, getFileContext } from './context' @@ -32,6 +32,7 @@ import { hasFailed, hasTests } from './utils/tasks' const now = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now const unixNow = Date.now +const { clearTimeout, setTimeout } = getSafeTimers() function updateSuiteHookState( task: Task, @@ -211,12 +212,21 @@ export async function finishSendTasksUpdate(runner: VitestRunner): Promise function throttle void>(fn: T, ms: number): T { let last = 0 - return function (this: any, ...args: any[]) { + let pendingCall: ReturnType | undefined + + return function call(this: any, ...args: any[]) { const now = unixNow() if (now - last > ms) { last = now + + clearTimeout(pendingCall) + pendingCall = undefined + return fn.apply(this, args) } + + // Make sure fn is still called even if there are no further calls + pendingCall ??= setTimeout(() => call.bind(this)(...args), ms) } as any } diff --git a/test/reporters/tests/test-run.test.ts b/test/reporters/tests/test-run.test.ts index aeab816d6..90719c83b 100644 --- a/test/reporters/tests/test-run.test.ts +++ b/test/reporters/tests/test-run.test.ts @@ -1,3 +1,4 @@ +import type { UserConsoleLog } from 'vitest' import type { ReportedHookContext, Reporter, @@ -159,7 +160,10 @@ describe('TestCase', () => { test('single test case', async () => { const report = await run({ 'example.test.ts': ts` - test('single test case', () => {}); + test('single test case', async () => { + await new Promise(resolve => setTimeout(resolve, 150)) + console.log("Test running!") + }); `, }) @@ -169,6 +173,7 @@ describe('TestCase', () => { onTestModuleCollected (example.test.ts) onTestModuleStart (example.test.ts) onTestCaseReady (example.test.ts) |single test case| + onUserConsoleLog (example.test.ts) |single test case| > Test running! onTestCaseResult (example.test.ts) |single test case| onTestModuleEnd (example.test.ts)" `) @@ -1141,9 +1146,14 @@ async function run( class CustomReporter implements Reporter { calls: string[] = [] + ctx!: Vitest constructor(private options: ReporterOptions = {}) {} + onInit(ctx: Vitest) { + this.ctx = ctx + } + onTestRunStart(specifications: ReadonlyArray) { if (this.options.printTestRunEvents) { this.calls.push(`onTestRunStart (${specifications.length} specifications)`) @@ -1188,6 +1198,13 @@ class CustomReporter implements Reporter { this.calls.push(`${padded(test, 'onTestCaseResult')} (${this.normalizeFilename(test.module)}) |${test.name}|`) } + onUserConsoleLog(log: UserConsoleLog) { + const task = this.ctx.state.idMap.get(log.taskId!) + const test = task && this.ctx.state.getReportedEntity(task) as TestCase + + this.calls.push(`${padded(test!, 'onUserConsoleLog')} (${this.normalizeFilename(test!.module)}) |${test!.name}| > ${log.content.replaceAll('\n', '')}`) + } + onHookStart(hook: ReportedHookContext) { const module = hook.entity.type === 'module' ? hook.entity : hook.entity.module const name = hook.entity.type !== 'module' ? ` |${hook.entity.name}|` : '' -- 2.51.2