Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions packages/angular/build/src/builders/unit-test/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,8 @@ export async function* execute(
const normalizedOptions = await normalizeOptions(context, projectName, options);
const runner = await loadTestRunner(normalizedOptions.runnerName);

await using executor = await runner.createExecutor(context, normalizedOptions);

if (runner.isStandalone) {
await using executor = await runner.createExecutor(context, normalizedOptions, undefined);
yield* executor.execute({
kind: ResultKind.Full,
files: {},
Expand Down Expand Up @@ -174,9 +173,16 @@ export async function* execute(
}

// Get runner-specific build options from the hook
const { buildOptions: runnerBuildOptions, virtualFiles } = await runner.getBuildOptions(
const {
buildOptions: runnerBuildOptions,
virtualFiles,
testEntryPointMappings,
} = await runner.getBuildOptions(normalizedOptions, buildTargetOptions);

await using executor = await runner.createExecutor(
context,
normalizedOptions,
buildTargetOptions,
testEntryPointMappings,
);

const finalExtensions = prepareBuildExtensions(
Expand Down
20 changes: 20 additions & 0 deletions packages/angular/build/src/builders/unit-test/runners/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,27 @@ import type { ApplicationBuilderInternalOptions } from '../../application/option
import type { FullResult, IncrementalResult } from '../../application/results';
import type { NormalizedUnitTestBuilderOptions } from '../options';

/**
* Represents the options for a test runner.
*/
export interface RunnerOptions {
/**
* Partial options for the application builder.
* These will be merged with the options from the build target.
*/
buildOptions: Partial<ApplicationBuilderInternalOptions>;

/**
* A record of virtual files to be added to the build.
* The key is the file path and the value is the file content.
*/
virtualFiles?: Record<string, string>;

/**
* A map of test entry points to their corresponding test files.
* This is used to avoid re-discovering the test files in the executor.
*/
testEntryPointMappings?: Map<string, string>;
}

/**
Expand Down Expand Up @@ -51,10 +69,12 @@ export interface TestRunner {
*
* @param context The Architect builder context.
* @param options The normalized unit test options.
* @param testEntryPointMappings A map of test entry points to their corresponding test files.
* @returns A TestExecutor instance that will handle the test runs.
*/
createExecutor(
context: BuilderContext,
options: NormalizedUnitTestBuilderOptions,
testEntryPointMappings: Map<string, string> | undefined,
): Promise<TestExecutor>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,6 @@ export async function getVitestBuildOptions(
virtualFiles: {
'angular:test-bed-init': testBedInitContents,
},
testEntryPointMappings: entryPoints,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,20 @@ export class VitestExecutor implements TestExecutor {
private readonly testFileToEntryPoint = new Map<string, string>();
private readonly entryPointToTestFile = new Map<string, string>();

constructor(projectName: string, options: NormalizedUnitTestBuilderOptions) {
constructor(
projectName: string,
options: NormalizedUnitTestBuilderOptions,
testEntryPointMappings: Map<string, string> | undefined,
) {
this.projectName = projectName;
this.options = options;

if (testEntryPointMappings) {
for (const [entryPoint, testFile] of testEntryPointMappings) {
this.testFileToEntryPoint.set(testFile, entryPoint);
this.entryPointToTestFile.set(entryPoint + '.js', testFile);
}
}
}

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

// The `getTestEntrypoints` function is used here to create the same mapping
// that was used in `build-options.ts` to generate the build entry points.
// This is a deliberate duplication to avoid a larger refactoring of the
// builder's core interfaces to pass the entry points from the build setup
// phase to the execution phase.
if (this.testFileToEntryPoint.size === 0) {
const { include, exclude = [], workspaceRoot, projectSourceRoot } = this.options;
const testFiles = await findTests(include, exclude, workspaceRoot, projectSourceRoot);
const entryPoints = getTestEntrypoints(testFiles, {
projectSourceRoot,
workspaceRoot,
removeTestExtension: true,
});
for (const [entryPoint, testFile] of entryPoints) {
this.testFileToEntryPoint.set(testFile, entryPoint);
this.entryPointToTestFile.set(entryPoint + '.js', testFile);
}
}

// Initialize Vitest if not already present.
this.vitest ??= await this.initializeVitest();
const vitest = this.vitest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ const VitestTestRunner: TestRunner = {
return getVitestBuildOptions(options, baseBuildOptions);
},

async createExecutor(context, options) {
async createExecutor(context, options, testEntryPointMappings) {
const projectName = context.target?.project;
assert(projectName, 'The builder requires a target.');

return new VitestExecutor(projectName, options);
return new VitestExecutor(projectName, options, testEntryPointMappings);
},
};

Expand Down