From 0e34c0311d58f968e511e6488e1236de152ea190 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:41:25 -0500 Subject: [PATCH] fix(@angular/build): allow overriding Vitest coverage `reportsDirectory` option The Vitest `reportsDirectory` coverage option can now be customized via a `runnerConfig` configuration file. --- .../unit-test/runners/vitest/plugins.ts | 32 +++++++----- .../behavior/runner-config-vitest_spec.ts | 50 +++++++++++++++++++ 2 files changed, 70 insertions(+), 12 deletions(-) diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts index 93ef7cfb9f85..102b6a6101fa 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts @@ -154,7 +154,11 @@ export async function createVitestConfigPlugin( return { test: { - coverage: await generateCoverageOption(options.coverage, projectName), + coverage: await generateCoverageOption( + options.coverage, + testConfig?.coverage, + projectName, + ), // eslint-disable-next-line @typescript-eslint/no-explicit-any ...(reporters ? ({ reporters } as any) : {}), projects: [projectConfig], @@ -303,11 +307,12 @@ export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins } async function generateCoverageOption( - coverage: NormalizedUnitTestBuilderOptions['coverage'], + optionsCoverage: NormalizedUnitTestBuilderOptions['coverage'], + configCoverage: VitestCoverageOption | undefined, projectName: string, ): Promise { let defaultExcludes: string[] = []; - if (coverage.exclude) { + if (optionsCoverage.exclude) { try { const vitestConfig = await import('vitest/config'); defaultExcludes = vitestConfig.coverageConfigDefaults.exclude; @@ -315,28 +320,31 @@ async function generateCoverageOption( } return { - enabled: coverage.enabled, excludeAfterRemap: true, + reportsDirectory: + configCoverage?.reportsDirectory ?? toPosixPath(path.join('coverage', projectName)), + ...(optionsCoverage.enabled !== undefined ? { enabled: optionsCoverage.enabled } : {}), // Vitest performs a pre-check and a post-check for sourcemaps. // The pre-check uses the bundled files, so specific bundled entry points and chunks need to be included. // The post-check uses the original source files, so the user's include is used. - ...(coverage.include ? { include: ['spec-*.js', 'chunk-*.js', ...coverage.include] } : {}), - reportsDirectory: toPosixPath(path.join('coverage', projectName)), - thresholds: coverage.thresholds, - watermarks: coverage.watermarks, + ...(optionsCoverage.include + ? { include: ['spec-*.js', 'chunk-*.js', ...optionsCoverage.include] } + : {}), + thresholds: optionsCoverage.thresholds, + watermarks: optionsCoverage.watermarks, // Special handling for `exclude`/`reporters` due to an undefined value causing upstream failures - ...(coverage.exclude + ...(optionsCoverage.exclude ? { exclude: [ // Augment the default exclude https://vitest.dev/config/#coverage-exclude // with the user defined exclusions - ...coverage.exclude, + ...optionsCoverage.exclude, ...defaultExcludes, ], } : {}), - ...(coverage.reporters - ? ({ reporter: coverage.reporters } satisfies VitestCoverageOption) + ...(optionsCoverage.reporters + ? ({ reporter: optionsCoverage.reporters } satisfies VitestCoverageOption) : {}), }; } diff --git a/packages/angular/build/src/builders/unit-test/tests/behavior/runner-config-vitest_spec.ts b/packages/angular/build/src/builders/unit-test/tests/behavior/runner-config-vitest_spec.ts index f68dd8daa171..603c69f533ea 100644 --- a/packages/angular/build/src/builders/unit-test/tests/behavior/runner-config-vitest_spec.ts +++ b/packages/angular/build/src/builders/unit-test/tests/behavior/runner-config-vitest_spec.ts @@ -42,6 +42,56 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { harness.expectFile('vitest-results.xml').toExist(); }); + it('should use custom reportsDirectory defined in runnerConfig file', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + runnerConfig: 'vitest.config.ts', + coverage: true, + }); + + harness.writeFile( + 'vitest.config.ts', + ` + import { defineConfig } from 'vitest/config'; + export default defineConfig({ + test: { + coverage: { + reportsDirectory: './custom-coverage-reports', + }, + }, + }); + `, + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectFile('custom-coverage-reports/coverage-final.json').toExist(); + }); + + it('should use default reportsDirectory when not defined in runnerConfig file', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + coverage: true, + runnerConfig: 'vitest.config.ts', + }); + + harness.writeFile( + 'vitest.config.ts', + ` + import { defineConfig } from 'vitest/config'; + export default defineConfig({ + test: { + coverage: {}, + }, + }); + `, + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectFile('coverage/test/coverage-final.json').toExist(); + }); + it('should exclude test files based on runnerConfig file', async () => { harness.useTarget('test', { ...BASE_OPTIONS,