Skip to content

Commit 511b73c

Browse files
authored
feat(reporters): summary option for verbose and default reporters (#6893)
1 parent 171041a commit 511b73c

File tree

22 files changed

+1124
-523
lines changed

22 files changed

+1124
-523
lines changed

docs/guide/reporters.md

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -96,33 +96,54 @@ This example will write separate JSON and XML reports as well as printing a verb
9696

9797
### Default Reporter
9898

99-
By default (i.e. if no reporter is specified), Vitest will display results for each test suite hierarchically as they run, and then collapse after a suite passes. When all tests have finished running, the final terminal output will display a summary of results and details of any failed tests.
99+
By default (i.e. if no reporter is specified), Vitest will display summary of running tests and their status at the bottom. Once a suite passes, its status will be reported on top of the summary.
100+
101+
You can disable the summary by configuring the reporter:
102+
103+
:::code-group
104+
```ts [vitest.config.ts]
105+
export default defineConfig({
106+
test: {
107+
reporters: [
108+
['default', { summary: false }]
109+
]
110+
},
111+
})
112+
```
113+
:::
100114

101115
Example output for tests in progress:
102116

103117
```bash
104-
✓ __tests__/file1.test.ts (2) 725ms
105-
✓ __tests__/file2.test.ts (5) 746ms
106-
✓ second test file (2) 746ms
107-
✓ 1 + 1 should equal 2
108-
✓ 2 - 1 should equal 1
118+
✓ test/example-1.test.ts (5 tests | 1 skipped) 306ms
119+
✓ test/example-2.test.ts (5 tests | 1 skipped) 307ms
120+
121+
❯ test/example-3.test.ts 3/5
122+
❯ test/example-4.test.ts 1/5
123+
124+
Test Files 2 passed (4)
125+
Tests 10 passed | 3 skipped (65)
126+
Start at 11:01:36
127+
Duration 2.00s
109128
```
110129

111130
Final output after tests have finished:
112131

113132
```bash
114-
✓ __tests__/file1.test.ts (2) 725ms
115-
✓ __tests__/file2.test.ts (2) 746ms
133+
✓ test/example-1.test.ts (5 tests | 1 skipped) 306ms
134+
✓ test/example-2.test.ts (5 tests | 1 skipped) 307ms
135+
✓ test/example-3.test.ts (5 tests | 1 skipped) 307ms
136+
✓ test/example-4.test.ts (5 tests | 1 skipped) 307ms
116137

117-
Test Files 2 passed (2)
118-
Tests 4 passed (4)
138+
Test Files 4 passed (4)
139+
Tests 16 passed | 4 skipped (20)
119140
Start at 12:34:32
120141
Duration 1.26s (transform 35ms, setup 1ms, collect 90ms, tests 1.47s, environment 0ms, prepare 267ms)
121142
```
122143

123144
### Basic Reporter
124145

125-
The `basic` reporter displays the test files that have run and a summary of results after the entire suite has finished running. Individual tests are not included in the report unless they fail.
146+
The `basic` reporter is equivalent to `default` reporter without `summary`.
126147

127148
:::code-group
128149
```bash [CLI]
@@ -151,7 +172,7 @@ Example output using basic reporter:
151172

152173
### Verbose Reporter
153174

154-
Follows the same hierarchical structure as the `default` reporter, but does not collapse sub-trees for passed test suites. The final terminal output displays all tests that have run, including those that have passed.
175+
Verbose reporter is same as `default` reporter, but it also displays each individual test after the suite has finished. It also displays currently running tests that are taking longer than [`slowTestThreshold`](/config/#slowtestthreshold). Similar to `default` reporter, you can disable the summary by configuring the reporter.
155176

156177
:::code-group
157178
```bash [CLI]
@@ -161,12 +182,32 @@ npx vitest --reporter=verbose
161182
```ts [vitest.config.ts]
162183
export default defineConfig({
163184
test: {
164-
reporters: ['verbose']
185+
reporters: [
186+
['verbose', { summary: false }]
187+
]
165188
},
166189
})
167190
```
168191
:::
169192

193+
Example output for tests in progress with default `slowTestThreshold: 300`:
194+
195+
```bash
196+
✓ __tests__/example-1.test.ts (2) 725ms
197+
✓ first test file (2) 725ms
198+
✓ 2 + 2 should equal 4
199+
✓ 4 - 2 should equal 2
200+
201+
❯ test/example-2.test.ts 3/5
202+
↳ should run longer than three seconds 1.57s
203+
❯ test/example-3.test.ts 1/5
204+
205+
Test Files 2 passed (4)
206+
Tests 10 passed | 3 skipped (65)
207+
Start at 11:01:36
208+
Duration 2.00s
209+
```
210+
170211
Example of final terminal output for a passing test suite:
171212

172213
```bash

packages/vitest/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,11 @@
162162
"expect-type": "^1.1.0",
163163
"magic-string": "^0.30.12",
164164
"pathe": "^1.1.2",
165+
"restore-cursor": "^5.1.0",
165166
"std-env": "^3.8.0",
166167
"tinybench": "^2.9.0",
167168
"tinyexec": "^0.3.1",
168-
"tinypool": "^1.0.1",
169+
"tinypool": "^1.0.2",
169170
"tinyrainbow": "^1.2.0",
170171
"vite": "^5.0.0",
171172
"vite-node": "workspace:*",

packages/vitest/src/node/reporters/base.ts

Lines changed: 33 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ import c from 'tinyrainbow'
1111
import { isCI, isDeno, isNode } from '../../utils/env'
1212
import { hasFailedSnapshot } from '../../utils/tasks'
1313
import { F_CHECK, F_POINTER, F_RIGHT } from './renderers/figures'
14-
import { countTestErrors, divider, formatProjectName, formatTimeString, getStateString, getStateSymbol, renderSnapshotSummary, taskFail, withLabel } from './renderers/utils'
14+
import { countTestErrors, divider, formatProjectName, formatTime, formatTimeString, getStateString, getStateSymbol, padSummaryTitle, renderSnapshotSummary, taskFail, withLabel } from './renderers/utils'
1515

1616
const BADGE_PADDING = ' '
17-
const LAST_RUN_LOG_TIMEOUT = 1_500
1817

1918
export interface BaseOptions {
2019
isTTY?: boolean
@@ -27,14 +26,12 @@ export abstract class BaseReporter implements Reporter {
2726
failedUnwatchedFiles: Task[] = []
2827
isTTY: boolean
2928
ctx: Vitest = undefined!
29+
renderSucceed = false
3030

3131
protected verbose = false
3232

3333
private _filesInWatchMode = new Map<string, number>()
3434
private _timeStart = formatTimeString(new Date())
35-
private _lastRunTimeout = 0
36-
private _lastRunTimer: NodeJS.Timeout | undefined
37-
private _lastRunCount = 0
3835

3936
constructor(options: BaseOptions = {}) {
4037
this.isTTY = options.isTTY ?? ((isNode || isDeno) && process.stdout?.isTTY && !isCI)
@@ -65,9 +62,6 @@ export abstract class BaseReporter implements Reporter {
6562
}
6663

6764
onTaskUpdate(packs: TaskResultPack[]) {
68-
if (this.isTTY) {
69-
return
70-
}
7165
for (const pack of packs) {
7266
const task = this.ctx.state.idMap.get(pack[0])
7367

@@ -117,6 +111,8 @@ export abstract class BaseReporter implements Reporter {
117111

118112
this.log(` ${title} ${task.name} ${suffix}`)
119113

114+
const anyFailed = tests.some(test => test.result?.state === 'fail')
115+
120116
for (const test of tests) {
121117
const duration = test.result?.duration
122118

@@ -137,6 +133,15 @@ export abstract class BaseReporter implements Reporter {
137133
+ ` ${c.yellow(Math.round(duration) + c.dim('ms'))}`,
138134
)
139135
}
136+
137+
// also print skipped tests that have notes
138+
else if (test.result?.state === 'skip' && test.result.note) {
139+
this.log(` ${getStateSymbol(test)} ${getTestName(test)}${c.dim(c.gray(` [${test.result.note}]`))}`)
140+
}
141+
142+
else if (this.renderSucceed || anyFailed) {
143+
this.log(` ${c.green(c.dim(F_CHECK))} ${getTestName(test, c.dim(' > '))}`)
144+
}
140145
}
141146
}
142147

@@ -153,8 +158,6 @@ export abstract class BaseReporter implements Reporter {
153158
}
154159

155160
onWatcherStart(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
156-
this.resetLastRunLog()
157-
158161
const failed = errors.length > 0 || hasFailed(files)
159162

160163
if (failed) {
@@ -177,38 +180,9 @@ export abstract class BaseReporter implements Reporter {
177180
}
178181

179182
this.log(BADGE_PADDING + hints.join(c.dim(', ')))
180-
181-
if (this._lastRunCount) {
182-
const LAST_RUN_TEXT = `rerun x${this._lastRunCount}`
183-
const LAST_RUN_TEXTS = [
184-
c.blue(LAST_RUN_TEXT),
185-
c.gray(LAST_RUN_TEXT),
186-
c.dim(c.gray(LAST_RUN_TEXT)),
187-
]
188-
this.ctx.logger.logUpdate(BADGE_PADDING + LAST_RUN_TEXTS[0])
189-
this._lastRunTimeout = 0
190-
this._lastRunTimer = setInterval(() => {
191-
this._lastRunTimeout += 1
192-
if (this._lastRunTimeout >= LAST_RUN_TEXTS.length) {
193-
this.resetLastRunLog()
194-
}
195-
else {
196-
this.ctx.logger.logUpdate(
197-
BADGE_PADDING + LAST_RUN_TEXTS[this._lastRunTimeout],
198-
)
199-
}
200-
}, LAST_RUN_LOG_TIMEOUT / LAST_RUN_TEXTS.length)
201-
}
202-
}
203-
204-
private resetLastRunLog() {
205-
clearInterval(this._lastRunTimer)
206-
this._lastRunTimer = undefined
207-
this.ctx.logger.logUpdate.clear()
208183
}
209184

210185
onWatcherRerun(files: string[], trigger?: string) {
211-
this.resetLastRunLog()
212186
this.watchFilters = files
213187
this.failedUnwatchedFiles = this.ctx.state.getFiles().filter(file =>
214188
!files.includes(file.filepath) && hasFailed(file),
@@ -222,11 +196,7 @@ export abstract class BaseReporter implements Reporter {
222196

223197
let banner = trigger ? c.dim(`${this.relative(trigger)} `) : ''
224198

225-
if (files.length > 1 || !files.length) {
226-
// we need to figure out how to handle rerun all from stdin
227-
this._lastRunCount = 0
228-
}
229-
else if (files.length === 1) {
199+
if (files.length === 1) {
230200
const rerun = this._filesInWatchMode.get(files[0]) ?? 1
231201
banner += c.blue(`x${rerun} `)
232202
}
@@ -248,10 +218,8 @@ export abstract class BaseReporter implements Reporter {
248218

249219
this.log('')
250220

251-
if (!this.isTTY) {
252-
for (const task of this.failedUnwatchedFiles) {
253-
this.printTask(task)
254-
}
221+
for (const task of this.failedUnwatchedFiles) {
222+
this.printTask(task)
255223
}
256224

257225
this._timeStart = formatTimeString(new Date())
@@ -351,6 +319,8 @@ export abstract class BaseReporter implements Reporter {
351319
}
352320

353321
reportTestSummary(files: File[], errors: unknown[]) {
322+
this.log()
323+
354324
const affectedFiles = [
355325
...this.failedUnwatchedFiles,
356326
...files,
@@ -364,21 +334,21 @@ export abstract class BaseReporter implements Reporter {
364334

365335
for (const [index, snapshot] of snapshotOutput.entries()) {
366336
const title = index === 0 ? 'Snapshots' : ''
367-
this.log(`${padTitle(title)} ${snapshot}`)
337+
this.log(`${padSummaryTitle(title)} ${snapshot}`)
368338
}
369339

370340
if (snapshotOutput.length > 1) {
371341
this.log()
372342
}
373343

374-
this.log(padTitle('Test Files'), getStateString(affectedFiles))
375-
this.log(padTitle('Tests'), getStateString(tests))
344+
this.log(padSummaryTitle('Test Files'), getStateString(affectedFiles))
345+
this.log(padSummaryTitle('Tests'), getStateString(tests))
376346

377347
if (this.ctx.projects.some(c => c.config.typecheck.enabled)) {
378348
const failed = tests.filter(t => t.meta?.typecheck && t.result?.errors?.length)
379349

380350
this.log(
381-
padTitle('Type Errors'),
351+
padSummaryTitle('Type Errors'),
382352
failed.length
383353
? c.bold(c.red(`${failed.length} failed`))
384354
: c.dim('no errors'),
@@ -387,19 +357,19 @@ export abstract class BaseReporter implements Reporter {
387357

388358
if (errors.length) {
389359
this.log(
390-
padTitle('Errors'),
360+
padSummaryTitle('Errors'),
391361
c.bold(c.red(`${errors.length} error${errors.length > 1 ? 's' : ''}`)),
392362
)
393363
}
394364

395-
this.log(padTitle('Start at'), this._timeStart)
365+
this.log(padSummaryTitle('Start at'), this._timeStart)
396366

397367
const collectTime = sum(files, file => file.collectDuration)
398368
const testsTime = sum(files, file => file.result?.duration)
399369
const setupTime = sum(files, file => file.setupDuration)
400370

401371
if (this.watchFilters) {
402-
this.log(padTitle('Duration'), time(collectTime + testsTime + setupTime))
372+
this.log(padSummaryTitle('Duration'), formatTime(collectTime + testsTime + setupTime))
403373
}
404374
else {
405375
const executionTime = this.end - this.start
@@ -409,16 +379,16 @@ export abstract class BaseReporter implements Reporter {
409379
const typecheck = sum(this.ctx.projects, project => project.typechecker?.getResult().time)
410380

411381
const timers = [
412-
`transform ${time(transformTime)}`,
413-
`setup ${time(setupTime)}`,
414-
`collect ${time(collectTime)}`,
415-
`tests ${time(testsTime)}`,
416-
`environment ${time(environmentTime)}`,
417-
`prepare ${time(prepareTime)}`,
418-
typecheck && `typecheck ${time(typecheck)}`,
382+
`transform ${formatTime(transformTime)}`,
383+
`setup ${formatTime(setupTime)}`,
384+
`collect ${formatTime(collectTime)}`,
385+
`tests ${formatTime(testsTime)}`,
386+
`environment ${formatTime(environmentTime)}`,
387+
`prepare ${formatTime(prepareTime)}`,
388+
typecheck && `typecheck ${formatTime(typecheck)}`,
419389
].filter(Boolean).join(', ')
420390

421-
this.log(padTitle('Duration'), time(executionTime) + c.dim(` (${timers})`))
391+
this.log(padSummaryTitle('Duration'), formatTime(executionTime) + c.dim(` (${timers})`))
422392
}
423393

424394
this.log()
@@ -544,17 +514,6 @@ function errorBanner(message: string) {
544514
return c.red(divider(c.bold(c.inverse(` ${message} `))))
545515
}
546516

547-
function padTitle(str: string) {
548-
return c.dim(`${str.padStart(11)} `)
549-
}
550-
551-
function time(time: number) {
552-
if (time > 1000) {
553-
return `${(time / 1000).toFixed(2)}s`
554-
}
555-
return `${Math.round(time)}ms`
556-
}
557-
558517
function sum<T>(items: T[], cb: (_next: T) => number | undefined) {
559518
return items.reduce((total, next) => {
560519
return total + Math.max(cb(next) || 0, 0)

packages/vitest/src/node/reporters/basic.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import type { File } from '@vitest/runner'
2+
import type { Vitest } from '../core'
3+
import c from 'tinyrainbow'
24
import { BaseReporter } from './base'
35

46
export class BasicReporter extends BaseReporter {
@@ -7,6 +9,16 @@ export class BasicReporter extends BaseReporter {
79
this.isTTY = false
810
}
911

12+
onInit(ctx: Vitest) {
13+
super.onInit(ctx)
14+
15+
ctx.logger.log(c.inverse(c.bold(c.yellow(' DEPRECATED '))), c.yellow(
16+
`'basic' reporter is deprecated and will be removed in Vitest v3.\n`
17+
+ `Remove 'basic' from 'reporters' option. To match 'basic' reporter 100%, use configuration:\n${
18+
JSON.stringify({ test: { reporters: [['default', { summary: false }]] } }, null, 2)}`,
19+
))
20+
}
21+
1022
reportSummary(files: File[], errors: unknown[]) {
1123
// non-tty mode doesn't add a new line
1224
this.ctx.logger.log()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy