Describe the bug
Under environment: 'jsdom', a Blob or File used as a request body arrives as the literal string undefined. Nothing throws, so a test uploading a file passes while sending nine bytes of ASCII instead of its content.
const blob = new Blob(['hello world'], { type: 'text/plain' })
const text = await new Request('http://x.test/', { method: 'POST', body: blob }).text()
| environment |
text |
| node |
"hello world" |
| happy-dom |
"hello world" |
| jsdom |
"undefined" |
Same for a File inside FormData: the multipart part body is undefined, and fetch(URL.createObjectURL(blob)) returns undefined too.
Cause
makeCompatBlob in packages/vitest/src/integrations/env/jsdom.ts bridges jsdom's Blob to Node's fetch by reading a private field:
const buffer = (blob as any)[implSymbol]._buffer
return new NodeBlob_([buffer], { type: blob.type })
jsdom 28.0.0 renamed that field to _bytes, so the read is undefined and new Blob([undefined]) stringifies it.
| jsdom |
impl keys |
| 24.1.3 – 27.4.0 |
_buffer,_globalObject,type |
| 28.1.0 – 29.1.1 |
_globalObject,type,_bytes |
Vitest's own devDependency is jsdom: ^29.1.1, so the default combination is the broken one. The existing tests in test/unit/test/environments/jsdom.spec.ts use an empty new Blob() and only assert toBeInstanceOf, which is why CI stays green.
Reproduction
The snippet above, in any project with vitest@5.0.1 and jsdom@29, run once per environment. A single test file:
import { expect, test } from 'vitest'
test('blob bytes survive a Request body', async () => {
const blob = new Blob(['hello world'], { type: 'text/plain' })
const text = await new Request('http://x.test/', { method: 'POST', body: blob }).text()
expect(text).toBe('hello world')
})
Passes with --environment=node and --environment=happy-dom, fails with --environment=jsdom.
System Info
vitest 5.0.1
jsdom 29.1.1
node 22.18.0
win32 x64
Used Package Manager
npm
Validations
I used Claude to help investigate this. I ran the reproduction myself across the three environments and the six jsdom versions above.
Describe the bug
Under
environment: 'jsdom', aBloborFileused as a request body arrives as the literal stringundefined. Nothing throws, so a test uploading a file passes while sending nine bytes of ASCII instead of its content.text"hello world""hello world""undefined"Same for a
FileinsideFormData: the multipart part body isundefined, andfetch(URL.createObjectURL(blob))returnsundefinedtoo.Cause
makeCompatBlobinpackages/vitest/src/integrations/env/jsdom.tsbridges jsdom'sBlobto Node'sfetchby reading a private field:jsdom 28.0.0 renamed that field to
_bytes, so the read isundefinedandnew Blob([undefined])stringifies it._buffer,_globalObject,type_globalObject,type,_bytesVitest's own devDependency is
jsdom: ^29.1.1, so the default combination is the broken one. The existing tests intest/unit/test/environments/jsdom.spec.tsuse an emptynew Blob()and only asserttoBeInstanceOf, which is why CI stays green.Reproduction
The snippet above, in any project with
vitest@5.0.1andjsdom@29, run once perenvironment. A single test file:Passes with
--environment=nodeand--environment=happy-dom, fails with--environment=jsdom.System Info
Used Package Manager
npm
Validations
I used Claude to help investigate this. I ran the reproduction myself across the three environments and the six jsdom versions above.