diff --git a/packages/vitest/src/runtime/moduleRunner/moduleEvaluator.ts b/packages/vitest/src/runtime/moduleRunner/moduleEvaluator.ts index f5e45ea53306..76fb1ed4c1d6 100644 --- a/packages/vitest/src/runtime/moduleRunner/moduleEvaluator.ts +++ b/packages/vitest/src/runtime/moduleRunner/moduleEvaluator.ts @@ -315,7 +315,8 @@ export class VitestModuleEvaluator implements ModuleEvaluator { )})=>{{` const wrappedCode = `${codeDefinition}${code}\n}}` const options = { - filename: module.id, + // use original id for auto spy module (vi.mock(..., { spy: true })) + filename: module.id.startsWith('mock:') ? module.id.slice(5) : module.id, lineOffset: 0, columnOffset: -codeDefinition.length, } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ede38bf7588f..1a840b66ebe9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10206,7 +10206,7 @@ snapshots: '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.28.6 '@babel/helper-compilation-targets@7.27.2': dependencies: @@ -10232,7 +10232,7 @@ snapshots: '@babel/helper-optimise-call-expression': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.6) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -10247,7 +10247,7 @@ snapshots: '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 - '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.27.1 debug: 4.4.3 lodash.debounce: 4.0.8 @@ -10259,8 +10259,8 @@ snapshots: '@babel/helper-member-expression-to-functions@7.28.5': dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 transitivePeerDependencies: - supports-color @@ -10287,15 +10287,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 @@ -10307,7 +10298,7 @@ snapshots: '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.28.6 '@babel/helper-plugin-utils@7.27.1': {} @@ -10316,7 +10307,7 @@ snapshots: '@babel/core': 7.28.6 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.3 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color @@ -10325,14 +10316,14 @@ snapshots: '@babel/core': 7.28.6 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 transitivePeerDependencies: - supports-color @@ -10346,9 +10337,9 @@ snapshots: '@babel/helper-wrap-function@7.28.3': dependencies: - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 transitivePeerDependencies: - supports-color @@ -10382,7 +10373,7 @@ snapshots: dependencies: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color @@ -10409,7 +10400,7 @@ snapshots: dependencies: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color @@ -10443,14 +10434,14 @@ snapshots: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.6) - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 - '@babel/helper-module-imports': 7.27.1 + '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.6) transitivePeerDependencies: @@ -10486,11 +10477,11 @@ snapshots: dependencies: '@babel/core': 7.28.6 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-globals': 7.28.0 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.6) - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color @@ -10498,13 +10489,13 @@ snapshots: dependencies: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.27.1 - '@babel/template': 7.27.2 + '@babel/template': 7.28.6 '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color @@ -10559,9 +10550,9 @@ snapshots: '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 - '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color @@ -10588,7 +10579,7 @@ snapshots: '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.6) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -10596,7 +10587,7 @@ snapshots: '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.6) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -10604,17 +10595,17 @@ snapshots: '@babel/plugin-transform-modules-systemjs@7.28.5(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.6) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.6) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -10643,11 +10634,11 @@ snapshots: '@babel/plugin-transform-object-rest-spread@7.28.4(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 - '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.6) - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color @@ -10778,9 +10769,9 @@ snapshots: '@babel/preset-env@7.28.5(@babel/core@7.28.6)': dependencies: - '@babel/compat-data': 7.28.5 + '@babel/compat-data': 7.28.6 '@babel/core': 7.28.6 - '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.28.6) @@ -10856,7 +10847,7 @@ snapshots: dependencies: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.27.1 - '@babel/types': 7.28.5 + '@babel/types': 7.28.6 esutils: 2.0.3 '@babel/runtime@7.26.0': @@ -12399,7 +12390,7 @@ snapshots: '@rollup/plugin-babel@5.3.1(@babel/core@7.28.6)(@types/babel__core@7.20.5)(rollup@4.56.0)': dependencies: '@babel/core': 7.28.6 - '@babel/helper-module-imports': 7.27.1 + '@babel/helper-module-imports': 7.28.6 '@rollup/pluginutils': 3.1.0(rollup@4.56.0) rollup: 4.56.0 optionalDependencies: @@ -13814,7 +13805,7 @@ snapshots: babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.6): dependencies: - '@babel/compat-data': 7.28.5 + '@babel/compat-data': 7.28.6 '@babel/core': 7.28.6 '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.6) semver: 6.3.1 @@ -16246,8 +16237,8 @@ snapshots: magicast@0.3.5: dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 source-map-js: 1.2.1 optional: true diff --git a/test/coverage-test/fixtures/src/mock-target.ts b/test/coverage-test/fixtures/src/mock-target.ts new file mode 100644 index 000000000000..bf28b98258e0 --- /dev/null +++ b/test/coverage-test/fixtures/src/mock-target.ts @@ -0,0 +1,7 @@ +export function double(value: number): number { + return value * 2 +} + +export function triple(value: number): number { + return value * 3 +} diff --git a/test/coverage-test/fixtures/test/mock-autospy-fixture.test.ts b/test/coverage-test/fixtures/test/mock-autospy-fixture.test.ts new file mode 100644 index 000000000000..9a0c310f3331 --- /dev/null +++ b/test/coverage-test/fixtures/test/mock-autospy-fixture.test.ts @@ -0,0 +1,10 @@ +import { expect, test, vi } from 'vitest' +import { double, triple } from '../src/mock-target' + +vi.mock(import('../src/mock-target'), { spy: true }) + +test('autospy calls original and can be spied on', () => { + expect(double(5)).toBe(10) + expect(double).toHaveBeenCalledWith(5) + expect(triple).not.toHaveBeenCalled() +}) diff --git a/test/coverage-test/fixtures/test/mock-importActual-fixture.test.ts b/test/coverage-test/fixtures/test/mock-importActual-fixture.test.ts new file mode 100644 index 000000000000..8f00072ba67e --- /dev/null +++ b/test/coverage-test/fixtures/test/mock-importActual-fixture.test.ts @@ -0,0 +1,17 @@ +import { expect, test, vi } from 'vitest' +import { double, triple } from '../src/mock-target' + +// Manual spy approach using importOriginal callback - this collects coverage +vi.mock(import('../src/mock-target'), async (importOriginal) => { + const actual = await importOriginal() + return { + double: vi.fn(actual.double), + triple: vi.fn(actual.triple), + } +}) + +test('importActual calls original and can be spied on', () => { + expect(double(5)).toBe(10) + expect(double).toHaveBeenCalledWith(5) + expect(triple).not.toHaveBeenCalled() +}) diff --git a/test/coverage-test/test/mock-autospy.test.ts b/test/coverage-test/test/mock-autospy.test.ts new file mode 100644 index 000000000000..2c96964dbec7 --- /dev/null +++ b/test/coverage-test/test/mock-autospy.test.ts @@ -0,0 +1,40 @@ +import { expect } from 'vitest' +import { readCoverageMap, runVitest, test } from '../utils' + +test('vi.mock({ spy: true }) collects coverage of original module', async () => { + await runVitest({ + include: ['fixtures/test/mock-autospy-fixture.test.ts'], + coverage: { + reporter: 'json', + include: ['fixtures/src/mock-target.ts'], + }, + }) + + const coverageMap = await readCoverageMap() + expect(coverageMap).toMatchInlineSnapshot(` + { + "branches": "0/0 (100%)", + "functions": "1/2 (50%)", + "lines": "1/2 (50%)", + "statements": "1/2 (50%)", + } + `) + + const coverage = coverageMap.fileCoverageFor('/fixtures/src/mock-target.ts') + const functionCoverage = Object.keys(coverage.fnMap) + .map(index => ({ name: coverage.fnMap[index].name, hits: coverage.f[index] })) + .sort((a, b) => a.name.localeCompare(b.name)) + + expect(functionCoverage).toMatchInlineSnapshot(` + [ + { + "hits": 1, + "name": "double", + }, + { + "hits": 0, + "name": "triple", + }, + ] + `) +}) diff --git a/test/coverage-test/test/mock-importActual.test.ts b/test/coverage-test/test/mock-importActual.test.ts new file mode 100644 index 000000000000..0ad1d2b7b972 --- /dev/null +++ b/test/coverage-test/test/mock-importActual.test.ts @@ -0,0 +1,54 @@ +import { expect } from 'vitest' +import { isBrowser, isNativeRunner, isV8Provider, readCoverageMap, runVitest, test } from '../utils' + +test('vi.importActual() collects coverage of original module', async () => { + await runVitest({ + include: ['fixtures/test/mock-importActual-fixture.test.ts'], + coverage: { + reporter: 'json', + include: ['fixtures/src/mock-target.ts'], + }, + }) + + const coverageMap = await readCoverageMap() + + // v8-browser and native runner report 100% due to different coverage collection behavior + if ((isBrowser() && isV8Provider()) || isNativeRunner()) { + expect(coverageMap).toMatchInlineSnapshot(` + { + "branches": "0/0 (100%)", + "functions": "2/2 (100%)", + "lines": "2/2 (100%)", + "statements": "2/2 (100%)", + } + `) + } + else { + expect(coverageMap).toMatchInlineSnapshot(` + { + "branches": "0/0 (100%)", + "functions": "1/2 (50%)", + "lines": "1/2 (50%)", + "statements": "1/2 (50%)", + } + `) + + const coverage = coverageMap.fileCoverageFor('/fixtures/src/mock-target.ts') + const functionCoverage = Object.keys(coverage.fnMap) + .map(index => ({ name: coverage.fnMap[index].name, hits: coverage.f[index] })) + .sort((a, b) => a.name.localeCompare(b.name)) + + expect(functionCoverage).toMatchInlineSnapshot(` + [ + { + "hits": 1, + "name": "double", + }, + { + "hits": 0, + "name": "triple", + }, + ] + `) + } +}) diff --git a/test/coverage-test/vitest.config.ts b/test/coverage-test/vitest.config.ts index f75455f42e3b..2afd3dcfa879 100644 --- a/test/coverage-test/vitest.config.ts +++ b/test/coverage-test/vitest.config.ts @@ -90,6 +90,8 @@ export default defineConfig({ '**/query-param-transforms.test.ts', '**/test/cjs-dependency.test.ts', '**/test/source-maps.test.ts', + '**/test/mock-autospy.test.ts', + '**/test/mock-importActual.test.ts', ], exclude: [FIXTURES], }, @@ -122,6 +124,8 @@ export default defineConfig({ '**/query-param-transforms.test.ts', '**/test/cjs-dependency.test.ts', '**/test/source-maps.test.ts', + '**/test/mock-autospy.test.ts', + '**/test/mock-importActual.test.ts', ], exclude: [FIXTURES], },