[CDX-407] Update packages and jsdom version#218
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
Alexey-Pavlov
left a comment
There was a problem hiding this comment.
Looks good, thank you! 🙏
I didn't like that we needed to make this patch at all, but after investigating, I found out that this is the way 🥲 Well, maybe migration to something like happy-dom would help, but it's even more changes than we have right now
Left just one minor comment
| const LocationImpl = | ||
| require('jsdom/lib/jsdom/living/window/Location-impl.js').implementation; | ||
| LocationImpl.prototype._locationObjectNavigate = function (url) { | ||
| this._relevantDocument._URL = url; | ||
| }; | ||
|
|
There was a problem hiding this comment.
@esezen I think Copilot overcomplicated things there, but maybe it's still worth adding try/catch with something like
throw new Error(
'Failed to patch jsdom LocationImpl. The internal jsdom API may have changed. ' +
`Original error: ${error.message}`,
);
WDYT?
| spy.mockImplementation(() => {}); | ||
| }); | ||
|
|
||
| afterAll(() => { |
There was a problem hiding this comment.
Since window.location is set in beforeEach, maybe it makes sense to use afterEach here and in other places with the same setup? Some test files already use afterEach (e.g., useFilter, useBrowseResults), so it would also be more consistent
There was a problem hiding this comment.
Code Review
This PR upgrades jest-environment-jsdom from v29 to v30 (which pulls in jsdom@26) and migrates all affected tests away from Object.defineProperty(window, 'location', ...) by instead directly assigning to window.location, enabled via a custom jsdom environment that monkey-patches LocationImpl._locationObjectNavigate.
Inline comments: 6 discussions added
Overall Assessment:
| try { | ||
| const LocationImpl = | ||
| require('jsdom/lib/jsdom/living/window/Location-impl.js').implementation; | ||
| LocationImpl.prototype._locationObjectNavigate = function (url) { |
There was a problem hiding this comment.
Important Issue: This patch reaches deep into jsdom's private internals (jsdom/lib/jsdom/living/window/Location-impl.js) which are not part of the public API and are subject to change without notice in any jsdom patch or minor release. If jsdom renames or restructures _locationObjectNavigate, the error thrown in the catch block will surface and break the entire test suite with a cryptic failure.
Consider adding a comment that references the jsdom version range this patch was tested against, and ideally document the specific jsdom issue/PR that may eventually make this workaround unnecessary. This will help future maintainers know when the patch can be removed.
Also, since the function signature changed (navigation used to take a URL string in some jsdom versions), it is worth asserting the argument type in a note or adding a check:
LocationImpl.prototype._locationObjectNavigate = function (url) {
// Patched for jsdom >=26 where window.location.href = '...' triggers navigation.
// Tested with jsdom@26.x. Remove when jsdom natively supports href assignment in tests.
this._relevantDocument._URL = url;
};| ); | ||
| } | ||
|
|
||
| module.exports = JSDOMEnvironment; |
There was a problem hiding this comment.
Suggestion: The module exports JSDOMEnvironment but never actually creates a subclass — the patch is applied side-effectfully at require-time to the shared LocationImpl prototype. This means the custom environment file is really just a side-effect loader, not a proper Jest environment extension.
This is fragile because module caching in Node means the patch runs exactly once regardless of how many test suites load this file. While that is generally fine here, a more explicit approach would be to create an actual subclass and apply the patch inside setup() or the constructor, making the intent clearer:
class PatchedJSDOMEnvironment extends JSDOMEnvironment {
// patch applied at module-level above; subclass exists only for documentation clarity
}
module.exports = PatchedJSDOMEnvironment;This also makes it obvious to future developers that this is a Jest environment class, not just a configuration file.
| writable: true, | ||
| configurable: true, | ||
| }); | ||
| window.location = mockUrl; |
There was a problem hiding this comment.
Important Issue: Assigning window.location = mockUrl (a plain string) replaces the entire window.location object with a string. Subsequent reads of window.location.pathname, window.location.href, window.location.origin etc. in production code (e.g., shopifyDefaults.ts:42 uses window.location.origin) will operate on a String primitive's properties — all of which are undefined. This makes the test unreliable.
The previous approach used Object.defineProperty to replace the location with a URL-like object; the new approach should instead assign only window.location.href (enabled by the jsdom patch) while leaving the Location object intact:
beforeEach(() => {
window.location.href = mockUrl;
});
afterEach(() => {
window.location.href = originalWindowLocation.href;
});| Object.defineProperty(window, 'location', { | ||
| value: new URL('https://example.com'), | ||
| }); | ||
| window.location = 'https://example.com'; |
There was a problem hiding this comment.
Important Issue: Same as above — window.location is being assigned a plain string throughout this and other test files (useCioPlp.test.tsx, useSearchResults.test.js, useBrowseResults.test.js, useFilter.test.js, usePagination.test.js, CioPlp.test.jsx). This replaces the real Location object with a String primitive.
In particular, useRequestConfigs.test.tsx:151 then calls new URL(window.location.href) — if window.location is a string, window.location.href is undefined, causing new URL(undefined) to throw a TypeError. This entire pattern should use window.location.href = '...' instead of window.location = '...' for consistency and correctness with the patched jsdom environment.
| "husky": "^8.0.0", | ||
| "jest": "^28.1.3", | ||
| "jest-environment-jsdom": "^29.7.0", | ||
| "jest-environment-jsdom": "^30.3.0", |
There was a problem hiding this comment.
Suggestion: The jest version in devDependencies is still pinned to ^28.1.3 while jest-environment-jsdom is being upgraded to ^30.3.0. These are from different major versions of Jest. While it appears to work (because jest-environment-jsdom 30 happens to be compatible with the peer dependency range of Jest 28's @jest/environment), this version mismatch is surprising and potentially fragile across npm installs or version resolutions.
Consider aligning the jest and jest-environment-jsdom major versions (i.e., upgrading jest to ^30.x as well, or pinning jest-environment-jsdom to a ^29.x version that works with jsdom 26), and documenting the intentional mismatch if it is kept as-is.
Ran
npm audit fixto get rid of security vulnerabilities. I had to upgrade jsdom to version 30 which is a breaking change with major differences.jest-environment-jsdomfrom^29.7.0to^30.3.0(pulls injsdom@26)Object.defineProperty(window, 'location', ...)which is no longer allowed in jsdom 26Why do we need a custom jsdom environment?
jsdom 26 does not support
window.location.href = '...'— it throws"Not implemented: navigation (except hash changes)". This is a long-standing jsdom limitation, but in previous versions our tests worked around it by replacingwindow.locationentirely with a plainURLobject (where.hrefis just a writable property, not a navigation trigger).With jsdom 26 locking down
window.location, we can no longer swap it out. Our source code (urlHelpers.ts,shopifyDefaults.ts) intentionally assigns towindow.location.hrefto navigate, and multiple tests exercise that code path. Without the patch, those tests would fail with the "Not implemented: navigation" error.Let me know if there is an easier approach to this 🤔