66 * found in the LICENSE file at https://angular.io/license
77 */
88import { BuilderContext , BuilderOutput , createBuilder } from '@angular-devkit/architect' ;
9- import { resolve } from 'path' ;
9+ import { experimental , getSystemPath , join } from '@angular-devkit/core' ;
10+ import { dirname , resolve } from 'path' ;
1011import { Observable , from } from 'rxjs' ;
1112import { defaultIfEmpty , switchMap } from 'rxjs/operators' ;
1213import * as webpack from 'webpack' ;
@@ -17,14 +18,19 @@ import {
1718 getTestConfig ,
1819 getWorkerConfig ,
1920} from '../angular-cli-files/models/webpack-configs' ;
21+ import {
22+ SingleTestTransformLoader ,
23+ SingleTestTransformLoaderOptions ,
24+ } from '../angular-cli-files/plugins/single-test-transform' ;
25+ import { findTests } from '../angular-cli-files/utilities/find-tests' ;
2026import { Schema as BrowserBuilderOptions } from '../browser/schema' ;
2127import { ExecutionTransformer } from '../transforms' ;
2228import { assertCompatibleAngularVersion } from '../utils/version' ;
2329import { generateBrowserWebpackConfigFromContext } from '../utils/webpack-browser-config' ;
2430import { Schema as KarmaBuilderOptions } from './schema' ;
2531
2632// tslint:disable-next-line:no-implicit-dependencies
27- export type KarmaConfigOptions = import ( 'karma' ) . ConfigOptions & {
33+ export type KarmaConfigOptions = import ( 'karma' ) . ConfigOptions & {
2834 buildWebpack ?: unknown ;
2935 configFile ?: string ;
3036} ;
@@ -33,13 +39,19 @@ async function initialize(
3339 options : KarmaBuilderOptions ,
3440 context : BuilderContext ,
3541 webpackConfigurationTransformer ?: ExecutionTransformer < webpack . Configuration > ,
36- // tslint:disable-next-line:no-implicit-dependencies
37- ) : Promise < [ typeof import ( 'karma' ) , webpack . Configuration ] > {
38- const { config } = await generateBrowserWebpackConfigFromContext (
42+ ) : Promise <
43+ [
44+ experimental . workspace . Workspace ,
45+ // tslint:disable-next-line:no-implicit-dependencies
46+ typeof import ( 'karma' ) ,
47+ webpack . Configuration ,
48+ ]
49+ > {
50+ const { config, workspace } = await generateBrowserWebpackConfigFromContext (
3951 // only two properties are missing:
4052 // * `outputPath` which is fixed for tests
4153 // * `budgets` which might be incorrect due to extra dev libs
42- { ...options as unknown as BrowserBuilderOptions , outputPath : '' , budgets : undefined } ,
54+ { ...( ( options as unknown ) as BrowserBuilderOptions ) , outputPath : '' , budgets : undefined } ,
4355 context ,
4456 wco => [
4557 getCommonConfig ( wco ) ,
@@ -54,6 +66,7 @@ async function initialize(
5466 const karma = await import ( 'karma' ) ;
5567
5668 return [
69+ workspace ,
5770 karma ,
5871 webpackConfigurationTransformer ? await webpackConfigurationTransformer ( config [ 0 ] ) : config [ 0 ] ,
5972 ] ;
@@ -63,71 +76,110 @@ export function execute(
6376 options : KarmaBuilderOptions ,
6477 context : BuilderContext ,
6578 transforms : {
66- webpackConfiguration ?: ExecutionTransformer < webpack . Configuration > ,
79+ webpackConfiguration ?: ExecutionTransformer < webpack . Configuration > ;
6780 // The karma options transform cannot be async without a refactor of the builder implementation
68- karmaOptions ?: ( options : KarmaConfigOptions ) => KarmaConfigOptions ,
81+ karmaOptions ?: ( options : KarmaConfigOptions ) => KarmaConfigOptions ;
6982 } = { } ,
7083) : Observable < BuilderOutput > {
7184 // Check Angular version.
7285 assertCompatibleAngularVersion ( context . workspaceRoot , context . logger ) ;
7386
7487 return from ( initialize ( options , context , transforms . webpackConfiguration ) ) . pipe (
75- switchMap ( ( [ karma , webpackConfig ] ) => new Observable < BuilderOutput > ( subscriber => {
76- const karmaOptions : KarmaConfigOptions = { } ;
77-
78- if ( options . watch !== undefined ) {
79- karmaOptions . singleRun = ! options . watch ;
80- }
81-
82- // Convert browsers from a string to an array
83- if ( options . browsers ) {
84- karmaOptions . browsers = options . browsers . split ( ',' ) ;
85- }
86-
87- if ( options . reporters ) {
88- // Split along commas to make it more natural, and remove empty strings.
89- const reporters = options . reporters
90- . reduce < string [ ] > ( ( acc , curr ) => acc . concat ( curr . split ( ',' ) ) , [ ] )
91- . filter ( x => ! ! x ) ;
92-
93- if ( reporters . length > 0 ) {
94- karmaOptions . reporters = reporters ;
95- }
96- }
97-
98- // Assign additional karmaConfig options to the local ngapp config
99- karmaOptions . configFile = resolve ( context . workspaceRoot , options . karmaConfig ) ;
100-
101- karmaOptions . buildWebpack = {
102- options,
103- webpackConfig,
104- // Pass onto Karma to emit BuildEvents.
105- successCb : ( ) => subscriber . next ( { success : true } ) ,
106- failureCb : ( ) => subscriber . next ( { success : false } ) ,
107- // Workaround for https://github.com/karma-runner/karma/issues/3154
108- // When this workaround is removed, user projects need to be updated to use a Karma
109- // version that has a fix for this issue.
110- toJSON : ( ) => { } ,
111- logger : context . logger ,
112- } ;
113-
114- // Complete the observable once the Karma server returns.
115- const karmaServer = new karma . Server (
116- transforms . karmaOptions ? transforms . karmaOptions ( karmaOptions ) : karmaOptions ,
117- ( ) => subscriber . complete ( ) ) ;
118- // karma typings incorrectly define start's return value as void
119- // tslint:disable-next-line:no-use-of-empty-return-value
120- const karmaStart = karmaServer . start ( ) as unknown as Promise < void > ;
121-
122- // Cleanup, signal Karma to exit.
123- return ( ) => {
124- // Karma only has the `stop` method start with 3.1.1, so we must defensively check.
125- const karmaServerWithStop = karmaServer as unknown as { stop : ( ) => Promise < void > } ;
126- if ( typeof karmaServerWithStop . stop === 'function' ) {
127- return karmaStart . then ( ( ) => karmaServerWithStop . stop ( ) ) ;
128- }
129- } ;
130- } ) ) ,
88+ switchMap (
89+ ( [ workspace , karma , webpackConfig ] ) =>
90+ new Observable < BuilderOutput > ( subscriber => {
91+ const karmaOptions : KarmaConfigOptions = { } ;
92+
93+ if ( options . watch !== undefined ) {
94+ karmaOptions . singleRun = ! options . watch ;
95+ }
96+
97+ // Convert browsers from a string to an array
98+ if ( options . browsers ) {
99+ karmaOptions . browsers = options . browsers . split ( ',' ) ;
100+ }
101+
102+ if ( options . reporters ) {
103+ // Split along commas to make it more natural, and remove empty strings.
104+ const reporters = options . reporters
105+ . reduce < string [ ] > ( ( acc , curr ) => acc . concat ( curr . split ( ',' ) ) , [ ] )
106+ . filter ( x => ! ! x ) ;
107+
108+ if ( reporters . length > 0 ) {
109+ karmaOptions . reporters = reporters ;
110+ }
111+ }
112+
113+ // prepend special webpack loader that will transform test.ts
114+ if (
115+ webpackConfig &&
116+ webpackConfig . module &&
117+ options . include &&
118+ options . include . length > 0
119+ ) {
120+ const mainFilePath = getSystemPath ( join ( workspace . root , options . main ) ) ;
121+ const files = findTests (
122+ options . include ,
123+ dirname ( mainFilePath ) ,
124+ getSystemPath ( workspace . root ) ,
125+ ) ;
126+ // early exit, no reason to start karma
127+ if ( ! files . length ) {
128+ subscriber . error (
129+ `Specified patterns: "${ options . include . join ( ', ' ) } " did not match any spec files` ,
130+ ) ;
131+
132+ return ;
133+ }
134+
135+ webpackConfig . module . rules . unshift ( {
136+ test : path => path === mainFilePath ,
137+ use : {
138+ // cannot be a simple path as it differs between environments
139+ loader : SingleTestTransformLoader ,
140+ options : {
141+ files,
142+ logger : context . logger ,
143+ } as SingleTestTransformLoaderOptions ,
144+ } ,
145+ } ) ;
146+ }
147+
148+ // Assign additional karmaConfig options to the local ngapp config
149+ karmaOptions . configFile = resolve ( context . workspaceRoot , options . karmaConfig ) ;
150+
151+ karmaOptions . buildWebpack = {
152+ options,
153+ webpackConfig,
154+ // Pass onto Karma to emit BuildEvents.
155+ successCb : ( ) => subscriber . next ( { success : true } ) ,
156+ failureCb : ( ) => subscriber . next ( { success : false } ) ,
157+ // Workaround for https://github.com/karma-runner/karma/issues/3154
158+ // When this workaround is removed, user projects need to be updated to use a Karma
159+ // version that has a fix for this issue.
160+ toJSON : ( ) => { } ,
161+ logger : context . logger ,
162+ } ;
163+
164+ // Complete the observable once the Karma server returns.
165+ const karmaServer = new karma . Server (
166+ transforms . karmaOptions ? transforms . karmaOptions ( karmaOptions ) : karmaOptions ,
167+ ( ) => subscriber . complete ( ) ,
168+ ) ;
169+ // karma typings incorrectly define start's return value as void
170+ // tslint:disable-next-line:no-use-of-empty-return-value
171+ const karmaStart = ( karmaServer . start ( ) as unknown ) as Promise < void > ;
172+
173+ // Cleanup, signal Karma to exit.
174+ return ( ) => {
175+ // Karma only has the `stop` method start with 3.1.1, so we must defensively check.
176+ const karmaServerWithStop = ( karmaServer as unknown ) as { stop : ( ) => Promise < void > } ;
177+ if ( typeof karmaServerWithStop . stop === 'function' ) {
178+ return karmaStart . then ( ( ) => karmaServerWithStop . stop ( ) ) ;
179+ }
180+ } ;
181+ } ) ,
182+ ) ,
131183 defaultIfEmpty ( { success : false } ) ,
132184 ) ;
133185}
0 commit comments