Skip to content

Commit 06b43fc

Browse files
committed
fix(@angular/build): bundle setup files in unit-test builder for Vitest
This change updates the Vitest runner in the unit-test builder to bundle 'setupFiles' through the application build pipeline, similar to test files. This ensures that setup files are processed with the same transformations and environment as the application code. Note: Only setup files specified in the 'setupFiles' builder option are bundled. Setup files referenced solely within a custom Vitest configuration file (via 'runnerConfig') will not be bundled by the Angular CLI and will be processed directly by Vitest.
1 parent fe4c310 commit 06b43fc

File tree

6 files changed

+52
-15
lines changed

6 files changed

+52
-15
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,20 @@ export async function getVitestBuildOptions(
9898
workspaceRoot,
9999
removeTestExtension: true,
100100
});
101+
102+
if (options.setupFiles?.length) {
103+
const setupEntryPoints = getTestEntrypoints(options.setupFiles, {
104+
projectSourceRoot,
105+
workspaceRoot,
106+
removeTestExtension: false,
107+
prefix: 'setup',
108+
});
109+
110+
for (const [entryPoint, setupFile] of setupEntryPoints) {
111+
entryPoints.set(entryPoint, setupFile);
112+
}
113+
}
114+
101115
entryPoints.set('init-testbed', 'angular:test-bed-init');
102116

103117
// The 'vitest' package is always external for testing purposes

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,15 @@ export class VitestExecutor implements TestExecutor {
240240
project = `${projectName} (${browserOptions.browser.instances[0].browser})`;
241241
}
242242

243+
// Filter internal entries and setup files from the include list
244+
const internalEntries = ['angular:'];
245+
const setupFileSet = new Set(testSetupFiles);
246+
const include = [...this.testFileToEntryPoint.keys()].filter((entry) => {
247+
return (
248+
!internalEntries.some((internal) => entry.startsWith(internal)) && !setupFileSet.has(entry)
249+
);
250+
});
251+
243252
return startVitest(
244253
'test',
245254
undefined,
@@ -272,10 +281,7 @@ export class VitestExecutor implements TestExecutor {
272281
reporters,
273282
setupFiles: testSetupFiles,
274283
projectPlugins,
275-
include: [...this.testFileToEntryPoint.keys()].filter(
276-
// Filter internal entries
277-
(entry) => !entry.startsWith('angular:'),
278-
),
284+
include,
279285
}),
280286
],
281287
},

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ interface TestEntrypointsOptions {
8181
projectSourceRoot: string;
8282
workspaceRoot: string;
8383
removeTestExtension?: boolean;
84+
prefix?: string;
8485
}
8586

8687
/**
@@ -93,15 +94,20 @@ interface TestEntrypointsOptions {
9394
*/
9495
export function getTestEntrypoints(
9596
testFiles: string[],
96-
{ projectSourceRoot, workspaceRoot, removeTestExtension }: TestEntrypointsOptions,
97+
{
98+
projectSourceRoot,
99+
workspaceRoot,
100+
removeTestExtension,
101+
prefix = 'spec',
102+
}: TestEntrypointsOptions,
97103
): Map<string, string> {
98104
const seen = new Set<string>();
99105
const roots = [projectSourceRoot, workspaceRoot];
100106

101107
return new Map(
102108
Array.from(testFiles, (testFile) => {
103109
const fileName = generateNameFromPath(testFile, roots, !!removeTestExtension);
104-
const baseName = `spec-${fileName}`;
110+
const baseName = `${prefix}-${fileName}`;
105111
let uniqueName = baseName;
106112
let suffix = 2;
107113
while (seen.has(uniqueName)) {

packages/angular/build/src/builders/unit-test/tests/options/setup-files_spec.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from '../setup';
1717

1818
describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
19-
xdescribe('Option: "setupFiles"', () => {
19+
describe('Option: "setupFiles"', () => {
2020
beforeEach(async () => {
2121
setupApplicationTarget(harness);
2222
});
@@ -27,19 +27,21 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
2727
setupFiles: ['src/setup.ts'],
2828
});
2929

30-
const { result, error } = await harness.executeOnce({ outputLogsOnFailure: false });
31-
expect(result).toBeUndefined();
32-
expect(error?.message).toMatch(`The specified setup file "src/setup.ts" does not exist.`);
30+
const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false });
31+
expect(result?.success).toBeFalse();
32+
// Verify that the build failed due to resolution error (esbuild error)
33+
expectLog(logs, /Could not resolve/);
34+
expectLog(logs, /src\/setup\.ts/);
3335
});
3436

3537
it('should include the setup files', async () => {
3638
await harness.writeFiles({
37-
'src/setup.ts': `console.log('Hello from setup.ts');`,
39+
'src/setup.ts': `globalThis['TEST_SETUP_RAN'] = true;`,
3840
'src/app/app.component.spec.ts': `
3941
import { describe, expect, test } from 'vitest'
4042
describe('AppComponent', () => {
41-
test('should create the app', () => {
42-
expect(true).toBe(true);
43+
test('should have run setup file', () => {
44+
expect(globalThis['TEST_SETUP_RAN']).toBe(true);
4345
});
4446
});`,
4547
});
@@ -49,9 +51,8 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
4951
setupFiles: ['src/setup.ts'],
5052
});
5153

52-
const { result, logs } = await harness.executeOnce();
54+
const { result } = await harness.executeOnce();
5355
expect(result?.success).toBeTrue();
54-
expectLog(logs, 'Hello from setup.ts');
5556
});
5657
});
5758
});

tests/e2e/tests/vitest/browser-playwright.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { applyVitestBuilder } from '../../utils/vitest';
33
import { ng } from '../../utils/process';
44
import { installPackage } from '../../utils/packages';
55
import { writeFile } from '../../utils/fs';
6+
import { updateJsonFile } from '../../utils/project';
67

78
export default async function (): Promise<void> {
89
await applyVitestBuilder();
@@ -19,6 +20,10 @@ export default async function (): Promise<void> {
1920
`,
2021
);
2122

23+
await updateJsonFile('tsconfig.spec.json', (json) => {
24+
json.include = [...(json.include || []), 'src/setup1.ts'];
25+
});
26+
2227
const { stdout } = await ng(
2328
'test',
2429
'--no-watch',

tests/e2e/tests/vitest/browser-webdriverio.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { applyVitestBuilder } from '../../utils/vitest';
33
import { ng } from '../../utils/process';
44
import { installPackage } from '../../utils/packages';
55
import { writeFile } from '../../utils/fs';
6+
import { updateJsonFile } from '../../utils/project';
67

78
export default async function (): Promise<void> {
89
await applyVitestBuilder();
@@ -20,6 +21,10 @@ export default async function (): Promise<void> {
2021
`,
2122
);
2223

24+
await updateJsonFile('tsconfig.spec.json', (json) => {
25+
json.include = [...(json.include || []), 'src/setup1.ts'];
26+
});
27+
2328
const { stdout } = await ng(
2429
'test',
2530
'--no-watch',

0 commit comments

Comments
 (0)