Skip to content

Commit 5629c8f

Browse files
committed
feat(reporters): summary option for verbose and default reporters
1 parent 07c19b8 commit 5629c8f

File tree

15 files changed

+604
-522
lines changed

15 files changed

+604
-522
lines changed

docs/guide/reporters.md

Lines changed: 36 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. Similar to `default` reporter, you can disable the summary by configuring the reporter.
155176

156177
:::code-group
157178
```bash [CLI]
@@ -161,7 +182,9 @@ 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
```

packages/vitest/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@
165165
"std-env": "^3.7.0",
166166
"tinybench": "^2.9.0",
167167
"tinyexec": "^0.3.1",
168-
"tinypool": "^1.0.1",
168+
"tinypool": "https://pkg.pr.new/tinypool@67f3129",
169169
"tinyrainbow": "^1.2.0",
170170
"vite": "^5.0.0",
171171
"vite-node": "workspace:*",

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

Lines changed: 28 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,10 @@ export abstract class BaseReporter implements Reporter {
137133
+ ` ${c.yellow(Math.round(duration) + c.dim('ms'))}`,
138134
)
139135
}
136+
137+
else if (this.renderSucceed || anyFailed) {
138+
this.log(` ${c.green(c.dim(F_CHECK))} ${getTestName(test, c.dim(' > '))}`)
139+
}
140140
}
141141
}
142142

@@ -153,8 +153,6 @@ export abstract class BaseReporter implements Reporter {
153153
}
154154

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

160158
if (failed) {
@@ -177,38 +175,9 @@ export abstract class BaseReporter implements Reporter {
177175
}
178176

179177
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()
208178
}
209179

210180
onWatcherRerun(files: string[], trigger?: string) {
211-
this.resetLastRunLog()
212181
this.watchFilters = files
213182
this.failedUnwatchedFiles = this.ctx.state.getFiles().filter(file =>
214183
!files.includes(file.filepath) && hasFailed(file),
@@ -222,11 +191,7 @@ export abstract class BaseReporter implements Reporter {
222191

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

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) {
194+
if (files.length === 1) {
230195
const rerun = this._filesInWatchMode.get(files[0]) ?? 1
231196
banner += c.blue(`x${rerun} `)
232197
}
@@ -248,10 +213,8 @@ export abstract class BaseReporter implements Reporter {
248213

249214
this.log('')
250215

251-
if (!this.isTTY) {
252-
for (const task of this.failedUnwatchedFiles) {
253-
this.printTask(task)
254-
}
216+
for (const task of this.failedUnwatchedFiles) {
217+
this.printTask(task)
255218
}
256219

257220
this._timeStart = formatTimeString(new Date())
@@ -351,6 +314,8 @@ export abstract class BaseReporter implements Reporter {
351314
}
352315

353316
reportTestSummary(files: File[], errors: unknown[]) {
317+
this.log()
318+
354319
const affectedFiles = [
355320
...this.failedUnwatchedFiles,
356321
...files,
@@ -364,21 +329,21 @@ export abstract class BaseReporter implements Reporter {
364329

365330
for (const [index, snapshot] of snapshotOutput.entries()) {
366331
const title = index === 0 ? 'Snapshots' : ''
367-
this.log(`${padTitle(title)} ${snapshot}`)
332+
this.log(`${padSummaryTitle(title)} ${snapshot}`)
368333
}
369334

370335
if (snapshotOutput.length > 1) {
371336
this.log()
372337
}
373338

374-
this.log(padTitle('Test Files'), getStateString(affectedFiles))
375-
this.log(padTitle('Tests'), getStateString(tests))
339+
this.log(padSummaryTitle('Test Files'), getStateString(affectedFiles))
340+
this.log(padSummaryTitle('Tests'), getStateString(tests))
376341

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

380345
this.log(
381-
padTitle('Type Errors'),
346+
padSummaryTitle('Type Errors'),
382347
failed.length
383348
? c.bold(c.red(`${failed.length} failed`))
384349
: c.dim('no errors'),
@@ -387,19 +352,19 @@ export abstract class BaseReporter implements Reporter {
387352

388353
if (errors.length) {
389354
this.log(
390-
padTitle('Errors'),
355+
padSummaryTitle('Errors'),
391356
c.bold(c.red(`${errors.length} error${errors.length > 1 ? 's' : ''}`)),
392357
)
393358
}
394359

395-
this.log(padTitle('Start at'), this._timeStart)
360+
this.log(padSummaryTitle('Start at'), this._timeStart)
396361

397362
const collectTime = sum(files, file => file.collectDuration)
398363
const testsTime = sum(files, file => file.result?.duration)
399364
const setupTime = sum(files, file => file.setupDuration)
400365

401366
if (this.watchFilters) {
402-
this.log(padTitle('Duration'), time(collectTime + testsTime + setupTime))
367+
this.log(padSummaryTitle('Duration'), formatTime(collectTime + testsTime + setupTime))
403368
}
404369
else {
405370
const executionTime = this.end - this.start
@@ -409,16 +374,16 @@ export abstract class BaseReporter implements Reporter {
409374
const typecheck = sum(this.ctx.projects, project => project.typechecker?.getResult().time)
410375

411376
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)}`,
377+
`transform ${formatTime(transformTime)}`,
378+
`setup ${formatTime(setupTime)}`,
379+
`collect ${formatTime(collectTime)}`,
380+
`tests ${formatTime(testsTime)}`,
381+
`environment ${formatTime(environmentTime)}`,
382+
`prepare ${formatTime(prepareTime)}`,
383+
typecheck && `typecheck ${formatTime(typecheck)}`,
419384
].filter(Boolean).join(', ')
420385

421-
this.log(padTitle('Duration'), time(executionTime) + c.dim(` (${timers})`))
386+
this.log(padSummaryTitle('Duration'), formatTime(executionTime) + c.dim(` (${timers})`))
422387
}
423388

424389
this.log()
@@ -544,17 +509,6 @@ function errorBanner(message: string) {
544509
return c.red(divider(c.bold(c.inverse(` ${message} `))))
545510
}
546511

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-
558512
function sum<T>(items: T[], cb: (_next: T) => number | undefined) {
559513
return items.reduce((total, next) => {
560514
return total + Math.max(cb(next) || 0, 0)

0 commit comments

Comments
 (0)