diff --git a/docs/advanced/reporters.md b/docs/advanced/reporters.md index b75f080db2e5..a426861d85cc 100644 --- a/docs/advanced/reporters.md +++ b/docs/advanced/reporters.md @@ -93,6 +93,7 @@ class MyReporter implements Reporter { 6. `JUnitReporter` 7. `TapFlatReporter` 8. `HangingProcessReporter` +9. `TreeReporter` ### Base Abstract reporters: diff --git a/docs/guide/cli-generated.md b/docs/guide/cli-generated.md index 5c17c73b5969..994418d7a024 100644 --- a/docs/guide/cli-generated.md +++ b/docs/guide/cli-generated.md @@ -89,7 +89,7 @@ Hide logs for skipped tests - **CLI:** `--reporter ` - **Config:** [reporters](/config/#reporters) -Specify reporters (default, blob, verbose, dot, json, tap, tap-flat, junit, hanging-process, github-actions) +Specify reporters (default, blob, verbose, dot, json, tap, tap-flat, junit, tree, hanging-process, github-actions) ### outputFile diff --git a/docs/guide/migration.md b/docs/guide/migration.md index eaf92cfa9414..f164565a2646 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -7,20 +7,6 @@ outline: deep ## Migrating to Vitest 4.0 {#vitest-4} -### Removed `reporters: 'basic'` - -Basic reporter is removed as it is equal to: - -```ts -export default defineConfig({ - test: { - reporters: [ - ['default', { summary: false }] - ] - } -}) -``` - ### V8 Code Coverage Major Changes Vitest's V8 code coverage provider is now using more accurate coverage result remapping logic. @@ -260,13 +246,39 @@ export default defineConfig({ The naming of properties in `playwright` factory now also aligns with [Playwright documentation](https://playwright.dev/docs/api/class-testoptions#test-options-launch-options) making it easier to find. +### Reporter Updates + +Reporter APIs `onCollected`, `onSpecsCollected`, `onPathsCollected`, `onTaskUpdate` and `onFinished` were removed. See [`Reporters API`](/advanced/api/reporters) for new alternatives. The new APIs were introduced in Vitest `v3.0.0`. + +The `basic` reporter was removed as it is equal to: + +```ts +export default defineConfig({ + test: { + reporters: [ + ['default', { summary: false }] + ] + } +}) +``` + +The [`verbose`](/guide/reporters#verbose-reporter) reporter now prints test cases as a flat list. To revert to the previous behaviour, use `--reporter=tree`: + +```ts +export default defineConfig({ + test: { + reporters: ['verbose'], // [!code --] + reporters: ['tree'], // [!code ++] + } +}) +``` + ### Deprecated APIs are Removed Vitest 4.0 removes some deprecated APIs, including: - `poolMatchGlobs` config option. Use [`projects`](/guide/projects) instead. - `environmentMatchGlobs` 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. - `browser.testerScripts` config option. Use [`browser.testerHtmlPath`](/guide/browser/config#browser-testerhtmlpath) instead. - `minWorkers` config option. Only `maxWorkers` has any effect on how tests are running, so we are removing this public option. diff --git a/docs/guide/reporters.md b/docs/guide/reporters.md index ea25f885e237..b411babb3583 100644 --- a/docs/guide/reporters.md +++ b/docs/guide/reporters.md @@ -141,9 +141,27 @@ Final output after tests have finished: Duration 1.26s (transform 35ms, setup 1ms, collect 90ms, tests 1.47s, environment 0ms, prepare 267ms) ``` +If there is only one test file running, Vitest will output the full test tree of that file, simillar to the [`tree`](#tree-reporter) reporter. The default reporter will also print the test tree if there is at least one failed test in the file. + +```bash +✓ __tests__/file1.test.ts (2) 725ms + ✓ first test file (2) 725ms + ✓ 2 + 2 should equal 4 + ✓ 4 - 2 should equal 2 + + Test Files 1 passed (1) + Tests 2 passed (2) + Start at 12:34:32 + Duration 1.26s (transform 35ms, setup 1ms, collect 90ms, tests 1.47s, environment 0ms, prepare 267ms) +``` + ### Verbose Reporter -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. +The verbose reporter prints every test case once it is finished. It does not report suites or files separately. If `--includeTaskLocation` is enabled, it will also include the location of each test in the output. Similar to `default` reporter, you can disable the summary by configuring the reporter. + +In addition to this, the `verbose` reporter prints test error messages right away. The full test error is reported when the test run is finished. + +This is the only terminal reporter that reports [annotations](/guide/test-annotations) when the test doesn't fail. :::code-group ```bash [CLI] @@ -161,6 +179,54 @@ export default defineConfig({ ``` ::: +Example output: + +```bash +✓ __tests__/file1.test.ts > first test file > 2 + 2 should equal 4 1ms +✓ __tests__/file1.test.ts > first test file > 4 - 2 should equal 2 1ms +✓ __tests__/file2.test.ts > second test file > 1 + 1 should equal 2 1ms +✓ __tests__/file2.test.ts > second test file > 2 - 1 should equal 1 1ms + + Test Files 2 passed (2) + Tests 4 passed (4) + Start at 12:34:32 + Duration 1.26s (transform 35ms, setup 1ms, collect 90ms, tests 1.47s, environment 0ms, prepare 267ms) +``` + +An example with `--includeTaskLocation`: + +```bash +✓ __tests__/file1.test.ts:2:1 > first test file > 2 + 2 should equal 4 1ms +✓ __tests__/file1.test.ts:3:1 > first test file > 4 - 2 should equal 2 1ms +✓ __tests__/file2.test.ts:2:1 > second test file > 1 + 1 should equal 2 1ms +✓ __tests__/file2.test.ts:3:1 > second test file > 2 - 1 should equal 1 1ms + + Test Files 2 passed (2) + Tests 4 passed (4) + Start at 12:34:32 + Duration 1.26s (transform 35ms, setup 1ms, collect 90ms, tests 1.47s, environment 0ms, prepare 267ms) +``` + +### Tree Reporter + +The tree 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. + +:::code-group +```bash [CLI] +npx vitest --reporter=tree +``` + +```ts [vitest.config.ts] +export default defineConfig({ + test: { + reporters: [ + ['tree', { summary: false }] + ] + }, +}) +``` +::: + Example output for tests in progress with default `slowTestThreshold: 300`: ```bash diff --git a/docs/guide/test-annotations.md b/docs/guide/test-annotations.md index 68983720991f..88f2362543c1 100644 --- a/docs/guide/test-annotations.md +++ b/docs/guide/test-annotations.md @@ -53,7 +53,7 @@ Error: thrown error ### verbose -In a TTY terminal, the `verbose` reporter works similarly to the `default` reporter. However, in a non-TTY environment, the `verbose` reporter will also print annotations after every test. +The `verbose` reporter is the only terminal reporter that reports annotations when the test doesn't fail. ``` ✓ example.test.js > an example of a test with annotation diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts index c60d763ef9c8..aba68597cea3 100644 --- a/packages/vitest/src/node/reporters/base.ts +++ b/packages/vitest/src/node/reporters/base.ts @@ -5,15 +5,29 @@ import type { Vitest } from '../core' 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' +import { getSuites, getTestName, getTests, hasFailed } from '@vitest/runner/utils' import { toArray } from '@vitest/utils/helpers' import { parseStacktrace } from '@vitest/utils/source-map' import { relative } from 'pathe' import c from 'tinyrainbow' import { isTTY } from '../../utils/env' import { hasFailedSnapshot } from '../../utils/tasks' -import { F_CHECK, F_DOWN_RIGHT, F_POINTER, F_RIGHT } from './renderers/figures' -import { countTestErrors, divider, errorBanner, formatProjectName, formatTime, formatTimeString, getStateString, getStateSymbol, padSummaryTitle, renderSnapshotSummary, taskFail, withLabel } from './renderers/utils' +import { F_CHECK, F_DOWN_RIGHT, F_POINTER } from './renderers/figures' +import { + countTestErrors, + divider, + errorBanner, + formatProjectName, + formatTime, + formatTimeString, + getStateString, + getStateSymbol, + padSummaryTitle, + renderSnapshotSummary, + separator, + taskFail, + withLabel, +} from './renderers/utils' const BADGE_PADDING = ' ' @@ -170,47 +184,25 @@ export abstract class BaseReporter implements Reporter { protected printTestCase(moduleState: TestModuleState, test: TestCase): void { const testResult = test.result() - const { duration, retryCount, repeatCount } = test.diagnostic() || {} + const { duration = 0 } = test.diagnostic() || {} const padding = this.getTestIndentation(test.task) - let suffix = this.getDurationPrefix(test.task) - - if (retryCount != null && retryCount > 0) { - suffix += c.yellow(` (retry x${retryCount})`) - } - - if (repeatCount != null && repeatCount > 0) { - suffix += c.yellow(` (repeat x${repeatCount})`) - } + const suffix = this.getTestCaseSuffix(test) if (testResult.state === 'failed') { - this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, c.dim(' > '))}`) + suffix) - - // print short errors, full errors will be at the end in summary - testResult.errors.forEach((error) => { - const message = this.formatShortError(error) - - if (message) { - this.log(c.red(` ${padding}${message}`)) - } - }) + this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test.task, separator)}`) + suffix) } // 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}`) + else if (duration > this.ctx.config.slowTestThreshold) { + this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test.task, separator)} ${suffix}`) } else if (this.ctx.config.hideSkippedTests && (testResult.state === 'skipped')) { // Skipped tests are hidden when --hideSkippedTests } - // 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}]`))}`) - } - else if (this.renderSucceed || moduleState === 'failed') { - this.log(` ${padding}${getStateSymbol(test.task)} ${this.getTestName(test.task, c.dim(' > '))}${suffix}`) + this.log(` ${padding}${this.getStateSymbol(test)} ${this.getTestName(test.task, separator)}${suffix}`) } } @@ -236,37 +228,43 @@ export abstract class BaseReporter implements Reporter { suffix += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`) } - let title = getStateSymbol(testModule.task) + const title = this.getEntityPrefix(testModule) - if (testModule.meta().typecheck) { - title += ` ${c.bgBlue(c.bold(' TS '))}` - } + return ` ${title} ${testModule.task.name} ${suffix}` + } - if (testModule.project.name) { - title += ` ${formatProjectName(testModule.project, '')}` + protected printTestSuite(testSuite: TestSuite): void { + if (!this.renderSucceed) { + return } - return ` ${title} ${testModule.task.name} ${suffix}` - } + const indentation = ' '.repeat(getIndentation(testSuite.task)) + const tests = Array.from(testSuite.children.allTests()) + const state = this.getStateSymbol(testSuite) - protected printTestSuite(_suite: TestSuite): void { - // Suite name is included in getTestName by default + this.log(` ${indentation}${state} ${testSuite.name} ${c.dim(`(${tests.length})`)}`) } - protected getTestName(test: Task, separator?: string): string { - return getTestName(test, separator) + protected getTestName(test: Task, _separator?: string): string { + return test.name } protected getFullName(test: Task, separator?: string): string { - return getFullName(test, separator) - } + if (test === test.file) { + return test.name + } - protected formatShortError(error: TestError): string { - return `${F_RIGHT} ${error.message}` + let name = test.file.name + if (test.location) { + name += c.dim(`:${test.location.line}:${test.location.column}`) + } + name += separator + name += getTestName(test, separator) + return name } - protected getTestIndentation(_test: Task) { - return ' ' + protected getTestIndentation(test: Task): string { + return ' '.repeat(getIndentation(test)) } protected printAnnotations(test: TestCase, console: 'log' | 'error', padding = 0): void { @@ -289,16 +287,59 @@ export abstract class BaseReporter implements Reporter { }) } - protected getDurationPrefix(task: Task): string { - if (!task.result?.duration) { + protected getEntityPrefix(entity: TestCase | TestModule | TestSuite): string { + let title = this.getStateSymbol(entity) + + if (entity.project.name) { + title += ` ${formatProjectName(entity.project, '')}` + } + + if (entity.meta().typecheck) { + title += ` ${c.bgBlue(c.bold(' TS '))}` + } + + return title + } + + protected getTestCaseSuffix(testCase: TestCase): string { + const { heap, retryCount, repeatCount } = testCase.diagnostic() || {} + const testResult = testCase.result() + let suffix = this.getDurationPrefix(testCase.task) + + if (retryCount != null && retryCount > 0) { + suffix += c.yellow(` (retry x${retryCount})`) + } + + if (repeatCount != null && repeatCount > 0) { + suffix += c.yellow(` (repeat x${repeatCount})`) + } + + if (heap != null) { + suffix += c.magenta(` ${Math.floor(heap / 1024 / 1024)} MB heap used`) + } + + if (testResult.state === 'skipped' && testResult.note) { + suffix += c.dim(c.gray(` [${testResult.note}]`)) + } + + return suffix + } + + protected getStateSymbol(test: TestCase | TestModule | TestSuite): string { + return getStateSymbol(test.task) + } + + private getDurationPrefix(task: Task): string { + const duration = task.result?.duration && Math.round(task.result?.duration) + if (duration == null) { return '' } - const color = task.result.duration > this.ctx.config.slowTestThreshold + const color = duration > this.ctx.config.slowTestThreshold ? c.yellow : c.green - return color(` ${Math.round(task.result.duration)}${c.dim('ms')}`) + return color(` ${duration}${c.dim('ms')}`) } onWatcherStart(files: File[] = this.ctx.state.getFiles(), errors: unknown[] = this.ctx.state.getUnhandledErrors()): void { @@ -386,7 +427,7 @@ export abstract class BaseReporter implements Reporter { const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : undefined if (task) { - headerText = this.getFullName(task, c.dim(' > ')) + headerText = this.getFullName(task, separator) } else if (log.taskId && log.taskId !== '__vitest__unknown_test__') { headerText = log.taskId @@ -446,7 +487,7 @@ export abstract class BaseReporter implements Reporter { const entity = task && this.ctx.state.getReportedEntity(task) const shouldLog = this.ctx.config.onConsoleLog(log.content, log.type, entity) if (shouldLog === false) { - return shouldLog + return false } } return true @@ -595,7 +636,7 @@ export abstract class BaseReporter implements Reporter { continue } - const groupName = this.getFullName(group, c.dim(' > ')) + const groupName = this.getFullName(group, separator) const project = this.ctx.projects.find(p => p.name === bench.file.projectName) this.log(` ${formatProjectName(project)}${bench.name}${c.dim(` - ${groupName}`)}`) @@ -652,7 +693,7 @@ export abstract class BaseReporter implements Reporter { const projectName = (task as File)?.projectName || task.file?.projectName || '' const project = this.ctx.projects.find(p => p.name === projectName) - let name = this.getFullName(task, c.dim(' > ')) + let name = this.getFullName(task, separator) if (filepath) { name += c.dim(` [ ${this.relative(filepath)} ]`) @@ -710,3 +751,11 @@ function sum(items: T[], cb: (_next: T) => number | undefined) { return total + Math.max(cb(next) || 0, 0) }, 0) } + +function getIndentation(suite: Task, level = 1): number { + if (suite.suite && !('filepath' in suite.suite)) { + return getIndentation(suite.suite, level + 1) + } + + return level +} diff --git a/packages/vitest/src/node/reporters/benchmark/reporter.ts b/packages/vitest/src/node/reporters/benchmark/reporter.ts index 2c2e820f9a77..1abc7f376a4b 100644 --- a/packages/vitest/src/node/reporters/benchmark/reporter.ts +++ b/packages/vitest/src/node/reporters/benchmark/reporter.ts @@ -8,7 +8,7 @@ import { getFullName } from '@vitest/runner/utils' import * as pathe from 'pathe' import c from 'tinyrainbow' import { DefaultReporter } from '../default' -import { formatProjectName, getStateSymbol } from '../renderers/utils' +import { formatProjectName, getStateSymbol, separator } from '../renderers/utils' import { createBenchmarkJsonReport, flattenFormattedBenchmarkReport } from './json-formatter' import { renderTable } from './tableRender' @@ -67,7 +67,7 @@ export class BenchmarkReporter extends DefaultReporter { 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(testTask.task)} ${formatProjectName(testTask.project)}${getFullName(testTask.task, c.dim(' > '))}` + let title = `\n ${getStateSymbol(testTask.task)} ${formatProjectName(testTask.project)}${getFullName(testTask.task, separator)}` 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/index.ts b/packages/vitest/src/node/reporters/index.ts index 946d8a609918..f22926e630da 100644 --- a/packages/vitest/src/node/reporters/index.ts +++ b/packages/vitest/src/node/reporters/index.ts @@ -14,6 +14,7 @@ import { JsonReporter } from './json' import { JUnitReporter } from './junit' import { TapReporter } from './tap' import { TapFlatReporter } from './tap-flat' +import { TreeReporter } from './tree' import { VerboseReporter } from './verbose' export { @@ -25,6 +26,7 @@ export { JUnitReporter, TapFlatReporter, TapReporter, + TreeReporter, VerboseReporter, } export type { BaseReporter, Reporter, TestRunEndReason } @@ -50,6 +52,7 @@ export const ReportersMap = { 'tap': TapReporter as typeof TapReporter, 'tap-flat': TapFlatReporter as typeof TapFlatReporter, 'junit': JUnitReporter as typeof JUnitReporter, + 'tree': TreeReporter as typeof TreeReporter, 'hanging-process': HangingProcessReporter as typeof HangingProcessReporter, 'github-actions': GithubActionsReporter as typeof GithubActionsReporter, } @@ -60,6 +63,7 @@ export interface BuiltinReporterOptions { 'default': DefaultReporterOptions 'verbose': DefaultReporterOptions 'dot': BaseOptions + 'tree': BaseOptions 'json': JsonOptions 'blob': BlobOptions 'tap': never diff --git a/packages/vitest/src/node/reporters/renderers/utils.ts b/packages/vitest/src/node/reporters/renderers/utils.ts index 16aa653d19a4..8863249a3c8c 100644 --- a/packages/vitest/src/node/reporters/renderers/utils.ts +++ b/packages/vitest/src/node/reporters/renderers/utils.ts @@ -23,6 +23,7 @@ export const testPass: string = c.green(F_CHECK) export const taskFail: string = c.red(F_CROSS) export const suiteFail: string = c.red(F_POINTER) export const pending: string = c.gray('·') +export const separator: string = c.dim(' > ') const labelDefaultColors = [c.bgYellow, c.bgCyan, c.bgGreen, c.bgMagenta] as const diff --git a/packages/vitest/src/node/reporters/tree.ts b/packages/vitest/src/node/reporters/tree.ts new file mode 100644 index 000000000000..ebb38d11da88 --- /dev/null +++ b/packages/vitest/src/node/reporters/tree.ts @@ -0,0 +1,6 @@ +import { DefaultReporter } from './default' + +export class TreeReporter extends DefaultReporter { + protected verbose = true + renderSucceed = true +} diff --git a/packages/vitest/src/node/reporters/verbose.ts b/packages/vitest/src/node/reporters/verbose.ts index a840bec5320c..7686a8d4a150 100644 --- a/packages/vitest/src/node/reporters/verbose.ts +++ b/packages/vitest/src/node/reporters/verbose.ts @@ -1,62 +1,42 @@ -import type { Task } from '@vitest/runner' -import type { TestCase, TestModule, TestSuite } from './reported-tasks' -import { getFullName } from '@vitest/runner/utils' +import type { TestCase, TestModule } from './reported-tasks' +import { getTestName } from '@vitest/runner/utils' import c from 'tinyrainbow' import { DefaultReporter } from './default' import { F_RIGHT } from './renderers/figures' -import { formatProjectName, getStateSymbol } from './renderers/utils' +import { separator } from './renderers/utils' export class VerboseReporter extends DefaultReporter { protected verbose = true renderSucceed = true - 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.printTestModule(module) - } + printTestModule(_module: TestModule): void { + // don't print test module, only print tests } 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 (this.ctx.config.hideSkippedTests && testResult.state === 'skipped') { return } - let title = ` ${getStateSymbol(test.task)} ` + let title = ` ${this.getEntityPrefix(test)} ` - if (test.project.name) { - title += formatProjectName(test.project) + title += test.module.task.name + if (test.location) { + title += c.dim(`:${test.location.line}:${test.location.column}`) } + title += separator - title += getFullName(test.task, c.dim(' > ')) - title += this.getDurationPrefix(test.task) - - const diagnostic = test.diagnostic() - if (diagnostic?.heap != null) { - title += c.magenta(` ${Math.floor(diagnostic.heap / 1024 / 1024)} MB heap used`) - } - - if (testResult.state === 'skipped' && testResult.note) { - title += c.dim(c.gray(` [${testResult.note}]`)) - } + title += getTestName(test.task, separator) + title += this.getTestCaseSuffix(test) this.log(title) if (testResult.state === 'failed') { - testResult.errors.forEach(error => this.log(c.red(` ${F_RIGHT} ${error?.message}`))) + testResult.errors.forEach(error => this.log(c.red(` ${F_RIGHT} ${error.message}`))) } if (test.annotations().length) { @@ -65,33 +45,4 @@ export class VerboseReporter extends DefaultReporter { this.log() } } - - 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} ${testSuite.name} ${c.dim(`(${tests.length})`)}`) - } - - protected getTestName(test: Task): string { - return test.name - } - - protected getTestIndentation(test: Task): string { - return ' '.repeat(getIndentation(test)) - } - - protected formatShortError(): string { - // Short errors are not shown in tree-view - return '' - } -} - -function getIndentation(suite: Task, level = 1): number { - if (suite.suite && !('filepath' in suite.suite)) { - return getIndentation(suite.suite, level + 1) - } - - return level } diff --git a/test/cli/test/public-api.test.ts b/test/cli/test/public-api.test.ts index 5f87fbcb1a9c..d0720c5fa4da 100644 --- a/test/cli/test/public-api.test.ts +++ b/test/cli/test/public-api.test.ts @@ -46,7 +46,7 @@ it.each([ expect(stderr).toBe('') - expect(stdout).toContain('custom.spec.ts > custom') + expect(stdout).toContain('custom.spec.ts:14:1 > custom') const suiteMeta = { done: true } const testMeta = { custom: 'some-custom-hanlder' } diff --git a/test/reporters/fixtures/metadata/metadata.test.ts b/test/reporters/fixtures/metadata/metadata.test.ts index 9a9dd9e1d8d0..c9ef44ff40d2 100644 --- a/test/reporters/fixtures/metadata/metadata.test.ts +++ b/test/reporters/fixtures/metadata/metadata.test.ts @@ -1,11 +1,11 @@ import {expect, test } from 'vitest'; -test('pass', ( { task }) => { +test('pass', ({ task }) => { task.meta.custom = "Passing test added this" }); -test('fails', ( { task }) => { +test('fails', ({ task }) => { task.meta.custom = "Failing test added this" expect(true).toBe(false) diff --git a/test/reporters/tests/default.test.ts b/test/reporters/tests/default.test.ts index aeea8296fcc3..e8512616c41b 100644 --- a/test/reporters/tests/default.test.ts +++ b/test/reporters/tests/default.test.ts @@ -1,59 +1,68 @@ -import type { RunnerTask, TestSpecification } from 'vitest/node' +import type { RunnerTask } from 'vitest/node' import { describe, expect, test } from 'vitest' import { DefaultReporter } from 'vitest/reporters' -import { runVitest, runVitestCli } from '../../test-utils' +import { runVitest, runVitestCli, StableTestFileOrderSorter } from '../../test-utils' +import { trimReporterOutput } from './utils' describe('default reporter', async () => { test('normal', async () => { const { stdout } = await runVitest({ include: ['b1.test.ts', 'b2.test.ts'], root: 'fixtures/default', - reporters: 'none', + reporters: ['default'], fileParallelism: false, sequence: { - sequencer: class StableTestFileOrderSorter { - sort(files: TestSpecification[]) { - return files.sort((a, b) => a.moduleId.localeCompare(b.moduleId)) - } - - shard(files: TestSpecification[]) { - return files - } - }, + sequencer: StableTestFileOrderSorter, }, }) expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(` "❯ b1.test.ts (13 tests | 1 failed) [...]ms - ✓ b1 passed > b1 test [...]ms - ✓ b1 passed > b2 test [...]ms - ✓ b1 passed > b3 test [...]ms - ✓ b1 passed > nested b > nested b1 test [...]ms - ✓ b1 passed > nested b > nested b2 test [...]ms - ✓ b1 passed > nested b > nested b3 test [...]ms - ✓ b1 failed > b1 test [...]ms - ✓ b1 failed > b2 test [...]ms - ✓ b1 failed > b3 test [...]ms - × b1 failed > b failed test [...]ms - → expected 1 to be 2 // Object.is equality - ✓ b1 failed > nested b > nested b1 test [...]ms - ✓ b1 failed > nested b > nested b2 test [...]ms - ✓ b1 failed > nested b > nested b3 test [...]ms + ✓ b1 test [...]ms + ✓ b2 test [...]ms + ✓ b3 test [...]ms + ✓ nested b1 test [...]ms + ✓ nested b2 test [...]ms + ✓ nested b3 test [...]ms + ✓ b1 test [...]ms + ✓ b2 test [...]ms + ✓ b3 test [...]ms + × b failed test [...]ms + ✓ nested b1 test [...]ms + ✓ nested b2 test [...]ms + ✓ nested b3 test [...]ms ❯ b2.test.ts (13 tests | 1 failed) [...]ms - ✓ b2 passed > b1 test [...]ms - ✓ b2 passed > b2 test [...]ms - ✓ b2 passed > b3 test [...]ms - ✓ b2 passed > nested b > nested b1 test [...]ms - ✓ b2 passed > nested b > nested b2 test [...]ms - ✓ b2 passed > nested b > nested b3 test [...]ms - ✓ b2 failed > b1 test [...]ms - ✓ b2 failed > b2 test [...]ms - ✓ b2 failed > b3 test [...]ms - × b2 failed > b failed test [...]ms - → expected 1 to be 2 // Object.is equality - ✓ b2 failed > nested b > nested b1 test [...]ms - ✓ b2 failed > nested b > nested b2 test [...]ms - ✓ b2 failed > nested b > nested b3 test [...]ms" + ✓ b1 test [...]ms + ✓ b2 test [...]ms + ✓ b3 test [...]ms + ✓ nested b1 test [...]ms + ✓ nested b2 test [...]ms + ✓ nested b3 test [...]ms + ✓ b1 test [...]ms + ✓ b2 test [...]ms + ✓ b3 test [...]ms + × b failed test [...]ms + ✓ nested b1 test [...]ms + ✓ nested b2 test [...]ms + ✓ nested b3 test [...]ms" + `) + }) + + test('normal without fails', async () => { + const { stdout } = await runVitest({ + include: ['b1.test.ts', 'b2.test.ts'], + root: 'fixtures/default', + reporters: ['default'], + fileParallelism: false, + testNamePattern: 'passed', + sequence: { + sequencer: StableTestFileOrderSorter, + }, + }) + + expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(` + "✓ b1.test.ts (13 tests | 7 skipped) [...]ms + ✓ b2.test.ts (13 tests | 7 skipped) [...]ms" `) }) @@ -64,12 +73,30 @@ describe('default reporter', async () => { reporters: 'none', }) - expect(stdout).toContain('✓ a passed > a1 test') - expect(stdout).toContain('✓ a passed > nested a > nested a3 test') - expect(stdout).toContain('× a failed > a failed test') - expect(stdout).toContain('nested a failed 1 test') - expect(stdout).toContain('[note]') - expect(stdout).toContain('[reason]') + expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(` + "❯ a.test.ts (16 tests | 1 failed | 3 skipped) [...]ms + ✓ a passed (6) + ✓ a1 test [...]ms + ✓ a2 test [...]ms + ✓ a3 test [...]ms + ✓ nested a (3) + ✓ nested a1 test [...]ms + ✓ nested a2 test [...]ms + ✓ nested a3 test [...]ms + ❯ a failed (7) + ✓ a failed 1 test [...]ms + ✓ a failed 2 test [...]ms + ✓ a failed 3 test [...]ms + × a failed test [...]ms + ✓ nested a failed (3) + ✓ nested a failed 1 test [...]ms + ✓ nested a failed 2 test [...]ms + ✓ nested a failed 3 test [...]ms + ✓ a skipped (3) + ↓ skipped with note [...]ms [reason] + ↓ condition [...]ms + ↓ condition with note [...]ms [note]" + `) }) test('rerun should undo', async () => { @@ -89,8 +116,10 @@ describe('default reporter', async () => { await vitest.waitForStdout('Filename pattern: a') await vitest.waitForStdout('Waiting for file changes...') - expect(vitest.stdout).contain('✓ a passed > a1 test') - expect(vitest.stdout).contain('✓ a passed > nested a > nested a3 test') + expect(vitest.stdout).toContain('✓ a passed') + expect(vitest.stdout).toContain('✓ a1 test') + expect(vitest.stdout).toContain('✓ nested a') + expect(vitest.stdout).toContain('✓ nested a3 test') // rerun and two files vitest.write('p') @@ -99,9 +128,7 @@ describe('default reporter', async () => { await vitest.waitForStdout('Waiting for file changes...') expect(vitest.stdout).toContain('✓ b1.test.ts') expect(vitest.stdout).toContain('✓ b2.test.ts') - expect(vitest.stdout).not.toContain('✓ nested b1 test') - expect(vitest.stdout).not.toContain('✓ b1 test') - expect(vitest.stdout).not.toContain('✓ b2 test') + expect(vitest.stdout).not.toContain('✓ b2 failed') }) test('doesn\'t print error properties', async () => { @@ -139,9 +166,11 @@ 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 - ✓ passing suite > passing test #2 [...]ms + ✓ passing suite (1) + ✓ passing test #2 [...]ms ↓ skipped test #1 - ↓ skipped suite > skipped test #2" + ↓ skipped suite (1) + ↓ skipped test #2" `) }) @@ -156,7 +185,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 - ✓ passing suite > passing test #2 [...]ms" + ✓ passing suite (1) + ✓ passing test #2 [...]ms" `) }) @@ -190,13 +220,13 @@ describe('default reporter', async () => { reporters: 'none', }) - expect(stdout).toContain('✓ passed > 0-based index of the test case is 0') - expect(stdout).toContain('✓ passed > 0-based index of the test case is 1') - expect(stdout).toContain('✓ passed > 0-based index of the test case is 2') + expect(stdout).toContain('✓ 0-based index of the test case is 0') + expect(stdout).toContain('✓ 0-based index of the test case is 1') + expect(stdout).toContain('✓ 0-based index of the test case is 2') - expect(stdout).toContain('✓ passed > 1-based index of the test case is 1') - expect(stdout).toContain('✓ passed > 1-based index of the test case is 2') - expect(stdout).toContain('✓ passed > 1-based index of the test case is 3') + expect(stdout).toContain('✓ 1-based index of the test case is 1') + expect(stdout).toContain('✓ 1-based index of the test case is 2') + expect(stdout).toContain('✓ 1-based index of the test case is 3') }) test('test.each/for title format', async () => { @@ -255,13 +285,3 @@ describe('default reporter', async () => { expect(stderr).toMatch('FAIL > { name: fails, meta: Failing test added this } (Custom getFullName here') }) }, 120000) - -function trimReporterOutput(report: string) { - const rows = report.replace(/\d+ms/g, '[...]ms').split('\n') - - // Trim start and end, capture just rendered tree - rows.splice(0, 1 + rows.findIndex(row => row.includes('RUN v'))) - rows.splice(rows.findIndex(row => row.includes('Test Files'))) - - return rows.join('\n').trim() -} diff --git a/test/reporters/tests/merge-reports.test.ts b/test/reporters/tests/merge-reports.test.ts index abdfa205a5dc..abe5480a5932 100644 --- a/test/reporters/tests/merge-reports.test.ts +++ b/test/reporters/tests/merge-reports.test.ts @@ -97,15 +97,13 @@ test('merge reports', async () => { ❯ first.test.ts (2 tests | 1 failed)