From da1cc7163dfeb53b01f4ffa86dc55438101025e7 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Thu, 9 Nov 2023 12:50:28 +0100 Subject: [PATCH] fix(xhr): support environments with missing "location" (#482) --- .../XMLHttpRequestController.ts | 11 +++++ test/envs/react-native-like.ts | 20 +++++++++ .../xhr-location-undefined.test.ts | 45 +++++++++++++++++++ test/vitest.config.js | 3 ++ 4 files changed, 79 insertions(+) create mode 100644 test/envs/react-native-like.ts create mode 100644 test/modules/XMLHttpRequest/regressions/xhr-location-undefined.test.ts diff --git a/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts b/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts index 424289b4..84baa4bf 100644 --- a/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +++ b/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts @@ -596,6 +596,17 @@ export class XMLHttpRequestController { } function toAbsoluteUrl(url: string | URL): URL { + /** + * @note XMLHttpRequest interceptor may run in environments + * that implement XMLHttpRequest but don't implement "location" + * (for example, React Native). If that's the case, return the + * input URL as-is (nothing to be relative to). + * @see https://github.com/mswjs/msw/issues/1777 + */ + if (typeof location === 'undefined') { + return new URL(url) + } + return new URL(url.toString(), location.href) } diff --git a/test/envs/react-native-like.ts b/test/envs/react-native-like.ts new file mode 100644 index 00000000..f40f6824 --- /dev/null +++ b/test/envs/react-native-like.ts @@ -0,0 +1,20 @@ +/** + * React Native-like environment for Vitest. + */ +import type { Environment } from 'vitest' +import { builtinEnvironments } from 'vitest/environments' + +export default { + name: 'react-native-like', + async setup(global, options) { + const { teardown } = await builtinEnvironments.jsdom.setup(global, options) + + // React Native does not have the global "location" property. + Reflect.deleteProperty(globalThis, 'window') + Reflect.deleteProperty(globalThis, 'location') + + return { + teardown, + } + }, +} diff --git a/test/modules/XMLHttpRequest/regressions/xhr-location-undefined.test.ts b/test/modules/XMLHttpRequest/regressions/xhr-location-undefined.test.ts new file mode 100644 index 00000000..8887e9aa --- /dev/null +++ b/test/modules/XMLHttpRequest/regressions/xhr-location-undefined.test.ts @@ -0,0 +1,45 @@ +// @vitest-environment react-native-like +import { it, expect, beforeAll, afterAll } from 'vitest' +import { XMLHttpRequestInterceptor } from '../../../../src/interceptors/XMLHttpRequest' +import { createXMLHttpRequest } from '../../../helpers' + +const interceptor = new XMLHttpRequestInterceptor() + +beforeAll(() => { + interceptor.apply() +}) + +afterAll(() => { + interceptor.dispose() +}) + +it('responds to a request with an absolute URL', async () => { + interceptor.once('request', ({ request }) => { + request.respondWith(new Response('Hello world')) + }) + + const request = await createXMLHttpRequest((request) => { + request.open('GET', 'https://example.com/resource') + request.send() + }) + + expect(request.status).toBe(200) + expect(request.response).toBe('Hello world') +}) + +it('throws on a request with a relative URL', async () => { + const createRequest = () => { + return createXMLHttpRequest((request) => { + /** + * @note Since the "location" is not present in React Native, + * relative requests will throw (nothing to be relative to). + * This is the correct behavior in React Native, where relative + * requests are a no-op. + */ + request.open('GET', '/relative/url') + request.send() + }) + } + + expect(createRequest).toThrow('Invalid URL: /relative/url') +}) diff --git a/test/vitest.config.js b/test/vitest.config.js index efb6558e..691ab0bd 100644 --- a/test/vitest.config.js +++ b/test/vitest.config.js @@ -5,5 +5,8 @@ export default defineConfig({ root: __dirname, include: ['**/*.test.ts'], exclude: ['**/*.browser.test.ts'], + alias: { + 'vitest-environment-react-native-like': './envs/react-native-like', + }, }, })