-
Notifications
You must be signed in to change notification settings - Fork 2
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
Preserve query parameters when rebasing location path #67
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the PR!
These changes are quite difficult to follow, and the issue at hand is lost on me. Can you please update the PR description to match this template please, and provide as much detail as possible (without giving away privileged information):
https://github.com/zambezi/address/blob/master/.github/PULL_REQUEST_TEMPLATE.md
Finally, I'm not convinced these changes require changing the location module from a singleton to an instantiated module with local state as opposed to global state. The reasons for this is that location is inherently global (i.e. window) state, and while a locally instantiated module may make testing easier in the short run, it also introduces significant risk of breaking this library in unexpected ways that may not be covered by the current test suite.
I'd like to see this revert to a more limited set of changes that cover the issue at hand, and that issue clearly described so I can better appreciate it. There's a lot of context lost on me I'm afraid. If you could improve on this I'm sure we can make quickly make some headway. Many thanks!
@@ -6,7 +6,7 @@ define(function(require) { | |||
, httpStatusCode: require('./http-status-code') | |||
, interpolate: require('./interpolate') | |||
, into: require('./into') | |||
, location: require('./location') | |||
, location: require('./location')() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The location module is effectively a singleton prior to these changes. I can understand how this makes testing more difficult, however it seems to me that by changing this to be an instantiated module with local state, this also breaks the API. Anything now depending on address.location
can no longer rely on the state of this module to be the same as what's used by address
internally. At least, that's how I read this – it's a bit difficult to follow the changes.
Thanks a lot for your comment @mstade I tried my best to keep the changes to a minimum to achieve the desired result. The diff may not look clear at first sight as Github doesn't ignore whitespace change. To make it easier I'm pasting the whitespac-ignored diff of diff --git a/location.js b/location.js
index 2bc4e9d..4a1c317 100644
--- a/location.js
+++ b/location.js
@@ -1,4 +1,15 @@
define(function(require) {
+ var instance
+
+ return function(force) {
+ if (!instance || force) {
+ instance = getInstance()
+ }
+
+ return instance
+ }
+
+ function getInstance() {
var findClosest = require('./find-closest')
, rebind = require('./rebind')
, dispatcher = require('d3-dispatch').dispatch('statechange')
@@ -12,7 +23,7 @@ define(function(require) {
if (isHashPath(location.hash)) {
// Redirect current hash fragment location to "real" path
- history.replaceState(null, null, rebase(fullPath(location)))
+ history.replaceState(null, null, rebase(fullPath(location)) + location.search)
}
var api =
@@ -51,7 +62,7 @@ define(function(require) {
if (path === getState()) {
return false
} else {
- method({ base: base, path: path }, null, rebase(path))
+ method({ base: base, path: path }, null, rebase(path) + location.search)
return path
}
}
@@ -194,4 +205,5 @@ define(function(require) {
function trimSlashes(path) {
return (path || '').replace(/^\/+|\/+$/g, '')
}
+ }
}) I've also added a templated PR description as you requested. In the latest commits I made sure the Just to make it clear, I'm not sure I can keep the changes volume to less that they are now to achieve the same result and provide the test coverage at the same time. The only way I could do it is refactoring the module (and the dependant code) on a broader scale, but I guess we want to avoid that as much as possible. Your concern about braking the I hope this makes it easier to review :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a lot better, thank you so much @braxnu! Let me address (no pun intended) some of your comments:
Just to make it clear, window.location object is always the global one regardless of the changes. I'm not entirely sure what you mean by local/global state. What changed is the scope depth at which the module code is executed, but that also doesn't matter as the returned API is exactly the same as before.
Yes, window.location
is always global and this was my point – location is also global due to the fact that it's state is effectively stored by window.global
, there's no getting away from this without massive refactoring just as you say. This is why I was a bit worried about going away from its singleton nature, since you can (and indeed, some consumers have in the past) get to the location
module outside of the address bundle. You've solved this neatly by making constructor function more or less memoized, this should do fine.
This is a good PR, but I'm not entirely sure it should work the way you describe, specifically around query parameters preceding a fragment based path:
// Current behaviour: URL are replaced as follows:
https://example.com/app/Monitor?debug=true => https://example.com/app/Monitor
https://example.com/app?debug=true#/Monitor => https://example.com/app/Monitor
// Expected behaviour is:
https://example.com/app/Monitor?debug=true => https://example.com/app/Monitor?debug=true
https://example.com/app?debug=true#/Monitor => https://example.com/app/Monitor?debug=true
I agree with the first case, the debug
parameter should not be lost. But in the second case, where there is a fragment based path, I'm not convinced the debug
parameter should be kept. I expect the fragment based path to trump the real path entirely, query parameters and all. Consider the following case:
https://example.com/app?debug=true#/Monitor?wibble=bob
What should the end result be? In this case, merging might seem like a decent solution, so something like this:
https://example.com/app?debug=true#/Monitor?wibble=bob => https://example.com/app/Monitor?debug=true&wibble=bob
However, what about this case:
https://example.com/app?debug=true#/Monitor?debug=false
Should the value of debug
be true
, false,
or maybe even true,false
? I don't know. Intuitively I expect the hash fragment path to just win, it's the simplest and least astonishing mechanic to me, but maybe I'm being too simplistic?
test/location.test.js
Outdated
|
||
window.history.replaceState( | ||
null, null, | ||
loc.origin + loc.pathname + '?' + originalQuery.toString() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of all this, can we just have `/?foo=bar&fizz=buzz' or something? It's a bit hard to follow this test.
test/location.test.js
Outdated
var newQuery = new URLSearchParams(window.location.search.substring(1)) | ||
|
||
expect(newQuery.get('foo')).to.equal('bar') | ||
expect(newQuery.get('fizz')).to.equal('buzz') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, couldn't we just expect(location.getState()).to.equal('/base/foo?foo=bar&fizz=buzz')
? By the way, should location.pushState
really preserve the query parameters like this? I'd expect any query parameters to be lost once a new state is pushed, unless those are indeed specified in the new state.
null, null, | ||
loc.origin + loc.pathname + | ||
'?' + originalQuery.toString() + | ||
'#/app/workspaces' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer it if we could use explicit constants wherever possible to make these tests more readable. So instead of all this string building, just /?foo=bar&fizz=buzz#/app/workspaces
or whatever is relevant test data.
var newQuery = new URLSearchParams(loc.search.substring(1)) | ||
|
||
expect(newQuery.get('foo')).to.equal('bar') | ||
expect(newQuery.get('fizz')).to.equal('buzz') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned in my general review, I'm not so sure this case actually makes sense. Any query parameters present in the fragment based path should remain, for sure, but anything in the real path should probably be discarded. Let's discuss that in the more general thread.
also: - update dependencies - fix test setup - fix failing tests
, dispatcher = require('d3-dispatch').dispatch('statechange') | ||
, history = window.history | ||
, location = window.location | ||
, on = require('./on') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we move the require
s outside of the function call? I'm not comfortable to have constants re-imported here. Of course we should keep the no-require stuff inside.
@@ -223,7 +223,7 @@ define(function(require) { | |||
, timeout: 30 | |||
} | |||
|
|||
expect(zapp.rootResource()).to.be.undefined | |||
// expect(zapp.rootResource()).to.be.undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did the API change in this?
Description
The fix prevents the library from discarding the query parameters from URL when opening a Zambezi resource.
Example:
Motivation and Context
debug=true
)How Was This Tested?
Case 1
location
's modulepushState
method is called with a new pathCase 2
location
module is forcibly re-instantiatedTypes of changes
Checklist:
@mstade @matthewdunsdon @Poetro @FabienDeshayes