From 3f40d10ade796f311d561b1977d490af1e2e4a7a Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:18:29 +0000 Subject: [PATCH 1/3] 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 --- .../schematics/angular/application/index.ts | 78 +++---------------- packages/schematics/angular/library/index.ts | 39 +++++----- .../schematics/angular/library/index_spec.ts | 4 +- .../schematics/angular/library/schema.json | 6 ++ packages/schematics/angular/ng-new/index.ts | 15 ++-- .../schematics/angular/ng-new/index_spec.ts | 1 + .../angular/utility/dependencies.ts | 63 ++++++++++++++- 7 files changed, 112 insertions(+), 94 deletions(-) diff --git a/packages/schematics/angular/application/index.ts b/packages/schematics/angular/application/index.ts index 53ddd53acad0..d228c5f12cad 100644 --- a/packages/schematics/angular/application/index.ts +++ b/packages/schematics/angular/application/index.ts @@ -23,6 +23,7 @@ import { url, } from '@angular-devkit/schematics'; import { Schema as ComponentOptions, Style as ComponentStyle } from '../component/schema'; +import { getTestRunnerDependencies } from '../utility/dependencies'; import { DependencyType, ExistingBehavior, @@ -187,62 +188,7 @@ function addDependenciesToPackageJson(options: ApplicationOptions): Rule { } if (!options.skipTests) { - if (options.testRunner === 'vitest') { - rules.push( - addDependency('vitest', latestVersions['vitest'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - addDependency('jsdom', latestVersions['jsdom'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - ); - } else { - rules.push( - addDependency('karma', latestVersions['karma'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - addDependency('karma-chrome-launcher', latestVersions['karma-chrome-launcher'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - addDependency('karma-coverage', latestVersions['karma-coverage'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - addDependency('karma-jasmine', latestVersions['karma-jasmine'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - addDependency( - 'karma-jasmine-html-reporter', - latestVersions['karma-jasmine-html-reporter'], - { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }, - ), - addDependency('jasmine-core', latestVersions['jasmine-core'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - addDependency('@types/jasmine', latestVersions['@types/jasmine'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: options.skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - ); - } + rules.push(...getTestRunnerDependencies(options.testRunner, !!options.skipInstall)); } return chain(rules); @@ -392,17 +338,15 @@ function addAppToWorkspaceFile(options: ApplicationOptions, appDir: string): Rul test: options.skipTests || options.minimal ? undefined - : options.testRunner === 'vitest' - ? { - builder: Builders.BuildUnitTest, - options: {}, - } - : { - builder: Builders.BuildUnitTest, - options: { - runner: 'karma', - }, - }, + : { + builder: Builders.BuildUnitTest, + options: + options.testRunner === 'vitest' + ? {} + : { + runner: 'karma', + }, + }, }, }; diff --git a/packages/schematics/angular/library/index.ts b/packages/schematics/angular/library/index.ts index b02e35b27758..aa01d36f798e 100644 --- a/packages/schematics/angular/library/index.ts +++ b/packages/schematics/angular/library/index.ts @@ -20,6 +20,7 @@ import { url, } from '@angular-devkit/schematics'; import { join } from 'node:path/posix'; +import { getTestRunnerDependencies } from '../utility/dependencies'; import { DependencyType, ExistingBehavior, @@ -69,7 +70,7 @@ function addTsProjectReference(...paths: string[]) { }; } -function addDependenciesToPackageJson(skipInstall: boolean): Rule { +function addDependenciesToPackageJson({ skipInstall, testRunner }: LibraryOptions): Rule { return chain([ ...LIBRARY_DEV_DEPENDENCIES.map((dependency) => addDependency(dependency.name, dependency.version, { @@ -78,6 +79,7 @@ function addDependenciesToPackageJson(skipInstall: boolean): Rule { install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, }), ), + ...getTestRunnerDependencies(testRunner, !!skipInstall), addDependency('tslib', latestVersions['tslib'], { type: DependencyType.Default, existing: ExistingBehavior.Skip, @@ -91,7 +93,6 @@ function addLibToWorkspaceFile( projectRoot: string, projectName: string, hasZoneDependency: boolean, - hasVitest: boolean, ): Rule { return updateWorkspace((workspace) => { workspace.projects.add({ @@ -113,20 +114,21 @@ function addLibToWorkspaceFile( }, }, }, - test: hasVitest - ? { - builder: Builders.BuildUnitTest, - options: { - tsConfig: `${projectRoot}/tsconfig.spec.json`, + test: + options.testRunner === 'vitest' + ? { + builder: Builders.BuildUnitTest, + options: { + tsConfig: `${projectRoot}/tsconfig.spec.json`, + }, + } + : { + builder: Builders.BuildKarma, + options: { + tsConfig: `${projectRoot}/tsconfig.spec.json`, + polyfills: hasZoneDependency ? ['zone.js', 'zone.js/testing'] : undefined, + }, }, - } - : { - builder: Builders.BuildKarma, - options: { - tsConfig: `${projectRoot}/tsconfig.spec.json`, - polyfills: hasZoneDependency ? ['zone.js', 'zone.js/testing'] : undefined, - }, - }, }, }); }); @@ -158,7 +160,6 @@ export default function (options: LibraryOptions): Rule { const distRoot = `dist/${folderName}`; const sourceDir = `${libDir}/src/lib`; - const hasVitest = getDependency(host, 'vitest') !== null; const templateSource = apply(url('./files'), [ applyTemplates({ @@ -172,7 +173,7 @@ export default function (options: LibraryOptions): Rule { angularLatestVersion: latestVersions.Angular.replace(/~|\^/, ''), tsLibLatestVersion: latestVersions['tslib'].replace(/~|\^/, ''), folderName, - testTypesPackage: hasVitest ? 'vitest/globals' : 'jasmine', + testTypesPackage: options.testRunner === 'vitest' ? 'vitest/globals' : 'jasmine', }), move(libDir), ]); @@ -181,8 +182,8 @@ export default function (options: LibraryOptions): Rule { return chain([ mergeWith(templateSource), - addLibToWorkspaceFile(options, libDir, packageName, hasZoneDependency, hasVitest), - options.skipPackageJson ? noop() : addDependenciesToPackageJson(!!options.skipInstall), + addLibToWorkspaceFile(options, libDir, packageName, hasZoneDependency), + options.skipPackageJson ? noop() : addDependenciesToPackageJson(options), options.skipTsConfig ? noop() : updateTsConfig(packageName, './' + distRoot), options.skipTsConfig ? noop() diff --git a/packages/schematics/angular/library/index_spec.ts b/packages/schematics/angular/library/index_spec.ts index 319abbaa5162..bf4f8714294e 100644 --- a/packages/schematics/angular/library/index_spec.ts +++ b/packages/schematics/angular/library/index_spec.ts @@ -407,11 +407,11 @@ describe('Library Schematic', () => { expect(workspace.projects.foo.architect.build.builder).toBe('@angular/build:ng-packagr'); }); - it(`should add 'karma' test builder`, async () => { + it(`should add 'unit-test' test builder`, async () => { const tree = await schematicRunner.runSchematic('library', defaultOptions, workspaceTree); const workspace = JSON.parse(tree.readContent('/angular.json')); - expect(workspace.projects.foo.architect.test.builder).toBe('@angular/build:karma'); + expect(workspace.projects.foo.architect.test.builder).toBe('@angular/build:unit-test'); }); it(`should add 'unit-test' test builder`, async () => { diff --git a/packages/schematics/angular/library/schema.json b/packages/schematics/angular/library/schema.json index 62ffdbb422a0..bb3d227e5245 100644 --- a/packages/schematics/angular/library/schema.json +++ b/packages/schematics/angular/library/schema.json @@ -53,6 +53,12 @@ "type": "boolean", "default": true, "x-user-analytics": "ep.ng_standalone" + }, + "testRunner": { + "description": "The unit testing runner to use.", + "type": "string", + "enum": ["vitest", "karma"], + "default": "vitest" } }, "required": ["name"] diff --git a/packages/schematics/angular/ng-new/index.ts b/packages/schematics/angular/ng-new/index.ts index 7fca64d69ce3..7e4e7d98d36b 100644 --- a/packages/schematics/angular/ng-new/index.ts +++ b/packages/schematics/angular/ng-new/index.ts @@ -67,16 +67,21 @@ export default function (options: NgNewOptions): Rule { mergeWith( apply(empty(), [ schematic('workspace', workspaceOptions), - options.createApplication ? schematic('application', applicationOptions) : noop, - schematic('ai-config', { - tool: options.aiConfig?.length ? options.aiConfig : undefined, - }), (tree: Tree) => { if (options.testRunner === 'karma') { const file = new JSONFile(tree, 'angular.json'); - file.modify(['schematics', '@schematics/angular:application', 'testRunner'], 'karma'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const schematics = file.get(['schematics']) ?? ({} as any); + (schematics['@schematics/angular:application'] ??= {}).testRunner = 'karma'; + (schematics['@schematics/angular:library'] ??= {}).testRunner = 'karma'; + + file.modify(['schematics'], schematics); } }, + options.createApplication ? schematic('application', applicationOptions) : noop, + schematic('ai-config', { + tool: options.aiConfig?.length ? options.aiConfig : undefined, + }), move(options.directory), ]), ), diff --git a/packages/schematics/angular/ng-new/index_spec.ts b/packages/schematics/angular/ng-new/index_spec.ts index 7f136908c747..fa68cf746be2 100644 --- a/packages/schematics/angular/ng-new/index_spec.ts +++ b/packages/schematics/angular/ng-new/index_spec.ts @@ -183,6 +183,7 @@ describe('Ng New Schematic', () => { const { schematics } = JSON.parse(tree.readContent('/bar/angular.json')); expect(schematics['@schematics/angular:application'].testRunner).toBe('karma'); + expect(schematics['@schematics/angular:library'].testRunner).toBe('karma'); }); it(`should not add type to class name when file name style guide is '2016'`, async () => { diff --git a/packages/schematics/angular/utility/dependencies.ts b/packages/schematics/angular/utility/dependencies.ts index 06c4f38653bd..3cbffa49a6a0 100644 --- a/packages/schematics/angular/utility/dependencies.ts +++ b/packages/schematics/angular/utility/dependencies.ts @@ -6,8 +6,11 @@ * found in the LICENSE file at https://angular.dev/license */ -import { Tree } from '@angular-devkit/schematics'; +import { Rule, Tree } from '@angular-devkit/schematics'; +import { TestRunner } from '../ng-new/schema'; +import { DependencyType, ExistingBehavior, InstallBehavior, addDependency } from './dependency'; import { JSONFile } from './json-file'; +import { latestVersions } from './latest-versions'; const PKG_JSON_PATH = '/package.json'; export enum NodeDependencyType { @@ -78,3 +81,61 @@ export function getPackageJsonDependency( return null; } + +export function getTestRunnerDependencies( + testRunner: TestRunner | undefined, + skipInstall: boolean, +): Rule[] { + if (testRunner === TestRunner.Vitest) { + return [ + addDependency('vitest', latestVersions['vitest'], { + type: DependencyType.Dev, + existing: ExistingBehavior.Skip, + install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, + }), + addDependency('jsdom', latestVersions['jsdom'], { + type: DependencyType.Dev, + existing: ExistingBehavior.Skip, + install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, + }), + ]; + } + + return [ + addDependency('karma', latestVersions['karma'], { + type: DependencyType.Dev, + existing: ExistingBehavior.Skip, + install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, + }), + addDependency('karma-chrome-launcher', latestVersions['karma-chrome-launcher'], { + type: DependencyType.Dev, + existing: ExistingBehavior.Skip, + install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, + }), + addDependency('karma-coverage', latestVersions['karma-coverage'], { + type: DependencyType.Dev, + existing: ExistingBehavior.Skip, + install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, + }), + addDependency('karma-jasmine', latestVersions['karma-jasmine'], { + type: DependencyType.Dev, + existing: ExistingBehavior.Skip, + install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, + }), + addDependency('karma-jasmine-html-reporter', latestVersions['karma-jasmine-html-reporter'], { + type: DependencyType.Dev, + existing: ExistingBehavior.Skip, + install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, + }), + addDependency('jasmine-core', latestVersions['jasmine-core'], { + type: DependencyType.Dev, + existing: ExistingBehavior.Skip, + install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, + }), + addDependency('@types/jasmine', latestVersions['@types/jasmine'], { + type: DependencyType.Dev, + existing: ExistingBehavior.Skip, + install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, + }), + ]; +} From 18f78949fd5f9abf9deb2dd279d6bf856591b8ea Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:32:00 +0000 Subject: [PATCH 2/3] refactor: replace string literals with TestRunner enum for test runner options. Replace string with enum --- packages/angular/build/src/builders/unit-test/options.ts | 6 +++--- .../angular/build/src/builders/unit-test/tests/setup.ts | 4 ++-- packages/schematics/angular/application/index.ts | 4 ++-- packages/schematics/angular/application/index_spec.ts | 4 ++-- packages/schematics/angular/library/index.ts | 6 +++--- packages/schematics/angular/ng-new/index.ts | 8 ++++---- packages/schematics/angular/ng-new/index_spec.ts | 6 +++--- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/angular/build/src/builders/unit-test/options.ts b/packages/angular/build/src/builders/unit-test/options.ts index ac3d1b4e9652..8fa5b14eda59 100644 --- a/packages/angular/build/src/builders/unit-test/options.ts +++ b/packages/angular/build/src/builders/unit-test/options.ts @@ -12,7 +12,7 @@ import path from 'node:path'; import { normalizeCacheOptions } from '../../utils/normalize-cache'; import { getProjectRootPaths } from '../../utils/project-metadata'; import { isTTY } from '../../utils/tty'; -import type { Schema as UnitTestBuilderOptions } from './schema'; +import { Runner, type Schema as UnitTestBuilderOptions } from './schema'; export type NormalizedUnitTestBuilderOptions = Awaited>; @@ -56,7 +56,7 @@ export async function normalizeOptions( const { runner, browsers, progress, filter, browserViewport, ui, runnerConfig } = options; - if (ui && runner !== 'vitest') { + if (ui && runner !== Runner.Vitest) { throw new Error('The "ui" option is only available for the "vitest" runner.'); } @@ -95,7 +95,7 @@ export async function normalizeOptions( include: options.include ?? ['**/*.spec.ts'], exclude: options.exclude, filter, - runnerName: runner ?? 'vitest', + runnerName: runner ?? Runner.Vitest, coverage: { enabled: options.coverage, exclude: options.coverageExclude, diff --git a/packages/angular/build/src/builders/unit-test/tests/setup.ts b/packages/angular/build/src/builders/unit-test/tests/setup.ts index db03c2b0b348..e6770e115789 100644 --- a/packages/angular/build/src/builders/unit-test/tests/setup.ts +++ b/packages/angular/build/src/builders/unit-test/tests/setup.ts @@ -14,7 +14,7 @@ import { ApplicationBuilderOptions as ApplicationSchema, buildApplication, } from '../../../builders/application'; -import { Schema } from '../schema'; +import { Runner, Schema } from '../schema'; // TODO: Consider using package.json imports field instead of relative path // after the switch to rules_js. @@ -61,7 +61,7 @@ export const UNIT_TEST_BUILDER_INFO = Object.freeze({ export const BASE_OPTIONS = Object.freeze({ buildTarget: 'test:build', tsConfig: 'src/tsconfig.spec.json', - runner: 'vitest' as any, + runner: Runner.Vitest, }); /** diff --git a/packages/schematics/angular/application/index.ts b/packages/schematics/angular/application/index.ts index d228c5f12cad..dc2cd9217fce 100644 --- a/packages/schematics/angular/application/index.ts +++ b/packages/schematics/angular/application/index.ts @@ -35,7 +35,7 @@ import { latestVersions } from '../utility/latest-versions'; import { relativePathToWorkspaceRoot } from '../utility/paths'; import { getWorkspace, updateWorkspace } from '../utility/workspace'; import { Builders, ProjectType } from '../utility/workspace-models'; -import { Schema as ApplicationOptions, Style } from './schema'; +import { Schema as ApplicationOptions, Style, TestRunner } from './schema'; const APPLICATION_DEV_DEPENDENCIES = [ { name: '@angular/compiler-cli', version: latestVersions.Angular }, @@ -341,7 +341,7 @@ function addAppToWorkspaceFile(options: ApplicationOptions, appDir: string): Rul : { builder: Builders.BuildUnitTest, options: - options.testRunner === 'vitest' + options.testRunner === TestRunner.Vitest ? {} : { runner: 'karma', diff --git a/packages/schematics/angular/application/index_spec.ts b/packages/schematics/angular/application/index_spec.ts index 4d95be8b5ea1..c2f91d110f27 100644 --- a/packages/schematics/angular/application/index_spec.ts +++ b/packages/schematics/angular/application/index_spec.ts @@ -10,7 +10,7 @@ import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/te import { parse as parseJson } from 'jsonc-parser'; import { latestVersions } from '../utility/latest-versions'; import { Schema as WorkspaceOptions } from '../workspace/schema'; -import { Schema as ApplicationOptions, Style, ViewEncapsulation } from './schema'; +import { Schema as ApplicationOptions, Style, TestRunner, ViewEncapsulation } from './schema'; // eslint-disable-next-line @typescript-eslint/no-explicit-any function readJsonFile(tree: UnitTestTree, path: string): any { @@ -442,7 +442,7 @@ describe('Application Schematic', () => { }); it('should set values in angular.json correctly when testRunner is karma', async () => { - const options = { ...defaultOptions, projectRoot: '', testRunner: 'karma' as const }; + const options = { ...defaultOptions, projectRoot: '', testRunner: TestRunner.Karma }; const tree = await schematicRunner.runSchematic('application', options, workspaceTree); const config = JSON.parse(tree.readContent('/angular.json')); diff --git a/packages/schematics/angular/library/index.ts b/packages/schematics/angular/library/index.ts index aa01d36f798e..0dd51ca448df 100644 --- a/packages/schematics/angular/library/index.ts +++ b/packages/schematics/angular/library/index.ts @@ -33,7 +33,7 @@ import { latestVersions } from '../utility/latest-versions'; import { relativePathToWorkspaceRoot } from '../utility/paths'; import { getWorkspace, updateWorkspace } from '../utility/workspace'; import { Builders, ProjectType } from '../utility/workspace-models'; -import { Schema as LibraryOptions } from './schema'; +import { Schema as LibraryOptions, TestRunner } from './schema'; const LIBRARY_DEV_DEPENDENCIES = [ { name: '@angular/compiler-cli', version: latestVersions.Angular }, @@ -115,7 +115,7 @@ function addLibToWorkspaceFile( }, }, test: - options.testRunner === 'vitest' + options.testRunner === TestRunner.Vitest ? { builder: Builders.BuildUnitTest, options: { @@ -173,7 +173,7 @@ export default function (options: LibraryOptions): Rule { angularLatestVersion: latestVersions.Angular.replace(/~|\^/, ''), tsLibLatestVersion: latestVersions['tslib'].replace(/~|\^/, ''), folderName, - testTypesPackage: options.testRunner === 'vitest' ? 'vitest/globals' : 'jasmine', + testTypesPackage: options.testRunner === TestRunner.Vitest ? 'vitest/globals' : 'jasmine', }), move(libDir), ]); diff --git a/packages/schematics/angular/ng-new/index.ts b/packages/schematics/angular/ng-new/index.ts index 7e4e7d98d36b..856343e82b8f 100644 --- a/packages/schematics/angular/ng-new/index.ts +++ b/packages/schematics/angular/ng-new/index.ts @@ -25,7 +25,7 @@ import { import { Schema as ApplicationOptions } from '../application/schema'; import { JSONFile } from '../utility/json-file'; import { Schema as WorkspaceOptions } from '../workspace/schema'; -import { Schema as NgNewOptions } from './schema'; +import { Schema as NgNewOptions, TestRunner } from './schema'; export default function (options: NgNewOptions): Rule { if (!options.directory) { @@ -68,12 +68,12 @@ export default function (options: NgNewOptions): Rule { apply(empty(), [ schematic('workspace', workspaceOptions), (tree: Tree) => { - if (options.testRunner === 'karma') { + if (options.testRunner === TestRunner.Karma) { const file = new JSONFile(tree, 'angular.json'); // eslint-disable-next-line @typescript-eslint/no-explicit-any const schematics = file.get(['schematics']) ?? ({} as any); - (schematics['@schematics/angular:application'] ??= {}).testRunner = 'karma'; - (schematics['@schematics/angular:library'] ??= {}).testRunner = 'karma'; + (schematics['@schematics/angular:application'] ??= {}).testRunner = TestRunner.Karma; + (schematics['@schematics/angular:library'] ??= {}).testRunner = TestRunner.Karma; file.modify(['schematics'], schematics); } diff --git a/packages/schematics/angular/ng-new/index_spec.ts b/packages/schematics/angular/ng-new/index_spec.ts index fa68cf746be2..28e1c13f315b 100644 --- a/packages/schematics/angular/ng-new/index_spec.ts +++ b/packages/schematics/angular/ng-new/index_spec.ts @@ -7,7 +7,7 @@ */ import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; -import { Schema as NgNewOptions } from './schema'; +import { Schema as NgNewOptions, TestRunner } from './schema'; describe('Ng New Schematic', () => { const schematicRunner = new SchematicTestRunner( @@ -159,7 +159,7 @@ describe('Ng New Schematic', () => { }); it(`should set 'testRunner' to 'karma'`, async () => { - const options = { ...defaultOptions, testRunner: 'karma' as const }; + const options = { ...defaultOptions, testRunner: TestRunner.Karma }; const tree = await schematicRunner.runSchematic('ng-new', options); const { @@ -178,7 +178,7 @@ describe('Ng New Schematic', () => { }); it(`should set 'testRunner' to 'karma' in workspace schematic options`, async () => { - const options = { ...defaultOptions, testRunner: 'karma' as const }; + const options = { ...defaultOptions, testRunner: TestRunner.Karma }; const tree = await schematicRunner.runSchematic('ng-new', options); const { schematics } = JSON.parse(tree.readContent('/bar/angular.json')); From 5c5019cc935f0a5a203eaecf5e3bc6d6202b6b63 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:38:25 +0000 Subject: [PATCH 3/3] refactor: streamline test runner dependency management using array mapping This commit simplifies the `getTestRunnerDependencies` implementation and renames it to `addTestRunnerDependencies`. --- .../schematics/angular/application/index.ts | 4 +- packages/schematics/angular/library/index.ts | 4 +- .../angular/utility/dependencies.ts | 64 +++++-------------- 3 files changed, 20 insertions(+), 52 deletions(-) diff --git a/packages/schematics/angular/application/index.ts b/packages/schematics/angular/application/index.ts index dc2cd9217fce..e84a40530032 100644 --- a/packages/schematics/angular/application/index.ts +++ b/packages/schematics/angular/application/index.ts @@ -23,7 +23,7 @@ import { url, } from '@angular-devkit/schematics'; import { Schema as ComponentOptions, Style as ComponentStyle } from '../component/schema'; -import { getTestRunnerDependencies } from '../utility/dependencies'; +import { addTestRunnerDependencies } from '../utility/dependencies'; import { DependencyType, ExistingBehavior, @@ -188,7 +188,7 @@ function addDependenciesToPackageJson(options: ApplicationOptions): Rule { } if (!options.skipTests) { - rules.push(...getTestRunnerDependencies(options.testRunner, !!options.skipInstall)); + rules.push(...addTestRunnerDependencies(options.testRunner, !!options.skipInstall)); } return chain(rules); diff --git a/packages/schematics/angular/library/index.ts b/packages/schematics/angular/library/index.ts index 0dd51ca448df..3069664c02d7 100644 --- a/packages/schematics/angular/library/index.ts +++ b/packages/schematics/angular/library/index.ts @@ -20,7 +20,7 @@ import { url, } from '@angular-devkit/schematics'; import { join } from 'node:path/posix'; -import { getTestRunnerDependencies } from '../utility/dependencies'; +import { addTestRunnerDependencies } from '../utility/dependencies'; import { DependencyType, ExistingBehavior, @@ -79,7 +79,7 @@ function addDependenciesToPackageJson({ skipInstall, testRunner }: LibraryOption install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, }), ), - ...getTestRunnerDependencies(testRunner, !!skipInstall), + ...addTestRunnerDependencies(testRunner, !!skipInstall), addDependency('tslib', latestVersions['tslib'], { type: DependencyType.Default, existing: ExistingBehavior.Skip, diff --git a/packages/schematics/angular/utility/dependencies.ts b/packages/schematics/angular/utility/dependencies.ts index 3cbffa49a6a0..deb78ca9c0e2 100644 --- a/packages/schematics/angular/utility/dependencies.ts +++ b/packages/schematics/angular/utility/dependencies.ts @@ -82,60 +82,28 @@ export function getPackageJsonDependency( return null; } -export function getTestRunnerDependencies( +export function addTestRunnerDependencies( testRunner: TestRunner | undefined, skipInstall: boolean, ): Rule[] { - if (testRunner === TestRunner.Vitest) { - return [ - addDependency('vitest', latestVersions['vitest'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - addDependency('jsdom', latestVersions['jsdom'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - ]; - } + const dependencies = + testRunner === TestRunner.Vitest + ? ['vitest', 'jsdom'] + : [ + 'karma', + 'karma-chrome-launcher', + 'karma-coverage', + 'karma-jasmine', + 'karma-jasmine-html-reporter', + 'jasmine-core', + '@types/jasmine', + ]; - return [ - addDependency('karma', latestVersions['karma'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - addDependency('karma-chrome-launcher', latestVersions['karma-chrome-launcher'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - addDependency('karma-coverage', latestVersions['karma-coverage'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - addDependency('karma-jasmine', latestVersions['karma-jasmine'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - addDependency('karma-jasmine-html-reporter', latestVersions['karma-jasmine-html-reporter'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - addDependency('jasmine-core', latestVersions['jasmine-core'], { - type: DependencyType.Dev, - existing: ExistingBehavior.Skip, - install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, - }), - addDependency('@types/jasmine', latestVersions['@types/jasmine'], { + return dependencies.map((name) => + addDependency(name, latestVersions[name], { type: DependencyType.Dev, existing: ExistingBehavior.Skip, install: skipInstall ? InstallBehavior.None : InstallBehavior.Auto, }), - ]; + ); }