Skip to content

Commit 23ff4e9

Browse files
committed
refactor(@angular/build): improve Vitest configuration merging
This commit refactors the Vitest configuration handling within the unit test builder to more reliably merge the CLI-generated configuration with a user's `vitest-base.config.ts` file. The new implementation uses Vitest's `mergeConfig` utility to create a layered configuration, ensuring that essential CLI settings (such as test entry points and in-memory file providers) are preserved while still allowing users to customize other options. This prevents user configurations from inadvertently overriding critical builder settings, leading to a more stable and predictable testing experience.
1 parent edc9693 commit 23ff4e9

File tree

3 files changed

+37
-53
lines changed

3 files changed

+37
-53
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ export class VitestExecutor implements TestExecutor {
226226
watch: null,
227227
},
228228
plugins: [
229-
createVitestConfigPlugin({
229+
await createVitestConfigPlugin({
230230
browser: browserOptions.browser,
231231
coverage,
232232
projectName,

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

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ async function findTestEnvironment(
5858
}
5959
}
6060

61-
export function createVitestConfigPlugin(options: VitestConfigPluginOptions): VitestPlugins[0] {
61+
export async function createVitestConfigPlugin(
62+
options: VitestConfigPluginOptions,
63+
): Promise<VitestPlugins[0]> {
6264
const {
6365
include,
6466
browser,
@@ -69,6 +71,8 @@ export function createVitestConfigPlugin(options: VitestConfigPluginOptions): Vi
6971
projectSourceRoot,
7072
} = options;
7173

74+
const { mergeConfig } = await import('vitest/config');
75+
7276
return {
7377
name: 'angular:vitest-configuration',
7478
async config(config) {
@@ -90,17 +94,6 @@ export function createVitestConfigPlugin(options: VitestConfigPluginOptions): Vi
9094
delete testConfig.include;
9195
}
9296

93-
// The user's setup files should be appended to the CLI's setup files.
94-
const combinedSetupFiles = [...setupFiles];
95-
if (testConfig?.setupFiles) {
96-
if (typeof testConfig.setupFiles === 'string') {
97-
combinedSetupFiles.push(testConfig.setupFiles);
98-
} else if (Array.isArray(testConfig.setupFiles)) {
99-
combinedSetupFiles.push(...testConfig.setupFiles);
100-
}
101-
delete testConfig.setupFiles;
102-
}
103-
10497
// Merge user-defined plugins from the Vitest config with the CLI's internal plugins.
10598
if (config.plugins) {
10699
const userPlugins = config.plugins.filter(
@@ -115,38 +108,49 @@ export function createVitestConfigPlugin(options: VitestConfigPluginOptions): Vi
115108
if (userPlugins.length > 0) {
116109
projectPlugins.push(...userPlugins);
117110
}
111+
delete config.plugins;
118112
}
119113

120114
const projectResolver = createRequire(projectSourceRoot + '/').resolve;
121115

122-
const projectConfig: UserWorkspaceConfig = {
116+
const projectDefaults: UserWorkspaceConfig = {
117+
test: {
118+
setupFiles,
119+
globals: true,
120+
// Default to `false` to align with the Karma/Jasmine experience.
121+
isolate: false,
122+
},
123+
optimizeDeps: {
124+
noDiscovery: true,
125+
include: options.optimizeDepsInclude,
126+
},
127+
};
128+
129+
const { optimizeDeps, resolve } = config;
130+
const projectOverrides: UserWorkspaceConfig = {
123131
test: {
124-
...testConfig,
125132
name: projectName,
126-
setupFiles: combinedSetupFiles,
127133
include,
128-
globals: testConfig?.globals ?? true,
129-
// Default to `false` to align with the Karma/Jasmine experience.
130-
isolate: testConfig?.isolate ?? false,
134+
// CLI provider browser options override, if present
131135
...(browser ? { browser } : {}),
132136
// If the user has not specified an environment, use a smart default.
133137
...(!testConfig?.environment
134138
? { environment: await findTestEnvironment(projectResolver) }
135139
: {}),
136140
},
137-
optimizeDeps: {
138-
noDiscovery: true,
139-
include: options.optimizeDepsInclude,
140-
},
141141
plugins: projectPlugins,
142+
optimizeDeps,
143+
resolve,
142144
};
143145

146+
const projectBase = mergeConfig(projectDefaults, testConfig ? { test: testConfig } : {});
147+
const projectConfig = mergeConfig(projectBase, projectOverrides);
148+
144149
return {
145150
test: {
146151
coverage: await generateCoverageOption(options.coverage, projectName),
147152
// eslint-disable-next-line @typescript-eslint/no-explicit-any
148153
...(reporters ? ({ reporters } as any) : {}),
149-
...(browser ? { browser } : {}),
150154
projects: [projectConfig],
151155
},
152156
};

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

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

1818
describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
19-
xdescribe('Option: "browsers"', () => {
19+
describe('Option: "browsers"', () => {
2020
beforeEach(async () => {
2121
setupApplicationTarget(harness);
2222
});
2323

24-
it('should use jsdom when browsers is not provided', async () => {
24+
it('should use DOM emulation when browsers is not provided', async () => {
2525
harness.useTarget('test', {
2626
...BASE_OPTIONS,
2727
browsers: undefined,
2828
});
2929

30-
const { result, logs } = await harness.executeOnce();
30+
const { result } = await harness.executeOnce();
3131
expect(result?.success).toBeTrue();
32-
expectLog(logs, 'Using jsdom in Node.js for test execution.');
3332
});
3433

35-
it('should fail when browsers is empty', async () => {
36-
harness.useTarget('test', {
37-
...BASE_OPTIONS,
38-
browsers: [],
39-
});
40-
41-
await expectAsync(harness.executeOnce()).toBeRejectedWithError(
42-
/must NOT have fewer than 1 items/,
43-
);
44-
});
45-
46-
it('should launch a browser when provided', async () => {
34+
it('should fail when a browser is requested but no provider is installed', async () => {
4735
harness.useTarget('test', {
4836
...BASE_OPTIONS,
4937
browsers: ['chrome'],
5038
});
5139

5240
const { result, logs } = await harness.executeOnce();
53-
expect(result?.success).toBeTrue();
54-
expectLog(logs, /Starting browser "chrome"/);
55-
});
56-
57-
it('should launch a browser in headless mode when specified', async () => {
58-
harness.useTarget('test', {
59-
...BASE_OPTIONS,
60-
browsers: ['chromeheadless'],
61-
});
62-
63-
const { result, logs } = await harness.executeOnce();
64-
expect(result?.success).toBeTrue();
65-
expectLog(logs, /Starting browser "chrome" in headless mode/);
41+
expect(result?.success).toBeFalse();
42+
expectLog(
43+
logs,
44+
`The "browsers" option requires either "@vitest/browser-playwright", "@vitest/browser-webdriverio", or "@vitest/browser-preview" to be installed`,
45+
);
6646
});
6747
});
6848
});

0 commit comments

Comments
 (0)