Skip to content
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

window.location.href can't be changed in tests. #890

Closed
sterpe opened this issue Apr 13, 2016 · 74 comments
Closed

window.location.href can't be changed in tests. #890

sterpe opened this issue Apr 13, 2016 · 74 comments

Comments

@sterpe
Copy link

@sterpe sterpe commented Apr 13, 2016

Hi @cpojer,

This is actually more of a jsdom@8 issue...see jsdom/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.

@cpojer
Copy link
Collaborator

@cpojer cpojer commented Apr 14, 2016

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.

@cpojer cpojer closed this Apr 14, 2016
@sterpe
Copy link
Author

@sterpe sterpe commented Apr 14, 2016

Cool, thanks I will try this out.

@sterpe
Copy link
Author

@sterpe sterpe commented Sep 30, 2016

@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?

@th3fallen
Copy link

@th3fallen th3fallen commented Apr 20, 2017

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

@sterpe
Copy link
Author

@sterpe sterpe commented Apr 20, 2017

Thanks @th3fallen . That's cool!

@sterpe
Copy link
Author

@sterpe sterpe commented Apr 20, 2017

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

@cpojer
Copy link
Collaborator

@cpojer cpojer commented Apr 21, 2017

Nice!

@okovpashko
Copy link

@okovpashko okovpashko commented May 2, 2017

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 jsdom/jsdom#1700.
@cpojer what about implementing some proxy method to jsdom.changeURL() in Jest?

@thymikee
Copy link
Collaborator

@thymikee thymikee commented May 2, 2017

@okovpashko we're planning to expose jsdom to the environment: #2460

@cpojer
Copy link
Collaborator

@cpojer cpojer commented May 2, 2017

Object.defineProperty works for us at FB.

@okovpashko
Copy link

@okovpashko okovpashko commented May 2, 2017

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

@matt-dalton
Copy link

@matt-dalton matt-dalton commented May 13, 2017

Apologies for dragging out this thread further, but I have tried mocking out push function as suggested...

reactRouterReduxMock.push = (url) => {
   Object.defineProperty(window.location, 'href', {
	writable: true,
	value: url
       })
})

but I'm still getting a jsdom error that I can't seem to get round:

       TypeError: Cannot read property '_location' of null
       at Window.location (/Users/user/projects/app/client/node_modules/jsdom/lib/jsdom/browser/Window.js:148:79)
       at value (/Users/user/projects/app/client/test/integration-tests/initialSetup.js:122:32) //this is the defineProperty line above

I realise this is a jsdom error, but for those who have solved this is there any more setup context you could share that might let me get round this?

Thanks

@th3fallen
Copy link

@th3fallen th3fallen commented May 13, 2017

@matt-dalton try my suggestion in #890 (comment) works well for me

@ianlyons
Copy link
Contributor

@ianlyons ianlyons commented May 13, 2017

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

@matt-dalton
Copy link

@matt-dalton matt-dalton commented May 16, 2017

@ianlyons Yeah I set value of "https://test.com/" for this in the package.json, and none of the paths are showing up as blank.

@th3fallen If I understand you correctly, I don't think this works for my use case. Are you passing the url as a context value that causes assign to be triggered? I am trying to put together a rudimentary integration test, so I want to check how the router responds to the initial data load. I have mocked the API response, and then need the URL change to take place using the app logic (i.e. I do not want to trigger it externally myself).

@msholty-fd
Copy link

@msholty-fd msholty-fd commented Nov 10, 2017

Object.defineProperty seems to do the trick for testing functionality that relies on window.location.search, for instance. That being said it mutates window.location.search so other tests may be impacted. Is there a way to "undo" the changes you've made on window.location.search via Object.defineProperty, kinda like jest mock functions have the mockReset function?

@dmgawel
Copy link

@dmgawel dmgawel commented Nov 28, 2017

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

@modestfake
Copy link

