Skip to content

Commit b613249

Browse files
committed
refactor(@angular/build): decouple test discovery from runner execution
This commit refactors the unit test builder to centralize test file discovery, preventing redundant operations in the test runner's execution phase. The `TestRunner` API has been updated to pass the test entry point mappings from the initial build options phase directly to the executor. This removes the need for the Vitest executor to re-discover tests, simplifying its logic and adhering to the DRY principle. The JSDoc comments for the runner API have also been updated to reflect these changes.
1 parent a195db3 commit b613249

File tree

5 files changed

+45
-26
lines changed

5 files changed

+45
-26
lines changed

packages/angular/build/src/builders/unit-test/builder.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,8 @@ export async function* execute(
144144
const normalizedOptions = await normalizeOptions(context, projectName, options);
145145
const runner = await loadTestRunner(normalizedOptions.runnerName);
146146

147-
await using executor = await runner.createExecutor(context, normalizedOptions);
148-
149147
if (runner.isStandalone) {
148+
await using executor = await runner.createExecutor(context, normalizedOptions, undefined);
150149
yield* executor.execute({
151150
kind: ResultKind.Full,
152151
files: {},
@@ -174,9 +173,16 @@ export async function* execute(
174173
}
175174

176175
// Get runner-specific build options from the hook
177-
const { buildOptions: runnerBuildOptions, virtualFiles } = await runner.getBuildOptions(
176+
const {
177+
buildOptions: runnerBuildOptions,
178+
virtualFiles,
179+
testEntryPointMappings,
180+
} = await runner.getBuildOptions(normalizedOptions, buildTargetOptions);
181+
182+
await using executor = await runner.createExecutor(
183+
context,
178184
normalizedOptions,
179-
buildTargetOptions,
185+
testEntryPointMappings,
180186
);
181187

182188
const finalExtensions = prepareBuildExtensions(

packages/angular/build/src/builders/unit-test/runners/api.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,27 @@ import type { ApplicationBuilderInternalOptions } from '../../application/option
1111
import type { FullResult, IncrementalResult } from '../../application/results';
1212
import type { NormalizedUnitTestBuilderOptions } from '../options';
1313

14+
/**
15+
* Represents the options for a test runner.
16+
*/
1417
export interface RunnerOptions {
18+
/**
19+
* Partial options for the application builder.
20+
* These will be merged with the options from the build target.
21+
*/
1522
buildOptions: Partial<ApplicationBuilderInternalOptions>;
23+
24+
/**
25+
* A record of virtual files to be added to the build.
26+
* The key is the file path and the value is the file content.
27+
*/
1628
virtualFiles?: Record<string, string>;
29+
30+
/**
31+
* A map of test entry points to their corresponding test files.
32+
* This is used to avoid re-discovering the test files in the executor.
33+
*/
34+
testEntryPointMappings?: Map<string, string>;
1735
}
1836

1937
/**
@@ -51,10 +69,12 @@ export interface TestRunner {
5169
*
5270
* @param context The Architect builder context.
5371
* @param options The normalized unit test options.
72+
* @param testEntryPointMappings A map of test entry points to their corresponding test files.
5473
* @returns A TestExecutor instance that will handle the test runs.
5574
*/
5675
createExecutor(
5776
context: BuilderContext,
5877
options: NormalizedUnitTestBuilderOptions,
78+
testEntryPointMappings: Map<string, string> | undefined,
5979
): Promise<TestExecutor>;
6080
}

packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,6 @@ export async function getVitestBuildOptions(
120120
virtualFiles: {
121121
'angular:test-bed-init': testBedInitContents,
122122
},
123+
testEntryPointMappings: entryPoints,
123124
};
124125
}

packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,20 @@ export class VitestExecutor implements TestExecutor {
4040
private readonly testFileToEntryPoint = new Map<string, string>();
4141
private readonly entryPointToTestFile = new Map<string, string>();
4242

43-
constructor(projectName: string, options: NormalizedUnitTestBuilderOptions) {
43+
constructor(
44+
projectName: string,
45+
options: NormalizedUnitTestBuilderOptions,
46+
testEntryPointMappings: Map<string, string> | undefined,
47+
) {
4448
this.projectName = projectName;
4549
this.options = options;
50+
51+
if (testEntryPointMappings) {
52+
for (const [entryPoint, testFile] of testEntryPointMappings) {
53+
this.testFileToEntryPoint.set(testFile, entryPoint);
54+
this.entryPointToTestFile.set(entryPoint + '.js', testFile);
55+
}
56+
}
4657
}
4758

4859
async *execute(buildResult: FullResult | IncrementalResult): AsyncIterable<BuilderOutput> {
@@ -60,25 +71,6 @@ export class VitestExecutor implements TestExecutor {
6071
}
6172
}
6273

63-
// The `getTestEntrypoints` function is used here to create the same mapping
64-
// that was used in `build-options.ts` to generate the build entry points.
65-
// This is a deliberate duplication to avoid a larger refactoring of the
66-
// builder's core interfaces to pass the entry points from the build setup
67-
// phase to the execution phase.
68-
if (this.testFileToEntryPoint.size === 0) {
69-
const { include, exclude = [], workspaceRoot, projectSourceRoot } = this.options;
70-
const testFiles = await findTests(include, exclude, workspaceRoot, projectSourceRoot);
71-
const entryPoints = getTestEntrypoints(testFiles, {
72-
projectSourceRoot,
73-
workspaceRoot,
74-
removeTestExtension: true,
75-
});
76-
for (const [entryPoint, testFile] of entryPoints) {
77-
this.testFileToEntryPoint.set(testFile, entryPoint);
78-
this.entryPointToTestFile.set(entryPoint + '.js', testFile);
79-
}
80-
}
81-
8274
// Initialize Vitest if not already present.
8375
this.vitest ??= await this.initializeVitest();
8476
const vitest = this.vitest;

packages/angular/build/src/builders/unit-test/runners/vitest/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ const VitestTestRunner: TestRunner = {
2121
return getVitestBuildOptions(options, baseBuildOptions);
2222
},
2323

24-
async createExecutor(context, options) {
24+
async createExecutor(context, options, testEntryPointMappings) {
2525
const projectName = context.target?.project;
2626
assert(projectName, 'The builder requires a target.');
2727

28-
return new VitestExecutor(projectName, options);
28+
return new VitestExecutor(projectName, options, testEntryPointMappings);
2929
},
3030
};
3131

0 commit comments

Comments
 (0)