6
6
* found in the LICENSE file at https://angular.dev/license
7
7
*/
8
8
9
- import {
10
- ApplicationBuilderInternalOptions ,
11
- buildApplicationInternal ,
12
- } from '@angular/build/private' ;
9
+ import { ResultKind , buildApplicationInternal } from '@angular/build/private' ;
13
10
import { BuilderContext , BuilderOutput , createBuilder } from '@angular-devkit/architect' ;
14
11
import { execFile as execFileCb } from 'node:child_process' ;
12
+ import { randomUUID } from 'node:crypto' ;
15
13
import * as fs from 'node:fs/promises' ;
16
14
import * as path from 'node:path' ;
17
15
import { promisify } from 'node:util' ;
18
16
import { colors } from '../../utils/color' ;
19
17
import { findTestFiles } from '../../utils/test-files' ;
20
18
import { OutputHashing } from '../browser-esbuild/schema' ;
19
+ import { writeTestFiles } from '../web-test-runner/write-test-files' ;
21
20
import { normalizeOptions } from './options' ;
22
21
import { Schema as JestBuilderSchema } from './schema' ;
23
22
@@ -31,7 +30,7 @@ export default createBuilder(
31
30
) ;
32
31
33
32
const options = normalizeOptions ( schema ) ;
34
- const testOut = 'dist/test-out' ; // TODO(dgp1130): Hide in temp directory.
33
+ const testOut = path . join ( context . workspaceRoot , 'dist/test-out' , randomUUID ( ) ) ; // TODO(dgp1130): Hide in temp directory.
35
34
36
35
// Verify Jest installation and get the path to it's binary.
37
36
// We need to `node_modules/.bin/jest`, but there is no means to resolve that directly. Fortunately Jest's `package.json` exports the
@@ -78,33 +77,47 @@ export default createBuilder(
78
77
// Build all the test files.
79
78
const jestGlobal = path . join ( __dirname , 'jest-global.mjs' ) ;
80
79
const initTestBed = path . join ( __dirname , 'init-test-bed.mjs' ) ;
81
- const buildResult = await build ( context , {
82
- // Build all the test files and also the `jest-global` and `init-test-bed` scripts.
83
- entryPoints : new Set ( [ ...testFiles , jestGlobal , initTestBed ] ) ,
84
- tsConfig : options . tsConfig ,
85
- polyfills : options . polyfills ?? [ 'zone.js' , 'zone.js/testing' ] ,
86
- outputPath : testOut ,
87
- aot : false ,
88
- index : false ,
89
- outputHashing : OutputHashing . None ,
90
- outExtension : 'mjs' , // Force native ESM.
91
- optimization : false ,
92
- sourceMap : {
93
- scripts : true ,
94
- styles : false ,
95
- vendor : false ,
96
- } ,
97
- } ) ;
98
- if ( ! buildResult . success ) {
99
- return buildResult ;
80
+ const buildResult = await first (
81
+ buildApplicationInternal (
82
+ {
83
+ // Build all the test files and also the `jest-global` and `init-test-bed` scripts.
84
+ entryPoints : new Set ( [ ...testFiles , jestGlobal , initTestBed ] ) ,
85
+ tsConfig : options . tsConfig ,
86
+ polyfills : options . polyfills ?? [ 'zone.js' , 'zone.js/testing' ] ,
87
+ outputPath : testOut ,
88
+ aot : false ,
89
+ index : false ,
90
+ outputHashing : OutputHashing . None ,
91
+ outExtension : 'mjs' , // Force native ESM.
92
+ optimization : false ,
93
+ sourceMap : {
94
+ scripts : true ,
95
+ styles : false ,
96
+ vendor : false ,
97
+ } ,
98
+ } ,
99
+ context ,
100
+ { write : false } ,
101
+ ) ,
102
+ ) ;
103
+ if ( buildResult . kind === ResultKind . Failure ) {
104
+ return { success : false } ;
105
+ } else if ( buildResult . kind !== ResultKind . Full ) {
106
+ return {
107
+ success : false ,
108
+ error : 'A full build result is required from the application builder.' ,
109
+ } ;
100
110
}
101
111
112
+ // Write test files
113
+ await writeTestFiles ( buildResult . files , testOut ) ;
114
+
102
115
// Execute Jest on the built output directory.
103
116
const jestProc = execFile ( process . execPath , [
104
117
'--experimental-vm-modules' ,
105
118
jest ,
106
119
107
- `--rootDir="${ path . join ( testOut , 'browser' ) } "` ,
120
+ `--rootDir="${ testOut } "` ,
108
121
`--config=${ path . join ( __dirname , 'jest.config.mjs' ) } ` ,
109
122
'--testEnvironment=jsdom' ,
110
123
@@ -157,22 +170,13 @@ export default createBuilder(
157
170
} ,
158
171
) ;
159
172
160
- async function build (
161
- context : BuilderContext ,
162
- options : ApplicationBuilderInternalOptions ,
163
- ) : Promise < BuilderOutput > {
164
- try {
165
- for await ( const _ of buildApplicationInternal ( options , context ) ) {
166
- // Nothing to do for each event, just wait for the whole build.
167
- }
168
-
169
- return { success : true } ;
170
- } catch ( err ) {
171
- return {
172
- success : false ,
173
- error : ( err as Error ) . message ,
174
- } ;
173
+ /** Returns the first item yielded by the given generator and cancels the execution. */
174
+ async function first < T > ( generator : AsyncIterable < T > ) : Promise < T > {
175
+ for await ( const value of generator ) {
176
+ return value ;
175
177
}
178
+
179
+ throw new Error ( 'Expected generator to emit at least once.' ) ;
176
180
}
177
181
178
182
/** Safely resolves the given Node module string. */
0 commit comments