Skip to content

Commit cd423ba

Browse files
authored
fix(mocker): fix native loader mocking edge cases (#11540)
1 parent caaef94 commit cd423ba

3 files changed

Lines changed: 100 additions & 17 deletions

File tree

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,10 @@ export function hoistMocks(
290290
let hasMockApiCall = false
291291

292292
esmWalker(ast, {
293+
// called after all call expressions are visited
293294
onIdentifier(id, info, parentStack) {
294295
const binding = idToImportMap.get(id.name)
295-
if (!binding) {
296+
if (!binding || !hasMockApiCall) {
296297
return
297298
}
298299

‎packages/mocker/src/node/parsers.ts‎

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,22 @@ export function collectModuleExports(
3939
if (format === 'module') {
4040
const [imports_, exports_] = parseModuleSyntax(code, filename)
4141
const fileExports = exports_.map((p) => p.n)
42+
// cached before re-exports are parsed, so circular re-exports stop here
43+
cachedFileExports.set(filename, fileExports)
4244
imports_.forEach(({ ss: start, se: end, n: name }) => {
4345
const substring = code.substring(start, end).replace(/ +/g, ' ')
4446
if (name && substring.startsWith('export *') && !substring.startsWith('export * as')) {
4547
fileExports.push(...tryParseModule(name))
4648
}
4749
})
48-
cachedFileExports.set(filename, fileExports)
4950
exports.push(...fileExports)
5051
} else {
5152
const { exports: exports_, reexports } = parseCjsSyntax(code, filename)
5253
const fileExports = [...exports_]
54+
cachedFileExports.set(filename, fileExports)
5355
reexports.forEach((name) => {
5456
fileExports.push(...tryParseModule(name))
5557
})
56-
cachedFileExports.set(filename, fileExports)
5758
exports.push(...fileExports)
5859
}
5960

@@ -96,27 +97,26 @@ export function collectModuleExports(
9697
return cachedFileExports.get(resolvedModulePath)!
9798
}
9899

99-
const fileContent = readFileSync(resolvedModulePath, 'utf-8')
100100
const ext = extname(resolvedModulePath)
101-
const code = transformCode(fileContent, resolvedModulePath)
102-
if (code == null) {
103-
cachedFileExports.set(resolvedModulePath, [])
104-
return []
105-
}
106-
107-
const resolvedModuleFormat = resolveModuleFormat(resolvedModulePath, code)
108101
if (ext === '.json') {
109102
return ['default']
110-
} else {
103+
}
104+
105+
const fileContent = readFileSync(resolvedModulePath, 'utf-8')
106+
const code = transformCode(fileContent, resolvedModulePath)
107+
const resolvedModuleFormat = resolveModuleFormat(
108+
pathToFileURL(resolvedModulePath).toString(),
109+
code,
110+
)
111+
if (!resolvedModuleFormat) {
111112
// can't do wasm, for example
112113
console.warn(
113-
`Cannot process '${resolvedModuleFormat}' imported from ${filename} because of unknown file extension: ${ext}.`,
114+
`Cannot process '${resolvedModulePath}' imported from ${filename} because of unknown file extension: ${ext}.`,
114115
)
116+
cachedFileExports.set(resolvedModulePath, [])
117+
return []
115118
}
116-
if (resolvedModuleFormat) {
117-
return collectModuleExports(resolvedModulePath, code, resolvedModuleFormat, exports)
118-
}
119-
return []
119+
return collectModuleExports(resolvedModulePath, code, resolvedModuleFormat, exports)
120120
}
121121

122122
return Array.from(new Set(exports))

‎test/e2e/test/no-module-runner.test.ts‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,88 @@ if (import.meta.vitest) {
508508
`)
509509
})
510510

511+
test('imports are kept when the file has no hoisted calls', async () => {
512+
const { stderr, testTree } = await runNoViteModuleRunnerTests({
513+
'add.js': `export const add = (a, b) => a + b`,
514+
'comment.test.js': /* js */ `
515+
import { add } from './add.js'
516+
// vi.mock('./add.js')
517+
test('add', () => {
518+
expect(add(1, 2)).toBe(3)
519+
})
520+
`,
521+
'import-actual.test.js': /* js */ `
522+
import { add } from './add.js'
523+
test('add', async () => {
524+
const actual = await vi.importActual('./add.js')
525+
expect(add).toBe(actual.add)
526+
})
527+
`,
528+
})
529+
expect(stderr).toBe('')
530+
expect(testTree()).toMatchInlineSnapshot(`
531+
{
532+
"comment.test.js": {
533+
"add": "passed",
534+
},
535+
"import-actual.test.js": {
536+
"add": "passed",
537+
},
538+
}
539+
`)
540+
})
541+
542+
test('manual mock keeps names re-exported with export *', async () => {
543+
const { stderr, testTree } = await runNoViteModuleRunnerTests({
544+
'base.js': `export function fromBase() { return 'base' }`,
545+
'star.js': /* js */ `
546+
export * from './base.js'
547+
export function own() { return 'own' }
548+
`,
549+
'star.test.js': /* js */ `
550+
import { fromBase, own } from './star.js'
551+
vi.mock(import('./star.js'), () => ({
552+
own: () => 'mocked-own',
553+
fromBase: () => 'mocked-base',
554+
}))
555+
test('re-exported names are mocked', () => {
556+
expect(own()).toBe('mocked-own')
557+
expect(fromBase()).toBe('mocked-base')
558+
})
559+
`,
560+
'users.js': /* js */ `
561+
export * from './posts.js'
562+
export const getUser = () => 'user'
563+
`,
564+
'posts.js': /* js */ `
565+
export * from './users.js'
566+
export const getPost = () => 'post'
567+
`,
568+
'circular.test.js': /* js */ `
569+
import { getPost, getUser } from './users.js'
570+
vi.mock(import('./users.js'), () => ({
571+
getUser: () => 'mocked-user',
572+
getPost: () => 'mocked-post',
573+
}))
574+
test('circular re-exports are mocked', () => {
575+
expect(getUser()).toBe('mocked-user')
576+
expect(getPost()).toBe('mocked-post')
577+
})
578+
`,
579+
})
580+
expect(stderr).toBe('')
581+
expect(testTree()).toMatchInlineSnapshot(`
582+
{
583+
"circular.test.js": {
584+
"circular re-exports are mocked": "passed",
585+
},
586+
"star.test.js": {
587+
"re-exported names are mocked": "passed",
588+
},
589+
}
590+
`)
591+
})
592+
511593
test('cannot import JS file without extension in ESM', async () => {
512594
const { stderr, root } = await runNoViteModuleRunnerTests({
513595
'add.js': /* js */ `

0 commit comments

Comments
 (0)