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