@@ -14,6 +14,7 @@ import {
1414} from '@angular-devkit/architect' ;
1515import { Path , getSystemPath , normalize , resolve , virtualFs } from '@angular-devkit/core' ;
1616import * as fs from 'fs' ;
17+ import * as glob from 'glob' ;
1718import { Observable , of } from 'rxjs' ;
1819import { concatMap } from 'rxjs/operators' ;
1920import * as ts from 'typescript' ; // tslint:disable-line:no-implicit-dependencies
@@ -76,12 +77,45 @@ export class KarmaBuilder implements Builder<KarmaBuilderSchema> {
7677 }
7778
7879 const sourceRoot = builderConfig . sourceRoot && resolve ( root , builderConfig . sourceRoot ) ;
80+ const webpackConfig = this . buildWebpackConfig ( root , projectRoot , sourceRoot , host , options ) ;
81+
82+ // generate new entry point with files matching provided glob
83+ if ( options . spec ) {
84+ const mainEntry = webpackConfig . entry . main ;
85+ const newMainEntry = webpackConfig . entry . main . replace ( / \. t s $ / , '.generated.ts' ) ;
86+ // replace original entry with generated one
87+ webpackConfig . entry . main = newMainEntry ;
88+
89+ try {
90+ this . createOrUpdateGeneratedTestFile (
91+ options . spec ,
92+ getSystemPath ( sourceRoot || projectRoot ) ,
93+ builderConfig . sourceRoot ,
94+ mainEntry ,
95+ newMainEntry ,
96+ ) ;
97+
98+ // early exit if we are only supposed to update generated file
99+ if ( options . specUpdate ) {
100+ obs . next ( { success : true , result : 'specs updated' } ) ;
101+ obs . complete ( ) ;
102+
103+ return ;
104+ }
105+ } catch ( err ) {
106+ this . context . logger . error ( err . message ) ;
107+ obs . next ( { success : false } ) ;
108+ obs . complete ( ) ;
109+
110+ return ;
111+ }
112+ }
79113
80114 karmaOptions . buildWebpack = {
81115 root : getSystemPath ( root ) ,
82116 projectRoot : getSystemPath ( projectRoot ) ,
83117 options,
84- webpackConfig : this . buildWebpackConfig ( root , projectRoot , sourceRoot , host , options ) ,
118+ webpackConfig,
85119 // Pass onto Karma to emit BuildEvents.
86120 successCb : ( ) => obs . next ( { success : true } ) ,
87121 failureCb : ( ) => obs . next ( { success : false } ) ,
@@ -160,6 +194,45 @@ export class KarmaBuilder implements Builder<KarmaBuilderSchema> {
160194
161195 return webpackMerge ( webpackConfigs ) ;
162196 }
197+
198+ createOrUpdateGeneratedTestFile (
199+ pattern : string ,
200+ path : string ,
201+ sourceRoot : Path | undefined ,
202+ mainEntry : string ,
203+ newMainEntry : string ,
204+ ) {
205+ let template = fs . readFileSync ( mainEntry ) . toString ( ) ;
206+ // remove source root to support absolute paths
207+ if ( sourceRoot && pattern . startsWith ( sourceRoot + '/' ) ) {
208+ pattern = pattern . substr ( sourceRoot . length + 1 ) ; // +1 to include slash
209+ }
210+ if ( pattern . endsWith ( '.ts' ) && pattern . indexOf ( '.spec.ts' ) === - 1 ) {
211+ pattern = pattern . substr ( 0 , pattern . length - 2 ) + 'spec.ts' ;
212+ } else if ( pattern . indexOf ( '.spec' ) === - 1 ) {
213+ pattern += '.spec.ts' ;
214+ }
215+
216+ const files = glob . sync ( pattern , { cwd : path } ) ;
217+ if ( ! files . length ) {
218+ throw new Error ( 'Specified spec glob does not match any files' ) ;
219+ }
220+
221+ const start = 'import \'' ;
222+ const end = '\';' ;
223+ const testCode = start + files
224+ . map ( path => `./${ path . replace ( '.ts' , '' ) } ` )
225+ . join ( `${ end } \n${ start } ` ) + end ;
226+ // TODO: maybe a documented 'marker/comment' inside test.ts would be nicer
227+ // or run typescript compiler and make changes based on the tree?
228+ let mockedRequireContext = '{ keys: () => ({ map: (_a: any) => { } }) };' ;
229+ mockedRequireContext += process . platform === 'win32' ? '\r\n' : '\n' ;
230+ template = template
231+ . replace ( / d e c l a r e \s + c o n s t \s + r e q u i r e : \s + a n y ; / , '' )
232+ . replace ( / r e q u i r e \. c o n t e x t \( .* / , mockedRequireContext + testCode ) ;
233+
234+ fs . writeFileSync ( newMainEntry , template ) ;
235+ }
163236}
164237
165238export default KarmaBuilder ;
0 commit comments