Skip to content

Commit bb802d1

Browse files
committed
refactor(@angular-devkit/build-angular): only write test related files in Jest builder
With the structured build results available, the Jest builder can now more easily write only the needed files to a temporary location. This reduces the need to assume build directory output structure and reduces the amount of potential filesystem calls. The temporary files are also now written into a UUID subdirectory within the existing `dist/test-out` location. This allows for multiple projects to be tested concurrently without overwriting each other.
1 parent 24be1e7 commit bb802d1

File tree

1 file changed

+44
-40
lines changed
  • packages/angular_devkit/build_angular/src/builders/jest

1 file changed

+44
-40
lines changed

packages/angular_devkit/build_angular/src/builders/jest/index.ts

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,17 @@
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';
1310
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
1411
import { execFile as execFileCb } from 'node:child_process';
12+
import { randomUUID } from 'node:crypto';
1513
import * as fs from 'node:fs/promises';
1614
import * as path from 'node:path';
1715
import { promisify } from 'node:util';
1816
import { colors } from '../../utils/color';
1917
import { findTestFiles } from '../../utils/test-files';
2018
import { OutputHashing } from '../browser-esbuild/schema';
19+
import { writeTestFiles } from '../web-test-runner/write-test-files';
2120
import { normalizeOptions } from './options';
2221
import { 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

Comments
 (0)