diff --git a/packages/browser/src/client/public/error-catcher.js b/packages/browser/src/client/public/error-catcher.js index a1326694e987..1e8a042cc115 100644 --- a/packages/browser/src/client/public/error-catcher.js +++ b/packages/browser/src/client/public/error-catcher.js @@ -56,7 +56,9 @@ async function reportUnexpectedError( error, ) { const processedError = serializeError(error) - await client.rpc.onUnhandledError(processedError, type) + await client.waitForConnection().then(() => { + return client.rpc.onUnhandledError(processedError, type) + }).catch(console.error) const state = __vitest_browser_runner__ if (state.type === 'orchestrator') { diff --git a/packages/browser/src/node/serverTester.ts b/packages/browser/src/node/serverTester.ts index 0773787cd67e..18ea0c0d99ee 100644 --- a/packages/browser/src/node/serverTester.ts +++ b/packages/browser/src/node/serverTester.ts @@ -60,6 +60,7 @@ export async function resolveTester( try { const indexhtml = await server.vite.transformIndexHtml(url.pathname, testerHtml) return replacer(indexhtml, { + __VITEST_FAVICON__: server.faviconUrl, __VITEST_INJECTOR__: injector, __VITEST_APPEND__: ` __vitest_browser_runner__.runningFiles = ${tests} diff --git a/packages/ui/package.json b/packages/ui/package.json index 8b8552d70b8c..4dd3069e4cf2 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -76,6 +76,7 @@ "codemirror-theme-vars": "^0.1.2", "d3-graph-controller": "^3.0.11", "floating-vue": "^5.2.2", + "rollup": "^4.24.0", "splitpanes": "^3.1.5", "unocss": "^0.63.6", "unplugin-auto-import": "^0.18.3", diff --git a/packages/vite-node/src/client.ts b/packages/vite-node/src/client.ts index 84aed6696067..db67097184d7 100644 --- a/packages/vite-node/src/client.ts +++ b/packages/vite-node/src/client.ts @@ -1,12 +1,10 @@ import type { HotContext, ModuleCache, ViteNodeRunnerOptions } from './types' import { createRequire } from 'node:module' -// we need native dirname, because windows __dirname has \\ -import { dirname } from 'node:path' +import { dirname, resolve } from 'node:path' import { fileURLToPath, pathToFileURL } from 'node:url' import vm from 'node:vm' import createDebug from 'debug' -import { resolve } from 'pathe' import { extractSourceMap } from './source-map' import { cleanUrl, diff --git a/packages/vite-node/src/utils.ts b/packages/vite-node/src/utils.ts index a4e5b5bc6586..edd2896c3b61 100644 --- a/packages/vite-node/src/utils.ts +++ b/packages/vite-node/src/utils.ts @@ -28,7 +28,9 @@ export function normalizeRequestId(id: string, base?: string): string { id = `/${id.slice(base.length)}` } - // keep drive the same as in process cwd + // keep drive the same as in process cwd. ideally, this should be resolved on Vite side + // Vite always resolves drive letters to the upper case because of the use of `realpathSync` + // https://github.com/vitejs/vite/blob/0ab20a3ee26eacf302415b3087732497d0a2f358/packages/vite/src/node/utils.ts#L635 if (driveRegexp && !driveRegexp?.test(id) && driveOppositeRegext?.test(id)) { id = id.replace(driveOppositeRegext, `${drive}$1`) } diff --git a/packages/vitest/src/node/plugins/mocks.ts b/packages/vitest/src/node/plugins/mocks.ts index 865496d762f9..e9def6730850 100644 --- a/packages/vitest/src/node/plugins/mocks.ts +++ b/packages/vitest/src/node/plugins/mocks.ts @@ -1,13 +1,15 @@ import type { Plugin } from 'vite' import { automockPlugin, hoistMocksPlugin } from '@vitest/mocker/node' +import { normalize } from 'pathe' import { distDir } from '../../paths' import { generateCodeFrame } from '../error' export function MocksPlugins(): Plugin[] { + const normalizedDistDir = normalize(distDir) return [ hoistMocksPlugin({ filter(id) { - if (id.includes(distDir)) { + if (id.includes(normalizedDistDir)) { return false } return true diff --git a/packages/vitest/src/node/pools/forks.ts b/packages/vitest/src/node/pools/forks.ts index 0eaaf1851480..cf925eb421fb 100644 --- a/packages/vitest/src/node/pools/forks.ts +++ b/packages/vitest/src/node/pools/forks.ts @@ -7,9 +7,9 @@ import type { SerializedConfig } from '../types/config' import type { WorkspaceProject } from '../workspace' import EventEmitter from 'node:events' import * as nodeos from 'node:os' +import { resolve } from 'node:path' import v8 from 'node:v8' import { createBirpc } from 'birpc' -import { resolve } from 'pathe' import { Tinypool } from 'tinypool' import { groupBy } from '../../utils/base' import { wrapSerializableConfig } from '../../utils/config-helpers' diff --git a/packages/vitest/src/node/pools/threads.ts b/packages/vitest/src/node/pools/threads.ts index 6199231a84a6..4d443975057a 100644 --- a/packages/vitest/src/node/pools/threads.ts +++ b/packages/vitest/src/node/pools/threads.ts @@ -7,9 +7,9 @@ import type { SerializedConfig } from '../types/config' import type { WorkerContext } from '../types/worker' import type { WorkspaceProject } from '../workspace' import * as nodeos from 'node:os' +import { resolve } from 'node:path' import { MessageChannel } from 'node:worker_threads' import { createBirpc } from 'birpc' -import { resolve } from 'pathe' import Tinypool from 'tinypool' import { groupBy } from '../../utils/base' import { envsOrder, groupFilesByEnv } from '../../utils/test-helpers' diff --git a/packages/vitest/src/node/pools/vmForks.ts b/packages/vitest/src/node/pools/vmForks.ts index 78306a98763a..14cd5881a35f 100644 --- a/packages/vitest/src/node/pools/vmForks.ts +++ b/packages/vitest/src/node/pools/vmForks.ts @@ -7,9 +7,9 @@ import type { ResolvedConfig, SerializedConfig } from '../types/config' import type { WorkspaceProject } from '../workspace' import EventEmitter from 'node:events' import * as nodeos from 'node:os' +import { resolve } from 'node:path' import v8 from 'node:v8' import { createBirpc } from 'birpc' -import { resolve } from 'pathe' import Tinypool from 'tinypool' import { rootDir } from '../../paths' import { wrapSerializableConfig } from '../../utils/config-helpers' diff --git a/packages/vitest/src/node/pools/vmThreads.ts b/packages/vitest/src/node/pools/vmThreads.ts index 040537e1a868..01420cfc87be 100644 --- a/packages/vitest/src/node/pools/vmThreads.ts +++ b/packages/vitest/src/node/pools/vmThreads.ts @@ -7,9 +7,9 @@ import type { ResolvedConfig, SerializedConfig } from '../types/config' import type { WorkerContext } from '../types/worker' import type { WorkspaceProject } from '../workspace' import * as nodeos from 'node:os' +import { resolve } from 'node:path' import { MessageChannel } from 'node:worker_threads' import { createBirpc } from 'birpc' -import { resolve } from 'pathe' import Tinypool from 'tinypool' import { rootDir } from '../../paths' import { getWorkerMemoryLimit, stringToBytes } from '../../utils/memory-limit' diff --git a/packages/vitest/src/node/workspace.ts b/packages/vitest/src/node/workspace.ts index 17887cdc40b8..487fa5dc3709 100644 --- a/packages/vitest/src/node/workspace.ts +++ b/packages/vitest/src/node/workspace.ts @@ -18,7 +18,8 @@ import type { import { promises as fs } from 'node:fs' import { rm } from 'node:fs/promises' import { tmpdir } from 'node:os' -import { deepMerge, nanoid } from '@vitest/utils' +import path from 'node:path' +import { deepMerge, nanoid, slash } from '@vitest/utils' import fg from 'fast-glob' import mm from 'micromatch' import { @@ -27,7 +28,6 @@ import { join, relative, resolve, - toNamespacedPath, } from 'pathe' import { ViteNodeRunner } from 'vite-node/client' import { ViteNodeServer } from 'vite-node/server' @@ -302,7 +302,10 @@ export class WorkspaceProject { } const files = await fg(include, globOptions) - return files.map(file => resolve(cwd, file)) + // keep the slashes consistent with Vite + // we are not using the pathe here because it normalizes the drive letter on Windows + // and we want to keep it the same as working dir + return files.map(file => slash(path.resolve(cwd, file))) } async isTargetFile(id: string, source?: string): Promise { @@ -329,7 +332,7 @@ export class WorkspaceProject { filterFiles(testFiles: string[], filters: string[], dir: string) { if (filters.length && process.platform === 'win32') { - filters = filters.map(f => toNamespacedPath(f)) + filters = filters.map(f => slash(f)) } if (filters.length) { diff --git a/packages/vitest/src/paths.ts b/packages/vitest/src/paths.ts index 55197e204d9a..11ceb81bf9c6 100644 --- a/packages/vitest/src/paths.ts +++ b/packages/vitest/src/paths.ts @@ -1,5 +1,5 @@ +import { resolve } from 'node:path' import url from 'node:url' -import { resolve } from 'pathe' export const rootDir = resolve(url.fileURLToPath(import.meta.url), '../../') export const distDir = resolve( diff --git a/packages/vitest/src/runtime/execute.ts b/packages/vitest/src/runtime/execute.ts index 7b07b66bcaa7..a5a6802cb5d5 100644 --- a/packages/vitest/src/runtime/execute.ts +++ b/packages/vitest/src/runtime/execute.ts @@ -17,6 +17,8 @@ import { import { distDir } from '../paths' import { VitestMocker } from './mocker' +const normalizedDistDir = normalize(distDir) + const { readFileSync } = fs export interface ExecuteOptions extends ViteNodeRunnerOptions { @@ -112,7 +114,7 @@ export async function startVitestExecutor(options: ContextExecutorOptions) { } // always externalize Vitest because we import from there before running tests // so we already have it cached by Node.js - if (id.includes(distDir)) { + if (id.includes(distDir) || id.includes(normalizedDistDir)) { const { path } = toFilePath(id, state().config.root) const externalize = pathToFileURL(path).toString() externalizeMap.set(id, externalize) diff --git a/packages/vitest/src/runtime/mocker.ts b/packages/vitest/src/runtime/mocker.ts index c924d010b4d0..fe193f2eaf53 100644 --- a/packages/vitest/src/runtime/mocker.ts +++ b/packages/vitest/src/runtime/mocker.ts @@ -1,11 +1,11 @@ import type { ManualMockedModule, MockedModuleType } from '@vitest/mocker' import type { MockFactory, MockOptions, PendingSuiteMock } from '../types/mocker' import type { VitestExecutor } from './execute' +import { isAbsolute, resolve } from 'node:path' import vm from 'node:vm' import { AutomockedModule, MockerRegistry, mockObject, RedirectedModule } from '@vitest/mocker' import { findMockRedirect } from '@vitest/mocker/redirect' import { highlight } from '@vitest/utils' -import { isAbsolute, resolve } from 'pathe' import { distDir } from '../paths' const spyModulePath = resolve(distDir, 'spy.js') diff --git a/packages/vitest/src/runtime/runners/index.ts b/packages/vitest/src/runtime/runners/index.ts index 49a7e1a87e80..2ac31beba3c7 100644 --- a/packages/vitest/src/runtime/runners/index.ts +++ b/packages/vitest/src/runtime/runners/index.ts @@ -1,7 +1,7 @@ import type { VitestRunner, VitestRunnerConstructor } from '@vitest/runner' import type { SerializedConfig } from '../config' import type { VitestExecutor } from '../execute' -import { resolve } from 'pathe' +import { resolve } from 'node:path' import { takeCoverageInsideWorker } from '../../integrations/coverage' import { distDir } from '../../paths' import { rpc } from '../rpc' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77cca4916dce..b5c7bede3af1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -752,6 +752,9 @@ importers: floating-vue: specifier: ^5.2.2 version: 5.2.2(vue@3.5.12(typescript@5.6.3)) + rollup: + specifier: ^4.24.0 + version: 4.24.0 splitpanes: specifier: ^3.1.5 version: 3.1.5 diff --git a/test/benchmark/test/reporter.test.ts b/test/benchmark/test/reporter.test.ts index 8bb9dd2a2254..d118732ff71d 100644 --- a/test/benchmark/test/reporter.test.ts +++ b/test/benchmark/test/reporter.test.ts @@ -5,6 +5,7 @@ import { runVitest } from '../../test-utils' it('summary', async () => { const root = pathe.join(import.meta.dirname, '../fixtures/reporter') const result = await runVitest({ root }, ['summary.bench.ts'], 'benchmark') + expect(result.stderr).toBe('') expect(result.stdout).not.toContain('NaNx') expect(result.stdout.split('BENCH Summary')[1].replaceAll(/[0-9.]+x/g, '(?)')).toMatchSnapshot() }) diff --git a/test/cli/fixtures/windows-drive-case/basic.test.ts b/test/cli/fixtures/windows-drive-case/basic.test.ts new file mode 100644 index 000000000000..4fa6fcaa6e95 --- /dev/null +++ b/test/cli/fixtures/windows-drive-case/basic.test.ts @@ -0,0 +1,5 @@ +import { expect, test } from 'vitest' + +test('basic test', () => { + expect(1).toBe(1) +}) diff --git a/test/cli/test/public-api.test.ts b/test/cli/test/public-api.test.ts index e2a551d94bdc..93de5e5e1eb8 100644 --- a/test/cli/test/public-api.test.ts +++ b/test/cli/test/public-api.test.ts @@ -1,4 +1,5 @@ -import type { File, TaskResultPack, UserConfig } from 'vitest' +import type { RunnerTaskResultPack, RunnerTestFile } from 'vitest' +import type { UserConfig } from 'vitest/node' import { resolve } from 'pathe' import { expect, it } from 'vitest' import { runVitest } from '../../test-utils' @@ -15,15 +16,15 @@ it.each([ headless: true, }, }, -] as UserConfig[])('passes down metadata when $name', { timeout: 60_000, retry: 3 }, async (config) => { - const taskUpdate: TaskResultPack[] = [] - const finishedFiles: File[] = [] - const collectedFiles: File[] = [] +] as UserConfig[])('passes down metadata when $name', { timeout: 60_000, retry: 1 }, async (config) => { + const taskUpdate: RunnerTaskResultPack[] = [] + const finishedFiles: RunnerTestFile[] = [] + const collectedFiles: RunnerTestFile[] = [] const { ctx, stdout, stderr } = await runVitest({ root: resolve(__dirname, '..', 'fixtures', 'public-api'), include: ['**/*.spec.ts'], reporters: [ - 'verbose', + ['verbose', { isTTY: false }], { onTaskUpdate(packs) { taskUpdate.push(...packs.filter(i => i[1]?.state === 'pass')) diff --git a/test/cli/test/windows-drive-case.test.ts b/test/cli/test/windows-drive-case.test.ts new file mode 100644 index 000000000000..4b44695faa8f --- /dev/null +++ b/test/cli/test/windows-drive-case.test.ts @@ -0,0 +1,19 @@ +import { join } from 'pathe' +import { expect, test } from 'vitest' +import { runVitestCli } from '../../test-utils' + +const _DRIVE_LETTER_START_RE = /^[A-Z]:\//i +const root = join(import.meta.dirname, '../fixtures/windows-drive-case') +const cwd = root.replace(_DRIVE_LETTER_START_RE, r => r.toLowerCase()) + +test.runIf(process.platform === 'win32')(`works on windows with a lowercase drive: ${cwd}`, async () => { + const { stderr, stdout } = await runVitestCli({ + nodeOptions: { + cwd, + }, + }, '--no-watch') + + expect(cwd[0]).toEqual(cwd[0].toLowerCase()) + expect(stderr).toBe('') + expect(stdout).toContain('1 passed') +})