Assigning window.location after 5.8.2 upgrade fails
🔎 Search Terms
Unable to assign window.location typescript 5.8.2 error TS2322: Type 'Location' is not assignable to type 'string & Location'. Type 'Location' is not assignable to type 'string'.
🕗 Version & Regression Information
- This changed between versions 5.7.3 and 5.8.2
⏯ Playground Link
https://www.typescriptlang.org/play/?ts=5.8.2#code/O4SwdgJg9sB0A2UDGBDALiKYAEBebokMCy6mYA3AFBA
💻 Code
window.location = window.location;
🙁 Actual behavior
Failing with error:
error TS2322: Type 'Location' is not assignable to type 'string & Location'.
Type 'Location' is not assignable to type 'string'.
🙂 Expected behavior
Expect it to either be a documented breaking change, OR it should continue to work as it did in 5.7.3:
https://www.typescriptlang.org/play/?ts=5.7.3#code/O4SwdgJg9sB0A2UDGBDALiKYAEBebokMCy6mYA3AFBA
Additional information about the issue
No response
Related: #48949
Had the same issue when upgrading to the new version.
A similar workaround to this one worked for me:
const win: typeof globalThis = window;
win.location = window.location; // Pass
I see the PR https://github.com/microsoft/TypeScript/pull/60987 added this part:
Document.location and Window.location now has stricter get/set types:
get location(): Locationis unchangedset location(href: string)previously allowed Location (probably because of Typescript's accessor assignability rules)
Seems to be the cause of the change, as set location expects a string, but globalThis['location'] expects a Location
A pain for old style iframe prevention - ts-ignore helps
if (window.top !== window.self) {
if (window.top) {
// @ts-ignore
window.top.location = window.self.location;
}
}
How can anything satisfy the type string & Location? Was it supposed to be string | Location instead?
Independent of whether or not this is a bug/regression: Shouldn't window.location.href = otherWindow.location.href do pretty much what you'd expect in most if not all of the cases?
This is hitting us in our test files where we will often override window.location to mock it in order to test client-side JavaScript behaviour like this:
window.location = { ...window.location, pathname: '/modals/main/change-to-delivery' };
@jakebailey @RyanCavanaugh sorry for tagging but this is a blocker for some codebases for upgrading to more recent TS versions, unless you add hundreds of ts-ignores. Is a fix planned, should someone make a PR or any other plans (it's been a month and no updates)?
https://developer.mozilla.org/en-US/docs/Web/API/Window/location says that reading the variable gives a Location object, but that you can write a string to the variable.
I can imagine that the type being string & Location is itself a problem (probably some known issue with getters/setters; you'll note that the DOM types do not actually have this type anywhere, so it must be created by the checker), it seems to me that window.location = window.location is not actually legal, no?
Is this only working at runtime due to coercion to string since Location.toString() gives a string? I can't see how window.location = { ...anything } should work, as that wouldn't even have a toString.
The change came from: https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1838
@saschanaz
Is this only working at runtime due to coercion to string since
Location.toString()gives a string? I can't see howwindow.location = { ...anything }should work, as that wouldn't even have atoString.
Yes, as far as I can tell from MDN and the spec. Writes to window.location are effectively forwarded to window.location.href, which coerces to a string.
window.location = window.location is effectively window.location.href = window.location.toString(). Which works on browsers, but only with two magics under the hood:
location.href: Thewindow.locationsetter is overriden so that the value goes towindow.location.hrefinstead.location.toString(): Given thatlocation.hrefexpects string, any passed value will be implicitly stringified.
The first magic is what's supported by https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1838. For the second magic, I think in general implicit stringification is not supported, if you want to pass a string then you should explicitly do so.
And window.location = window.location is just weird, maybe do location.reload() instead? If you feel strongly about the current code, you can do window.location.href = window.location.href which includes no magic.
But the other part of me says maybe we should be less strict and allow passing Location where URLs are expected, as done in https://github.com/microsoft/TypeScript-DOM-lib-generator/commit/2bffd44cf19e5772489f6300a253e9d0dd4d8685.
I note that this code gives a TypeScript error, which doesn't seem like it can be intentional?
window.location = window.location;
If you're here because you're stubbing some window location properties - you can bypass the assignment type check:
For example with a jest stub:
const oldWindowLocation = window.location;
Object.defineProperty(window, 'location', {
configurable: true,
value: Object.defineProperties(
{},
{
...Object.getOwnPropertyDescriptors(oldWindowLocation),
assign: {
configurable: true,
value: jest.fn(),
},
),
});
// do stuff... and then
Object.defineProperty(window, 'location', {
configurable: true,
value: oldWindowLocation,
});
@tomh-t where should that be placed? @benmvp posted a similar fix a few years ago, but I can't either yours or his working. Typescript vis VS Code shows a missing property assignment near the end of the block. I tried at the top of the describe block, as well as inside the beforeEach block.
@commadelimited - looks like a small syntax error you need to fix. I wouldn't trust a copy & paste in a github comment character for character - I have to edit it in here to avoid sharing irrelevant code and writing code in a comment is susceptible to syntax errors (missing curly)
But anyway - we're using this in a helper
export const withLocationMock = () => {
const oldWindowLocation = window.location;
beforeEach(() => {
Object.defineProperty(window, 'location', {
configurable: true,
value: Object.defineProperties(
{},
{
...Object.getOwnPropertyDescriptors(oldWindowLocation),
assign: {
configurable: true,
value: jest.fn(),
},
reload: {
configurable: true,
value: jest.fn(),
},
host: {
configurable: true,
writable: true,
value: 'initial-host-for-tests',
},
protocol: {
configurable: true,
writable: true,
value: 'http:',
},
},
),
});
});
afterEach(() => {
Object.defineProperty(window, 'location', {
configurable: true,
value: oldWindowLocation,
});
});
};
And then in any test
describe('some test', () => {
withLocationMock();
Thanks my friend, I'll give that a shot...appreciated.