Error not implemented navigation except hash changes

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

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

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and
privacy statement. We’ll occasionally send you account related emails.

Already on GitHub?
Sign in
to your account


Open

ramusus opened this issue

Jan 12, 2018

· 61 comments


Open

Error: Not implemented: navigation

#2112

ramusus opened this issue

Jan 12, 2018

· 61 comments

Comments

@ramusus

After recent upgrade jest (which uses jsdom in background) from version 21.2.0 to 22.0.6 I have started getting error: "Error: Not implemented:" navigation

My code relies on window.location and I use in tests:

beforeEach(() => {
                window.location.href = `/ms/submission/?mybib`;
                window.location.search = '?mybib';
});

Is there a way to define a value of window.location.search using new version of jsdom?

mliq, probablyup, underbyte, davidjb, desfero, konekoya, goodwin64, lvlohammadi, linoleum-js, jfulse, and 71 more reacted with thumbs up emoji

@ramusus
ramusus

changed the title
«Error: Not implemented:» navigation

Error: Not implemented: navigation

Jan 12, 2018

@cpenarrieta

I’m getting this error too

Error: Not implemented: navigation (except hash changes)
    at module.exports (...node_modulesjsdomlibjsdombrowsernot-implemented.js:9:17)
    at navigateFetch (...node_modulesjsdomlibjsdomlivingwindownavigation.js:74:3)

@domenic

jsdom does not support navigation, so setting window.location.href or similar will give this message. I’m not sure if Jest was just suppressing these messages before, or what.

This is probably something you should fix in your tests, because it means that if you were running those tests in the browser, the test runner would get completely blown away as you navigated the page to a new URL, and you would never see any tests results. In jsdom instead we just output a message to the console, which you can ignore if you want, or you can fix your tests to make them work better in more environments.

Anyway, I’d like to add more documentation on this for people, so I’ll leave this issue open to track doing so.

davidjb, IvanRave, konekoya, RyanLiu0235, wduqu001, hoangtranson, ug02fast, mwmcode, Evandro18, KevinHerklotz, and 17 more reacted with thumbs up emoji
worc, mtweus, wandroll, StJohn3D, eddieyu1998, vizv, DevDaveFrame, salah-mw, and kevr reacted with thumbs down emoji

@probablyup

Totally get what you’re saying. The recent Jest 22 update went from JSDOM 9 to 11 IIRC, so the behavior back in 9.x might have been quite different.

All that aside, I would love to see navigation implemented in JSDOM with some sort of flag to make it a no-op in terms of loading a different page (in similar spirit to HTML5 pushstate.) The library is very commonly used for testing purposes so, while perhaps a quirky request, it would be used often.

@domenic

I don’t think we should add a flag that makes your tests run different in jsdom than in browsers. Then your stuff could be broken in browsers (e.g. it could be redirecting users to some other page, instead of doing the action that your tests see happening) and you wouldn’t even notice!

@probablyup

Well in this case it wouldn’t doing anything different, other than not unloading the current page context. I’d still expect window.location.href to be updated, etc.

@hontas

@domenic I’m having the same issue and I was wondering if there is some sort of best practice to setup JSDOM with an app that sets window.location. From what I can tell JSDOM throws an error when trying to set window.location and logs an error when trying to set window.location.href — however I’m reading on mdn that the two should be synonyms. Should I be updating the location in another way thats easier to stub?
Thankful for help 😅

@hontas

Allow me to post the answer to my own question 😁
I simply replace the usages of window.location = url; and window.location.href = url; with

window.location.assign(url);

and then in my tests I did:

sinon.stub(window.location, 'assign');
expect(window.location.assign).to.have.been.calledWith(url);

Works like a charm — hope it can be of help to someone else 👍

leopku, edouardsouan, kalinowski5, rolandgnm, sbancal, john-codeworx, arvinsim, drewmoore, skratchdot, cvharris, and 82 more reacted with thumbs up emoji
Evandro18, ljx213101212, noambenami, ohelixa, keanan, alekshs, boatcoder, felipecodes, adam-gipril, nck, and 6 more reacted with hooray emoji
MuAugusto and hitallow reacted with confused emoji
kalinowski5, cvharris, AlbertWhite, markokostovski, mandemeskel, bogdosarov, gthmb, wduqu001, achepukov, andrewshatnyy, and 25 more reacted with heart emoji

@xixixao

Agree this should work out of the box. We mock window.location at FB, but that conflicts with jsdom’s History implementation.

@Zirro

