From 54f767395a12a4c91d3c38737bdb5600bf7453cd Mon Sep 17 00:00:00 2001 From: Maruthan G Date: Thu, 2 Apr 2026 18:19:57 +0530 Subject: [PATCH] fix(@angular/build): resolve coverage overrides paths relative to workspace root karma-coverage normalises coverage-map keys using the karma `basePath`, which the builder sets to the temporary build output directory. As a result, override patterns written relative to the workspace root (e.g. `src/app/app.ts`) were never matched and the overrides were silently ignored. Fix: after parsing the karma config, rewrite the keys of `coverageReporter.check.global.overrides` and `coverageReporter.check.each.overrides` so they are expressed as paths relative to `basePath` (the temp output path), matching how karma-coverage itself normalises the keys in the coverage map. Closes #30956 --- .../src/builders/karma/application_builder.ts | 38 ++++++++ .../tests/behavior/coverage-overrides_spec.ts | 87 +++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 packages/angular/build/src/builders/karma/tests/behavior/coverage-overrides_spec.ts diff --git a/packages/angular/build/src/builders/karma/application_builder.ts b/packages/angular/build/src/builders/karma/application_builder.ts index 34e94b1b7645..4892ac08dada 100644 --- a/packages/angular/build/src/builders/karma/application_builder.ts +++ b/packages/angular/build/src/builders/karma/application_builder.ts @@ -413,5 +413,43 @@ async function configureKarma( parsedKarmaConfig.reporters = (parsedKarmaConfig.reporters ?? []).concat(['coverage']); } + // Adjust coverage reporter `overrides` paths to be relative to the karma `basePath` + // (the temporary build output directory). Users write override patterns relative to the + // workspace root, but karma-coverage normalises coverage-map keys using `basePath`, so the + // patterns must be expressed as paths relative to that same directory. + // See https://github.com/angular/angular-cli/issues/30956 + if ('coverageReporter' in parsedKarmaConfig) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const coverageReporterOptions = (parsedKarmaConfig as any)['coverageReporter'] as { + check?: { + global?: { overrides?: Record }; + each?: { overrides?: Record }; + }; + }; + + const rebaseOverrides = (overrides: Record): Record => { + const rebased: Record = {}; + for (const [pattern, thresholds] of Object.entries(overrides)) { + // Resolve the user pattern from the workspace root, then make it relative + // to the karma basePath (outputPath) so it matches how karma-coverage + // normalises coverage-map keys. + const absolutePattern = path.isAbsolute(pattern) + ? pattern + : path.join(context.workspaceRoot, pattern); + const rebasedPattern = path.relative(outputPath, absolutePattern); + rebased[rebasedPattern] = thresholds; + } + return rebased; + }; + + const check = coverageReporterOptions?.check; + if (check?.global?.overrides && typeof check.global.overrides === 'object') { + check.global.overrides = rebaseOverrides(check.global.overrides); + } + if (check?.each?.overrides && typeof check.each.overrides === 'object') { + check.each.overrides = rebaseOverrides(check.each.overrides); + } + } + return parsedKarmaConfig; } diff --git a/packages/angular/build/src/builders/karma/tests/behavior/coverage-overrides_spec.ts b/packages/angular/build/src/builders/karma/tests/behavior/coverage-overrides_spec.ts new file mode 100644 index 000000000000..92e196858124 --- /dev/null +++ b/packages/angular/build/src/builders/karma/tests/behavior/coverage-overrides_spec.ts @@ -0,0 +1,87 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { last, tap } from 'rxjs'; +import { execute } from '../../index'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; + +/** + * Regression test for https://github.com/angular/angular-cli/issues/30956 + * + * Coverage `overrides` in karma.conf.js were ignored because karma-coverage + * resolves the override patterns relative to the karma `basePath`, which is + * the temporary build output directory. Patterns written relative to the + * workspace root (e.g. `src/app/app.component.ts`) never matched. + */ +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { + describe('Behavior: "coverageReporter.check overrides"', () => { + beforeEach(async () => { + await setupTarget(harness); + }); + + it('should respect per-file overrides in coverageReporter.check.each.overrides', async () => { + // Add a function that won't be covered – with a 100% global threshold this + // would normally cause a failure. The override for app.component.ts drops + // the threshold to 0 so the run should still succeed. + await harness.appendToFile( + 'src/app/app.component.ts', + ` +export function notCovered(): boolean { + return true; +} + `, + ); + + await harness.modifyFile('karma.conf.js', (content) => + content.replace( + 'coverageReporter: {', + `coverageReporter: { + check: { + global: { + statements: 100, + lines: 100, + branches: 100, + functions: 100, + }, + each: { + statements: 0, + lines: 0, + branches: 0, + functions: 0, + overrides: { + 'src/app/app.component.ts': { + statements: 0, + lines: 0, + branches: 0, + functions: 0, + }, + }, + }, + }, + `, + ), + ); + + harness.useTarget('test', { + ...BASE_OPTIONS, + codeCoverage: true, + }); + + // If overrides are ignored the global 100% threshold will be applied to + // app.component.ts and the run will fail. With the fix the per-file + // override (0%) is used and the run succeeds. + await harness + .execute() + .pipe( + last(), + tap((buildEvent) => expect(buildEvent.result?.success).toBeTrue()), + ) + .toPromise(); + }); + }); +});