From f89781b70ce7b130befe339b1b4bd1eae61f3fce Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:48:34 -0500 Subject: [PATCH] fix(@angular/build): inject source-map-support for Vitest browser tests This change ensures that `source-map-support` is injected into the setup files when running Vitest tests in a browser environment. This allows stack traces from failing tests to correctly map back to the original source files, significantly improving debugging capabilities. A regression test has been added to `tests/vitest/browser-sourcemaps.ts` to verify that a failing test correctly identifies the source file in its stack trace. --- .../unit-test/runners/vitest/build-options.ts | 16 ++++++++ .../e2e/tests/vitest/browser-sourcemaps.ts | 39 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 tests/legacy-cli/e2e/tests/vitest/browser-sourcemaps.ts 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 1c65d6fc4d50..3d99b56f6191 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 @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ +import { createRequire } from 'node:module'; import path from 'node:path'; import { toPosixPath } from '../../../../utils/path'; import type { ApplicationBuilderInternalOptions } from '../../../application/options'; @@ -18,6 +19,7 @@ function createTestBedInitVirtualFile( providersFile: string | undefined, projectSourceRoot: string, polyfills: string[] = [], + sourcemapSupport: boolean = false, ): string { const usesZoneJS = polyfills.includes('zone.js'); let providersImport = 'const providers = [];'; @@ -28,6 +30,18 @@ function createTestBedInitVirtualFile( providersImport = `import providers from './${importPath}';`; } + // Resolve and add sourcemap support (mainly for browsers) + let sourceMapSetup; + if (sourcemapSupport) { + const packageResolve = createRequire(__filename).resolve; + const sourceMapPath = packageResolve('source-map-support'); + + sourceMapSetup = ` + import sourceMapSupport from '${sourceMapPath}'; + sourceMapSupport.install(); + `; + } + return ` // Initialize the Angular testing environment import { NgModule${usesZoneJS ? ', provideZoneChangeDetection' : ''} } from '@angular/core'; @@ -35,6 +49,7 @@ function createTestBedInitVirtualFile( import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; import { afterEach, beforeEach } from 'vitest'; ${providersImport} + ${sourceMapSetup} // The beforeEach and afterEach hooks are registered outside the globalThis guard. // This ensures that the hooks are always applied, even in non-isolated browser environments. @@ -134,6 +149,7 @@ export async function getVitestBuildOptions( providersFile, projectSourceRoot, buildOptions.polyfills, + !!options.browsers?.length, ); return { diff --git a/tests/legacy-cli/e2e/tests/vitest/browser-sourcemaps.ts b/tests/legacy-cli/e2e/tests/vitest/browser-sourcemaps.ts new file mode 100644 index 000000000000..8c4747f2974c --- /dev/null +++ b/tests/legacy-cli/e2e/tests/vitest/browser-sourcemaps.ts @@ -0,0 +1,39 @@ +import assert from 'node:assert/strict'; +import { applyVitestBuilder } from '../../utils/vitest'; +import { ng } from '../../utils/process'; +import { installPackage } from '../../utils/packages'; +import { writeFile } from '../../utils/fs'; + +export default async function (): Promise { + await applyVitestBuilder(); + await installPackage('playwright@1'); + await installPackage('@vitest/browser-playwright@4'); + await ng('generate', 'component', 'my-comp'); + + // Add a failing test to verify source map support + await writeFile( + 'src/app/failing.spec.ts', + ` + describe('Failing Test', () => { + it('should fail', () => { + throw new Error('This is a failing test'); + }); + }); + `, + ); + + try { + await ng('test', '--no-watch', '--browsers', 'chromiumHeadless'); + throw new Error('Expected "ng test" to fail.'); + } catch (error: any) { + const stdout = error.stdout || error.message; + // We expect the failure from failing.spec.ts + assert.match(stdout, /1 failed/, 'Expected 1 test to fail.'); + // Check that the stack trace points to the correct file + assert.match( + stdout, + /src\/app\/failing\.spec\.ts:\d+:\d+/, + 'Expected stack trace to point to the source file.', + ); + } +}