From 800a7ebb31b190480cd18af1e3fb0168d2f12cda Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 28 Oct 2025 20:44:03 -0400 Subject: [PATCH] refactor(@angular/build): normalize unit test coverage options for consistency Previously, the unit test builder's `coverage` option was conditional. It was only defined if `coverage: true` was set, and it consolidated several top-level `coverage*` properties from the schema. This required downstream consumers, like the Karma and Vitest executors, to handle a potentially undefined object and led to complex and sometimes incorrect option merging, especially in the Vitest runner. This commit refactors the option normalization logic to ensure the `coverage` object is *always* defined. A new `enabled` flag, controlled by the main `coverage` option, is now part of this object. --- .../build/src/builders/unit-test/options.ts | 31 +++++++++---------- .../unit-test/runners/karma/executor.ts | 6 ++-- .../unit-test/runners/vitest/executor.ts | 8 +---- .../unit-test/runners/vitest/index.ts | 2 +- 4 files changed, 20 insertions(+), 27 deletions(-) diff --git a/packages/angular/build/src/builders/unit-test/options.ts b/packages/angular/build/src/builders/unit-test/options.ts index ef7132198451..74b30b1e95a6 100644 --- a/packages/angular/build/src/builders/unit-test/options.ts +++ b/packages/angular/build/src/builders/unit-test/options.ts @@ -96,22 +96,21 @@ export async function normalizeOptions( exclude: options.exclude, filter, runnerName: runner ?? 'vitest', - coverage: options.coverage - ? { - exclude: options.coverageExclude, - include: options.coverageInclude, - reporters: normalizeReporterOption(options.coverageReporters), - thresholds: options.coverageThresholds, - // The schema generation tool doesn't support tuple types for items, but the schema validation - // does ensure that the array has exactly two numbers. - watermarks: options.coverageWatermarks as { - statements?: [number, number]; - branches?: [number, number]; - functions?: [number, number]; - lines?: [number, number]; - }, - } - : undefined, + coverage: { + enabled: options.coverage, + exclude: options.coverageExclude, + include: options.coverageInclude, + reporters: normalizeReporterOption(options.coverageReporters), + thresholds: options.coverageThresholds, + // The schema generation tool doesn't support tuple types for items, but the schema validation + // does ensure that the array has exactly two numbers. + watermarks: options.coverageWatermarks as { + statements?: [number, number]; + branches?: [number, number]; + functions?: [number, number]; + lines?: [number, number]; + }, + }, tsConfig, buildProgress: progress, reporters: normalizeReporterOption(options.reporters), diff --git a/packages/angular/build/src/builders/unit-test/runners/karma/executor.ts b/packages/angular/build/src/builders/unit-test/runners/karma/executor.ts index 9d1627df000b..4b30ff0405bf 100644 --- a/packages/angular/build/src/builders/unit-test/runners/karma/executor.ts +++ b/packages/angular/build/src/builders/unit-test/runners/karma/executor.ts @@ -87,8 +87,8 @@ export class KarmaExecutor implements TestExecutor { poll: buildTargetOptions.poll, preserveSymlinks: buildTargetOptions.preserveSymlinks, browsers: unitTestOptions.browsers?.join(','), - codeCoverage: !!unitTestOptions.coverage, - codeCoverageExclude: unitTestOptions.coverage?.exclude, + codeCoverage: unitTestOptions.coverage.enabled, + codeCoverageExclude: unitTestOptions.coverage.exclude, fileReplacements: buildTargetOptions.fileReplacements, reporters: unitTestOptions.reporters?.map((reporter) => { // Karma only supports string reporters. @@ -123,7 +123,7 @@ export class KarmaExecutor implements TestExecutor { } // Add coverage options - if (unitTestOptions.coverage) { + if (unitTestOptions.coverage.enabled) { const { thresholds, watermarks } = unitTestOptions.coverage; // eslint-disable-next-line @typescript-eslint/no-explicit-any const coverageReporter = ((options as any).coverageReporter ??= {}); diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts index 4ffebb0ad28b..69668ace1be4 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts @@ -242,12 +242,6 @@ async function generateCoverageOption( coverage: NormalizedUnitTestBuilderOptions['coverage'], projectName: string, ): Promise { - if (!coverage) { - return { - enabled: false, - }; - } - let defaultExcludes: string[] = []; if (coverage.exclude) { try { @@ -257,7 +251,7 @@ async function generateCoverageOption( } return { - enabled: true, + enabled: coverage.enabled, excludeAfterRemap: true, include: coverage.include, reportsDirectory: toPosixPath(path.join('coverage', projectName)), diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/index.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/index.ts index 4c8e8ad7043b..a36b75331388 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/index.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/index.ts @@ -32,7 +32,7 @@ const VitestTestRunner: TestRunner = { checker.check('jsdom'); } - if (options.coverage) { + if (options.coverage.enabled) { checker.check('@vitest/coverage-v8'); }