Skip to content

Commit 54f7673

Browse files
committed
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
1 parent 7fbc715 commit 54f7673

File tree

2 files changed

+125
-0
lines changed

2 files changed

+125
-0
lines changed

packages/angular/build/src/builders/karma/application_builder.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,5 +413,43 @@ async function configureKarma(
413413
parsedKarmaConfig.reporters = (parsedKarmaConfig.reporters ?? []).concat(['coverage']);
414414
}
415415

416+
// Adjust coverage reporter `overrides` paths to be relative to the karma `basePath`
417+
// (the temporary build output directory). Users write override patterns relative to the
418+
// workspace root, but karma-coverage normalises coverage-map keys using `basePath`, so the
419+
// patterns must be expressed as paths relative to that same directory.
420+
// See https://github.com/angular/angular-cli/issues/30956
421+
if ('coverageReporter' in parsedKarmaConfig) {
422+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
423+
const coverageReporterOptions = (parsedKarmaConfig as any)['coverageReporter'] as {
424+
check?: {
425+
global?: { overrides?: Record<string, unknown> };
426+
each?: { overrides?: Record<string, unknown> };
427+
};
428+
};
429+
430+
const rebaseOverrides = (overrides: Record<string, unknown>): Record<string, unknown> => {
431+
const rebased: Record<string, unknown> = {};
432+
for (const [pattern, thresholds] of Object.entries(overrides)) {
433+
// Resolve the user pattern from the workspace root, then make it relative
434+
// to the karma basePath (outputPath) so it matches how karma-coverage
435+
// normalises coverage-map keys.
436+
const absolutePattern = path.isAbsolute(pattern)
437+
? pattern
438+
: path.join(context.workspaceRoot, pattern);
439+
const rebasedPattern = path.relative(outputPath, absolutePattern);
440+
rebased[rebasedPattern] = thresholds;
441+
}
442+
return rebased;
443+
};
444+
445+
const check = coverageReporterOptions?.check;
446+
if (check?.global?.overrides && typeof check.global.overrides === 'object') {
447+
check.global.overrides = rebaseOverrides(check.global.overrides);
448+
}
449+
if (check?.each?.overrides && typeof check.each.overrides === 'object') {
450+
check.each.overrides = rebaseOverrides(check.each.overrides);
451+
}
452+
}
453+
416454
return parsedKarmaConfig;
417455
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import { last, tap } from 'rxjs';
10+
import { execute } from '../../index';
11+
import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup';
12+
13+
/**
14+
* Regression test for https://github.com/angular/angular-cli/issues/30956
15+
*
16+
* Coverage `overrides` in karma.conf.js were ignored because karma-coverage
17+
* resolves the override patterns relative to the karma `basePath`, which is
18+
* the temporary build output directory. Patterns written relative to the
19+
* workspace root (e.g. `src/app/app.component.ts`) never matched.
20+
*/
21+
describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => {
22+
describe('Behavior: "coverageReporter.check overrides"', () => {
23+
beforeEach(async () => {
24+
await setupTarget(harness);
25+
});
26+
27+
it('should respect per-file overrides in coverageReporter.check.each.overrides', async () => {
28+
// Add a function that won't be covered – with a 100% global threshold this
29+
// would normally cause a failure. The override for app.component.ts drops
30+
// the threshold to 0 so the run should still succeed.
31+
await harness.appendToFile(
32+
'src/app/app.component.ts',
33+
`
34+
export function notCovered(): boolean {
35+
return true;
36+
}
37+
`,
38+
);
39+
40+
await harness.modifyFile('karma.conf.js', (content) =>
41+
content.replace(
42+
'coverageReporter: {',
43+
`coverageReporter: {
44+
check: {
45+
global: {
46+
statements: 100,
47+
lines: 100,
48+
branches: 100,
49+
functions: 100,
50+
},
51+
each: {
52+
statements: 0,
53+
lines: 0,
54+
branches: 0,
55+
functions: 0,
56+
overrides: {
57+
'src/app/app.component.ts': {
58+
statements: 0,
59+
lines: 0,
60+
branches: 0,
61+
functions: 0,
62+
},
63+
},
64+
},
65+
},
66+
`,
67+
),
68+
);
69+
70+
harness.useTarget('test', {
71+
...BASE_OPTIONS,
72+
codeCoverage: true,
73+
});
74+
75+
// If overrides are ignored the global 100% threshold will be applied to
76+
// app.component.ts and the run will fail. With the fix the per-file
77+
// override (0%) is used and the run succeeds.
78+
await harness
79+
.execute()
80+
.pipe(
81+
last(),
82+
tap((buildEvent) => expect(buildEvent.result?.success).toBeTrue()),
83+
)
84+
.toPromise();
85+
});
86+
});
87+
});

0 commit comments

Comments
 (0)