diff --git a/packages/vite-node/src/utils.ts b/packages/vite-node/src/utils.ts index b5c2d933d59e..d0b33edeb4cf 100644 --- a/packages/vite-node/src/utils.ts +++ b/packages/vite-node/src/utils.ts @@ -1,6 +1,7 @@ import { fileURLToPath, pathToFileURL } from 'url' -import { resolve } from 'pathe' +import { relative, resolve } from 'pathe' import type { TransformResult } from 'vite' +import { isNodeBuiltin } from 'mlly' import type { Arrayable, Nullable } from './types' export const isWindows = process.platform === 'win32' @@ -45,6 +46,34 @@ export function isPrimitive(v: any) { return v !== Object(v) } +export function pathFromRoot(root: string, filename: string) { + if (isNodeBuiltin(filename)) + return filename + + // don't replace with "/" on windows, "/C:/foo" is not a valid path + filename = filename.replace(/^\/@fs\//, isWindows ? '' : '/') + + if (!filename.startsWith(root)) + return filename + + const relativePath = relative(root, filename) + // foo.js -> /foo.js + if (!relativePath.startsWith('/') && !relativePath.startsWith('.')) + return `/${relativePath}` + + let index = 0 + for (const char of relativePath) { + // ../../foo.js + // ^ returns from here -> /foo.js + if (char !== '.' && char !== '/') + return relativePath.slice(index - 1) + + index++ + } + + return relativePath +} + export function toFilePath(id: string, root: string): string { let absolute = id.startsWith('/@fs/') ? id.slice(4) diff --git a/packages/vitest/src/runtime/mocker.ts b/packages/vitest/src/runtime/mocker.ts index d14183ba119c..03981177547c 100644 --- a/packages/vitest/src/runtime/mocker.ts +++ b/packages/vitest/src/runtime/mocker.ts @@ -1,9 +1,9 @@ import { existsSync, readdirSync } from 'fs' import { isNodeBuiltin } from 'mlly' import { basename, dirname, join, resolve } from 'pathe' -import { normalizeRequestId, toFilePath } from 'vite-node/utils' +import { normalizeRequestId, pathFromRoot, toFilePath } from 'vite-node/utils' import type { ModuleCacheMap } from 'vite-node/client' -import { getAllMockableProperties, getType, getWorkerState, isWindows, mergeSlashes, slash } from '../utils' +import { getAllMockableProperties, getType, getWorkerState, mergeSlashes, slash } from '../utils' import { distDir } from '../constants' import type { PendingSuiteMock } from '../types/mocker' import type { ExecuteOptions } from './execute' @@ -142,14 +142,14 @@ export class VitestMocker { } public normalizePath(path: string) { - return normalizeRequestId(path.replace(this.root, ''), this.base).replace(/^\/@fs\//, isWindows ? '' : '/') + return pathFromRoot(this.root, normalizeRequestId(path, this.base)) } public getFsPath(path: string, external: string | null) { if (external) return mergeSlashes(`/@fs/${path}`) - return normalizeRequestId(path.replace(this.root, '')) + return normalizeRequestId(path, this.base) } public resolveMockPath(mockPath: string, external: string | null) { diff --git a/test/vite-config/src/src.ts b/test/vite-config/src/src.ts new file mode 100644 index 000000000000..cb356468240d --- /dev/null +++ b/test/vite-config/src/src.ts @@ -0,0 +1 @@ +export const foo = 'foo' diff --git a/test/vite-config/src/test/root.test.ts b/test/vite-config/src/test/root.test.ts index eabf33a1e79e..5f0edf39e390 100644 --- a/test/vite-config/src/test/root.test.ts +++ b/test/vite-config/src/test/root.test.ts @@ -1,5 +1,12 @@ -import { it } from 'vitest' +import { expect, it, vi } from 'vitest' +import { foo } from '../src' + +vi.mock('../src.ts', () => ({ foo: 'baz' })) it('should work', () => { - // + expect(1).toBe(1) +}) + +it('mocking with root works', () => { + expect(foo).toBe('baz') })