[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

feat: web worker support (#726)

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>

authored by

Vladimir
Anthony Fu
and committed by
GitHub
(Mar 16, 2022, 10:58 PM UTC) 73753475 39883e7b

+842 -367
+32 -4
pnpm-lock.yaml
··· 142 142 lit: 2.2.1 143 143 devDependencies: 144 144 '@vitest/ui': link:../../packages/ui 145 - happy-dom: 2.47.2 145 + happy-dom: 2.49.0 146 146 vite: 2.8.6 147 147 vitest: link:../../packages/vitest 148 148 ··· 471 471 devDependencies: 472 472 '@vitejs/plugin-vue': 2.2.4_vite@2.8.6+vue@3.2.26 473 473 '@vue/test-utils': 2.0.0-rc.18_vue@3.2.26 474 - happy-dom: 2.47.2 474 + happy-dom: 2.49.0 475 475 unplugin-auto-import: 0.6.6_2567a1d8483486229eeea0338e7e9b48 476 476 unplugin-vue-components: 0.18.0_5f389c9a2ae06d3bb3a1d5490dc2bafe 477 477 vitest: link:../../packages/vitest ··· 488 488 devDependencies: 489 489 '@vitejs/plugin-vue': 2.2.4_vite@2.8.6+vue@3.2.31 490 490 '@vue/test-utils': 2.0.0-rc.18_vue@3.2.31 491 - happy-dom: 2.47.2 491 + happy-dom: 2.49.0 492 492 vitest: link:../../packages/vitest 493 493 494 494 examples/vue-jsx: ··· 504 504 '@vitejs/plugin-vue': 2.2.4_vite@2.8.6+vue@3.2.26 505 505 '@vitejs/plugin-vue-jsx': 1.3.8 506 506 '@vue/test-utils': 2.0.0-rc.18_vue@3.2.26 507 - happy-dom: 2.47.2 507 + happy-dom: 2.49.0 508 508 vite: 2.8.6 509 509 vitest: link:../../packages/vitest 510 510 vue: 3.2.26 ··· 674 674 vite-node: link:../vite-node 675 675 ws: 8.5.0 676 676 677 + packages/web-worker: 678 + specifiers: 679 + rollup: ^2.67.2 680 + devDependencies: 681 + rollup: 2.70.1 682 + 677 683 packages/ws-client: 678 684 specifiers: 679 685 birpc: ^0.1.0 ··· 775 781 776 782 test/snapshots: 777 783 specifiers: {} 784 + 785 + test/web-worker: 786 + specifiers: 787 + '@vitest/web-worker': workspace:* 788 + vitest: workspace:* 789 + devDependencies: 790 + '@vitest/web-worker': link:../../packages/web-worker 791 + vitest: link:../../packages/vitest 778 792 779 793 packages: 780 794 ··· 12401 12415 12402 12416 /happy-dom/2.47.2: 12403 12417 resolution: {integrity: sha512-AuB34J7tZGHHdgGcES4SuUntaVqsWpHUiRfl2j/HCYBY+XsAPUcysadrBWGbRMGkEgCCIR7zet+I3V9pNB3wXA==} 12418 + dependencies: 12419 + css.escape: 1.5.1 12420 + he: 1.2.0 12421 + node-fetch: 2.6.7 12422 + sync-request: 6.1.0 12423 + webidl-conversions: 7.0.0 12424 + whatwg-encoding: 1.0.5 12425 + whatwg-mimetype: 2.3.0 12426 + transitivePeerDependencies: 12427 + - encoding 12428 + dev: true 12429 + 12430 + /happy-dom/2.49.0: 12431 + resolution: {integrity: sha512-mnPY9LmumUs8EmKyAQjutmFn/XzafvQeQ65w7MsZlHqG6OH3MratBzS0N4AAmuB3+F51KFUbUKNF763i3ZV19Q==} 12404 12432 dependencies: 12405 12433 css.escape: 1.5.1 12406 12434 he: 1.2.0
+60
packages/web-worker/README.md
··· 1 + # @vitest/addon-web-worker 2 + 3 + > Web Worker support for Vitest testing. Doesn't require JSDom. 4 + 5 + Simulates Web Worker, but in the same thread. Supports both `new Worker(url)` and `import from './worker?worker`. 6 + 7 + ## Installing 8 + 9 + ```sh 10 + # with npm 11 + npm install -D @vitest/addon-web-worker 12 + 13 + # with pnpm 14 + pnpm install -D @vitest/addon-web-worker 15 + 16 + # with yarn 17 + yarn install -D @vitest/addon-web-worker 18 + ``` 19 + 20 + ## Usage 21 + 22 + Just import `@vitest/addon-web-worker` in your test file to test only in current suite. 23 + 24 + Or add `@vitest/addon-web-worker` in your `setupFiles`, if you want to have a global support. 25 + 26 + ```ts 27 + import { defineConfig } from 'vitest/node' 28 + 29 + export default defineConfig({ 30 + test: { 31 + setupFiles: ['@vitest/addon-web-worker'], 32 + }, 33 + }) 34 + ``` 35 + 36 + ## Examples 37 + 38 + ```ts 39 + // worker.ts 40 + import '@vitest/addon-web-worker' 41 + import MyWorker from '../worker?worker' 42 + 43 + self.onmessage = (e) => { 44 + self.postMessage(`${e.data} world`) 45 + } 46 + 47 + // worker.test.ts 48 + let worker = new MyWorker() 49 + // new Worker is also supported 50 + worker = new Worker(new URL('../src/worker.ts', import.meta.url)) 51 + 52 + worker.postMessage('hello') 53 + worker.onmessage = (e) => { 54 + // e.data equals to 'hello world' 55 + } 56 + ``` 57 + 58 + ## Notice 59 + 60 + - Does not support `onmessage = () => {}`. Please, use `self.onmessage = () => {}`.
+1
packages/web-worker/index.d.ts
··· 1 +
+37
packages/web-worker/package.json
··· 1 + { 2 + "name": "@vitest/web-worker", 3 + "version": "1.0.0", 4 + "description": "Web Worker support for testing in Vitest", 5 + "type": "module", 6 + "files": [ 7 + "dist", 8 + "*.d.ts" 9 + ], 10 + "exports": { 11 + ".": { 12 + "import": "./dist/index.js", 13 + "default": "./dist/index.js", 14 + "types": "./index.d.ts" 15 + }, 16 + "./pure": { 17 + "import": "./dist/pure.js", 18 + "default": "./dist/pure.js", 19 + "types": "./pure.d.ts" 20 + } 21 + }, 22 + "main": "./dist/index.js", 23 + "module": "./dist/index.js", 24 + "scripts": { 25 + "build": "rimraf dist && rollup -c", 26 + "dev": "rollup -c --watch --watch.include=src/**", 27 + "prepublishOnly": "nr build", 28 + "typecheck": "tsc --noEmit" 29 + }, 30 + "types": "./index.d.ts", 31 + "peerDependencies": { 32 + "vitest": "*" 33 + }, 34 + "devDependencies": { 35 + "rollup": "^2.67.2" 36 + } 37 + }
+3
packages/web-worker/pure.d.ts
··· 1 + declare function defineWebWorker(): void; 2 + 3 + export { defineWebWorker };
+54
packages/web-worker/rollup.config.js
··· 1 + import esbuild from 'rollup-plugin-esbuild' 2 + import dts from 'rollup-plugin-dts' 3 + import commonjs from '@rollup/plugin-commonjs' 4 + import json from '@rollup/plugin-json' 5 + import alias from '@rollup/plugin-alias' 6 + import pkg from './package.json' 7 + 8 + const entries = { 9 + index: 'src/index.ts', 10 + pure: 'src/pure.ts', 11 + } 12 + 13 + const external = [ 14 + ...Object.keys(pkg.dependencies || {}), 15 + ...Object.keys(pkg.peerDependencies || {}), 16 + 'vitest', 17 + ] 18 + 19 + const plugins = [ 20 + alias({ 21 + entries: [ 22 + { find: /^node:(.+)$/, replacement: '$1' }, 23 + ], 24 + }), 25 + json(), 26 + commonjs(), 27 + esbuild({ 28 + target: 'node14', 29 + }), 30 + ] 31 + 32 + export default () => [ 33 + { 34 + input: entries, 35 + output: { 36 + dir: 'dist', 37 + format: 'esm', 38 + }, 39 + external, 40 + plugins, 41 + }, 42 + { 43 + input: entries, 44 + output: { 45 + dir: process.cwd(), 46 + entryFileNames: '[name].d.ts', 47 + format: 'esm', 48 + }, 49 + external, 50 + plugins: [ 51 + dts({ respectExternal: true }), 52 + ], 53 + }, 54 + ]
+4
packages/web-worker/tsconfig.json
··· 1 + { 2 + "extends": "../../tsconfig.json", 3 + "exclude": ["./dist"] 4 + }
+13
test/web-worker/package.json
··· 1 + { 2 + "name": "@vitest/test-global-setup", 3 + "private": true, 4 + "type": "module", 5 + "scripts": { 6 + "test": "vitest", 7 + "coverage": "vitest run --coverage" 8 + }, 9 + "devDependencies": { 10 + "@vitest/web-worker": "workspace:*", 11 + "vitest": "workspace:*" 12 + } 13 + }
+1
test/web-worker/setup.ts
··· 1 + import '@vitest/web-worker'
+15
test/web-worker/vitest.config.ts
··· 1 + import { defineConfig } from 'vite' 2 + 3 + export default defineConfig({ 4 + test: { 5 + environment: 'jsdom', 6 + setupFiles: [ 7 + './setup.ts', 8 + ], 9 + deps: { 10 + external: [ 11 + /packages\/web-worker/, 12 + ], 13 + }, 14 + }, 15 + })
+3
packages/web-worker/src/index.ts
··· 1 + import { defineWebWorker } from './pure' 2 + 3 + defineWebWorker()
+177
packages/web-worker/src/pure.ts
··· 1 + /* eslint-disable no-restricted-imports */ 2 + 3 + import { VitestRunner } from 'vitest/node' 4 + import type { WorkerGlobalState } from 'vitest' 5 + 6 + function getWorkerState(): WorkerGlobalState { 7 + // @ts-expect-error untyped global 8 + return globalThis.__vitest_worker__ 9 + } 10 + 11 + type Procedure = (...args: any[]) => void 12 + 13 + class Bridge { 14 + private callbacks: Record<string, Procedure[]> = {} 15 + 16 + public on(event: string, fn: Procedure) { 17 + this.callbacks[event] ??= [] 18 + this.callbacks[event].push(fn) 19 + } 20 + 21 + public off(event: string, fn: Procedure) { 22 + if (this.callbacks[event]) 23 + this.callbacks[event] = this.callbacks[event].filter(f => f !== fn) 24 + } 25 + 26 + public removeEvents(event: string) { 27 + this.callbacks[event] = [] 28 + } 29 + 30 + public clear() { 31 + this.callbacks = {} 32 + } 33 + 34 + public emit(event: string, ...data: any[]) { 35 + return (this.callbacks[event] || []).map(fn => fn(...data)) 36 + } 37 + } 38 + 39 + interface InlineWorkerContext { 40 + onmessage: Procedure | null 41 + dispatchEvent: (e: Event) => void 42 + addEventListener: (e: string, fn: Procedure) => void 43 + removeEventListener: (e: string, fn: Procedure) => void 44 + postMessage: (data: any) => void 45 + self: InlineWorkerContext 46 + global: InlineWorkerContext 47 + invalidate: string[] 48 + importScripts?: any 49 + } 50 + 51 + class InlineWorkerRunner extends VitestRunner { 52 + constructor(options: any, private context: InlineWorkerContext) { 53 + super(options) 54 + } 55 + 56 + prepareContext(context: Record<string, any>) { 57 + const ctx = super.prepareContext(context) 58 + // not supported for now 59 + // need to be async 60 + this.context.self.importScripts = () => {} 61 + return Object.assign(ctx, this.context, { 62 + importScripts: () => {}, 63 + }) 64 + } 65 + } 66 + 67 + export function defineWebWorker() { 68 + if ('Worker' in globalThis) return 69 + 70 + const { config, rpc, mockMap, moduleCache } = getWorkerState() 71 + 72 + const options = { 73 + fetchModule(id: string) { 74 + return rpc.fetch(id) 75 + }, 76 + resolveId(id: string, importer?: string) { 77 + return rpc.resolveId(id, importer) 78 + }, 79 + moduleCache, 80 + mockMap, 81 + interopDefault: config.deps.interopDefault ?? true, 82 + root: config.root, 83 + base: config.base, 84 + } 85 + 86 + globalThis.Worker = class Worker { 87 + private inside = new Bridge() 88 + private outside = new Bridge() 89 + 90 + private messageQueue: any[] | null = [] 91 + 92 + public onmessage: null | Procedure = null 93 + public onmessageerror: null | Procedure = null 94 + public onerror: null | Procedure = null 95 + 96 + constructor(url: URL | string) { 97 + const invalidate: string[] = [] 98 + const context: InlineWorkerContext = { 99 + onmessage: null, 100 + dispatchEvent: (event: Event) => { 101 + this.inside.emit(event.type, event) 102 + return true 103 + }, 104 + addEventListener: this.inside.on, 105 + removeEventListener: this.inside.off, 106 + postMessage: (data) => { 107 + this.outside.emit('message', { data }) 108 + }, 109 + get self() { 110 + return context 111 + }, 112 + get global() { 113 + return context 114 + }, 115 + invalidate, 116 + } 117 + 118 + this.inside.on('message', (e) => { 119 + context.onmessage?.(e) 120 + }) 121 + 122 + this.outside.on('message', (e) => { 123 + this.onmessage?.(e) 124 + }) 125 + 126 + const runner = new InlineWorkerRunner(options, context) 127 + 128 + let id = url instanceof URL ? url.toString() : url 129 + 130 + id = id.replace('?worker_file', '') 131 + 132 + invalidate.push(id) 133 + 134 + runner.executeId(id) 135 + .then(() => { 136 + invalidate.forEach((path) => { 137 + // worker should be new every time 138 + moduleCache.delete(path) 139 + moduleCache.delete(`${path}__mock`) 140 + }) 141 + const q = this.messageQueue 142 + this.messageQueue = null 143 + if (q) 144 + q.forEach(this.postMessage, this) 145 + }).catch((e) => { 146 + this.outside.emit('error', e) 147 + this.onerror?.(e) 148 + console.error(e) 149 + }) 150 + } 151 + 152 + dispatchEvent(event: Event) { 153 + this.outside.emit(event.type, event) 154 + return true 155 + } 156 + 157 + addEventListener(event: string, fn: Procedure) { 158 + this.outside.on(event, fn) 159 + } 160 + 161 + removeEventListener(event: string, fn: Procedure) { 162 + this.outside.off(event, fn) 163 + } 164 + 165 + postMessage(data: any) { 166 + if (this.messageQueue != null) 167 + this.messageQueue.push(data) 168 + else 169 + this.inside.emit('message', { data }) 170 + } 171 + 172 + terminate() { 173 + this.outside.clear() 174 + this.inside.clear() 175 + } 176 + } 177 + }
+19
test/core/types/vite.d.ts
··· 1 + interface ImportMeta { 2 + readonly env: ImportMetaEnv 3 + } 4 + 5 + interface ImportMetaEnv { 6 + [key: string]: string | boolean | undefined 7 + BASE_URL: string 8 + MODE: string 9 + DEV: boolean 10 + PROD: boolean 11 + SSR: boolean 12 + } 13 + 14 + declare module '*?worker' { 15 + const workerConstructor: { 16 + new (): Worker 17 + } 18 + export default workerConstructor 19 + }
+3
test/web-worker/src/worker.ts
··· 1 + self.onmessage = (e) => { 2 + self.postMessage(`${e.data} world`) 3 + }
+37
test/web-worker/test/init.test.ts
··· 1 + import { expect, it } from 'vitest' 2 + 3 + import MyWorker from '../src/worker?worker' 4 + 5 + const testWorker = (worker: Worker) => { 6 + return new Promise<void>((resolve) => { 7 + worker.postMessage('hello') 8 + worker.onmessage = (e) => { 9 + expect(e.data).toBe('hello world') 10 + 11 + resolve() 12 + } 13 + }) 14 + } 15 + 16 + it('worker exists', async() => { 17 + expect(Worker).toBeDefined() 18 + }) 19 + 20 + it('simple worker', async() => { 21 + expect.assertions(1) 22 + 23 + await testWorker(new MyWorker()) 24 + }) 25 + 26 + it('can test workers several times', async() => { 27 + expect.assertions(1) 28 + 29 + await testWorker(new MyWorker()) 30 + }) 31 + 32 + it('worker with url', async() => { 33 + expect.assertions(1) 34 + const url = import.meta.url 35 + 36 + await testWorker(new Worker(new URL('../src/worker.ts', url))) 37 + })
+3 -2
packages/vitest/src/integrations/jest-mock.ts
··· 1 - import { util } from 'chai' 2 1 import type { SpyImpl } from 'tinyspy' 3 2 import * as tinyspy from 'tinyspy' 4 3 ··· 228 227 stub.mockRejectedValueOnce = (val: unknown) => 229 228 stub.mockImplementationOnce(() => Promise.reject(val)) 230 229 231 - util.addProperty(stub, 'mock', () => mockContext) 230 + Object.defineProperty(stub, 'mock', { 231 + get: () => mockContext, 232 + }) 232 233 233 234 stub.willCall(function(this: unknown, ...args) { 234 235 instances.push(this)
+1 -1
packages/vitest/src/integrations/vi.ts
··· 1 1 /* eslint-disable @typescript-eslint/no-unused-vars */ 2 2 3 3 import { parseStacktrace } from '../utils/source-map' 4 - import type { VitestMocker } from '../node/mocker' 4 + import type { VitestMocker } from '../runtime/mocker' 5 5 import { FakeTimers } from './timers' 6 6 import type { EnhancedSpy, MaybeMocked, MaybeMockedDeep } from './jest-mock' 7 7 import { fn, isMockFunction, spies, spyOn } from './jest-mock'
-55
packages/vitest/src/node/execute.ts
··· 1 - import { ViteNodeRunner } from 'vite-node/client' 2 - import type { ModuleCache, ViteNodeRunnerOptions } from 'vite-node' 3 - import { normalizePath } from 'vite' 4 - import type { SuiteMocks } from './mocker' 5 - import { VitestMocker } from './mocker' 6 - 7 - export interface ExecuteOptions extends ViteNodeRunnerOptions { 8 - files: string[] 9 - mockMap: SuiteMocks 10 - } 11 - 12 - export async function executeInViteNode(options: ExecuteOptions) { 13 - const runner = new VitestRunner(options) 14 - 15 - // provide the vite define variable in this context 16 - await runner.executeId('/@vite/env') 17 - 18 - const result: any[] = [] 19 - for (const file of options.files) 20 - result.push(await runner.executeFile(file)) 21 - 22 - return result 23 - } 24 - 25 - export class VitestRunner extends ViteNodeRunner { 26 - mocker: VitestMocker 27 - entries = new Set<string>() 28 - 29 - constructor(public options: ExecuteOptions) { 30 - super(options) 31 - this.mocker = new VitestMocker(options, this.moduleCache) 32 - } 33 - 34 - prepareContext(context: Record<string, any>) { 35 - const request = context.__vite_ssr_import__ 36 - 37 - const mocker = this.mocker.withRequest(request) 38 - 39 - mocker.on('mocked', (dep: string, module: Partial<ModuleCache>) => { 40 - this.setCache(dep, module) 41 - }) 42 - 43 - // support `import.meta.vitest` for test entry 44 - if (__vitest_worker__.filepath && normalizePath(__vitest_worker__.filepath) === normalizePath(context.__filename)) { 45 - // @ts-expect-error injected untyped global 46 - Object.defineProperty(context.__vite_ssr_import_meta__, 'vitest', { get: () => globalThis.__vitest_index__ }) 47 - } 48 - 49 - return Object.assign(context, { 50 - __vite_ssr_import__: (dep: string) => mocker.requestWithMock(dep), 51 - __vite_ssr_dynamic_import__: (dep: string) => mocker.requestWithMock(dep), 52 - __vitest_mocker__: mocker, 53 - }) 54 - } 55 - }
+3
packages/vitest/src/node/index.ts
··· 2 2 export { createVitest } from './create' 3 3 export { VitestPlugin } from './plugins' 4 4 export { startVitest } from './cli-api' 5 + 6 + export { VitestRunner } from '../runtime/execute' 7 + export type { ExecuteOptions } from '../runtime/execute'
-281
packages/vitest/src/node/mocker.ts
··· 1 - import { existsSync, readdirSync } from 'fs' 2 - import { isNodeBuiltin } from 'mlly' 3 - import { basename, dirname, resolve } from 'pathe' 4 - import type { ModuleCache } from 'vite-node' 5 - import { toFilePath } from 'vite-node/utils' 6 - import { isWindows, mergeSlashes, normalizeId } from '../utils' 7 - import { distDir } from '../constants' 8 - import type { ExecuteOptions } from './execute' 9 - 10 - export type SuiteMocks = Record<string, Record<string, string | null | (() => unknown)>> 11 - 12 - type Callback = (...args: any[]) => unknown 13 - 14 - interface PendingSuiteMock { 15 - id: string 16 - importer: string 17 - type: 'mock' | 'unmock' 18 - factory?: () => unknown 19 - } 20 - 21 - function getObjectType(value: unknown): string { 22 - return Object.prototype.toString.apply(value).slice(8, -1) 23 - } 24 - 25 - function mockPrototype(spyOn: typeof import('../integrations/jest-mock')['spyOn'], proto: any) { 26 - if (!proto) return null 27 - 28 - const newProto: any = {} 29 - 30 - const protoDescr = Object.getOwnPropertyDescriptors(proto) 31 - 32 - // eslint-disable-next-line no-restricted-syntax 33 - for (const d in protoDescr) { 34 - Object.defineProperty(newProto, d, protoDescr[d]) 35 - 36 - if (typeof protoDescr[d].value === 'function') 37 - spyOn(newProto, d).mockImplementation(() => {}) 38 - } 39 - 40 - return newProto 41 - } 42 - 43 - const pendingIds: PendingSuiteMock[] = [] 44 - 45 - export class VitestMocker { 46 - private request!: (dep: string) => unknown 47 - 48 - private root: string 49 - 50 - private callbacks: Record<string, ((...args: any[]) => unknown)[]> = {} 51 - private spy?: typeof import('../integrations/jest-mock') 52 - 53 - constructor( 54 - public options: ExecuteOptions, 55 - private moduleCache: Map<string, ModuleCache>, 56 - request?: (dep: string) => unknown, 57 - ) { 58 - this.root = this.options.root 59 - this.request = request! 60 - } 61 - 62 - get mockMap() { 63 - return this.options.mockMap 64 - } 65 - 66 - public on(event: string, cb: Callback) { 67 - this.callbacks[event] ??= [] 68 - this.callbacks[event].push(cb) 69 - } 70 - 71 - private emit(event: string, ...args: any[]) { 72 - (this.callbacks[event] ?? []).forEach(fn => fn(...args)) 73 - } 74 - 75 - public getSuiteFilepath(): string { 76 - return __vitest_worker__?.filepath || 'global' 77 - } 78 - 79 - public getMocks() { 80 - const suite = this.getSuiteFilepath() 81 - const suiteMocks = this.mockMap[suite] 82 - const globalMocks = this.mockMap.global 83 - 84 - return { 85 - ...globalMocks, 86 - ...suiteMocks, 87 - } 88 - } 89 - 90 - private async resolvePath(id: string, importer: string) { 91 - const path = await this.options.resolveId!(id, importer) 92 - return { 93 - path: normalizeId(path?.id || id), 94 - external: path?.id.includes('/node_modules/') ? id : null, 95 - } 96 - } 97 - 98 - private async resolveMocks() { 99 - await Promise.all(pendingIds.map(async(mock) => { 100 - const { path, external } = await this.resolvePath(mock.id, mock.importer) 101 - if (mock.type === 'unmock') 102 - this.unmockPath(path) 103 - if (mock.type === 'mock') 104 - this.mockPath(path, external, mock.factory) 105 - })) 106 - 107 - pendingIds.length = 0 108 - } 109 - 110 - private async callFunctionMock(dep: string, mock: () => any) { 111 - const cacheName = `${dep}__mock` 112 - const cached = this.moduleCache.get(cacheName)?.exports 113 - if (cached) 114 - return cached 115 - const exports = await mock() 116 - this.emit('mocked', cacheName, { exports }) 117 - return exports 118 - } 119 - 120 - public getDependencyMock(dep: string) { 121 - return this.getMocks()[this.resolveDependency(dep)] 122 - } 123 - 124 - public resolveDependency(dep: string) { 125 - return normalizeId(dep).replace(/^\/@fs\//, isWindows ? '' : '/') 126 - } 127 - 128 - public normalizePath(path: string) { 129 - return normalizeId(path.replace(this.root, '')).replace(/^\/@fs\//, isWindows ? '' : '/') 130 - } 131 - 132 - public getFsPath(path: string, external: string | null) { 133 - if (external) 134 - return mergeSlashes(`/@fs/${path}`) 135 - 136 - return normalizeId(path.replace(this.root, '')) 137 - } 138 - 139 - public resolveMockPath(mockPath: string, external: string | null) { 140 - const path = normalizeId(external || mockPath) 141 - 142 - // it's a node_module alias 143 - // all mocks should be inside <root>/__mocks__ 144 - if (external || isNodeBuiltin(mockPath)) { 145 - const mockDirname = dirname(path) // for nested mocks: @vueuse/integration/useJwt 146 - const baseFilename = basename(path) 147 - const mockFolder = resolve(this.root, '__mocks__', mockDirname) 148 - 149 - if (!existsSync(mockFolder)) return null 150 - 151 - const files = readdirSync(mockFolder) 152 - 153 - for (const file of files) { 154 - const [basename] = file.split('.') 155 - if (basename === baseFilename) 156 - return resolve(mockFolder, file).replace(this.root, '') 157 - } 158 - 159 - return null 160 - } 161 - 162 - const dir = dirname(path) 163 - const baseId = basename(path) 164 - const fullPath = resolve(dir, '__mocks__', baseId) 165 - return existsSync(fullPath) ? fullPath.replace(this.root, '') : null 166 - } 167 - 168 - public mockObject(obj: any) { 169 - if (!this.spy) 170 - throw new Error('Internal Vitest error: Spy function is not defined.') 171 - 172 - const type = getObjectType(obj) 173 - 174 - if (Array.isArray(obj)) 175 - return [] 176 - else if (type !== 'Object' && type !== 'Module') 177 - return obj 178 - 179 - const newObj = { ...obj } 180 - 181 - const proto = mockPrototype(this.spy.spyOn, Object.getPrototypeOf(obj)) 182 - Object.setPrototypeOf(newObj, proto) 183 - 184 - // eslint-disable-next-line no-restricted-syntax 185 - for (const k in obj) { 186 - newObj[k] = this.mockObject(obj[k]) 187 - const type = getObjectType(obj[k]) 188 - 189 - if (type.includes('Function') && !obj[k]._isMockFunction) { 190 - this.spy.spyOn(newObj, k).mockImplementation(() => {}) 191 - Object.defineProperty(newObj[k], 'length', { value: 0 }) // tinyspy retains length, but jest doesn't 192 - } 193 - } 194 - return newObj 195 - } 196 - 197 - public unmockPath(path: string) { 198 - const suitefile = this.getSuiteFilepath() 199 - 200 - const fsPath = this.normalizePath(path) 201 - 202 - if (this.mockMap[suitefile]?.[fsPath]) 203 - delete this.mockMap[suitefile][fsPath] 204 - } 205 - 206 - public mockPath(path: string, external: string | null, factory?: () => any) { 207 - const suitefile = this.getSuiteFilepath() 208 - 209 - const fsPath = this.normalizePath(path) 210 - 211 - this.mockMap[suitefile] ??= {} 212 - this.mockMap[suitefile][fsPath] = factory || this.resolveMockPath(path, external) 213 - } 214 - 215 - public async importActual<T>(id: string, importer: string): Promise<T> { 216 - const { path, external } = await this.resolvePath(id, importer) 217 - const fsPath = this.getFsPath(path, external) 218 - const result = await this.request(fsPath) 219 - return result as T 220 - } 221 - 222 - public async importMock(id: string, importer: string): Promise<any> { 223 - const { path, external } = await this.resolvePath(id, importer) 224 - 225 - let mock = this.getDependencyMock(path) 226 - 227 - if (mock === undefined) 228 - mock = this.resolveMockPath(path, external) 229 - 230 - if (mock === null) { 231 - await this.ensureSpy() 232 - const fsPath = this.getFsPath(path, external) 233 - const mod = await this.request(fsPath) 234 - return this.mockObject(mod) 235 - } 236 - if (typeof mock === 'function') 237 - return this.callFunctionMock(path, mock) 238 - return this.requestWithMock(mock) 239 - } 240 - 241 - private async ensureSpy() { 242 - if (this.spy) return 243 - this.spy = await this.request(resolve(distDir, 'jest-mock.js')) as typeof import('../integrations/jest-mock') 244 - } 245 - 246 - public async requestWithMock(dep: string) { 247 - await this.ensureSpy() 248 - await this.resolveMocks() 249 - 250 - const mock = this.getDependencyMock(dep) 251 - 252 - if (mock === null) { 253 - const cacheName = `${dep}__mock` 254 - const cache = this.moduleCache.get(cacheName) 255 - if (cache?.exports) 256 - return cache.exports 257 - const cacheKey = toFilePath(dep, this.root) 258 - const mod = this.moduleCache.get(cacheKey)?.exports || await this.request(dep) 259 - const exports = this.mockObject(mod) 260 - this.emit('mocked', cacheName, { exports }) 261 - return exports 262 - } 263 - if (typeof mock === 'function') 264 - return this.callFunctionMock(dep, mock) 265 - if (typeof mock === 'string') 266 - dep = mock 267 - return this.request(dep) 268 - } 269 - 270 - public queueMock(id: string, importer: string, factory?: () => unknown) { 271 - pendingIds.push({ type: 'mock', id, importer, factory }) 272 - } 273 - 274 - public queueUnmock(id: string, importer: string) { 275 - pendingIds.push({ type: 'unmock', id, importer }) 276 - } 277 - 278 - public withRequest(request: (dep: string) => unknown) { 279 - return new VitestMocker(this.options, this.moduleCache, request) 280 - } 281 - }
+3 -2
packages/vitest/src/runtime/context.ts
··· 1 1 import type { Awaitable, DoneCallback, RuntimeContext, SuiteCollector, TestFunction } from '../types' 2 + import { getWorkerState } from '../utils' 2 3 3 4 export const context: RuntimeContext = { 4 5 tasks: [], ··· 17 18 } 18 19 19 20 export function getDefaultTestTimeout() { 20 - return __vitest_worker__!.config!.testTimeout 21 + return getWorkerState().config.testTimeout 21 22 } 22 23 23 24 export function getDefaultHookTimeout() { 24 - return __vitest_worker__!.config!.hookTimeout 25 + return getWorkerState().config.hookTimeout 25 26 } 26 27 27 28 export function withTimeout<T extends((...args: any[]) => any)>(
+4 -2
packages/vitest/src/runtime/entry.ts
··· 1 1 import { promises as fs } from 'fs' 2 2 import type { BuiltinEnvironment, ResolvedConfig } from '../types' 3 + import { getWorkerState } from '../utils' 3 4 import { setupGlobalEnv, withEnv } from './setup' 4 5 import { startTests } from './run' 5 6 ··· 14 15 if (!['node', 'jsdom', 'happy-dom'].includes(env)) 15 16 throw new Error(`Unsupported environment: ${env}`) 16 17 17 - __vitest_worker__.filepath = file 18 + const workerState = getWorkerState() 19 + workerState.filepath = file 18 20 19 21 await withEnv(env as BuiltinEnvironment, config.environmentOptions || {}, async() => { 20 22 await startTests([file], config) 21 23 }) 22 24 23 - __vitest_worker__.filepath = undefined 25 + workerState.filepath = undefined 24 26 } 25 27 }
+57
packages/vitest/src/runtime/execute.ts
··· 1 + import { ViteNodeRunner } from 'vite-node/client' 2 + import type { ModuleCache, ViteNodeRunnerOptions } from 'vite-node' 3 + import { normalizePath } from 'vite' 4 + import type { SuiteMocks } from '../types/mocker' 5 + import { getWorkerState } from '../utils' 6 + import { VitestMocker } from './mocker' 7 + 8 + export interface ExecuteOptions extends ViteNodeRunnerOptions { 9 + mockMap: SuiteMocks 10 + } 11 + 12 + export async function executeInViteNode(options: ExecuteOptions & { files: string[] }) { 13 + const runner = new VitestRunner(options) 14 + 15 + // provide the vite define variable in this context 16 + await runner.executeId('/@vite/env') 17 + 18 + const result: any[] = [] 19 + for (const file of options.files) 20 + result.push(await runner.executeFile(file)) 21 + 22 + return result 23 + } 24 + 25 + export class VitestRunner extends ViteNodeRunner { 26 + mocker: VitestMocker 27 + entries = new Set<string>() 28 + 29 + constructor(public options: ExecuteOptions) { 30 + super(options) 31 + this.mocker = new VitestMocker(options, this.moduleCache) 32 + } 33 + 34 + prepareContext(context: Record<string, any>) { 35 + const request = context.__vite_ssr_import__ 36 + 37 + const mocker = this.mocker.withRequest(request) 38 + 39 + mocker.on('mocked', (dep: string, module: Partial<ModuleCache>) => { 40 + this.setCache(dep, module) 41 + }) 42 + 43 + const workerState = getWorkerState() 44 + 45 + // support `import.meta.vitest` for test entry 46 + if (workerState.filepath && normalizePath(workerState.filepath) === normalizePath(context.__filename)) { 47 + // @ts-expect-error injected untyped global 48 + Object.defineProperty(context.__vite_ssr_import_meta__, 'vitest', { get: () => globalThis.__vitest_index__ }) 49 + } 50 + 51 + return Object.assign(context, { 52 + __vite_ssr_import__: (dep: string) => mocker.requestWithMock(dep), 53 + __vite_ssr_dynamic_import__: (dep: string) => mocker.requestWithMock(dep), 54 + __vitest_mocker__: mocker, 55 + }) 56 + } 57 + }
+272
packages/vitest/src/runtime/mocker.ts
··· 1 + import { existsSync, readdirSync } from 'fs' 2 + import { isNodeBuiltin } from 'mlly' 3 + import { basename, dirname, resolve } from 'pathe' 4 + import type { ModuleCache } from 'vite-node' 5 + import { toFilePath } from 'vite-node/utils' 6 + import { getWorkerState, isWindows, mergeSlashes, normalizeId } from '../utils' 7 + import { distDir } from '../constants' 8 + import type { PendingSuiteMock } from '../types/mocker' 9 + import type { ExecuteOptions } from './execute' 10 + 11 + type Callback = (...args: any[]) => unknown 12 + 13 + function getObjectType(value: unknown): string { 14 + return Object.prototype.toString.apply(value).slice(8, -1) 15 + } 16 + 17 + function mockPrototype(spyOn: typeof import('../integrations/jest-mock')['spyOn'], proto: any) { 18 + if (!proto) return null 19 + 20 + const newProto: any = {} 21 + 22 + const protoDescr = Object.getOwnPropertyDescriptors(proto) 23 + 24 + // eslint-disable-next-line no-restricted-syntax 25 + for (const d in protoDescr) { 26 + Object.defineProperty(newProto, d, protoDescr[d]) 27 + 28 + if (typeof protoDescr[d].value === 'function') 29 + spyOn(newProto, d).mockImplementation(() => {}) 30 + } 31 + 32 + return newProto 33 + } 34 + 35 + export class VitestMocker { 36 + private static pendingIds: PendingSuiteMock[] = [] 37 + private static spyModule?: typeof import('../integrations/jest-mock') 38 + 39 + private request!: (dep: string) => unknown 40 + 41 + private root: string 42 + private callbacks: Record<string, ((...args: any[]) => unknown)[]> = {} 43 + 44 + constructor( 45 + public options: ExecuteOptions, 46 + private moduleCache: Map<string, ModuleCache>, 47 + request?: (dep: string) => unknown, 48 + ) { 49 + this.root = this.options.root 50 + this.request = request! 51 + } 52 + 53 + get mockMap() { 54 + return this.options.mockMap 55 + } 56 + 57 + public on(event: string, cb: Callback) { 58 + this.callbacks[event] ??= [] 59 + this.callbacks[event].push(cb) 60 + } 61 + 62 + private emit(event: string, ...args: any[]) { 63 + (this.callbacks[event] ?? []).forEach(fn => fn(...args)) 64 + } 65 + 66 + public getSuiteFilepath(): string { 67 + return getWorkerState().filepath || 'global' 68 + } 69 + 70 + public getMocks() { 71 + const suite = this.getSuiteFilepath() 72 + const suiteMocks = this.mockMap[suite] 73 + const globalMocks = this.mockMap.global 74 + 75 + return { 76 + ...globalMocks, 77 + ...suiteMocks, 78 + } 79 + } 80 + 81 + private async resolvePath(id: string, importer: string) { 82 + const path = await this.options.resolveId!(id, importer) 83 + return { 84 + path: normalizeId(path?.id || id), 85 + external: path?.id.includes('/node_modules/') ? id : null, 86 + } 87 + } 88 + 89 + private async resolveMocks() { 90 + await Promise.all(VitestMocker.pendingIds.map(async(mock) => { 91 + const { path, external } = await this.resolvePath(mock.id, mock.importer) 92 + if (mock.type === 'unmock') 93 + this.unmockPath(path) 94 + if (mock.type === 'mock') 95 + this.mockPath(path, external, mock.factory) 96 + })) 97 + 98 + VitestMocker.pendingIds = [] 99 + } 100 + 101 + private async callFunctionMock(dep: string, mock: () => any) { 102 + const cacheName = `${dep}__mock` 103 + const cached = this.moduleCache.get(cacheName)?.exports 104 + if (cached) 105 + return cached 106 + const exports = await mock() 107 + this.emit('mocked', cacheName, { exports }) 108 + return exports 109 + } 110 + 111 + public getDependencyMock(dep: string) { 112 + return this.getMocks()[this.resolveDependency(dep)] 113 + } 114 + 115 + public resolveDependency(dep: string) { 116 + return normalizeId(dep).replace(/^\/@fs\//, isWindows ? '' : '/') 117 + } 118 + 119 + public normalizePath(path: string) { 120 + return normalizeId(path.replace(this.root, '')).replace(/^\/@fs\//, isWindows ? '' : '/') 121 + } 122 + 123 + public getFsPath(path: string, external: string | null) { 124 + if (external) 125 + return mergeSlashes(`/@fs/${path}`) 126 + 127 + return normalizeId(path.replace(this.root, '')) 128 + } 129 + 130 + public resolveMockPath(mockPath: string, external: string | null) { 131 + const path = normalizeId(external || mockPath) 132 + 133 + // it's a node_module alias 134 + // all mocks should be inside <root>/__mocks__ 135 + if (external || isNodeBuiltin(mockPath)) { 136 + const mockDirname = dirname(path) // for nested mocks: @vueuse/integration/useJwt 137 + const baseFilename = basename(path) 138 + const mockFolder = resolve(this.root, '__mocks__', mockDirname) 139 + 140 + if (!existsSync(mockFolder)) return null 141 + 142 + const files = readdirSync(mockFolder) 143 + 144 + for (const file of files) { 145 + const [basename] = file.split('.') 146 + if (basename === baseFilename) 147 + return resolve(mockFolder, file).replace(this.root, '') 148 + } 149 + 150 + return null 151 + } 152 + 153 + const dir = dirname(path) 154 + const baseId = basename(path) 155 + const fullPath = resolve(dir, '__mocks__', baseId) 156 + return existsSync(fullPath) ? fullPath.replace(this.root, '') : null 157 + } 158 + 159 + public mockObject(obj: any) { 160 + if (!VitestMocker.spyModule) 161 + throw new Error('Internal Vitest error: Spy function is not defined.') 162 + 163 + const type = getObjectType(obj) 164 + 165 + if (Array.isArray(obj)) 166 + return [] 167 + else if (type !== 'Object' && type !== 'Module') 168 + return obj 169 + 170 + const newObj = { ...obj } 171 + 172 + const proto = mockPrototype(VitestMocker.spyModule.spyOn, Object.getPrototypeOf(obj)) 173 + Object.setPrototypeOf(newObj, proto) 174 + 175 + // eslint-disable-next-line no-restricted-syntax 176 + for (const k in obj) { 177 + newObj[k] = this.mockObject(obj[k]) 178 + const type = getObjectType(obj[k]) 179 + 180 + if (type.includes('Function') && !obj[k]._isMockFunction) { 181 + VitestMocker.spyModule.spyOn(newObj, k).mockImplementation(() => {}) 182 + Object.defineProperty(newObj[k], 'length', { value: 0 }) // tinyspy retains length, but jest doesnt 183 + } 184 + } 185 + return newObj 186 + } 187 + 188 + public unmockPath(path: string) { 189 + const suitefile = this.getSuiteFilepath() 190 + 191 + const fsPath = this.normalizePath(path) 192 + 193 + if (this.mockMap[suitefile]?.[fsPath]) 194 + delete this.mockMap[suitefile][fsPath] 195 + } 196 + 197 + public mockPath(path: string, external: string | null, factory?: () => any) { 198 + const suitefile = this.getSuiteFilepath() 199 + 200 + const fsPath = this.normalizePath(path) 201 + 202 + this.mockMap[suitefile] ??= {} 203 + this.mockMap[suitefile][fsPath] = factory || this.resolveMockPath(path, external) 204 + } 205 + 206 + public async importActual<T>(id: string, importer: string): Promise<T> { 207 + const { path, external } = await this.resolvePath(id, importer) 208 + const fsPath = this.getFsPath(path, external) 209 + const result = await this.request(fsPath) 210 + return result as T 211 + } 212 + 213 + public async importMock(id: string, importer: string): Promise<any> { 214 + const { path, external } = await this.resolvePath(id, importer) 215 + 216 + let mock = this.getDependencyMock(path) 217 + 218 + if (mock === undefined) 219 + mock = this.resolveMockPath(path, external) 220 + 221 + if (mock === null) { 222 + await this.ensureSpy() 223 + const fsPath = this.getFsPath(path, external) 224 + const mod = await this.request(fsPath) 225 + return this.mockObject(mod) 226 + } 227 + if (typeof mock === 'function') 228 + return this.callFunctionMock(path, mock) 229 + return this.requestWithMock(mock) 230 + } 231 + 232 + private async ensureSpy() { 233 + if (VitestMocker.spyModule) return 234 + VitestMocker.spyModule = await this.request(resolve(distDir, 'jest-mock.js')) as typeof import('../integrations/jest-mock') 235 + } 236 + 237 + public async requestWithMock(dep: string) { 238 + await this.ensureSpy() 239 + await this.resolveMocks() 240 + 241 + const mock = this.getDependencyMock(dep) 242 + 243 + if (mock === null) { 244 + const cacheName = `${dep}__mock` 245 + const cache = this.moduleCache.get(cacheName) 246 + if (cache?.exports) 247 + return cache.exports 248 + const cacheKey = toFilePath(dep, this.root) 249 + const mod = this.moduleCache.get(cacheKey)?.exports || await this.request(dep) 250 + const exports = this.mockObject(mod) 251 + this.emit('mocked', cacheName, { exports }) 252 + return exports 253 + } 254 + if (typeof mock === 'function') 255 + return this.callFunctionMock(dep, mock) 256 + if (typeof mock === 'string') 257 + dep = mock 258 + return this.request(dep) 259 + } 260 + 261 + public queueMock(id: string, importer: string, factory?: () => unknown) { 262 + VitestMocker.pendingIds.push({ type: 'mock', id, importer, factory }) 263 + } 264 + 265 + public queueUnmock(id: string, importer: string) { 266 + VitestMocker.pendingIds.push({ type: 'unmock', id, importer }) 267 + } 268 + 269 + public withRequest(request: (dep: string) => unknown) { 270 + return new VitestMocker(this.options, this.moduleCache, request) 271 + } 272 + }
+3 -1
packages/vitest/src/runtime/rpc.ts
··· 1 + import { getWorkerState } from '../utils' 2 + 1 3 export const rpc = () => { 2 - return __vitest_worker__!.rpc 4 + return getWorkerState().rpc 3 5 }
+6 -4
packages/vitest/src/runtime/run.ts
··· 2 2 import type { File, HookListener, ResolvedConfig, Suite, SuiteHooks, Task, TaskResult, TaskState, Test } from '../types' 3 3 import { vi } from '../integrations/vi' 4 4 import { getSnapshotClient } from '../integrations/snapshot/chai' 5 - import { getFullName, hasFailed, hasTests, partitionSuiteChildren } from '../utils' 5 + import { getFullName, getWorkerState, hasFailed, hasTests, partitionSuiteChildren } from '../utils' 6 6 import { getState, setState } from '../integrations/chai/jest-expect' 7 7 import { takeCoverage } from '../integrations/coverage' 8 8 import { getFn, getHooks } from './map' ··· 79 79 80 80 getSnapshotClient().setTest(test) 81 81 82 - __vitest_worker__.current = test 82 + const workerState = getWorkerState() 83 + 84 + workerState.current = test 83 85 84 86 try { 85 87 await callSuiteHook(test.suite, test, 'beforeEach', [test, test.suite]) ··· 130 132 131 133 test.result.duration = performance.now() - start 132 134 133 - __vitest_worker__.current = undefined 135 + workerState.current = undefined 134 136 135 137 updateTask(test) 136 138 } ··· 241 243 } 242 244 243 245 export function clearModuleMocks() { 244 - const { clearMocks, mockReset, restoreMocks } = __vitest_worker__.config 246 + const { clearMocks, mockReset, restoreMocks } = getWorkerState().config 245 247 246 248 // since each function calls another, we can just call one 247 249 if (restoreMocks)
+4 -4
packages/vitest/src/runtime/setup.ts
··· 2 2 import { Writable } from 'stream' 3 3 import { environments } from '../integrations/env' 4 4 import type { ResolvedConfig } from '../types' 5 - import { toArray } from '../utils' 5 + import { getWorkerState, toArray } from '../utils' 6 6 import * as VitestIndex from '../index' 7 7 import { rpc } from './rpc' 8 8 ··· 59 59 rpc().onUserConsoleLog({ 60 60 type: 'stdout', 61 61 content: stdoutBuffer.map(i => String(i)).join(''), 62 - taskId: __vitest_worker__.current?.id, 62 + taskId: getWorkerState().current?.id, 63 63 time: stdoutTime || Date.now(), 64 64 }) 65 65 } ··· 71 71 rpc().onUserConsoleLog({ 72 72 type: 'stderr', 73 73 content: stderrBuffer.map(i => String(i)).join(''), 74 - taskId: __vitest_worker__.current?.id, 74 + taskId: getWorkerState().current?.id, 75 75 time: stderrTime || Date.now(), 76 76 }) 77 77 } ··· 121 121 const files = toArray(config.setupFiles) 122 122 await Promise.all( 123 123 files.map(async(file) => { 124 - __vitest_worker__.moduleCache.delete(file) 124 + getWorkerState().moduleCache.delete(file) 125 125 await import(file) 126 126 }), 127 127 )
+8 -9
packages/vitest/src/runtime/worker.ts
··· 1 1 import { resolve } from 'pathe' 2 2 import { createBirpc } from 'birpc' 3 - import type { ModuleCache, ResolvedConfig, WorkerContext, WorkerGlobalState, WorkerRPC } from '../types' 3 + import type { ModuleCache, ResolvedConfig, WorkerContext, WorkerRPC } from '../types' 4 4 import { distDir } from '../constants' 5 - import { executeInViteNode } from '../node/execute' 5 + import { getWorkerState } from '../utils' 6 + import { executeInViteNode } from './execute' 6 7 import { rpc } from './rpc' 7 8 8 9 let _viteNode: { 9 10 run: (files: string[], config: ResolvedConfig) => Promise<void> 10 11 collect: (files: string[], config: ResolvedConfig) => Promise<void> 11 12 } 12 - let __vitest_worker__: WorkerGlobalState 13 + 13 14 const moduleCache: Map<string, ModuleCache> = new Map() 14 15 const mockMap = {} 15 16 ··· 53 54 } 54 55 55 56 function init(ctx: WorkerContext) { 56 - if (__vitest_worker__ && ctx.config.threads && ctx.config.isolate) 57 - throw new Error(`worker for ${ctx.files.join(',')} already initialized by ${__vitest_worker__.ctx.files.join(',')}. This is probably an internal bug of Vitest.`) 57 + // @ts-expect-error untyped global 58 + if (typeof __vitest_worker__ !== 'undefined' && ctx.config.threads && ctx.config.isolate) 59 + throw new Error(`worker for ${ctx.files.join(',')} already initialized by ${getWorkerState().ctx.files.join(',')}. This is probably an internal bug of Vitest.`) 58 60 59 61 process.stdout.write('\0') 60 62 ··· 67 69 ctx, 68 70 moduleCache, 69 71 config, 72 + mockMap, 70 73 rpc: createBirpc<WorkerRPC>( 71 74 {}, 72 75 { ··· 92 95 init(ctx) 93 96 const { run } = await startViteNode(ctx) 94 97 return run(ctx.files, ctx.config) 95 - } 96 - 97 - declare global { 98 - let __vitest_worker__: import('vitest').WorkerGlobalState 99 98 }
+8
packages/vitest/src/types/mocker.ts
··· 1 + export type SuiteMocks = Record<string, Record<string, string | null | (() => unknown)>> 2 + 3 + export interface PendingSuiteMock { 4 + id: string 5 + importer: string 6 + type: 'mock' | 'unmock' 7 + factory?: () => unknown 8 + }
+2
packages/vitest/src/types/worker.ts
··· 1 1 import type { MessagePort } from 'worker_threads' 2 2 import type { FetchFunction, ModuleCache, RawSourceMap, ViteNodeResolveId } from 'vite-node' 3 3 import type { BirpcReturn } from 'birpc' 4 + import type { SuiteMocks } from './mocker' 4 5 import type { ResolvedConfig } from './config' 5 6 import type { File, TaskResultPack, Test } from './tasks' 6 7 import type { SnapshotResult } from './snapshot' ··· 37 38 current?: Test 38 39 filepath?: string 39 40 moduleCache: Map<string, ModuleCache> 41 + mockMap: SuiteMocks 40 42 }
+6
packages/vitest/src/utils/global.ts
··· 1 + import type { WorkerGlobalState } from '../types' 2 + 3 + export function getWorkerState(): WorkerGlobalState { 4 + // @ts-expect-error untyped global 5 + return globalThis.__vitest_worker__ 6 + }
+1
packages/vitest/src/utils/index.ts
··· 7 7 export * from './tasks' 8 8 export * from './path' 9 9 export * from './base' 10 + export * from './global' 10 11 11 12 export const isWindows = process.platform === 'win32' 12 13
+2 -2
packages/vitest/src/integrations/snapshot/client.ts
··· 2 2 import { expect } from 'chai' 3 3 import type { SnapshotResult, Test } from '../../types' 4 4 import { rpc } from '../../runtime/rpc' 5 - import { getNames } from '../../utils' 5 + import { getNames, getWorkerState } from '../../utils' 6 6 import { equals, iterableEquality, subsetEquality } from '../chai/jest-utils' 7 7 import { deepMergeSnapshot } from './port/utils' 8 8 import SnapshotState from './port/state' ··· 34 34 this.testFile = this.test!.file!.filepath 35 35 this.snapshotState = new SnapshotState( 36 36 resolveSnapshotPath(this.testFile), 37 - __vitest_worker__!.config.snapshotOptions, 37 + getWorkerState().config.snapshotOptions, 38 38 ) 39 39 } 40 40 }