From 95c642648da7bc8cf154c71f150dc042fa24ec34 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 24 Jun 2026 16:38:49 -0400 Subject: [PATCH] fix(@angular/build): auto-inject localize/init in library unit tests Injects '@angular/localize/init' at the top of the internal virtual initialization file (angular:test-bed-init) if '@angular/localize' is installed in the workspace. This prevents ReferenceErrors when testing libraries that consume localization APIs. Also updates the warning plugin to skip internal imports originating from the 'angular:' namespace. --- .../build/src/builders/karma/application_builder.ts | 9 +++++++++ .../builders/unit-test/runners/vitest/build-options.ts | 10 ++++++++++ .../esbuild/angular-localize-init-warning-plugin.ts | 4 ++++ 3 files changed, 23 insertions(+) diff --git a/packages/angular/build/src/builders/karma/application_builder.ts b/packages/angular/build/src/builders/karma/application_builder.ts index 34e94b1b7645..1371173a4efe 100644 --- a/packages/angular/build/src/builders/karma/application_builder.ts +++ b/packages/angular/build/src/builders/karma/application_builder.ts @@ -11,6 +11,7 @@ import type { Config, ConfigOptions, FilePattern, InlinePluginDef, Server } from import { randomUUID } from 'node:crypto'; import { rmSync } from 'node:fs'; import * as fs from 'node:fs/promises'; +import { createRequire } from 'node:module'; import path from 'node:path'; import { ReadableStream } from 'node:stream/web'; import { createVirtualModulePlugin } from '../../tools/esbuild/virtual-module-plugin'; @@ -199,10 +200,18 @@ async function runEsbuild( projectSourceRoot: string, ): Promise<[Result & { kind: ResultKind.Full }, AsyncIterator | null]> { const usesZoneJS = buildOptions.polyfills?.includes('zone.js'); + let hasLocalize = false; + try { + const projectRequire = createRequire(path.join(projectSourceRoot, 'package.json')); + projectRequire.resolve('@angular/localize'); + hasLocalize = true; + } catch {} + const virtualTestBedInit = createVirtualModulePlugin({ namespace: 'angular:test-bed-init', loadContent: async () => { const contents: string[] = [ + ...(hasLocalize ? [`import '@angular/localize/init';`] : []), // Initialize the Angular testing environment `import { NgModule${usesZoneJS ? ', provideZoneChangeDetection' : ''} } from '@angular/core';`, `import { getTestBed } from '@angular/core/testing';`, diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts index 34d65d3d418a..23b5f5024de6 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts @@ -34,6 +34,7 @@ function createTestBedInitVirtualFile( projectSourceRoot: string, teardown: boolean, zoneTestingStrategy: 'none' | 'static' | 'dynamic', + hasLocalize: boolean, ): string { let providersImport = 'const providers = [];'; if (providersFile) { @@ -58,6 +59,7 @@ function createTestBedInitVirtualFile( // when running Vitest in non-isolated mode with JSDOM. It looks up the // document dynamically on every operation instead of caching it. return ` + ${hasLocalize ? "import '@angular/localize/init';" : ''} // Initialize the Angular testing environment import { NgModule, provideZoneChangeDetection } from '@angular/core'; import { getTestBed, ɵgetCleanupHook as getCleanupHook, TestComponentRenderer } from '@angular/core/testing'; @@ -254,11 +256,19 @@ export async function getVitestBuildOptions( // Inject the zone.js testing polyfill if Zone.js is installed. const zoneTestingStrategy = getZoneTestingStrategy(buildOptions, projectSourceRoot); + let hasLocalize = false; + try { + const projectRequire = createRequire(path.join(projectSourceRoot, 'package.json')); + projectRequire.resolve('@angular/localize'); + hasLocalize = true; + } catch {} + const testBedInitContents = createTestBedInitVirtualFile( providersFile, projectSourceRoot, !options.debug, zoneTestingStrategy, + hasLocalize, ); const mockPatchContents = ` diff --git a/packages/angular/build/src/tools/esbuild/angular-localize-init-warning-plugin.ts b/packages/angular/build/src/tools/esbuild/angular-localize-init-warning-plugin.ts index 341d40b00541..d0aa9aff841d 100644 --- a/packages/angular/build/src/tools/esbuild/angular-localize-init-warning-plugin.ts +++ b/packages/angular/build/src/tools/esbuild/angular-localize-init-warning-plugin.ts @@ -26,6 +26,10 @@ export function createAngularLocalizeInitWarningPlugin(): Plugin { return null; } + if (args.namespace?.startsWith('angular:')) { + return null; + } + const { importer, kind, resolveDir, namespace, pluginData = {} } = args; pluginData[NG_LOCALIZE_RESOLUTION] = true;