Skip to content

Commit 515b09c

Browse files
clydinalan-agius4
authored andcommitted
fix(@schematics/angular): add Vitest config generation and runner checks
This commit expands the `config` schematic to support generating a `vitest-base.config.ts` file and includes runner checks to prevent misconfiguration. Changes include: - Added 'vitest' as a valid type for the `config` schematic in `schema.json`. - Created a new `vitest-base.config.ts.template` file for generating a basic Vitest configuration. - Implemented the `addVitestConfig` function in `packages/schematics/angular/config/index.ts`: - Verifies that the project's `test` target uses the `@angular/build:unit-test` builder. - Copies the `vitest-base.config.ts` template to the project root. - Sets the `runnerConfig` option to `true` in `angular.json` to enable automatic discovery. - Includes warning logic to notify users if the `runner` option in `angular.json` is explicitly set to `karma`, indicating that the generated Vitest config may not be used. (cherry picked from commit 8edd3ef)
1 parent 62938e7 commit 515b09c

File tree

4 files changed

+144
-1
lines changed

4 files changed

+144
-1
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Learn more about Vitest configuration options at https://vitest.dev/config/
2+
3+
import { defineConfig } from 'vitest/config';
4+
5+
export default defineConfig({
6+
test: {
7+
// ...
8+
},
9+
});

packages/schematics/angular/config/index.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,71 @@ export default createProjectSchematic<ConfigOptions>((options, { project }) => {
3030
return addKarmaConfig(options);
3131
case ConfigType.Browserslist:
3232
return addBrowserslistConfig(project.root);
33+
case ConfigType.Vitest:
34+
return addVitestConfig(options);
3335
default:
3436
throw new SchematicsException(`"${options.type}" is an unknown configuration file type.`);
3537
}
3638
});
3739

