mirror of
https://github.com/vitest-dev/vitest.git
synced 2025-12-08 18:26:03 +00:00
116 lines
3.9 KiB
TypeScript
116 lines
3.9 KiB
TypeScript
import type { Plugin } from 'vite'
|
|
import type { MockedModuleSerialized } from '../registry'
|
|
import { readFile } from 'node:fs/promises'
|
|
import { join } from 'node:path/posix'
|
|
import { ManualMockedModule, MockerRegistry } from '../registry'
|
|
import { cleanUrl } from '../utils'
|
|
import { automockModule } from './automockPlugin'
|
|
|
|
export interface InterceptorPluginOptions {
|
|
/**
|
|
* @default "__vitest_mocker__"
|
|
*/
|
|
globalThisAccessor?: string
|
|
}
|
|
|
|
export function interceptorPlugin(options: InterceptorPluginOptions): Plugin {
|
|
const registry = new MockerRegistry()
|
|
return {
|
|
name: 'vitest:mocks:interceptor',
|
|
enforce: 'pre',
|
|
async load(id) {
|
|
const mock = registry.get(id)
|
|
if (!mock) {
|
|
return
|
|
}
|
|
if (mock.type === 'manual') {
|
|
const exports = Object.keys(await mock.resolve())
|
|
const accessor = options.globalThisAccessor || '"__vitest_mocker__"'
|
|
const serverUrl = (mock as any).serverUrl as string
|
|
const module = `const module = globalThis[${accessor}].getFactoryModule("${serverUrl}");`
|
|
const keys = exports
|
|
.map((name) => {
|
|
if (name === 'default') {
|
|
return `export default module["default"];`
|
|
}
|
|
return `export const ${name} = module["${name}"];`
|
|
})
|
|
.join('\n')
|
|
return `${module}\n${keys}`
|
|
}
|
|
if (mock.type === 'redirect') {
|
|
return readFile(mock.redirect, 'utf-8')
|
|
}
|
|
},
|
|
transform: {
|
|
order: 'post',
|
|
handler(code, id) {
|
|
const mock = registry.get(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) }),
|
|
}
|
|
}
|
|
},
|
|
},
|
|
configureServer(server) {
|
|
server.ws.on('vitest:interceptor:register', (event: MockedModuleSerialized) => {
|
|
const serverUrl = event.url
|
|
// the browsers stores the url relative to the root
|
|
// but on the server "id" operates on the file paths
|
|
event.url = join(server.config.root, event.url)
|
|
if (event.type === 'manual') {
|
|
const module = ManualMockedModule.fromJSON(event, async () => {
|
|
const keys = await getFactoryExports(serverUrl)
|
|
return Object.fromEntries(keys.map(key => [key, null]))
|
|
})
|
|
Object.assign(module, {
|
|
serverUrl,
|
|
})
|
|
registry.add(module)
|
|
}
|
|
else {
|
|
if (event.type === 'redirect') {
|
|
const redirectUrl = new URL(event.redirect)
|
|
event.redirect = join(server.config.root, redirectUrl.pathname)
|
|
}
|
|
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:register:delete')
|
|
})
|
|
server.ws.on('vitest:interceptor:invalidate', () => {
|
|
registry.clear()
|
|
server.ws.send('vitest:interceptor:register:invalidate')
|
|
})
|
|
|
|
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)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
},
|
|
}
|
|
}
|