66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9- import {
10- ApplicationBuilderInternalOptions ,
11- buildApplicationInternal ,
12- } from '@angular/build/private' ;
9+ import { ResultKind , buildApplicationInternal } from '@angular/build/private' ;
1310import { BuilderContext , BuilderOutput , createBuilder } from '@angular-devkit/architect' ;
1411import { execFile as execFileCb } from 'node:child_process' ;
12+ import { randomUUID } from 'node:crypto' ;
1513import * as fs from 'node:fs/promises' ;
1614import * as path from 'node:path' ;
1715import { promisify } from 'node:util' ;
1816import { colors } from '../../utils/color' ;
1917import { findTestFiles } from '../../utils/test-files' ;
2018import { OutputHashing } from '../browser-esbuild/schema' ;
19+ import { writeTestFiles } from '../web-test-runner/write-test-files' ;
2120import { normalizeOptions } from './options' ;
2221import { Schema as JestBuilderSchema } from './schema' ;
2322
@@ -31,7 +30,7 @@ export default createBuilder(
3130 ) ;
3231
3332 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.
3534
3635 // Verify Jest installation and get the path to it's binary.
3736 // 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(
7877 // Build all the test files.
7978 const jestGlobal = path . join ( __dirname , 'jest-global.mjs' ) ;
8079 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+ } ;
100110 }
101111
112+ // Write test files
113+ await writeTestFiles ( buildResult . files , testOut ) ;
114+
102115 // Execute Jest on the built output directory.
103116 const jestProc = execFile ( process . execPath , [
104117 '--experimental-vm-modules' ,
105118 jest ,
106119
107- `--rootDir="${ path . join ( testOut , 'browser' ) } "` ,
120+ `--rootDir="${ testOut } "` ,
108121 `--config=${ path . join ( __dirname , 'jest.config.mjs' ) } ` ,
109122 '--testEnvironment=jsdom' ,
110123
@@ -157,22 +170,13 @@ export default createBuilder(
157170 } ,
158171) ;
159172
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 ;
175177 }
178+
179+ throw new Error ( 'Expected generator to emit at least once.' ) ;
176180}
177181
178182/** Safely resolves the given Node module string. */
0 commit comments