Skip to content

Commit 2ffdae4

Browse files
committed
fix(@schematics/angular): support testRunner option in library schematic
This commit introduces the `testRunner` option to the library schematic, allowing users to explicitly select between `vitest` and `karma` as their test runner. Additionally, the `ng-new` schematic has been updated to configure the default `testRunner` for libraries in `angular.json` based on the workspace creation options. Closes #31887 (cherry picked from commit a525c93)
1 parent 6c2c10b commit 2ffdae4

File tree

7 files changed

+112
-94
lines changed

7 files changed

+112
-94
lines changed

packages/schematics/angular/application/index.ts

Lines changed: 11 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
url,
2424
} from '@angular-devkit/schematics';
2525
import { Schema as ComponentOptions, Style as ComponentStyle } from '../component/schema';
26+
import { getTestRunnerDependencies } from '../utility/dependencies';
2627
import {
2728
DependencyType,
2829
ExistingBehavior,
@@ -187,62 +188,7 @@ function addDependenciesToPackageJson(options: ApplicationOptions): Rule {
187188
}
188189

189190
if (!options.skipTests) {
190-
if (options.testRunner === 'vitest') {
191-
rules.push(
192-
addDependency('vitest', latestVersions['vitest'], {
193-
type: DependencyType.Dev,
194-
existing: ExistingBehavior.Skip,
195-
install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
196-
}),
197-
addDependency('jsdom', latestVersions['jsdom'], {
198-
type: DependencyType.Dev,
199-
existing: ExistingBehavior.Skip,
200-
install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
201-
}),
202-
);
203-
} else {
204-
rules.push(
205-
addDependency('karma', latestVersions['karma'], {
206-
type: DependencyType.Dev,
207-
existing: ExistingBehavior.Skip,
208-
install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
209-
}),
210-
addDependency('karma-chrome-launcher', latestVersions['karma-chrome-launcher'], {
211-
type: DependencyType.Dev,
212-
existing: ExistingBehavior.Skip,
213-
install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
214-
}),
215-
addDependency('karma-coverage', latestVersions['karma-coverage'], {
216-
type: DependencyType.Dev,
217-
existing: ExistingBehavior.Skip,
218-
install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
219-
}),
220-
addDependency('karma-jasmine', latestVersions['karma-jasmine'], {
221-
type: DependencyType.Dev,
222-
existing: ExistingBehavior.Skip,
223-
install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
224-
}),
225-
addDependency(
226-
'karma-jasmine-html-reporter',
227-
latestVersions['karma-jasmine-html-reporter'],
228-
{
229-
type: DependencyType.Dev,
230-
existing: ExistingBehavior.Skip,
231-
install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
232-
},
233-
),
234-
addDependency('jasmine-core', latestVersions['jasmine-core'], {
235-
type: DependencyType.Dev,
236-
existing: ExistingBehavior.Skip,
237-
install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
238-
}),
239-
addDependency('@types/jasmine', latestVersions['@types/jasmine'], {
240-
type: DependencyType.Dev,
241-
existing: ExistingBehavior.Skip,
242-
install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
243-
}),
244-
);
245-
}
191+
rules.push(...getTestRunnerDependencies(options.testRunner, !!options.skipInstall));
246192
}
247193

248194
return chain(rules);
@@ -392,17 +338,15 @@ function addAppToWorkspaceFile(options: ApplicationOptions, appDir: string): Rul
392338
test:
393339
options.skipTests || options.minimal
394340
? undefined
395-
: options.testRunner === 'vitest'
396-
? {
397-
builder: Builders.BuildUnitTest,
398-
options: {},
399-
}
400-
: {
401-
builder: Builders.BuildUnitTest,
402-
options: {
403-
runner: 'karma',
404-
},
405-
},
341+
: {
342+
builder: Builders.BuildUnitTest,
343+
options:
344+
options.testRunner === 'vitest'
345+
? {}
346+
: {
347+
runner: 'karma',
348+
},
349+
},
406350
},
407351
};
408352

packages/schematics/angular/library/index.ts

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
url,
2121
} from '@angular-devkit/schematics';
2222
import { join } from 'node:path/posix';
23+
import { getTestRunnerDependencies } from '../utility/dependencies';
2324
import {
2425
DependencyType,
2526
ExistingBehavior,
@@ -69,7 +70,7 @@ function addTsProjectReference(...paths: string[]) {
6970
};
7071
}
7172

