diff --git a/packages/angular/build/src/builders/unit-test/builder.ts b/packages/angular/build/src/builders/unit-test/builder.ts index 31b46f61064e..dab2f0e18e54 100644 --- a/packages/angular/build/src/builders/unit-test/builder.ts +++ b/packages/angular/build/src/builders/unit-test/builder.ts @@ -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: {}, @@ -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( diff --git a/packages/angular/build/src/builders/unit-test/runners/api.ts b/packages/angular/build/src/builders/unit-test/runners/api.ts index 60d30ecf0cc3..dd88c4435469 100644 --- a/packages/angular/build/src/builders/unit-test/runners/api.ts +++ b/packages/angular/build/src/builders/unit-test/runners/api.ts @@ -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; + + /** + * 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; + + /** + * 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; } /** @@ -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 | undefined, ): Promise; } diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts index e94a35e7f8a4..8cea7afe2bbe 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts @@ -120,5 +120,6 @@ export async function getVitestBuildOptions( virtualFiles: { 'angular:test-bed-init': testBedInitContents, }, + testEntryPointMappings: entryPoints, }; } diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts index b97c451f08a0..6dec1f546004 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts @@ -40,9 +40,20 @@ export class VitestExecutor implements TestExecutor { private readonly testFileToEntryPoint = new Map(); private readonly entryPointToTestFile = new Map(); - constructor(projectName: string, options: NormalizedUnitTestBuilderOptions) { + constructor( + projectName: string, + options: NormalizedUnitTestBuilderOptions, + testEntryPointMappings: Map | 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 { @@ -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; diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/index.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/index.ts index b257bb984250..8b44a1a397a8 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/index.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/index.ts @@ -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); }, };