Skip to content

Commit 7719e79

Browse files
sheremet-vaDunqing
andauthoredJan 5, 2024
fix(vitest): vi.mock breaks tests when using imported variables inside the factory (#4873)
Co-authored-by: Dunqing <dengqing0821@gmail.com>
1 parent c946b1c commit 7719e79

File tree

2 files changed

+79
-16
lines changed

2 files changed

+79
-16
lines changed
 

‎packages/vitest/src/node/hoistMocks.ts

+20-15
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ export function hoistMocks(code: string, id: string, parse: PluginContext['parse
7878

7979
const hoistIndex = code.match(hashbangRE)?.[0].length ?? 0
8080

81-
let hoistedCode = ''
8281
let hoistedVitestImports = ''
8382

8483
let uid = 0
@@ -148,6 +147,7 @@ export function hoistMocks(code: string, id: string, parse: PluginContext['parse
148147
}
149148

150149
const declaredConst = new Set<string>()
150+
const hoistedNodes: Node[] = []
151151

152152
esmWalker(ast, {
153153
onIdentifier(id, info, parentStack) {
@@ -184,12 +184,8 @@ export function hoistMocks(code: string, id: string, parse: PluginContext['parse
184184
) {
185185
const methodName = node.callee.property.name
186186

187-
if (methodName === 'mock' || methodName === 'unmock') {
188-
const end = getBetterEnd(code, node)
189-
const nodeCode = code.slice(node.start, end)
190-
hoistedCode += `${nodeCode}${nodeCode.endsWith('\n') ? '' : '\n'}`
191-
s.remove(node.start, end)
192-
}
187+
if (methodName === 'mock' || methodName === 'unmock')
188+
hoistedNodes.push(node)
193189

194190
if (methodName === 'hoisted') {
195191
const declarationNode = findNodeAround(ast, node.start, 'VariableDeclaration')?.node as Positioned<VariableDeclaration> | undefined
@@ -212,23 +208,32 @@ export function hoistMocks(code: string, id: string, parse: PluginContext['parse
212208

213209
if (canMoveDeclaration) {
214210
// hoist "const variable = vi.hoisted(() => {})"
215-
const end = getBetterEnd(code, declarationNode)
216-
const nodeCode = code.slice(declarationNode.start, end)
217-
hoistedCode += `${nodeCode}${nodeCode.endsWith('\n') ? '' : '\n'}`
218-
s.remove(declarationNode.start, end)
211+
hoistedNodes.push(declarationNode)
219212
}
220213
else {
221214
// hoist "vi.hoisted(() => {})"
222-
const end = getBetterEnd(code, node)
223-
const nodeCode = code.slice(node.start, end)
224-
hoistedCode += `${nodeCode}${nodeCode.endsWith('\n') ? '' : '\n'}`
225-
s.remove(node.start, end)
215+
hoistedNodes.push(node)
226216
}
227217
}
228218
}
229219
},
230220
})
231221

222+
// Wait for imports to be hoisted and then hoist the mocks
223+
const hoistedCode = hoistedNodes.map((node) => {
224+
const end = getBetterEnd(code, node)
225+
/**
226+
* In the following case, we need to change the `user` to user: __vi_import_x__.user
227+
* So we should get the latest code from `s`.
228+
*
229+
* import user from './user'
230+
* vi.mock('./mock.js', () => ({ getSession: vi.fn().mockImplementation(() => ({ user })) }))
231+
*/
232+
const nodeCode = s.slice(node.start, end)
233+
s.remove(node.start, end)
234+
return `${nodeCode}${nodeCode.endsWith('\n') ? '' : '\n'}`
235+
}).join('')
236+
232237
if (hoistedCode || hoistedVitestImports) {
233238
s.prepend(
234239
hoistedVitestImports

‎test/core/test/injector-mock.test.ts

+59-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ describe('transform', () => {
9999
}
100100
test('default import', async () => {
101101
expect(
102-
await hoistSimpleCodeWithoutMocks(`import foo from 'vue';console.log(foo.bar)`),
102+
hoistSimpleCodeWithoutMocks(`import foo from 'vue';console.log(foo.bar)`),
103103
).toMatchInlineSnapshot(`
104104
"const { vi } = await import('vitest')
105105
vi.mock('faker');
@@ -109,6 +109,64 @@ describe('transform', () => {
109109
`)
110110
})
111111

112+
test('can use imported variables inside the mock', () => {
113+
expect(
114+
hoistMocks(`
115+
import { vi } from 'vitest'
116+
import user from './user'
117+
import { admin } from './admin'
118+
vi.mock('./mock.js', () => ({
119+
getSession: vi.fn().mockImplementation(() => ({
120+
user,
121+
admin: admin,
122+
}))
123+
}))
124+
`, './test.js', parse)?.code.trim(),
125+
).toMatchInlineSnapshot(`
126+
"const { vi } = await import('vitest')
127+
vi.mock('./mock.js', () => ({
128+
getSession: vi.fn().mockImplementation(() => ({
129+
user: __vi_import_0__.default,
130+
admin: __vi_import_1__.admin,
131+
}))
132+
}))
133+
const __vi_import_0__ = await import('./user')
134+
const __vi_import_1__ = await import('./admin')"
135+
`)
136+
})
137+
138+
test('can use hoisted variables inside the mock', () => {
139+
expect(
140+
hoistMocks(`
141+
import { vi } from 'vitest'
142+
const { user, admin } = await vi.hoisted(async () => {
143+
const { default: user } = await import('./user')
144+
const { admin } = await import('./admin')
145+
return { user, admin }
146+
})
147+
vi.mock('./mock.js', () => {
148+
getSession: vi.fn().mockImplementation(() => ({
149+
user,
150+
admin: admin,
151+
}))
152+
})
153+
`, './test.js', parse)?.code.trim(),
154+
).toMatchInlineSnapshot(`
155+
"const { vi } = await import('vitest')
156+
const { user, admin } = await vi.hoisted(async () => {
157+
const { default: user } = await import('./user')
158+
const { admin } = await import('./admin')
159+
return { user, admin }
160+
})
161+
vi.mock('./mock.js', () => {
162+
getSession: vi.fn().mockImplementation(() => ({
163+
user,
164+
admin: admin,
165+
}))
166+
})"
167+
`)
168+
})
169+
112170
test('named import', async () => {
113171
expect(
114172
await hoistSimpleCodeWithoutMocks(

0 commit comments

Comments
 (0)
Please sign in to comment.