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 [email protected]/[email protected]
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.
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.
Cool, thanks I will try this out.
@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 [email protected]
?
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}`);
});
Thanks @th3fallen . That's cool!
Btw @cpojer I start at FB on 1May.... ;P
Nice!
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?
@okovpashko we're planning to expose jsdom to the environment: https://github.com/facebook/jest/issues/2460
Object.defineProperty
works for us at FB.
@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,
});
});
};
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
@matt-dalton try my suggestion in https://github.com/facebook/jest/issues/890#issuecomment-295939071 works well 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
?
@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).
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?
@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
});
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>)
Hmm, we might need to somehow allow people to call reconfigure
. https://github.com/tmpvar/jsdom/blob/05a6deb6b91b4e02c53ce240116146e59f7e14d7/README.md#reconfiguring-the-jsdom-with-reconfiguresettings
Opened a new issue related to this, since this one was closed: #5124
@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.
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
If you just want some other url than about:blank
, you can use testURL
config.
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
@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 @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.
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.
@petar-prog91 that was helpful. You have a typo though - it should be testURL
not TestURL
@BarthesSimpson thanks for notice, updated comment.
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/')
})
})
})
@UserNT confirm — it gives TypeError: Cannot redefine property: href
@annemarie35 not works — ReferenceError: JSDOM is not defined
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`);
});
...
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;
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;
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
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!"
@Mike-Tran You rock! That totally worked, so simple. I didn't even have to use the testURL setting.
@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 😅
@jcmcneal thanks that did it for me! (jest 23.0.0)
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');
});
@Mike-Tran , @jcmcneal thank! All works as aspected!
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
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
This worked for me.
delete global.window.location
global.window.location = { href: 'https://test-domain.com.br', ...anyOptions }
@FelipeBohnertPaetzold thanks
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.
This TypeScript is working for me on Jest 24.0.0 and Node 10.15.0:
import { mockWindow } from './testUtils';
mockWindow(window, 'http://localhost');
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();
});
});
});
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;
}
}
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();
});
});
});
@jedmao
Hey, man) Great util!
For me tests in src/setupTests.test.ts
a little bit redundant, cause you already completely tested mockWindow
util in src/testUtils.test.ts. So, in tests for src/setupTests.ts
it's enough to test, that you call mockWindow
with correct arguments.
Thank you)
@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.
In case you're getting this error when using Vue, just use this.$router.push({...})
instead of this.$router.go({...})
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"
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]));
Or better yet...
delete (global as any).window;
(global as any).window = new JSDOM(undefined, { url: 'http://google.com' }).window;
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
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 theObject.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
andlocation.hash
, and I want to test it withdefineProperty
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.
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!
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',
};
I use this....
window.history.pushState({}, '', `${url}/`);
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);
}
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
});
Another solution works for me currently without writing jsdom
:
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!
not working for me:
TypeError: Assignment to read-only properties not allowed in strict mode
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 = "";
});
not working for me:
TypeError: Assignment to read-only properties not allowed in strict mode
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 = "";
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.
this works for me on jest 24.9.0
window.history.replaceState({}, 'Test Title', '/test?userName=James&userNumber=007');
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 😃
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/"
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?
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)
})
Most helpful comment
You are right, this is indeed a jsdom issue. At Facebook, what we have done to work around this, is use this:
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.