diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts index d614d16f97b8..93ef7cfb9f85 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts @@ -9,6 +9,7 @@ import assert from 'node:assert'; import { readFile } from 'node:fs/promises'; import { createRequire } from 'node:module'; +import { platform } from 'node:os'; import path from 'node:path'; import type { BrowserConfigOptions, @@ -173,6 +174,7 @@ async function loadResultFile(file: ResultFile): Promise { export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins { const { workspaceRoot, buildResultFiles, testFileToEntryPoint } = pluginOptions; + const isWindows = platform() === 'win32'; return [ { @@ -184,6 +186,31 @@ export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins return id; } + // Workaround for Vitest in Windows when a fully qualified absolute path is provided with + // a superfluous leading slash. This can currently occur with the `@vitest/coverage-v8` provider + // when it uses `removeStartsWith(url, FILE_PROTOCOL)` to convert a file URL resulting in + // `/D:/tmp_dir/...` instead of `D:/tmp_dir/...`. + if (id[0] === '/' && isWindows) { + const slicedId = id.slice(1); + if (path.isAbsolute(slicedId)) { + return slicedId; + } + } + + if (importer && (id[0] === '.' || id[0] === '/')) { + let fullPath; + if (testFileToEntryPoint.has(importer)) { + fullPath = toPosixPath(path.join(workspaceRoot, id)); + } else { + fullPath = toPosixPath(path.join(path.dirname(importer), id)); + } + + const relativePath = path.relative(workspaceRoot, fullPath); + if (buildResultFiles.has(toPosixPath(relativePath))) { + return fullPath; + } + } + // Determine the base directory for resolution. let baseDir: string; if (importer) { diff --git a/tests/legacy-cli/e2e/tests/vitest/larger-project-coverage.ts b/tests/legacy-cli/e2e/tests/vitest/larger-project-coverage.ts index 4b5d5cc72bfa..3594bdc7dfee 100644 --- a/tests/legacy-cli/e2e/tests/vitest/larger-project-coverage.ts +++ b/tests/legacy-cli/e2e/tests/vitest/larger-project-coverage.ts @@ -36,16 +36,12 @@ export default async function () { const { stdout: jsdomStdout } = await ng('test', '--no-watch', '--coverage'); assert.match(jsdomStdout, expectedMessage, `Expected ${totalTests} tests to pass in JSDOM mode.`); - // TODO: Investigate why coverage-final.json is empty on Windows in JSDOM mode. - // For now, skip the coverage report check on Windows. - if (process.platform !== 'win32') { - // Assert that every generated file is in the coverage report by reading the JSON output. - const jsdomSummary = JSON.parse(await readFile(coverageJsonPath)); - const jsdomSummaryKeys = Object.keys(jsdomSummary); - for (const file of generatedFiles) { - const found = jsdomSummaryKeys.some((key) => key.endsWith(file)); - assert.ok(found, `Expected ${file} to be in the JSDOM coverage report.`); - } + // Assert that every generated file is in the coverage report by reading the JSON output. + const jsdomSummary = JSON.parse(await readFile(coverageJsonPath)); + const jsdomSummaryKeys = Object.keys(jsdomSummary); + for (const file of generatedFiles) { + const found = jsdomSummaryKeys.some((key) => key.endsWith(file)); + assert.ok(found, `Expected ${file} to be in the JSDOM coverage report.`); } // Setup for browser mode