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
1 change: 1 addition & 0 deletions goldens/public-api/angular/build/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ export type UnitTestBuilderOptions = {
runner?: Runner;
runnerConfig?: RunnerConfig;
setupFiles?: string[];
splitting?: boolean;
tsConfig?: string;
ui?: boolean;
watch?: boolean;
Expand Down
14 changes: 12 additions & 2 deletions packages/angular/build/src/builders/unit-test/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,17 @@ export async function normalizeOptions(
const cacheOptions = normalizeCacheOptions(projectMetadata, workspaceRoot);
cacheOptions.path = path.join(cacheOptions.path, projectName);

const { runner, browsers, progress, filter, browserViewport, ui, runnerConfig, isolate } =
options;
const {
runner,
browsers,
progress,
filter,
browserViewport,
ui,
runnerConfig,
isolate,
splitting = true,
} = options;

if (ui && runner !== Runner.Vitest) {
throw new Error('The "ui" option is only available for the "vitest" runner.');
Expand Down Expand Up @@ -140,6 +149,7 @@ export async function normalizeOptions(
debug: options.debug ?? false,
ui: process.env['CI'] ? false : ui,
isolate,
splitting,
quiet: options.quiet ?? (process.env['CI'] ? false : true),
providersFile: options.providersFile && path.join(workspaceRoot, options.providersFile),
setupFiles: options.setupFiles
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,8 @@ export async function getVitestBuildOptions(
entryPoints,
// Vitest's Node-based module loading emulation (vite-node) is not fully spec compliant and lacks
// live ESM bindings across chunk boundaries. This can cause uninitialized exports or break mocking.
// In browser tests, however, the real browser adheres to the ECMAScript spec, so code splitting can
// be safely enabled.
disableCodeSplitting: options.browsers?.length ? false : true,
// Disabling code splitting avoids shared chunks, but increases build and coverage memory/time.
disableCodeSplitting: !options.splitting,
// Enable support for vitest browser prebundling. Excludes can be controlled with a runnerConfig
// and the `optimizeDeps.exclude` option.
externalPackages: true,
Expand Down
5 changes: 5 additions & 0 deletions packages/angular/build/src/builders/unit-test/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@
"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."
},
"splitting": {
"type": "boolean",
"description": "Enables code splitting for test execution. When enabled, shared code between test files is split into separate chunks. Issues with live ESM bindings in Node.js environments (such as uninitialized exports or mocking failures) can be resolved by disabling splitting. This option is only available for the Vitest runner.",
"default": true
},
"quiet": {
"type": "boolean",
"description": "Suppresses the verbose build summary and stats table on each rebuild. Defaults to `true` locally and `false` in CI environments."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {

harness.useTarget('test', {
...BASE_OPTIONS,
splitting: false,
});

// Keep the default project's spec deterministic; a third spec entry that does not touch
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* @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,
expectNoLog,
} from '../setup';

describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
describe('Option: "splitting"', () => {
beforeEach(async () => {
setupApplicationTarget(harness);
});

it('should default to true and run tests successfully', async () => {
harness.useTarget('test', {
...BASE_OPTIONS,
});

const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();
});

it('should run tests successfully when splitting is true', async () => {
harness.useTarget('test', {
...BASE_OPTIONS,
splitting: true,
});

const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();
});

it('should run tests successfully when splitting is false', async () => {
harness.useTarget('test', {
...BASE_OPTIONS,
splitting: false,
});

const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();
});

it('should split shared code into a separate chunk when splitting is true', async () => {
await harness.writeFile(
'src/app/shared.ts',
`export const SHARED_DATA = 'shared-data-value';`,
);

await harness.writeFile(
'src/app/first.spec.ts',
`
import { SHARED_DATA } from './shared';

it('tests first', () => {
expect(SHARED_DATA).toBe('shared-data-value');
});
`,
);

await harness.writeFile(
'src/app/second.spec.ts',
`
import { SHARED_DATA } from './shared';

it('tests second', () => {
expect(SHARED_DATA).toBe('shared-data-value');
});
`,
);

harness.useTarget('test', {
...BASE_OPTIONS,
splitting: true,
quiet: false,
});

const { result, logs } = await harness.executeOnce();
expect(result?.success).toBeTrue();
expectLog(logs, /chunk-[a-z0-9]+\.js/i);
});

it('should not split shared code into a separate chunk when splitting is false', async () => {
await harness.writeFile(
'src/app/shared.ts',
`export const SHARED_DATA = 'shared-data-value';`,
);

await harness.writeFile(
'src/app/first.spec.ts',
`
import { SHARED_DATA } from './shared';

it('tests first', () => {
expect(SHARED_DATA).toBe('shared-data-value');
});
`,
);

await harness.writeFile(
'src/app/second.spec.ts',
`
import { SHARED_DATA } from './shared';

it('tests second', () => {
expect(SHARED_DATA).toBe('shared-data-value');
});
`,
);

harness.useTarget('test', {
...BASE_OPTIONS,
splitting: false,
quiet: false,
});

const { result, logs } = await harness.executeOnce();
expect(result?.success).toBeTrue();
expectNoLog(logs, /chunk-[a-z0-9]+\.js/i);
});
});
});