Skip to content
sonukapoorPublic

Latest commit

 

History

860 Commits

Folders and files

Repository files navigation

warpdom

warpdom

DOI Sponsor OpenSSF Scorecard

A DOM for unit and component tests: your tests get the same results as on jsdom, faster. A drop-in environment for Vitest and Jest, written from the WHATWG specifications.

  • Same results. On 23 open-source suites (14 we tuned on, such as MUI, React Spectrum, handsontable, Radix, Vuetify, ariakit and svelte, about 43,600 tests; and 9 held out, about 13,500 tests), every test has the status it has on jsdom, apart from flaky tests and a short list of explained differences (below). In 12 CI jobs, 17 of the 25 suites (with two web applications) had no test that differed from jsdom in any round.
  • Faster on every suite we tuned on. Drop-in, warpdom was faster than jsdom on all 13 tuning suites in each of three independent runs: a median of about 1.27x, from 1.03x (solid) to 1.89x (MUI) per suite. handsontable, whose core runs jsdom 16.7, ran about 8–9x faster at the measured commit and about 3.2x on its current branch, which has since added its own jsdom speed-ups; it is reported separately.
  • Check it on your own suite in one command: npx warpdom compare.

Status: 0.x, before 1.0; what 1.0 will promise and what is still missing is in docs/road-to-1.0.md, how releases, jsdom, Node and test runner versions are supported in docs/stability.md. Numbers, method and raw data: docs/research/README.md, measured with dom-api-tracer.

Requirements

Node 22.12 or later. Vitest 2 or later (tested with 2, 3, 4 and 5) or Jest 27 or later (tested with 27 and 30). Fast mode needs Vitest 3.2 or later.

Setup

npm install --save-dev warpdom

Vitest. Vitest takes a path (or a vitest-environment-<name> package) as test.environment; warpdom exports the path:

// vitest.config.js
import { defineConfig } from 'vitest/config';
import { environment } from 'warpdom/vitest/environment';

export default defineConfig({
  test: { environment },
});

Or install the small companion package vitest-environment-warpdom too, and use the name: test: { environment: 'warpdom' }.

With test.projects, set it on the projects that use jsdom today.

Jest.

// jest.config.js
module.exports = {
  testEnvironment: 'warpdom/jest',
};

Or install the small companion package jest-environment-warpdom too, and use the name, as with jsdom: testEnvironment: 'warpdom', and /** @jest-environment warpdom */ in test files (Jest 27 to 30).

