Skip to content

Commit f05ffd1

Browse files
committed
fix(@angular/build): correct Vitest coverage include handling for virtual files
Addresses an issue where user-defined `coverageInclude` patterns in Vitest could incorrectly exclude bundled virtual files during the pre-sourcemap coverage check, leading to incomplete or empty coverage reports. This change modifies the `generateCoverageOption` function to augment user-provided `coverageInclude` arrays with specific globs (`spec-*.js`, `chunk-*.js`) that match the internal bundled test entry points and code chunks. This ensures that all necessary files are considered during Vitest's initial coverage pass, while still respecting the user's original `include` patterns for the post-sourcemap remapping. (cherry picked from commit 14cd1e1)
1 parent bb29254 commit f05ffd1

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,10 @@ async function generateCoverageOption(
285285
return {
286286
enabled: coverage.enabled,
287287
excludeAfterRemap: true,
288-
include: coverage.include,
288+
// Vitest performs a pre-check and a post-check for sourcemaps.
289+
// The pre-check uses the bundled files, so specific bundled entry points and chunks need to be included.
290+
// The post-check uses the original source files, so the user's include is used.
291+
...(coverage.include ? { include: ['spec-*.js', 'chunk-*.js', ...coverage.include] } : {}),
289292
reportsDirectory: toPosixPath(path.join('coverage', projectName)),
290293
thresholds: coverage.thresholds,
291294
watermarks: coverage.watermarks,
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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 { execute } from '../../index';
10+
import {
11+
BASE_OPTIONS,
12+
describeBuilder,
13+
UNIT_TEST_BUILDER_INFO,
14+
setupApplicationTarget,
15+
} from '../setup';
16+
17+
describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
18+
describe('Option: "coverageInclude"', () => {
19+
beforeEach(async () => {
20+
setupApplicationTarget(harness);
21+
await harness.writeFiles({
22+
'src/app/included.ts': `export const a = 1;`,
23+
'src/app/included.spec.ts': `
24+
import { a } from './included';
25+
describe('included', () => {
26+
it('should work', () => {
27+
expect(a).toBe(1);
28+
});
29+
});
30+
`,
31+
'src/app/excluded.ts': `export const b = 2;`,
32+
});
33+
});
34+
35+
it('should only include and report coverage for files that match the glob pattern', async () => {
36+
harness.useTarget('test', {
37+
...BASE_OPTIONS,
38+
coverage: true,
39+
coverageInclude: ['**/included.ts'],
40+
});
41+
42+
const { result } = await harness.executeOnce();
43+
expect(result?.success).toBeTrue();
44+
45+
const summary = JSON.parse(harness.readFile('coverage/test/coverage-final.json'));
46+
const summaryKeys = Object.keys(summary);
47+
48+
const includedKey = summaryKeys.find((key) => key.endsWith('src/app/included.ts'));
49+
const excludedKey = summaryKeys.find((key) => key.endsWith('src/app/excluded.ts'));
50+
51+
// Check that the included file is in the report and the excluded one is not.
52+
expect(includedKey).toBeDefined();
53+
expect(excludedKey).toBeUndefined();
54+
55+
// Check that the coverage data for the included file is valid.
56+
const includedCoverage = summary[includedKey!];
57+
// The file has one statement, and it should have been executed once.
58+
expect(includedCoverage.s['0']).toBe(1);
59+
});
60+
61+
it('should only include referenced files when no include pattern is provided', async () => {
62+
harness.useTarget('test', {
63+
...BASE_OPTIONS,
64+
coverage: true,
65+
// coverageInclude is not provided, so only referenced files should be included.
66+
});
67+
68+
const { result } = await harness.executeOnce();
69+
expect(result?.success).toBeTrue();
70+
const summary = JSON.parse(harness.readFile('coverage/test/coverage-final.json'));
71+
const summaryKeys = Object.keys(summary);
72+
73+
const includedKey = summaryKeys.find((key) => key.endsWith('src/app/included.ts'));
74+
const excludedKey = summaryKeys.find((key) => key.endsWith('src/app/excluded.ts'));
75+
76+
// The included file is referenced by its spec and should be in the report.
77+
expect(includedKey).toBeDefined();
78+
// The excluded file is not referenced and should NOT be in the report.
79+
expect(excludedKey).toBeUndefined();
80+
});
81+
});
82+
});

0 commit comments

Comments
 (0)