@@ -103,6 +103,57 @@ class AngularAssetsMiddleware {
103
103
} ;
104
104
}
105
105
}
106
+ class AngularPolyfillsPlugin {
107
+ static $inject = [ 'config.files' ] ;
108
+ static NAME = 'angular-polyfills' ;
109
+ static createPlugin ( polyfillsFile , jasmineCleanupFiles ) {
110
+ return {
111
+ // This has to be a "reporter" because reporters run _after_ frameworks
112
+ // and karma-jasmine-html-reporter injects additional scripts that may
113
+ // depend on Jasmine but aren't modules - which means that they would run
114
+ // _before_ all module code (including jasmine).
115
+ [ `reporter:${ AngularPolyfillsPlugin . NAME } ` ] : [
116
+ 'factory' ,
117
+ Object . assign ( ( files ) => {
118
+ // The correct order is zone.js -> jasmine -> zone.js/testing.
119
+ // Jasmine has to see the patched version of the global `setTimeout`
120
+ // function so it doesn't cache the unpatched version. And /testing
121
+ // needs to see the global `jasmine` object so it can patch it.
122
+ const polyfillsIndex = 0 ;
123
+ files . splice ( polyfillsIndex , 0 , polyfillsFile ) ;
124
+ // Insert just before test_main.js.
125
+ const zoneTestingIndex = files . findIndex ( ( f ) => {
126
+ if ( typeof f === 'string' ) {
127
+ return false ;
128
+ }
129
+ return f . pattern . endsWith ( '/test_main.js' ) ;
130
+ } ) ;
131
+ if ( zoneTestingIndex === - 1 ) {
132
+ throw new Error ( 'Could not find test entrypoint file.' ) ;
133
+ }
134
+ files . splice ( zoneTestingIndex , 0 , jasmineCleanupFiles ) ;
135
+ // We need to ensure that all files are served as modules, otherwise
136
+ // the order in the files list gets really confusing: Karma doesn't
137
+ // set defer on scripts, so all scripts with type=js will run first,
138
+ // even if type=module files appeared earlier in `files`.
139
+ for ( const f of files ) {
140
+ if ( typeof f === 'string' ) {
141
+ throw new Error ( `Unexpected string-based file: "${ f } "` ) ;
142
+ }
143
+ if ( f . included === false ) {
144
+ // Don't worry about files that aren't included on the initial
145
+ // page load. `type` won't affect them.
146
+ continue ;
147
+ }
148
+ if ( 'js' === ( f . type ?? 'js' ) ) {
149
+ f . type = 'module' ;
150
+ }
151
+ }
152
+ } , AngularPolyfillsPlugin ) ,
153
+ ] ,
154
+ } ;
155
+ }
156
+ }
106
157
function injectKarmaReporter ( buildOptions , buildIterator , karmaConfig , subscriber ) {
107
158
const reporterName = 'angular-progress-notifier' ;
108
159
class ProgressNotifierReporter {
@@ -199,9 +250,21 @@ async function getProjectSourceRoot(context) {
199
250
}
200
251
function normalizePolyfills ( polyfills ) {
201
252
if ( typeof polyfills === 'string' ) {
202
- return [ polyfills ] ;
253
+ polyfills = [ polyfills ] ;
203
254
}
204
- return polyfills ?? [ ] ;
255
+ else if ( ! polyfills ) {
256
+ polyfills = [ ] ;
257
+ }
258
+ const jasmineGlobalEntryPoint = '@angular-devkit/build-angular/src/builders/karma/jasmine_global.js' ;
259
+ const jasmineGlobalCleanupEntrypoint = '@angular-devkit/build-angular/src/builders/karma/jasmine_global_cleanup.js' ;
260
+ const zoneTestingEntryPoint = 'zone.js/testing' ;
261
+ const polyfillsExludingZoneTesting = polyfills . filter ( ( p ) => p !== zoneTestingEntryPoint ) ;
262
+ return [
263
+ polyfillsExludingZoneTesting . concat ( [ jasmineGlobalEntryPoint ] ) ,
264
+ polyfillsExludingZoneTesting . length === polyfills . length
265
+ ? [ jasmineGlobalCleanupEntrypoint ]
266
+ : [ jasmineGlobalCleanupEntrypoint , zoneTestingEntryPoint ] ,
267
+ ] ;
205
268
}
206
269
async function collectEntrypoints ( options , context , projectSourceRoot ) {
207
270
// Glob for files to test.
@@ -229,6 +292,10 @@ async function initializeApplication(options, context, karmaOptions, transforms
229
292
const instrumentForCoverage = options . codeCoverage
230
293
? createInstrumentationFilter ( projectSourceRoot , getInstrumentationExcludedPaths ( context . workspaceRoot , options . codeCoverageExclude ?? [ ] ) )
231
294
: undefined ;
295
+ const [ polyfills , jasmineCleanup ] = normalizePolyfills ( options . polyfills ) ;
296
+ for ( let idx = 0 ; idx < jasmineCleanup . length ; ++ idx ) {
297
+ entryPoints . set ( `jasmine-cleanup-${ idx } ` , jasmineCleanup [ idx ] ) ;
298
+ }
232
299
const buildOptions = {
233
300
assets : options . assets ,
234
301
entryPoints,
@@ -245,7 +312,7 @@ async function initializeApplication(options, context, karmaOptions, transforms
245
312
} ,
246
313
instrumentForCoverage,
247
314
styles : options . styles ,
248
- polyfills : normalizePolyfills ( options . polyfills ) ,
315
+ polyfills,
249
316
webWorkerTsConfig : options . webWorkerTsConfig ,
250
317
watch : options . watch ?? ! karmaOptions . singleRun ,
251
318
stylePreprocessorOptions : options . stylePreprocessorOptions ,
@@ -260,10 +327,24 @@ async function initializeApplication(options, context, karmaOptions, transforms
260
327
}
261
328
// Write test files
262
329
await writeTestFiles ( buildOutput . files , buildOptions . outputPath ) ;
330
+ // We need to add this to the beginning *after* the testing framework has
331
+ // prepended its files.
332
+ const polyfillsFile = {
333
+ pattern : `${ outputPath } /polyfills.js` ,
334
+ included : true ,
335
+ served : true ,
336
+ type : 'module' ,
337
+ watched : false ,
338
+ } ;
339
+ const jasmineCleanupFiles = {
340
+ pattern : `${ outputPath } /jasmine-cleanup-*.js` ,
341
+ included : true ,
342
+ served : true ,
343
+ type : 'module' ,
344
+ watched : false ,
345
+ } ;
263
346
karmaOptions . files ??= [ ] ;
264
347
karmaOptions . files . push (
265
- // Serve polyfills first.
266
- { pattern : `${ outputPath } /polyfills.js` , type : 'module' , watched : false } ,
267
348
// Serve global setup script.
268
349
{ pattern : `${ outputPath } /${ mainName } .js` , type : 'module' , watched : false } ,
269
350
// Serve all source maps.
@@ -305,6 +386,9 @@ async function initializeApplication(options, context, karmaOptions, transforms
305
386
parsedKarmaConfig . plugins . push ( AngularAssetsMiddleware . createPlugin ( buildOutput ) ) ;
306
387
parsedKarmaConfig . middleware ??= [ ] ;
307
388
parsedKarmaConfig . middleware . push ( AngularAssetsMiddleware . NAME ) ;
389
+ parsedKarmaConfig . plugins . push ( AngularPolyfillsPlugin . createPlugin ( polyfillsFile , jasmineCleanupFiles ) ) ;
390
+ parsedKarmaConfig . reporters ??= [ ] ;
391
+ parsedKarmaConfig . reporters . push ( AngularPolyfillsPlugin . NAME ) ;
308
392
// When using code-coverage, auto-add karma-coverage.
309
393
// This was done as part of the karma plugin for webpack.
310
394
if ( options . codeCoverage &&
0 commit comments