From 92d4358754b09fe3fc9b4ba0392c89a184b877c9 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 2 Jun 2026 16:18:35 +0900 Subject: [PATCH 1/2] refactor(ui): simplify HTML reporter asset handling (#10498) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Codex --- packages/ui/node/index.ts | 4 +-- packages/ui/node/paths.ts | 4 +++ packages/ui/node/reporter.ts | 60 ++++++++++++------------------------ 3 files changed, 26 insertions(+), 42 deletions(-) create mode 100644 packages/ui/node/paths.ts diff --git a/packages/ui/node/index.ts b/packages/ui/node/index.ts index ac849e50a232..7b877d960a51 100644 --- a/packages/ui/node/index.ts +++ b/packages/ui/node/index.ts @@ -1,13 +1,13 @@ import type { Vite, Vitest } from 'vitest/node' import fs from 'node:fs' -import { fileURLToPath } from 'node:url' import { join, resolve } from 'pathe' import sirv from 'sirv' import c from 'tinyrainbow' import { isFileServingAllowed, isValidApiRequest } from 'vitest/node' import { version } from '../package.json' +import { distClientRoot } from './paths' -export const distClientRoot: string = resolve(fileURLToPath(import.meta.url), '../client') +export { distClientRoot } export default (ctx: Vitest): Vite.Plugin => { if (ctx.version !== version) { diff --git a/packages/ui/node/paths.ts b/packages/ui/node/paths.ts new file mode 100644 index 000000000000..32cbad0ac418 --- /dev/null +++ b/packages/ui/node/paths.ts @@ -0,0 +1,4 @@ +import { fileURLToPath } from 'node:url' +import { resolve } from 'pathe' + +export const distClientRoot: string = resolve(fileURLToPath(import.meta.url), '../client') diff --git a/packages/ui/node/reporter.ts b/packages/ui/node/reporter.ts index 0e9419b341bb..f5ec42ca5387 100644 --- a/packages/ui/node/reporter.ts +++ b/packages/ui/node/reporter.ts @@ -1,23 +1,20 @@ import type { TestAttachment } from '@vitest/runner' import type { SerializedError } from 'vitest' -import type { HTMLOptions, Reporter, RunnerTask, RunnerTestFile, TestModule, Vitest } from 'vitest/node' +import type { HTMLOptions, Reporter, ResolvedConfig, RunnerTask, RunnerTestFile, TestModule, Vitest } from 'vitest/node' import type { HTMLReportMetadata } from '../client/composables/client/static' import { existsSync, promises as fs, readFileSync } from 'node:fs' -import { fileURLToPath } from 'node:url' import { promisify } from 'node:util' import { gzip, constants as zlibConstants } from 'node:zlib' import { stringify } from 'flatted' import { dirname, relative, resolve } from 'pathe' -import { globSync } from 'tinyglobby' import c from 'tinyrainbow' import { getModuleGraph } from '../../vitest/src/utils/graph' +import { distClientRoot } from './paths' -interface PotentialConfig { - outputFile?: string | Partial> -} +const gzipAsync = promisify(gzip) -function getOutputFile(config: PotentialConfig | undefined) { - if (!config?.outputFile) { +function getOutputFile(config: ResolvedConfig) { + if (!config.outputFile) { return } @@ -28,15 +25,11 @@ function getOutputFile(config: PotentialConfig | undefined) { return config.outputFile.html } -const distDir = resolve(fileURLToPath(import.meta.url), '../../dist') - export default class HTMLReporter implements Reporter { - start = 0 ctx!: Vitest options: HTMLOptions private reporterDir!: string - private htmlFilePath!: string constructor(options: HTMLOptions) { this.options = options @@ -44,16 +37,12 @@ export default class HTMLReporter implements Reporter { async onInit(ctx: Vitest): Promise { this.ctx = ctx - this.start = Date.now() const htmlFile = this.options.outputFile || getOutputFile(this.ctx.config) || 'html/index.html' const htmlFilePath = resolve(this.ctx.config.root, htmlFile) this.reporterDir = dirname(htmlFilePath) - this.htmlFilePath = htmlFilePath - - await fs.mkdir(resolve(this.reporterDir, 'assets'), { recursive: true }) } async onTestRunEnd( @@ -69,29 +58,20 @@ export default class HTMLReporter implements Reporter { await inlineAttachments(result.files) } - const report = stringify(result) - const promiseGzip = promisify(gzip) - const data = await promiseGzip(report, { + // copy ui assets + await fs.cp(distClientRoot, this.reporterDir, { recursive: true }) + + // create index.html and metadata + const rawData = stringify(result) + const data = await gzipAsync(rawData, { level: zlibConstants.Z_BEST_COMPRESSION, }) - const ui = resolve(distDir, 'client') - // copy ui - const files = globSync(['**/*'], { cwd: ui, expandDirectories: false }) - await Promise.all( - files.map(async (f) => { - if (f === 'index.html') { - await handleIndexHtml({ - srcDir: ui, - dstDir: this.reporterDir, - data, - singleFile: this.options.singleFile, - }) - } - else { - await fs.copyFile(resolve(ui, f), resolve(this.reporterDir, f)) - } - }), - ) + await handleIndexHtml({ + srcDir: distClientRoot, + dstDir: this.reporterDir, + data, + singleFile: this.options.singleFile, + }) // copy attachments // TODO: unify attachmentsDir and html outputFile, so both live together without extra copy @@ -220,12 +200,12 @@ async function handleIndexHtml(options: { if (options.singleFile) { html = await inlineHtmlAssets(indexHtmlFilePath, html) - const base64 = Buffer.from(options.data).toString('base64') + const base64 = options.data.toString('base64') metadataCode = `Promise.resolve((${uint8ArrayFromBase64.toString()})("${base64}"))` } else { - const dataFile = `html.meta.json.gz` - await fs.writeFile(resolve(options.dstDir, dataFile), options.data, 'base64') + const dataFile = 'html.meta.json.gz' + await fs.writeFile(resolve(options.dstDir, dataFile), options.data) metadataCode = `fetch(new URL("./${dataFile}", window.location.href)).then(async res => new Uint8Array(await res.arrayBuffer()))` } From 7cb346c83dee27d4956395e437962df7b9dbb1ff Mon Sep 17 00:00:00 2001 From: "Bonaventure C. J. Ugwu" <73999585+BonaventureCJ@users.noreply.github.com> Date: Tue, 2 Jun 2026 08:19:13 +0100 Subject: [PATCH 2/2] docs: clarify placeholder usage (#10496) --- docs/guide/learn/writing-tests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/learn/writing-tests.md b/docs/guide/learn/writing-tests.md index a047001bd2f3..35be6bb31633 100644 --- a/docs/guide/learn/writing-tests.md +++ b/docs/guide/learn/writing-tests.md @@ -176,7 +176,7 @@ test.for([ }) ``` -The placeholders `%i`, `%s`, and `%f` in the test name are replaced with the corresponding values from each row, so the output shows `add(1, 1) -> 2`, `add(1, 2) -> 3`, and so on. +In the example above, the %i placeholders are replaced with the integer values from each data row. Vitest also supports other placeholder types, such as %s for strings and %f for floating-point numbers. As a result, the test runner generates test names such as add(1, 1) -> 2, add(1, 2) -> 3, and add(2, 1) -> 3. If your cases have more than two or three values, passing objects is more readable. Use `$property` in the name to interpolate fields: