Skip to content

Commit cf47d15

Browse files
committed
refactor(@angular/build): refactor Vitest configuration to use project-based setup
Refactors the Vitest executor and plugin creation to streamline configuration. This moves project-specific settings into the `startVitest` call and simplifies the `createVitestPlugins` function for better modularity.
1 parent 90cd2fc commit cf47d15

File tree

2 files changed

+118
-135
lines changed

2 files changed

+118
-135
lines changed

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

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export class VitestExecutor implements TestExecutor {
190190
);
191191

192192
const testSetupFiles = this.prepareSetupFiles();
193-
const plugins = createVitestPlugins(this.options, testSetupFiles, browserOptions, {
193+
const plugins = createVitestPlugins({
194194
workspaceRoot,
195195
projectSourceRoot: this.options.projectSourceRoot,
196196
projectName: this.projectName,
@@ -209,36 +209,54 @@ export class VitestExecutor implements TestExecutor {
209209
: {};
210210

211211
const runnerConfig = this.options.runnerConfig;
212+
const externalConfigPath =
213+
runnerConfig === true
214+
? await findVitestBaseConfig([this.options.projectRoot, this.options.workspaceRoot])
215+
: runnerConfig;
216+
const projectName = this.projectName;
212217

213218
return startVitest(
214219
'test',
215220
undefined,
216221
{
217-
config:
218-
runnerConfig === true
219-
? await findVitestBaseConfig([this.options.projectRoot, this.options.workspaceRoot])
220-
: runnerConfig,
222+
config: externalConfigPath,
221223
root: workspaceRoot,
222-
project: ['base', this.projectName],
223-
name: 'base',
224-
include: [],
224+
project: projectName,
225+
outputFile,
225226
testNamePattern: this.options.filter,
226227
watch,
227228
ui,
229+
...debugOptions,
228230
},
229231
{
230232
test: {
231233
coverage: await generateCoverageOption(coverage, this.projectName),
232-
outputFile,
233-
...debugOptions,
234234
...(reporters ? { reporters } : {}),
235+
projects: [
236+
{
237+
extends: externalConfigPath || true,
238+
test: {
239+
name: projectName,
240+
globals: true,
241+
setupFiles: testSetupFiles,
242+
...(this.options.exclude ? { exclude: this.options.exclude } : {}),
243+
browser: browserOptions.browser,
244+
// Use `jsdom` if no browsers are explicitly configured.
245+
...(browserOptions.browser ? {} : { environment: 'jsdom' }),
246+
...(this.options.include ? { include: this.options.include } : {}),
247+
},
248+
optimizeDeps: {
249+
noDiscovery: true,
250+
},
251+
plugins,
252+
},
253+
],
235254
},
236255
server: {
237256
// Disable the actual file watcher. The boolean watch option above should still
238257
// be enabled as it controls other internal behavior related to rerunning tests.
239258
watch: null,
240259
},
241-
plugins,
242260
},
243261
);
244262
}

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

Lines changed: 89 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -28,143 +28,108 @@ interface PluginOptions {
2828
testFileToEntryPoint: ReadonlyMap<string, string>;
2929
}
3030

31-
export function createVitestPlugins(
32-
options: NormalizedUnitTestBuilderOptions,
33-
testSetupFiles: string[],
34-
browserOptions: BrowserConfiguration,
35-
pluginOptions: PluginOptions,
36-
): VitestPlugins {
37-
const { workspaceRoot, projectName, buildResultFiles, testFileToEntryPoint } = pluginOptions;
31+
export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins {
32+
const { workspaceRoot, buildResultFiles, testFileToEntryPoint } = pluginOptions;
3833

3934
return [
4035
{
41-
name: 'angular:project-init',
42-
// Type is incorrect. This allows a Promise<void>.
43-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
44-
configureVitest: async (context) => {
45-
// Create a subproject that can be configured with plugins for browser mode.
46-
// Plugins defined directly in the vite overrides will not be present in the
47-
// browser specific Vite instance.
48-
await context.injectTestProjects({
49-
test: {
50-
name: projectName,
51-
root: workspaceRoot,
52-
globals: true,
53-
setupFiles: testSetupFiles,
54-
include: options.include,
55-
...(options.exclude ? { exclude: options.exclude } : {}),
56-
browser: browserOptions.browser,
57-
// Use `jsdom` if no browsers are explicitly configured.
58-
...(browserOptions.browser ? {} : { environment: 'jsdom' }),
59-
},
60-
plugins: [
61-
{
62-
name: 'angular:test-in-memory-provider',
63-
enforce: 'pre',
64-
resolveId: (id, importer) => {
65-
if (importer && (id[0] === '.' || id[0] === '/')) {
66-
let fullPath;
67-
if (testFileToEntryPoint.has(importer)) {
68-
fullPath = toPosixPath(path.join(workspaceRoot, id));
69-
} else {
70-
fullPath = toPosixPath(path.join(path.dirname(importer), id));
71-
}
36+
name: 'angular:test-in-memory-provider',
37+
enforce: 'pre',
38+
resolveId: (id, importer) => {
39+
if (importer && (id[0] === '.' || id[0] === '/')) {
40+
let fullPath;
41+
if (testFileToEntryPoint.has(importer)) {
42+
fullPath = toPosixPath(path.join(workspaceRoot, id));
43+
} else {
44+
fullPath = toPosixPath(path.join(path.dirname(importer), id));
45+
}
7246

73-
const relativePath = path.relative(workspaceRoot, fullPath);
74-
if (buildResultFiles.has(toPosixPath(relativePath))) {
75-
return fullPath;
76-
}
77-
}
47+
const relativePath = path.relative(workspaceRoot, fullPath);
48+
if (buildResultFiles.has(toPosixPath(relativePath))) {
49+
return fullPath;
50+
}
51+
}
7852

79-
if (testFileToEntryPoint.has(id)) {
80-
return id;
81-
}
53+
if (testFileToEntryPoint.has(id)) {
54+
return id;
55+
}
8256

83-
assert(buildResultFiles.size > 0, 'buildResult must be available for resolving.');
84-
const relativePath = path.relative(workspaceRoot, id);
85-
if (buildResultFiles.has(toPosixPath(relativePath))) {
86-
return id;
87-
}
88-
},
89-
load: async (id) => {
90-
assert(
91-
buildResultFiles.size > 0,
92-
'buildResult must be available for in-memory loading.',
93-
);
57+
assert(buildResultFiles.size > 0, 'buildResult must be available for resolving.');
58+
const relativePath = path.relative(workspaceRoot, id);
59+
if (buildResultFiles.has(toPosixPath(relativePath))) {
60+
return id;
61+
}
62+
},
63+
load: async (id) => {
64+
assert(buildResultFiles.size > 0, 'buildResult must be available for in-memory loading.');
9465

95-
// Attempt to load as a source test file.
96-
const entryPoint = testFileToEntryPoint.get(id);
97-
let outputPath;
98-
if (entryPoint) {
99-
outputPath = entryPoint + '.js';
66+
// Attempt to load as a source test file.
67+
const entryPoint = testFileToEntryPoint.get(id);
68+
let outputPath;
69+
if (entryPoint) {
70+
outputPath = entryPoint + '.js';
10071

101-
// To support coverage exclusion of the actual test file, the virtual
102-
// test entry point only references the built and bundled intermediate file.
103-
return {
104-
code: `import "./${outputPath}";`,
105-
};
106-
} else {
107-
// Attempt to load as a built artifact.
108-
const relativePath = path.relative(workspaceRoot, id);
109-
outputPath = toPosixPath(relativePath);
110-
}
72+
// To support coverage exclusion of the actual test file, the virtual
73+
// test entry point only references the built and bundled intermediate file.
74+
return {
75+
code: `import "./${outputPath}";`,
76+
};
77+
} else {
78+
// Attempt to load as a built artifact.
79+
const relativePath = path.relative(workspaceRoot, id);
80+
outputPath = toPosixPath(relativePath);
81+
}
11182

112-
const outputFile = buildResultFiles.get(outputPath);
113-
if (outputFile) {
114-
const sourceMapPath = outputPath + '.map';
115-
const sourceMapFile = buildResultFiles.get(sourceMapPath);
116-
const code =
117-
outputFile.origin === 'memory'
118-
? Buffer.from(outputFile.contents).toString('utf-8')
119-
: await readFile(outputFile.inputPath, 'utf-8');
120-
const sourceMapText = sourceMapFile
121-
? sourceMapFile.origin === 'memory'
122-
? Buffer.from(sourceMapFile.contents).toString('utf-8')
123-
: await readFile(sourceMapFile.inputPath, 'utf-8')
124-
: undefined;
83+
const outputFile = buildResultFiles.get(outputPath);
84+
if (outputFile) {
85+
const sourceMapPath = outputPath + '.map';
86+
const sourceMapFile = buildResultFiles.get(sourceMapPath);
87+
const code =
88+
outputFile.origin === 'memory'
89+
? Buffer.from(outputFile.contents).toString('utf-8')
90+
: await readFile(outputFile.inputPath, 'utf-8');
91+
const sourceMapText = sourceMapFile
92+
? sourceMapFile.origin === 'memory'
93+
? Buffer.from(sourceMapFile.contents).toString('utf-8')
94+
: await readFile(sourceMapFile.inputPath, 'utf-8')
95+
: undefined;
12596

126-
// Vitest will include files in the coverage report if the sourcemap contains no sources.
127-
// For builder-internal generated code chunks, which are typically helper functions,
128-
// a virtual source is added to the sourcemap to prevent them from being incorrectly
129-
// included in the final coverage report.
130-
const map = sourceMapText ? JSON.parse(sourceMapText) : undefined;
131-
if (map) {
132-
if (!map.sources?.length && !map.sourcesContent?.length && !map.mappings) {
133-
map.sources = ['virtual:builder'];
134-
}
135-
}
97+
// Vitest will include files in the coverage report if the sourcemap contains no sources.
98+
// For builder-internal generated code chunks, which are typically helper functions,
99+
// a virtual source is added to the sourcemap to prevent them from being incorrectly
100+
// included in the final coverage report.
101+
const map = sourceMapText ? JSON.parse(sourceMapText) : undefined;
102+
if (map) {
103+
if (!map.sources?.length && !map.sourcesContent?.length && !map.mappings) {
104+
map.sources = ['virtual:builder'];
105+
}
106+
}
136107

137-
return {
138-
code,
139-
map,
140-
};
141-
}
142-
},
143-
configureServer: (server) => {
144-
server.middlewares.use(
145-
createBuildAssetsMiddleware(server.config.base, buildResultFiles),
146-
);
147-
},
148-
},
108+
return {
109+
code,
110+
map,
111+
};
112+
}
113+
},
114+
configureServer: (server) => {
115+
server.middlewares.use(createBuildAssetsMiddleware(server.config.base, buildResultFiles));
116+
},
117+
},
118+
{
119+
name: 'angular:html-index',
120+
transformIndexHtml: () => {
121+
// Add all global stylesheets
122+
if (buildResultFiles.has('styles.css')) {
123+
return [
149124
{
150-
name: 'angular:html-index',
151-
transformIndexHtml: () => {
152-
// Add all global stylesheets
153-
if (buildResultFiles.has('styles.css')) {
154-
return [
155-
{
156-
tag: 'link',
157-
attrs: { href: 'styles.css', rel: 'stylesheet' },
158-
injectTo: 'head',
159-
},
160-
];
161-
}
162-
163-
return [];
164-
},
125+
tag: 'link',
126+
attrs: { href: 'styles.css', rel: 'stylesheet' },
127+
injectTo: 'head',
165128
},
166-
],
167-
});
129+
];
130+
}
131+
132+
return [];
168133
},
169134
},
170135
];

0 commit comments

Comments
 (0)