From 596f73986abe2161a9a06f0ca03df68e82690b21 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 16 Apr 2026 16:42:18 +0900 Subject: [PATCH] fix: project color label on html reporter (#10142) --- .../ui/client/composables/client/index.ts | 11 ++-- .../ui/client/composables/client/static.ts | 13 ++--- packages/ui/node/reporter.ts | 8 ++- packages/vitest/src/api/setup.ts | 2 +- packages/vitest/src/api/types.ts | 7 ++- .../vitest/src/node/config/serializeConfig.ts | 4 +- packages/vitest/src/node/core.ts | 9 +++- packages/vitest/src/public/index.ts | 1 + packages/vitest/src/runtime/config.ts | 6 +++ .../reporters/__snapshots__/html.test.ts.snap | 6 --- test/cli/test/reporters/html.test.ts | 54 +++++++++++++++++++ 11 files changed, 91 insertions(+), 30 deletions(-) diff --git a/packages/ui/client/composables/client/index.ts b/packages/ui/client/composables/client/index.ts index 5f80117a2..7ac61a40b 100644 --- a/packages/ui/client/composables/client/index.ts +++ b/packages/ui/client/composables/client/index.ts @@ -4,7 +4,7 @@ import type { RunnerTaskEventPack, RunnerTaskResultPack, RunnerTestFile, - SerializedConfig, + SerializedRootConfig, TestAnnotation, } from 'vitest' import type { BrowserRunnerState } from '../../../types' @@ -67,7 +67,7 @@ export const client = (function createVitestClient() { } })() -export const config = shallowRef>({} as any) +export const config = shallowRef>({} as any) export const status = ref('CONNECTING') export const availableProjects = shallowRef([]) @@ -171,12 +171,15 @@ watch( ws.addEventListener('open', async () => { status.value = 'OPEN' client.state.filesMap.clear() - let [files, _config, errors, projects] = await Promise.all([ + let [files, _config, errors] = await Promise.all([ client.rpc.getFiles(), client.rpc.getConfig(), client.rpc.getUnhandledErrors(), - client.rpc.getResolvedProjectLabels(), ]) + const projects = _config.projects.map(project => ({ + name: project.name || '', + color: project.color, + })) if (_config.standalone) { const filenames = await client.rpc.getTestFiles() files = filenames.map(([{ name, root }, filepath]) => { diff --git a/packages/ui/client/composables/client/static.ts b/packages/ui/client/composables/client/static.ts index e538dcf19..84be0130d 100644 --- a/packages/ui/client/composables/client/static.ts +++ b/packages/ui/client/composables/client/static.ts @@ -3,7 +3,7 @@ import type { BirpcReturn } from 'birpc' import type { ModuleGraphData, RunnerTestFile, - SerializedConfig, + SerializedRootConfig, WebSocketEvents, WebSocketHandlers, } from 'vitest' @@ -15,8 +15,7 @@ import { StateManager } from '../../../../ws-client/src/state' interface HTMLReportMetadata { paths: string[] files: RunnerTestFile[] - config: SerializedConfig - projects: string[] + config: SerializedRootConfig moduleGraph: Record> unhandledErrors: unknown[] // filename -> source @@ -49,12 +48,6 @@ export function createStaticClient(): VitestClient { getConfig: () => { return metadata.config }, - getResolvedProjectNames: () => { - return metadata.projects - }, - getResolvedProjectLabels: () => { - return [] - }, getModuleGraph: async (projectName, id) => { return metadata.moduleGraph[projectName]?.[id] }, @@ -85,7 +78,7 @@ export function createStaticClient(): VitestClient { saveTestFile: asyncNoop, getProvidedContext: () => ({}), getTestFiles: asyncNoop, - } as WebSocketHandlers + } as Omit ctx.rpc = rpc as any as BirpcReturn diff --git a/packages/ui/node/reporter.ts b/packages/ui/node/reporter.ts index ba5f19173..04b3aa596 100644 --- a/packages/ui/node/reporter.ts +++ b/packages/ui/node/reporter.ts @@ -1,4 +1,4 @@ -import type { ModuleGraphData, RunnerTestFile, SerializedConfig } from 'vitest' +import type { ModuleGraphData, RunnerTestFile, SerializedRootConfig } from 'vitest' import type { HTMLOptions, Reporter, Vitest } from 'vitest/node' import { existsSync, promises as fs } from 'node:fs' import { fileURLToPath } from 'node:url' @@ -29,8 +29,7 @@ function getOutputFile(config: PotentialConfig | undefined) { interface HTMLReportData { paths: string[] files: RunnerTestFile[] - config: SerializedConfig - projects: string[] + config: SerializedRootConfig moduleGraph: Record> unhandledErrors: unknown[] // filename -> source @@ -69,9 +68,8 @@ export default class HTMLReporter implements Reporter { const result: HTMLReportData = { paths: this.ctx.state.getPaths(), files: this.ctx.state.getFiles(), - config: this.ctx.getRootProject().serializedConfig, + config: this.ctx.serializedRootConfig, unhandledErrors: this.ctx.state.getUnhandledErrors(), - projects: this.ctx.projects.map(p => p.name), moduleGraph: {}, sources: {}, } diff --git a/packages/vitest/src/api/setup.ts b/packages/vitest/src/api/setup.ts index 0ce87d4db..875a669d7 100644 --- a/packages/vitest/src/api/setup.ts +++ b/packages/vitest/src/api/setup.ts @@ -98,7 +98,7 @@ export function setup(ctx: Vitest, _server?: ViteDevServer): void { await ctx.rerunTask(id) }, getConfig() { - return ctx.getRootProject().serializedConfig + return ctx.serializedRootConfig }, getResolvedProjectLabels(): { name: string; color?: LabelColor }[] { return ctx.projects.map(p => ({ name: p.name, color: p.color })) diff --git a/packages/vitest/src/api/types.ts b/packages/vitest/src/api/types.ts index 231c1008a..24d100cf7 100644 --- a/packages/vitest/src/api/types.ts +++ b/packages/vitest/src/api/types.ts @@ -1,7 +1,7 @@ import type { File, TaskEventPack, TaskResultPack, TestAnnotation, TestArtifact } from '@vitest/runner' import type { Awaitable } from '@vitest/utils' import type { BirpcReturn } from 'birpc' -import type { SerializedConfig } from '../runtime/config' +import type { SerializedRootConfig } from '../runtime/config' import type { SerializedTestSpecification } from '../runtime/types/utils' import type { LabelColor, ModuleGraphData, UserConsoleLog } from '../types/general' import type { ModuleDefinitionDurationsDiagnostic, UntrackedModuleDefinitionDiagnostic } from '../types/module-locations' @@ -39,7 +39,10 @@ export interface WebSocketHandlers { getFiles: () => File[] getTestFiles: () => Promise getPaths: () => string[] - getConfig: () => SerializedConfig + getConfig: () => SerializedRootConfig + /** + * @deprecated Use `getConfig().projects` instead. + */ getResolvedProjectLabels: () => { name: string; color?: LabelColor }[] getModuleGraph: ( projectName: string, diff --git a/packages/vitest/src/node/config/serializeConfig.ts b/packages/vitest/src/node/config/serializeConfig.ts index 98706dff6..c49676612 100644 --- a/packages/vitest/src/node/config/serializeConfig.ts +++ b/packages/vitest/src/node/config/serializeConfig.ts @@ -1,5 +1,6 @@ +import type { SerializedConfig } from '../../runtime/config' import type { TestProject } from '../project' -import type { ApiConfig, SerializedConfig } from '../types/config' +import type { ApiConfig } from '../types/config' import { configDefaults } from '../../defaults' import { isAgent } from '../../utils/env' @@ -46,6 +47,7 @@ export function serializeConfig(project: TestProject): SerializedConfig { disableConsoleIntercept: config.disableConsoleIntercept, root: config.root, name: config.name, + color: config.color, globals: config.globals, snapshotEnvironment: config.snapshotEnvironment, passWithNoTests: config.passWithNoTests, diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 1853ac572..93834a7f3 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -3,7 +3,7 @@ import type { Awaitable } from '@vitest/utils' import type { Writable } from 'node:stream' import type { ViteDevServer } from 'vite' import type { ModuleRunner } from 'vite/module-runner' -import type { SerializedCoverageConfig } from '../runtime/config' +import type { SerializedCoverageConfig, SerializedRootConfig } from '../runtime/config' import type { ArgumentsType, ProvidedContext, UserConsoleLog } from '../types/general' import type { SourceModuleDiagnostic, SourceModuleLocations } from '../types/module-locations' import type { CliOptions } from './cli/cli-api' @@ -503,6 +503,13 @@ export class Vitest { return this.coreWorkspaceProject } + public get serializedRootConfig(): SerializedRootConfig { + return { + ...this.getRootProject().serializedConfig, + projects: this.projects.map(project => project.serializedConfig), + } + } + public getProjectByName(name: string): TestProject { const project = this.projects.find(p => p.name === name) || this.coreWorkspaceProject diff --git a/packages/vitest/src/public/index.ts b/packages/vitest/src/public/index.ts index 6bf7a83b9..35ddd19cc 100644 --- a/packages/vitest/src/public/index.ts +++ b/packages/vitest/src/public/index.ts @@ -40,6 +40,7 @@ export type { RuntimeConfig, SerializedConfig, SerializedCoverageConfig, + SerializedRootConfig, } from '../runtime/config' export { VitestEvaluatedModules as EvaluatedModules } from '../runtime/moduleRunner/evaluatedModules' diff --git a/packages/vitest/src/runtime/config.ts b/packages/vitest/src/runtime/config.ts index 702c0c604..27bdb2b75 100644 --- a/packages/vitest/src/runtime/config.ts +++ b/packages/vitest/src/runtime/config.ts @@ -3,12 +3,14 @@ import type { PrettyFormatOptions } from '@vitest/pretty-format' import type { SequenceHooks, SequenceSetupFiles, SerializableRetry, TestTagDefinition } from '@vitest/runner' import type { SnapshotEnvironment, SnapshotUpdateState } from '@vitest/snapshot' import type { SerializedDiffOptions } from '@vitest/utils/diff' +import type { LabelColor } from '../types/general' /** * Config that tests have access to. */ export interface SerializedConfig { name: string | undefined + color?: LabelColor globals: boolean base: string | undefined snapshotEnvironment?: string @@ -158,6 +160,10 @@ export interface SerializedCoverageConfig { customProviderModule: string | undefined } +export interface SerializedRootConfig extends SerializedConfig { + projects: SerializedConfig[] +} + export type RuntimeConfig = Pick< SerializedConfig, | 'allowOnly' diff --git a/test/cli/test/reporters/__snapshots__/html.test.ts.snap b/test/cli/test/reporters/__snapshots__/html.test.ts.snap index 723134a6c..8bf3047d3 100644 --- a/test/cli/test/reporters/__snapshots__/html.test.ts.snap +++ b/test/cli/test/reporters/__snapshots__/html.test.ts.snap @@ -109,9 +109,6 @@ exports[`html reporter > resolves to "failing" status for test file "json-fail" "paths": [ "/test/cli/fixtures/reporters/json-fail.test.ts", ], - "projects": [ - "", - ], "sources": { "/test/cli/fixtures/reporters/json-fail.test.ts": "import { expect, test } from 'vitest' @@ -218,9 +215,6 @@ exports[`html reporter > resolves to "passing" status for test file "all-passing "paths": [ "/test/cli/fixtures/reporters/all-passing-or-skipped.test.ts", ], - "projects": [ - "", - ], "sources": { "/test/cli/fixtures/reporters/all-passing-or-skipped.test.ts": "import { expect, test } from 'vitest' diff --git a/test/cli/test/reporters/html.test.ts b/test/cli/test/reporters/html.test.ts index 13a60bb3b..5d6cb1bf2 100644 --- a/test/cli/test/reporters/html.test.ts +++ b/test/cli/test/reporters/html.test.ts @@ -170,3 +170,57 @@ test('add', () => { } `) }) + +it('projects', async () => { + const result = await runInlineTests({ + 'basic.test.ts': /* ts */` +import { test } from "vitest"; +test('basic', () => {}); +`, + }, { + reporters: ['default', 'html'], + projects: [ + { + test: { + name: { + label: 'project1', + color: 'black', + }, + }, + }, + { + test: { + name: { + label: 'project2', + color: 'white', + }, + }, + }, + ], + }) + expect(result.stderr).toMatchInlineSnapshot(`""`) + expect(result.errorTree({ project: true })).toMatchInlineSnapshot(` + { + "project1": { + "basic.test.ts": { + "basic": "passed", + }, + }, + "project2": { + "basic.test.ts": { + "basic": "passed", + }, + }, + } + `) + expect(result.ctx?.serializedRootConfig.projects).toMatchObject([ + { + name: 'project1', + color: 'black', + }, + { + name: 'project2', + color: 'white', + }, + ]) +}) -- 2.51.2