jest: window.location.href can't be changed in tests.

Hi @cpojer,

This is actually more of a jsdom@8 issue…see tmpvar/jsdom#1388, but I want to pin here as well so Jest picks up whatever solution jsdom comes up with.

Previously with jest-cli@0.8/jsdom@7.x you could write a test like this:

jest.autoMockOff()
jest.setMock('../lib/window', window)

jest.mock('cookies-js')
jest.mock('query-string')
jest.mock('superagent')

describe(['@utils/auth - When an AJAX response returns:'
].join('')
, function () {

  beforeEach(function () {
    window.location.href = 'http://quux.default.com'
    var queryString = require('query-string')
    queryString.__setMockParseReturns([{
      'access_token': '1234foo',
      'expires_in': '9999'
    }])
  })

  it(['should set a redirect token and goto platform ',
    'when the AJAX request returns 401.'
  ].join('')
  , function () {
    var superagent = require('superagent')
    superagent.__setMockAjaxResponses([
      [null, { 'status': 401 }]
    ])

    var href = window.location.href
    var auth = require('../index.js')
    auth.login(function (res) {})
    var Cookies = require('cookies-js')
    var config = require.requireActual('../config')
    expect(decodeURIComponent(window.location.href)).toBe([
      config.loginUrl,
      config.loginServiceLogin,
      '?client_id=',
      config.clientId,
      '&response_type=token',
      '&redirect_uri=',
      config.clientRedirectUri
    ].join(''))
    expect(Cookies.__getMockCookieData()[config.clientId + '_state_locationAfterLogin']).toBe(escape(href))
  })

And that test would pass. Since jsdom@8 this is no longer possible and these tests fail.

Seems like jsdom is looking at some type of capability, just wanted to make sure that Jest will pick up that capability when it is available.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 4
  • Comments: 74 (12 by maintainers)

Commits related to this issue

Most upvoted comments

You are right, this is indeed a jsdom issue. At Facebook, what we have done to work around this, is use this:

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

this works for us, however we are still on jsdom 7 internally.

I’ll close this, as I believe the Object.defineProperty way of doing things is fine. If that doesn’t work for you in jsdom 8, I’m happy to reopen it.

It stopped working in Jest 22.0.1

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

Error message:

TypeError: Cannot redefine property: href
        at Function.defineProperty (<anonymous>)

old ticket but for those still having this issue we’ve started using window.location.assign() instead so in our tests we can mock the assign function like so…

it('will redirect with a bad route', () => {
    window.location.assign = jest.fn();
    const redirection = shallow(<Redirection />, {
      context: {
        router: {
          location: {
            pathname: '/wubbalubbadubdub',
          },
        },
      },
    });
    expect(window.location.assign).toBeCalledWith(`${CONFIG.APP_LEGACY_ROOT}`);
  });

This worked for me.

delete global.window.location
global.window.location = { href: 'https://test-domain.com.br', ...anyOptions }

I found this post to be very helpful: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

"In your Jest configuration, make sure to set the following:

"testURL": "https://www.somthing.com/test.html"

Then in your beforeEach() section for your test, change the path as needed by using history.pushState().

window.history.pushState({}, 'Test Title', '/test.html?query=true');

Voila! Now you change out your path for any test, without having to override any jsdom configurations as others suggest in the thread mentioned above. Not sure on which thread I found this solution on, but kuddos to the dev that posted it!"

@thymikee I saw that issue but thought that the proposition was rejected. @cpojer I misread your example and mixed it up with other related to this problem, where people suggested to use Object.defineProperty(window, 'location', {value: 'url'});. Thank you!

I need to change not only the href, so I wrote simple method, that may be useful for someone who will read this thread:

const setURL = (url) => {
  const parser = document.createElement('a');
  parser.href = url;
  ['href', 'protocol', 'host', 'hostname', 'origin', 'port', 'pathname', 'search', 'hash'].forEach(prop => {
    Object.defineProperty(window.location, prop, {
      value: parser[prop],
      writable: true,
    });
  });
};

this worked for me

    const location = JSON.stringify(window.location);
    delete window.location;

    Object.defineProperty(window, 'location', {
      value: JSON.parse(location)
    });

    Object.defineProperty(global.location, 'href', {
      value: 'http://localhost/newURL',
      configurable: true
    });

on jest version 23.6.0

@Mike-Tran That works! Thanks you! However, I didn’t need the testURL or beforeEach. I just did:

window.history.pushState({}, 'Test Title', '/test.html?query=true');

And now I don’t have to use Object.defineProperty anymore 😅

Stop posting this, it does not work on jest": “^22.4.2”

Hi, I have used this in the test, i delete the global state and create a new one with jsdom… :

   describe('componentDidMount', () => {
    delete global.window
    const window = (new JSDOM(``, {url: 'https://example.org/'})).window
    global.window = window
    describe('When window is defined', () => {
      const spy = jest.spyOn(Utils, 'extractTokenFromUrl')
      it('should call extract token function with window location', () => {
        mount(<Header />)
        expect(spy).toHaveBeenCalledWith('https://example.org/')
      })
    })
  })

Btw @cpojer I start at FB on 1May… ;P

Posted it on other ticket, but I’ll post it here:

Found nice solution for Jest 21.2.1

Ok, so far the easiest solution around this is: Go into your Jest settings (for example I’ll use package.json):

"jest": { "testURL": "http://localhost" }

Now you will have access to window object and then you can set URL to whatever you like during tests.

it('Should set href url to testURL', () => {
    // Here I set href to my needs, opinionated stuff bellow
    const newUrl = 'http://localhost/editor.html/content/raiweb/it/news/2018/02/altered-carbon-best-cyberpunk-tv-series-ever.html';
    Object.defineProperty(window.location, 'href', {
        writable: true,
        value: newUrl
    });

    console.log(window.location.href);
});

it('Should set pathname url to testURL', () => {
    // Here I set href to my needs, opinionated stuff bellow
    const newUrl = '/editor.html/content/raiweb/it/news/2018/02/altered-carbon-best-cyberpunk-tv-series-ever.html';
    Object.defineProperty(window.location, 'pathname', {
        writable: true,
        value: newUrl
    });

    console.log(window.location.pathname);
});

Hopefully this helps someone.

To get this working as of June 2019 I had to do this:

    delete global.window.location;
    global.window = Object.create(window);
    global.window.location = {
      port: '123',
      protocol: 'http:',
      hostname: 'localhost',
    };

this works for me on jest 24.9.0

 window.history.replaceState({}, 'Test Title', '/test?userName=James&userNumber=007');

@cpojer, I can’t seem to figure out what I need to click on to reopen this issue…

Is there anyway in jest environment for one to call jsdom.changeUrl(window, url) as described here https://github.com/tmpvar/jsdom#changing-the-url-of-an-existing-jsdom-window-instance in jest-cli@15.1.1?

This approach works as of Sep 27, 2019: https://stackoverflow.com/a/54034379/1344144

global.window = Object.create(window);
const url = "http://dummy.com";
Object.defineProperty(window, "location", {
    value: {
       href: url
    },
    writable: true
});

This TypeScript is working for me on Jest 24.0.0 and Node 10.15.0:

src/setupTests.ts

import { mockWindow } from './testUtils';
mockWindow(window, 'http://localhost');

src/setupTests.test.ts

describe('setup tests', () => {

    describe('window.location', () => {
        const saveLocation = window.location;

        afterAll(() => {
            delete window.location;
            window.location = saveLocation;
        });

        it('location.assign assigns a location', () => {
            window.location.assign('http://foo.com');

            expect(window.location.href).toBe('http://foo.com/');

            (window.location.assign as jest.Mock<void, [string]>).mockClear();
        });

        it('location.replace replaces a location', () => {
            window.location.replace('http://bar.com');

            expect(window.location.href).toBe('http://bar.com/');

            (window.location.replace as jest.Mock<void, [string]>).mockClear();
        });

        it('location.reload is a spy', () => {
            window.location.reload();

            expect(window.location.reload).toHaveBeenCalledTimes(1);

            (window.location.reload as jest.Mock).mockClear();
        });
    });
});

src/testUtils.ts

interface MockedLocation extends Location {
    assign: jest.Mock<void, [string]>;
    reload: jest.Mock;
    replace: jest.Mock<void, [string]>;
}

interface MockedWindow extends Window {
    location: MockedLocation;
}

export function mockWindow(win: Window = window, href = win.location.href) {
    const locationMocks: Partial<MockedLocation> = {
        assign: jest.fn().mockImplementation(replaceLocation),
        reload: jest.fn(),
        replace: jest.fn().mockImplementation(replaceLocation),
    };

    return replaceLocation(href);

    function replaceLocation(url: string) {
        delete win.location;
        // tslint:disable-next-line:no-any
        win.location = Object.assign(new URL(url), locationMocks) as any;
        return win as MockedWindow;
    }
}

src/testUtils.test.ts

import { mockWindow } from './testUtils';

describe('test utils', () => {

    describe('mockWindow', () => {
        const saveLocation = window.location;

        afterAll(() => {
            delete window.location;
            window.location = saveLocation;
        });

        it('location.assign assigns a location', () => {
            const { assign } = mockWindow().location;
            assign('http://foo.com');

            expect(window.location.href).toBe('http://foo.com/');

            assign.mockClear();
        });

        it('location.replace replaces a location', () => {
            const { replace } = mockWindow().location;
            replace('http://bar.com');

            expect(window.location.href).toBe('http://bar.com/');

            replace.mockClear();
        });

        it('location.reload is a spy', () => {
            const { reload } = mockWindow().location;
            reload();

            expect(window.location.reload).toHaveBeenCalledTimes(1);

            reload.mockClear();
        });
    });
});

If you just want some other url than about:blank, you can use testURL config.

I use this…

window.history.pushState({}, '', `${url}/`);

Thanks @FelipeBohnertPaetzold. I was using location.host in my code, so found I needed a full location object, so the following worked better for me, rather than having to manually pass each location property:

delete global.window.location;
global.window.location = new URL("https://www.ediblecode.com/");

Note, this works in Node 6.13+ (see URL class docs) and I was using Jest 24.

Also note, this doesn’t work with relative URLs, see https://url.spec.whatwg.org/#example-url-parsing.

@msholty-fd you could try this approach:

const origLocation = document.location.href;
let location = origLocation;

beforeAll(() => {
  const parser = document.createElement('a');
  ['href', 'protocol', 'host', 'hostname', 'origin', 'port', 'pathname', 'search', 'hash'].forEach(prop => {
    Object.defineProperty(window.location, prop, {
      get: function() {
        parser.href = location;
        return parser[prop];
      }
    });
  });
});

afterEach(() => {
  location = origLocation;
});

test('location 1', () => {
  location = "https://www.google.com/";
  console.log(document.location.href); // https://www.google.com/
});

test('location 2', () => {
  console.log(document.location.href); // about:blank
});

this works for me on jest 24.9.0

 window.history.replaceState({}, 'Test Title', '/test?userName=James&userNumber=007');

I had to make code async in order to get this to work because I was running code inside a promise.

so is working now 😃

This worked for me.

delete global.window.location
global.window.location = { href: 'https://test-domain.com.br', ...anyOptions }

this creates a Location with all the original functionality, but it’s mockable:

beforeAll(() => {
  const location = window.location
  delete global.window.location
  global.window.location = Object.assign({}, location)
})

I found this post to be very helpful: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

"In your Jest configuration, make sure to set the following:

"testURL": "https://www.somthing.com/test.html"

Then in your beforeEach() section for your test, change the path as needed by using history.pushState().

window.history.pushState({}, 'Test Title', '/test.html?query=true');

Voila! Now you change out your path for any test, without having to override any jsdom configurations as others suggest in the thread mentioned above. Not sure on which thread I found this solution on, but kuddos to the dev that posted it!"


Excellent solution!!! Thank you very much! @Mike-Tran I wanted a short and not invasive solution like this!

@SimenB I’m not convinced that Jest should fix this. JSDOM should allow window.location.assign() to work as intended and reconfigure the output of window.location.href etc.

this creates a Location with all the original functionality, but it’s mockable:

beforeAll(() => {
  const location = window.location
  delete global.window.location
  global.window.location = Object.assign({}, location)
})

In TypeScript 4.1.3, this (as well as all other suggestions to delete anything on window) gives me:

error TS2790: The operand of a ‘delete’ operator must be optional.

class SSOtestComponent extends React.Component {

componentDidMount() {
	let isSuccess = this.props.location.pathname === '/sso/test/success' ? true : false

	window.opener.postMessage({ type: "sso_test", isSuccess,...this.props.location.query}, window.location.origin)
}

onSsoAuthenticate() {
	
}

componentWillUnmount() {
}

render() {
	return (<Loader />);
}

}

module.exports = SSOtestComponent;

i am write the unit test case using enjyme and jest how will write the condition window.location …pls give the answer

I have this at the top of my JSDOM setup file:

const { JSDOM } = require('jsdom');
const jsdom = new JSDOM('<!doctype html><html><body><div id="root"></div></body></html>', {
  url: "http://test.com"
});
const { window } = jsdom;

function copyProps(src, target) {
  const props = Object.getOwnPropertyNames(src)
    .filter(prop => typeof target[prop] === 'undefined')
    .map(prop => Object.getOwnPropertyDescriptor(src, prop));
  Object.defineProperties(target, props);
}

global.document = window.document;
global.window = window;
global.navigator = {
  userAgent: 'node.js',
};

global.HTMLElement = window.HTMLElement;

Another solution works for me currently without writing jsdom:

  1. Make sure you have set testURL in jest.config.js no matter the value:
// jest.config.js
'testURL': 'https://someurl.com'

In your test file:

window.history.pushState({}, 'Mocked page title', 'www.yoururl.com');

Learned from: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking. Thansk to Ryan!

Or better yet…

delete (global as any).window;
(global as any).window = new JSDOM(undefined, { url: 'http://google.com' }).window;

Fixed it by setting “testURL”: “http://localhost/” in Jest config (I’m using latest version). By default it’s “about:blank” and it was causing JSDOM error (you cannot change “about:blank” url to something else).

Resources: http://jestjs.io/docs/en/configuration#testurl-string https://github.com/jsdom/jsdom/issues/1372

thanks @simon360

that’s what I did 😉 I used jsdom.reconfigure to setup different initial urls in my tests, and whenever I need to change url in code (not test), I use window.location.assign and mocked it. which worked for me.

just for people who may/will run into the same issue, to set the url for your jsdom

// jest.config.js
 module.exorts={ 
  testURL: 'http://localhost:3000',
  // or : 
  testEnvironmentOptions: {
     url: "http://localhost:3000/",
    referrer: "https://example.com/",
  }
}

note that this will set url for all your tests; if you want a different url in some particular tests, use jsdom.reconfigure api; if you need to change url on the fly outside of unit test code (i.e. production code), you need to use window.location.assign and mock it.

not working for me:

TypeError: Assignment to read-only properties not allowed in strict mode

image

it("should save hash when history is not found", () => {
	const historyBkp = global.window.history;

	delete global.window.history;
	global.window.history = false;

	externalLoader.savePageURL(urlTraining);

	expect(window.location.hash).to.be.equal(`#page=${urlTraining}`);

	global.window.history = historyBkp;
	window.location.hash = "";
});

add this to the global file.

delete global.window.location; global.window.location = “”;

@Mike-Tran You rock! That totally worked, so simple. I didn’t even have to use the testURL setting.

I don’t know if this would help someone, but this is what I am currently doing.

const redirectTo = (url: string): void => {
  if (process.env.NODE_ENV === "test") {
    global.jsdom.reconfigure({ url: `${getBaseUrl()}${url}` });
  } else {
    window.location.replace(url);
  }
};

Write a redirect function and use that instead. So in testing env, it will rely on jsdom.reconfigure url to change url part.

I use it like this

export const clientFetchData = (
  history: Object,
  routes: Object,
  store: Object
) => {
  const callback = location =>
    match({ routes, location }, (error, redirectLocation, renderProps) => {
      if (error) {
        redirectTo("/500.html");
      } else if (redirectLocation) {
        redirectTo(redirectLocation.pathname + redirectLocation.search);
      } else if (renderProps) {
        if (!isEmpty(window.prerenderData)) {
          // Delete initial data so that subsequent data fetches can occur
          window.prerenderData = undefined;
        } else {
          // Fetch mandatory data dependencies for 2nd route change onwards
          trigger(
            FETCH_DATA_HOOK,
            renderProps.components,
            getDefaultParams(store, renderProps)
          );
        }

        trigger(
          UPDATE_HEADER_HOOK,
          renderProps.components,
          getDefaultParams(store, renderProps)
        );
      } else {
        redirectTo("/404.html");
      }
    });

  history.listen(callback);
  callback(history.getCurrentLocation());
};

After that, in your test, it can be sth like this

    describe("# match route", () => {
      it("should navigate to error page", () => {
        fetchData.clientFetchData(history, components, store);
        reactRouter.match.mock.calls[0][1](true);
        expect(window.location.href).toEqual(`${SERVER_URL}/500.html`);
      });

      it("should redirect to /hello-world.html page", () => {
        fetchData.clientFetchData(history, components, store);
        reactRouter.match.mock.calls[0][1](undefined, {
          pathname: "/hello-world.html",
          search: ""
        });
        expect(window.location.href).toEqual(`${SERVER_URL}/hello-world.html`);
      });
...

Probably part of my JSDOMTestWrapper can help somebody

    /** @type {Window} */
    this.testWindowObject = Object.create(window);
    const wnd = this.testWindowObject;
    this.testWindowObject.history = {
      state: null,
      prev: { /** @todo immutable stack with the go(step) method emulation */
        state: null,
        pathname: null,
        search: null,
      },
      go(step) {
        logger.special('history go called', step);
        logger.warn('history go has not supported yet');
      },
      back() {
        this.state = this.prev.state;
        wnd.location.pathname = this.prev.pathname;
        wnd.location.search = this.prev.search;
        const eventData = this.state ? { url: this.state.displayURL, newState: this.state, type: 'push' } : null;
        wnd.sm.eventsService.triggerEvent(ROUTER_EVENTS.ROUTE_PUSH, eventData);
        wnd.sm.urlService.simpleRouteTo(`${ wnd.location.pathname || '' }${ wnd.location.search || '' }`);
        logger.special('history back emulated');
      },
      pushState(state, title, url) {
        this.prev.state = Object.assign({}, this.state);
        this.prev.pathname = '' + wnd.location.pathname;
        this.prev.search = '' + wnd.location.search;
        this.state = state;
        if (title) wnd.document.title = title;
        const [p, s] = url.split('?');
        wnd.location.pathname = p;
        wnd.location.search = s ? `?${ s }` : '';
        logger.special('push state emulated', { state, title, url });
      },
      replaceState(state, title, url) {
        this.prev.state = Object.assign({}, this.state);
        this.prev.pathname = '' + wnd.location.pathname;
        this.prev.search = '' + wnd.location.search;
        this.state = state;
        if (title) wnd.document.title = title;
        const [p, s] = url.split('?');
        wnd.location.pathname = p;
        wnd.location.search = s ? `?${ s }` : '';
        logger.special('replace state emulated', { state, title, url });
        logger.special('test: urlService.getPathName()', wnd.sm.urlService.getPathName());
      },
    };
    this.testWindowObject.innerWidth = WND_WIDTH;
    this.testWindowObject.innerHeight = WND_HEIGHT;
    this.testWindowObject.fetch = fetchFn;
    this.testWindowObject.localStorage = lstMock;
    this.testWindowObject.scrollTo = (x, y) => {
      /** not implemented yet https://github.com/jsdom/jsdom/issues/1422 */
      if (typeof x !== 'number' && (x.left || x.top)) {
        y = x.top;
        x = x.left;
      }
      // logger.info(`window.scrollTo(${ x }, ${ y })`);
    };

    if (fetchFn === JSDOMTestWrapper.FETCH_FN.DEV_MOCK) {
      global.Request = RequestMock;
      this.testWindowObject.Request = RequestMock;
    }

    if (href) {
      this.testWindowObject.location = Object.assign({}, this.testWindowObject.location, urlapi.parse(href));
    }
    else {
      this.testWindowObject.location = Object.assign({}, this.testWindowObject.location);
    }

    (function(ELEMENT) {
      ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector;
      ELEMENT.closest = ELEMENT.closest || function closest(selector) {
          if (!this) return null;
          if (this.matches(selector)) return this;
          if (!this.parentElement) {return null}
          else return this.parentElement.closest(selector)
        };
      ELEMENT.getBoundingClientRect = ELEMENT.getBoundingClientRect || (() =>
        ({ bottom: WND_HEIGHT, height: WND_HEIGHT, left: 0, right: WND_WIDTH, top: 0, width: WND_WIDTH, x: 0, y: 0 }));
    }(Element.prototype));

    this.testWindowObject.getBoundingClientRect = () =>
      ({ bottom: WND_HEIGHT, height: WND_HEIGHT, left: 0, right: WND_WIDTH, top: 0, width: WND_WIDTH, x: 0, y: 0 });

    this.testWindowObject.__resizeListeners__ = [];
    this.testWindowObject.__resizeTriggers__ = {};
    this.testWindowObject._detectElementResize = {
      removeResizeListener: () => {},
    };

    this.testWindowObject.matchMedia = jest.fn().mockImplementation(query => {
      return {
        matches: false,
        media: query,
        onchange: null,
        addListener: jest.fn(),
        removeListener: jest.fn(),
      };
    });

    this.rftpr = () => {};
    this.mode = mode;
    this.renderFirstTimePromise = new Promise((resolve) => {
      this.rftpr = resolve;
    });

    this.marpr = () => {};
    this.mobileAppReadyPromise = new Promise((resolve) => {
      this.marpr = resolve;
    });

    if (mode === JSDOMTestWrapper.MODE.MOBILE_APP) {
      this.testWindowObject.navigator = Object.assign({}, this.testWindowObject.navigator, {
        language: storeKey,
        appVersion: '5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Mobile Safari/537.36',
        userAgent: 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Mobile Safari/537.36',
        vendor: 'Google Inc.',
      });

      global.intercom = {
        registerUnidentifiedUser: jest.fn(),
        registerForPush: jest.fn(),
      };
    }

    const XApp = mode ? MobileApp : App;
    const app = <XApp window={ this.testWindowObject } rftResolve={ this.rftpr } storeKey={ storeKey } apiHost={ apiVersion } forceMobileDetection={ mode } />;
    render(app, this.testWindowObject.document.body);

    if (mode === JSDOMTestWrapper.MODE.MOBILE_APP) {
      setTimeout(() => {
        this.testWindowObject.sm.deviceService.appRestorePathHasInit = this.marpr;
        this.testWindowObject.sm.deviceService.fireEvent(this.testWindowObject.document, 'deviceready');
      }, 200);
    }

@tzvipm @j-u-p-iter thanks for the 👍. I just released @jedmao/storage and @jedmao/location, which are both completely agnostic of Jest. You should be able to spyOn the appropriate methods without writing any additional tests, as the npm packages come completely tested.

If your goal is to mock the window object, here is my (not so elegant, but it works) solution:

Create an interface (not sure if interface is the right word, but I hope you get the point) class:

// window.js
export const { location } = window;

In your actual code, swap out window with the interface method’s, e.g. win

// myFile.js
import * as win from './window';

export function doSomethingThatRedirectsPage() {
  win.location.href = 'google.com';
}

Then, in your jest tests, you just mock them out so jsdom doesn’t complain. You can even assert them:

// myFile.test.js
import * as myFile from './myFile';
import * as win from './window';

it('should redirect', () => {
  win.location = { href: 'original-url' };

  expect(win.location.href).toBe('original-url');

  myFile.doSomethingThatRedirectsPage();

  expect(win.location.href).toBe('google.com');
});

I ended up doing this which worked:

global.window = new jsdom.JSDOM('', {
  url: 'http://www.test.com/test?foo=1&bar=2&fizz=3'
}).window;

@bochen2014 this issue has more information on how to use the newer version of jsdom: #5124

tl;dr: you can mock window.location.assign(), or you can use the jest-environment-jsdom-global, which will allow you to reconfigure jsdom in flight.

thanks @SimenB for your reply.

No I was talking about base url not url. I have code that will do window.location.href="/login" and when running jest, jsdom throw exception complaining /login is not a valid url

TypeError: Could not parse "/login" as a URL

I checked the source code of jsdom and realised this is because I don’t have a base url setup ( this is equivalent of typing “/login” in browser URL bar without a base address).

with jsdom, normally we can set up base url via

global.jsdom = new JSDOM('<html><head> <base href="base_url" /></head></html>')

but because jest set up jsdom , it is beyond our control.
— update: I suppose I can explicitly add jsdom as dependency and configure jsdom manually. but I’m not sure if it’s the recommended way to do it

I then found a solution which is to substitute window.location.href= with window.location.assign and mock assign function and it worked for me

@matt-dalton what’s your URL? do you have testURL set in your jest-config.json or does it initialize as about:blank?

I’m trying to migrate our tests from Mocha+Chai+Sinon.js to Jest and can’t figure out how to change location for a particular test. Jest 19.x uses JSDom 9.12 that does not allow to change location using Object.defineProperty trick. Also, I can’t use jsdom.changeURL() because of the reasons described in tmpvar/jsdom#1700. @cpojer what about implementing some proxy method to jsdom.changeURL() in Jest?