As a small team, we would certainly appreciate help from the larger projects that depend on us to properly implement navigation in jsdom.

If anyone is interested, #1913 could be a good place to start.

@vvo

Possible solution is to rely on dependency injection/mock for the window object in unit tests.

Something like:

it('can test', () => {
  const mockWindow = {location: {href: null}};
  fn({window: mockWindow});
  expect(mockWindow.href).toEqual('something');
});

This is not ideal but as said by @domenic:

This is probably something you should fix in your tests, because it means that if you were running those tests in the browser, the test runner would get completely blown away as you navigated the page to a new URL

For now we live with this and yes we change our implementation code for tests which is considered bad practice but we also sleep well at night!

Happy testing

ydogandjiev, s-pace, skyboyer, dileepthomas, PunchyRascal, javorosas, benjaminkay93, stefan505, pbroschwitz, zhanghao-zhoushan, and 2 more reacted with thumbs up emoji
toddpi314, elkwood, rubixibuc, dlo, avegancafe, wouterkroes, verydevuser, hvidalnegreiros-godaddy, MayersonMeli, and lucaskorz reacted with thumbs down emoji
goingtogogo reacted with laugh emoji

@vvo
vvo

mentioned this issue

Apr 25, 2018

@zxiest

@hontas’s solution helped:

I did use window.location.assign(Config.BASE_URL); in my code.

And here’s the test:

jest.spyOn(window.location, 'assign').mockImplementation( l => {
   expect(l).toEqual(Config.BASE_URL);
})

window.location.assign.mockClear();
hontas, kaplich, bluemagma612, briandelancey, vafanasenka, josemigallas, felipepastor, ozmoroz, icsaba, EduardoFLima, and 4 more reacted with thumbs up emoji
nextgennexia, leonidasv, and mina-ltse reacted with thumbs down emoji

@yvele

Same problem, I’m using window.location.search = foo; in my code and I would like to test it using jsdom (and jest) 🤔

PS: Related to facebook/jest#5266

@yuri-sakharov

After updating to jsdom 12.2.0 got error:
TypeError: Cannot redefine property: assign
on const assign = sinon.stub(document.location, 'assign')
how to fix it?

@nickhallph

@yuri-sakharov

After updating to jsdom 12.2.0 got error:
TypeError: Cannot redefine property: assign
on const assign = sinon.stub(document.location, 'assign')
how to fix it?

sinon.stub(document.location, 'assign')

needs to be:

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

you need to replacedocument with window

@yordis

I have the following function