@modestfake modestfake commented Dec 18, 2017

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

@SimenB
Copy link
Collaborator

@SimenB SimenB commented Dec 18, 2017

@simon360
Copy link

@simon360 simon360 commented Dec 18, 2017

Opened a new issue related to this, since this one was closed: #5124

@probablyup
Copy link
Contributor

@probablyup probablyup commented Dec 19, 2017

@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.

@bochen2014
Copy link
Contributor

@bochen2014 bochen2014 commented Jan 15, 2018

I got TypeError: Could not parse "/upgrades/userlogin?hardwareSku=sku1351000490stgvha" as a URL because jsdom has base url default to about:blank

I tried to assign a base url to jsdom, spent 4 hours on it without sucess (I know how to do it, just insert <base href='your_base_url' /> to the dom; but, the dom is created by jest , not by me, so i gave up.

the Object.defineProperty solution only works with old ver of jsdom (you get an 'cannot redefine property error with later version of jsdom);
if you are using jsdom ver > 10, as @th3fallen mentioned is the right solution.
use window.location.assign is the right way to go

@SimenB
Copy link
Collaborator

@SimenB SimenB commented Jan 15, 2018

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

@bochen2014
Copy link
Contributor

@bochen2014 bochen2014 commented Jan 15, 2018

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

@simon360
Copy link

@simon360 simon360 commented Jan 15, 2018

@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.

@bochen2014
Copy link
Contributor

@bochen2014 bochen2014 commented Jan 15, 2018

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.

@petar-prog91
Copy link

@petar-prog91 petar-prog91 commented Feb 16, 2018

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.

@dnutiu
Copy link

@dnutiu dnutiu commented Mar 19, 2019

In case you're getting this error when using Vue, just use this.$router.push({...}) instead of this.$router.go({...})

@brianli-whitbread
Copy link

@brianli-whitbread brianli-whitbread commented Mar 27, 2019

image

Place the code below on line 1:
delete global.window.location;
global.window.location = "";

A click event that is changing the window.location can now be captured.

"jest": "^23.6.0"
<node.version>v10.15.0</node.version>
<npm.version>6.5.0</npm.version>

@gaui
Copy link

@gaui gaui commented Apr 16, 2019

This works:

delete window.location;
window.location = Object.assign({}, window.location);
const url = Object.assign({}, new URL('http://google.com'));
Object.keys(url).forEach(prop => (window.location[prop] = url[prop]));

@gaui
Copy link

@gaui gaui commented Apr 17, 2019

Or better yet...

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

@GHLandy
Copy link

@GHLandy GHLandy commented Jun 12, 2019

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.

Yeah, I have some functions deal with location.search and location.hash, and I want to test it with defineProperty as you mentioned. It won't work!

When I turn off jest silent mode, I found this: Error: Not implemented: navigation (except hash changes)

console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
    Error: Not implemented: navigation (except hash changes)
        at module.exports (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
        at navigateFetch (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:74:3)
        at exports.navigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:52:3)
        at LocationImpl._locationObjectNavigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:29:5)
        at LocationImpl.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:213:10)
        at Location.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/generated/Location.js:93:25)
        at Object.assign (/home/ghlandy/projects/wdph-utils/src/__tests__/url.test.js:6:14)
        at Object.asyncJestLifecycle (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:53:37)
        at resolve (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
        at new Promise (<anonymous>) undefined

And now I have no idea about how to test my funtions.

Anyone have any way to change the test url

@GHLandy
Copy link

@GHLandy GHLandy commented Jun 12, 2019

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.

Yeah, I have some functions deal with location.search and location.hash, and I want to test it with defineProperty as you mentioned. It won't work!

When I turn off jest silent mode, I found this: Error: Not implemented: navigation (except hash changes)

console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
    Error: Not implemented: navigation (except hash changes)
        at module.exports (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
        at navigateFetch (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:74:3)
        at exports.navigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:52:3)
        at LocationImpl._locationObjectNavigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:29:5)
        at LocationImpl.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:213:10)
        at Location.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/generated/Location.js:93:25)
        at Object.assign (/home/ghlandy/projects/wdph-utils/src/__tests__/url.test.js:6:14)
        at Object.asyncJestLifecycle (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:53:37)
        at resolve (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
        at new Promise (<anonymous>) undefined

And now I have no idea about how to test my funtions.

Anyone have any way to change the test url

And In my situation, have a field testURL in jest.config.js file may works. But what if I want change testURL before each test.

@jherax
Copy link

@jherax jherax commented Jun 14, 2019

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!

@watadarkstar
Copy link
Contributor

@watadarkstar watadarkstar commented Jun 28, 2019

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',
    };

