From 26c503df831370cee0d70a290e6f717186782557 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Tue, 29 Oct 2024 13:54:26 +0100 Subject: [PATCH] fix(browser): improve source maps when `vi.mock` is present --- packages/mocker/src/node/hoistMocksPlugin.ts | 211 ++++++------ test/core/test/injector-mock.test.ts | 319 ++++++++++--------- 2 files changed, 268 insertions(+), 262 deletions(-) diff --git a/packages/mocker/src/node/hoistMocksPlugin.ts b/packages/mocker/src/node/hoistMocksPlugin.ts index 5515b3b1f09b..f0252a1dbb5b 100644 --- a/packages/mocker/src/node/hoistMocksPlugin.ts +++ b/packages/mocker/src/node/hoistMocksPlugin.ts @@ -6,16 +6,16 @@ import type { Expression, Identifier, ImportDeclaration, - ImportExpression, VariableDeclaration, } from 'estree' import type { SourceMap } from 'magic-string' +import type { RollupAstNode } from 'rollup' import type { Plugin, Rollup } from 'vite' import type { Node, Positioned } from './esmWalker' import { findNodeAround } from 'acorn-walk' import MagicString from 'magic-string' import { createFilter } from 'vite' -import { esmWalker, getArbitraryModuleIdentifier } from './esmWalker' +import { esmWalker } from './esmWalker' interface HoistMocksOptions { /** @@ -106,11 +106,14 @@ function isIdentifier(node: any): node is Positioned { return node.type === 'Identifier' } -function getBetterEnd(code: string, node: Node) { +function getNodeTail(code: string, node: Node) { let end = node.end if (code[node.end] === ';') { end += 1 } + if (code[node.end] === '\n') { + return end + 1 + } if (code[node.end + 1] === '\n') { end += 1 } @@ -163,45 +166,41 @@ export function hoistMocks( hoistedModules = ['vitest'], } = options - const hoistIndex = code.match(hashbangRE)?.[0].length ?? 0 + // hoist at the start of the file, after the hashbang + let hoistIndex = hashbangRE.exec(code)?.[0].length ?? 0 let hoistedModuleImported = false let uid = 0 const idToImportMap = new Map() + const imports: { + node: RollupAstNode + id: string + }[] = [] + // this will transform import statements into dynamic ones, if there are imports // it will keep the import as is, if we don't need to mock anything // in browser environment it will wrap the module value with "vitest_wrap_module" function // that returns a proxy to the module so that named exports can be mocked - const transformImportDeclaration = (node: ImportDeclaration) => { - const source = node.source.value as string - - const importId = `__vi_import_${uid++}__` - const hasSpecifiers = node.specifiers.length > 0 - const code = hasSpecifiers - ? `const ${importId} = await import('${source}')\n` - : `await import('${source}')\n` - return { - code, - id: importId, - } - } - - function defineImport(node: Positioned) { + function defineImport( + index: number, + importNode: ImportDeclaration & { + start: number + end: number + }, + ) { + const source = importNode.source.value as string // always hoist vitest import to top of the file, so // "vi" helpers can access it - if (hoistedModules.includes(node.source.value as string)) { + if (hoistedModules.includes(source)) { hoistedModuleImported = true return } + const importId = `__vi_import_${uid++}__` + imports.push({ id: importId, node: importNode }) - const declaration = transformImportDeclaration(node) - if (!declaration) { - return null - } - s.appendLeft(hoistIndex, declaration.code) - return declaration.id + return importId } // 1. check all import statements and record id -> importName map @@ -210,17 +209,24 @@ export function hoistMocks( // import { baz } from 'foo' --> baz -> __import_foo__.baz // import * as ok from 'foo' --> ok -> __import_foo__ if (node.type === 'ImportDeclaration') { - const importId = defineImport(node) + const importId = defineImport(hoistIndex, node) if (!importId) { continue } - s.remove(node.start, getBetterEnd(code, node)) for (const spec of node.specifiers) { if (spec.type === 'ImportSpecifier') { - idToImportMap.set( - spec.local.name, - `${importId}.${getArbitraryModuleIdentifier(spec.imported)}`, - ) + if (spec.imported.type === 'Identifier') { + idToImportMap.set( + spec.local.name, + `${importId}.${spec.imported.name}`, + ) + } + else { + idToImportMap.set( + spec.local.name, + `${importId}[${JSON.stringify(spec.imported.value as string)}]`, + ) + } } else if (spec.type === 'ImportDefaultSpecifier') { idToImportMap.set(spec.local.name, `${importId}.default`) @@ -235,7 +241,7 @@ export function hoistMocks( const declaredConst = new Set() const hoistedNodes: Positioned< - CallExpression | VariableDeclaration | AwaitExpression + CallExpression | VariableDeclaration | AwaitExpression >[] = [] function createSyntaxError(node: Positioned, message: string) { @@ -347,6 +353,35 @@ export function hoistMocks( `Cannot export the result of "${method}". Remove export declaration because "${method}" doesn\'t return anything.`, ) } + // rewrite vi.mock(import('..')) into vi.mock('..') + if ( + node.type === 'CallExpression' + && node.callee.type === 'MemberExpression' + && dynamicImportMockMethodNames.includes((node.callee.property as Identifier).name) + ) { + const moduleInfo = node.arguments[0] as Positioned + // vi.mock(import('./path')) -> vi.mock('./path') + if (moduleInfo.type === 'ImportExpression') { + const source = moduleInfo.source as Positioned + s.overwrite( + moduleInfo.start, + moduleInfo.end, + s.slice(source.start, source.end), + ) + } + // vi.mock(await import('./path')) -> vi.mock('./path') + if ( + moduleInfo.type === 'AwaitExpression' + && moduleInfo.argument.type === 'ImportExpression' + ) { + const source = moduleInfo.argument.source as Positioned + s.overwrite( + moduleInfo.start, + moduleInfo.end, + s.slice(source.start, source.end), + ) + } + } hoistedNodes.push(node) } // vi.doMock(import('./path')) -> vi.doMock('./path') @@ -384,7 +419,6 @@ export function hoistMocks( declarationNode, 'Cannot export hoisted variable. You can control hoisting behavior by placing the import from this file first.', ) - // hoist "const variable = vi.hoisted(() => {})" hoistedNodes.push(declarationNode) } else { @@ -393,10 +427,8 @@ export function hoistMocks( node.start, 'AwaitExpression', )?.node as Positioned | undefined - // hoist "await vi.hoisted(async () => {})" or "vi.hoisted(() => {})" - hoistedNodes.push( - awaitedExpression?.argument === node ? awaitedExpression : node, - ) + const moveNode = awaitedExpression?.argument === node ? awaitedExpression : node + hoistedNodes.push(moveNode) } } } @@ -446,24 +478,6 @@ export function hoistMocks( ) } - function rewriteMockDynamicImport( - nodeCode: string, - moduleInfo: Positioned, - expressionStart: number, - expressionEnd: number, - mockStart: number, - ) { - const source = moduleInfo.source as Positioned - const importPath = s.slice(source.start, source.end) - const nodeCodeStart = expressionStart - mockStart - const nodeCodeEnd = expressionEnd - mockStart - return ( - nodeCode.slice(0, nodeCodeStart) - + importPath - + nodeCode.slice(nodeCodeEnd) - ) - } - // validate hoistedNodes doesn't have nodes inside other nodes for (let i = 0; i < hoistedNodes.length; i++) { const node = hoistedNodes[i] @@ -479,61 +493,42 @@ export function hoistMocks( } } - // Wait for imports to be hoisted and then hoist the mocks - const hoistedCode = hoistedNodes - .map((node) => { - const end = getBetterEnd(code, node) - /** - * In the following case, we need to change the `user` to user: __vi_import_x__.user - * So we should get the latest code from `s`. - * - * import user from './user' - * vi.mock('./mock.js', () => ({ getSession: vi.fn().mockImplementation(() => ({ user })) })) - */ - let nodeCode = s.slice(node.start, end) - - // rewrite vi.mock(import('..')) into vi.mock('..') - if ( - node.type === 'CallExpression' - && node.callee.type === 'MemberExpression' - && dynamicImportMockMethodNames.includes((node.callee.property as Identifier).name) - ) { - const moduleInfo = node.arguments[0] as Positioned - // vi.mock(import('./path')) -> vi.mock('./path') - if (moduleInfo.type === 'ImportExpression') { - nodeCode = rewriteMockDynamicImport( - nodeCode, - moduleInfo, - moduleInfo.start, - moduleInfo.end, - node.start, - ) - } - // vi.mock(await import('./path')) -> vi.mock('./path') - if ( - moduleInfo.type === 'AwaitExpression' - && moduleInfo.argument.type === 'ImportExpression' - ) { - nodeCode = rewriteMockDynamicImport( - nodeCode, - moduleInfo.argument as Positioned, - moduleInfo.start, - moduleInfo.end, - node.start, - ) - } - } + // hoist vi.mock/vi.hoisted + for (const node of hoistedNodes) { + const end = getNodeTail(code, node) + if (hoistIndex === end) { + hoistIndex = end + } + else if (hoistIndex !== node.start) { + s.move(node.start, end, hoistIndex) + } + } - s.remove(node.start, end) - return `${nodeCode}${nodeCode.endsWith('\n') ? '' : '\n'}` - }) - .join('') + // hoist actual dynamic imports last so they are inserted after all hoisted mocks + for (const { node: importNode, id: importId } of imports) { + const source = importNode.source.value as string - if (hoistedCode || hoistedModuleImported) { - s.prepend( - (!hoistedModuleImported && hoistedCode ? API_NOT_FOUND_CHECK(utilsObjectNames) : '') - + hoistedCode, + s.update( + importNode.start, + importNode.end, + `const ${importId} = await import(${JSON.stringify( + source, + )});\n`, ) + + if (importNode.start === hoistIndex) { + // no need to hoist, but update hoistIndex to keep the order + hoistIndex = importNode.end + } + else { + // There will be an error if the module is called before it is imported, + // so the module import statement is hoisted to the top + s.move(importNode.start, importNode.end, hoistIndex) + } + } + + if (!hoistedModuleImported && hoistedNodes.length) { + s.prepend(API_NOT_FOUND_CHECK(utilsObjectNames)) } return { diff --git a/test/core/test/injector-mock.test.ts b/test/core/test/injector-mock.test.ts index 80f8b2d35b77..c6ebbe8565d4 100644 --- a/test/core/test/injector-mock.test.ts +++ b/test/core/test/injector-mock.test.ts @@ -19,7 +19,7 @@ const hoistMocksOptions: HoistMocksPluginOptions = { }, } -async function hoistSimple(code: string, url = '') { +function hoistSimple(code: string, url = '') { return hoistMocks(code, url, parse, hoistMocksOptions) } @@ -42,21 +42,18 @@ test('hoists mock, unmock, hoisted', () => { test('always hoists import from vitest', () => { expect(hoistSimpleCode(` - import { vi } from 'vitest' - vi.mock('path', () => {}) - vi.unmock('path') - vi.hoisted(() => {}) - import { test } from 'vitest' +import { vi } from 'vitest' +vi.mock('path', () => {}) +vi.unmock('path') +vi.hoisted(() => {}) +import { test } from 'vitest' `)).toMatchInlineSnapshot(` "vi.mock('path', () => {}) vi.unmock('path') vi.hoisted(() => {}) - import { vi } from 'vitest' - - - - import { test } from 'vitest'" + import { vi } from 'vitest' + import { test } from 'vitest'" `) }) @@ -73,16 +70,13 @@ test('always hoists all imports but they are under mocks', () => { "vi.mock('path', () => {}) vi.unmock('path') vi.hoisted(() => {}) - const __vi_import_0__ = await import('./path.js') - const __vi_import_1__ = await import('./path2.js') + const __vi_import_0__ = await import("./path.js"); + const __vi_import_1__ = await import("./path2.js"); import { vi } from 'vitest' - - - - import { test } from 'vitest'" + import { test } from 'vitest'" `) }) @@ -93,7 +87,7 @@ test('correctly mocks namespaced', () => { vi.mock('../src/add', () => {}) `)).toMatchInlineSnapshot(` "vi.mock('../src/add', () => {}) - const __vi_import_0__ = await import('../src/add') + const __vi_import_0__ = await import("../src/add"); import { vi } from 'vitest'" `) @@ -107,7 +101,7 @@ test('correctly access import', () => { vi.mock('../src/add', () => {}) `)).toMatchInlineSnapshot(` "vi.mock('../src/add', () => {}) - const __vi_import_0__ = await import('../src/add') + const __vi_import_0__ = await import("../src/add"); import { vi } from 'vitest' @@ -117,14 +111,14 @@ test('correctly access import', () => { describe('transform', () => { const hoistSimpleCodeWithoutMocks = (code: string) => { - return hoistMocks(`import {vi} from "vitest";\n${code}\nvi.mock('faker');`, '/test.js', parse, hoistMocksOptions)?.code.trim() + return hoistMocks(`import {vi} from "vitest";\n${code}\nvi.mock('faker');\n`, '/test.js', parse, hoistMocksOptions)?.code.trim() } - test('default import', async () => { + test('default import', () => { expect( hoistSimpleCodeWithoutMocks(`import foo from 'vue';console.log(foo.bar)`), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; console.log(__vi_import_0__.default.bar)" `) @@ -150,8 +144,8 @@ vi.mock('./mock.js', () => ({ admin: __vi_import_1__.admin, })) })) - const __vi_import_0__ = await import('./user') - const __vi_import_1__ = await import('./admin') + const __vi_import_0__ = await import("./user"); + const __vi_import_1__ = await import("./admin"); import { vi } from 'vitest'" `) @@ -190,34 +184,34 @@ vi.mock('./mock.js', () => { `) }) - test('named import', async () => { + test('named import', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( `import { ref } from 'vue';function foo() { return ref(0) }`, ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; function foo() { return __vi_import_0__.ref(0) }" `) }) - test('namespace import', async () => { + test('namespace import', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( `import * as vue from 'vue';function foo() { return vue.ref(0) }`, ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; function foo() { return __vi_import_0__.ref(0) }" `) }) - test('export function declaration', async () => { - expect(await hoistSimpleCodeWithoutMocks(`export function foo() {}`)) + test('export function declaration', () => { + expect(hoistSimpleCodeWithoutMocks(`export function foo() {}`)) .toMatchInlineSnapshot(` "vi.mock('faker'); import {vi} from "vitest"; @@ -225,8 +219,8 @@ vi.mock('./mock.js', () => { `) }) - test('export class declaration', async () => { - expect(await hoistSimpleCodeWithoutMocks(`export class foo {}`)) + test('export class declaration', () => { + expect(hoistSimpleCodeWithoutMocks(`export class foo {}`)) .toMatchInlineSnapshot(` "vi.mock('faker'); import {vi} from "vitest"; @@ -234,8 +228,8 @@ vi.mock('./mock.js', () => { `) }) - test('export var declaration', async () => { - expect(await hoistSimpleCodeWithoutMocks(`export const a = 1, b = 2`)) + test('export var declaration', () => { + expect(hoistSimpleCodeWithoutMocks(`export const a = 1, b = 2`)) .toMatchInlineSnapshot(` "vi.mock('faker'); import {vi} from "vitest"; @@ -243,9 +237,9 @@ vi.mock('./mock.js', () => { `) }) - test('export named', async () => { + test('export named', () => { expect( - await hoistSimpleCodeWithoutMocks(`const a = 1, b = 2; export { a, b as c }`), + hoistSimpleCodeWithoutMocks(`const a = 1, b = 2; export { a, b as c }`), ).toMatchInlineSnapshot(` "vi.mock('faker'); import {vi} from "vitest"; @@ -253,9 +247,9 @@ vi.mock('./mock.js', () => { `) }) - test('export named from', async () => { + test('export named from', () => { expect( - await hoistSimpleCodeWithoutMocks(`export { ref, computed as c } from 'vue'`), + hoistSimpleCodeWithoutMocks(`export { ref, computed as c } from 'vue'`), ).toMatchInlineSnapshot(` "vi.mock('faker'); import {vi} from "vitest"; @@ -263,22 +257,22 @@ vi.mock('./mock.js', () => { `) }) - test('named exports of imported binding', async () => { + test('named exports of imported binding', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( `import {createApp} from 'vue';export {createApp}`, ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; export {createApp}" `) }) - test('export * from', async () => { + test('export * from', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( `export * from 'vue'\n` + `export * from 'react'`, ), ).toMatchInlineSnapshot(` @@ -289,8 +283,8 @@ vi.mock('./mock.js', () => { `) }) - test('export * as from', async () => { - expect(await hoistSimpleCodeWithoutMocks(`export * as foo from 'vue'`)) + test('export * as from', () => { + expect(hoistSimpleCodeWithoutMocks(`export * as foo from 'vue'`)) .toMatchInlineSnapshot(` "vi.mock('faker'); import {vi} from "vitest"; @@ -298,9 +292,9 @@ vi.mock('./mock.js', () => { `) }) - test('export default', async () => { + test('export default', () => { expect( - await hoistSimpleCodeWithoutMocks(`export default {}`), + hoistSimpleCodeWithoutMocks(`export default {}`), ).toMatchInlineSnapshot(` "vi.mock('faker'); import {vi} from "vitest"; @@ -308,35 +302,35 @@ vi.mock('./mock.js', () => { `) }) - test('export then import minified', async () => { + test('export then import minified', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( `export * from 'vue';import {createApp} from 'vue';`, ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; export * from 'vue';" `) }) - test('hoist import to top', async () => { + test('hoist import to top', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( `path.resolve('server.js');import path from 'node:path';`, ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('node:path') + const __vi_import_0__ = await import("node:path"); import {vi} from "vitest"; __vi_import_0__.default.resolve('server.js');" `) }) - test('import.meta', async () => { + test('import.meta', () => { expect( - await hoistSimpleCodeWithoutMocks(`console.log(import.meta.url)`), + hoistSimpleCodeWithoutMocks(`console.log(import.meta.url)`), ).toMatchInlineSnapshot(` "vi.mock('faker'); import {vi} from "vitest"; @@ -344,8 +338,8 @@ vi.mock('./mock.js', () => { `) }) - test('dynamic import', async () => { - const result = await hoistSimpleCodeWithoutMocks( + test('dynamic import', () => { + const result = hoistSimpleCodeWithoutMocks( `export const i = () => import('./foo')`, ) expect(result).toMatchInlineSnapshot(` @@ -355,115 +349,115 @@ vi.mock('./mock.js', () => { `) }) - test('do not rewrite method definition', async () => { - const result = await hoistSimpleCodeWithoutMocks( + test('do not rewrite method definition', () => { + const result = hoistSimpleCodeWithoutMocks( `import { fn } from 'vue';class A { fn() { fn() } }`, ) expect(result).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; class A { fn() { __vi_import_0__.fn() } }" `) }) - test('do not rewrite when variable is in scope', async () => { - const result = await hoistSimpleCodeWithoutMocks( + test('do not rewrite when variable is in scope', () => { + const result = hoistSimpleCodeWithoutMocks( `import { fn } from 'vue';function A(){ const fn = () => {}; return { fn }; }`, ) expect(result).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; function A(){ const fn = () => {}; return { fn }; }" `) }) // #5472 - test('do not rewrite when variable is in scope with object destructuring', async () => { - const result = await hoistSimpleCodeWithoutMocks( + test('do not rewrite when variable is in scope with object destructuring', () => { + const result = hoistSimpleCodeWithoutMocks( `import { fn } from 'vue';function A(){ let {fn, test} = {fn: 'foo', test: 'bar'}; return { fn }; }`, ) expect(result).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; function A(){ let {fn, test} = {fn: 'foo', test: 'bar'}; return { fn }; }" `) }) // #5472 - test('do not rewrite when variable is in scope with array destructuring', async () => { - const result = await hoistSimpleCodeWithoutMocks( + test('do not rewrite when variable is in scope with array destructuring', () => { + const result = hoistSimpleCodeWithoutMocks( `import { fn } from 'vue';function A(){ let [fn, test] = ['foo', 'bar']; return { fn }; }`, ) expect(result).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; function A(){ let [fn, test] = ['foo', 'bar']; return { fn }; }" `) }) // #5727 - test('rewrite variable in string interpolation in function nested arguments', async () => { - const result = await hoistSimpleCodeWithoutMocks( + test('rewrite variable in string interpolation in function nested arguments', () => { + const result = hoistSimpleCodeWithoutMocks( `import { fn } from 'vue';function A({foo = \`test\${fn}\`} = {}){ return {}; }`, ) expect(result).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; function A({foo = \`test\${__vi_import_0__.fn}\`} = {}){ return {}; }" `) }) // #6520 - test('rewrite variables in default value of destructuring params', async () => { - const result = await hoistSimpleCodeWithoutMocks( + test('rewrite variables in default value of destructuring params', () => { + const result = hoistSimpleCodeWithoutMocks( `import { fn } from 'vue';function A({foo = fn}){ return {}; }`, ) expect(result).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; function A({foo = __vi_import_0__.fn}){ return {}; }" `) }) - test('do not rewrite when function declaration is in scope', async () => { - const result = await hoistSimpleCodeWithoutMocks( + test('do not rewrite when function declaration is in scope', () => { + const result = hoistSimpleCodeWithoutMocks( `import { fn } from 'vue';function A(){ function fn() {}; return { fn }; }`, ) expect(result).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; function A(){ function fn() {}; return { fn }; }" `) }) - test('do not rewrite catch clause', async () => { - const result = await hoistSimpleCodeWithoutMocks( + test('do not rewrite catch clause', () => { + const result = hoistSimpleCodeWithoutMocks( `import {error} from './dependency';try {} catch(error) {}`, ) expect(result).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('./dependency') + const __vi_import_0__ = await import("./dependency"); import {vi} from "vitest"; try {} catch(error) {}" `) }) // #2221 - test('should declare variable for imported super class', async () => { + test('should declare variable for imported super class', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( `import { Foo } from './dependency';` + `class A extends Foo {}`, ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('./dependency') + const __vi_import_0__ = await import("./dependency"); import {vi} from "vitest"; const Foo = __vi_import_0__.Foo; class A extends Foo {}" @@ -472,14 +466,14 @@ vi.mock('./mock.js', () => { // exported classes: should prepend the declaration at root level, before the // first class that uses the binding expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( `import { Foo } from './dependency';` + `export default class A extends Foo {}\n` + `export class B extends Foo {}`, ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('./dependency') + const __vi_import_0__ = await import("./dependency"); import {vi} from "vitest"; const Foo = __vi_import_0__.Foo; export default class A extends Foo {} @@ -488,16 +482,16 @@ vi.mock('./mock.js', () => { }) // #4049 - test('should handle default export variants', async () => { + test('should handle default export variants', () => { // default anonymous functions - expect(await hoistSimpleCodeWithoutMocks(`export default function() {}\n`)) + expect(hoistSimpleCodeWithoutMocks(`export default function() {}\n`)) .toMatchInlineSnapshot(` "vi.mock('faker'); import {vi} from "vitest"; export default function() {}" `) // default anonymous class - expect(await hoistSimpleCodeWithoutMocks(`export default class {}\n`)) + expect(hoistSimpleCodeWithoutMocks(`export default class {}\n`)) .toMatchInlineSnapshot(` "vi.mock('faker'); import {vi} from "vitest"; @@ -505,7 +499,7 @@ vi.mock('./mock.js', () => { `) // default named functions expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( `export default function foo() {}\n` + `foo.prototype = Object.prototype;`, ), @@ -517,7 +511,7 @@ vi.mock('./mock.js', () => { `) // default named classes expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( `export default class A {}\n` + `export class B extends A {}`, ), ).toMatchInlineSnapshot(` @@ -528,9 +522,9 @@ vi.mock('./mock.js', () => { `) }) - test('sourcemap source', async () => { + test('sourcemap source', () => { const map = ( - (await hoistSimple( + (hoistSimple( `vi.mock(any); export const a = 1`, 'input.js', @@ -539,9 +533,19 @@ vi.mock('./mock.js', () => { expect(map?.sources).toStrictEqual(['input.js']) }) - test('overwrite bindings', async () => { + test('overwrite bindings', () => { + // console.log(hoistSimpleCodeWithoutMocks( + // `import { inject } from 'vue';` + // + `const a = { inject }\n` + // + `const b = { test: inject }\n` + // + `function c() { const { test: inject } = { test: true }; console.log(inject) }\n` + // + `const d = inject\n` + // + `function f() { console.log(inject) }\n` + // + `function e() { const { inject } = { inject: true } }\n` + // + `function g() { const f = () => { const inject = true }; console.log(inject) }\n`, + // )) expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( `import { inject } from 'vue';` + `const a = { inject }\n` + `const b = { test: inject }\n` @@ -553,7 +557,7 @@ vi.mock('./mock.js', () => { ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; const a = { inject: __vi_import_0__.inject } const b = { test: __vi_import_0__.inject } @@ -565,9 +569,9 @@ vi.mock('./mock.js', () => { `) }) - test('Empty array pattern', async () => { + test('Empty array pattern', () => { expect( - await hoistSimpleCodeWithoutMocks(`const [, LHS, RHS] = inMatch;`), + hoistSimpleCodeWithoutMocks(`const [, LHS, RHS] = inMatch;`), ).toMatchInlineSnapshot(` "vi.mock('faker'); import {vi} from "vitest"; @@ -575,9 +579,9 @@ vi.mock('./mock.js', () => { `) }) - test('function argument destructure', async () => { + test('function argument destructure', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( ` import { foo, bar } from 'foo' const a = ({ _ = foo() }) => {} @@ -587,7 +591,7 @@ function c({ _ = bar() + foo() }) {} ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('foo') + const __vi_import_0__ = await import("foo"); import {vi} from "vitest"; @@ -597,9 +601,9 @@ function c({ _ = bar() + foo() }) {} `) }) - test('object destructure alias', async () => { + test('object destructure alias', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( ` import { n } from 'foo' const a = () => { @@ -610,7 +614,7 @@ const a = () => { ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('foo') + const __vi_import_0__ = await import("foo"); import {vi} from "vitest"; @@ -622,7 +626,7 @@ const a = () => { // #9585 expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( ` import { n, m } from 'foo' const foo = {} @@ -634,7 +638,7 @@ const foo = {} ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('foo') + const __vi_import_0__ = await import("foo"); import {vi} from "vitest"; @@ -646,9 +650,9 @@ const foo = {} `) }) - test('nested object destructure alias', async () => { + test('nested object destructure alias', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( ` import { remove, add, get, set, rest, objRest } from 'vue' @@ -678,10 +682,11 @@ objRest() ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; + function a() { const { o: { remove }, @@ -707,9 +712,9 @@ objRest() `) }) - test('object props and methods', async () => { + test('object props and methods', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( ` import foo from 'foo' @@ -728,10 +733,11 @@ const obj = { ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('foo') + const __vi_import_0__ = await import("foo"); import {vi} from "vitest"; + const bar = 'bar' const obj = { @@ -746,9 +752,9 @@ const obj = { `) }) - test('class props', async () => { + test('class props', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( ` import { remove, add } from 'vue' @@ -760,10 +766,11 @@ class A { ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; + const add = __vi_import_0__.add; const remove = __vi_import_0__.remove; class A { @@ -773,9 +780,9 @@ class A { `) }) - test('class methods', async () => { + test('class methods', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( ` import foo from 'foo' @@ -792,10 +799,11 @@ class A { ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('foo') + const __vi_import_0__ = await import("foo"); import {vi} from "vitest"; + const bar = 'bar' class A { @@ -808,9 +816,9 @@ class A { `) }) - test('declare scope', async () => { + test('declare scope', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( ` import { aaa, bbb, ccc, ddd } from 'vue' @@ -838,10 +846,11 @@ bbb() ), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('vue') + const __vi_import_0__ = await import("vue"); import {vi} from "vitest"; + function foobar() { ddd() @@ -865,9 +874,9 @@ bbb() `) }) - test('continuous exports', async () => { + test('continuous exports', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( ` export function fn1() { }export function fn2() { @@ -885,7 +894,7 @@ export function fn1() { }) // https://github.com/vitest-dev/vitest/issues/1141 - test('export default expression', async () => { + test('export default expression', () => { // esbuild transform result of following TS code // export default function getRandom() { // return Math.random() @@ -896,7 +905,7 @@ export default (function getRandom() { }); `.trim() - expect(await hoistSimpleCodeWithoutMocks(code)).toMatchInlineSnapshot(` + expect(hoistSimpleCodeWithoutMocks(code)).toMatchInlineSnapshot(` "vi.mock('faker'); import {vi} from "vitest"; export default (function getRandom() { @@ -905,7 +914,7 @@ export default (function getRandom() { `) expect( - await hoistSimpleCodeWithoutMocks(`export default (class A {});`), + hoistSimpleCodeWithoutMocks(`export default (class A {});`), ).toMatchInlineSnapshot(` "vi.mock('faker'); import {vi} from "vitest"; @@ -914,18 +923,18 @@ export default (function getRandom() { }) // #8002 - test('with hashbang', async () => { + test('with hashbang', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( `#!/usr/bin/env node console.log("it can parse the hashbang")`, ), ).toMatchInlineSnapshot(`undefined`) }) - test('import hoisted after hashbang', async () => { + test('import hoisted after hashbang', () => { expect( - await hoistSimpleCodeWithoutMocks( + hoistSimpleCodeWithoutMocks( `#!/usr/bin/env node console.log(foo); import foo from "foo"`, @@ -934,7 +943,7 @@ import foo from "foo"`, }) // #10289 - test('track scope by class, function, condition blocks', async () => { + test('track scope by class, function, condition blocks', () => { const code = ` import { foo, bar } from 'foobar' if (false) { @@ -962,9 +971,9 @@ export class Test { } };`.trim() - expect(await hoistSimpleCodeWithoutMocks(code)).toMatchInlineSnapshot(` + expect(hoistSimpleCodeWithoutMocks(code)).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('foobar') + const __vi_import_0__ = await import("foobar"); import {vi} from "vitest"; if (false) { @@ -995,9 +1004,9 @@ export class Test { }) // #10386 - test('track var scope by function', async () => { + test('track var scope by function', () => { expect( - await hoistSimpleCodeWithoutMocks(` + hoistSimpleCodeWithoutMocks(` import { foo, bar } from 'foobar' function test() { if (true) { @@ -1007,7 +1016,7 @@ function test() { }`), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('foobar') + const __vi_import_0__ = await import("foobar"); import {vi} from "vitest"; @@ -1021,9 +1030,9 @@ function test() { }) // #11806 - test('track scope by blocks', async () => { + test('track scope by blocks', () => { expect( - await hoistSimpleCodeWithoutMocks(` + hoistSimpleCodeWithoutMocks(` import { foo, bar, baz } from 'foobar' function test() { [foo]; @@ -1036,7 +1045,7 @@ function test() { }`), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('foobar') + const __vi_import_0__ = await import("foobar"); import {vi} from "vitest"; @@ -1052,9 +1061,9 @@ function test() { `) }) - test('track scope in for loops', async () => { + test('track scope in for loops', () => { expect( - await hoistSimpleCodeWithoutMocks(` + hoistSimpleCodeWithoutMocks(` import { test } from './test.js' for (const test of tests) { @@ -1070,10 +1079,11 @@ for (const test in tests) { }`), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('./test.js') + const __vi_import_0__ = await import("./test.js"); import {vi} from "vitest"; + for (const test of tests) { console.log(test) } @@ -1088,8 +1098,8 @@ for (const test in tests) { `) }) - test('avoid binding ClassExpression', async () => { - const result = await hoistSimpleCodeWithoutMocks( + test('avoid binding ClassExpression', () => { + const result = hoistSimpleCodeWithoutMocks( ` import Foo, { Bar } from './foo'; @@ -1103,10 +1113,11 @@ const Baz = class extends Foo {} ) expect(result).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('./foo') + const __vi_import_0__ = await import("./foo"); import {vi} from "vitest"; + console.log(__vi_import_0__.default, __vi_import_0__.Bar); const obj = { foo: class Foo {}, @@ -1116,15 +1127,15 @@ const Baz = class extends Foo {} `) }) - test('import assertion attribute', async () => { + test('import assertion attribute', () => { expect( - await hoistSimpleCodeWithoutMocks(` + hoistSimpleCodeWithoutMocks(` import * as foo from './foo.json' with { type: 'json' }; import('./bar.json', { with: { type: 'json' } }); `), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('./foo.json') + const __vi_import_0__ = await import("./foo.json"); import {vi} from "vitest"; @@ -1132,12 +1143,12 @@ const Baz = class extends Foo {} `) }) - test('import and export ordering', async () => { + test('import and export ordering', () => { // Given all imported modules logs `mod ${mod}` on execution, // and `foo` is `bar`, the logging order should be: // "mod a", "mod foo", "mod b", "bar1", "bar2" expect( - await hoistSimpleCodeWithoutMocks(` + hoistSimpleCodeWithoutMocks(` console.log(foo + 1) export * from './a' import { foo } from './foo' @@ -1146,7 +1157,7 @@ console.log(foo + 2) `), ).toMatchInlineSnapshot(` "vi.mock('faker'); - const __vi_import_0__ = await import('./foo') + const __vi_import_0__ = await import("./foo"); import {vi} from "vitest"; console.log(__vi_import_0__.foo + 1) @@ -1157,7 +1168,7 @@ console.log(foo + 2) `) }) - test('handle single "await vi.hoisted"', async () => { + test('handle single "await vi.hoisted"', () => { expect( hoistSimpleCode(` import { vi } from 'vitest';