-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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
Unable to change window.location using Object.defineProperty #5124
Comments
I have the similar issue. You can create your own JSDOMEnvironment and expose jsdom object to the global like this.
And then you can call jsdom.reconfigure in your test case as you like |
That's a good workaround, thanks for sharing! You should return |
Perfect, @oliverzy - I'll give that a try. Thanks! Is there an appropriate place to document this? It seems to be a question that comes up reasonably often; hopefully, future issues could be cut down if this were integrated in the docs? |
jestjs/jest#5124 ``` Object.defineProperty(location, "hostname", { value: "example.com", writable: true }); ``` ``` TypeError: Cannot redefine property: hostname at Function.defineProperty (<anonymous>) ```
This solution didn't quite work. Inside our test files, it seems like In other words, inside a test suite, As a result, having this: describe("test suite", () => {
it("should not fail", () => {
global.jsdom.reconfigure({
url: "https://www.example.com/"
});
});
}); fails because I got around it by doing this, but I'm not super fussed about it. const JSDOMEnvironment = require("jest-environment-jsdom");
module.exports = class JSDOMEnvironmentGlobal extends JSDOMEnvironment {
constructor(config) {
super(config);
this.dom.window.jsdom = this.dom;
}
}; With this environment, To me, it feels like setting |
you need to write |
@oliverzy Like this? describe("test suite", () => {
it("should not fail", () => {
jsdom.reconfigure({
url: "https://www.example.com/"
});
});
}); That throws |
@simon360 Please configure the |
@danielbayerlein my Jest config has this:
where |
BTW, does anyone know why the original solution doesn't work in the new version? Object.defineProperty(location, "hostname", {
value: "example.com",
writable: true
}); |
@modestfake we have upgraded from JSDOM@9 to JSDOM@11, my guess is that they changed how the variable is defined |
@SimenB Got it. Just found a description of jsdom
|
I added a new repository to demonstrate this behaviour. Is anyone able to reproduce it by cloning locally? https://github.com/simon360/test-environment-for-jest |
@simon360 reproduced |
@simon360 I've found. You've missed const JSDOMEnvironment = require("jest-environment-jsdom");
module.exports = class JSDOMEnvironmentGlobal extends JSDOMEnvironment {
constructor(config) {
super(config);
this.global.jsdom = this.dom;
}
teardown() {
this.global.jsdom = null;
return super.teardown();
}
}; |
What about |
@andrewBalekha What about this? jsdom.reconfigure({
url: 'https://www.example.com/endpoint?queryparam1=15&queryparam2=test'
}); |
Thanks @modestfake - sorry for the dumb mistake! Ok, I see it now - However, I do hope there's a cleaner way to do this in Jest in the future. This isn't a low friction way to change Could there be a new docblock, like there is for /**
* @jest-url https://www.example.com/
*/ Or, maybe JSDom can be exposed on a special part of the jest.environment.jsdom.reconfigure({
url: "https://www.example.com/"
}); (which would have the added benefit of being able to change |
We have merged #5003 now. being able to add it as a docblock might make sense, not sure. @cpojer? We could deprecate
I think that makes sense in any case, as it does more than just let you set url - it exposes the full JSDOM to the environment |
@andrewBalekha |
Object.defineProperty(window.location, 'href', { |
I've published a new package on npm called |
Does anyone have a workaround for For example: Object.defineProperty(window.location, 'href', { writable: true })
...
Object.defineProperty(window.location, 'hash', { writable: true })
...
Object.defineProperty(window.location, 'search', { writable: true }) |
@danielbayerlein read this thread. You need to create custom environment. Previous message contains url with example |
@modestfake I've already read this thread and #5124 (comment) works fine. But I've another use case. With Jest 21.x.x I have set |
@danielbayerlein what the use case to make it writable but not override it actually? Maybe understanding this can help me to come up with workaround |
I've a function that changes the URL. routing.js ...
export function redirectToErrorPage () {
window.location.href = '/error.html'
}
... routing.test.js test('redirect to the error page', () => {
...
expect(window.location.href).toBe('/error.html')
...
}) With Jest 21.x.x I have set |
Solved my issue using the solution given by @kdelmonte, I've had to mock the window.history.pushState({}, null, '?skuId=1234') |
your answer is the only one that works for my situation, thanks! |
For a later test, we'll want to be able to set up the test environment to appear as though it's a browser at a specified URL. Jest currently uses `jsdom` as its mock-browser environment, but doesn't (presently) expose the `jsdom` facilities needed to set the mock `location`. (See jestjs/jest#5124.) Fortunately, Jest does allow alternate mock environments. We install one which is identical to Jest's default environment, except that it does expose that capability.
This is no longer workin 😢 |
Not working for me either |
I solved this doing:
|
I'm using the following mock as a utility for working with export class MockLocation extends URL implements Location {
ancestorOrigins: any = []
toString = jest.fn().mockImplementation(() => this.toString())
assign = jest.fn(href => this.href = href)
replace = jest.fn(href => this.href = href)
reload = jest.fn()
constructor(
url: string = 'http://mock.localhost',
) {
super(url)
}
onWindow(window: Window) {
Object.defineProperty(window, 'location', {
writable: true,
value: this
});
return this
}
} Then in my tests let location: MockLocation
beforeEach(() => {
location = new MockLocation(MOCK_PARTNER_URL).onWindow(window)
}) |
I found myself stubbing tricky objects like these all the time, and created a flexible helper function: export const safelyStubAndThenCleanup = (target, method, value) => {
const original = target[method]
beforeEach(() => {
Object.defineProperty(target, method, { configurable: true, value })
})
afterEach(() => {
Object.defineProperty(target, method, { configurable: true, value: original })
})
} And then usage: describe('when on /pages', () => {
safelyStubAndThenCleanup(window, 'location', { pathname: '/pages' })
it('should do something neat', () => { /* ... */ })
}) And you can stub whatever you want: The key is you can't mess with |
As I can see in my debugging session, let originalLocationDescriptor;
beforeAll(() => {
originalLocationDescriptor = Object.getOwnPropertyDescriptor(global, 'location');
delete global.location;
global.location = {};
});
afterAll(() => {
Object.defineProperty(global, 'location', originalLocationDescriptor);
}): Though it's hard to imagine why would I want to use the original |
This has been working for me, using jest 26.5 function stubLocation(location) {
beforeEach(() => {
jest.spyOn(window, "location", "get").mockReturnValue({
...window.location,
...location,
});
});
}
stubLocation({ pathname: "/facebook/jest/issues/5124" });
test("mocks location prop", () => {
expect(window.location.pathname).toEqual("/facebook/jest/issues/5124");
}); |
This worked exactly well for the problem I was having |
Maybe it's help anyone.
You just need write in setupTests.js
|
Most of the workarounds in here haven't been working for me. In particular, the In the end, I decided that we didn't need to change the origin for our purposes and in that case, we might as well just use the built in browser mechanism for changing the URL using
|
This still works |
…..) with `window.location` method used in List.test.js [+] Reasons: - Consistency with List.test.js - Altering `window` with custom `location` is the method used by Facebook, the owners of Jest: jestjs/jest#890 (note that the original method stopped working -- see jestjs/jest#5124) - `window.history.pushState` simulates something slightly different than what we are testing for, and in a real browser changes state and also triggers events. While here in these tests we are not dealing with a browser `window` object but the one provided by jsdom, it would still seem more brittle, because whether or not the jsdom project implements history to work the way it does in actual browsers, it might have an API in flux, and we don't want to have to keep track of the fluctuations and potential side effects. The `delete window.location` followed by replacement with our own `location` is not subject to changes to the jsdom API (in theory, although see the 2nd link in the previous point).
Had the same issue, but this piece of code worked with me
|
I found the best way to do it for my function which was calling
|
I've fixed this issue by re-implementing window.location & window.history: https://gist.github.com/tkrotoff/52f4a29e919445d6e97f9a9e44ada449 It's heavily inspired by #5124 (comment) (thx @sanek306) and firefox-devtools window-navigation.js (thx @gregtatum & @julienw) It comes with unit tests and it works well with our source base using Vitest. You can then use window.location in your tests like you would expect: it('should ...', () => {
window.location.pathname = '/product/308072';
render(<MyComponent />);
const link = screen.getByRole<HTMLAnchorElement>('link', { name: 'Show more' });
expect(link.href).toBe('http://localhost:3000/product/308072/more');
});
it('should ...', () => {
const assignSpy = vi.spyOn(window.location, 'assign');
render(<MyComponent />);
const input = screen.getByRole<HTMLInputElement>('searchbox');
fireEvent.change(input, { target: { value: 'My Search Query' } });
fireEvent.submit(input);
expect(assignSpy).toHaveBeenCalledTimes(1);
expect(assignSpy).toHaveBeenNthCalledWith(1, '/search?query=My+Search+Query');
assignSpy.mockRestore();
}); |
And it's still working in jest "29.5.0" ❤️ |
Do you want to request a feature or report a bug? Report a bug
What is the current behavior?
Calling the following from inside a test suite:
throws the following error:
What is the expected behavior?
The code should not throw an exception, and
window.location.hostname === "example.com"
should evaluate true.From the looks of it,
jsdom
now sets window.location to be unforgeable. The only way to change the values withinwindow.location
is to usereconfigure
, but (per #2460) Jest doesn't exposejsdom
for tests to play around with.Please provide your exact Jest configuration and mention your Jest, node,
yarn/npm version and operating system.
Jest version: 22.0.1
Node version: 8.6.0
Yarn version: 1.2.0
OS: macOS High Sierra 10.13.2
The text was updated successfully, but these errors were encountered: