Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/vitest/src/node/reporters/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { SummaryReporter } from './summary'

export interface DefaultReporterOptions extends BaseOptions {
summary?: boolean

/** @internal */
summaryOptions?: SummaryReporter['options']
}

export class DefaultReporter extends BaseReporter {
Expand Down Expand Up @@ -87,6 +90,6 @@ export class DefaultReporter extends BaseReporter {

onInit(ctx: Vitest): void {
super.onInit(ctx)
this.summary?.onInit(ctx, { verbose: this.verbose })
this.summary?.onInit(ctx, { verbose: this.verbose, ...this.options.summaryOptions })
}
}
36 changes: 29 additions & 7 deletions packages/vitest/src/node/reporters/renderers/windowedRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import type { Writable } from 'node:stream'
import type { Vitest } from '../../core'
import { stripVTControlCharacters } from 'node:util'

/** Minimum time between two renders, no matter how many scheduled renderes were called */
const DEFAULT_RENDER_THRESHOLD_MS = 100

/** Interval between automatic renders. If no test state changes happened, this will increase just duration field */
const DEFAULT_RENDER_INTERVAL_MS = 1_000

const ESC = '\x1B['
Expand All @@ -10,9 +14,10 @@ const MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`
const SYNC_START = `${ESC}?2026h`
const SYNC_END = `${ESC}?2026l`

interface Options {
export interface Options {
logger: Vitest['logger']
interval?: number
threshold?: number
getWindow: () => string[]
}

Expand All @@ -36,10 +41,12 @@ export class WindowRenderer {

constructor(options: Options) {
this.options = {
interval: DEFAULT_RENDER_INTERVAL_MS,
...options,
threshold: options.threshold ?? DEFAULT_RENDER_THRESHOLD_MS,
interval: options.interval ?? DEFAULT_RENDER_INTERVAL_MS,
}

// Capture the original write methods early, before intercepting these
this.streams = {
output: options.logger.outputStream.write.bind(options.logger.outputStream),
error: options.logger.errorStream.write.bind(options.logger.errorStream),
Expand All @@ -50,6 +57,14 @@ export class WindowRenderer {
this.interceptStream(process.stderr, 'error'),
)

// Intercept calls to custom VitestOptions.stdout and stderr streams
if (options.logger.outputStream !== process.stdout) {
this.cleanups.push(this.interceptStream(options.logger.outputStream, 'output'))
}
if (options.logger.errorStream !== process.stderr) {
this.cleanups.push(this.interceptStream(options.logger.errorStream, 'error'))
}

// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
this.options.logger.onTerminalCleanup(() => {
this.flushBuffer()
Expand Down Expand Up @@ -86,9 +101,14 @@ export class WindowRenderer {
this.renderScheduled = true
this.flushBuffer()

setTimeout(() => {
if (this.options.threshold) {
setTimeout(() => {
this.renderScheduled = false
}, this.options.threshold).unref()
}
else {
this.renderScheduled = false
}, 100).unref()
}
}
}

Expand Down Expand Up @@ -121,9 +141,12 @@ export class WindowRenderer {
}

private render(message?: string, type: StreamType = 'output') {
this.write(SYNC_START)

if (this.finished) {
this.clearWindow()
return this.write(message || '', type)
this.write(message || '', type)
return this.write(SYNC_END)
}

const windowContent = this.options.getWindow()
Expand All @@ -134,7 +157,6 @@ export class WindowRenderer {
padding -= getRenderedRowCount([message], this.options.logger.getColumns())
}

this.write(SYNC_START)
this.clearWindow()

if (message) {
Expand Down Expand Up @@ -165,7 +187,7 @@ export class WindowRenderer {
this.windowHeight = 0
}

private interceptStream(stream: NodeJS.WriteStream, type: StreamType) {
private interceptStream(stream: NodeJS.WriteStream | Writable, type: StreamType) {
const original = stream.write

// @ts-expect-error -- not sure how 2 overloads should be typed
Expand Down
9 changes: 9 additions & 0 deletions packages/vitest/src/node/reporters/summary.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Vitest } from '../core'
import type { TestSpecification } from '../test-specification'
import type { Reporter } from '../types/reporter'
import type { Options as WindowRendererOptions } from './renderers/windowedRenderer'
import type { ReportedHookContext, TestCase, TestModule } from './reported-tasks'
import c from 'tinyrainbow'
import { F_POINTER, F_TREE_NODE_END, F_TREE_NODE_MIDDLE } from './renderers/figures'
Expand All @@ -12,6 +13,12 @@ const FINISHED_TEST_CLEANUP_TIME_MS = 1_000

interface Options {
verbose?: boolean

/** @internal */
interval?: WindowRendererOptions['interval']

/** @internal */
threshold?: WindowRendererOptions['threshold']
}

interface Counter {
Expand Down Expand Up @@ -76,6 +83,8 @@ export class SummaryReporter implements Reporter {
this.renderer = new WindowRenderer({
logger: ctx.logger,
getWindow: () => this.createSummary(),
interval: this.options.interval,
threshold: this.options.threshold,
})

this.ctx.onClose(() => {
Expand Down
18 changes: 18 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ catalog:
'@vitejs/plugin-vue': ^6.0.4
'@vueuse/core': ^14.2.1
acorn-walk: ^8.3.5
ansivision: ^0.2.7
birpc: ^4.0.0
cac: ^6.7.14
chai: ^6.2.2
Expand Down
21 changes: 21 additions & 0 deletions test/e2e/fixtures/reporters/summary/first.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { setTimeout } from 'node:timers/promises'
import { describe, test } from 'vitest'

const TIMEOUT = 100;

// Test queue time
await setTimeout(TIMEOUT);

describe("suite", () => {
test("one",async () => {
await setTimeout(TIMEOUT);
})

test("two", async () => {
await setTimeout(TIMEOUT);
})

test("three", async () => {
await setTimeout(TIMEOUT);
})
})
21 changes: 21 additions & 0 deletions test/e2e/fixtures/reporters/summary/second.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { setTimeout } from 'node:timers/promises'
import { describe, test } from 'vitest'

const TIMEOUT = 100;

// Test queue time
await setTimeout(TIMEOUT);

describe("suite", () => {
test("one",async () => {
await setTimeout(TIMEOUT);
})

test("two", async () => {
await setTimeout(TIMEOUT);
})

test("three", async () => {
await setTimeout(TIMEOUT);
})
})
1 change: 1 addition & 0 deletions test/e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@vitest/mocker": "workspace:*",
"@vitest/runner": "workspace:*",
"@vitest/utils": "workspace:*",
"ansivision": "catalog:",
"flatted": "catalog:",
"jest-image-snapshot": "^6.5.1",
"obug": "^2.1.1",
Expand Down
Loading
Loading