How to fix Error: Not implemented: navigation (except hash changes)

42,811

Solution 1

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.


References:

Solution 2

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/

Solution 3

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
    });

Solution 4

You can use jest-location-mock package for that

Usage example with CRA (create-react-app)

// src/setupTests.ts
import "jest-location-mock";

Solution 5

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() }
Share:
42,811

Related videos on Youtube

Tran Son Hoang
Author by

Tran Son Hoang

Make this shit happen :D

Updated on July 09, 2022

Comments

  • Tran Son Hoang
    Tran Son Hoang almost 2 years

    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
  • Tran Son Hoang
    Tran Son Hoang over 5 years
    Yes. I applied this solution by replacing href by assign and it works well.
  • Bennybear
    Bennybear almost 4 years
    For Ionic React users. You can place this code in your setupTests.ts file.
  • Jabinator1
    Jabinator1 over 2 years
    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!
  • Viraj Singh
    Viraj Singh over 2 years
    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');
  • Logan Cundiff
    Logan Cundiff about 2 years
    For Typescript: window.location = ({ assign: assignMock as any }) as Location;