diff --git a/docs/advanced/api/test-case.md b/docs/advanced/api/test-case.md
index 4071209159a1..40fae5f57bae 100644
--- a/docs/advanced/api/test-case.md
+++ b/docs/advanced/api/test-case.md
@@ -125,7 +125,7 @@ Checks if the test did not fail the suite. If the test is not finished yet or wa
function meta(): TaskMeta
```
-Custom metadata that was attached to the test during its execution. The meta can be attached by assigning a property to the `ctx.task.meta` object during a test run:
+Custom [metadata](/advanced/metadata) that was attached to the test during its execution. The meta can be attached by assigning a property to the `ctx.task.meta` object during a test run:
```ts {3,6}
import { test } from 'vitest'
diff --git a/docs/advanced/api/test-module.md b/docs/advanced/api/test-module.md
index 9a39dc0e460c..5e5138018cc5 100644
--- a/docs/advanced/api/test-module.md
+++ b/docs/advanced/api/test-module.md
@@ -32,6 +32,33 @@ function state(): TestModuleState
Works the same way as [`testSuite.state()`](/advanced/api/test-suite#state), but can also return `queued` if module wasn't executed yet.
+## meta 3.1.0 {#meta}
+
+```ts
+function meta(): TaskMeta
+```
+
+Custom [metadata](/advanced/metadata) that was attached to the module during its execution or collection. The meta can be attached by assigning a property to the `task.meta` object during a test run:
+
+```ts {5,10}
+import { test } from 'vitest'
+
+describe('the validation works correctly', (task) => {
+ // assign "decorated" during collection
+ task.file.meta.decorated = false
+
+ test('some test', ({ task }) => {
+ // assign "decorated" during test run, it will be available
+ // only in onTestCaseReady hook
+ task.file.meta.decorated = false
+ })
+})
+```
+
+:::tip
+If metadata was attached during collection (outside of the `test` function), then it will be available in [`onTestModuleCollected`](./reporters#ontestmodulecollected) hook in the custom reporter.
+:::
+
## diagnostic
```ts
@@ -63,5 +90,10 @@ interface ModuleDiagnostic {
* Accumulated duration of all tests and hooks in the module.
*/
readonly duration: number
+ /**
+ * The amount of memory used by the module in bytes.
+ * This value is only available if the test was executed with `logHeapUsage` flag.
+ */
+ readonly heap: number | undefined
}
```
diff --git a/docs/advanced/api/test-suite.md b/docs/advanced/api/test-suite.md
index b0638ec7ef8f..bc3e2ab4de8b 100644
--- a/docs/advanced/api/test-suite.md
+++ b/docs/advanced/api/test-suite.md
@@ -190,3 +190,30 @@ describe('collection failed', () => {
::: warning
Note that errors are serialized into simple objects: `instanceof Error` will always return `false`.
:::
+
+## meta 3.1.0 {#meta}
+
+```ts
+function meta(): TaskMeta
+```
+
+Custom [metadata](/advanced/metadata) that was attached to the suite during its execution or collection. The meta can be attached by assigning a property to the `task.meta` object during a test run:
+
+```ts {5,10}
+import { test } from 'vitest'
+
+describe('the validation works correctly', (task) => {
+ // assign "decorated" during collection
+ task.meta.decorated = false
+
+ test('some test', ({ task }) => {
+ // assign "decorated" during test run, it will be available
+ // only in onTestCaseReady hook
+ task.suite.meta.decorated = false
+ })
+})
+```
+
+:::tip
+If metadata was attached during collection (outside of the `test` function), then it will be available in [`onTestModuleCollected`](./reporters#ontestmodulecollected) hook in the custom reporter.
+:::
diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts
index cbaa082b3ad9..bc55de624f19 100644
--- a/packages/vitest/src/node/core.ts
+++ b/packages/vitest/src/node/core.ts
@@ -502,25 +502,7 @@ export class Vitest {
await this._testRun.start(specifications).catch(noop)
for (const file of files) {
- const project = this.getProjectByName(file.projectName || '')
- await this._testRun.enqueued(project, file).catch(noop)
- await this._testRun.collected(project, [file]).catch(noop)
-
- const logs: UserConsoleLog[] = []
-
- const { packs, events } = convertTasksToEvents(file, (task) => {
- if (task.logs) {
- logs.push(...task.logs)
- }
- })
-
- logs.sort((log1, log2) => log1.time - log2.time)
-
- for (const log of logs) {
- await this._testRun.log(log).catch(noop)
- }
-
- await this._testRun.updated(packs, events).catch(noop)
+ await this._reportFileTask(file)
}
if (hasFailed(files)) {
@@ -538,6 +520,29 @@ export class Vitest {
}
}
+ /** @internal */
+ public async _reportFileTask(file: File): Promise {
+ const project = this.getProjectByName(file.projectName || '')
+ await this._testRun.enqueued(project, file).catch(noop)
+ await this._testRun.collected(project, [file]).catch(noop)
+
+ const logs: UserConsoleLog[] = []
+
+ const { packs, events } = convertTasksToEvents(file, (task) => {
+ if (task.logs) {
+ logs.push(...task.logs)
+ }
+ })
+
+ logs.sort((log1, log2) => log1.time - log2.time)
+
+ for (const log of logs) {
+ await this._testRun.log(log).catch(noop)
+ }
+
+ await this._testRun.updated(packs, events).catch(noop)
+ }
+
async collect(filters?: string[]): Promise {
const files = await this.specifications.getRelevantTestSpecifications(filters)
diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts
index 2aa3e10e61c8..15acfc28a32b 100644
--- a/packages/vitest/src/node/reporters/base.ts
+++ b/packages/vitest/src/node/reporters/base.ts
@@ -1,8 +1,8 @@
-import type { File, Task, TaskResultPack } from '@vitest/runner'
+import type { File, Task } from '@vitest/runner'
import type { ErrorWithDiff, UserConsoleLog } from '../../types/general'
import type { Vitest } from '../core'
import type { Reporter } from '../types/reporter'
-import type { TestCase, TestModule, TestResult, TestSuite } from './reported-tasks'
+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'
import { toArray } from '@vitest/utils'
@@ -24,7 +24,7 @@ export abstract class BaseReporter implements Reporter {
start = 0
end = 0
watchFilters?: string[]
- failedUnwatchedFiles: Task[] = []
+ failedUnwatchedFiles: TestModule[] = []
isTTY: boolean
ctx: Vitest = undefined!
renderSucceed = false
@@ -83,6 +83,8 @@ export abstract class BaseReporter implements Reporter {
if (testModule.state() === 'failed') {
this.logFailedTask(testModule.task)
}
+
+ this.printTestModule(testModule)
}
private logFailedTask(task: Task) {
@@ -93,121 +95,153 @@ export abstract class BaseReporter implements Reporter {
}
}
- onTaskUpdate(packs: TaskResultPack[]): void {
- for (const pack of packs) {
- const task = this.ctx.state.idMap.get(pack[0])
-
- if (task) {
- this.printTask(task)
- }
- }
- }
-
- /**
- * Callback invoked with a single `Task` from `onTaskUpdate`
- */
- protected printTask(task: Task): void {
- if (
- !('filepath' in task)
- || !task.result?.state
- || task.result?.state === 'run'
- || task.result?.state === 'queued') {
+ protected printTestModule(testModule: TestModule): void {
+ const moduleState = testModule.state()
+ if (moduleState === 'queued' || moduleState === 'pending') {
return
}
- const suites = getSuites(task)
- const allTests = getTests(task)
- const failed = allTests.filter(t => t.result?.state === 'fail')
- const skipped = allTests.filter(t => t.mode === 'skip' || t.mode === 'todo')
+ let testsCount = 0
+ let failedCount = 0
+ let skippedCount = 0
- let state = c.dim(`${allTests.length} test${allTests.length > 1 ? 's' : ''}`)
+ // delaying logs to calculate the test stats first
+ // which minimizes the amount of for loops
+ const logs: string[] = []
+ const originalLog = this.log.bind(this)
+ this.log = (msg: string) => logs.push(msg)
- if (failed.length) {
- state += c.dim(' | ') + c.red(`${failed.length} failed`)
+ const visit = (suiteState: TestSuiteState, children: TestCollection) => {
+ for (const child of children) {
+ if (child.type === 'suite') {
+ const suiteState = child.state()
+
+ // Skipped suites are hidden when --hideSkippedTests, print otherwise
+ if (!this.ctx.config.hideSkippedTests || suiteState !== 'skipped') {
+ this.printTestSuite(child)
+ }
+
+ visit(suiteState, child.children)
+ }
+ else {
+ const testResult = child.result()
+
+ testsCount++
+ if (testResult.state === 'failed') {
+ failedCount++
+ }
+ else if (testResult.state === 'skipped') {
+ skippedCount++
+ }
+
+ if (this.ctx.config.hideSkippedTests && suiteState === 'skipped') {
+ // Skipped suites are hidden when --hideSkippedTests
+ continue
+ }
+
+ this.printTestCase(moduleState, child)
+ }
+ }
}
- if (skipped.length) {
- state += c.dim(' | ') + c.yellow(`${skipped.length} skipped`)
+ try {
+ visit(moduleState, testModule.children)
+ }
+ finally {
+ this.log = originalLog
}
- let suffix = c.dim('(') + state + c.dim(')') + this.getDurationPrefix(task)
+ this.log(this.getModuleLog(testModule, {
+ tests: testsCount,
+ failed: failedCount,
+ skipped: skippedCount,
+ }))
+ logs.forEach(log => this.log(log))
+ }
- if (this.ctx.config.logHeapUsage && task.result.heap != null) {
- suffix += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`)
- }
+ protected printTestCase(moduleState: TestModuleState, test: TestCase): void {
+ const testResult = test.result()
- let title = getStateSymbol(task)
+ const { duration, retryCount, repeatCount } = test.diagnostic() || {}
+ const padding = this.getTestIndentation(test.task)
+ let suffix = this.getDurationPrefix(test.task)
- if (task.meta.typecheck) {
- title += ` ${c.bgBlue(c.bold(' TS '))}`
+ if (retryCount != null && retryCount > 0) {
+ suffix += c.yellow(` (retry x${retryCount})`)
}
- if (task.projectName) {
- title += ` ${formatProjectName(task.projectName, '')}`
+ if (repeatCount != null && repeatCount > 0) {
+ suffix += c.yellow(` (repeat x${repeatCount})`)
}
- this.log(` ${title} ${task.name} ${suffix}`)
+ if (testResult.state === 'failed') {
+ this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, c.dim(' > '))}`) + suffix)
- for (const suite of suites) {
- if (this.ctx.config.hideSkippedTests && (suite.mode === 'skip' || suite.result?.state === 'skip')) {
- // Skipped suites are hidden when --hideSkippedTests
- continue
- }
+ // print short errors, full errors will be at the end in summary
+ testResult.errors.forEach((error) => {
+ const message = this.formatShortError(error)
- const tests = suite.tasks.filter(task => task.type === 'test')
+ if (message) {
+ this.log(c.red(` ${padding}${message}`))
+ }
+ })
+ }
- if (!('filepath' in suite)) {
- this.printSuite(suite)
- }
+ // also print slow tests
+ else if (duration && duration > this.ctx.config.slowTestThreshold) {
+ this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, c.dim(' > '))} ${suffix}`)
+ }
- for (const test of tests) {
- const { duration, retryCount, repeatCount } = test.result || {}
- const padding = this.getTestIndentation(test)
- let suffix = this.getDurationPrefix(test)
+ else if (this.ctx.config.hideSkippedTests && (testResult.state === 'skipped')) {
+ // Skipped tests are hidden when --hideSkippedTests
+ }
- if (retryCount != null && retryCount > 0) {
- suffix += c.yellow(` (retry x${retryCount})`)
- }
+ // also print skipped tests that have notes
+ else if (testResult.state === 'skipped' && testResult.note) {
+ this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(' > '))}${c.dim(c.gray(` [${testResult.note}]`))}`)
+ }
- if (repeatCount != null && repeatCount > 0) {
- suffix += c.yellow(` (repeat x${repeatCount})`)
- }
+ else if (this.renderSucceed || moduleState === 'failed') {
+ this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(' > '))}${suffix}`)
+ }
+ }
- if (test.result?.state === 'fail') {
- this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test, c.dim(' > '))}`) + suffix)
+ private getModuleLog(testModule: TestModule, counts: {
+ tests: number
+ failed: number
+ skipped: number
+ }): string {
+ let state = c.dim(`${counts.tests} test${counts.tests > 1 ? 's' : ''}`)
- // print short errors, full errors will be at the end in summary
- test.result?.errors?.forEach((error) => {
- const message = this.formatShortError(error)
+ if (counts.failed) {
+ state += c.dim(' | ') + c.red(`${counts.failed} failed`)
+ }
- if (message) {
- this.log(c.red(` ${padding}${message}`))
- }
- })
- }
+ if (counts.skipped) {
+ state += c.dim(' | ') + c.yellow(`${counts.skipped} skipped`)
+ }
- // also print slow tests
- else if (duration && duration > this.ctx.config.slowTestThreshold) {
- this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test, c.dim(' > '))} ${suffix}`)
- }
+ let suffix = c.dim('(') + state + c.dim(')') + this.getDurationPrefix(testModule.task)
- else if (this.ctx.config.hideSkippedTests && (test.mode === 'skip' || test.result?.state === 'skip')) {
- // Skipped tests are hidden when --hideSkippedTests
- }
+ const diagnostic = testModule.diagnostic()
+ if (diagnostic.heap != null) {
+ suffix += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`)
+ }
- // also print skipped tests that have notes
- else if (test.result?.state === 'skip' && test.result.note) {
- this.log(` ${padding}${getStateSymbol(test)} ${this.getTestName(test)}${c.dim(c.gray(` [${test.result.note}]`))}`)
- }
+ let title = getStateSymbol(testModule.task)
- else if (this.renderSucceed || failed.length > 0) {
- this.log(` ${padding}${getStateSymbol(test)} ${this.getTestName(test, c.dim(' > '))}${suffix}`)
- }
- }
+ if (testModule.meta().typecheck) {
+ title += ` ${c.bgBlue(c.bold(' TS '))}`
}
+
+ if (testModule.project.name) {
+ title += ` ${formatProjectName(testModule.project.name, '')}`
+ }
+
+ return ` ${title} ${testModule.task.name} ${suffix}`
}
- protected printSuite(_task: Task): void {
+ protected printTestSuite(_suite: TestSuite): void {
// Suite name is included in getTestName by default
}
@@ -262,8 +296,8 @@ export abstract class BaseReporter implements Reporter {
onWatcherRerun(files: string[], trigger?: string): void {
this.watchFilters = files
- this.failedUnwatchedFiles = this.ctx.state.getFiles().filter(file =>
- !files.includes(file.filepath) && hasFailed(file),
+ this.failedUnwatchedFiles = this.ctx.state.getTestModules().filter(testModule =>
+ !files.includes(testModule.task.filepath) && testModule.state() === 'failed',
)
// Update re-run count for each file
@@ -296,8 +330,8 @@ export abstract class BaseReporter implements Reporter {
this.log('')
- for (const task of this.failedUnwatchedFiles) {
- this.printTask(task)
+ for (const testModule of this.failedUnwatchedFiles) {
+ this.printTestModule(testModule)
}
this._timeStart = formatTimeString(new Date())
@@ -405,7 +439,7 @@ export abstract class BaseReporter implements Reporter {
this.log()
const affectedFiles = [
- ...this.failedUnwatchedFiles,
+ ...this.failedUnwatchedFiles.map(m => m.task),
...files,
]
const tests = getTests(affectedFiles)
diff --git a/packages/vitest/src/node/reporters/benchmark/reporter.ts b/packages/vitest/src/node/reporters/benchmark/reporter.ts
index 363c4e27a9f9..c32c56c9cfd8 100644
--- a/packages/vitest/src/node/reporters/benchmark/reporter.ts
+++ b/packages/vitest/src/node/reporters/benchmark/reporter.ts
@@ -1,5 +1,6 @@
-import type { File, Task, TaskResultPack } from '@vitest/runner'
+import type { File, TaskResultPack } from '@vitest/runner'
import type { Vitest } from '../../core'
+import type { TestModule, TestSuite } from '../reported-tasks'
import fs from 'node:fs'
import { getFullName } from '@vitest/runner/utils'
import * as pathe from 'pathe'
@@ -43,20 +44,28 @@ export class BenchmarkReporter extends DefaultReporter {
})
}
}
+ }
+
+ onTestSuiteResult(testSuite: TestSuite): void {
+ super.onTestSuiteResult(testSuite)
+ this.printSuiteTable(testSuite)
+ }
- super.onTaskUpdate(packs)
+ protected printTestModule(testModule: TestModule): void {
+ this.printSuiteTable(testModule)
}
- printTask(task: Task): void {
- if (task?.type !== 'suite' || !task.result?.state || task.result?.state === 'run' || task.result?.state === 'queued') {
+ private printSuiteTable(testTask: TestModule | TestSuite): void {
+ const state = testTask.state()
+ if (state === 'pending' || state === 'queued') {
return
}
- const benches = task.tasks.filter(t => t.meta.benchmark)
- const duration = task.result.duration
+ const benches = testTask.task.tasks.filter(t => t.meta.benchmark)
+ const duration = testTask.task.result?.duration || 0
if (benches.length > 0 && benches.every(t => t.result?.state !== 'run' && t.result?.state !== 'queued')) {
- let title = `\n ${getStateSymbol(task)} ${formatProjectName(task.file.projectName)}${getFullName(task, c.dim(' > '))}`
+ let title = `\n ${getStateSymbol(testTask.task)} ${formatProjectName(testTask.project.name)}${getFullName(testTask.task, c.dim(' > '))}`
if (duration != null && duration > this.ctx.config.slowTestThreshold) {
title += c.yellow(` ${Math.round(duration)}${c.dim('ms')}`)
diff --git a/packages/vitest/src/node/reporters/dot.ts b/packages/vitest/src/node/reporters/dot.ts
index e8de4e222e7e..75677d9e470e 100644
--- a/packages/vitest/src/node/reporters/dot.ts
+++ b/packages/vitest/src/node/reporters/dot.ts
@@ -1,4 +1,4 @@
-import type { File, Task, Test } from '@vitest/runner'
+import type { File, Test } from '@vitest/runner'
import type { Vitest } from '../core'
import type { TestCase, TestModule } from './reported-tasks'
import c from 'tinyrainbow'
@@ -30,9 +30,9 @@ export class DotReporter extends BaseReporter {
}
}
- printTask(task: Task): void {
+ printTestModule(testModule: TestModule): void {
if (!this.isTTY) {
- super.printTask(task)
+ super.printTestModule(testModule)
}
}
diff --git a/packages/vitest/src/node/reporters/reported-tasks.ts b/packages/vitest/src/node/reporters/reported-tasks.ts
index afb52eeb7750..1966e4f532bd 100644
--- a/packages/vitest/src/node/reporters/reported-tasks.ts
+++ b/packages/vitest/src/node/reporters/reported-tasks.ts
@@ -52,6 +52,13 @@ class ReportedTaskImplementation {
return !result || result.state !== 'fail'
}
+ /**
+ * Custom metadata that was attached to the test during its execution.
+ */
+ public meta(): TaskMeta {
+ return this.task.meta
+ }
+
/**
* Creates a new reported task instance and stores it in the project's state for future use.
* @internal
@@ -170,13 +177,6 @@ export class TestCase extends ReportedTaskImplementation {
} satisfies TestResultFailed
}
- /**
- * Custom metadata that was attached to the test during its execution.
- */
- public meta(): TaskMeta {
- return this.task.meta
- }
-
/**
* Useful information about the test like duration, memory usage, etc.
* Diagnostic is only available after the test has finished.
@@ -387,6 +387,11 @@ export class TestSuite extends SuiteImplementation {
*/
declare public ok: () => boolean
+ /**
+ * The meta information attached to the suite during its collection or execution.
+ */
+ declare public meta: () => TaskMeta
+
/**
* Checks the running state of the suite.
*/
@@ -446,6 +451,11 @@ export class TestModule extends SuiteImplementation {
*/
declare public ok: () => boolean
+ /**
+ * The meta information attached to the module during its collection or execution.
+ */
+ declare public meta: () => TaskMeta
+
/**
* Useful information about the module like duration, memory usage, etc.
* If the module was not executed yet, all diagnostic values will return `0`.
@@ -456,12 +466,14 @@ export class TestModule extends SuiteImplementation {
const prepareDuration = this.task.prepareDuration || 0
const environmentSetupDuration = this.task.environmentLoad || 0
const duration = this.task.result?.duration || 0
+ const heap = this.task.result?.heap
return {
environmentSetupDuration,
prepareDuration,
collectDuration,
setupDuration,
duration,
+ heap,
}
}
}
@@ -609,6 +621,11 @@ export interface ModuleDiagnostic {
* Accumulated duration of all tests and hooks in the module.
*/
readonly duration: number
+ /**
+ * The amount of memory used by the test module in bytes.
+ * This value is only available if the test was executed with `logHeapUsage` flag.
+ */
+ readonly heap: number | undefined
}
function storeTask(
diff --git a/packages/vitest/src/node/reporters/verbose.ts b/packages/vitest/src/node/reporters/verbose.ts
index 522011b0349f..fb4ae262086f 100644
--- a/packages/vitest/src/node/reporters/verbose.ts
+++ b/packages/vitest/src/node/reporters/verbose.ts
@@ -1,5 +1,6 @@
import type { Task } from '@vitest/runner'
-import { getFullName, getTests } from '@vitest/runner/utils'
+import type { TestCase, TestModule, TestSuite } from './reported-tasks'
+import { getFullName } from '@vitest/runner/utils'
import c from 'tinyrainbow'
import { DefaultReporter } from './default'
import { F_RIGHT } from './renderers/figures'
@@ -9,45 +10,62 @@ export class VerboseReporter extends DefaultReporter {
protected verbose = true
renderSucceed = true
- printTask(task: Task): void {
+ printTestModule(module: TestModule): void {
+ // still print the test module in TTY,
+ // but don't print it in the CLI because we
+ // print all the tests when they finish
+ // instead of printing them when the test file finishes
if (this.isTTY) {
- return super.printTask(task)
+ return super.printTestModule(module)
}
+ }
+
+ onTestCaseResult(test: TestCase): void {
+ super.onTestCaseResult(test)
+
+ // don't print tests in TTY as they go, only print them
+ // in the CLI when they finish
+ if (this.isTTY) {
+ return
+ }
+
+ const testResult = test.result()
- if (task.type !== 'test' || !task.result?.state || task.result?.state === 'run' || task.result?.state === 'queued') {
+ if (this.ctx.config.hideSkippedTests && testResult.state === 'skipped') {
return
}
- let title = ` ${getStateSymbol(task)} `
+ let title = ` ${getStateSymbol(test.task)} `
- if (task.file.projectName) {
- title += formatProjectName(task.file.projectName)
+ if (test.project.name) {
+ title += formatProjectName(test.project.name)
}
- title += getFullName(task, c.dim(' > '))
- title += super.getDurationPrefix(task)
+ title += getFullName(test.task, c.dim(' > '))
+ title += this.getDurationPrefix(test.task)
- if (this.ctx.config.logHeapUsage && task.result.heap != null) {
- title += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`)
+ const diagnostic = test.diagnostic()
+ if (diagnostic?.heap != null) {
+ title += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`)
}
- if (task.result?.note) {
- title += c.dim(c.gray(` [${task.result.note}]`))
+ if (testResult.state === 'skipped' && testResult.note) {
+ title += c.dim(c.gray(` [${testResult.note}]`))
}
- this.ctx.logger.log(title)
+ this.log(title)
- if (task.result.state === 'fail') {
- task.result.errors?.forEach(error => this.log(c.red(` ${F_RIGHT} ${error?.message}`)))
+ if (testResult.state === 'failed') {
+ testResult.errors.forEach(error => this.log(c.red(` ${F_RIGHT} ${error?.message}`)))
}
}
- protected printSuite(task: Task): void {
- const indentation = ' '.repeat(getIndentation(task))
- const tests = getTests(task)
- const state = getStateSymbol(task)
+ protected printTestSuite(testSuite: TestSuite): void {
+ const indentation = ' '.repeat(getIndentation(testSuite.task))
+ const tests = Array.from(testSuite.children.allTests())
+ const state = getStateSymbol(testSuite.task)
- this.log(` ${indentation}${state} ${task.name} ${c.dim(`(${tests.length})`)}`)
+ this.log(` ${indentation}${state} ${testSuite.name} ${c.dim(`(${tests.length})`)}`)
}
protected getTestName(test: Task): string {
diff --git a/packages/vitest/src/public/index.ts b/packages/vitest/src/public/index.ts
index 56d2d302f354..1612dfbc4041 100644
--- a/packages/vitest/src/public/index.ts
+++ b/packages/vitest/src/public/index.ts
@@ -274,6 +274,7 @@ export type {
Custom as RunnerCustomCase,
Task as RunnerTask,
TaskBase as RunnerTaskBase,
+ TaskEventPack as RunnerTaskEventPack,
TaskResult as RunnerTaskResult,
TaskResultPack as RunnerTaskResultPack,
Test as RunnerTestCase,
diff --git a/test/cli/fixtures/custom-pool/pool/custom-pool.ts b/test/cli/fixtures/custom-pool/pool/custom-pool.ts
index b85dc0e97a93..391a4e4ac689 100644
--- a/test/cli/fixtures/custom-pool/pool/custom-pool.ts
+++ b/test/cli/fixtures/custom-pool/pool/custom-pool.ts
@@ -1,7 +1,14 @@
-import type { RunnerTestFile, RunnerTestCase } from 'vitest'
+import type {
+ RunnerTestFile,
+ RunnerTestCase,
+ RunnerTestSuite,
+ RunnerTaskResultPack,
+ RunnerTaskEventPack,
+ RunnerTask
+} from 'vitest'
import type { ProcessPool, Vitest } from 'vitest/node'
import { createMethodsRPC } from 'vitest/node'
-import { getTasks, generateFileHash } from '@vitest/runner/utils'
+import { generateFileHash } from '@vitest/runner/utils'
import { normalize, relative } from 'pathe'
export default (vitest: Vitest): ProcessPool => {
@@ -16,7 +23,6 @@ export default (vitest: Vitest): ProcessPool => {
vitest.logger.console.warn('[pool] array option', options.array)
for (const [project, file] of specs) {
vitest.state.clearFiles(project)
- const methods = createMethodsRPC(project)
vitest.logger.console.warn('[pool] running tests for', project.name, 'in', normalize(file).toLowerCase().replace(normalize(process.cwd()).toLowerCase(), ''))
const path = relative(project.config.root, file)
const taskFile: RunnerTestFile = {
@@ -49,12 +55,7 @@ export default (vitest: Vitest): ProcessPool => {
},
}
taskFile.tasks.push(taskTest)
- await methods.onCollected([taskFile])
- await methods.onTaskUpdate(getTasks(taskFile).map(task => [
- task.id,
- task.result,
- task.meta,
- ]), [])
+ await vitest._reportFileTask(taskFile)
}
},
close() {
diff --git a/test/config/test/bail.test.ts b/test/config/test/bail.test.ts
index 4b9d25e5d448..79b8a671dce9 100644
--- a/test/config/test/bail.test.ts
+++ b/test/config/test/bail.test.ts
@@ -87,17 +87,21 @@ for (const config of configs) {
if (browser) {
expect(stdout).toMatch(`✓ |chromium| test/first.test.ts > 1 - first.test.ts - this should pass`)
expect(stdout).toMatch(`× |chromium| test/first.test.ts > 2 - first.test.ts - this should fail`)
+
+ expect(stdout).not.toMatch('✓ |chromium| test/first.test.ts > 3 - first.test.ts - this should be skipped')
+ expect(stdout).not.toMatch('✓ |chromium| test/second.test.ts > 1 - second.test.ts - this should be skipped')
+ expect(stdout).not.toMatch('✓ |chromium| test/second.test.ts > 2 - second.test.ts - this should be skipped')
+ expect(stdout).not.toMatch('✓ |chromium| test/second.test.ts > 3 - second.test.ts - this should be skipped')
}
else {
expect(stdout).toMatch('✓ test/first.test.ts > 1 - first.test.ts - this should pass')
expect(stdout).toMatch('× test/first.test.ts > 2 - first.test.ts - this should fail')
- }
- // Cancelled tests should not be run
- expect(stdout).not.toMatch('test/first.test.ts > 3 - first.test.ts - this should be skipped')
- expect(stdout).not.toMatch('test/second.test.ts > 1 - second.test.ts - this should be skipped')
- expect(stdout).not.toMatch('test/second.test.ts > 2 - second.test.ts - this should be skipped')
- expect(stdout).not.toMatch('test/second.test.ts > 3 - second.test.ts - this should be skipped')
+ expect(stdout).not.toMatch('✓ test/first.test.ts > 3 - first.test.ts - this should be skipped')
+ expect(stdout).not.toMatch('✓ test/second.test.ts > 1 - second.test.ts - this should be skipped')
+ expect(stdout).not.toMatch('✓ test/second.test.ts > 2 - second.test.ts - this should be skipped')
+ expect(stdout).not.toMatch('✓ test/second.test.ts > 3 - second.test.ts - this should be skipped')
+ }
},
)
}
diff --git a/test/reporters/tests/default.test.ts b/test/reporters/tests/default.test.ts
index ff947520f60b..be9cdfecc823 100644
--- a/test/reporters/tests/default.test.ts
+++ b/test/reporters/tests/default.test.ts
@@ -138,8 +138,8 @@ describe('default reporter', async () => {
expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(`
"✓ fixtures/pass-and-skip-test-suites.test.ts (4 tests | 2 skipped) [...]ms
✓ passing test #1 [...]ms
- ↓ skipped test #1
✓ passing suite > passing test #2 [...]ms
+ ↓ skipped test #1
↓ skipped suite > skipped test #2"
`)
})
diff --git a/test/reporters/tests/verbose.test.ts b/test/reporters/tests/verbose.test.ts
index a1982f15d82e..9fa7b59e246e 100644
--- a/test/reporters/tests/verbose.test.ts
+++ b/test/reporters/tests/verbose.test.ts
@@ -35,9 +35,9 @@ test('prints skipped tests by default', async () => {
expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(`
"✓ fixtures/pass-and-skip-test-suites.test.ts (4 tests | 2 skipped) [...]ms
✓ passing test #1 [...]ms
- ↓ skipped test #1
✓ passing suite (1)
✓ passing test #2 [...]ms
+ ↓ skipped test #1
↓ skipped suite (1)
↓ skipped test #2"
`)
@@ -149,6 +149,44 @@ test('does not render tree when in non-TTY', async () => {
},
})
+ expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(`
+ "✓ fixtures/verbose/example-1.test.ts > test pass in root [...]ms
+ ↓ fixtures/verbose/example-1.test.ts > test skip in root
+ ✓ fixtures/verbose/example-1.test.ts > suite in root > test pass in 1. suite #1 [...]ms
+ ✓ fixtures/verbose/example-1.test.ts > suite in root > test pass in 1. suite #2 [...]ms
+ ✓ fixtures/verbose/example-1.test.ts > suite in root > suite in suite > test pass in nested suite #1 [...]ms
+ ✓ fixtures/verbose/example-1.test.ts > suite in root > suite in suite > test pass in nested suite #2 [...]ms
+ × fixtures/verbose/example-1.test.ts > suite in root > suite in suite > suite in nested suite > test failure in 2x nested suite [...]ms
+ → expected 'should fail' to be 'as expected' // Object.is equality
+ ↓ fixtures/verbose/example-1.test.ts > suite skip in root > test 1.3
+ ↓ fixtures/verbose/example-1.test.ts > suite skip in root > suite in suite > test in nested suite
+ ↓ fixtures/verbose/example-1.test.ts > suite skip in root > suite in suite > test failure in nested suite of skipped suite
+ ✓ fixtures/verbose/example-2.test.ts > test 0.1 [...]ms
+ ↓ fixtures/verbose/example-2.test.ts > test 0.2
+ ✓ fixtures/verbose/example-2.test.ts > suite 1.1 > test 1.1 [...]ms"
+ `)
+})
+
+test('hides skipped tests when --hideSkippedTests and in non-TTY', async () => {
+ const { stdout } = await runVitest({
+ include: ['fixtures/verbose/*.test.ts'],
+ reporters: [['verbose', { isTTY: false, summary: false }]],
+ hideSkippedTests: true,
+ config: false,
+ fileParallelism: false,
+ sequence: {
+ sequencer: class StableTestFileOrderSorter {
+ sort(files: TestSpecification[]) {
+ return files.sort((a, b) => a.moduleId.localeCompare(b.moduleId))
+ }
+
+ shard(files: TestSpecification[]) {
+ return files
+ }
+ },
+ },
+ })
+
expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(`
"✓ fixtures/verbose/example-1.test.ts > test pass in root [...]ms
✓ fixtures/verbose/example-1.test.ts > suite in root > test pass in 1. suite #1 [...]ms