From c6b13a62e3902d96d51f5a54d3bc0593ad71c116 Mon Sep 17 00:00:00 2001 From: MeAkib Date: Fri, 23 Oct 2026 06:35:45 +0600 Subject: [PATCH 1/3] fix(@angular/build): only require karma-coverage when coverage is enabled The upfront dependency validation of the unit-test builder's karma runner checks the truthiness of the normalized `coverage` option, which is always an object since the introduction of the advanced coverage options. As a result, `ng test` with the karma runner fails with a `MissingDependenciesError` requesting the `karma-coverage` package even when coverage is disabled. Check the `enabled` property instead, matching the vitest runner and the karma executor. A new unit spec for the karma runner's dependency validation fails without this change. --- .../builders/unit-test/runners/karma/index.ts | 2 +- .../unit-test/runners/karma/index_spec.ts | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 packages/angular/build/src/builders/unit-test/runners/karma/index_spec.ts diff --git a/packages/angular/build/src/builders/unit-test/runners/karma/index.ts b/packages/angular/build/src/builders/unit-test/runners/karma/index.ts index 36eadb255c47..b4720e91f350 100644 --- a/packages/angular/build/src/builders/unit-test/runners/karma/index.ts +++ b/packages/angular/build/src/builders/unit-test/runners/karma/index.ts @@ -30,7 +30,7 @@ const KarmaTestRunner: TestRunner = { } } - if (options.coverage) { + if (options.coverage.enabled) { checker.check('karma-coverage'); } diff --git a/packages/angular/build/src/builders/unit-test/runners/karma/index_spec.ts b/packages/angular/build/src/builders/unit-test/runners/karma/index_spec.ts new file mode 100644 index 000000000000..d7ab17c27279 --- /dev/null +++ b/packages/angular/build/src/builders/unit-test/runners/karma/index_spec.ts @@ -0,0 +1,56 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import assert from 'node:assert'; +import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import type { NormalizedUnitTestBuilderOptions } from '../../options'; +import KarmaTestRunner from './index'; + +describe('KarmaTestRunner', () => { + describe('validateDependencies', () => { + let projectSourceRoot: string; + + function createOptions(coverageEnabled: boolean): NormalizedUnitTestBuilderOptions { + return { + projectSourceRoot, + coverage: { enabled: coverageEnabled }, + } as NormalizedUnitTestBuilderOptions; + } + + beforeAll(async () => { + const baseTmpDir = process.env['TEST_TMPDIR']; + assert(baseTmpDir, 'TEST_TMPDIR is not set'); + projectSourceRoot = await mkdtemp(join(baseTmpDir, 'angular-cli-karma-runner-')); + + // A project with karma and karma-jasmine installed but not karma-coverage + await writeFile(join(projectSourceRoot, 'package.json'), '{"name": "test-project"}'); + for (const packageName of ['karma', 'karma-jasmine']) { + const packageRoot = join(projectSourceRoot, 'node_modules', packageName); + await mkdir(packageRoot, { recursive: true }); + await writeFile( + join(packageRoot, 'package.json'), + JSON.stringify({ name: packageName, version: '0.0.0', main: 'index.js' }), + ); + await writeFile(join(packageRoot, 'index.js'), ''); + } + }); + + it('does not require karma-coverage when coverage is disabled', async () => { + await expectAsync( + Promise.resolve().then(() => KarmaTestRunner.validateDependencies?.(createOptions(false))), + ).toBeResolved(); + }); + + it('requires karma-coverage when coverage is enabled', async () => { + await expectAsync( + Promise.resolve().then(() => KarmaTestRunner.validateDependencies?.(createOptions(true))), + ).toBeRejectedWithError(/karma-coverage/); + }); + }); +}); From 70579194392a6b03c4c08858364536e152d17491 Mon Sep 17 00:00:00 2001 From: MeAkib Date: Fri, 23 Oct 2026 06:49:20 +0600 Subject: [PATCH 2/3] fixup! fix(@angular/build): only require karma-coverage when coverage is enabled --- .../builders/unit-test/runners/karma/index_spec.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/angular/build/src/builders/unit-test/runners/karma/index_spec.ts b/packages/angular/build/src/builders/unit-test/runners/karma/index_spec.ts index d7ab17c27279..05f6f687b85a 100644 --- a/packages/angular/build/src/builders/unit-test/runners/karma/index_spec.ts +++ b/packages/angular/build/src/builders/unit-test/runners/karma/index_spec.ts @@ -41,16 +41,14 @@ describe('KarmaTestRunner', () => { } }); - it('does not require karma-coverage when coverage is disabled', async () => { - await expectAsync( - Promise.resolve().then(() => KarmaTestRunner.validateDependencies?.(createOptions(false))), - ).toBeResolved(); + it('does not require karma-coverage when coverage is disabled', () => { + expect(() => KarmaTestRunner.validateDependencies?.(createOptions(false))).not.toThrow(); }); - it('requires karma-coverage when coverage is enabled', async () => { - await expectAsync( - Promise.resolve().then(() => KarmaTestRunner.validateDependencies?.(createOptions(true))), - ).toBeRejectedWithError(/karma-coverage/); + it('requires karma-coverage when coverage is enabled', () => { + expect(() => KarmaTestRunner.validateDependencies?.(createOptions(true))).toThrowError( + /karma-coverage/, + ); }); }); }); From 2526ef4d169a5b8950e389737424efc6e7469caf Mon Sep 17 00:00:00 2001 From: MeAkib Date: Fri, 23 Oct 2026 15:56:58 +0600 Subject: [PATCH 3/3] fixup! fix(@angular/build): only require karma-coverage when coverage is enabled --- .../unit-test/runners/karma/index_spec.ts | 29 +++---------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/packages/angular/build/src/builders/unit-test/runners/karma/index_spec.ts b/packages/angular/build/src/builders/unit-test/runners/karma/index_spec.ts index 05f6f687b85a..8d291f03775e 100644 --- a/packages/angular/build/src/builders/unit-test/runners/karma/index_spec.ts +++ b/packages/angular/build/src/builders/unit-test/runners/karma/index_spec.ts @@ -6,43 +6,22 @@ * found in the LICENSE file at https://angular.dev/license */ -import assert from 'node:assert'; -import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'; -import { join } from 'node:path'; import type { NormalizedUnitTestBuilderOptions } from '../../options'; import KarmaTestRunner from './index'; describe('KarmaTestRunner', () => { describe('validateDependencies', () => { - let projectSourceRoot: string; - function createOptions(coverageEnabled: boolean): NormalizedUnitTestBuilderOptions { return { - projectSourceRoot, + projectSourceRoot: '/non-existent', coverage: { enabled: coverageEnabled }, } as NormalizedUnitTestBuilderOptions; } - beforeAll(async () => { - const baseTmpDir = process.env['TEST_TMPDIR']; - assert(baseTmpDir, 'TEST_TMPDIR is not set'); - projectSourceRoot = await mkdtemp(join(baseTmpDir, 'angular-cli-karma-runner-')); - - // A project with karma and karma-jasmine installed but not karma-coverage - await writeFile(join(projectSourceRoot, 'package.json'), '{"name": "test-project"}'); - for (const packageName of ['karma', 'karma-jasmine']) { - const packageRoot = join(projectSourceRoot, 'node_modules', packageName); - await mkdir(packageRoot, { recursive: true }); - await writeFile( - join(packageRoot, 'package.json'), - JSON.stringify({ name: packageName, version: '0.0.0', main: 'index.js' }), - ); - await writeFile(join(packageRoot, 'index.js'), ''); - } - }); - it('does not require karma-coverage when coverage is disabled', () => { - expect(() => KarmaTestRunner.validateDependencies?.(createOptions(false))).not.toThrow(); + expect(() => KarmaTestRunner.validateDependencies?.(createOptions(false))).toThrowError( + /^(?!.*karma-coverage)/s, + ); }); it('requires karma-coverage when coverage is enabled', () => {