Skip to content

ProgressEvent support is cached at module load but read at call time, throwing after a jsdom teardown #818

Description

@nader-eloshaiker

Summary

SUPPORTS_PROGRESS_EVENT is computed once at module load, but ProgressEvent is read from the bare
global at call time. If the global disappears between those two moments, the cached flag sends
execution into the branch that references it and throws ReferenceError: ProgressEvent is not defined — while ProgressEventPolyfill, which exists for exactly this case, is never reached.

Where

src/interceptors/XMLHttpRequest/utils/createEvent.ts (0.41.9 and still present in 0.42.3):

const SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== 'undefined' // evaluated once, at import
// ...
const ProgressEventClass = SUPPORTS_PROGRESS_EVENT ? ProgressEvent : ProgressEventPolyfill // read later

How it happens

Under Vitest with the jsdom environment, each test file gets its own environment and it is torn
down when the file finishes.

  1. The module is imported while jsdom is alive, so SUPPORTS_PROGRESS_EVENT caches true.
  2. A test file ends with a request still in flight.
  3. Vitest tears down the jsdom environment; ProgressEvent is no longer a global.
  4. The response lands, createEvent runs, takes the true branch, and references a global that is
    gone.

The result is an unhandled rejection attributed to a test file whose tests all passed. Vitest counts
it as an error and fails the run, so a green suite reports red:

Tests  365 passed (365)
Errors  2 errors

ReferenceError: ProgressEvent is not defined
 ❯ createEvent .../XMLHttpRequest-Dw6Wm-UU.mjs:91:55
 ❯ XMLHttpRequestController.trigger .../XMLHttpRequest-Dw6Wm-UU.mjs:595:17
 ❯ XMLHttpRequestController.respondWith .../XMLHttpRequest-Dw6Wm-UU.mjs:407:9

It is timing-dependent, so it reproduces intermittently — ours appears on CI and not locally.

Suggested fix

Resolve the class at call time rather than caching the capability:

const ProgressEventClass = typeof ProgressEvent !== 'undefined' ? ProgressEvent : ProgressEventPolyfill

That keeps the React Native behaviour from #40 and also covers an environment where the global goes
away after import, at the cost of one typeof per event.

Versions

  • @mswjs/interceptors 0.41.9, via msw 2.15.0
  • Verified the same code is present in 0.42.3
  • Vitest 4.1.10, environment: 'jsdom', Node 24

Workaround

Cancel in-flight requests before a test file's environment is torn down, so no response arrives after
teardown. That removes the trigger but not the cause — any consumer that leaves a request in flight
hits this.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions