Node 22 Compatibility Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Fix fake XHR compatibility for supported Node runtimes (>= 22) by separating Blob, FileReader, and FormData detection, updating tests, and aligning CI with the supported Node baseline.

Architecture: The fix should remove the assumption that Blob and FileReader always coexist. Production code should gate behavior on the narrow capability each path actually needs, while tests should use APIs that exist in supported Node versions. CI should reflect the new Node >= 22 support policy directly.

Tech Stack: Node.js, Mocha, jsdom, GitHub Actions, nise fake XHR implementation


Task 1: Reproduce the Node 22 failure mode

Files: - Modify: lib/fake-xhr/index.test.js - Test: lib/fake-xhr/index.test.js

Step 1: Add or adjust a failing test that captures the current incorrect assumption

Add a focused test near the existing blob/form-data coverage that proves the current code couples blob behavior to FileReader or uses an outdated capability guard.

it("supports blob responses when Blob exists without requiring FileReader", function () {
    this.xhr.responseType = "blob";
    this.xhr.open("GET", "/");
    this.xhr.send();

    this.xhr.respond(
        200,
        { "Content-Type": "application/octet-stream" },
        "blob body",
    );

    assert.equals(this.xhr.response.constructor.name, "Blob");
});

Step 2: Run the focused test to verify it fails or exposes the current mismatch

Run: npm test -- --grep "supports blob responses when Blob exists without requiring FileReader" Expected: FAIL or demonstrate that existing blob tests are gated incorrectly for Node 22.

Step 3: Add a focused test for FormData header handling under supported Node

it("does not set Content-Type for FormData payloads", function () {
    this.xhr.open("POST", "/");
    var formData = new FormData();
    formData.append("username", "biz");

    this.xhr.send(formData);

    assert.isUndefined(this.xhr.requestHeaders["Content-Type"]);
    assert.isUndefined(this.xhr.requestHeaders["content-type"]);
});

Step 4: Run the focused FormData test

Run: npm test -- --grep "does not set Content-Type for FormData payloads" Expected: PASS if native Node 22 FormData already works, otherwise FAIL with a concrete compatibility error to address.

Step 5: Commit

git add lib/fake-xhr/index.test.js
git commit -m "test: reproduce node 22 xhr compatibility issues"

Task 2: Narrow runtime capability detection in production code

Files: - Modify: lib/fake-xhr/index.js - Modify: lib/fake-xhr/blob.js - Test: lib/fake-xhr/index.test.js

Step 1: Write the failing test for the production guard change

If Task 1 did not already fail at the right seam, add a smaller test around the specific branch that currently uses supportsBlob.

it("creates blob responses when Blob is supported", function () {
    this.xhr.responseType = "blob";
    this.xhr.open("GET", "/");
    this.xhr.send();

    this.xhr.respond(200, {}, "data");

    assert.equals(this.xhr.response.constructor.name, "Blob");
});

Step 2: Run the targeted test

Run: npm test -- --grep "creates blob responses when Blob is supported" Expected: FAIL or show the current guard is broader/narrower than intended.

Step 3: Write the minimal implementation

In lib/fake-xhr/index.js:

  • keep supportsFormData as a direct typeof FormData !== "undefined" check;
  • keep blob support separate from any FileReader check;
  • ensure verifyResponseBodyType and blob response creation use the blob-specific capability only.

In lib/fake-xhr/blob.js:

  • keep or rename the helper so it answers only one question: can the runtime construct a Blob?

Example direction:

var supportsFormData = typeof FormData !== "undefined";
var supportsBlob = require("./blob").isSupported;
var supportsFileReader = typeof FileReader !== "undefined";

Use supportsBlob in production blob paths. Only use supportsFileReader in code that directly depends on it.

Step 4: Run the targeted tests

Run: npm test -- --grep "blob" Expected: targeted blob tests PASS on Node 22.

Step 5: Commit

git add lib/fake-xhr/index.js lib/fake-xhr/blob.js lib/fake-xhr/index.test.js
git commit -m "fix: separate xhr feature detection for node 22"

Task 3: Update blob assertions to avoid a hard FileReader dependency

Files: - Modify: lib/fake-xhr/index.test.js - Test: lib/fake-xhr/index.test.js

Step 1: Replace FileReader-based blob assertion helpers with a supported API

Prefer Blob.text() in the shared blob assertion helper.

function assertBlobMatches(actual, expected, done) {
    Promise.all([
        actual.text(),
        expected instanceof Blob ? expected.text() : Promise.resolve(expected),
    ]).then(function (values) {
        assert.same(values[0], values[1]);
        done();
    }, done);
}

Step 2: Run the focused blob response tests

Run: npm test -- --grep "with Blob support|with Blob and FileReader support|blob" Expected: PASS on Node 22 without requiring a global FileReader.

Step 3: Clean up test descriptions and guards

  • rename outdated describe blocks if they refer to Blob and FileReader support;
  • gate only truly FileReader-specific tests on typeof FileReader !== "undefined".

Step 4: Re-run the fake XHR test file

Run: npm test -- lib/fake-xhr/index.test.js Expected: PASS

Step 5: Commit

git add lib/fake-xhr/index.test.js
git commit -m "test: align blob assertions with node 22 APIs"

Task 4: Align CI with supported Node versions

Files: - Modify: .github/workflows/main.yml

Step 1: Add a failing CI expectation in review notes

Document the intended supported versions before editing:

Supported Node versions for this change: 22 and newer.

Step 2: Update the test matrix

Change the workflow matrix in .github/workflows/main.yml from Node 16-only coverage to supported versions, for example:

strategy:
    matrix:
        node-version: [22, 24]

Keep the existing dedicated Node 20 coverage job only if the project still wants that separate signal; otherwise align it to the supported baseline too.

Step 3: Validate workflow syntax locally by inspection

Run: sed -n '1,180p' .github/workflows/main.yml Expected: Node test entries show only supported versions.

Step 4: Commit

git add .github/workflows/main.yml
git commit -m "ci: test supported node versions"

Task 5: Full verification

Files: - Modify: docs/plans/2026-03-02-node-22-compat.md if commands need correction - Test: lib/fake-xhr/index.test.js

Step 1: Run the fake XHR test file

Run: npm test -- lib/fake-xhr/index.test.js Expected: PASS

Step 2: Run the full test suite

Run: npm test Expected: PASS

Step 3: Run lint if code structure changed materially

Run: npm run lint Expected: PASS

Step 4: Inspect the final diff

Run: git diff --stat Expected: only fake XHR code, tests, CI, and plan docs are changed.

Step 5: Commit

git add .github/workflows/main.yml lib/fake-xhr/index.js lib/fake-xhr/blob.js lib/fake-xhr/index.test.js docs/plans/2026-03-02-node-22-compat*.md
git commit -m "fix: restore fake xhr compatibility on node 22"