The page URL: environmentOptions: { warpdom: { url } } with Vitest (default http://localhost:3000, as with Vitest's jsdom environment, which reads environmentOptions.jsdom.url), testEnvironmentOptions: { url } with Jest (default http://localhost/, as with jest-environment-jsdom).

Test files that choose jsdom with a comment (/** @vitest-environment jsdom */, @jest-environment jsdom) keep using jsdom; change the comment to switch them.

Switch those comments too, even if the files pass on jsdom. Vitest groups test files by environment name and runs each group on its own workers, so files whose comment names another environment than the config run apart from the others. In a suite whose files share module state (isolate: false), that changes which files run before which in a worker, and with it the result of order-dependent tests: one of solid's tests passes or fails by that order alone, on jsdom and on warpdom alike. Use the same name in the config and in the comments (environment: 'warpdom' with vitest-environment-warpdom, and /** @vitest-environment warpdom */), or remove the comments.

Which jsdom warpdom behaves like

Test suites encode the behavior of the jsdom version they were written against (for example, getComputedStyle returned empty strings for most properties before jsdom 29, and initial values since). warpdom detects the jsdom your project installs (and, for jsdom 26 and earlier, the cssstyle version it loads) and follows that version's observable behavior. With jsdom alone, another jsdom version changed test results in 13 of 23 projects we measured (Version dependence). To set it yourself:

// Vitest
test: { environment, environmentOptions: { warpdom: { jsdom: '26.1.0' } } }
// Jest
testEnvironmentOptions: { jsdom: '26.1.0' }

Interfaces, members, event handlers, the window's shape, style objects and computed values follow the version, as measured on jsdom's releases: for example, crypto.randomUUID() exists from jsdom 22.1.0, and computed lengths are px from 30.0.0. The full list, as tables by area: docs/jsdom-versions.md. The window has the globals that jsdom version has; interfaces warpdom has before it (ToggleEvent, TimeRanges, the CSS rule interfaces before jsdom 29, ...) get their globals with beyondJsdom: true, set where jsdom is.

warpdom models jsdom 20.0.3 to 30.1.3. With a newer jsdom installed (a later minor or major release), tests get jsdom 30.1.3's behavior and warpdom prints a warning once; a weekly check (upstream-watch) opens an issue when such a release appears.

XMLHttpRequest and request mocking

XMLHttpRequest exists in every jsdom version, so warpdom has it too: written from the XHR standard on top of the host's fetch, with jsdom's observable behavior (events and their order, CORS checks and preflights, timeouts, aborts, errors; jsdom 28 changed several of these when it moved to undici, and the profile follows). HTTP libraries that look for it (axios) take their XHR adapter, as with jsdom. Request mocking works as with jsdom: msw answers the request the XHR makes through fetch (so a cross-origin mocked response needs Access-Control-Allow-Origin, as on jsdom), and nock answers XHR itself. The differences are listed in docs/differences.md.

Before XMLHttpRequest, axios used Node's HTTP client under warpdom. To keep that (no XMLHttpRequest at all):

// Vitest
test: { environment, environmentOptions: { warpdom: { xhr: false } } }
// Jest
testEnvironmentOptions: { xhr: false }

Check your suite first: warpdom compare

npx warpdom compare                 # one round: results and an indicative speedup
npx warpdom compare --rounds 3      # numbers worth quoting
npx warpdom compare --correctness   # results only (for CI)

Run it where you run vitest or jest. It runs your suite on jsdom (or happy-dom, whichever you use) and on warpdom, and reports every test whose status changes, grouped by error, and the speedup. Nothing in your project is edited. It exits 1 if any test that passes on jsdom fails on warpdom. The report's summary contains no file or test names, so it can be shared. Monorepos with Vitest projects or workspaces and Jest projects are supported. Details: docs/compare.md.

In CI, the warpdom compare action runs the same comparison in a non-blocking job on every pull request and puts the differing tests and the time saved in the job summary (and, optionally, one pull request comment), without switching anything:

- uses: sonukapoor/warpdom/compare-action@main # better: a release tag or commit SHA

One caveat: on the warpdom side, compare switches Vitest test files that pin jsdom with a comment to another environment name than the config's, so they run in a separate group (see Setup). In a suite with isolate: false and order-dependent tests, that alone can change a result; check such a difference with the comments switched.

Switch: warpdom migrate

npx warpdom migrate --dry-run  # the plan, nothing changed
npx warpdom migrate            # make the changes, then install

Run it at the root of your project after warpdom compare (compare needs jsdom still installed). It switches every place that picks jsdom or happy-dom to warpdom: Vitest configs, projects and workspace files, environmentMatchGlobs, Jest configs and package.json "jest", @vitest-environment / @jest-environment comments and --environment flags in scripts, each with an exact text edit. It adds warpdom and vitest-environment-warpdom or jest-environment-warpdom where jsdom was declared, removes jsdom, happy-dom and their environment packages when nothing imports them any more, and prints the install command (it does not install). What it cannot change exactly (a computed environment, one inherited from another package) is listed as manual, and lines that mention jsdom (setup files, polyfills, console filters) are listed for review. It also warns about release-age settings (pnpm minimumReleaseAge, yarn npmMinimalAgeGate) with the date the new packages can be installed. warpdom behaves like the jsdom version it finds installed, so when migrate removes a jsdom version other than the one warpdom assumes by default, it pins that version next to the environment (environmentOptions.warpdom.jsdom, testEnvironmentOptions.jsdom) and your tests keep their current jsdom behavior (--no-keep-jsdom-version leaves it out). Details: docs/migrate.md.

Going back to jsdom

warpdom is one setting, so going back is one line (with fast mode, also remove withFastMode and warpdom vitest run). jsdom (with Jest, jest-environment-jsdom) must be installed; warpdom migrate removes it, so reinstall the version you had before first.

// The whole project
test: { environment: 'jsdom' }   // Vitest
testEnvironment: 'jsdom'         // Jest

One test file (wins over the config and the command line; with Jest, as the file's first comment):

// @vitest-environment jsdom
/** @jest-environment jsdom */
# One run, no file edited
npx vitest run --environment jsdom
npx jest --env=jsdom

For happy-dom: happy-dom (Vitest), @happy-dom/jest-environment (Jest). To undo warpdom migrate: git restore . (or git revert its commit), then install. Running both DOMs side by side, CI-only switches, uninstalling, and reporting a test that needs jsdom's behavior: docs/switching-back.md.

Faster still

Fast mode (Vitest). Vitest isolates every test file, and reloading modules per file is a large part of a component suite's time. Fast mode runs files without isolation and resets warpdom's window between them; files that still leak module state are learned and run isolated. How much it gains depends on how much of a suite's time goes into reloading modules per file; measure it on your suite before relying on it.

Measured on GitHub Actions on 16 open-source Vitest suites (up to 5 rounds; warpdom 2606b3d): where sharing pays it is much faster than both jsdom's default and warpdom's drop-in, e.g. VueUse 9.6x over jsdom (5.2x over the drop-in), MUI 6.7x (3.7x), Vuetify 5.6x (3.8x), Radix 4.7x (2.6x), naive-ui 3.4x (3.0x), tanstack-query 2.4x (1.6x). Where sharing does not pay it runs the suite isolated (or splits for a gain within noise) and equals the drop-in (0.98-1.03x on the other suites). Median over the 16: 1.54x over jsdom's default, 1.01x over the drop-in. Against the fastest jsdom configuration that keeps every result (isolate: false, a cleanup, or the VM pool), the median is 1.27x, and it is slower on two suites (naive-ui 0.67x; melt-ui 0.84x). On Vitest older than 3.2, warpdom vitest run is not available and runs Vitest as configured, that is, the drop-in. Four of the 16 suites (melt-ui among them) run an older Vitest and ariakit passes three --project arguments, so those five were measured with our harness's emulation of the same rules.

Suite Mode chosen vs jsdom default vs best correct jsdom setup vs warpdom drop-in
VueUse split 9.62x 9.62x 5.17x
MUI split 6.67x 1.54x 3.73x
Vuetify split 5.61x 1.11x 3.80x
Radix split 4.73x 1.47x 2.59x
naive-ui split 3.36x 0.67x 3.01x
tanstack-query 7 of 19 projects split 2.41x 2.41x 1.59x
CopilotKit vue split 1.52x 1.52x 1.22x
ark-ui partly split 1.59x 1.59x 1.01x
floating-ui / melt-ui isolated 1.56x / 1.44x 1.01x / 0.84x 1.01x / 0.99x
7 more suites isolated 1.05-1.35x – 0.98-1.03x
Median of 16 1.54x 1.27x 1.01x

docs/fast-mode.md

// vitest.config.js
import { withFastMode } from 'warpdom/vitest/fast';
export default withFastMode({ test: { environment: 'warpdom' } });
npx warpdom vitest run

Fast frames (opt-in). requestAnimationFrame normally waits 16.7 ms of real time per frame, in jsdom and warpdom. With fastFrames, frames run as soon as the event loop gets to them; suites whose test helpers wait for frames get much faster (ariakit 1.6x over warpdom's default). A few tests can change result, so it is off by default: check with npx warpdom compare --fast-frames.

docs/fast-frames.md

// Vitest
test: { environment, environmentOptions: { warpdom: { fastFrames: true } } }
// Jest
testEnvironmentOptions: { fastFrames: true }

How warpdom is verified

Some checks run on every change, others on a schedule; all of them are public:

  • jsdom as a black-box reference. Behavior comes from the WHATWG and W3C specifications. Where jsdom differs from them, or from itself across versions, the difference is measured from outside (scripts that run jsdom 20 to 30 and compare results), never copied from its source. Each measured difference becomes a profile setting (Which jsdom warpdom behaves like) and a test.
  • Tests on every pull request and every push to main (ci.yml: npm test, about 1,400 tests on Node 22.22 and 24): unit tests, parity tests that run the same code on warpdom and on the matching jsdom version side by side (test/parity.js), and an API test that walks the windows of the newest jsdom of every major from 20 to 30 and fails when warpdom lacks a member jsdom has (test/jsdom-api-superset.test.js; known exceptions are listed in the test).
  • Real test suites before merging a behavior change. The maintainer starts a comparison (a manually started workflow, not an automatic check) in which open-source component libraries (React, Vue, Svelte, Solid and Angular) and two applications run on jsdom and on warpdom on GitHub-hosted runners, and every test's result is compared (corpus-timing.yml, dom-api-tracer). These suites also drove many fixes, so they are regression checks, not an independent validation; the method and every number are in docs/research/. The remaining differences are listed in Differences from jsdom.
  • Web Platform Tests on 17 areas, monthly at the newest WPT (wpt.yml, Web Platform Tests).
  • Benchmarks on pull requests (bench.yml): the same scripts on the base and the head, alternating on one runner, posted as a comment (for pull requests from forks and Dependabot, in the run's summary instead).
  • Review and supply chain: every change is a pull request with automated code review and a dependency scan (CVE Lite CLI, in ci.yml); workflow actions are pinned to commits except CVE Lite CLI, which follows its v1 tag on purpose; OpenSSF Scorecard runs on pushes to main and weekly (scorecard.yml); releases are published from GitHub Actions with npm provenance (RELEASING.md).
  • Watching upstream (upstream-watch.yml): a weekly job opens an issue when a jsdom newer than the newest modeled one, a changed inline style surface, or a new Vitest or Jest major appears.

You can run the same comparison on your own suite with warpdom compare.

Measured

All numbers below are from GitHub Actions runs on one frozen commit, warpdom 8630317 (2026-10-13): hosted Linux runners with 4 cores, pinned project commits, each project's own runner, config and jsdom, 2 workers. In each job the DOMs alternate on the same machine: an untimed warm-up, then 5 rounds with the order rotated. A speedup is the median of the per-round ratios, so it never mixes machines. Raw data, scripts and every derived number: docs/research/ (docs/research/data/ci-2026-10-13-208ad49-*).

At a glance

Result
Drop-in speed, 13 suites, 3 independent runs median 1.28x / 1.27x / 1.26x faster than jsdom; every suite faster in every run (1.03x to 1.89x)
handsontable (jsdom 16.7, outside the medians) 8.0-8.9x (593 s to 69 s); 2.5x on the first run after install
Held-out suites (8) median 1.36x (1.01x to 1.96x)
Applications Excalidraw 1.21x, Wire 1.03x, 0 tests changed
Same results 17 of 25 suites: no test changed in any round; the other 45 tests are each explained below
happy-dom, for comparison median 1.25-1.28x faster than jsdom, but changes results in 12 of 13 suites
Fast mode, 16 Vitest suites up to 9.6x over jsdom (VueUse); median 1.54x over jsdom, 1.01x over the drop-in
Version dependence (jsdom alone) another jsdom version changes results in 13 of 23 projects
Web Platform Tests 75.7% of subtests in 18 test-DOM areas (jsdom: 67.7%)

Drop-in speed and results

The first of three identical runs (run 38426985620), sorted by jsdom time. Times are the median of 5 rounds. The range is the suite's speedup in each of the three runs.

Suite jsdom happy-dom warpdom warpdom vs jsdom warpdom, 3 runs Tests that change result: happy-dom / warpdom
ariakit 537.3 s 232.1 s 407.2 s 1.32x 1.32–1.39x 51 / 7 ¹
CopilotKit 456.5 s 48.6 s ² 366.3 s 1.24x 1.23–1.25x 4,681 / 0
React Spectrum 419.9 s 363.1 s 338.4 s 1.24x 1.19–1.26x 1,441 / 0
MUI 264.0 s 185.1 s 141.2 s 1.89x 1.88–1.89x 1,042 / 0
ark-ui 205.7 s 141.5 s 130.8 s 1.59x 1.56–1.61x 116 / 3 ³
svelte 91.4 s 85.5 s 84.6 s 1.08x 1.08–1.09x 288 / 1 ⁴
Vuetify 39.6 s 26.7 s 26.8 s 1.49x 1.49–1.52x 4 / 0
emoji-picker-element 37.2 s 32.3 s 29.1 s 1.28x 1.18–1.28x 1 / 0
Radix 29.6 s 19.7 s 16.4 s 1.79x 1.75–1.86x 18 / 0
floating-ui 23.7 s 18.8 s 15.5 s 1.53x 1.52–1.55x 14 / 0
analog 19.3 s 17.3 s 16.0 s 1.20x 1.19–1.21x 2 / 0
react-hook-form 12.8 s 14.2 s 10.3 s 1.24x 1.21–1.24x 8 / 0
solid 3.4 s 3.1 s 3.3 s 1.03x 1.03x in each 0 / 1 ⁵

¹ Tests that fail on jsdom and pass on warpdom. ² CopilotKit is fast on happy-dom because 4,681 of its tests break there. ³ Tests that fail on jsdom and pass on warpdom, and one that is flaky on jsdom too (2 without it). ⁴ A deliberate difference (DOMException, below). ⁵ An order-dependent test of solid's, not a DOM difference (below).

  • Median: 1.28x, 1.27x and 1.26x in the three runs (95% two-level bootstrap 1.22–1.54x, 1.21–1.54x, 1.19–1.55x), 1.27x over the three runs' rounds pooled. The suites fall into two groups, about 1.0x to 1.3x and 1.5x to 1.9x, so read the table rather than the median. Between runs, a suite's speedup moved by a median of 2.3% and at most 8.6% (emoji-picker-element), although absolute times moved by up to 1.6x with the runner's CPU (Radix on jsdom: 28.9 s to 46.8 s).
  • Where the gain is small: a faster DOM only speeds up the part of a run spent in the DOM; compilers, frameworks, module loading and waiting stay as they are. solid (1.03x; single rounds 0.97x to 1.06x) and svelte (1.08x to 1.09x) gain little, as do the held-out taiga-ui (1.01x, 0.99x to 1.03x) and react-router (1.04x) and the Wire web app (1.03x, 1.00x to 1.03x).
  • happy-dom is about as fast (median 1.25x, 1.27x, 1.28x over jsdom in the three runs) but changes results in 12 of the 13 suites, and is slower than jsdom on react-hook-form (0.88x to 0.94x).

handsontable

Reported apart from the medians because its core, React and Vue packages run jsdom 16.7.0, older than the jsdom 20 the medians require. With a warm Jest cache the whole suite ran 8.62x faster in the run above (593.2 s on jsdom, 69.2 s on warpdom) and 7.97x to 8.87x in the three runs; the core package alone 11.2x to 12.5x, the Angular wrapper (jsdom 20.0.3) 1.86x to 1.98x. On the first run after install, when both DOMs pay for a cold transform cache and the Angular wrapper's compile, it was about 2.5x (2.53x to 2.55x). 3 tests differ, all deliberately (below). happy-dom completed no timed round.

On handsontable's current branch (develop at fa6f901, measured 2026-10-14 with warpdom 0.1.1, 5 rounds) the speedup is smaller, 3.17x (251 s on jsdom, 79 s on warpdom; core 3.48x): handsontable has since added its own workarounds for jsdom's slowness (a stylesheet mock and a patch of jsdom's CSS library), which make its jsdom baseline faster. 7 of its new tests test that patch of jsdom's CSS library, which warpdom does not have, so they fail on warpdom.

Held-out suites and two applications

Held-out. Nine projects warpdom was not tuned on (naive-ui, taiga-ui, melt-ui, dom-testing-library, tanstack-query, solid-primitives, VueUse, react-router, downshift; about 13,500 tests). At first contact 58 tests (0.43%) differed; fixing their causes means the set is no longer unseen. Drop-in, 5 rounds: a median of 1.36x (95% two-level bootstrap 1.05x to 1.69x) over the 8 projects with valid jsdom rounds, from 1.01x (taiga-ui) to 1.96x (VueUse); solid-primitives 1.14x from its raw rounds (its jsdom runs report 901 or 922 tests, so the broken-round rule drops them). In the 17 CI rounds that ran each of them, only these tests changed result: tanstack-query 1 (the ownerNode difference below), melt-ui 1 (speed-sensitive, below) and solid-primitives 4 (an order dependence of the project's own, below), plus flaky tests.

Web applications. Excalidraw ran 1.21x faster (1.20x to 1.23x) and the Wire web app 1.03x (1.00x to 1.03x, about equal), with no test that changed result. happy-dom changed 44 and 63 of their tests and ran Wire at 0.54x of jsdom's speed. Both apps drove fixes when they were first tried (2 and 141 tests differed then), so this is a migration case study, not held-out evidence.

Version dependence

Suites encode the jsdom version they were written against. Measured with jsdom alone, no warpdom: running each project on jsdom 20.0.3, 26.1.0 or 30.1.2 instead of its own version changed the status of tests in 13 of 23 projects (counting tests that ran; files that fail to load under a forced version are left out). It is concentrated: ariakit 258 tests (13.9%) on jsdom 20, React Spectrum 138 on jsdom 30, svelte 38, the others 1 to 16. Even a minor update of cssstyle, the CSS library jsdom 26 and earlier load (4.1.0 to 4.6.0), changed 12 svelte tests. warpdom therefore follows the jsdom (and cssstyle) each project installs. Given the latest jsdom's behavior instead of their own, warpdom would differ from jsdom in more tests in 7 of 23 suites: React Spectrum 0 → 146, svelte 1 → 25, handsontable 3 → 23, downshift 0 → 7, ark-ui 2–3 → 6, CopilotKit 0 → 1, dom-testing-library 0 → 1.

Web Platform Tests

On the 18 WPT areas a test DOM is expected to cover (3,825 files; dom, domparsing, html/dom, html/semantics/forms and interactive-elements, css/cssom, css/cssom-view, url, encoding, encoding/streams, FileAPI, WebCryptoAPI, hr-time, user-timing, the resize and intersection observers, web-animations/interfaces and xhr), warpdom passes 75.7% of 188,497 subtests (jsdom 30.1.2: 67.7%). It is ahead or level in every area: html/semantics 3,972 vs 3,838 subtests, domparsing 449 vs 342. Neither implements WebCrypto's subtle, the observers or Web Animations (opt-in work, #5-#12), which keeps both totals low; most xhr tests need WPT's server, which the runner does not have. warpdom was worked on against the first four areas (87.4% vs 80.5% there on 2026-10-13), so those are not an independent check. The runner is in wpt/; the workflow runs all 18 areas monthly at the newest WPT.

Full tables, method, profiles and caveats: docs/research/README.md.

Differences from jsdom

API by API, how warpdom, jsdom, happy-dom and linkedom each support the web platform is on DOMParity, a separate reference site (maintained by warpdom's author).

warpdom matches jsdom's observable behavior where tests depend on it, and otherwise follows the standards. Deliberate differences, each with a test, that changed a test in the 25 measured suites:

Area warpdom jsdom Seen in
DOMException An Error in the test's realm, as the standard says Not one in Vitest's default pools 1 svelte test
CSSStyleSheet.ownerNode The owning element in the jsdom 27 and 28 profiles, as in jsdom 29 and later; none in the 20 to 26 profiles, as those versions 27.0: none; 27.1 and 27.2: null; 27.3 to 28.1: an internal object that is not a Node 1 tanstack-query test (jsdom 27.4)
Style-sheet parse errors Not reported to the console jsdom 16 cannot parse CSS nesting, drops such a sheet and reports "Could not parse CSS stylesheet" 3 handsontable tests of its filter for that message (jsdomThemeCssFilter.unit.js)

The other differences, in full in docs/differences.md:

Category In short
Results that change for other reasons Tests that fail on jsdom and pass on warpdom (ariakit, ark-ui), one speed-sensitive test (melt-ui), two order dependences of the projects' own suites (solid, solid-primitives)
Events and focus isTrusted is a prototype accessor; tabindex="abc" gives the default tab index; a disabled fieldset, optgroup or option is not focusable, as the standard says
Standard behavior where jsdom deviates FormData, formnovalidate, Selection, custom elements, XPath, MutationObserver, document.write, CSSOM, XML serialization, iframes and more
Additions jsdom lacks innerText, ToggleEvent, Range rects and others
Styles MediaList and indexed style properties the same in every profile; spied style setters see assignments only; some cssstyle details and computed values not modeled
Platform objects FileReader, TextDecoder, URL, WebAssembly in iframes, WebSocket
XMLHttpRequest Requests go through the host's fetch: header lists, cookies, sync requests, file: URLs, Referer and a few event orders differ

Not implemented (jsdom lacks these too): layout (sizes and positions are 0, as in jsdom), inert, navigation to other documents. Not implemented in warpdom only: loading external style sheets, scripts or pages, which jsdom does when configured to (resources: 'usable').

API

import { createWindow } from 'warpdom';   // or const { createWindow } = require('warpdom');

const window = createWindow({ url: 'http://localhost/', jsdom: '30.1.2', fastFrames: false });
window.document.body.innerHTML = '<p>hello</p>';

require('warpdom') works in Node and in Jest test files, and TypeScript types ship with the package. docs/api.md has the options and the equivalents of jsdom's API (new JSDOM(html, { url }), dom.window, dom.serialize()) for tests that create windows themselves.

warpdom/compare exports compare(options), the API behind the CLI; warpdom/migrate exports migrate({ cwd, apply }), which returns the plan.

Development

npm test              # unit and parity tests (node:test; parity tests run jsdom side by side)
npm run test:types    # the published types, under tsc --strict (test/types)
npm run build         # dist/warpdom.cjs, the CommonJS build (also run by npm test and npm pack)
npm run bench:load    # cold load and window creation
npm run bench:hot     # hot DOM members against jsdom and happy-dom
npm run format        # Prettier (CI runs npm run format:check)
npm run lint          # ESLint readability rules, such as no nested ternaries (Node 22.13+)
node wpt/run.mjs dom/nodes --engine warpdom,jsdom   # Web Platform Tests

warpdom is written from the specifications; jsdom is used as a black-box reference (its source is not read or copied). Plans and decisions: PLAN.md; architecture: docs/design.md; the road to 1.0: docs/road-to-1.0.md; versions and support: docs/stability.md; reporting a vulnerability: SECURITY.md. Code is formatted with Prettier and linted with ESLint in CI; existing nested ternaries are listed in eslint-suppressions.json until they are rewritten. git config blame.ignoreRevsFile .git-blame-ignore-revs keeps the formatting commit out of git blame. Releases are published from GitHub Actions with npm provenance when a version tag is pushed: RELEASING.md.

The weekly upstream check (upstream-watch), which opens an issue when a jsdom release newer than the modeled range appears, also reads DOMParity's existence data and files the gaps it finds (scripts/api-gaps.mjs): one issue (labels parity, missing-api) per interface jsdom has that warpdom lacks, or with members jsdom has that warpdom lacks, at most 15 a run; and one tracking issue, "Beyond jsdom: core APIs other test DOMs have", whose checklist lists the "Test DOM core" APIs neither jsdom nor warpdom has but happy-dom does, rewritten each run with ticked boxes kept. What an existing API issue names is skipped, and nothing happens when the data has not changed and no gaps are pending (a run that hit the limit of 15 issues files the rest on later runs, with the same data). Run node scripts/api-gaps.mjs locally for a dry run that prints what it would open.

CI scans the dependencies with CVE Lite CLI (fails on high or critical advisories in production dependencies; results in GitHub Code Scanning) and runs OpenSSF Scorecard weekly.

Citing

If you use warpdom in research, please cite it: GitHub's "Cite this repository" button reads CITATION.cff, and each release is archived on Zenodo with a DOI.

License

MIT. Generated data tables in src/ are derived from the Unicode Character Database (src/bidi-data.js, Unicode License v3) and from webref (src/css-data.js, src/html-elements.js, src/event-handler-names.js; MIT); the scripts that generate them are in scripts/. The Web Platform Tests are not included: the runner in wpt/ uses a separate checkout (WPT is under the 3-Clause BSD License).

Releases

Sponsor this project

Packages

Contributors

Languages