Skip to content

Commit 8ad499b

Browse files
committed
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.
1 parent 685ebae commit 8ad499b

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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('Behavior: "coverage ignore comments"', () => {
19+
beforeEach(async () => {
20+
setupApplicationTarget(harness);
21+
});
22+
23+
it('should respect istanbul ignore next comments when computing coverage', async () => {
24+
harness.useTarget('test', {
25+
...BASE_OPTIONS,
26+
coverage: true,
27+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
28+
coverageReporters: ['json'] as any,
29+
});
30+
31+
harness.writeFile(
32+
'src/app/app.component.ts',
33+
`
34+
import { Component } from '@angular/core';
35+
36+
@Component({
37+
selector: 'app-root',
38+
template: '<h1>hello</h1>',
39+
standalone: true,
40+
})
41+
export class AppComponent {
42+
title = 'app';
43+
44+
/* istanbul ignore next */
45+
untestedFunction() {
46+
return false;
47+
}
48+
}
49+
`,
50+
);
51+
52+
harness.writeFile(
53+
'src/app/app.component.spec.ts',
54+
`
55+
import { TestBed } from '@angular/core/testing';
56+
import { App } from './app';
57+
58+
describe('App', () => {
59+
beforeEach(async () => {
60+
await TestBed.configureTestingModule({
61+
imports: [App],
62+
}).compileComponents();
63+
});
64+
65+
it('should create the app', () => {
66+
const fixture = TestBed.createComponent(App);
67+
const app = fixture.componentInstance;
68+
expect(app).toBeTruthy();
69+
});
70+
71+
it('should render title', async () => {
72+
const fixture = TestBed.createComponent(App);
73+
await fixture.whenStable();
74+
const compiled = fixture.nativeElement as HTMLElement;
75+
expect(compiled.querySelector('h1')?.textContent).toContain('hello');
76+
});
77+
});
78+
`,
79+
);
80+
81+
const { result } = await harness.executeOnce();
82+
expect(result?.success).toBeTrue();
83+
harness.expectFile('coverage/test/coverage-final.json').toExist();
84+
85+
const coverageMap = JSON.parse(harness.readFile('coverage/test/coverage-final.json'));
86+
const appComponentPath = Object.keys(coverageMap).find((p) => p.includes('app.component.ts'));
87+
expect(appComponentPath).toBeDefined();
88+
89+
const appComponentCoverage = coverageMap[appComponentPath!];
90+
91+
const statementCounts = Object.values(appComponentCoverage.s) as number[];
92+
const hasUncoveredStatements = statementCounts.some((count) => count === 0);
93+
expect(hasUncoveredStatements)
94+
.withContext('There should be no uncovered statements as the uncalled function was ignored')
95+
.toBeFalse();
96+
});
97+
98+
it('should respect v8 ignore next comments when computing coverage', async () => {
99+
harness.useTarget('test', {
100+
...BASE_OPTIONS,
101+
coverage: true,
102+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
103+
coverageReporters: ['json'] as any,
104+
});
105+
106+
harness.writeFile(
107+
'src/app/app.component.ts',
108+
`
109+
import { Component } from '@angular/core';
110+
111+
@Component({
112+
selector: 'app-root',
113+
template: '<h1>hello</h1>',
114+
standalone: true,
115+
})
116+
export class AppComponent {
117+
title = 'app';
118+
119+
/* v8 ignore next */
120+
untestedFunction() {
121+
return false;
122+
}
123+
}
124+
`,
125+
);
126+
127+
harness.writeFile(
128+
'src/app/app.component.spec.ts',
129+
`
130+
import { TestBed } from '@angular/core/testing';
131+
import { App } from './app';
132+
133+
describe('App', () => {
134+
beforeEach(async () => {
135+
await TestBed.configureTestingModule({
136+
imports: [App],
137+
}).compileComponents();
138+
});
139+
140+
it('should create the app', () => {
141+
const fixture = TestBed.createComponent(App);
142+
const app = fixture.componentInstance;
143+
expect(app).toBeTruthy();
144+
});
145+
146+
it('should render title', async () => {
147+
const fixture = TestBed.createComponent(App);
148+
await fixture.whenStable();
149+
const compiled = fixture.nativeElement as HTMLElement;
150+
expect(compiled.querySelector('h1')?.textContent).toContain('hello');
151+
});
152+
});
153+
`,
154+
);
155+
156+
const { result } = await harness.executeOnce();
157+
expect(result?.success).toBeTrue();
158+
harness.expectFile('coverage/test/coverage-final.json').toExist();
159+
160+
const coverageMap = JSON.parse(harness.readFile('coverage/test/coverage-final.json'));
161+
const appComponentPath = Object.keys(coverageMap).find((p) => p.includes('app.component.ts'));
162+
expect(appComponentPath).toBeDefined();
163+
164+
const appComponentCoverage = coverageMap[appComponentPath!];
165+
166+
const statementCounts = Object.values(appComponentCoverage.s) as number[];
167+
const hasUncoveredStatements = statementCounts.some((count) => count === 0);
168+
expect(hasUncoveredStatements)
169+
.withContext('There should be no uncovered statements as the uncalled function was ignored')
170+
.toBeFalse();
171+
});
172+
});
173+
});

0 commit comments

Comments
 (0)