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