fix(reporter)!: remove deprecated APIs (#8223)

Co-authored-by: Vladimir Sheremet <sleuths.slews0s@icloud.com>
This commit is contained in:
Ari Perkkiö 2025-08-04 15:30:28 +03:00 committed by GitHub
parent 61703c9ea7
commit 149f8e5095
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 263 additions and 190 deletions

View File

@ -25,6 +25,7 @@ Vitest has its own test run lifecycle. These are represented by reporter's metho
- [`onHookEnd(afterAll)`](#onhookend)
- [`onTestSuiteResult`](#ontestsuiteresult)
- [`onTestModuleEnd`](#ontestmoduleend)
- [`onCoverage`](#oncoverage)
- [`onTestRunEnd`](#ontestrunend)
Tests and suites within a single module will be reported in order unless they were skipped. All skipped tests are reported at the end of suite/module.

View File

@ -67,7 +67,7 @@ The function is called only once (unless the server config was updated), and it'
Vitest calls `runTest` when new tests are scheduled to run. It will not call it if `files` is empty. The first argument is an array of [TestSpecifications](/advanced/api/test-specification). Files are sorted using [`sequencer`](/config/#sequence-sequencer) before `runTests` is called. It's possible (but unlikely) to have the same file twice, but it will always have a different project - this is implemented via [`projects`](/guide/projects) configuration.
Vitest will wait until `runTests` is executed before finishing a run (i.e., it will emit [`onFinished`](/advanced/reporters) only after `runTests` is resolved).
Vitest will wait until `runTests` is executed before finishing a run (i.e., it will emit [`onTestRunEnd`](/advanced/reporters) only after `runTests` is resolved).
If you are using a custom pool, you will have to provide test files and their results yourself - you can reference [`vitest.state`](https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/node/state.ts) for that (most important are `collectFiles` and `updateTasks`). Vitest uses `startTests` function from `@vitest/runner` package to do that.

View File

@ -26,7 +26,7 @@ And here is an example of a custom reporter:
import { BaseReporter } from 'vitest/reporters'
export default class CustomReporter extends BaseReporter {
onCollected() {
onTestModuleCollected() {
const files = this.ctx.state.getFiles(this.watchFilters)
this.reportTestSummary(files)
}
@ -39,7 +39,7 @@ Or implement the `Reporter` interface:
import type { Reporter } from 'vitest/node'
export default class CustomReporter implements Reporter {
onCollected() {
onTestModuleCollected() {
// print something
}
}
@ -65,22 +65,14 @@ Instead of using the tasks that reporters receive, it is recommended to use the
You can get access to this API by calling `vitest.state.getReportedEntity(runnerTask)`:
```ts twoslash
import type { Reporter, RunnerTestFile, TestModule, Vitest } from 'vitest/node'
import type { Reporter, TestModule } from 'vitest/node'
class MyReporter implements Reporter {
private vitest!: Vitest
onInit(vitest: Vitest) {
this.vitest = vitest
}
onFinished(files: RunnerTestFile[]) {
for (const file of files) {
// note that the old task implementation uses "file" instead of "module"
const testModule = this.vitest.state.getReportedEntity(file) as TestModule
onTestRunEnd(testModules: ReadonlyArray<TestModule>) {
for (const testModule of testModules) {
for (const task of testModule.children) {
// ^?
console.log('finished', task.type, task.fullName)
console.log('test run end', task.type, task.fullName)
}
}
}

View File

@ -200,6 +200,7 @@ Vitest 4.0 removes some deprecated APIs, including:
- `poolMatchGlobs` config option. Use [`projects`](/guide/projects) instead.
- `environmentMatchGlobs` config option. Use [`projects`](/guide/projects) instead.
- `workspace` config option. Use [`projects`](/guide/projects) instead.
- Reporter APIs `onCollected`, `onSpecsCollected`, `onPathsCollected`, `onTaskUpdate` and `onFinished`. See [`Reporters API`](/advanced/api/reporters) for new alternatives. These APIs were introduced in Vitest `v3.0.0`.
- `deps.external`, `deps.inline`, `deps.fallbackCJS` config options. Use `server.deps.external`, `server.deps.inline`, or `server.deps.fallbackCJS` instead.
This release also removes all deprecated types. This finally fixes an issue where Vitest accidentally pulled in `@types/node` (see [#5481](https://github.com/vitest-dev/vitest/issues/5481) and [#6141](https://github.com/vitest-dev/vitest/issues/6141)).

View File

@ -63,11 +63,6 @@ export type Awaitable<T> = T | PromiseLike<T>
export interface WebSocketEvents {
onCollected?: (files: RunnerTestFile[]) => Awaitable<void>
onFinished?: (
files: File[],
errors: unknown[],
coverage?: unknown
) => Awaitable<void>
onTaskUpdate?: (packs: TaskResultPack[]) => Awaitable<void>
onUserConsoleLog?: (log: UserConsoleLog) => Awaitable<void>
onPathsCollected?: (paths?: string[]) => Awaitable<void>

View File

@ -71,7 +71,7 @@ export default class HTMLReporter implements Reporter {
await fs.mkdir(resolve(this.reporterDir, 'assets'), { recursive: true })
}
async onFinished(): Promise<void> {
async onTestRunEnd(): Promise<void> {
const result: HTMLReportData = {
paths: this.ctx.state.getPaths(),
files: this.ctx.state.getFiles(),

View File

@ -1,12 +1,13 @@
import type { File, TaskEventPack, TaskResultPack, TestAnnotation } from '@vitest/runner'
import type { SerializedError } from '@vitest/utils'
import type { IncomingMessage } from 'node:http'
import type { ViteDevServer } from 'vite'
import type { WebSocket } from 'ws'
import type { Vitest } from '../node/core'
import type { TestCase } from '../node/reporters/reported-tasks'
import type { TestCase, TestModule } from '../node/reporters/reported-tasks'
import type { TestSpecification } from '../node/spec'
import type { Reporter } from '../node/types/reporter'
import type { SerializedTestSpecification } from '../runtime/types/utils'
import type { Awaitable, LabelColor, ModuleGraphData, UserConsoleLog } from '../types/general'
import type { LabelColor, ModuleGraphData, UserConsoleLog } from '../types/general'
import type {
TransformResultWithSource,
WebSocketEvents,
@ -163,21 +164,25 @@ export class WebSocketReporter implements Reporter {
public clients: Map<WebSocket, WebSocketRPC>,
) {}
onCollected(files?: File[]): void {
onTestModuleCollected(testModule: TestModule): void {
if (this.clients.size === 0) {
return
}
this.clients.forEach((client) => {
client.onCollected?.(files)?.catch?.(noop)
client.onCollected?.([testModule.task])?.catch?.(noop)
})
}
onSpecsCollected(specs?: SerializedTestSpecification[] | undefined): Awaitable<void> {
onTestRunStart(specifications: ReadonlyArray<TestSpecification>): void {
if (this.clients.size === 0) {
return
}
const serializedSpecs = specifications.map(spec => spec.toJSON())
this.clients.forEach((client) => {
client.onSpecsCollected?.(specs)?.catch?.(noop)
client.onSpecsCollected?.(serializedSpecs)?.catch?.(noop)
})
}
@ -220,7 +225,14 @@ export class WebSocketReporter implements Reporter {
})
}
onFinished(files: File[], errors: unknown[]): void {
onTestRunEnd(testModules: ReadonlyArray<TestModule>, unhandledErrors: ReadonlyArray<SerializedError>): void {
if (!this.clients.size) {
return
}
const files = testModules.map(testModule => testModule.task)
const errors = [...unhandledErrors]
this.clients.forEach((client) => {
client.onFinished?.(files, errors)?.catch?.(noop)
})

View File

@ -446,7 +446,6 @@ export class Vitest {
this.state.blobs = { files, errors, coverages, executionTimes }
await this.report('onInit', this)
await this.report('onPathsCollected', files.flatMap(f => f.filepath))
const specifications: TestSpecification[] = []
for (const file of files) {
@ -455,7 +454,6 @@ export class Vitest {
specifications.push(specification)
}
await this.report('onSpecsCollected', specifications.map(spec => spec.toJSON()))
await this._testRun.start(specifications).catch(noop)
for (const file of files) {
@ -698,7 +696,6 @@ export class Vitest {
}
}
finally {
// TODO: wait for coverage only if `onFinished` is defined
const coverage = await this.coverageProvider?.generateCoverage({ allTestsRun })
const errors = this.state.getUnhandledErrors()

View File

@ -4,6 +4,7 @@ import type { Vitest } from '../core'
import type { ProcessPool } from '../pool'
import type { TestProject } from '../project'
import type { TestSpecification } from '../spec'
import type { TestRunEndReason } from '../types/reporter'
import { hasFailed } from '@vitest/runner/utils'
import { createDefer } from '@vitest/utils'
import { Typechecker } from '../../typecheck/typechecker'
@ -42,7 +43,15 @@ export function createTypecheckPool(vitest: Vitest): ProcessPool {
// triggered by TSC watcher, not Vitest watcher, so we need to emulate what Vitest does in this case
if (vitest.config.watch && !vitest.runningPromise) {
await vitest.report('onFinished', files, [])
const modules = files.map(file => vitest.state.getReportedEntity(file)).filter(e => e?.type === 'module')
const state: TestRunEndReason = vitest.isCancelling
? 'interrupted'
: modules.some(m => !m.ok())
? 'failed'
: 'passed'
await vitest.report('onTestRunEnd', modules, [], state)
await vitest.report('onWatcherStart', files, [
...(project.config.typecheck.ignoreSourceErrors ? [] : sourceErrors),
...vitest.state.getUnhandledErrors(),

View File

@ -1,7 +1,8 @@
import type { File, Task } from '@vitest/runner'
import type { SerializedError } from '@vitest/utils'
import type { TestError, UserConsoleLog } from '../../types/general'
import type { Vitest } from '../core'
import type { Reporter } from '../types/reporter'
import type { Reporter, TestRunEndReason } from '../types/reporter'
import type { TestCase, TestCollection, TestModule, TestModuleState, TestResult, TestSuite, TestSuiteState } from './reported-tasks'
import { performance } from 'node:perf_hooks'
import { getFullName, getSuites, getTestName, getTests, hasFailed } from '@vitest/runner/utils'
@ -57,7 +58,14 @@ export abstract class BaseReporter implements Reporter {
return relative(this.ctx.config.root, path)
}
onFinished(files: File[] = this.ctx.state.getFiles(), errors: unknown[] = this.ctx.state.getUnhandledErrors()): void {
onTestRunEnd(
testModules: ReadonlyArray<TestModule>,
unhandledErrors: ReadonlyArray<SerializedError>,
_reason: TestRunEndReason,
): void {
const files = testModules.map(testModule => testModule.task)
const errors = [...unhandledErrors]
this.end = performance.now()
if (!files.length && !errors.length) {
this.ctx.logger.printNoTestFound(this.ctx.filenamePattern)

View File

@ -1,5 +1,7 @@
import type { File, TaskResultPack } from '@vitest/runner'
import type { TaskResultPack } from '@vitest/runner'
import type { SerializedError } from '@vitest/utils'
import type { Vitest } from '../../core'
import type { TestRunEndReason } from '../../types/reporter'
import type { TestModule, TestSuite } from '../reported-tasks'
import fs from 'node:fs'
import { getFullName } from '@vitest/runner/utils'
@ -84,8 +86,12 @@ export class BenchmarkReporter extends DefaultReporter {
}
}
async onFinished(files: File[] = this.ctx.state.getFiles(), errors: unknown[] = this.ctx.state.getUnhandledErrors()): Promise<void> {
super.onFinished(files, errors)
async onTestRunEnd(
testModules: ReadonlyArray<TestModule>,
unhandledErrors: ReadonlyArray<SerializedError>,
reason: TestRunEndReason,
): Promise<void> {
super.onTestRunEnd(testModules, unhandledErrors, reason)
// write output for future comparison
let outputFile = this.ctx.config.benchmark?.outputJson
@ -98,7 +104,9 @@ export class BenchmarkReporter extends DefaultReporter {
await fs.promises.mkdir(outputDirectory, { recursive: true })
}
const files = testModules.map(t => t.task.file)
const output = createBenchmarkJsonReport(files)
await fs.promises.writeFile(outputFile, JSON.stringify(output, null, 2))
this.log(`Benchmark report written to ${outputFile}`)
}

View File

@ -1,7 +1,9 @@
import type { File } from '@vitest/runner'
import type { SerializedError } from '@vitest/utils'
import type { Vitest } from '../core'
import type { TestProject } from '../project'
import type { Reporter } from '../types/reporter'
import type { TestModule } from './reported-tasks'
import { existsSync } from 'node:fs'
import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises'
import { parse, stringify } from 'flatted'
@ -16,6 +18,7 @@ export class BlobReporter implements Reporter {
start = 0
ctx!: Vitest
options: BlobOptions
coverage: unknown | undefined
constructor(options: BlobOptions) {
this.options = options
@ -28,15 +31,20 @@ export class BlobReporter implements Reporter {
this.ctx = ctx
this.start = performance.now()
this.coverage = undefined
}
async onFinished(
files: File[] = [],
errors: unknown[] = [],
coverage: unknown,
): Promise<void> {
onCoverage(coverage: unknown): void {
this.coverage = coverage
}
async onTestRunEnd(testModules: ReadonlyArray<TestModule>, unhandledErrors: ReadonlyArray<SerializedError>): Promise<void> {
const executionTime = performance.now() - this.start
const files = testModules.map(testModule => testModule.task)
const errors = [...unhandledErrors]
const coverage = this.coverage
let outputFile
= this.options.outputFile ?? getOutputFile(this.ctx.config, 'blob')
if (!outputFile) {

View File

@ -1,5 +1,7 @@
import type { SerializedError } from '@vitest/utils'
import type { Vitest } from '../core'
import type { TestSpecification } from '../spec'
import type { TestRunEndReason } from '../types/reporter'
import type { BaseOptions } from './base'
import type { ReportedHookContext, TestCase, TestModule } from './reported-tasks'
import { BaseReporter } from './base'
@ -43,6 +45,15 @@ export class DefaultReporter extends BaseReporter {
this.summary?.onTestRunStart(specifications)
}
onTestRunEnd(
testModules: ReadonlyArray<TestModule>,
unhandledErrors: ReadonlyArray<SerializedError>,
reason: TestRunEndReason,
): void {
super.onTestRunEnd(testModules, unhandledErrors, reason)
this.summary?.onTestRunEnd()
}
onTestModuleQueued(file: TestModule): void {
this.summary?.onTestModuleQueued(file)
}
@ -77,8 +88,4 @@ export class DefaultReporter extends BaseReporter {
super.onInit(ctx)
this.summary?.onInit(ctx, { verbose: this.verbose })
}
onTestRunEnd(): void {
this.summary?.onTestRunEnd()
}
}

View File

@ -1,6 +1,8 @@
import type { File, Test } from '@vitest/runner'
import type { Test } from '@vitest/runner'
import type { SerializedError } from '@vitest/utils'
import type { Writable } from 'node:stream'
import type { Vitest } from '../core'
import type { TestRunEndReason } from '../types/reporter'
import type { TestCase, TestModule } from './reported-tasks'
import c from 'tinyrainbow'
import { BaseReporter } from './base'
@ -40,7 +42,11 @@ export class DotReporter extends BaseReporter {
super.onWatcherRerun(files, trigger)
}
onFinished(files?: File[], errors?: unknown[]): void {
onTestRunEnd(
testModules: ReadonlyArray<TestModule>,
unhandledErrors: ReadonlyArray<SerializedError>,
reason: TestRunEndReason,
): void {
if (this.isTTY) {
const finalLog = formatTests(Array.from(this.tests.values()))
this.ctx.logger.log(finalLog)
@ -52,7 +58,7 @@ export class DotReporter extends BaseReporter {
this.tests.clear()
this.renderer?.finish()
super.onFinished(files, errors)
super.onTestRunEnd(testModules, unhandledErrors, reason)
}
onTestModuleCollected(module: TestModule): void {

View File

@ -1,8 +1,9 @@
import type { File, TestAnnotation } from '@vitest/runner'
import type { SerializedError } from '@vitest/utils'
import type { Vitest } from '../core'
import type { TestProject } from '../project'
import type { Reporter } from '../types/reporter'
import type { TestCase } from './reported-tasks'
import type { TestCase, TestModule } from './reported-tasks'
import { stripVTControlCharacters } from 'node:util'
import { getFullName, getTasks } from '@vitest/runner/utils'
import { capturePrintError } from '../printError'
@ -42,7 +43,13 @@ export class GithubActionsReporter implements Reporter {
this.ctx.logger.log(`\n${formatted}`)
}
onFinished(files: File[] = [], errors: unknown[] = []): void {
onTestRunEnd(
testModules: ReadonlyArray<TestModule>,
unhandledErrors: ReadonlyArray<SerializedError>,
): void {
const files = testModules.map(testModule => testModule.task)
const errors = [...unhandledErrors]
// collect all errors and associate them with projects
const projectErrors = new Array<{
project: TestProject

View File

@ -1,8 +1,9 @@
import type { File, Suite, TaskMeta, TaskState } from '@vitest/runner'
import type { Suite, TaskMeta, TaskState } from '@vitest/runner'
import type { SnapshotSummary } from '@vitest/snapshot'
import type { CoverageMap } from 'istanbul-lib-coverage'
import type { Vitest } from '../core'
import type { Reporter } from '../types/reporter'
import type { TestModule } from './reported-tasks'
import { existsSync, promises as fs } from 'node:fs'
import { getSuites, getTests } from '@vitest/runner/utils'
import { dirname, resolve } from 'pathe'
@ -78,6 +79,7 @@ export class JsonReporter implements Reporter {
start = 0
ctx!: Vitest
options: JsonOptions
coverageMap?: CoverageMap
constructor(options: JsonOptions) {
this.options = options
@ -86,9 +88,16 @@ export class JsonReporter implements Reporter {
onInit(ctx: Vitest): void {
this.ctx = ctx
this.start = Date.now()
this.coverageMap = undefined
}
protected async logTasks(files: File[], coverageMap?: CoverageMap | null): Promise<void> {
onCoverage(coverageMap: unknown): void {
this.coverageMap = coverageMap as CoverageMap
}
async onTestRunEnd(testModules: ReadonlyArray<TestModule>): Promise<void> {
const files = testModules.map(testModule => testModule.task)
const suites = getSuites(files)
const numTotalTestSuites = suites.length
const tests = getTests(files)
@ -190,16 +199,12 @@ export class JsonReporter implements Reporter {
startTime: this.start,
success,
testResults,
coverageMap,
coverageMap: this.coverageMap,
}
await this.writeReport(JSON.stringify(result))
}
async onFinished(files: File[] = this.ctx.state.getFiles(), _errors: unknown[] = [], coverageMap?: unknown): Promise<void> {
await this.logTasks(files, coverageMap as CoverageMap)
}
/**
* Writes the report to an output file if specified in the config,
* or logs it to the console otherwise.

View File

@ -1,6 +1,7 @@
import type { File, Task } from '@vitest/runner'
import type { Task } from '@vitest/runner'
import type { Vitest } from '../core'
import type { Reporter } from '../types/reporter'
import type { TestModule } from './reported-tasks'
import { existsSync, promises as fs } from 'node:fs'
import { hostname } from 'node:os'
@ -284,7 +285,9 @@ export class JUnitReporter implements Reporter {
}
}
async onFinished(files: File[] = this.ctx.state.getFiles()): Promise<void> {
async onTestRunEnd(testModules: ReadonlyArray<TestModule>): Promise<void> {
const files = testModules.map(testModule => testModule.task)
await this.logger.log('<?xml version="1.0" encoding="UTF-8" ?>')
const transformed = files.map((file) => {

View File

@ -1,5 +1,6 @@
import type { File, Task } from '@vitest/runner'
import type { Task } from '@vitest/runner'
import type { Vitest } from '../core'
import type { TestModule } from './reported-tasks'
import { TapReporter } from './tap'
function flattenTasks(task: Task, baseName = ''): Task[] {
@ -25,10 +26,10 @@ export class TapFlatReporter extends TapReporter {
super.onInit(ctx)
}
onFinished(files: File[] = this.ctx.state.getFiles()): void {
onTestRunEnd(testModules: ReadonlyArray<TestModule>): void {
this.ctx.logger.log('TAP version 13')
const flatTasks = files.flatMap(task => flattenTasks(task))
const flatTasks = testModules.flatMap(testModule => flattenTasks(testModule.task))
this.logTasks(flatTasks)
}

View File

@ -1,7 +1,8 @@
import type { File, Task } from '@vitest/runner'
import type { Task } from '@vitest/runner'
import type { ParsedStack, TestError } from '@vitest/utils'
import type { Vitest } from '../core'
import type { Reporter } from '../types/reporter'
import type { TestModule } from './reported-tasks'
import { parseErrorStacktrace } from '../../utils/source-map'
import { IndentedLogger } from './renderers/indented-logger'
@ -132,7 +133,9 @@ export class TapReporter implements Reporter {
}
}
onFinished(files: File[] = this.ctx.state.getFiles()): void {
onTestRunEnd(testModules: ReadonlyArray<TestModule>): void {
const files = testModules.map(testModule => testModule.task)
this.logger.log('TAP version 13')
this.logTasks(files)

View File

@ -28,8 +28,6 @@ export class TestRun {
const filepaths = specifications.map(spec => spec.moduleId)
this.vitest.state.collectPaths(filepaths)
await this.vitest.report('onPathsCollected', Array.from(new Set(filepaths)))
await this.vitest.report('onSpecsCollected', specifications.map(spec => spec.toJSON()))
await this.vitest.report('onTestRunStart', [...specifications])
}
@ -41,13 +39,12 @@ export class TestRun {
async collected(project: TestProject, files: RunnerTestFile[]): Promise<void> {
this.vitest.state.collectFiles(project, files)
await Promise.all([
this.vitest.report('onCollected', files),
...files.map((file) => {
await Promise.all(
files.map((file) => {
const testModule = this.vitest.state.getReportedEntity(file) as TestModule
return this.vitest.report('onTestModuleCollected', testModule)
}),
])
)
}
async log(log: UserConsoleLog): Promise<void> {
@ -86,9 +83,12 @@ export class TestRun {
}
async end(specifications: TestSpecification[], errors: unknown[], coverage?: unknown): Promise<void> {
if (coverage) {
await this.vitest.report('onCoverage', coverage)
}
// specification won't have the File task if they were filtered by the --shard command
const modules = specifications.map(spec => spec.testModule).filter(s => s != null)
const files = modules.map(m => m.task)
const state: TestRunEndReason = this.vitest.isCancelling
? 'interrupted'
@ -102,18 +102,7 @@ export class TestRun {
process.exitCode = 1
}
try {
await Promise.all([
this.vitest.report('onTestRunEnd', modules, [...errors] as SerializedError[], state),
// TODO: in a perfect world, the coverage should be done in parallel to `onFinished`
this.vitest.report('onFinished', files, errors, coverage),
])
}
finally {
if (coverage) {
await this.vitest.report('onCoverage', coverage)
}
}
this.vitest.report('onTestRunEnd', modules, [...errors] as SerializedError[], state)
}
private hasFailed(modules: TestModule[]) {

View File

@ -1,6 +1,5 @@
import type { File, TaskEventPack, TaskResultPack, TestAnnotation } from '@vitest/runner'
import type { SerializedError } from '@vitest/utils'
import type { SerializedTestSpecification } from '../../runtime/types/utils'
import type { Awaitable, UserConsoleLog } from '../../types/general'
import type { Vitest } from '../core'
import type { TestProject } from '../project'
@ -17,29 +16,7 @@ export interface Reporter {
* @experimental
*/
onBrowserInit?: (project: TestProject) => Awaitable<void>
/**
* @deprecated use `onTestRunStart` instead
*/
onPathsCollected?: (paths?: string[]) => Awaitable<void>
/**
* @deprecated use `onTestRunStart` instead
*/
onSpecsCollected?: (specs?: SerializedTestSpecification[]) => Awaitable<void>
/**
* @deprecated use `onTestModuleCollected` instead
*/
onCollected?: (files: File[]) => Awaitable<void>
/**
* @deprecated use `onTestRunEnd` instead
*/
onFinished?: (
files: File[],
errors: unknown[],
coverage?: unknown
) => Awaitable<void>
/**
* @deprecated use `onTestModuleQueued`, `onTestModuleStart`, `onTestModuleEnd`, `onTestCaseReady`, `onTestCaseResult` instead
*/
/** @internal */
onTaskUpdate?: (packs: TaskResultPack[], events: TaskEventPack[]) => Awaitable<void>
onTestRemoved?: (trigger?: string) => Awaitable<void>
onWatcherStart?: (files?: File[], errors?: unknown[]) => Awaitable<void>

View File

@ -33,10 +33,9 @@ describe('running browser tests', async () => {
'json',
{
onInit: noop,
onPathsCollected: noop,
onCollected: noop,
onFinished: noop,
onTaskUpdate: noop,
onTestRunStart: noop,
onTestModuleCollected: noop,
onTestRunEnd: noop,
onTestRemoved: noop,
onWatcherStart: noop,
onWatcherRerun: noop,

View File

@ -19,7 +19,7 @@ it('automatically assigns the port', async () => {
onInit(ctx_) {
ctx = ctx_
},
onFinished() {
onTestRunEnd() {
urls = ctx.projects.map(p => p.browser?.vite.resolvedUrls?.local[0])
},
},

View File

@ -1,29 +1,28 @@
import type { TestModule } from 'vitest/node'
import { expect, it, vi } from 'vitest'
import { createVitest } from 'vitest/node'
it(createVitest, async () => {
const onFinished = vi.fn()
const onTestRunEnd = vi.fn()
const ctx = await createVitest('test', {
watch: false,
root: 'fixtures/create-vitest',
reporters: [
{
onFinished,
onTestRunEnd,
},
],
})
const testFiles = await ctx.globTestSpecifications()
await ctx.runTestSpecifications(testFiles, false)
expect(onFinished.mock.calls[0]).toMatchObject([
[
{
name: 'basic.test.ts',
result: {
state: 'pass',
},
},
],
[],
undefined,
])
const [testModules, errors, reason] = onTestRunEnd.mock.calls[0]
expect(testModules).toHaveLength(1)
const testModule = testModules[0]
expect((testModule as TestModule).task?.name).toBe('basic.test.ts')
expect((testModule as TestModule).state()).toBe('passed')
expect(errors).toHaveLength(0)
expect(reason).toBe('passed')
})

View File

@ -1,5 +1,5 @@
import type { RunnerTaskResultPack, RunnerTestFile } from 'vitest'
import type { TestUserConfig } from 'vitest/node'
import type { TaskMeta } from '@vitest/runner'
import type { TestModule, TestUserConfig } from 'vitest/node'
import { resolve } from 'pathe'
import { expect, it } from 'vitest'
import { rolldownVersion } from 'vitest/node'
@ -15,23 +15,28 @@ it.each([
},
},
] as TestUserConfig[])('passes down metadata when $name', { timeout: 60_000, retry: 1 }, async (config) => {
const taskUpdate: RunnerTaskResultPack[] = []
const finishedFiles: RunnerTestFile[] = []
const collectedFiles: RunnerTestFile[] = []
const finishedTestCaseMetas: TaskMeta[] = []
const finishedTestModuleMetas: TaskMeta[] = []
const finishedTestModules: TestModule[] = []
const collectedTestModules: TestModule[] = []
const { ctx, stdout, stderr } = await runVitest({
root: resolve(__dirname, '..', 'fixtures', 'public-api'),
include: ['**/*.spec.ts'],
reporters: [
['verbose', { isTTY: false }],
{
onTaskUpdate(packs) {
taskUpdate.push(...packs.filter(i => i[1]?.state === 'pass'))
onTestCaseResult(testCase) {
finishedTestCaseMetas.push(testCase.meta())
},
onFinished(files) {
finishedFiles.push(...files || [])
onTestModuleEnd(testModule) {
finishedTestModuleMetas.push(testModule.meta())
},
onCollected(files) {
collectedFiles.push(...files || [])
onTestRunEnd(testModules) {
finishedTestModules.push(...testModules)
},
onTestModuleCollected(testModule) {
collectedTestModules.push(testModule)
},
},
],
@ -46,39 +51,30 @@ it.each([
const suiteMeta = { done: true }
const testMeta = { custom: 'some-custom-hanlder' }
expect(taskUpdate).toHaveLength(4)
expect(finishedFiles).toHaveLength(1)
expect(finishedTestCaseMetas).toHaveLength(3)
expect(finishedTestModuleMetas).toHaveLength(1)
expect(finishedTestModules).toHaveLength(1)
const files = ctx?.state.getFiles() || []
expect(files).toHaveLength(1)
expect(taskUpdate).toContainEqual(
[
expect.any(String),
expect.anything(),
suiteMeta,
],
)
expect(finishedTestModuleMetas).toContainEqual(suiteMeta)
expect(taskUpdate).toContainEqual(
[
expect.any(String),
expect.anything(),
testMeta,
],
)
expect(finishedTestCaseMetas).toContainEqual(testMeta)
expect(finishedFiles[0].meta).toEqual(suiteMeta)
expect(finishedFiles[0].tasks[0].meta).toEqual(testMeta)
const test = finishedTestModules[0].children.tests().next().value!
expect(finishedTestModules[0].meta()).toEqual(suiteMeta)
expect(test.meta()).toEqual(testMeta)
expect(files[0].meta).toEqual(suiteMeta)
expect(files[0].tasks[0].meta).toEqual(testMeta)
expect(finishedFiles[0].tasks[0].location).toEqual({
expect(test.location).toEqual({
line: 14,
column: 1,
})
expect(collectedFiles[0].tasks[0].location).toEqual({
expect(collectedTestModules[0].task.tasks[0].location).toEqual({
line: 14,
column: 1,
})

View File

@ -7,8 +7,7 @@ import { beforeAll, expect, it } from 'vitest'
import { runVitest } from '../../test-utils'
const now = new Date()
// const finishedFiles: File[] = []
const collectedFiles: RunnerTestFile[] = []
const collectedTestModules: TestModule[] = []
let state: StateManager
let project: WorkspaceProject
let files: RunnerTestFile[]
@ -23,11 +22,8 @@ beforeAll(async () => {
reporters: [
'verbose',
{
// onFinished(files) {
// finishedFiles.push(...files || [])
// },
onCollected(files) {
collectedFiles.push(...files || [])
onTestModuleCollected(testModule) {
collectedTestModules.push(testModule)
},
},
],
@ -40,6 +36,7 @@ beforeAll(async () => {
expect(files).toHaveLength(1)
testModule = state.getReportedEntity(files[0])! as TestModule
expect(testModule).toBeDefined()
expect(testModule).toBe(collectedTestModules[0])
})
it('correctly reports a file', () => {

View File

@ -3,7 +3,7 @@ export default class PackageReporter {
this.ctx = ctx
}
onFinished() {
onTestRunEnd() {
this.ctx.logger.log('hello from package reporter')
}
}

View File

@ -44,6 +44,7 @@ export function getContext(): Context {
context.logger = {
ctx: context as Vitest,
log: (text: string) => output += `${text}\n`,
highlight: () => {},
} as unknown as Logger
return {

View File

@ -3,7 +3,7 @@ export default class TestReporter {
this.ctx = ctx
}
onFinished() {
onTestRunEnd() {
this.ctx.logger.log('hello from custom reporter')
}
}

View File

@ -12,7 +12,7 @@ export default class TestReporter implements Reporter {
this.ctx = ctx
}
onFinished() {
onTestRunEnd() {
this.ctx.logger.log('hello from custom reporter')
if (this.options) {

View File

@ -17,7 +17,7 @@ test('reporters, single', () => {
assertType<Configuration>({ reporters: 'custom-reporter' })
assertType<Configuration>({ reporters: './reporter.mjs' })
assertType<Configuration>({ reporters: { onFinished() {} } })
assertType<Configuration>({ reporters: { onTestRunEnd() {} } })
})
test('reporters, multiple', () => {

View File

@ -7,7 +7,6 @@ import { runVitest } from '../../test-utils'
class LogReporter extends DefaultReporter {
isTTY = true
onTaskUpdate() {}
}
test('should print logs correctly', async () => {

View File

@ -1,3 +1,4 @@
import type { TestModule } from '../../../packages/vitest/src/node/reporters/reported-tasks'
import { existsSync, readFileSync, rmSync } from 'node:fs'
import { normalize, resolve } from 'pathe'
import { beforeEach, expect, test, vi } from 'vitest'
@ -9,6 +10,9 @@ import { getContext } from '../src/context'
import { files, passedFiles } from '../src/data'
const beautify = (json: string) => JSON.parse(json)
function getTestModules(_files = files) {
return _files.map(task => ({ task }) as TestModule)
}
vi.mock('os', () => ({
hostname: () => 'hostname',
@ -25,10 +29,11 @@ test('tap reporter', async () => {
// Arrange
const reporter = new TapReporter()
const context = getContext()
const testModules = getTestModules()
// Act
reporter.onInit(context.vitest)
await reporter.onFinished(files)
reporter.onTestRunEnd(testModules)
// Assert
expect(context.output).toMatchSnapshot()
@ -38,10 +43,11 @@ test('tap-flat reporter', async () => {
// Arrange
const reporter = new TapFlatReporter()
const context = getContext()
const testModules = getTestModules()
// Act
reporter.onInit(context.vitest)
await reporter.onFinished(files)
reporter.onTestRunEnd(testModules)
// Assert
expect(context.output).toMatchSnapshot()
@ -54,7 +60,7 @@ test('JUnit reporter', async () => {
// Act
await reporter.onInit(context.vitest)
await reporter.onFinished([])
await reporter.onTestRunEnd([])
// Assert
expect(context.output).toMatchSnapshot()
@ -64,11 +70,12 @@ test('JUnit reporter without classname', async () => {
// Arrange
const reporter = new JUnitReporter({})
const context = getContext()
const testModules = getTestModules(passedFiles)
// Act
await reporter.onInit(context.vitest)
await reporter.onFinished(passedFiles)
await reporter.onTestRunEnd(testModules)
// Assert
expect(context.output).toMatchSnapshot()
@ -78,11 +85,12 @@ test('JUnit reporter with custom string classname', async () => {
// Arrange
const reporter = new JUnitReporter({ classnameTemplate: 'my-custom-classname' })
const context = getContext()
const testModules = getTestModules(passedFiles)
// Act
await reporter.onInit(context.vitest)
await reporter.onFinished(passedFiles)
await reporter.onTestRunEnd(testModules)
// Assert
expect(context.output).toMatchSnapshot()
@ -92,11 +100,12 @@ test('JUnit reporter with custom function classnameTemplate', async () => {
// Arrange
const reporter = new JUnitReporter({ classnameTemplate: task => `filename:${task.filename} - filepath:${task.filepath}` })
const context = getContext()
const testModules = getTestModules(passedFiles)
// Act
await reporter.onInit(context.vitest)
await reporter.onFinished(passedFiles)
await reporter.onTestRunEnd(testModules)
// Assert
expect(context.output).toMatchSnapshot()
@ -105,11 +114,12 @@ test('JUnit reporter with custom string classnameTemplate', async () => {
// Arrange
const reporter = new JUnitReporter({ classnameTemplate: `filename:{filename} - filepath:{filepath}` })
const context = getContext()
const testModules = getTestModules(passedFiles)
// Act
await reporter.onInit(context.vitest)
await reporter.onFinished(passedFiles)
await reporter.onTestRunEnd(testModules)
// Assert
expect(context.output).toMatchSnapshot()
@ -123,7 +133,7 @@ test('JUnit reporter (no outputFile entry)', async () => {
// Act
await reporter.onInit(context.vitest)
await reporter.onFinished([])
await reporter.onTestRunEnd([])
// Assert
expect(context.output).toMatchSnapshot()
@ -138,7 +148,7 @@ test('JUnit reporter with outputFile', async () => {
// Act
await reporter.onInit(context.vitest)
await reporter.onFinished([])
await reporter.onTestRunEnd([])
// Assert
expect(normalizeCwd(context.output)).toMatchSnapshot()
@ -160,7 +170,7 @@ test('JUnit reporter with outputFile object', async () => {
// Act
await reporter.onInit(context.vitest)
await reporter.onFinished([])
await reporter.onTestRunEnd([])
// Assert
expect(normalizeCwd(context.output)).toMatchSnapshot()
@ -181,7 +191,7 @@ test('JUnit reporter with outputFile in non-existing directory', async () => {
// Act
await reporter.onInit(context.vitest)
await reporter.onFinished([])
await reporter.onTestRunEnd([])
// Assert
expect(normalizeCwd(context.output)).toMatchSnapshot()
@ -204,7 +214,7 @@ test('JUnit reporter with outputFile object in non-existing directory', async ()
// Act
await reporter.onInit(context.vitest)
await reporter.onFinished([])
await reporter.onTestRunEnd([])
// Assert
expect(normalizeCwd(context.output)).toMatchSnapshot()
@ -219,12 +229,13 @@ test('json reporter', async () => {
// Arrange
const reporter = new JsonReporter({})
const context = getContext()
const testModules = getTestModules()
vi.setSystemTime(1642587001759)
// Act
reporter.onInit(context.vitest)
await reporter.onFinished(files)
await reporter.onTestRunEnd(testModules)
// Assert
expect(JSON.parse(context.output)).toMatchSnapshot()
@ -235,12 +246,13 @@ test('json reporter (no outputFile entry)', async () => {
const reporter = new JsonReporter({})
const context = getContext()
context.vitest.config.outputFile = {}
const testModules = getTestModules()
vi.setSystemTime(1642587001759)
// Act
reporter.onInit(context.vitest)
await reporter.onFinished(files)
await reporter.onTestRunEnd(testModules)
// Assert
expect(JSON.parse(context.output)).toMatchSnapshot()
@ -252,12 +264,13 @@ test('json reporter with outputFile', async () => {
const outputFile = resolve('report.json')
const context = getContext()
context.vitest.config.outputFile = outputFile
const testModules = getTestModules()
vi.setSystemTime(1642587001759)
// Act
reporter.onInit(context.vitest)
await reporter.onFinished(files)
await reporter.onTestRunEnd(testModules)
// Assert
expect(normalizeCwd(context.output)).toMatchSnapshot()
@ -276,12 +289,13 @@ test('json reporter with outputFile object', async () => {
context.vitest.config.outputFile = {
json: outputFile,
}
const testModules = getTestModules()
vi.setSystemTime(1642587001759)
// Act
reporter.onInit(context.vitest)
await reporter.onFinished(files)
await reporter.onTestRunEnd(testModules)
// Assert
expect(normalizeCwd(context.output)).toMatchSnapshot()
@ -299,12 +313,13 @@ test('json reporter with outputFile in non-existing directory', async () => {
const outputFile = `${rootDirectory}/deeply/nested/report.json`
const context = getContext()
context.vitest.config.outputFile = outputFile
const testModules = getTestModules()
vi.setSystemTime(1642587001759)
// Act
reporter.onInit(context.vitest)
await reporter.onFinished(files)
await reporter.onTestRunEnd(testModules)
// Assert
expect(normalizeCwd(context.output)).toMatchSnapshot()
@ -324,12 +339,13 @@ test('json reporter with outputFile object in non-existing directory', async ()
context.vitest.config.outputFile = {
json: outputFile,
}
const testModules = getTestModules()
vi.setSystemTime(1642587001759)
// Act
reporter.onInit(context.vitest)
await reporter.onFinished(files)
await reporter.onTestRunEnd(testModules)
// Assert
expect(normalizeCwd(context.output)).toMatchSnapshot()

View File

@ -101,5 +101,4 @@ Log from failed suite`,
class LogReporter extends DefaultReporter {
isTTY = true
onTaskUpdate() {}
}

View File

@ -2,6 +2,44 @@ import { expect, test } from 'vitest'
import { runVitest } from '../../test-utils'
test('handle custom error without name', async () => {
let { stdout, stderr } = await runVitest({ reporters: 'tap', root: './fixtures/custom-error' })
stdout = stdout.replaceAll(/time=(\S*)/g, 'time=[...]') // strip non-deterministic output
expect(stdout).toMatchInlineSnapshot(`
"TAP version 13
1..1
not ok 1 - basic.test.ts # time=[...] {
1..4
not ok 1 - no name object # time=[...]
---
error:
name: "Unknown Error"
message: "undefined"
...
not ok 2 - string # time=[...]
---
error:
name: "Unknown Error"
message: "hi"
...
not ok 3 - number # time=[...]
---
error:
name: "Unknown Error"
message: "1234"
...
not ok 4 - number name object # time=[...]
---
error:
name: "1234"
message: "undefined"
...
}
"
`)
expect(stderr).toBe('')
})
test('tap-flat handles custom error without name', async () => {
let { stdout, stderr } = await runVitest({ reporters: 'tap-flat', root: './fixtures/custom-error' })
stdout = stdout.replaceAll(/time=(\S*)/g, 'time=[...]') // strip non-deterministic output
expect(stdout).toMatchInlineSnapshot(`