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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217import type { Declaration, ExportAllDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, Expression, Pattern, Positioned, Program,} from './esmWalker'import { readFileSync } from 'node:fs'import { fileURLToPath, pathToFileURL } from 'node:url'import MagicString from 'magic-string'import { getArbitraryModuleIdentifier } from './esmWalker'import { collectModuleExports, resolveModuleFormat, transformCode } from './parsers'
export interface AutomockOptions { /** * @default "__vitest_mocker__" */ globalThisAccessor?: string id?: string}
// TODO: better source map replacementexport function automockModule( code: string, mockType: 'automock' | 'autospy', parse: (code: string) => any, options: AutomockOptions = {},): MagicString { const globalThisAccessor = options.globalThisAccessor || '"__vitest_mocker__"' let ast: Program try { ast = parse(code) as Program } catch (cause) { if (options.id) { throw new Error(`failed to parse ${options.id}`, { cause }) } throw cause }
const m = new MagicString(code)
const allSpecifiers: { name: string; alias?: string }[] = [] const replacers: (() => void)[] = [] let importIndex = 0 for (const _node of ast.body) { if (_node.type === 'ExportAllDeclaration') { const node = _node as Positioned<ExportAllDeclaration> // TODO: pass it down in the browser mode if (!options.id) { throw new Error( `automocking files with \`export *\` is not supported because it cannot be easily statically analysed`, ) }
const source = node.source.value if (typeof source !== 'string') { throw new TypeError(`unknown source type while automocking: ${source}`) }
const moduleUrl = import.meta.resolve(source, pathToFileURL(options.id).toString()) const modulePath = fileURLToPath(moduleUrl) const moduleContent = readFileSync(modulePath, 'utf-8') const transformedCode = transformCode(moduleContent, moduleUrl) const moduleFormat = resolveModuleFormat(moduleUrl, transformedCode) const moduleExports = collectModuleExports( modulePath, transformedCode, moduleFormat || 'module', ) replacers.push(() => { const importNames: string[] = [] moduleExports.forEach((exportName) => { const isReexported = allSpecifiers.some( ({ name, alias }) => name === exportName || alias === exportName, ) if (!isReexported) { importNames.push(exportName) allSpecifiers.push({ name: exportName }) } })
const importString = `import { ${importNames.join(', ')} } from '${source}';`
m.overwrite(node.start, node.end, importString) }) }
if (_node.type === 'ExportNamedDeclaration') { const node = _node as Positioned<ExportNamedDeclaration> const declaration = node.declaration // export const name
function traversePattern(expression: Pattern) { // export const test = '1' if (expression.type === 'Identifier') { allSpecifiers.push({ name: expression.name }) } // export const [test, ...rest] = [1, 2, 3] else if (expression.type === 'ArrayPattern') { expression.elements.forEach((element) => { if (!element) { return } traversePattern(element) }) } else if (expression.type === 'ObjectPattern') { expression.properties.forEach((property) => { // export const { ...rest } = {} if (property.type === 'RestElement') { traversePattern(property) } // export const { test, test2: alias } = {} else if (property.type === 'Property') { traversePattern(property.value) } else { property satisfies never } }) } else if (expression.type === 'RestElement') { traversePattern(expression.argument) } // const [name[1], name[2]] = [] // cannot be used in export else if (expression.type === 'AssignmentPattern') { throw new Error(`AssignmentPattern is not supported. Please open a new bug report.`) } // const test = thing.func() // cannot be used in export else if (expression.type === 'MemberExpression') { throw new Error(`MemberExpression is not supported. Please open a new bug report.`) } else { expression satisfies never } }
if (declaration) { if (declaration.type === 'FunctionDeclaration') { allSpecifiers.push({ name: declaration.id.name }) } else if (declaration.type === 'VariableDeclaration') { declaration.declarations.forEach((declaration) => { traversePattern(declaration.id) }) } else if (declaration.type === 'ClassDeclaration') { allSpecifiers.push({ name: declaration.id.name }) } else { declaration satisfies never } m.remove(node.start, (declaration as Positioned<Declaration>).start) }
const specifiers = node.specifiers || [] const source = node.source
if (!source && specifiers.length) { specifiers.forEach((specifier) => { allSpecifiers.push({ alias: getArbitraryModuleIdentifier(specifier.exported), name: getArbitraryModuleIdentifier(specifier.local), }) }) m.remove(node.start, node.end) } else if (source && specifiers.length) { const importNames: [string, string][] = []
specifiers.forEach((specifier) => { const importedName = `__vitest_imported_${importIndex++}__` importNames.push([getArbitraryModuleIdentifier(specifier.local), importedName]) allSpecifiers.push({ name: importedName, alias: getArbitraryModuleIdentifier(specifier.exported), }) })
const importString = `import { ${importNames .map(([name, alias]) => `${name} as ${alias}`) .join(', ')} } from '${source.value}'`
m.overwrite(node.start, node.end, importString) } } if (_node.type === 'ExportDefaultDeclaration') { const node = _node as Positioned<ExportDefaultDeclaration> const declaration = node.declaration as Positioned<Expression> allSpecifiers.push({ name: '__vitest_default', alias: 'default' }) m.overwrite(node.start, declaration.start, `const __vitest_default = `) } } replacers.forEach((cb) => cb()) const moduleObject = `const __vitest_current_es_module__ = { __esModule: true, ${allSpecifiers.map(({ name }) => `["${name}"]: ${name},`).join('\n ')}}const __vitest_mocked_module__ = globalThis[${globalThisAccessor}].mockObject(__vitest_current_es_module__, "${mockType}")` const assigning = allSpecifiers .map(({ name }, index) => { return `const __vitest_mocked_${index}__ = __vitest_mocked_module__["${name}"]` }) .join('\n')
const redeclarations = allSpecifiers .map(({ name, alias }, index) => { return ` __vitest_mocked_${index}__ as ${alias || name},` }) .join('\n') const specifiersExports = `export {${redeclarations}}` m.append(moduleObject + assigning + specifiersExports) return m}