@@ -15,6 +15,7 @@ import {
15
15
} from '@angular/build/private' ;
16
16
import { BuilderContext , BuilderOutput } from '@angular-devkit/architect' ;
17
17
import { randomUUID } from 'crypto' ;
18
+ import glob from 'fast-glob' ;
18
19
import * as fs from 'fs/promises' ;
19
20
import type { Config , ConfigOptions , InlinePluginDef } from 'karma' ;
20
21
import * as path from 'path' ;
@@ -87,9 +88,8 @@ async function getProjectSourceRoot(context: BuilderContext): Promise<string> {
87
88
async function collectEntrypoints (
88
89
options : KarmaBuilderOptions ,
89
90
context : BuilderContext ,
91
+ projectSourceRoot : string ,
90
92
) : Promise < [ Set < string > , string [ ] ] > {
91
- const projectSourceRoot = await getProjectSourceRoot ( context ) ;
92
-
93
93
// Glob for files to test.
94
94
const testFiles = await findTests (
95
95
options . include ?? [ ] ,
@@ -127,15 +127,23 @@ async function initializeApplication(
127
127
}
128
128
129
129
const testDir = path . join ( context . workspaceRoot , 'dist/test-out' , randomUUID ( ) ) ;
130
+ const projectSourceRoot = await getProjectSourceRoot ( context ) ;
130
131
131
132
const [ karma , [ entryPoints , polyfills ] ] = await Promise . all ( [
132
133
import ( 'karma' ) ,
133
- collectEntrypoints ( options , context ) ,
134
+ collectEntrypoints ( options , context , projectSourceRoot ) ,
134
135
fs . rm ( testDir , { recursive : true , force : true } ) ,
135
136
] ) ;
136
137
137
138
const outputPath = testDir ;
138
139
140
+ const instrumentForCoverage = options . codeCoverage
141
+ ? createInstrumentationFilter (
142
+ projectSourceRoot ,
143
+ getInstrumentationExcludedPaths ( context . workspaceRoot , options . codeCoverageExclude ?? [ ] ) ,
144
+ )
145
+ : undefined ;
146
+
139
147
// Build tests with `application` builder, using test files as entry points.
140
148
const buildOutput = await first (
141
149
buildApplicationInternal (
@@ -152,6 +160,7 @@ async function initializeApplication(
152
160
styles : true ,
153
161
vendor : true ,
154
162
} ,
163
+ instrumentForCoverage,
155
164
styles : options . styles ,
156
165
polyfills,
157
166
webWorkerTsConfig : options . webWorkerTsConfig ,
@@ -281,3 +290,24 @@ async function first<T>(generator: AsyncIterable<T>): Promise<T> {
281
290
282
291
throw new Error ( 'Expected generator to emit at least once.' ) ;
283
292
}
293
+
294
+ function createInstrumentationFilter ( includedBasePath : string , excludedPaths : Set < string > ) {
295
+ return ( request : string ) : boolean => {
296
+ return (
297
+ ! excludedPaths . has ( request ) &&
298
+ ! / \. ( e 2 e | s p e c ) \. t s x ? $ | [ \\ / ] n o d e _ m o d u l e s [ \\ / ] / . test ( request ) &&
299
+ request . startsWith ( includedBasePath )
300
+ ) ;
301
+ } ;
302
+ }
303
+
304
+ function getInstrumentationExcludedPaths ( root : string , excludedPaths : string [ ] ) : Set < string > {
305
+ const excluded = new Set < string > ( ) ;
306
+
307
+ for ( const excludeGlob of excludedPaths ) {
308
+ const excludePath = excludeGlob [ 0 ] === '/' ? excludeGlob . slice ( 1 ) : excludeGlob ;
309
+ glob . sync ( excludePath , { cwd : root } ) . forEach ( ( p ) => excluded . add ( path . join ( root , p ) ) ) ;
310
+ }
311
+
312
+ return excluded ;
313
+ }
0 commit comments