From b71b59f033dafc0f7ecfb1e6d9f9109971d11e27 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Mon, 30 Mar 2026 18:15:34 -0400 Subject: [PATCH] test(@angular/build): verify coverage ignore comments are preserved during compilation The underlying Vitest coverage engine depends on specific developer comments like `/* istanbul ignore next */` or `/* v8 ignore next */` being present in the executing code to accurately isolate unmeasured blocks. This commit adds strict behavioral tests to assert that the Angular CLI's in-memory compilation pipeline (via esbuild) properly preserves these structural comments and forwards them reliably to Vitest's coverage processing engine. --- .../behavior/coverage-ignore-comments_spec.ts | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 packages/angular/build/src/builders/unit-test/tests/behavior/coverage-ignore-comments_spec.ts diff --git a/packages/angular/build/src/builders/unit-test/tests/behavior/coverage-ignore-comments_spec.ts b/packages/angular/build/src/builders/unit-test/tests/behavior/coverage-ignore-comments_spec.ts new file mode 100644 index 000000000000..d04c9aa7e690 --- /dev/null +++ b/packages/angular/build/src/builders/unit-test/tests/behavior/coverage-ignore-comments_spec.ts @@ -0,0 +1,143 @@ +/** + * @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 { execute } from '../../index'; +import { + BASE_OPTIONS, + describeBuilder, + UNIT_TEST_BUILDER_INFO, + setupApplicationTarget, +} from '../setup'; + +describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { + describe('Behavior: "coverage ignore comments"', () => { + beforeEach(async () => { + setupApplicationTarget(harness); + }); + + it('should respect istanbul ignore next comments when computing coverage', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + coverage: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + coverageReporters: ['json'] as any, + }); + + harness.writeFile( + 'src/app/app.component.ts', + ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'app-root', + template: '

hello

', + standalone: true, + }) + export class AppComponent { + title = 'app'; + + /* istanbul ignore next */ + untestedFunction() { + return false; + } + } + `, + ); + + harness.writeFile( + 'src/app/app.component.spec.ts', + ` + import { AppComponent } from './app.component'; + + describe('AppComponent', () => { + it('should create', () => { + const comp = new AppComponent(); + expect(comp).toBeTruthy(); + }); + }); + `, + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectFile('coverage/test/coverage-final.json').toExist(); + + const coverageMap = JSON.parse(harness.readFile('coverage/test/coverage-final.json')); + const appComponentPath = Object.keys(coverageMap).find((p) => p.includes('app.component.ts')); + expect(appComponentPath).toBeDefined(); + + const appComponentCoverage = coverageMap[appComponentPath!]; + + const statementCounts = Object.values(appComponentCoverage.s) as number[]; + const hasUncoveredStatements = statementCounts.some((count) => count === 0); + expect(hasUncoveredStatements) + .withContext('There should be no uncovered statements as the uncalled function was ignored') + .toBeFalse(); + }); + + it('should respect v8 ignore next comments when computing coverage', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + coverage: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + coverageReporters: ['json'] as any, + }); + + harness.writeFile( + 'src/app/app.component.ts', + ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'app-root', + template: '

hello

', + standalone: true, + }) + export class AppComponent { + title = 'app'; + + /* v8 ignore next */ + untestedFunction() { + return false; + } + } + `, + ); + + harness.writeFile( + 'src/app/app.component.spec.ts', + ` + import { AppComponent } from './app.component'; + + describe('AppComponent', () => { + it('should create', () => { + const comp = new AppComponent(); + expect(comp).toBeTruthy(); + }); + }); + `, + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectFile('coverage/test/coverage-final.json').toExist(); + + const coverageMap = JSON.parse(harness.readFile('coverage/test/coverage-final.json')); + const appComponentPath = Object.keys(coverageMap).find((p) => p.includes('app.component.ts')); + expect(appComponentPath).toBeDefined(); + + const appComponentCoverage = coverageMap[appComponentPath!]; + + const statementCounts = Object.values(appComponentCoverage.s) as number[]; + const hasUncoveredStatements = statementCounts.some((count) => count === 0); + expect(hasUncoveredStatements) + .withContext('There should be no uncovered statements as the uncalled function was ignored') + .toBeFalse(); + }); + }); +});