mirror of
https://github.com/vitest-dev/vitest.git
synced 2026-02-01 17:36:51 +00:00
fix(browser): improve error handling and don't rely on Node.js builtin modules in browser mode (#4244)
This commit is contained in:
parent
c05b11a0fa
commit
e7e8c3cc0d
@ -85,7 +85,8 @@
|
||||
]
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@types/chai@4.3.6": "patches/@types__chai@4.3.6.patch"
|
||||
"@types/chai@4.3.6": "patches/@types__chai@4.3.6.patch",
|
||||
"@sinonjs/fake-timers@11.1.0": "patches/@sinonjs__fake-timers@11.1.0.patch"
|
||||
}
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
|
||||
@ -57,12 +57,47 @@ async function loadConfig() {
|
||||
throw new Error('cannot load configuration after 5 retries')
|
||||
}
|
||||
|
||||
function on(event: string, listener: (...args: any[]) => void) {
|
||||
window.addEventListener(event, listener)
|
||||
return () => window.removeEventListener(event, listener)
|
||||
}
|
||||
|
||||
// we can't import "processError" yet because error might've been thrown before the module was loaded
|
||||
async function defaultErrorReport(type: string, unhandledError: any) {
|
||||
const error = {
|
||||
...unhandledError,
|
||||
name: unhandledError.name,
|
||||
message: unhandledError.message,
|
||||
stack: unhandledError.stack,
|
||||
}
|
||||
await client.rpc.onUnhandledError(error, type)
|
||||
await client.rpc.onDone(testId)
|
||||
}
|
||||
|
||||
const stopErrorHandler = on('error', e => defaultErrorReport('Error', e.error))
|
||||
const stopRejectionHandler = on('unhandledrejection', e => defaultErrorReport('Unhandled Rejection', e.reason))
|
||||
|
||||
let runningTests = false
|
||||
|
||||
async function reportUnexpectedError(rpc: typeof client.rpc, type: string, error: any) {
|
||||
const { processError } = await importId('vitest/browser') as typeof import('vitest/browser')
|
||||
await rpc.onUnhandledError(processError(error), type)
|
||||
if (!runningTests)
|
||||
await rpc.onDone(testId)
|
||||
}
|
||||
|
||||
ws.addEventListener('open', async () => {
|
||||
await loadConfig()
|
||||
|
||||
const { getSafeTimers } = await importId('vitest/utils') as typeof import('vitest/utils')
|
||||
const safeRpc = createSafeRpc(client, getSafeTimers)
|
||||
|
||||
stopErrorHandler()
|
||||
stopRejectionHandler()
|
||||
|
||||
on('error', event => reportUnexpectedError(safeRpc, 'Error', event.error))
|
||||
on('unhandledrejection', event => reportUnexpectedError(safeRpc, 'Unhandled Rejection', event.reason))
|
||||
|
||||
// @ts-expect-error untyped global for internal use
|
||||
globalThis.__vitest_browser__ = true
|
||||
// @ts-expect-error mocking vitest apis
|
||||
@ -134,10 +169,14 @@ async function runTests(paths: string[], config: ResolvedConfig) {
|
||||
const now = `${new Date().getTime()}`
|
||||
files.forEach(i => browserHashMap.set(i, [true, now]))
|
||||
|
||||
runningTests = true
|
||||
|
||||
for (const file of files)
|
||||
await startTests([file], runner)
|
||||
}
|
||||
finally {
|
||||
runningTests = false
|
||||
|
||||
await rpcDone()
|
||||
await rpc().onDone(testId)
|
||||
}
|
||||
|
||||
@ -6,24 +6,19 @@ import type { VitestClient } from '@vitest/ws-client'
|
||||
const { get } = Reflect
|
||||
|
||||
function withSafeTimers(getTimers: typeof getSafeTimers, fn: () => void) {
|
||||
const { setTimeout, clearTimeout, nextTick, setImmediate, clearImmediate } = getTimers()
|
||||
const { setTimeout, clearTimeout, setImmediate, clearImmediate } = getTimers()
|
||||
|
||||
const currentSetTimeout = globalThis.setTimeout
|
||||
const currentClearTimeout = globalThis.clearTimeout
|
||||
const currentSetImmediate = globalThis.setImmediate
|
||||
const currentClearImmediate = globalThis.clearImmediate
|
||||
|
||||
const currentNextTick = globalThis.process?.nextTick
|
||||
|
||||
try {
|
||||
globalThis.setTimeout = setTimeout
|
||||
globalThis.clearTimeout = clearTimeout
|
||||
globalThis.setImmediate = setImmediate
|
||||
globalThis.clearImmediate = clearImmediate
|
||||
|
||||
if (globalThis.process)
|
||||
globalThis.process.nextTick = nextTick
|
||||
|
||||
const result = fn()
|
||||
return result
|
||||
}
|
||||
@ -32,12 +27,6 @@ function withSafeTimers(getTimers: typeof getSafeTimers, fn: () => void) {
|
||||
globalThis.clearTimeout = currentClearTimeout
|
||||
globalThis.setImmediate = currentSetImmediate
|
||||
globalThis.clearImmediate = currentClearImmediate
|
||||
|
||||
if (globalThis.process) {
|
||||
nextTick(() => {
|
||||
globalThis.process.nextTick = currentNextTick
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
"chai": "^4.3.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.6",
|
||||
"@types/chai": "4.3.6",
|
||||
"@vitest/runner": "workspace:*",
|
||||
"picocolors": "^1.0.0",
|
||||
"rollup-plugin-copy": "^3.5.0"
|
||||
|
||||
@ -3,4 +3,5 @@ export { test, it, describe, suite, getCurrentSuite, createTaskCollector } from
|
||||
export { beforeAll, beforeEach, afterAll, afterEach, onTestFailed } from './hooks'
|
||||
export { setFn, getFn } from './map'
|
||||
export { getCurrentTest } from './test-state'
|
||||
export { processError } from '@vitest/utils/error'
|
||||
export * from './types'
|
||||
|
||||
@ -172,7 +172,7 @@
|
||||
"@ampproject/remapping": "^2.2.1",
|
||||
"@antfu/install-pkg": "^0.1.1",
|
||||
"@edge-runtime/vm": "3.0.3",
|
||||
"@sinonjs/fake-timers": "^11.0.0",
|
||||
"@sinonjs/fake-timers": "11.1.0",
|
||||
"@types/diff": "^5.0.3",
|
||||
"@types/estree": "^1.0.1",
|
||||
"@types/istanbul-lib-coverage": "^2.0.4",
|
||||
|
||||
@ -40,6 +40,9 @@ export function setup(vitestOrWorkspace: Vitest | WorkspaceProject, server?: Vit
|
||||
function setupClient(ws: WebSocket) {
|
||||
const rpc = createBirpc<WebSocketEvents, WebSocketHandlers>(
|
||||
{
|
||||
async onUnhandledError(error, type) {
|
||||
ctx.state.catchError(error, type)
|
||||
},
|
||||
async onDone(testId) {
|
||||
return ctx.state.browserTestPromises.get(testId)?.resolve(true)
|
||||
},
|
||||
|
||||
@ -7,6 +7,7 @@ export interface TransformResultWithSource extends TransformResult {
|
||||
}
|
||||
|
||||
export interface WebSocketHandlers {
|
||||
onUnhandledError(error: unknown, type: string): Promise<void>
|
||||
onCollected(files?: File[]): Promise<void>
|
||||
onTaskUpdate(packs: TaskResultPack[]): void
|
||||
onAfterSuiteRun(meta: AfterSuiteRunMeta): void
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export { startTests } from '@vitest/runner'
|
||||
export { startTests, processError } from '@vitest/runner'
|
||||
export { setupCommonEnv, loadDiffConfig } from './runtime/setup.common'
|
||||
export { takeCoverageInsideWorker, stopCoverageInsideWorker, getCoverageProvider, startCoverageInsideWorker } from './integrations/coverage'
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { isatty } from 'node:tty'
|
||||
import { createRequire } from 'node:module'
|
||||
import util from 'node:util'
|
||||
import timers from 'node:timers'
|
||||
import { performance } from 'node:perf_hooks'
|
||||
import { startTests } from '@vitest/runner'
|
||||
import { createColors, setupColors } from '@vitest/utils'
|
||||
@ -36,6 +38,12 @@ export async function run(files: string[], config: ResolvedConfig, executor: Vit
|
||||
_require.extensions['.less'] = () => ({})
|
||||
}
|
||||
|
||||
// @ts-expect-error not typed global for patched timers
|
||||
globalThis.__vitest_required__ = {
|
||||
util,
|
||||
timers,
|
||||
}
|
||||
|
||||
await startCoverageInsideWorker(config.coverage, executor)
|
||||
|
||||
if (config.chaiConfig)
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { createRequire } from 'node:module'
|
||||
import util from 'node:util'
|
||||
import timers from 'node:timers'
|
||||
import { isatty } from 'node:tty'
|
||||
import { installSourcemapsSupport } from 'vite-node/source-map'
|
||||
import { createColors, setupColors } from '@vitest/utils'
|
||||
@ -43,6 +45,12 @@ export async function setupGlobalEnv(config: ResolvedConfig, { environment }: Re
|
||||
process.env.SSR = '1'
|
||||
}
|
||||
|
||||
// @ts-expect-error not typed global for patched timers
|
||||
globalThis.__vitest_required__ = {
|
||||
util,
|
||||
timers,
|
||||
}
|
||||
|
||||
installSourcemapsSupport({
|
||||
getSourceMap: source => state.moduleCache.getSourceMap(source),
|
||||
})
|
||||
|
||||
25
patches/@sinonjs__fake-timers@11.1.0.patch
Normal file
25
patches/@sinonjs__fake-timers@11.1.0.patch
Normal file
@ -0,0 +1,25 @@
|
||||
diff --git a/src/fake-timers-src.js b/src/fake-timers-src.js
|
||||
index 607336d6a9c568a32b0cde4499c8fd56f06d424a..35187b0ee298df858118494b5a9b3e5efa8197b0 100644
|
||||
--- a/src/fake-timers-src.js
|
||||
+++ b/src/fake-timers-src.js
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
const globalObject = require("@sinonjs/commons").global;
|
||||
let timersModule;
|
||||
-if (typeof require === "function" && typeof module === "object") {
|
||||
+if (typeof __vitest_required__ !== 'undefined') {
|
||||
try {
|
||||
- timersModule = require("timers");
|
||||
+ timersModule = __vitest_required__.timers;
|
||||
} catch (e) {
|
||||
// ignored
|
||||
}
|
||||
@@ -159,7 +159,7 @@ function withGlobal(_global) {
|
||||
hrtimePresent && typeof _global.process.hrtime.bigint === "function";
|
||||
const nextTickPresent =
|
||||
_global.process && typeof _global.process.nextTick === "function";
|
||||
- const utilPromisify = _global.process && require("util").promisify;
|
||||
+ const utilPromisify = _global.process && _global.__vitest_required__ && _global.__vitest_required__.util.promisify;
|
||||
const performancePresent =
|
||||
_global.performance && typeof _global.performance.now === "function";
|
||||
const hasPerformancePrototype =
|
||||
77
pnpm-lock.yaml
generated
77
pnpm-lock.yaml
generated
@ -9,6 +9,9 @@ overrides:
|
||||
vitest: workspace:*
|
||||
|
||||
patchedDependencies:
|
||||
'@sinonjs/fake-timers@11.1.0':
|
||||
hash: trok5obk3l5tdlygozv34fknii
|
||||
path: patches/@sinonjs__fake-timers@11.1.0.patch
|
||||
'@types/chai@4.3.6':
|
||||
hash: s5kzatt2y2dzfxfynxzvzt5kbm
|
||||
path: patches/@types__chai@4.3.6.patch
|
||||
@ -1082,7 +1085,7 @@ importers:
|
||||
version: 4.3.10
|
||||
devDependencies:
|
||||
'@types/chai':
|
||||
specifier: ^4.3.6
|
||||
specifier: 4.3.6
|
||||
version: 4.3.6(patch_hash=s5kzatt2y2dzfxfynxzvzt5kbm)
|
||||
'@vitest/runner':
|
||||
specifier: workspace:*
|
||||
@ -1372,8 +1375,8 @@ importers:
|
||||
specifier: 3.0.3
|
||||
version: 3.0.3
|
||||
'@sinonjs/fake-timers':
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0
|
||||
specifier: 11.1.0
|
||||
version: 11.1.0(patch_hash=trok5obk3l5tdlygozv34fknii)
|
||||
'@types/diff':
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3
|
||||
@ -1397,7 +1400,7 @@ importers:
|
||||
version: 2.4.4
|
||||
'@types/sinonjs__fake-timers':
|
||||
specifier: ^8.1.2
|
||||
version: 8.1.2
|
||||
version: 8.1.3
|
||||
birpc:
|
||||
specifier: 0.2.14
|
||||
version: 0.2.14
|
||||
@ -2775,24 +2778,6 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/helper-create-class-features-plugin@7.22.15(@babel/core@7.22.9):
|
||||
resolution: {integrity: sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
dependencies:
|
||||
'@babel/core': 7.22.9
|
||||
'@babel/helper-annotate-as-pure': 7.22.5
|
||||
'@babel/helper-environment-visitor': 7.22.20
|
||||
'@babel/helper-function-name': 7.23.0
|
||||
'@babel/helper-member-expression-to-functions': 7.22.15
|
||||
'@babel/helper-optimise-call-expression': 7.22.5
|
||||
'@babel/helper-replace-supers': 7.22.9(@babel/core@7.22.9)
|
||||
'@babel/helper-skip-transparent-expression-wrappers': 7.22.5
|
||||
'@babel/helper-split-export-declaration': 7.22.6
|
||||
semver: 6.3.1
|
||||
dev: true
|
||||
|
||||
/@babel/helper-create-class-features-plugin@7.22.15(@babel/core@7.23.0):
|
||||
resolution: {integrity: sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@ -3185,18 +3170,6 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/helper-replace-supers@7.22.9(@babel/core@7.22.9):
|
||||
resolution: {integrity: sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
dependencies:
|
||||
'@babel/core': 7.22.9
|
||||
'@babel/helper-environment-visitor': 7.22.20
|
||||
'@babel/helper-member-expression-to-functions': 7.22.15
|
||||
'@babel/helper-optimise-call-expression': 7.22.5
|
||||
dev: true
|
||||
|
||||
/@babel/helper-replace-supers@7.22.9(@babel/core@7.23.0):
|
||||
resolution: {integrity: sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@ -4574,16 +4547,6 @@ packages:
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
dev: true
|
||||
|
||||
/@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.22.9):
|
||||
resolution: {integrity: sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.22.9
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
dev: true
|
||||
|
||||
/@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.23.0):
|
||||
resolution: {integrity: sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@ -5819,19 +5782,6 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/plugin-transform-typescript@7.22.15(@babel/core@7.22.9):
|
||||
resolution: {integrity: sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.22.9
|
||||
'@babel/helper-annotate-as-pure': 7.22.5
|
||||
'@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.22.9)
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
'@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.22.9)
|
||||
dev: true
|
||||
|
||||
/@babel/plugin-transform-typescript@7.22.15(@babel/core@7.23.0):
|
||||
resolution: {integrity: sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@ -6285,7 +6235,9 @@ packages:
|
||||
'@babel/core': 7.22.9
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
'@babel/helper-validator-option': 7.22.5
|
||||
'@babel/plugin-transform-typescript': 7.22.15(@babel/core@7.22.9)
|
||||
'@babel/plugin-transform-typescript': 7.21.3(@babel/core@7.22.9)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/register@7.18.9(@babel/core@7.22.9):
|
||||
@ -8495,11 +8447,12 @@ packages:
|
||||
type-detect: 4.0.8
|
||||
dev: true
|
||||
|
||||
/@sinonjs/fake-timers@11.0.0:
|
||||
resolution: {integrity: sha512-bqiI/5ur6ZOozG06BeJjbplIqHY/KftV1zaewbZHORH902GrHURKwl7H1G/4OC5EaxDYQJlrD0OLJ1XD6x01dQ==}
|
||||
/@sinonjs/fake-timers@11.1.0(patch_hash=trok5obk3l5tdlygozv34fknii):
|
||||
resolution: {integrity: sha512-pUBaWhXoa9N0R/LeYKLqkrN9mqN3jwKBeMfbvlRtHUzLmk55o+0swncIuZBcSH/PpXDttRf/AcPF22pknAzORQ==}
|
||||
dependencies:
|
||||
'@sinonjs/commons': 3.0.0
|
||||
dev: true
|
||||
patched: true
|
||||
|
||||
/@sinonjs/fake-timers@8.1.0:
|
||||
resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==}
|
||||
@ -10615,8 +10568,8 @@ packages:
|
||||
resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==}
|
||||
dev: true
|
||||
|
||||
/@types/sinonjs__fake-timers@8.1.2:
|
||||
resolution: {integrity: sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==}
|
||||
/@types/sinonjs__fake-timers@8.1.3:
|
||||
resolution: {integrity: sha512-4g+2YyWe0Ve+LBh+WUm1697PD0Kdi6coG1eU0YjQbwx61AZ8XbEpL1zIT6WjuUKrCMCROpEaYQPDjBnDouBVAQ==}
|
||||
dev: true
|
||||
|
||||
/@types/sizzle@2.3.3:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user