Skip to content

Commit

Permalink
fix(browser): fix mocking modules out of root (#7415)
Browse files Browse the repository at this point in the history
  • Loading branch information
hi-ogawa authored Feb 4, 2025
1 parent 1154662 commit d3acbd8
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 11 deletions.
11 changes: 6 additions & 5 deletions packages/mocker/src/browser/mocker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,13 @@ export class ModuleMocker {

public async importMock<T>(rawId: string, importer: string): Promise<T> {
await this.prepare()
const { resolvedId, redirectUrl } = await this.rpc.resolveMock(
const { resolvedId, resolvedUrl, redirectUrl } = await this.rpc.resolveMock(
rawId,
importer,
{ mock: 'auto' },
)

const mockUrl = this.resolveMockPath(cleanVersion(resolvedId))
const mockUrl = this.resolveMockPath(cleanVersion(resolvedUrl))
let mock = this.registry.get(mockUrl)

if (!mock) {
Expand Down Expand Up @@ -139,8 +139,8 @@ export class ModuleMocker {
? 'factory'
: factoryOrOptions?.spy ? 'spy' : 'auto',
})
.then(async ({ redirectUrl, resolvedId, needsInterop, mockType }) => {
const mockUrl = this.resolveMockPath(cleanVersion(resolvedId))
.then(async ({ redirectUrl, resolvedId, resolvedUrl, needsInterop, mockType }) => {
const mockUrl = this.resolveMockPath(cleanVersion(resolvedUrl))
this.mockedIds.add(resolvedId)
const factory = typeof factoryOrOptions === 'function'
? async () => {
Expand Down Expand Up @@ -185,7 +185,7 @@ export class ModuleMocker {
if (!resolved) {
return
}
const mockUrl = this.resolveMockPath(cleanVersion(resolved.id))
const mockUrl = this.resolveMockPath(cleanVersion(resolved.url))
this.mockedIds.add(resolved.id)
this.registry.delete(mockUrl)
await this.interceptor.delete(mockUrl)
Expand Down Expand Up @@ -241,6 +241,7 @@ export interface ResolveIdResult {
export interface ResolveMockResult {
mockType: MockedModuleType
resolvedId: string
resolvedUrl: string
redirectUrl?: string | null
needsInterop?: boolean
}
Expand Down
19 changes: 13 additions & 6 deletions packages/mocker/src/node/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ export class ServerMockResolver {
options: { mock: 'spy' | 'factory' | 'auto' },
): Promise<ServerMockResolution> {
const { id, fsPath, external } = await this.resolveMockId(rawId, importer)
const resolvedUrl = this.normalizeResolveIdToUrl({ id }).url

if (options.mock === 'factory') {
const manifest = getViteDepsManifest(this.server.config)
const needsInterop = manifest?.[fsPath]?.needsInterop ?? false
return { mockType: 'manual', resolvedId: id, needsInterop }
return { mockType: 'manual', resolvedId: id, resolvedUrl, needsInterop }
}

if (options.mock === 'spy') {
return { mockType: 'autospy', resolvedId: id }
return { mockType: 'autospy', resolvedId: id, resolvedUrl }
}

const redirectUrl = findMockRedirect(this.server.config.root, fsPath, external)
Expand All @@ -43,6 +44,7 @@ export class ServerMockResolver {
mockType: redirectUrl === null ? 'automock' : 'redirect',
redirectUrl,
resolvedId: id,
resolvedUrl,
}
}

Expand All @@ -67,10 +69,14 @@ export class ServerMockResolver {
if (!resolved) {
return null
}
return this.normalizeResolveIdToUrl(resolved)
}

private normalizeResolveIdToUrl(resolved: { id: string }) {
const isOptimized = resolved.id.startsWith(withTrailingSlash(this.server.config.cacheDir))
let url: string
// normalise the URL to be acceptable by the browser
// https://github.com/vitejs/vite/blob/e833edf026d495609558fd4fb471cf46809dc369/packages/vite/src/node/plugins/importAnalysis.ts#L335
// https://github.com/vitejs/vite/blob/14027b0f2a9b01c14815c38aab22baf5b29594bb/packages/vite/src/node/plugins/importAnalysis.ts#L103
const root = this.server.config.root
if (resolved.id.startsWith(withTrailingSlash(root))) {
url = resolved.id.slice(root.length)
Expand All @@ -86,9 +92,9 @@ export class ServerMockResolver {
url = resolved.id
}
if (url[0] !== '.' && url[0] !== '/') {
url = id.startsWith(VALID_ID_PREFIX)
? id
: VALID_ID_PREFIX + id.replace('\0', '__x00__')
url = resolved.id.startsWith(VALID_ID_PREFIX)
? resolved.id
: VALID_ID_PREFIX + resolved.id.replace('\0', '__x00__')
}
return {
id: resolved.id,
Expand Down Expand Up @@ -177,6 +183,7 @@ function withTrailingSlash(path: string): string {
export interface ServerMockResolution {
mockType: 'manual' | 'redirect' | 'automock' | 'autospy'
resolvedId: string
resolvedUrl: string
needsInterop?: boolean
redirectUrl?: string | null
}
Expand Down
10 changes: 10 additions & 0 deletions test/browser/fixtures/mocking-out-of-root/project1/basic.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { test, expect, vi } from 'vitest';
import project2 from "../project2/index.js"

vi.mock("../project2/index.js", () => ({
default: 'project2-mocked'
}))

test("basic", () => {
expect(project2).toMatchInlineSnapshot(`"project2-mocked"`)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig } from 'vitest/config';
import { fileURLToPath } from 'node:url'
import { instances, provider } from '../../../settings'

// BROWSER=chromium pnpm -C test/browser test-fixtures --root fixtures/mocking-out-of-root/project1

export default defineConfig({
cacheDir: fileURLToPath(new URL("./node_modules/.vite", import.meta.url)),
root: import.meta.dirname,
test: {
browser: {
enabled: true,
provider: provider,
screenshotFailures: false,
instances,
},
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "project2"
13 changes: 13 additions & 0 deletions test/browser/specs/mocking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,16 @@ test('mocking dependency correctly invalidates it on rerun', async () => {
expect(vitest.stdout).not.toReportPassedTest('2_not-mocked-import.test.ts', browser)
})
})

test('mocking out of root', async () => {
const { vitest, ctx } = await runVitest({
root: 'fixtures/mocking-out-of-root/project1',
})
onTestFinished(async () => {
await ctx.close()
})
expect(vitest.stderr).toReportNoErrors()
instances.forEach(({ browser }) => {
expect(vitest.stdout).toReportPassedTest('basic.test.js', browser)
})
})

0 comments on commit d3acbd8

Please sign in to comment.