91

I am implementing unit test for a file that contain window.location.href and I need to check it.

My jest version is 22.0.4. Everything is fine when I run my test on node version >=10

But I get this error when I run it on v8.9.3

console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
      Error: Not implemented: navigation (except hash changes)

I have no idea about it. I have searched on many page to find out the solution or any hint about this to figure out what happened here.

[UPDATE] - I took a look deep to source code and I think this error is from jsdom.

at module.exports (webapp/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
at navigateFetch (webapp/node_modules/jsdom/lib/jsdom/living/window/navigation.js:74:3)

navigation.js file

exports.evaluateJavaScriptURL = (window, urlRecord) => {
  const urlString = whatwgURL.serializeURL(urlRecord);
  const scriptSource = whatwgURL.percentDecode(Buffer.from(urlString)).toString();
  if (window._runScripts === "dangerously") {
    try {
      return window.eval(scriptSource);
    } catch (e) {
      reportException(window, e, urlString);
    }
  }
  return undefined;
};
exports.navigate = (window, newURL, flags) => {
  // This is NOT a spec-compliant implementation of navigation in any way. It implements a few selective steps that
  // are nice for jsdom users, regarding hash changes and JavaScript URLs. Full navigation support is being worked on
  // and will likely require some additional hooks to be implemented.

  const document = idlUtils.implForWrapper(window._document);
  const currentURL = document._URL;

  if (!flags.reloadTriggered && urlEquals(currentURL, newURL, { excludeFragments: true })) {
    if (newURL.fragment !== currentURL.fragment) {
      navigateToFragment(window, newURL, flags);
    }
    return;
  }

  // NOT IMPLEMENTED: Prompt to unload the active document of browsingContext.

  // NOT IMPLEMENTED: form submission algorithm
  // const navigationType = 'other';

  // NOT IMPLEMENTED: if resource is a response...
  if (newURL.scheme === "javascript") {
    window.setTimeout(() => {
      const result = exports.evaluateJavaScriptURL(window, newURL);
      if (typeof result === "string") {
        notImplemented("string results from 'javascript:' URLs", window);
      }
    }, 0);
    return;
  }
  navigateFetch(window);
};

not-implemented.js

module.exports = function (nameForErrorMessage, window) {
  if (!window) {
    // Do nothing for window-less documents.
    return;
  }

  const error = new Error(`Not implemented: ${nameForErrorMessage}`);
  error.type = "not implemented";

  window._virtualConsole.emit("jsdomError", error);
};

I see some weird logics in these file.

  1. const scriptSource = whatwgURL.percentDecode(Buffer.from(urlString)).toString();
  2. then check string and return error
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
Hoang Subin
  • 6,610
  • 6
  • 37
  • 56
  • 2
    Note that this isn't necessarily an error you need to fix. It's simply a result of trying to navigate, which JSDOM doesn't yet support. For many/most users, this is a test-only error, and does not indicate any real problem with their code. See https://github.com/jsdom/jsdom/issues/2112#issuecomment-359297866. – Andrew Jan 17 '23 at 12:40

7 Answers7

57

Alternative version that worked for me with jest only:

let assignMock = jest.fn();

delete window.location;
window.location = { assign: assignMock };

afterEach(() => {
  assignMock.mockClear();
});

Reference: https://remarkablemark.org/blog/2018/11/17/mock-window-location/

blah
  • 783
  • 7
  • 7
  • 2
    just to add to this, I personally had to include all of the keys of the window.location object in order for this to work, assigning all the functions to assignMock and everything else to a string, except for ancestorOrigins, which I just set to null. This may be due to my eslint settings getting mad at me, so this might not be necessary for everyone. Other than that, this worked for me in 2021! – Jabinator1 Aug 24 '21 at 22:38
  • 7
    For Typescript: `window.location = ({ assign: assignMock as any }) as Location;` – Logan Cundiff Mar 17 '22 at 16:11
  • Note that this won't work with `window.location.href = ...`. You must use `window.location.assign(...)` in your subject under test. – Tyler Collier Jun 17 '23 at 17:55
51

Alternate solution: You could mock the location object

const mockResponse = jest.fn();
Object.defineProperty(window, 'location', {
  value: {
    hash: {
      endsWith: mockResponse,
      includes: mockResponse,
    },
    assign: mockResponse,
  },
  writable: true,
});
Mario Petrovic
  • 7,500
  • 14
  • 42
  • 62
Akhilesh.tiwari
  • 611
  • 5
  • 3
  • 3
    For **Ionic React** users. You can place this code in your *setupTests.ts* file. – Bennybear Jul 12 '20 at 16:12
  • And to expand on @Bennybear's comment, for _Vue 3_ you'll also want to add this code to your setupTests.ts file just make sure you specify the path to your setup file in your vite.config.ts And if you're using _Vitest_ just swap the jest.fn() for vi.fn() – Matt Mar 21 '23 at 14:29
35

I faced a similar issue in one of my unit tests. Here's what I did to resolve it.

  • Replace window.location.href with window.location.assign(url) OR window.location.replace(url)

  • JSDOM will still complain about window.location.assign not implemented.

    Error: Not implemented: navigation (except hash changes)

  • Then, in one of your unit tests for the above component / function containing window.assign(url) or window.replace(url) define the following

  • sinon.stub(window.location, 'assign');

  • sinon.stub(window.location, 'replace');

  • Make sure you import sinon import sinon from 'sinon';


Hopefully, this should fix the issue for you as it did for me.


The reason JSDOM complains about the ` Error: Not implemented: navigation (except hash changes)` is because JSDOM does not implement methods like `window.alert`, `window.location.assign`, etc.
Reference:
Barry Michael Doyle
  • 9,333
  • 30
  • 83
  • 143
Mihir Kale
  • 1,028
  • 1
  • 12
  • 20
  • 1
    Yes. I applied this solution by replacing `href` by `assign` and it works well. – Hoang Subin Feb 08 '19 at 13:22
  • 5
    Getting this error in typescript TypeError: Descriptor for property assign is non-configurable and non-writable 9 | > 10 | sinon.stub(window.location, 'assign'); | ^ 11 | sinon.stub(window.location, 'replace'); – Viraj Singh Feb 02 '22 at 06:55
10

You can use jest-location-mock package for that

Usage example with CRA (create-react-app)

// src/setupTests.ts
import "jest-location-mock";
Yusufbek
  • 2,180
  • 1
  • 17
  • 23
7

I found a good reference that explain and solve the problem: https://remarkablemark.org/blog/2018/11/17/mock-window-location/

Because the tests are running under Node, it can't understand window.location, so we need to mock the function:

Ex:

delete window.location;
window.location = { reload: jest.fn() }
Felipe Coelho
  • 171
  • 1
  • 3
2

Following worked for me:

  const originalHref = window.location.href;
  afterEach(() => {
    window.history.replaceState({}, "", decodeURIComponent(originalHref));
  });

happy coding :)

Luckylooke
  • 4,061
  • 4
  • 36
  • 49
  • 1
    Fixed my issue. I couldn't reassign window.location.href after navigating to a new url by using userEvent.click() function. The url of the last test was still set to the url where I was redirected on the previous test. But your solution did the job. Thank you for sharing! – Sunamin34 Jan 12 '23 at 11:24
0
 it('test', () => {
    const { open } = window;
    delete window.open;
    window.open = jest.fn();
    jest.spyOn(window, 'open');
    // then call the function that's calling the window
    expect(window.open).toHaveBeenCalled();
    window.open = open;
  });
Majid Eltayeb
  • 558
  • 4
  • 16