Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126import type { Plugin } from 'vite'import type { MockedModuleSerialized } from '../registry'import { readFile } from 'node:fs/promises'import { join } from 'node:path/posix'import { slash } from '@vitest/utils/helpers'import { isFileLoadingAllowed } from 'vite'import { ManualMockedModule, MockerRegistry } from '../registry'import { cleanUrl, createManualModuleSource } from '../utils'import { automockModule } from './automock'
export interface InterceptorPluginOptions { /** * @default "__vitest_mocker__" */ globalThisAccessor?: string registry?: MockerRegistry /** * Register the `vitest:interceptor:*` WebSocket events in `configureServer`. * Disable this when mocks are registered through another authenticated * channel and the raw dev-server socket should not accept them. * @default true */ registerWebSocketEvents?: boolean}
export function interceptorPlugin(options: InterceptorPluginOptions = {}): Plugin { const registry = options.registry || new MockerRegistry() const plugin: Plugin = { name: 'vitest:mocks:interceptor', enforce: 'pre', load: { order: 'pre', async handler(id) { const mock = registry.getById(id) if (!mock) { return } if (mock.type === 'manual') { const exports = Object.keys(await mock.resolve()) const accessor = options.globalThisAccessor || '"__vitest_mocker__"' return createManualModuleSource(mock.url, exports, accessor) } if (mock.type === 'redirect') { return readFile(mock.redirect, 'utf-8') } }, }, transform: { order: 'post', handler(code, id) { const mock = registry.getById(id) if (!mock) { return } if (mock.type === 'automock' || mock.type === 'autospy') { const m = automockModule(code, mock.type, this.parse, { globalThisAccessor: options.globalThisAccessor, })
return { code: m.toString(), map: m.generateMap({ hires: 'boundary', source: cleanUrl(id) }), } } }, }, }
if (options.registerWebSocketEvents !== false) { plugin.configureServer = (server) => { server.ws.on('vitest:interceptor:register', (event: MockedModuleSerialized) => { if (event.type === 'manual') { const module = ManualMockedModule.fromJSON(event, async () => { const keys = await getFactoryExports(event.url) return Object.fromEntries(keys.map((key) => [key, null])) }) registry.add(module) } else { if (event.type === 'redirect') { const redirectUrl = new URL(event.redirect) const redirect = join(server.config.root, slash(redirectUrl.pathname)) // the redirect is served through the `load` hook below, so it must // stay inside the file-serving allowlist and never escape the root if (!isFileLoadingAllowed(server.config, redirect)) { server.ws.send('vitest:interceptor:register:result') return } event.redirect = redirect } registry.register(event) } server.ws.send('vitest:interceptor:register:result') }) server.ws.on('vitest:interceptor:delete', (id: string) => { registry.delete(id) server.ws.send('vitest:interceptor:delete:result') }) server.ws.on('vitest:interceptor:invalidate', () => { registry.clear() server.ws.send('vitest:interceptor:invalidate:result') })
function getFactoryExports(url: string) { server.ws.send('vitest:interceptor:resolve', url) let timeout: NodeJS.Timeout return new Promise<string[]>((resolve, reject) => { timeout = setTimeout(() => { reject(new Error(`Timeout while waiting for factory exports of ${url}`)) }, 10_000) server.ws.on( 'vitest:interceptor:resolved', ({ url: resolvedUrl, keys }: { url: string; keys: string[] }) => { if (resolvedUrl === url) { clearTimeout(timeout) resolve(keys) } }, ) }) } } }
return plugin}