0

Jest 30 uses JSDOM 26, which breaks my tests. I want to test if location.search was modified. Since JSDOM 21, it is not possible to mock location.search as easily as it used to be:

// does not work anymore
delete window.location;
Object.defineProperty(window, 'location', { // TypeError: Cannot redefine property: location
  writable: true,
  value: { search: '' },
});

Then I learned about @jest/environment-jsdom-abstract. It is designed to inject custom behavior into JSDOM.

So I created a wrapper and tried it without jest.

const { JSDOM } = require("jsdom");

/** @implements {Partial<Location>} */
class DummyLocation {
    hash = "";
    host = "";
    hostname = "";
    href = "";
    toString() { return this.href; }
    origin = "";
    pathname = "";
    port = "";
    protocol = "";
    search = "";
    /** @param {string | URL} url */
    assign(url) {
        this.href = String(url);
    }
    reload() {}
    /** @param {string | URL} url */
    replace(url) {
        this.href = String(url);
    }
}
class JSDOMWithDummyLocation extends JSDOM {
    constructor(...args) {
        super(...args);
    }
    #mockLocation = new DummyLocation();
    #documentProxy = new Proxy(super.window.document, {
        get: (target, prop, receiver) => {
            if(prop !== "location") return Reflect.get(target, prop, receiver);
            return this.#mockLocation;
        }
    });
    #windowProxy = new Proxy(super.window, {
        get: (target, prop, receiver) => {
            switch (prop) {
                case "document": return this.#documentProxy;
                case "location": return this.#mockLocation;
                default: return Reflect.get(target, prop, receiver);
            }
        }
    });

    get window() {
        return this.#windowProxy;
    }
}

const dom = new JSDOMWithDummyLocation(``, {});

console.assert(
    dom.window.location.search === "",
    "document.location.search should be\n",
    "",
    "\nbut is\n",
    dom.window.location.search
);

const searchValue = "foo=x";
dom.window.location.search = searchValue;

console.assert(
    dom.window.location.search === searchValue,
    "document.location.search should be\n",
    searchValue,
    "\nbut is\n",
    dom.window.location.search
);

It works as expected.

Now I wrap it in a file to use it for jest-environment

// jest-environment-jsdom-with-mock-location.js
import BaseEnv from "@jest/environment-jsdom-abstract";
import JSDOM from "jsdom";

/** @implements {Partial<Location>} */
class DummyLocation {
    //...
}
class JSDOMWithDummyLocation extends JSDOM {
    //...
}

export default class JestJSDOMEnvironment extends BaseEnv {
    /**
     * @param {*} config
     * @param {*} context
     */
    constructor(config, context) {
        super(config, context, {...JSDOM, JSDOM: JSDOMWithDummyLocation});
    }
}

And here is the test file:

/**
  * @jest-environment ./jest-environment-jsdom-with-mock-location.js
 */

describe("location", () => {
    test("window.document.location", () => {
        expect(window.document.location.search).toBe("");
        window.document.location.search = "foo=bar";
        expect(window.document.location.search).toBe("foo=bar"); // fails
    });
});

It fails. It also logs: Error: Not implemented: navigation to another Document. This means that it doesn't use my implementation, but the original JSDOM implementation.

What concept of @jest/environment-jsdom-abstract did I not understand? How do I have to change my jest-environment-jsdom-with-mock-location.js to properly redirect to my implementation of location?

5
  • 1
    I would say your usage of @jest/environment-jsdom-abstract is correct and expected. Besides, I was thinking instead of setting value directly for window.location, how about we use browser API? Something like window.history.pushState({}, '', '/?foo=bar');expect(window.document.location.search).toBe('?foo=bar'); I don't know how to fix that Proxy problem. It seems to be difficult to mock window.location these days Commented Oct 8 at 14:11
  • I never used the history API, but I suppose Jsdom doesn't implement it either. Commented Oct 8 at 20:32
  • I looked at the sources of @jest/environment-jsdom-abstract but I couldn't find any place where "my" window is replaced with the original. Commented Oct 8 at 20:34
  • I created an issue: github.com/jestjs/jest/issues/15857 Commented Oct 9 at 14:11
  • Interesting, I debugged and I can see this.global returns your Window proxy object and this.global.location returns DummyLocation. I only see the error console.error Error: Not implemented: navigation (except hash changes) Commented Oct 13 at 19:13

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.