diff --git a/packages/vitest/src/node/reporters/renderers/dotRenderer.ts b/packages/vitest/src/node/reporters/renderers/dotRenderer.ts index 5b323fc05ea2..86a8e09ccb10 100644 --- a/packages/vitest/src/node/reporters/renderers/dotRenderer.ts +++ b/packages/vitest/src/node/reporters/renderers/dotRenderer.ts @@ -27,54 +27,74 @@ function getIcon(task: Task) { } } -function render(tasks: Task[]): string { +function render(tasks: Task[], width: number): string { const all = getTests(tasks) - const output: string[] = [] + let currentIcon = pending + let currentTasks = 0 + let previousLineWidth = 0 + let output = '' // The log-update uses various ANSI helper utilities, e.g. ansi-warp, ansi-slice, // when printing. Passing it hundreds of single characters containing ANSI codes reduces // performances. We can optimize it by reducing amount of ANSI codes, e.g. by coloring // multiple tasks at once instead of each task separately. - let currentIcon = pending - let currentTasks = 0 - - const addOutput = () => output.push(currentIcon.color(currentIcon.char.repeat(currentTasks))) + const addOutput = () => { + const { char, color } = currentIcon + const availableWidth = width - previousLineWidth + if (availableWidth > currentTasks) { + output += color(char.repeat(currentTasks)) + previousLineWidth += currentTasks + } + else { + // We need to split the line otherwise it will mess up log-update's height calculation + // and spam the scrollback buffer with dots. + + // Fill the current line first + let buf = `${char.repeat(availableWidth)}\n` + const remaining = currentTasks - availableWidth + + // Then fill as many full rows as possible + const fullRows = Math.floor(remaining / width) + buf += `${char.repeat(width)}\n`.repeat(fullRows) + + // Add remaining dots which don't make a full row + const partialRow = remaining % width + if (partialRow > 0) { + buf += char.repeat(partialRow) + previousLineWidth = partialRow + } + else { + previousLineWidth = 0 + } + output += color(buf) + } + } for (const task of all) { const icon = getIcon(task) - const isLast = all.indexOf(task) === all.length - 1 - if (icon === currentIcon) { currentTasks++ - - if (isLast) - addOutput() - continue } - // Task mode/state has changed, add previous group to output addOutput() // Start tracking new group currentTasks = 1 currentIcon = icon - - if (isLast) - addOutput() } - - return output.join('') + addOutput() + return output } export function createDotRenderer(_tasks: Task[], options: DotRendererOptions) { let tasks = _tasks let timer: any - const log = options.logger.logUpdate + const { logUpdate: log, outputStream } = options.logger function update() { - log(render(tasks)) + log(render(tasks, outputStream.columns)) } return { @@ -94,7 +114,7 @@ export function createDotRenderer(_tasks: Task[], options: DotRendererOptions) { timer = undefined } log.clear() - options.logger.log(render(tasks)) + options.logger.log(render(tasks, outputStream.columns)) return this }, clear() {