72-
function addDependenciesToPackageJson(skipInstall: boolean): Rule {
73+
function addDependenciesToPackageJson({ skipInstall, testRunner }: LibraryOptions): Rule {
7374
return chain([
7475
...LIBRARY_DEV_DEPENDENCIES.map((dependency) =>
7576
addDependency(dependency.name, dependency.version, {
@@ -78,6 +79,7 @@ function addDependenciesToPackageJson(skipInstall: boolean): Rule {
7879
install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
7980
}),
8081
),
82+
...getTestRunnerDependencies(testRunner, !!skipInstall),
8183
addDependency('tslib', latestVersions['tslib'], {
8284
type: DependencyType.Default,
8385
existing: ExistingBehavior.Skip,
@@ -91,7 +93,6 @@ function addLibToWorkspaceFile(
9193
projectRoot: string,
9294
projectName: string,
9395
hasZoneDependency: boolean,
94-
hasVitest: boolean,
9596
): Rule {
9697
return updateWorkspace((workspace) => {
9798
workspace.projects.add({
@@ -113,20 +114,21 @@ function addLibToWorkspaceFile(
113114
},
114115
},
115116
},
116-
test: hasVitest
117-
? {
118-
builder: Builders.BuildUnitTest,
119-
options: {
120-
tsConfig: `${projectRoot}/tsconfig.spec.json`,
117+
test:
118+
options.testRunner === 'vitest'
119+
? {
120+
builder: Builders.BuildUnitTest,
121+
options: {
122+
tsConfig: `${projectRoot}/tsconfig.spec.json`,
123+
},
124+
}
125+
: {
126+
builder: Builders.BuildKarma,
127+
options: {
128+
tsConfig: `${projectRoot}/tsconfig.spec.json`,
129+
polyfills: hasZoneDependency ? ['zone.js', 'zone.js/testing'] : undefined,
130+
},
121131
},
122-
}
123-
: {
124-
builder: Builders.BuildKarma,
125-
options: {
126-
tsConfig: `${projectRoot}/tsconfig.spec.json`,
127-
polyfills: hasZoneDependency ? ['zone.js', 'zone.js/testing'] : undefined,
128-
},
129-
},
130132
},
131133
});
132134
});
@@ -158,7 +160,6 @@ export default function (options: LibraryOptions): Rule {
158160

159161
const distRoot = `dist/${folderName}`;
160162
const sourceDir = `${libDir}/src/lib`;
161-
const hasVitest = getDependency(host, 'vitest') !== null;
162163

163164
const templateSource = apply(url('./files'), [
164165
applyTemplates({
@@ -172,7 +173,7 @@ export default function (options: LibraryOptions): Rule {
172173
angularLatestVersion: latestVersions.Angular.replace(/~|\^/, ''),
173174
tsLibLatestVersion: latestVersions['tslib'].replace(/~|\^/, ''),
174175
folderName,
175-
testTypesPackage: hasVitest ? 'vitest/globals' : 'jasmine',
176+
testTypesPackage: options.testRunner === 'vitest' ? 'vitest/globals' : 'jasmine',
176177
}),
177178
move(libDir),
178179
]);
@@ -181,8 +182,8 @@ export default function (options: LibraryOptions): Rule {
181182

182183
return chain([
183184
mergeWith(templateSource),
184-
addLibToWorkspaceFile(options, libDir, packageName, hasZoneDependency, hasVitest),
185-
options.skipPackageJson ? noop() : addDependenciesToPackageJson(!!options.skipInstall),
185+
addLibToWorkspaceFile(options, libDir, packageName, hasZoneDependency),
186+
options.skipPackageJson ? noop() : addDependenciesToPackageJson(options),
186187
options.skipTsConfig ? noop() : updateTsConfig(packageName, './' + distRoot),
187188
options.skipTsConfig
188189
? noop()

packages/schematics/angular/library/index_spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,11 +407,11 @@ describe('Library Schematic', () => {
407407
expect(workspace.projects.foo.architect.build.builder).toBe('@angular/build:ng-packagr');
408408
});
409409

410-
it(`should add 'karma' test builder`, async () => {
410+
it(`should add 'unit-test' test builder`, async () => {
411411
const tree = await schematicRunner.runSchematic('library', defaultOptions, workspaceTree);
412412

413413
const workspace = JSON.parse(tree.readContent('/angular.json'));
414-
expect(workspace.projects.foo.architect.test.builder).toBe('@angular/build:karma');
414+
expect(workspace.projects.foo.architect.test.builder).toBe('@angular/build:unit-test');
415415
});
416416

417417
it(`should add 'unit-test' test builder`, async () => {

packages/schematics/angular/library/schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@
5353
"type": "boolean",
5454
"default": true,
5555
"x-user-analytics": "ep.ng_standalone"
56+
},
57+
"testRunner": {
58+
"description": "The unit testing runner to use.",
59+
"type": "string",
60+
"enum": ["vitest", "karma"],
61+
"default": "vitest"
5662
}
5763
},
5864
"required": ["name"]

packages/schematics/angular/ng-new/index.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,21 @@ export default function (options: NgNewOptions): Rule {
6767
mergeWith(
6868
apply(empty(), [
6969
schematic('workspace', workspaceOptions),
70-
options.createApplication ? schematic('application', applicationOptions) : noop,
71-
schematic('ai-config', {
72-
tool: options.aiConfig?.length ? options.aiConfig : undefined,
73-
}),
7470
(tree: Tree) => {
7571
if (options.testRunner === 'karma') {
7672
const file = new JSONFile(tree, 'angular.json');
77-
file.modify(['schematics', '@schematics/angular:application', 'testRunner'], 'karma');
73+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
74+
const schematics = file.get(['schematics']) ?? ({} as any);
75+
(schematics['@schematics/angular:application'] ??= {}).testRunner = 'karma';
76+
(schematics['@schematics/angular:library'] ??= {}).testRunner = 'karma';
77+
78+
file.modify(['schematics'], schematics);
7879
}
7980
},
81+
options.createApplication ? schematic('application', applicationOptions) : noop,
82+
schematic('ai-config', {
83+
tool: options.aiConfig?.length ? options.aiConfig : undefined,
84+
}),
8085
move(options.directory),
8186
]),
8287
),

packages/schematics/angular/ng-new/index_spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ describe('Ng New Schematic', () => {
183183

184184
const { schematics } = JSON.parse(tree.readContent('/bar/angular.json'));
185185
expect(schematics['@schematics/angular:application'].testRunner).toBe('karma');
186+
expect(schematics['@schematics/angular:library'].testRunner).toBe('karma');
186187
});
187188

188189
it(`should not add type to class name when file name style guide is '2016'`, async () => {

packages/schematics/angular/utility/dependencies.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import { Tree } from '@angular-devkit/schematics';
9+
import { Rule, Tree } from '@angular-devkit/schematics';
10+
import { TestRunner } from '../ng-new/schema';
11+
import { DependencyType, ExistingBehavior, InstallBehavior, addDependency } from './dependency';
1012
import { JSONFile } from './json-file';
13+
import { latestVersions } from './latest-versions';
1114

1215
const PKG_JSON_PATH = '/package.json';
1316
export enum NodeDependencyType {
@@ -78,3 +81,61 @@ export function getPackageJsonDependency(
7881

7982
return null;
8083
}
84+
85+
export function getTestRunnerDependencies(
86+
testRunner: TestRunner | undefined,
87+
skipInstall: boolean,
88+
): Rule[] {
89+
if (testRunner === TestRunner.Vitest) {
90+
return [
91+
addDependency('vitest', latestVersions['vitest'], {
92+
type: DependencyType.Dev,
93+
existing: ExistingBehavior.Skip,
94+
install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
95+
}),
96+
addDependency('jsdom', latestVersions['jsdom'], {
97+
type: DependencyType.Dev,
98+
existing: ExistingBehavior.Skip,
99+
install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
100+
}),
101+
];
102+
}
103+
104+
return [
105+
addDependency('karma', latestVersions['karma'], {
106+
type: DependencyType.Dev,
107+
existing: ExistingBehavior.Skip,
108+
install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
109+
}),
110+
addDependency('karma-chrome-launcher', latestVersions['karma-chrome-launcher'], {
111+
type: DependencyType.Dev,
112+
existing: ExistingBehavior.Skip,
113+
install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
114+
}),
115+
addDependency('karma-coverage', latestVersions['karma-coverage'], {
116+
type: DependencyType.Dev,
117+
existing: ExistingBehavior.Skip,
118+
install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
119+
}),
120+
addDependency('karma-jasmine', latestVersions['karma-jasmine'], {
121+
type: DependencyType.Dev,
122+
existing: ExistingBehavior.Skip,
123+
install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
124+
}),
125+
addDependency('karma-jasmine-html-reporter', latestVersions['karma-jasmine-html-reporter'], {
126+
type: DependencyType.Dev,
127+
existing: ExistingBehavior.Skip,
128+
install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
129+
}),
130+
addDependency('jasmine-core', latestVersions['jasmine-core'], {
131+
type: DependencyType.Dev,
132+
existing: ExistingBehavior.Skip,
133+
install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
134+
}),
135+
addDependency('@types/jasmine', latestVersions['@types/jasmine'], {
136+
type: DependencyType.Dev,
137+
existing: ExistingBehavior.Skip,
138+
install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto,
139+
}),
140+
];
141+
}

0 commit comments

Comments
 (0)