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 96d3337e0149..d614d16f97b8 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 @@ -163,6 +163,14 @@ export async function createVitestConfigPlugin( }; } +async function loadResultFile(file: ResultFile): Promise { + if (file.origin === 'memory') { + return new TextDecoder('utf-8').decode(file.contents); + } + + return readFile(file.inputPath, 'utf-8'); +} + export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins { const { workspaceRoot, buildResultFiles, testFileToEntryPoint } = pluginOptions; @@ -171,29 +179,33 @@ export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins name: 'angular:test-in-memory-provider', enforce: 'pre', resolveId: (id, importer) => { - 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; - } - } - + // Fast path for test entry points. if (testFileToEntryPoint.has(id)) { return id; } - assert(buildResultFiles.size > 0, 'buildResult must be available for resolving.'); - const relativePath = path.relative(workspaceRoot, id); + // Determine the base directory for resolution. + let baseDir: string; + if (importer) { + // If the importer is a test entry point, resolve relative to the workspace root. + // Otherwise, resolve relative to the importer's directory. + baseDir = testFileToEntryPoint.has(importer) ? workspaceRoot : path.dirname(importer); + } else { + // If there's no importer, assume the id is relative to the workspace root. + baseDir = workspaceRoot; + } + + // Construct the full, absolute path and normalize it to POSIX format. + const fullPath = toPosixPath(path.join(baseDir, id)); + + // Check if the resolved path corresponds to a known build artifact. + const relativePath = path.relative(workspaceRoot, fullPath); if (buildResultFiles.has(toPosixPath(relativePath))) { - return id; + return fullPath; } + + // If the module cannot be resolved from the build artifacts, let other plugins handle it. + return undefined; }, load: async (id) => { assert(buildResultFiles.size > 0, 'buildResult must be available for in-memory loading.'); @@ -217,17 +229,10 @@ export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins const outputFile = buildResultFiles.get(outputPath); if (outputFile) { + const code = await loadResultFile(outputFile); const sourceMapPath = outputPath + '.map'; const sourceMapFile = buildResultFiles.get(sourceMapPath); - const code = - outputFile.origin === 'memory' - ? Buffer.from(outputFile.contents).toString('utf-8') - : await readFile(outputFile.inputPath, 'utf-8'); - const sourceMapText = sourceMapFile - ? sourceMapFile.origin === 'memory' - ? Buffer.from(sourceMapFile.contents).toString('utf-8') - : await readFile(sourceMapFile.inputPath, 'utf-8') - : undefined; + const sourceMapText = sourceMapFile ? await loadResultFile(sourceMapFile) : undefined; // Vitest will include files in the coverage report if the sourcemap contains no sources. // For builder-internal generated code chunks, which are typically helper functions,