Skip to content

Commit 70fb546

Browse files
committed
fix(@angular/build): deduplicate and merge coverage excludes with vitest
Previously, providing a --coverage-exclude CLI option to the builder would completely clobber any custom coverage.exclude items defined natively within vitest.config.ts. This correctly merges both sources using an internal Set to prevent duplicate exclusions and preserves configurations so developers can combine global ignores alongside CLI-specific boundaries.
1 parent 9cb03fe commit 70fb546

File tree

2 files changed

+65
-6
lines changed

2 files changed

+65
-6
lines changed

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,9 @@ async function generateCoverageOption(
406406
projectName: string,
407407
): Promise<VitestCoverageOption> {
408408
let defaultExcludes: string[] = [];
409+
// When a coverage exclude option is provided, Vitest's default coverage excludes
410+
// will be overridden. To retain them, we manually fetch the defaults to append to the
411+
// user's provided exclusions.
409412
if (optionsCoverage.exclude) {
410413
try {
411414
const vitestConfig = await import('vitest/config');
@@ -437,12 +440,15 @@ async function generateCoverageOption(
437440
// Special handling for `exclude`/`reporters` due to an undefined value causing upstream failures
438441
...(optionsCoverage.exclude
439442
? {
440-
exclude: [
441-
// Augment the default exclude https://vitest.dev/config/#coverage-exclude
442-
// with the user defined exclusions
443-
...optionsCoverage.exclude,
444-
...defaultExcludes,
445-
],
443+
exclude: Array.from(
444+
new Set([
445+
// Augment the default exclude https://vitest.dev/config/#coverage-exclude
446+
// with the user defined exclusions
447+
...(configCoverage?.exclude || []),
448+
...optionsCoverage.exclude,
449+
...defaultExcludes,
450+
]),
451+
),
446452
}
447453
: {}),
448454
...(optionsCoverage.reporters

packages/angular/build/src/builders/unit-test/tests/behavior/runner-config-vitest_spec.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,59 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
164164
expect(results.numPassedTests).toBe(1);
165165
});
166166

167+
it('should correctly merge coverage.exclude arrays from builder and runner options', async () => {
168+
harness.useTarget('test', {
169+
...BASE_OPTIONS,
170+
coverage: true,
171+
runnerConfig: 'vitest.config.ts',
172+
coverageExclude: ['src/app/cli-excluded.ts'],
173+
});
174+
175+
harness.writeFile(
176+
'vitest.config.ts',
177+
`
178+
import { defineConfig } from 'vitest/config';
179+
export default defineConfig({
180+
test: {
181+
coverage: {
182+
exclude: ['src/app/config-excluded.ts'],
183+
},
184+
},
185+
});
186+
`,
187+
);
188+
189+
// Create two files that would normally be covered
190+
harness.writeFile('src/app/cli-excluded.ts', 'export const cliExcluded = true;');
191+
harness.writeFile('src/app/config-excluded.ts', 'export const configExcluded = true;');
192+
193+
// Update the test file to import them so they're picked up by coverage
194+
harness.writeFile(
195+
'src/app/app.component.spec.ts',
196+
`
197+
import { test, expect } from 'vitest';
198+
import './cli-excluded';
199+
import './config-excluded';
200+
test('should pass', () => {
201+
expect(true).toBe(true);
202+
});
203+
`,
204+
);
205+
206+
const { result } = await harness.executeOnce();
207+
expect(result?.success).toBeTrue();
208+
harness.expectFile('coverage/test/coverage-final.json').toExist();
209+
210+
const coverageMap = JSON.parse(harness.readFile('coverage/test/coverage-final.json'));
211+
const coveredFiles = Object.keys(coverageMap);
212+
213+
const hasCliExcluded = coveredFiles.some((f) => f.includes('cli-excluded.ts'));
214+
const hasConfigExcluded = coveredFiles.some((f) => f.includes('config-excluded.ts'));
215+
216+
expect(hasCliExcluded).withContext('CLI target should be excluded').toBeFalse();
217+
expect(hasConfigExcluded).withContext('Config file target should be excluded').toBeFalse();
218+
});
219+
167220
it('should allow overriding globals to false via runnerConfig file', async () => {
168221
harness.useTarget('test', {
169222
...BASE_OPTIONS,

0 commit comments

Comments
 (0)