Skip to content

Don't stuff jsdom globals onto the Node global

Zearin edited this page Feb 25, 2023 · 9 revisions

A common antipattern we see when people use jsdom is copying globals from a jsdom window onto the Node.js global, and then trying to run the code—intended for a browser—inside Node.js. This is very bad and you should not do it. It runs code intended for the web browser in some kind of hybrid franken-environment polluted with a ton of globals that don't make sense there, and loses all benefits of isolating your code into a jsdom window.

Running code inside the jsdom context

Instead, you should run the code inside the jsdom context itself. There are a variety of ways to do this. If you need to run a string of code, you can do

const { window } = new JSDOM(``, { runScripts: "outside-only" });

window.eval(`
  // This code executes in the jsdom global scope
  globalVariable = typeof XMLHttpRequest === "function";
`);

assert(window.globalVariable === true);

If you want to load a file into jsdom, you can do it by creating a script element:

const { window } = new JSDOM(``, { runScripts: "dangerously" });
const myLibrary = fs.readFileSync("../../whatever.js", { encoding: "utf-8" });

const scriptEl = window.document.createElement("script");
scriptEl.textContent = myLibrary;
window.document.body.appendChild(scriptEl);

An example in a test runner

Here's an example of how to load a library into a separate jsdom window created for each test:

const { JSDOM } = require("jsdom");
const myLibrary = fs.readFileSync("../../whatever.js", { encoding: "utf-8" });

let window;
beforeEach(() => {
  window = (new JSDOM(``, { runScripts: "dangerously" })).window;

  // Execute my library by inserting a <script> tag containing it.
  const scriptEl = window.document.createElement("script");
  scriptEl.textContent = myLibrary;
  window.document.body.appendChild(scriptEl);
});

it("should do the right thing", () => {
  assert.equal(window.myLibrary.doThing("foo"), "bar");
});

This way, each test creates an entirely new window and document. This contrasts with the very bad approach of mashing various properties of window onto the Node.js global, which causes all these properties to be shared not only across all your tests, but across your entire Node.js process.