Skip to content

Commit

Permalink
fix: do not treat static asset requests as unhandled by default (#2440)
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito authored Feb 24, 2025
1 parent d0729ae commit eb45e7a
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 49 deletions.
1 change: 1 addition & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export * from './HttpResponse'
export * from './delay'
export { bypass } from './bypass'
export { passthrough } from './passthrough'
export { isCommonAssetRequest } from './isCommonAssetRequest'

// Validate environmental globals before executing any code.
// This ensures that the library gives user-friendly errors
Expand Down
45 changes: 45 additions & 0 deletions src/core/isCommonAssetRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Determines if the given request is a static asset request.
* Useful when deciding which unhandled requests to ignore.
* @note Despite being ignored, you can still intercept and mock
* static assets by creating request handlers for them.
*
* @example
* import { isCommonAssetRequest } from 'msw'
*
* await worker.start({
* onUnhandledRequest(request, print) {
* if (!isCommonAssetRequest(request)) {
* print.warning()
* }
* }
* })
*/
export function isCommonAssetRequest(request: Request): boolean {
const url = new URL(request.url)

// Ignore certain protocols.
if (url.protocol === 'file:') {
return true
}

// Ignore static assets hosts.
if (/(fonts\.googleapis\.com)/.test(url.hostname)) {
return true
}

// Ignore node modules served over HTTP.
if (/node_modules/.test(url.pathname)) {
return true
}

// Ignore internal Vite requests, like "/@vite/client".
if (url.pathname.includes('@vite')) {
return true
}

// Ignore common static assets.
return /\.(s?css|less|m?jsx?|m?tsx?|html|ttf|otf|woff|woff2|eot|gif|jpe?g|png|avif|webp|svg|mp4|webm|ogg|mov|mp3|wav|ogg|flac|aac|pdf|txt|csv|json|xml|md|zip|tar|gz|rar|7z)$/i.test(
url.pathname,
)
}
75 changes: 65 additions & 10 deletions src/core/utils/request/onUnhandledRequest.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/**
* @vitest-environment jsdom
*/
// @vitest-environment jsdom
import {
onUnhandledRequest,
UnhandledRequestCallback,
Expand Down Expand Up @@ -90,7 +88,7 @@ test('supports the "error" request strategy', async () => {
})

test('supports a custom callback function', async () => {
const callback = vi.fn<Parameters<UnhandledRequestCallback>>((request) => {
const callback = vi.fn<UnhandledRequestCallback>((request) => {
console.warn(`callback: ${request.method} ${request.url}`)
})
const request = new Request(new URL('/user', 'http://localhost:3000'))
Expand All @@ -109,12 +107,10 @@ test('supports a custom callback function', async () => {
})

test('supports calling default strategies from the custom callback function', async () => {
const callback = vi.fn<Parameters<UnhandledRequestCallback>>(
(request, print) => {
// Call the default "error" strategy.
print.error()
},
)
const callback = vi.fn<UnhandledRequestCallback>((request, print) => {
// Call the default "error" strategy.
print.error()
})
const request = new Request(new URL('http://localhost/api'))
await expect(onUnhandledRequest(request, callback)).rejects.toThrow(
`[MSW] Cannot bypass a request when using the "error" strategy for the "onUnhandledRequest" option.`,
Expand Down Expand Up @@ -171,3 +167,62 @@ test('prints with an absolute URL and search params', async () => {
fixtures.warningWithoutSuggestions(`https://mswjs.io/api?foo=boo`),
)
})

test('ignores common static assets when using the "warn" strategy', async () => {
await Promise.allSettled([
onUnhandledRequest(
new Request(new URL('https://example.com/main.css')),
'warn',
),
onUnhandledRequest(
new Request(new URL('https://example.com/index.mjs')),
'warn',
),
onUnhandledRequest(
new Request(new URL('https://example.com/node_modules/abc-123')),
'warn',
),
onUnhandledRequest(
new Request(new URL('https://fonts.googleapis.com/some-font')),
'warn',
),
])

expect(console.warn).not.toHaveBeenCalled()
})

test('ignores common static assets when using the "error" strategy', async () => {
await Promise.allSettled([
onUnhandledRequest(
new Request(new URL('https://example.com/main.css')),
'error',
),
onUnhandledRequest(
new Request(new URL('https://example.com/index.mjs')),
'error',
),
onUnhandledRequest(
new Request(new URL('https://example.com/node_modules/abc-123')),
'error',
),
onUnhandledRequest(
new Request(new URL('https://fonts.googleapis.com/some-font')),
'error',
),
])

expect(console.error).not.toHaveBeenCalled()
})

test('exposes common static assets to the explicit callback', async () => {
let callbackRequest!: Request
await onUnhandledRequest(
new Request(new URL('https://example.com/main.css')),
(request) => {
callbackRequest = request
},
)

expect(callbackRequest).toBeInstanceOf(Request)
expect(callbackRequest.url).toBe('https://example.com/main.css')
})
16 changes: 6 additions & 10 deletions src/core/utils/request/onUnhandledRequest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { toPublicUrl } from './toPublicUrl'
import { InternalError, devUtils } from '../internal/devUtils'
import { isCommonAssetRequest } from '../../isCommonAssetRequest'

export interface UnhandledRequestPrint {
warning(): void
Expand Down Expand Up @@ -71,15 +72,10 @@ export async function onUnhandledRequest(
return
}

/**
* @note Ignore "file://" requests.
* Those often are an implementation detail of modern tooling
* that fetches modules via HTTP. Developers don't issue those
* requests and so they mustn't be warned about them.
*/
if (url.protocol === 'file:') {
return
// Ignore common static asset requests when using a built-in strategy.
// There's a slight overhead here because this utility will create a request URL
// instance again despite us having done so previously in this function.
if (!isCommonAssetRequest(request)) {
applyStrategy(strategy)
}

applyStrategy(strategy)
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,19 @@ test('does not warn on request which handler implicitly returns no mocked respon
]),
)
})

test('ignores common static assets when using the "warn" strategy', async ({
loadExample,
spyOnConsole,
page,
}) => {
const consoleSpy = spyOnConsole()
await loadExample(require.resolve('./warn.mocks.ts'))

// This request will error so perform it accordingly.
await page.evaluate(() => {
return fetch('https://example.com/styles/main.css').catch(() => null)
})

expect(consoleSpy.get('warning')).toBeUndefined()
})
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ beforeEach(() => {
})

afterEach(() => {
vi.resetAllMocks()
vi.clearAllMocks()
})

afterAll(async () => {
Expand All @@ -54,7 +54,7 @@ afterAll(async () => {
await httpServer.close()
})

test('errors on unhandled request when using the "error" value', async () => {
test('errors on unhandled request when using the "error" strategy', async () => {
const endpointUrl = httpServer.http.url('/')
const makeRequest = () => {
return fetch(endpointUrl)
Expand Down Expand Up @@ -105,3 +105,9 @@ test('does not error on request which handler implicitly returns no mocked respo

expect(console.error).not.toHaveBeenCalled()
})

test('ignores common static assets when using the "error" strategy', async () => {
await fetch('https://example.com/styles/main.css')

expect(console.error).not.toHaveBeenCalled()
})
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
/**
* @vitest-environment node
*/
// @vitest-environment node
import { setupServer } from 'msw/node'
import { HttpResponse, http } from 'msw'

const server = setupServer(
http.get('https://test.mswjs.io/user', () => {
return HttpResponse.json({ firstName: 'John' })
}),
)
const server = setupServer()

beforeAll(() => {
server.listen({ onUnhandledRequest: 'warn' })
vi.spyOn(global.console, 'warn').mockImplementation(() => void 0)
})

afterEach(() => {
vi.clearAllMocks()
})

afterAll(() => {
server.close()
vi.restoreAllMocks()
})

test('warns on unhandled request when using the "warn" value', async () => {
const res = await fetch('https://test.mswjs.io')
test('warns on unhandled request when using the "warn" strategy', async () => {
await fetch('https://test.mswjs.io/user')

expect(res).toHaveProperty('status', 404)
expect(console.warn).toBeCalledWith(`\
[MSW] Warning: intercepted a request without a matching request handler:
• GET https://test.mswjs.io/
• GET https://test.mswjs.io/user
If you still wish to intercept this unhandled request, please create a request handler for it.
Read more: https://mswjs.io/docs/getting-started/mocks`)
})

test('ignores common static assets when using the "warn" strategy', async () => {
await fetch('https://example.com/styles/main.css')

expect(console.warn).not.toHaveBeenCalled()
})

0 comments on commit eb45e7a

Please sign in to comment.