export const isLocalHost = () => Boolean(
  window.location.hostname === 'localhost' ||
  // [::1] is the IPv6 localhost address.
  window.location.hostname === '[::1]' ||
  // 127.0.0.1/8 is considered localhost for IPv4.
  window.location.hostname.match(
    /^127(?:.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
  )
);

for me to be able to test that it works I inject directly the hostname

it('#isLocalHost should return true for all the cases of localhost', () => {
    window.location.hostname = 'localhost';
    expect(isLocalHost()).toBeTruthy();

    window.location.hostname = '[::1]';
    expect(isLocalHost()).toBeTruthy();

    window.location.hostname = '127.0.0.1';
    expect(isLocalHost()).toBeTruthy();

    // Reset back the hostname to avoid issues with it
    window.location.hostname = '';
  });

But I am getting this error.

I don’t expect jsdom to fully implement the navigation but at least add the keys and mock the functions.

I am confused on why I keep getting this error, I just want to be able to setup the value.

@yuri-sakharov

@nickhallph
I replaced it to window.location as you wrote but result the same
TypeError: Cannot redefine property: assign
Any ideas?

nielskrijger, daon, metasean, WingMrL, abhaynahar, RichardWright, imakecodes, fefrontend, mzedeler, tamj0rd2, and 6 more reacted with thumbs up emoji

@nielskrijger

Same problem as @yuri-sakharov running mocha.

By no means I seem able to replace/update/mock or do anything with window.location.*. The only way I see around this is creating my own custom window.location mock and change the entire codebase to depend on that.

@tomturton

@hontas’s solution helped:

I did use window.location.assign(Config.BASE_URL); in my code.

And here’s the test:

jest.spyOn(window.location, 'assign').mockImplementation( l => {
   expect(l).toEqual(Config.BASE_URL);
})

window.location.assign.mockClear();

@zxiest’s Jest version of @hontas’ solution didn’t work for me, but this did:

window.location.assign = jest.fn();
expect(window.location.assign).toHaveBeenCalledWith('https://correct-uri.com');
window.location.assign.mockRestore();
yordis, hontas, pegel03, chenxiaochun, builtbywill, matteocng, misstricky, vladyn, kreatemore, planetchili, and 7 more reacted with thumbs up emoji
coler-j and akanksha-swiggy reacted with thumbs down emoji
antick reacted with rocket emoji

@chrisbateman

If you don’t want to change your code to use location.assign — this seems to work with JSDom 11 and 13 (though there’s a chance JSDom might break it in the future…)

delete window.location;
window.location = {}; // or stub/spy etc.
igorlima, LmKupke, stephanie-caa, fantasyroot, kareemsalah227, wyqydsyq, Kelier, VictorKolb, JPeer264, abritabroad, and 67 more reacted with thumbs up emoji
LmKupke, i-am-lioness, kareemsalah227, linconkusunoki, VictorKolb, JPeer264, mattlo, mohitb3, besideL, rawatpallavi, and 19 more reacted with hooray emoji
kareemsalah227, VictorKolb, JPeer264, mattlo, rawatpallavi, EugeneGrigorenko, geniusab, njj, richi1717, brunorcunha, and 16 more reacted with heart emoji
VitalyEmelyanov, alanquigley-toast, TolikSyedin, StJohn3D, ibrahimBeladi, and morsh reacted with rocket emoji

@ggregoire

The last answer worked for me, but I had to define replace:

delete window.location
window.location = { replace: jest.fn() }

Hope it helps.

JPeer264, JoshuaVSherman, chrisjlee, PierrickGT, sdwvit, JesseDeBruijne, yuriigeivakh, sunderls, arubtsov, damianszocik, and 18 more reacted with thumbs up emoji
Chaoste and hvidalnegreiros-godaddy reacted with thumbs down emoji
sdwvit, krulik, VitalyEmelyanov, icsaba, mdumrauf, and edwinwong90 reacted with hooray emoji
sdwvit, yuriigeivakh, krulik, VitalyEmelyanov, mdumrauf, and OlesyaKlochko reacted with heart emoji
sdwvit, yuriigeivakh, krulik, VitalyEmelyanov, icsaba, and mdumrauf reacted with rocket emoji
JPeer264 reacted with eyes emoji

@RichardWright

I’m getting TypeError: Cannot redefine property: assign with sinon 7.2.3 and jsdom 13.2.0. No idea why this works for some people and not others?

@sergioviniciuss

This is what worked for me:

    global.window = Object.create(window);
    const url = 'http://localhost';
    Object.defineProperty(window, 'location', {
      value: {
        href: url,
      },
      writable: true,
    });
annakleszcz, felipeegasneris, westpole, harrisgeo88, ggabrakadabra, stevenmusumeche, austi10, shaolinmkz, braxtondiggs, damianszocik, and 19 more reacted with thumbs up emoji
elemanhillary-zz and Chaoste reacted with thumbs down emoji
drphelps and khanhhaquang reacted with confused emoji
bakareao and GiampaoloF95 reacted with heart emoji
bakareao reacted with rocket emoji

@yordis

We used pushState to make this to work

 window.history.pushState(
        {},
        '',
        'http://localhost/something/123?order=asc'
      );
bhrutledge, gaui, stefk, sofiafrocha, youngzhao-xyz, JofBigHealth, menttys, JRJurman, PhilippMi, eldoy, and 9 more reacted with thumbs up emoji

@mpareja

This is a difficult situation. JSDOM does not fully supports navigation (other than breadcrumbs) and JSDOM does not allow us to mock-out navigation. The end result is that I can’t write tests which ultimately attempt to trigger navigation.

If JSDOM did learn to navigate (I’m not even sure what that means), maybe I could assert about being on the appropriate page. For my testing use cases, though, asserting the navigation was triggered, rather than actually performed, is much cleaner/faster. It’s what I’ve historically done when testing using jsdom and now it’s broken.

maneeshpal, thomaslitton, Zukzuk, joshjg, MilosRasic, niketpathak, ibash, StJohn3D, s-taylor, nwalters512, and zbyte64 reacted with thumbs up emoji

@mattcphillips

Is anyone else running into the same Error: Not implemented: navigation (except hash changes) during Jest tests that fire a click event on an anchor element with an href? For my tests, I’m spying on a function that gets called onClick and making assertions about that so I need to actually fire the click event on the anchor element.

The solutions above related to mocking window.location work for me where I am explicitly calling window.location.replace or window.location.assign, but don’t help for this case where the navigation originates from an anchor element being clicked.

Any ideas on solutions? Thanks!

Sabrinovsky, sammndhr, StJohn3D, molinx, jsroga, mjvalade, jkkjonah, TheOtherDude, coler-j, Daemeron, and 22 more reacted with thumbs up emoji

@domenic

How would you test that code in a browser? Keep in mind that in the browser clicking on the link would blow away your whole page and throw away any test results. So whatever you would do to prevent that, will also prevent the much-less-dramatic warning message jsdom outputs to the console.

@romanhavr

In my situation project upgraded Jest from 23 to 26 version.
I had a problem with location.search. Had the same error Error: Not implemented: navigation.
Module I tested gets values of search query params.
Next implementation worked for me:

beforeAll(() => {
  delete window.location;
  window.location = new URL('your URL');
})

afterAll(() => {
  window.location.search = '';
})

@Sabrinovsky

@mattcphillips have you figured out how to fix this? i’m also having problem when clicking

@mattcphillips

@Sabrinovsky No, I ended up just adding a script to the setupFiles in my jest config that would swallow the console errors coming from jsdom navigation so that they weren’t cluttering up our tests. More of a bandaid than anything else, but it sounded like these errors are to be expected in our test setup.

Here’s the script that I run before my tests:

// There should be a single listener which simply prints to the
// console. We will wrap that listener in our own listener.
const listeners = window._virtualConsole.listeners('jsdomError');
const originalListener = listeners && listeners[0];

window._virtualConsole.removeAllListeners('jsdomError');

// Add a new listener to swallow JSDOM errors that orginate from clicks on anchor tags.
window._virtualConsole.addListener('jsdomError', error => {
  if (
    error.type !== 'not implemented' &&
    error.message !== 'Not implemented: navigation (except hash changes)' &&
    originalListener
  ) {
    originalListener(error);
  }

  // swallow error
});
Sabrinovsky, markplindsay, tswaters, JamesMcMahon, sarunast, jsroga, daniesg, sorahn, blephy, mkerry, and 4 more reacted with thumbs up emoji
DarkTrick reacted with heart emoji

@Akhil-bridge

This worked for me.

delete global.window.location
global.window.location = { href: 'https://test.com' }

@hendrawanhadikusuma

This worked for me

delete window.location
window.location = { assign: jest.fn() }

@devniel

If you get TypeError: Cannot assign to read only property 'assign' of object '[object Location]', then using something like this in your jest.setup.ts:

global.window = Object.create(window);
Object.defineProperty(window, 'location', {
  value: {
    ...window.location,
  },
  writable: true,
});

@s-taylor

If it helps anyone this is what I did. We are using mocha not jest so only have sinon available.

exports.jsdom = new JSDOM('<!doctype html><html><body></body></html>', { url: 'http://localhost' });
const { window } = exports.jsdom;

/* This enables stubbing window.location.assign for url changes */
const { location } = window;
delete window.location;
window.location = { ...location, assign: () => {} };

You can then freely stub…
const assignStub = sandbox.stub(window.location, 'assign');

@blephy

@Sabrinovsky No, I ended up just adding a script to the setupFiles in my jest config that would swallow the console errors coming from jsdom navigation so that they weren’t cluttering up our tests. More of a bandaid than anything else, but it sounded like these errors are to be expected in our test setup.

Here’s the script that I run before my tests:

// There should be a single listener which simply prints to the
// console. We will wrap that listener in our own listener.
const listeners = window._virtualConsole.listeners('jsdomError');
const originalListener = listeners && listeners[0];

window._virtualConsole.removeAllListeners('jsdomError');

// Add a new listener to swallow JSDOM errors that orginate from clicks on anchor tags.
window._virtualConsole.addListener('jsdomError', error => {
  if (
    error.type !== 'not implemented' &&
    error.message !== 'Not implemented: navigation (except hash changes)' &&
    originalListener
  ) {
    originalListener(error);
  }

  // swallow error
});

This worked for me with angular.
All stub workaround provided in other messages doesn’t work with angular.

Ty @Sabrinovsky

@s-taylor

Second attempt at this, my previous attempt broke using the history lib, presumably because assign was no longer a getter.

This solves that issue…

// This makes window.location.assign writable so sinon can stub
function patchLocation(location) {
  const clone = Object.create(Object.getPrototypeOf(location));
  const descriptors = Object.getOwnPropertyDescriptors(location);
  descriptors.assign.writable = true;
  descriptors.assign.configurable = true;
  Object.defineProperties(clone, descriptors);
  return clone;
}

const { location } = window;
delete window.location;
window.location = patchLocation(location);

You can then freely stub…
const assignStub = sandbox.stub(window.location, 'assign');

@fooey

@Sabrinovsky No, I ended up just adding a script to the setupFiles in my jest config that would swallow the console errors coming from jsdom navigation so that they weren’t cluttering up our tests. More of a bandaid than anything else, but it sounded like these errors are to be expected in our test setup.

Here’s the script that I run before my tests:

// There should be a single listener which simply prints to the
// console. We will wrap that listener in our own listener.
const listeners = window._virtualConsole.listeners('jsdomError');
const originalListener = listeners && listeners[0];

window._virtualConsole.removeAllListeners('jsdomError');

// Add a new listener to swallow JSDOM errors that orginate from clicks on anchor tags.
window._virtualConsole.addListener('jsdomError', error => {
  if (
    error.type !== 'not implemented' &&
    error.message !== 'Not implemented: navigation (except hash changes)' &&
    originalListener
  ) {
    originalListener(error);
  }

  // swallow error
});

Thank you for this!

For anyone who wants to make TypeScript happy

import { VirtualConsole } from 'jsdom';

declare global {
  interface Window {
    _virtualConsole: VirtualConsole;
  }
}

@Whoaa512

What’s the workaround here if the navigation event is coming from a deeply nested click on an element wrapped in an <a> tag? I’m not able to easily stub the onClick handler to add a e.preventDefault() and I didn’t see and easy way to intercept this error and know if it was an anchor click or location.assign

MonkeyDo

added a commit
to metabrainz/listenbrainz-server
that referenced
this issue

Sep 13, 2021

@MonkeyDo

Solves warnings about navigation not being implemented in JSDOM:
jsdom/jsdom#2112

@markoboy

I can see that there has been a lot of struggle with this from a lot of people.

In my case I had an issue while clicking on html anchor tag element in order to test that analytics are fired correctly. None of the above solutions worked.

In the end it was something very simple…

        clickedLink = getAllByRole('link').find((link) =>
          link.textContent?.includes(getDirectionsText),
        );

        clickedLink.addEventListener(
          'click',
          (event) => event.preventDefault(),
          false,
        );

        userEvent.click(clickedLink);

Attaching a click event listener to the anchor tag before emitting the click event does the trick.

I hope this will help someone else facing the same issue.

TomD0wning, audunru, jthannah, samerset-rmn, jethrodaniel, mhipszki, raebo1017, wf-benmcgarvey, ybentz, pionxzh, and 3 more reacted with thumbs up emoji
audunru, samerset-rmn, jethrodaniel, cgilroy, wronaa89, Mechdriver, tom-fx, and jonaszb reacted with hooray emoji
audunru, samerset-rmn, jethrodaniel, cgilroy, targumon, and tom-fx reacted with heart emoji

@dnalbach

For anyone who is interested, here’s how I adapted the @markoboy solution in my Jest test with Vue3:

const button = wrapper.find('#some_id');
button.wrapperElement.addEventListener('click', (event) => event.preventDefault(), false);
button.trigger('click');

@jhnance

Is anyone else running into the same Error: Not implemented: navigation (except hash changes) during Jest tests that fire a click event on an anchor element with an href? For my tests, I’m spying on a function that gets called onClick and making assertions about that so I need to actually fire the click event on the anchor element.

The solutions above related to mocking window.location work for me where I am explicitly calling window.location.replace or window.location.assign, but don’t help for this case where the navigation originates from an anchor element being clicked.

Any ideas on solutions? Thanks!

@mattcphillips

I think this is the issue I just ran into. The response below yours is what triggered a realization for me. I was testing this in the browser by setting a breakpoint on the beforeunload event. So, I just added the same thing in my test and got it to work. Still get the annoying console logs but the test is passing.

However, I will say that I’m a bit confused why the test passes on its own without this kind of event listener, but needs the event listener when run in the same file as other tests. Probably something else I’m missing here that could make my test better…

Anyway, here’s what I tried. Again, this is for an anchor element with an onClick:

window.addEventListener('beforeunload', function () {
  // make your assertion here based on w/e it is your anchor's click handler does
});

// click your anchor after later in your test

@kevr

How would you test that code in a browser? Keep in mind that in the browser clicking on the link would blow away your whole page and throw away any test results. So whatever you would do to prevent that, will also prevent the much-less-dramatic warning message jsdom outputs to the console.

In all of your replies in this thread, you seem to be completely missing the fact that there are ways to test against the DOM and changes in the location, as is done when testing frameworks like React/Angular/Vue. We are able to fire events which manipulate the DOM, and this is pretty much what’s tested everywhere in these front-end frameworks. We aren’t testing inside of a browser; we’re testing using jest or other test frameworks; we just need to be able to actually stub out these for things like React to be rid of the console.error that is produced by JSDom.

Perhaps you could help users here with some direction on doing this, rather than telling everybody to go fix their «broken code» that wasn’t even written by them. Nothing you’ve said here has been helpful or even really factual at all.

Kinda blows my mind that this issue is still open without conclusion since Jan of 2018.

Frankly, this issue is about the console.error coming out of JSDom, and it’s not about any sort of broken tests. The error just looks like it’s something breaking, but it’s just a console.error.

@domenic

jsdom is not intended to provide a testing library. We’re providing a browser implementation. If it’s not suitable for your purposes, then you should not use it.

@kevr

jsdom is not intended to provide a testing library. We’re providing a browser implementation. If it’s not suitable for your purposes, then you should not use it.

Makes much more sense.

@Focus-me34

Hey guys!

I found a solution without using mock or Object.defineProperty or by creating a. new window etc. …
Here it is :

  afterEach(() => {
      window.history.replaceState({}, "", decodeURIComponent(http://localhost/));
  });

I think the reason for the error we all had is that there’s something wrong going on with the browser history in test env (I’m personally using react-router v6 with ).

Anyway, the code snippet above replaces the current URL with the one passed as the third argument of the .replaceState() function by manipulating not the URL directly, but the history of navigation.

More about this function here: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState

Hope it was helpful! 🔥

I have a React component that is part of a wider Ruby on Rails application. It fetches a record,
and if the record does not exist, it redirects to the server-rendered 404 page.

Unfortunately, while the user will see the ‘not found’ page, the HTTP status code will be 200 OK
by default, because this page is static. Best practise would be to define a location block in your web server (Nginx, Apache, IIS etc) to respond with a 404 status when this static file is requested.

Having made this component, I’d like to add a Jest assertion that when the record is absent, the
component does not render but instead tells the window to change it’s location.

Unfortunately, a naieve assertion does not work the way I expected:

it("navigates to /404 when the record is absent", () => {
  render(<MyComponent record={undefined} />);
  expect(window.location.pathname).toBe("/404");
});

Jest tells us that we can’t use window.location in our component, because jsdom can’t navigate:

Error: Not implemented: navigation (except hash changes)

That’s fair enough! Adding navigation would be a big increase in scope for jsdom, and I can’t blame anyone for not supporting this.

The trick to getting this to work is to redefine the location property on the window object in our test, so that we are using and asserting against an object that we control:

it("navigates to /404 when the record is absent", () => {
  // jsdom doesn't allow us to 'navigate' from a component, because how would that work?
  // Since we don't need to navigate, but just assert that navigation _would_ have occurred,
  // we can replace window.location with a URL, which has a pathname property we can assert
  // against
  Object.defineProperty(window, "location", {
    value: new URL("http://example.com"),
    configurable: true,
  });

  render(<MyComponent record={undefined} />);
  expect(window.location.pathname).toBe("/404");
});

You might notice that we’re using a
URL object there. Why
is that? Well, it turns out that window.location shares many properties with
URL. Not all, and
that’s important to know — especially for things like events and functions like
reload(), but if you’re trying to assert that a property of the location
changed, this is perfect. We replace the window.location property with a new
URL object, change a property of that URL within our component, then assert that
the property changed to the value we expect it to.

You might also notice that we’re passing an option to defineProperty
configurable: true. Adding this option means that other tests can change the
value of this property later on the test. It’s also important to note that we’re
replacing window.location from when this test runs onwards. To me, this is
OK, since window.location raises errors in jsdom and doesn’t work the way we
expect anyway, but if you want to avoid side effects, you can assign the
original window.location to a variable, then assign the property back at the
end of your test — or put all this in a before/after hook in your test file
or suite.

References
  1. https://stackoverflow.com/questions/54021037/how-to-mock-window-location-href-with-jest-vuejs
  2. https://www.benmvp.com/blog/mocking-window-location-methods-jest-jsdom/
  3. https://remarkablemark.org/blog/2018/11/17/mock-window-location/

Понравилась статья? Поделить с друзьями:
  • Error non whitespace before first tag
  • Error no valid session type
  • Error no matching packages to list
  • Error no match for operator operand types are std istream
  • Error no configured target data sources