From 19d72f183532b041f3c9897afd02bfb65c4941d1 Mon Sep 17 00:00:00 2001 From: "Houston (Jeb's AI)" Date: Mon, 27 Apr 2026 06:49:55 +0000 Subject: [PATCH 1/2] fix(jest): default isolatedModules to true for faster compilation (fixes #1899) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Angular 19 introduced signals, new control flow syntax, and standalone-by-default components. These produce significantly more complex TypeScript that causes the ts-jest language service (used when isolatedModules: false) to build a full cross-file Program per test file, resulting in severe slowdowns (reports of 2 min → 15 min test runs). Root cause: when isolatedModules is false (previously the implicit default), ts-jest instantiates a TypeScript LanguageService and rebuilds a full Program for each file. Angular 19+'s richer type surface makes this path prohibitively slow. Fix: set isolatedModules: true in the builder's default transformer options. This switches ts-jest to its fast per-file transpile path, matching the recommendation from jest-preset-angular's own example apps since v14.4.0. Cross-file type checking is better served by tsc --noEmit or ng build. Users who need the previous behaviour can opt out in their jest.config.ts: transform: { '^.+\.(ts|js|mjs|html|svg)$': ['jest-preset-angular', { isolatedModules: false }] } BREAKING CHANGE: isolatedModules now defaults to true. This disables cross-file TypeScript type checking during jest runs. Targeted for the next major version. --- packages/jest/src/default-config.resolver.spec.ts | 2 ++ packages/jest/src/default-config.resolver.ts | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/jest/src/default-config.resolver.spec.ts b/packages/jest/src/default-config.resolver.spec.ts index 2235f8f3db..26ff41ec69 100644 --- a/packages/jest/src/default-config.resolver.spec.ts +++ b/packages/jest/src/default-config.resolver.spec.ts @@ -14,6 +14,7 @@ describe('Resolve project default configuration', () => { { stringifyContentPathRegex: '\\.(html|svg)$', tsconfig: getSystemPath(normalize(`/some/cool/directory/${tsConfigName}`)), + isolatedModules: true, }, ]); }); @@ -28,6 +29,7 @@ describe('Resolve project default configuration', () => { { stringifyContentPathRegex: '\\.(html|svg)$', tsconfig: getSystemPath(normalize(`/some/cool/project/ts-configs/tsconfig.spec.json`)), + isolatedModules: true, }, ]); }); diff --git a/packages/jest/src/default-config.resolver.ts b/packages/jest/src/default-config.resolver.ts index f9d950c19e..022bce8234 100644 --- a/packages/jest/src/default-config.resolver.ts +++ b/packages/jest/src/default-config.resolver.ts @@ -47,6 +47,16 @@ export class DefaultConfigResolver { stringifyContentPathRegex: '\\.(html|svg)$', // Join with the default `tsConfigName` if the `tsConfig` option is not provided tsconfig: getTsConfigPath(projectRoot, this.options), + // Default to isolatedModules: true for significantly faster compilation. + // With isolatedModules: false (the previous implicit default), ts-jest uses the + // TypeScript language service to build a full cross-file Program for every test + // file, which becomes extremely slow with Angular 19+ code (signals, new control + // flow, standalone-by-default). Angular 19+ users report 2min → 15min regressions. + // Cross-file type checking is better handled by `tsc --noEmit` or `ng build`. + // Users who need the old behaviour can opt out via their jest.config.ts: + // transform: { '...': ['jest-preset-angular', { isolatedModules: false }] } + // BREAKING CHANGE: targeted for the next major version. + isolatedModules: true, }, ], }, From 9733a2628f8e6e4a258429d12fb3960ae02ecca7 Mon Sep 17 00:00:00 2001 From: Jeb Date: Mon, 18 May 2026 13:47:56 +0200 Subject: [PATCH 2/2] test(jest): replace config-shape assertions with behavioral integration test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove isolatedModules:true assertions from the unit spec — they tested implementation details (object shape), not user-visible behavior. Replace with targeted assertions on tsconfig path resolution, which is the actual behavioral contract of the resolver. Add isolated-modules-default integration test entry that proves Angular component tests still pass end-to-end with isolatedModules:true active, providing the behavioral regression guard for #1899. --- .../jest/src/default-config.resolver.spec.ts | 24 +++++++------------ packages/jest/tests/integration.js | 11 +++++++++ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/jest/src/default-config.resolver.spec.ts b/packages/jest/src/default-config.resolver.spec.ts index 26ff41ec69..8c2ef385ad 100644 --- a/packages/jest/src/default-config.resolver.spec.ts +++ b/packages/jest/src/default-config.resolver.spec.ts @@ -9,14 +9,10 @@ const defaultConfigResolver = new DefaultConfigResolver({}); describe('Resolve project default configuration', () => { it('Should resolve tsconfig relatively to project root', () => { const config = defaultConfigResolver.resolveForProject(normalize('/some/cool/directory')); - expect(config.transform[defaultConfigResolver.tsJestTransformRegExp]).toEqual([ - 'jest-preset-angular', - { - stringifyContentPathRegex: '\\.(html|svg)$', - tsconfig: getSystemPath(normalize(`/some/cool/directory/${tsConfigName}`)), - isolatedModules: true, - }, - ]); + const [, transformOptions] = config.transform[defaultConfigResolver.tsJestTransformRegExp]; + expect(transformOptions.tsconfig).toEqual( + getSystemPath(normalize(`/some/cool/directory/${tsConfigName}`)) + ); }); it('Should resolve path to the tsconfig if "tsConfig" is provided', () => { @@ -24,14 +20,10 @@ describe('Resolve project default configuration', () => { tsConfig: './ts-configs/tsconfig.spec.json', }); const config = defaultConfigResolver.resolveForProject(normalize('/some/cool/project')); - expect(config.transform[defaultConfigResolver.tsJestTransformRegExp]).toEqual([ - 'jest-preset-angular', - { - stringifyContentPathRegex: '\\.(html|svg)$', - tsconfig: getSystemPath(normalize(`/some/cool/project/ts-configs/tsconfig.spec.json`)), - isolatedModules: true, - }, - ]); + const [, transformOptions] = config.transform[defaultConfigResolver.tsJestTransformRegExp]; + expect(transformOptions.tsconfig).toEqual( + getSystemPath(normalize(`/some/cool/project/ts-configs/tsconfig.spec.json`)) + ); }); it('Should resolve testMatch pattern relatively to project root', () => { diff --git a/packages/jest/tests/integration.js b/packages/jest/tests/integration.js index 3ff15587eb..a35bb72b99 100644 --- a/packages/jest/tests/integration.js +++ b/packages/jest/tests/integration.js @@ -15,6 +15,17 @@ module.exports = [ command: 'yarn test:esm --no-cache', }, + // isolatedModules default - behavioral proof + { + id: 'isolated-modules-default', + name: 'jest: isolatedModules:true default works', + purpose: + 'Tests pass correctly with isolatedModules:true (default since v21), regression for #1899', + app: 'examples/jest/simple-app', + command: + 'node ../../../packages/jest/tests/validate.js --no-cache --expect-suites=2 --expect-tests=4', + }, + // CLI passthrough - validated tests { id: 'cli-no-cache',