@gaui
Copy link

@gaui gaui commented Jun 29, 2019

I use this....

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

@studentIvan
Copy link

@studentIvan studentIvan commented Jul 9, 2019

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

@idleherb
Copy link

@idleherb idleherb commented Sep 27, 2019

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

@Lyukx
Copy link

@Lyukx Lyukx commented Nov 27, 2019

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!

@jeancatarina
Copy link

@jeancatarina jeancatarina commented Dec 4, 2019

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

@brianli-whitbread
Copy link

@brianli-whitbread brianli-whitbread commented Dec 4, 2019

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 = "";

@clearyandzap
Copy link

@clearyandzap clearyandzap commented Jan 22, 2020

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

I'm trying something similar, with location.assign, but seems this isn't working anymore.

@dudenamedjune
Copy link

@dudenamedjune dudenamedjune commented Jan 22, 2020

this works for me on jest 24.9.0

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

@clearyandzap
Copy link

@clearyandzap clearyandzap commented Jan 22, 2020

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 😃

@golubvladimir
Copy link

@golubvladimir golubvladimir commented Feb 20, 2020

How to test chenge location in vuex action ?

async setForm({ rootState, state, commit, dispatch }, formData) {
          ....
          switch (answ.result.type) {
            ....
            case 'redirect':
              console.log(answ.data.url);
              window.location = answ.data.url;
              console.log({ location: window.location.href });
              break;
            default:
              break;
it('setForm - success, redirect', async done => {
      expect(window.location.href).toBe('https://www.google.ru/');

I have error:

    expect(received).toBe(expected) // Object.is equality

    Expected: "https://www.google.ru/"
    Received: "http://localhost/"

@alanhg
Copy link

@alanhg alanhg commented May 23, 2020

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

what's the global?

where's the global definition?

@jakeonfire
Copy link

@jakeonfire jakeonfire commented Aug 28, 2020

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

@vincentntang
Copy link

@vincentntang vincentntang commented Jan 23, 2021

I had a really hard time getting this to work. I had a list of pathnames that needed to be checked against the current user's pathname

describe('<GearMenuButton />', () => {
  const defaultProps = {
    label: 'Nav Menu Button'
  }

 it.only('should have Account username underlined if the URL pathname is in the list of URLs', () => {
    const props = { locationUrls: ['/home.aspx', '/another.path'], ...defaultProps}

    global.window = Object.create(window)
    const pathname = '/home.aspx'
    Object.defineProperty(window, 'location', {
      value: {
        pathname: pathname
      },
      writable: true
    })

    render(<GearMenuButton {...props} />)

    const button = document.querySelector('.class-of-interest--active')
    expect(button).toBeInTheDocument()
  })
 }

@CharlesStover
Copy link
Contributor

@CharlesStover CharlesStover commented Feb 9, 2021

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.

@yogitabholwankar
Copy link

@yogitabholwankar yogitabholwankar commented Apr 29, 2021

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

I am facing the same issue and after trying the above solution ,getting 'cannot redefine window.href'

@github-actions
Copy link

@github-actions github-actions bot commented May 30, 2021

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 30, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet