@@ -12,7 +12,7 @@ import { randomUUID } from 'node:crypto';
12
12
import * as fs from 'node:fs/promises' ;
13
13
import type { IncomingMessage , ServerResponse } from 'node:http' ;
14
14
import { createRequire } from 'node:module' ;
15
- import * as path from 'node:path' ;
15
+ import path from 'node:path' ;
16
16
import { ReadableStreamController } from 'node:stream/web' ;
17
17
import { globSync } from 'tinyglobby' ;
18
18
import { BuildOutputFileType } from '../../tools/esbuild/bundler-context' ;
@@ -24,7 +24,9 @@ import { ApplicationBuilderInternalOptions } from '../application/options';
24
24
import { Result , ResultFile , ResultKind } from '../application/results' ;
25
25
import { OutputHashing } from '../application/schema' ;
26
26
import { findTests , getTestEntrypoints } from './find-tests' ;
27
- import { NormalizedKarmaBuilderOptions } from './options' ;
27
+ import { NormalizedKarmaBuilderOptions , normalizeOptions } from './options' ;
28
+ import { Schema as KarmaBuilderOptions } from './schema' ;
29
+ import type { KarmaBuilderTransformsOptions } from './index' ;
28
30
29
31
const localResolve = createRequire ( __filename ) . resolve ;
30
32
const isWindows = process . platform === 'win32' ;
@@ -275,21 +277,20 @@ function injectKarmaReporter(
275
277
}
276
278
277
279
export function execute (
278
- options : NormalizedKarmaBuilderOptions ,
280
+ options : KarmaBuilderOptions ,
279
281
context : BuilderContext ,
280
- karmaOptions : ConfigOptions ,
281
- transforms : {
282
- // The karma options transform cannot be async without a refactor of the builder implementation
283
- karmaOptions ?: ( options : ConfigOptions ) => ConfigOptions ;
284
- } = { } ,
282
+ transforms ?: KarmaBuilderTransformsOptions ,
285
283
) : AsyncIterable < BuilderOutput > {
284
+ const normalizedOptions = normalizeOptions ( context , options ) ;
285
+ const karmaOptions = getBaseKarmaOptions ( normalizedOptions , context ) ;
286
+
286
287
let karmaServer : Server ;
287
288
288
289
return new ReadableStream ( {
289
290
async start ( controller ) {
290
291
let init ;
291
292
try {
292
- init = await initializeApplication ( options , context , karmaOptions , transforms ) ;
293
+ init = await initializeApplication ( normalizedOptions , context , karmaOptions , transforms ) ;
293
294
} catch ( err ) {
294
295
if ( err instanceof ApplicationBuildError ) {
295
296
controller . enqueue ( { success : false , message : err . message } ) ;
@@ -336,13 +337,9 @@ async function getProjectSourceRoot(context: BuilderContext): Promise<string> {
336
337
return projectSourceRoot ;
337
338
}
338
339
339
- function normalizePolyfills ( polyfills : string | string [ ] | undefined ) : [ string [ ] , string [ ] ] {
340
- if ( typeof polyfills === 'string' ) {
341
- polyfills = [ polyfills ] ;
342
- } else if ( ! polyfills ) {
343
- polyfills = [ ] ;
344
- }
345
-
340
+ function normalizePolyfills (
341
+ polyfills : string [ ] | undefined = [ ] ,
342
+ ) : [ polyfills : string [ ] , jasmineCleanup : string [ ] ] {
346
343
const jasmineGlobalEntryPoint = localResolve ( './polyfills/jasmine_global.js' ) ;
347
344
const jasmineGlobalCleanupEntrypoint = localResolve ( './polyfills/jasmine_global_cleanup.js' ) ;
348
345
const sourcemapEntrypoint = localResolve ( './polyfills/init_sourcemaps.js' ) ;
@@ -379,9 +376,7 @@ async function initializeApplication(
379
376
options : NormalizedKarmaBuilderOptions ,
380
377
context : BuilderContext ,
381
378
karmaOptions : ConfigOptions ,
382
- transforms : {
383
- karmaOptions ?: ( options : ConfigOptions ) => ConfigOptions ;
384
- } = { } ,
379
+ transforms ?: KarmaBuilderTransformsOptions ,
385
380
) : Promise <
386
381
[ typeof import ( 'karma' ) , Config & ConfigOptions , BuildOptions , AsyncIterator < Result > | null ]
387
382
> {
@@ -423,13 +418,7 @@ async function initializeApplication(
423
418
index : false ,
424
419
outputHashing : OutputHashing . None ,
425
420
optimization : false ,
426
- sourceMap : options . codeCoverage
427
- ? {
428
- scripts : true ,
429
- styles : true ,
430
- vendor : true ,
431
- }
432
- : options . sourceMap ,
421
+ sourceMap : options . sourceMap ,
433
422
instrumentForCoverage,
434
423
styles : options . styles ,
435
424
scripts : options . scripts ,
@@ -551,8 +540,8 @@ async function initializeApplication(
551
540
}
552
541
553
542
const parsedKarmaConfig : Config & ConfigOptions = await karma . config . parseConfig (
554
- options . karmaConfig && path . resolve ( context . workspaceRoot , options . karmaConfig ) ,
555
- transforms . karmaOptions ? transforms . karmaOptions ( karmaOptions ) : karmaOptions ,
543
+ options . karmaConfig ,
544
+ transforms ? .karmaOptions ? await transforms . karmaOptions ( karmaOptions ) : karmaOptions ,
556
545
{ promiseConfig : true , throwErrors : true } ,
557
546
) ;
558
547
@@ -718,3 +707,83 @@ function getInstrumentationExcludedPaths(root: string, excludedPaths: string[]):
718
707
719
708
return excluded ;
720
709
}
710
+ function getBaseKarmaOptions (
711
+ options : NormalizedKarmaBuilderOptions ,
712
+ context : BuilderContext ,
713
+ ) : ConfigOptions {
714
+ const singleRun = ! options . watch ;
715
+
716
+ // Determine project name from builder context target
717
+ const projectName = context . target ?. project ;
718
+ if ( ! projectName ) {
719
+ throw new Error ( `The 'karma' builder requires a target to be specified.` ) ;
720
+ }
721
+
722
+ const karmaOptions : ConfigOptions = options . karmaConfig
723
+ ? { }
724
+ : getBuiltInKarmaConfig ( context . workspaceRoot , projectName ) ;
725
+
726
+ karmaOptions . singleRun = singleRun ;
727
+
728
+ // Workaround https://github.com/angular/angular-cli/issues/28271, by clearing context by default
729
+ // for single run executions. Not clearing context for multi-run (watched) builds allows the
730
+ // Jasmine Spec Runner to be visible in the browser after test execution.
731
+ karmaOptions . client ??= { } ;
732
+ karmaOptions . client . clearContext ??= singleRun ?? false ; // `singleRun` defaults to `false` per Karma docs.
733
+
734
+ // Convert browsers from a string to an array
735
+ if ( options . browsers ) {
736
+ karmaOptions . browsers = options . browsers ;
737
+ }
738
+
739
+ if ( options . reporters ) {
740
+ karmaOptions . reporters = options . reporters ;
741
+ }
742
+
743
+ return karmaOptions ;
744
+ }
745
+
746
+ function getBuiltInKarmaConfig (
747
+ workspaceRoot : string ,
748
+ projectName : string ,
749
+ ) : ConfigOptions & Record < string , unknown > {
750
+ let coverageFolderName = projectName . charAt ( 0 ) === '@' ? projectName . slice ( 1 ) : projectName ;
751
+ coverageFolderName = coverageFolderName . toLowerCase ( ) ;
752
+
753
+ const workspaceRootRequire = createRequire ( workspaceRoot + '/' ) ;
754
+
755
+ // Any changes to the config here need to be synced to: packages/schematics/angular/config/files/karma.conf.js.template
756
+ return {
757
+ basePath : '' ,
758
+ frameworks : [ 'jasmine' ] ,
759
+ plugins : [
760
+ 'karma-jasmine' ,
761
+ 'karma-chrome-launcher' ,
762
+ 'karma-jasmine-html-reporter' ,
763
+ 'karma-coverage' ,
764
+ ] . map ( ( p ) => workspaceRootRequire ( p ) ) ,
765
+ jasmineHtmlReporter : {
766
+ suppressAll : true , // removes the duplicated traces
767
+ } ,
768
+ coverageReporter : {
769
+ dir : path . join ( workspaceRoot , 'coverage' , coverageFolderName ) ,
770
+ subdir : '.' ,
771
+ reporters : [ { type : 'html' } , { type : 'text-summary' } ] ,
772
+ } ,
773
+ reporters : [ 'progress' , 'kjhtml' ] ,
774
+ browsers : [ 'Chrome' ] ,
775
+ customLaunchers : {
776
+ // Chrome configured to run in a bazel sandbox.
777
+ // Disable the use of the gpu and `/dev/shm` because it causes Chrome to
778
+ // crash on some environments.
779
+ // See:
780
+ // https://github.com/puppeteer/puppeteer/blob/v1.0.0/docs/troubleshooting.md#tips
781
+ // https://stackoverflow.com/questions/50642308/webdriverexception-unknown-error-devtoolsactiveport-file-doesnt-exist-while-t
782
+ ChromeHeadlessNoSandbox : {
783
+ base : 'ChromeHeadless' ,
784
+ flags : [ '--no-sandbox' , '--headless' , '--disable-gpu' , '--disable-dev-shm-usage' ] ,
785
+ } ,
786
+ } ,
787
+ restartOnFileChange : true ,
788
+ } ;
789
+ }
0 commit comments