40+
function addVitestConfig(options: ConfigOptions): Rule {
41+
return (tree, context) =>
42+
updateWorkspace((workspace) => {
43+
const project = workspace.projects.get(options.project);
44+
if (!project) {
45+
throw new SchematicsException(`Project name "${options.project}" doesn't not exist.`);
46+
}
47+
48+
const testTarget = project.targets.get('test');
49+
if (!testTarget) {
50+
throw new SchematicsException(
51+
`No "test" target found for project "${options.project}".` +
52+
' A "test" target is required to generate a Vitest configuration.',
53+
);
54+
}
55+
56+
if (testTarget.builder !== AngularBuilder.BuildUnitTest) {
57+
throw new SchematicsException(
58+
`Cannot add a Vitest configuration as builder for "test" target in project does not` +
59+
` use "${AngularBuilder.BuildUnitTest}".`,
60+
);
61+
}
62+
63+
testTarget.options ??= {};
64+
testTarget.options.runnerConfig = true;
65+
66+
// Check runner option.
67+
if (testTarget.options.runner === 'karma') {
68+
context.logger.warn(
69+
`The "test" target is configured to use the "karma" runner in the main options.` +
70+
' The generated "vitest-base.config.ts" file may not be used.',
71+
);
72+
}
73+
74+
for (const [name, config] of Object.entries(testTarget.configurations ?? {})) {
75+
if (
76+
config &&
77+
typeof config === 'object' &&
78+
'runner' in config &&
79+
config.runner === 'karma'
80+
) {
81+
context.logger.warn(
82+
`The "test" target's "${name}" configuration is configured to use the "karma" runner.` +
83+
' The generated "vitest-base.config.ts" file may not be used for that configuration.',
84+
);
85+
}
86+
}
87+
88+
return mergeWith(
89+
apply(url('./files'), [
90+
filter((p) => p.endsWith('vitest-base.config.ts.template')),
91+
applyTemplates({}),
92+
move(project.root),
93+
]),
94+
);
95+
});
96+
}
97+
3898
async function addBrowserslistConfig(projectRoot: string): Promise<Rule> {
3999
return mergeWith(
40100
apply(url('./files'), [

packages/schematics/angular/config/index_spec.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,78 @@ describe('Config Schematic', () => {
185185
expect(tree.exists('projects/foo/.browserslistrc')).toBeTrue();
186186
});
187187
});
188+
189+
describe(`when 'type' is 'vitest'`, () => {
190+
beforeEach(() => {
191+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
192+
const angularJson = applicationTree.readJson('angular.json') as any;
193+
angularJson.projects.foo.architect.test.builder = '@angular/build:unit-test';
194+
applicationTree.overwrite('angular.json', JSON.stringify(angularJson));
195+
});
196+
197+
it('should create a vitest-base.config.ts file', async () => {
198+
const tree = await runConfigSchematic(ConfigType.Vitest);
199+
expect(tree.exists('projects/foo/vitest-base.config.ts')).toBeTrue();
200+
});
201+
202+
it(`should set 'runnerConfig' in test builder`, async () => {
203+
const tree = await runConfigSchematic(ConfigType.Vitest);
204+
const config = JSON.parse(tree.readContent('/angular.json'));
205+
const prj = config.projects.foo;
206+
const { runnerConfig } = prj.architect.test.options;
207+
expect(runnerConfig).toBe(true);
208+
});
209+
210+
it('should throw an error if the builder is not "unit-test"', async () => {
211+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
212+
const angularJson = applicationTree.readJson('angular.json') as any;
213+
angularJson.projects.foo.architect.test.builder = '@angular/build:karma';
214+
applicationTree.overwrite('angular.json', JSON.stringify(angularJson));
215+
216+
await expectAsync(runConfigSchematic(ConfigType.Vitest)).toBeRejectedWithError(
217+
/Cannot add a Vitest configuration as builder for "test" target/,
218+
);
219+
});
220+
221+
it(`should warn when 'runner' is 'karma' in options`, async () => {
222+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
223+
const angularJson = applicationTree.readJson('angular.json') as any;
224+
angularJson.projects.foo.architect.test.options ??= {};
225+
angularJson.projects.foo.architect.test.options.runner = 'karma';
226+
applicationTree.overwrite('angular.json', JSON.stringify(angularJson));
227+
228+
const logs: string[] = [];
229+
schematicRunner.logger.subscribe(({ message }) => logs.push(message));
230+
await runConfigSchematic(ConfigType.Vitest);
231+
expect(
232+
logs.some((v) =>
233+
v.includes(
234+
`The "test" target is configured to use the "karma" runner in the main options.`,
235+
),
236+
),
237+
).toBeTrue();
238+
});
239+
240+
it(`should warn when 'runner' is 'karma' in a configuration`, async () => {
241+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
242+
const angularJson = applicationTree.readJson('angular.json') as any;
243+
angularJson.projects.foo.architect.test.configurations ??= {};
244+
angularJson.projects.foo.architect.test.configurations.ci = { runner: 'karma' };
245+
applicationTree.overwrite('angular.json', JSON.stringify(angularJson));
246+
247+
const logs: string[] = [];
248+
schematicRunner.logger.subscribe(({ message }) => logs.push(message));
249+
await runConfigSchematic(ConfigType.Vitest);
250+
expect(
251+
logs.some((v) => v.includes(`"ci" configuration is configured to use the "karma" runner`)),
252+
).toBeTrue();
253+
});
254+
255+
it(`should not warn when 'runner' is not set`, async () => {
256+
const logs: string[] = [];
257+
schematicRunner.logger.subscribe(({ message }) => logs.push(message));
258+
await runConfigSchematic(ConfigType.Vitest);
259+
expect(logs.length).toBe(0);
260+
});
261+
});
188262
});

packages/schematics/angular/config/schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"type": {
1717
"type": "string",
1818
"description": "Specifies the type of configuration file to generate.",
19-
"enum": ["karma", "browserslist"],
19+
"enum": ["karma", "browserslist", "vitest"],
2020
"x-prompt": "Which type of configuration file would you like to create?",
2121
"$default": {
2222
"$source": "argv",

0 commit comments

Comments
 (0)