diff --git a/packages/angular/build/src/builders/unit-test/options.ts b/packages/angular/build/src/builders/unit-test/options.ts index 8fa5b14eda59..3fb7bc05f2d4 100644 --- a/packages/angular/build/src/builders/unit-test/options.ts +++ b/packages/angular/build/src/builders/unit-test/options.ts @@ -8,6 +8,7 @@ import { type BuilderContext, targetFromTargetString } from '@angular-devkit/architect'; import { constants, promises as fs } from 'node:fs'; +import { createRequire } from 'node:module'; import path from 'node:path'; import { normalizeCacheOptions } from '../../utils/normalize-cache'; import { getProjectRootPaths } from '../../utils/project-metadata'; @@ -135,6 +136,22 @@ export async function normalizeOptions( }; } -export function injectTestingPolyfills(polyfills: string[] = []): string[] { - return polyfills.includes('zone.js') ? [...polyfills, 'zone.js/testing'] : polyfills; +export function injectTestingPolyfills( + configuredPolyfills: string[] = [], + sourcemapSupport: boolean = false, +): string[] { + const updatedPolyfills = [...configuredPolyfills]; + + // Resolve and add sourcemap support (mainly for browsers) + if (sourcemapSupport) { + const packageResolve = createRequire(__filename).resolve; + updatedPolyfills.unshift(packageResolve('source-map-support/browser-source-map-support.js')); + } + + // Add zone.js testing if zone.js is present + if (configuredPolyfills.includes('zone.js')) { + updatedPolyfills.push('zone.js/testing'); + } + + return updatedPolyfills; } 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..ac0a2c702c7c 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 @@ -128,7 +128,10 @@ export async function getVitestBuildOptions( externalDependencies, }; - buildOptions.polyfills = injectTestingPolyfills(buildOptions.polyfills); + buildOptions.polyfills = injectTestingPolyfills( + buildOptions.polyfills, + !!options.browsers?.length, + ); const testBedInitContents = createTestBedInitVirtualFile( providersFile, 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.', + ); + } +}