diff --git a/packages/coverage-v8/src/provider.ts b/packages/coverage-v8/src/provider.ts index b0f230fcf261..7733007804e3 100644 --- a/packages/coverage-v8/src/provider.ts +++ b/packages/coverage-v8/src/provider.ts @@ -13,7 +13,8 @@ import remapping from '@ampproject/remapping' import { normalize, resolve } from 'pathe' import c from 'picocolors' import { provider } from 'std-env' -import type { EncodedSourceMap } from 'vite-node' +import { cleanUrl } from 'vite-node/utils' +import type { EncodedSourceMap, FetchResult } from 'vite-node' import { coverageConfigDefaults, defaultExclude, defaultInclude } from 'vitest/config' import { BaseCoverageProvider } from 'vitest/coverage' import type { AfterSuiteRunMeta, CoverageProvider, CoverageV8Options, ReportContext, ResolvedCoverageOptions } from 'vitest' @@ -36,6 +37,7 @@ interface TestExclude { } type Options = ResolvedCoverageOptions<'v8'> +type TransformResults = Map // TODO: vite-node should export this const WRAPPER_LENGTH = 185 @@ -99,18 +101,19 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage if (provider === 'stackblitz') this.ctx.logger.log(c.blue(' % ') + c.yellow('@vitest/coverage-v8 does not work on Stackblitz. Report will be empty.')) + const transformResults = normalizeTransformResults(this.ctx.projects.map(project => project.vitenode.fetchCache)) const merged = mergeProcessCovs(this.coverages) const scriptCoverages = merged.result.filter(result => this.testExclude.shouldInstrument(fileURLToPath(result.url))) if (this.options.all && allTestsRun) { const coveredFiles = Array.from(scriptCoverages.map(r => r.url)) - const untestedFiles = await this.getUntestedFiles(coveredFiles) + const untestedFiles = await this.getUntestedFiles(coveredFiles, transformResults) scriptCoverages.push(...untestedFiles) } const converted = await Promise.all(scriptCoverages.map(async ({ url, functions }) => { - const sources = await this.getSources(url, functions) + const sources = await this.getSources(url, transformResults, functions) // If no source map was found from vite-node we can assume this file was not run in the wrapper const wrapperLength = sources.sourceMap ? WRAPPER_LENGTH : 0 @@ -177,14 +180,14 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage } } - private async getUntestedFiles(testedFiles: string[]): Promise { + private async getUntestedFiles(testedFiles: string[], transformResults: TransformResults): Promise { const includedFiles = await this.testExclude.glob(this.ctx.config.root) const uncoveredFiles = includedFiles .map(file => pathToFileURL(resolve(this.ctx.config.root, file))) .filter(file => !testedFiles.includes(file.href)) return await Promise.all(uncoveredFiles.map(async (uncoveredFile) => { - const { source } = await this.getSources(uncoveredFile.href) + const { source } = await this.getSources(uncoveredFile.href, transformResults) return { url: uncoveredFile.href, @@ -204,16 +207,14 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage })) } - private async getSources(url: string, functions: Profiler.FunctionCoverage[] = []): Promise<{ + private async getSources(url: string, transformResults: TransformResults, functions: Profiler.FunctionCoverage[] = []): Promise<{ source: string originalSource?: string sourceMap?: { sourcemap: EncodedSourceMap } }> { const filePath = normalize(fileURLToPath(url)) - const transformResult = this.ctx.projects - .map(project => project.vitenode.fetchCache.get(filePath)?.result) - .filter(Boolean) - .shift() + + const transformResult = transformResults.get(filePath) const map = transformResult?.map const code = transformResult?.code @@ -277,3 +278,18 @@ function findLongestFunctionLength(functions: Profiler.FunctionCoverage[]) { return Math.max(previous, maxEndOffset) }, 0) } + +function normalizeTransformResults(fetchCaches: Map[]) { + const normalized: TransformResults = new Map() + + for (const fetchCache of fetchCaches) { + for (const [key, value] of fetchCache.entries()) { + const cleanEntry = cleanUrl(key) + + if (!normalized.has(cleanEntry)) + normalized.set(cleanEntry, value.result) + } + } + + return normalized +} diff --git a/test/coverage-test/coverage-report-tests/__snapshots__/v8.report.test.ts.snap b/test/coverage-test/coverage-report-tests/__snapshots__/v8.report.test.ts.snap index d561f7561b8b..1d002a4ef9c3 100644 --- a/test/coverage-test/coverage-report-tests/__snapshots__/v8.report.test.ts.snap +++ b/test/coverage-test/coverage-report-tests/__snapshots__/v8.report.test.ts.snap @@ -704,12 +704,11 @@ exports[`v8 json report 1`] = ` "0": 1, "1": 1, "2": 1, - "3": 1, }, "statementMap": { "0": { "end": { - "column": 50, + "column": 38, "line": 1, }, "start": { @@ -719,7 +718,7 @@ exports[`v8 json report 1`] = ` }, "1": { "end": { - "column": 38, + "column": 0, "line": 2, }, "start": { @@ -729,7 +728,7 @@ exports[`v8 json report 1`] = ` }, "2": { "end": { - "column": 0, + "column": 21, "line": 3, }, "start": { @@ -737,16 +736,6 @@ exports[`v8 json report 1`] = ` "line": 3, }, }, - "3": { - "end": { - "column": 39, - "line": 4, - }, - "start": { - "column": 0, - "line": 4, - }, - }, }, }, "/src/Defined.vue": { diff --git a/test/coverage-test/src/Counter/index.ts b/test/coverage-test/src/Counter/index.ts index 378476d78d22..65b75829d50c 100644 --- a/test/coverage-test/src/Counter/index.ts +++ b/test/coverage-test/src/Counter/index.ts @@ -1,4 +1,3 @@ -import CounterComponent from './Counter.component' import CounterVue from './Counter.vue' -export { CounterComponent, CounterVue } +export { CounterVue }