diff --git a/goldens/public-api/angular/build/index.api.md b/goldens/public-api/angular/build/index.api.md index 4a56a0c4b683..3ca6e8d98d12 100644 --- a/goldens/public-api/angular/build/index.api.md +++ b/goldens/public-api/angular/build/index.api.md @@ -233,6 +233,7 @@ export type UnitTestBuilderOptions = { filter?: string; headless?: boolean; include?: string[]; + isolate?: boolean; listTests?: boolean; outputFile?: string; progress?: boolean; diff --git a/packages/angular/build/src/builders/unit-test/options.ts b/packages/angular/build/src/builders/unit-test/options.ts index b2b3d6740ff1..5cd04c4ca7bf 100644 --- a/packages/angular/build/src/builders/unit-test/options.ts +++ b/packages/angular/build/src/builders/unit-test/options.ts @@ -54,12 +54,17 @@ export async function normalizeOptions( const buildTargetSpecifier = options.buildTarget ?? `::development`; const buildTarget = targetFromTargetString(buildTargetSpecifier, projectName, 'build'); - const { runner, browsers, progress, filter, browserViewport, ui, runnerConfig } = options; + const { runner, browsers, progress, filter, browserViewport, ui, runnerConfig, isolate } = + options; if (ui && runner !== Runner.Vitest) { throw new Error('The "ui" option is only available for the "vitest" runner.'); } + if (isolate && runner !== Runner.Vitest) { + throw new Error('The "isolate" option is only available for the "vitest" runner.'); + } + const [width, height] = browserViewport?.split('x').map(Number) ?? []; let tsConfig = options.tsConfig; @@ -121,6 +126,7 @@ export async function normalizeOptions( watch, debug: options.debug ?? false, ui: process.env['CI'] ? false : ui, + isolate: isolate ?? false, quiet: options.quiet ?? (process.env['CI'] ? false : true), providersFile: options.providersFile && path.join(workspaceRoot, options.providersFile), setupFiles: options.setupFiles 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 c5b70e9a2487..0a7a3c7ea63b 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 @@ -378,6 +378,7 @@ export class VitestExecutor implements TestExecutor { projectPlugins, include, watch, + isolate: this.options.isolate, }), ], }; diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts index 59a1e5136456..76cbe7d58d03 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts @@ -54,6 +54,7 @@ interface VitestConfigPluginOptions { include: string[]; optimizeDepsInclude: string[]; watch: boolean; + isolate: boolean; } async function findTestEnvironment( @@ -271,6 +272,8 @@ export async function createVitestConfigPlugin( include, // CLI provider browser options override, if present ...(browser ? { browser } : {}), + // Only override if the user explicitly enabled it via CLI + ...(options.isolate ? { isolate: true } : {}), // If the user has not specified an environment, use a smart default. ...(!testConfig?.environment ? { environment: await findTestEnvironment(projectResolver) } diff --git a/packages/angular/build/src/builders/unit-test/schema.json b/packages/angular/build/src/builders/unit-test/schema.json index 403b61a9009b..d891c3b7928d 100644 --- a/packages/angular/build/src/builders/unit-test/schema.json +++ b/packages/angular/build/src/builders/unit-test/schema.json @@ -73,6 +73,10 @@ "type": "boolean", "description": "Enables the Vitest UI for interactive test execution. This option is only available for the Vitest runner." }, + "isolate": { + "type": "boolean", + "description": "Enables isolation for test execution. When true, Vitest runs tests in separate threads or processes. This option is only available for the Vitest runner. Defaults to false to align with the Karma/Jasmine experience." + }, "quiet": { "type": "boolean", "description": "Suppresses the verbose build summary and stats table on each rebuild. Defaults to `true` locally and `false` in CI environments." diff --git a/packages/angular/build/src/builders/unit-test/tests/options/isolate_spec.ts b/packages/angular/build/src/builders/unit-test/tests/options/isolate_spec.ts new file mode 100644 index 000000000000..6ca00a02d94d --- /dev/null +++ b/packages/angular/build/src/builders/unit-test/tests/options/isolate_spec.ts @@ -0,0 +1,58 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { execute } from '../../index'; +import { + BASE_OPTIONS, + describeBuilder, + UNIT_TEST_BUILDER_INFO, + setupApplicationTarget, + expectLog, +} from '../setup'; + +describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { + describe('Option: "isolate"', () => { + beforeEach(async () => { + setupApplicationTarget(harness); + }); + + it('should fail when isolate is true and runner is karma', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + runner: 'karma' as any, + isolate: true, + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeFalse(); + expectLog(logs, /The "isolate" option is only available for the "vitest" runner/); + }); + + it('should run tests successfully when isolate is true and runner is vitest', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + runner: 'vitest' as any, + isolate: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + }); + + it('should run tests successfully when isolate is false and runner is vitest', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + runner: 'vitest' as any, + isolate: false, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + }); + }); +});