Skip to content

fix(jsdom): fix Request with Blob body on jsdom 28+ - #11295

Merged
hi-ogawa merged 1 commit into
vitest-dev:mainfrom
harshit-d3v:fix/jsdom-blob-bytes
Sep 17, 2026
Merged

hi-ogawa merged 1 commit into
vitest-dev:mainfrom
harshit-d3v:fix/jsdom-blob-bytes

Conversation

@harshit-d3v

@harshit-d3v harshit-d3v commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Description

Under environment: 'jsdom', a Blob used as a request body arrives as the string undefined. makeCompatBlob reads jsdom's private _buffer field, which jsdom 28 renamed to _bytes. Reads whichever exists so both halves of the * peer range keep working.

Adds a test that fails without the change: a Blob used as a Request body. The existing supports Blob test uses an empty blob and only checks toBeInstanceOf, so it never saw the content go missing.

The related Node 24 File/FormData breakage (#9135) is a separate issue with a different cause (undici 7 reading File from the global) and will be a follow-up PR.

Resolves #11294

Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time.
  • Ideally, include a test that fails without this PR but passes with it.
  • Please, don't make changes to pnpm-lock.yaml unless you introduce a new test example.

Tests

  • Run the tests with pnpm test:ci.

I used Claude to help find and fix this. I reproduced it myself and reviewed the change.

Copilot AI lite review requested due to automatic review settings September 16, 2026 19:27
@netlify

netlify Bot commented Sep 16, 2026

Copy link
Copy Markdown

✅ Deploy Preview for vitest-dev ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 3fe973e
🔍 Latest deploy log https://app.netlify.com/projects/vitest-dev/deploys/6aaaedb72912c900088204a6
😎 Deploy Preview https://deploy-preview-11295--vitest-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The fix and regression tests address the reported issue with no unresolved review comments.

Pull request overview

Fixes Blob and File request bodies under jsdom 28+ by supporting its renamed internal byte field.

Changes:

  • Supports both _bytes and legacy _buffer.
  • Adds regression tests for Blob and multipart File bodies.
File summaries
File Description
test/unit/test/environments/jsdom.spec.ts Adds request-body regression tests.
packages/vitest/src/integrations/env/jsdom.ts Adds compatibility for both jsdom byte-field names.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@hi-ogawa hi-ogawa left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check CI fali?

@harshit-d3v

Copy link
Copy Markdown
Contributor Author

Pushed a fix. undici 7 on Node 24 resolves File from the global inside FormData.append, and the jsdom env swaps that global, so the Node blob got wrapped in a jsdom File. Now wraps it in Node's File first. Unit jobs are green. The two remaining failures are browser mode (mocking.test.ts importOriginal, looks like #11251, and bail-out.test.ts), could you re-run those?

@hi-ogawa

Copy link
Copy Markdown
Collaborator

@harshit-d3v harshit-d3v changed the title fix(jsdom): keep Blob bytes on jsdom 28+ fix(jsdom): fix Blob body and FormData on Node 24 Sep 17, 2026
@harshit-d3v

Copy link
Copy Markdown
Contributor Author

Yes, that's it. undici 7 dropped the node:buffer imports (nodejs/undici#4362, Node 24.6+), so FormData.append and the multipart parser read File from the global at call time, and in this env that global is jsdom's File.

Pushed the other half too. The parser builds each part with new File from the global and then brand checks it, so formData() threw under threads and forks. Now Node's File is put back on the global for the duration of formData() on Request and Response, and the result is converted to jsdom FormData/File so instanceof still works in tests. Restored on teardown. That covers #9135. Tested on Node 22 and 24.21 locally.

@hi-ogawa

Copy link
Copy Markdown
Collaborator

Aren't the two issues independent? Should we split PR?

@harshit-d3v

Copy link
Copy Markdown
Contributor Author

You're right, they're independent: #11294 is the jsdom 28 _buffer rename (any Node), #9135 is undici 7 reading File from the global on Node 24.6+ (any jsdom). I'd normally split, but the repo limits outside contributors to one open PR, so I bundled them to avoid opening a second. They're all in the same few lines of jsdom.ts. Happy to drop the Node 24 commits and open a separate PR for #9135 after this merges if you'd prefer that.

@hi-ogawa

Copy link
Copy Markdown
Collaborator

split pls

jsdom 28 renamed the private `_buffer` field to `_bytes`, so
`makeCompatBlob` read `undefined` and every Blob or File used as a
request body arrived as the string "undefined".
@harshit-d3v harshit-d3v changed the title fix(jsdom): fix Blob body and FormData on Node 24 fix(jsdom): keep Blob bytes on jsdom 28+ Sep 17, 2026
@harshit-d3v

Copy link
Copy Markdown
Contributor Author

Done. This PR is now just #11294: the jsdom 28 _buffer to _bytes rename, plus one test for a Blob request body. The Node 24 File/FormData work (#9135) is off on a branch and I'll open it as a separate PR once this one lands.

@harshit-d3v

Copy link
Copy Markdown
Contributor Author

The only red job is the #11251 browser-mode mock flake again (mocking virtual module without importOriginal ... (playwright), the "failed to fetch dynamically imported module" race). Not related to this change, the jsdom test passes. Could you re-run that job?

@hi-ogawa

Copy link
Copy Markdown
Collaborator

The existing tests in test/unit/test/environments/jsdom.spec.ts use an empty new Blob() and only assert toBeInstanceOf (from #11294)

Where is this? You seem to suggest there's already a test case to expand.

@harshit-d3v

harshit-d3v commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor Author

it's the supports Blob test in test/unit/test/environments/jsdom.spec.ts:160:

const data = new Blob()
form.set(key, data)
const retrievedBlob = form.get(key)
expect(retrievedBlob).toBeInstanceOf(Blob)

that one uses an empty blob and only checks the type, so it never noticed the bytes were gone. the test i added sends a real blob as a request body and reads it back, thats the case that was broken.

@hi-ogawa

hi-ogawa commented Sep 17, 2026 •

Copy link
Copy Markdown
Collaborator

should supports Blob exercise something with non-empty blob? I didn't get the nuance of why you bring that test in the issue if it's not relevant for this PR.

@harshit-d3v

Copy link
Copy Markdown
Contributor Author

right, that was more of a side note than something this PR needs.

making supports Blob non-empty wouldn't catch this one either. that test goes through form.get(), which hands back jsdom's own Blob and never touches the compat conversion. the broken path is only when a Blob is used as a Request body, which is what my new test covers.

i mentioned it in the issue just to explain why the regression wasn't caught before. i can drop that line if it reads as if the old test should change.

@hi-ogawa

Copy link
Copy Markdown
Collaborator

What about the coverage of new Blob(['hello']).text()?

@harshit-d3v

Copy link
Copy Markdown
Contributor Author

new Blob(['hello']).text() goes through jsdom's own Blob and returns 'hello' fine, with or without this fix. same for .arrayBuffer(). those never hit the compat conversion.

the break only shows when the blob is passed to Node's fetch/Request, which is the path makeCompatBlob is on. i just checked on the current main build: .text() and .arrayBuffer() pass, only the Request-body case fails. so a .text() test wouldn't catch this one, that's why the test sends the blob as a request body.

@hi-ogawa hi-ogawa changed the title fix(jsdom): keep Blob bytes on jsdom 28+ fix(jsdom): fix Request with Blob body Sep 17, 2026
@hi-ogawa hi-ogawa changed the title fix(jsdom): fix Request with Blob body fix(jsdom): fix Request with Blob body since jsdom 28 Sep 17, 2026
@hi-ogawa hi-ogawa changed the title fix(jsdom): fix Request with Blob body since jsdom 28 fix(jsdom): fix Request with Blob body on jsdom 28+ Sep 17, 2026
@hi-ogawa
hi-ogawa merged commit d1c3ecc into vitest-dev:main Sep 17, 2026
28 of 29 checks passed
dadezzz pushed a commit to dadezzz/forgejo-agent that referenced this pull request Sep 30, 2026
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [vitest](https://vitest.dev) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | [`5.0.1` → `5.0.2`](https://renovatebot.com/diffs/npm/vitest/5.0.1/5.0.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vitest/5.0.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vitest/5.0.1/5.0.2?slim=true) |

---

### Release Notes

<details>
<summary>vitest-dev/vitest (vitest)</summary>

### [`v5.0.2`](https://github.com/vitest-dev/vitest/releases/tag/v5.0.2)

[Compare Source](vitest-dev/vitest@v5.0.1...v5.0.2)

#####    🐞 Bug Fixes

- Bind `process` in case global is overwritten  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;11343](vitest-dev/vitest#11343) [<samp>(0b792)</samp>](vitest-dev/vitest@0b79231ad)
- **detect-async-leaks**:
  - Ignore `process.stdio` handles  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;11333](vitest-dev/vitest#11333) [<samp>(0fd6b)</samp>](vitest-dev/vitest@0fd6b9790)
- **expect**:
  - Fix `toMatchObject` with asymmetric matchers  -  by [@&#8203;ShreeBohara](https://github.com/ShreeBohara), **Claude Opus 5**, [@&#8203;hi-ogawa](https://github.com/hi-ogawa), **Hiroshi Ogawa** and **Codex (GPT-5)** in [#&#8203;11100](vitest-dev/vitest#11100) [<samp>(42523)</samp>](vitest-dev/vitest@42523289e)
- **jsdom**:
  - Fix `Request` with `Blob` body on jsdom 28+  -  by [@&#8203;harshit-d3v](https://github.com/harshit-d3v) in [#&#8203;11295](vitest-dev/vitest#11295) [<samp>(d1c3e)</samp>](vitest-dev/vitest@d1c3ecc93)
- **reporter**:
  - `agent` to respect `--silent`  -  by [@&#8203;Raj4478](https://github.com/Raj4478) and [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;11271](vitest-dev/vitest#11271) [<samp>(5b95e)</samp>](vitest-dev/vitest@5b95efb6d)
- **reporters**:
  - Handle concurrent `createReport` calls  -  by [@&#8203;7rulnik](https://github.com/7rulnik) in [#&#8203;11278](vitest-dev/vitest#11278) [<samp>(e8e55)</samp>](vitest-dev/vitest@e8e556ff7)
  - `hanging-process` to use ESM entrypoint  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;11316](vitest-dev/vitest#11316) [<samp>(4e91e)</samp>](vitest-dev/vitest@4e91e5668)
- **spy**:
  - Fix stack overflow when spying `Set.prototype.add`  -  by [@&#8203;fengmk2](https://github.com/fengmk2) in [#&#8203;11299](vitest-dev/vitest#11299) [<samp>(a0a93)</samp>](vitest-dev/vitest@a0a939653)
- **ui**:
  - Persist authentication cookie beyond current browser session  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa), **Hiroshi Ogawa** and **OpenCode (gpt-5.6-sol)** in [#&#8203;11066](vitest-dev/vitest#11066) [<samp>(f8819)</samp>](vitest-dev/vitest@f88195e16)

#####     [View changes on GitHub](vitest-dev/vitest@v5.0.1...v5.0.2)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMTUuMTMiLCJ1cGRhdGVkSW5WZXIiOiI0NC4xMTUuMTMiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

jsdom environment turns every Blob/File request body into the string "undefined"

3 participants