From 2ae38c8cf97d0e2050cb3c6578369317f5ae3dff Mon Sep 17 00:00:00 2001 From: MeAkib Date: Tue, 20 Oct 2026 00:36:43 +0600 Subject: [PATCH 1/3] fix(@schematics/angular): keep base test build options in per-configuration Karma migration The Karma to Vitest migration moves build-only test options such as `styles` into a `testing` build configuration and points the test target at it. For each test configuration it created a separate `testing-` build configuration containing only that configuration's own options. Because a test configuration's `buildTarget` replaces the base one, running `ng test --configuration ` lost the options moved from the base test options. Merge the base options into each per-configuration build configuration, with the configuration's own values taking precedence. --- .../migrate-karma-to-vitest/migration.ts | 8 ++++++- .../migrate-karma-to-vitest/migration_spec.ts | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration.ts b/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration.ts index fc5b11fb5eb0..ee4eed193ce7 100644 --- a/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration.ts +++ b/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration.ts @@ -306,9 +306,15 @@ function updateProjects(tree: Tree, context: SchematicContext): Rule { for (const [configKey, configOptions] of Object.entries(customBuildOptions)) { const finalConfig: Record = {}; + // A test configuration inherits the base test options, so its build configuration must + // include the build options moved from them, with the configuration's own values on top. + const mergedOptions = configKey + ? { ...customBuildOptions[''], ...configOptions } + : configOptions; + // Omit options that already have the same value in the base build options. // Using isDeepStrictEqual for a deep comparison of arrays and objects. - for (const [key, value] of Object.entries(configOptions)) { + for (const [key, value] of Object.entries(mergedOptions)) { if (!isDeepStrictEqual(value, baseOptions[key])) { finalConfig[key] = value; } diff --git a/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration_spec.ts b/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration_spec.ts index fb6737b77d26..ed2efd243c4f 100644 --- a/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration_spec.ts +++ b/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration_spec.ts @@ -128,6 +128,28 @@ describe('Migration from Karma to Vitest unit-test builder', () => { expect(newProjects.app.targets.test.configurations.ci.assets).toBeUndefined(); }); + it('should keep base custom build options in per configuration testing configurations', async () => { + const { projects } = tree.readJson('/angular.json') as any; + projects.app.targets.test.options.styles = ['src/test-styles.scss']; + projects.app.targets.test.configurations = { + ci: { + watch: false, + }, + }; + tree.overwrite('/angular.json', JSON.stringify({ version: 1, projects })); + + const newTree = await schematicRunner.runSchematic('migrate-karma-to-vitest', {}, tree); + const { projects: newProjects } = newTree.readJson('/angular.json') as any; + + expect(newProjects.app.targets.build.configurations.testing.styles).toEqual([ + 'src/test-styles.scss', + ]); + expect(newProjects.app.targets.test.configurations.ci.buildTarget).toBe(':build:testing-ci'); + expect(newProjects.app.targets.build.configurations['testing-ci'].styles).toEqual([ + 'src/test-styles.scss', + ]); + }); + it('should omit testing configuration if options match base build options', async () => { const { projects } = tree.readJson('/angular.json') as any; projects.app.targets.build.options.aot = false; From 79620cf808406607d4aeb7b05ae561796eac4df7 Mon Sep 17 00:00:00 2001 From: Shuaib Hasan Akib <48770016+MeAkib@users.noreply.github.com> Date: Wed, 21 Oct 2026 07:51:55 +0600 Subject: [PATCH 2/3] fixup! fix(@schematics/angular): keep base test build options in per-configuration Karma migration --- .../angular/migrations/migrate-karma-to-vitest/migration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration.ts b/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration.ts index ee4eed193ce7..b84b4e0a25df 100644 --- a/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration.ts +++ b/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration.ts @@ -308,7 +308,7 @@ function updateProjects(tree: Tree, context: SchematicContext): Rule { // A test configuration inherits the base test options, so its build configuration must // include the build options moved from them, with the configuration's own values on top. - const mergedOptions = configKey + const mergedOptions = configKey && customBuildOptions[''] ? { ...customBuildOptions[''], ...configOptions } : configOptions; From 5846b1857126c3b49d61cb7e2038e99247d51fb0 Mon Sep 17 00:00:00 2001 From: MeAkib Date: Wed, 21 Oct 2026 07:55:22 +0600 Subject: [PATCH 3/3] fixup! fix(@schematics/angular): keep base test build options in per-configuration Karma migration --- .../migrate-karma-to-vitest/migration.ts | 23 ++++++++----- .../migrate-karma-to-vitest/migration_spec.ts | 33 +++++++++++++++++++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration.ts b/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration.ts index b84b4e0a25df..5a10f4a6f8a3 100644 --- a/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration.ts +++ b/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration.ts @@ -34,12 +34,16 @@ async function processTestTargetOptions( const configKey = configName || ''; if (!customBuildOptions[configKey]) { - // Match Karma behavior where AOT was disabled by default - customBuildOptions[configKey] = { - aot: false, - optimization: false, - extractLicenses: false, - }; + // Match Karma behavior where AOT was disabled by default. Configurations inherit these + // from the base options when present, so seeding them would override explicit base values. + customBuildOptions[configKey] = + configKey && testTarget.options + ? {} + : { + aot: false, + optimization: false, + extractLicenses: false, + }; } // Collect custom build options @@ -308,9 +312,10 @@ function updateProjects(tree: Tree, context: SchematicContext): Rule { // A test configuration inherits the base test options, so its build configuration must // include the build options moved from them, with the configuration's own values on top. - const mergedOptions = configKey && customBuildOptions[''] - ? { ...customBuildOptions[''], ...configOptions } - : configOptions; + const mergedOptions = + configKey && customBuildOptions[''] + ? { ...customBuildOptions[''], ...configOptions } + : configOptions; // Omit options that already have the same value in the base build options. // Using isDeepStrictEqual for a deep comparison of arrays and objects. diff --git a/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration_spec.ts b/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration_spec.ts index ed2efd243c4f..1989ec8777c7 100644 --- a/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration_spec.ts +++ b/packages/schematics/angular/migrations/migrate-karma-to-vitest/migration_spec.ts @@ -150,6 +150,39 @@ describe('Migration from Karma to Vitest unit-test builder', () => { ]); }); + it('should keep explicit base Karma build defaults in per configuration testing configurations', async () => { + const { projects } = tree.readJson('/angular.json') as any; + projects.app.targets.test.options.styles = ['src/test-styles.scss']; + projects.app.targets.test.options.aot = true; + projects.app.targets.test.configurations = { + ci: { + watch: false, + }, + }; + tree.overwrite('/angular.json', JSON.stringify({ version: 1, projects })); + + const newTree = await schematicRunner.runSchematic('migrate-karma-to-vitest', {}, tree); + const { projects: newProjects } = newTree.readJson('/angular.json') as any; + + expect(newProjects.app.targets.build.configurations.testing.aot).toBeTrue(); + expect(newProjects.app.targets.build.configurations['testing-ci'].aot).toBeTrue(); + }); + + it('should apply Karma build defaults to testing configurations without base test options', async () => { + const { projects } = tree.readJson('/angular.json') as any; + projects.app.targets.test.configurations = { + ci: projects.app.targets.test.options, + }; + delete projects.app.targets.test.options; + tree.overwrite('/angular.json', JSON.stringify({ version: 1, projects })); + + const newTree = await schematicRunner.runSchematic('migrate-karma-to-vitest', {}, tree); + const { projects: newProjects } = newTree.readJson('/angular.json') as any; + + expect(newProjects.app.targets.test.configurations.ci.buildTarget).toBe(':build:testing-ci'); + expect(newProjects.app.targets.build.configurations['testing-ci'].aot).toBeFalse(); + }); + it('should omit testing configuration if options match base build options', async () => { const { projects } = tree.readJson('/angular.json') as any; projects.app.targets.build.options.aot = false;