Skip to content

Commit

Permalink
fix(xhr): handle responses without body correctly (#411)
Browse files Browse the repository at this point in the history
Co-authored-by: avivasyuta <[email protected]>
Co-authored-by: Artem Zakharchenko <[email protected]>
  • Loading branch information
3 people authored Sep 2, 2023
1 parent bfac018 commit d4257a5
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 1 deletion.
16 changes: 15 additions & 1 deletion src/interceptors/XMLHttpRequest/utils/createResponse.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const statusCodesWithoutBody = [204, 205, 304]

/**
* Creates a Fetch API `Response` instance from the given
* `XMLHttpRequest` instance and a response body.
Expand All @@ -6,7 +8,19 @@ export function createResponse(
request: XMLHttpRequest,
body: BodyInit | null
): Response {
return new Response(body, {
/**
* Handle XMLHttpRequest responses that must have null as the
* response body when represented using Fetch API Response.
* XMLHttpRequest response will always have an empty string
* as the "request.response" in those cases, resulting in an error
* when constructing a Response instance.
* @see https://github.com/mswjs/interceptors/issues/379
*/
const responseBodyOrNull = statusCodesWithoutBody.includes(request.status)
? null
: body

return new Response(responseBodyOrNull, {
status: request.status,
statusText: request.statusText,
headers: createHeadersFromXMLHttpReqestHeaders(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// @vitest-environment jsdom
import { afterAll, afterEach, beforeAll, expect, it, vi } from 'vitest'
import { HttpServer } from '@open-draft/test-server/http'
import { XMLHttpRequestInterceptor } from '../../../../src/interceptors/XMLHttpRequest'
import { createXMLHttpRequest, useCors } from '../../../helpers'
import type { HttpRequestEventMap } from '../../../../src'

const httpServer = new HttpServer((app) => {
app.use(useCors)
app.get('/:statusCode', (req, res) =>
res.status(+req.params.statusCode).end()
)
})

const interceptor = new XMLHttpRequestInterceptor()

const responseListener = vi.fn<HttpRequestEventMap['response']>()
interceptor.on('response', responseListener)

beforeAll(async () => {
await httpServer.listen()
interceptor.apply()
})

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

afterAll(async () => {
interceptor.dispose()
await httpServer.close()
})

it('represents a 204 response without body using fetch api response', async () => {
const request = await createXMLHttpRequest((request) => {
request.open('GET', httpServer.http.url('/204'))
request.send()
})

expect(request.response).toBe('')
expect(responseListener).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
response: expect.objectContaining({
status: 204,
body: null,
} satisfies Partial<Response>),
})
)
expect(responseListener).toHaveBeenCalledTimes(1)
})

it('represents a 205 response without body using fetch api response', async () => {
const request = await createXMLHttpRequest((request) => {
request.open('GET', httpServer.http.url('/205'))
request.send()
})

expect(request.response).toBe('')
expect(responseListener).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
response: expect.objectContaining({
status: 205,
body: null,
} satisfies Partial<Response>),
})
)
expect(responseListener).toHaveBeenCalledTimes(1)
})

it('represents a 304 response without body using fetch api response', async () => {
const request = await createXMLHttpRequest((request) => {
request.open('GET', httpServer.http.url('/304'))
request.send()
})

expect(request.response).toBe('')
expect(responseListener).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
response: expect.objectContaining({
status: 304,
body: null,
} satisfies Partial<Response>),
})
)
expect(responseListener).toHaveBeenCalledTimes(1)
})

0 comments on commit d4257a5

Please sign in to comment.