Skip to content

Commit b7ffed4

Browse files
committed
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.
1 parent edeb41c commit b7ffed4

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ export async function createVitestConfigPlugin(
112112
delete config.plugins;
113113
}
114114

115+
// Add browser source map support
116+
if (browser || testConfig?.browser?.enabled) {
117+
// TODO: Add sourcemap support loader plugin here.
118+
projectPlugins.unshift(createSourcemapSupportPlugin());
119+
setupFiles.unshift('source-map-support');
120+
}
121+
115122
const projectResolver = createRequire(projectSourceRoot + '/').resolve;
116123

117124
const projectDefaults: UserWorkspaceConfig = {
@@ -306,6 +313,32 @@ export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins
306313
];
307314
}
308315

316+
function createSourcemapSupportPlugin(): VitestPlugins[0] {
317+
return {
318+
name: 'angular:source-map-support',
319+
enforce: 'pre',
320+
resolveId(source) {
321+
if (source === 'source-map-support') {
322+
return 'source-map-support';
323+
}
324+
},
325+
async load(id) {
326+
if (id !== 'source-map-support') {
327+
return;
328+
}
329+
330+
const packageResolve = createRequire(__filename).resolve;
331+
const supportPath = packageResolve('source-map-support/browser-source-map-support.js');
332+
333+
const content = await readFile(supportPath, 'utf-8');
334+
335+
return (
336+
content.replaceAll('this.', 'globalThis.') + '\n;globalThis.sourceMapSupport.install();'
337+
);
338+
},
339+
};
340+
}
341+
309342
async function generateCoverageOption(
310343
optionsCoverage: NormalizedUnitTestBuilderOptions['coverage'],
311344
configCoverage: VitestCoverageOption | undefined,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import assert from 'node:assert/strict';
2+
import { applyVitestBuilder } from '../../utils/vitest';
3+
import { ng, noSilentNg } from '../../utils/process';
4+
import { installPackage } from '../../utils/packages';
5+
import { writeFile } from '../../utils/fs';
6+
7+
export default async function (): Promise<void> {
8+
await applyVitestBuilder();
9+
await installPackage('playwright@1');
10+
await installPackage('@vitest/browser-playwright@4');
11+
await ng('generate', 'component', 'my-comp');
12+
13+
// Add a failing test to verify source map support
14+
await writeFile(
15+
'src/app/failing.spec.ts',
16+
`
17+
describe('Failing Test', () => {
18+
it('should fail', () => {
19+
expect(true).toBe(false);
20+
});
21+
});
22+
`,
23+
);
24+
25+
try {
26+
await noSilentNg('test', '--no-watch', '--browsers', 'chromiumHeadless');
27+
throw new Error('Expected "ng test" to fail.');
28+
} catch (error: any) {
29+
const stdout = error.stdout || error.message;
30+
// We expect the failure from failing.spec.ts
31+
assert.match(stdout, /1 failed/, 'Expected 1 test to fail.');
32+
// Check that the stack trace points to the correct file
33+
assert.match(
34+
stdout,
35+
/src\/app\/failing\.spec\.ts:\d+:\d+/,
36+
'Expected stack trace to point to the source file.',
37+
);
38+
}
39+
}

0 commit comments

Comments
 (0)