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

Stubbing/patching window.location.href in JSDom 8? #1388

Closed
dylanpyle opened this issue Feb 17, 2016 · 16 comments
Closed

Stubbing/patching window.location.href in JSDom 8? #1388

dylanpyle opened this issue Feb 17, 2016 · 16 comments

Comments

@dylanpyle
Copy link

We've got some code in use in tests that worked in jsdom 7 but appears not to work in jsdom 8.

Our JSDom setup:

  const doc = jsdom.jsdom('<html><body></body></html>', {
    referrer: 'https://example.com/baz',
    url: 'https://example.com/foo'
  });

  global.document = doc;
  global.window = doc.defaultView;

And the test itself:

      window.location.href = 'https://newurl.com/foo';
      // some assertions that depend on window.location

Adding some logging immediately after setting window.location.href shows that the value wasn't actually updated or changed, and other means overriding (e.g. stubbing with sinon) don't seem to work either since it's implemented via getters/setters rather than an actual property. What's the best way to override this value?

It looks like setting is supposed to do something based on https://github.com/tmpvar/jsdom/blob/master/lib/jsdom/living/window/Location-impl.js#L46 but I can't figure out what the final effect of that is supposed to be.

@domenic
Copy link
Member

domenic commented Feb 17, 2016

So, the issue is that you can't set location in browsers, without navigating. And jsdom is trying to behave like a browser. (Although it doesn't do the navigation part yet.)

The way we generally work around this is adding methods like jsdom.reconfigureLocation(window, { href: "..." }) which allow you to modify the DOM objects from the outside, using "superpower" APIs that are not part of the normal DOM. Would that suffice for your use case?

@domenic
Copy link
Member

domenic commented Feb 17, 2016

Alternately, it might be a better solution to add something like jsdom.changeURL(window, ...) and not mess with Location directly. Then you still get all the usual behaviors from window.location, but everything in the DOM (including e.g. document.URL, not just location) now uses a new page URL.

@dylanpyle
Copy link
Author

Ah yeah, makes sense. An API for either of those sounds great — didn't realize there was already a reconfigureWindow but adding it there seems reasonable? I'm basically looking for something with the semantics of "override the stuff I set up in config initially, without recreating the whole DOM".

@sterpe
Copy link
Contributor

sterpe commented Apr 13, 2016

I am also running into with test written for [email protected], which depends on jsdom@7 upgrading to jest@latest which bumps to jsdom 8. Testing some authentication/oauth code that sets a new window location (e.g., to an oauth server).

@domenic
Copy link
Member

domenic commented Apr 13, 2016

Can you explain more how that works, and how you would test that code in browsers?

@sterpe
Copy link
Contributor

sterpe commented Apr 13, 2016

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

This test would be kind of difficult to do without something like Selenium. But that's kind of what makes jsdom powerful as an environment. I don't know though...maybe this isn't a good test.

@sterpe
Copy link
Contributor

sterpe commented Apr 21, 2016

Thoughts?

@mummybot
Copy link

Hi @domenic ,

Your example sounds promising however I am a failure of imagination implementing it. At present I have the following:

/*global global*/
import jsdom from 'jsdom';

const doc = jsdom.jsdom('<!doctype html><html><body></body></html>', {
  url: 'http://test/'
});
const win = doc.defaultView;

global.document = doc;
global.window = win;

Object.keys(window).forEach((key) => {
  if (!(key in global)) {
    global[key] = window[key];
  }
});

// All individually output http://test/
jsdom.reconfigureWindow(window, {url: 'http://localhost'});
jsdom.reconfigureWindow(window, {location: 'http://localhost'});
jsdom.reconfigureWindow(global.window, {url: 'http://localhost'});
window.location.href = 'http://localhost';
location.href = 'http://localhost';

console.log('window.location.href', window.location.href);

Help?!?

@domenic
Copy link
Member

domenic commented May 16, 2016

I'd strongly suggest not copying properties onto the global. Also, I can't run that code in my Node.js, so I can't really help with it.

@mummybot
Copy link

Thanks for the follow up.

I followed Tero's tutorial http://teropa.info/blog/2015/09/10/full-stack-redux-tutorial.html and he states that for running the test suite as standalone that React needs access to many of these variables (such as navigator) on the global state. I guess there will (should) be a better way of testing React components with JSDom that doesn't litter the global scope, I'll look into that.

Even if you cannot run it, if you expect that jsdom.reconfigureWindow(window, {url: 'http://localhost'}); would update the URL then I can play around with the set up more. If I am in totally the wrong direction then at least I know.

@Sebmaster
Copy link
Member

Sebmaster commented May 16, 2016

if you expect that jsdom.reconfigureWindow(window, {url: 'http://localhost'}); would update the URL then I can play around with the set up more

It does not, that was never implemented after all. reconfigureWindow can only overwrite window.top.

@domenic
Copy link
Member

domenic commented May 16, 2016

Right, that was just an idea. I guess I can try to work on jsdom.changeURL(window, "url") this upcoming weekend maybe. PR welcome, of course.

@danawoodman
Copy link

Any updates on this? This would be helpful to mock out any code that references window.location

@domenic
Copy link
Member

domenic commented Jun 23, 2016

Yes, this was implemented recently: https://github.com/tmpvar/jsdom#changing-the-url-of-an-existing-jsdom-window-instance

@domenic domenic closed this as completed Jun 23, 2016
@danawoodman
Copy link

Just discovered that, awesome! 👍

@mhipszki
Copy link

I've just noticed that content on the link @domenic posted above possibly lives on this one now

https://github.com/tmpvar/jsdom#reconfiguring-the-jsdom-with-reconfiguresettings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants