Skip to content

Commit a5af669

Browse files
committed
Fixed browser back button
Browser Back button followed by Forward button wasn't properly working. To reproduce: - Perform a text search - Make a facet filter selection - Press the back button - Press the forward button - Note that pressing the forward button will not reapply the filter, as it should.
1 parent c10c866 commit a5af669

File tree

3 files changed

+133
-69
lines changed

3 files changed

+133
-69
lines changed

src/search-lib/URLManager.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ export default class URLManager {
177177
// want to notify that the URL changed.
178178
if (`?${this.lastPushSearchString}` === location.search) return;
179179

180+
// Once we've decided to return based on lastPushSearchString, reset
181+
// it so that we don't break back / forward button.
182+
this.lastPushSearchString = "";
183+
180184
callback(
181185
paramsToState(
182186
queryString.parse(location.search, {

src/search-lib/URLManager.test.js

Lines changed: 129 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,50 @@ function createManager() {
1212
return manager;
1313
}
1414

15+
const basicParameterState = {
16+
filters: [
17+
{
18+
dependencies: ["underscore", "another"]
19+
},
20+
{
21+
keywords: ["node"]
22+
}
23+
],
24+
resultsPerPage: 20,
25+
searchTerm: "node",
26+
sortDirection: "asc",
27+
sortField: "name"
28+
};
29+
30+
const basicParameterStateAsUrl =
31+
"?fv-dependencies=underscore&fv-dependencies=another&fv-keywords=node&q=node&size=20&sort-direction=asc&sort-field=name";
32+
33+
const parameterStateWithRangeFilters = {
34+
filters: [
35+
{
36+
date: [
37+
{
38+
from: 12,
39+
to: 4000
40+
},
41+
{
42+
to: 4000
43+
}
44+
]
45+
},
46+
{
47+
cost: [
48+
{
49+
from: 50
50+
}
51+
]
52+
},
53+
{
54+
keywords: "node"
55+
}
56+
]
57+
};
58+
1559
it("can be initialized", () => {
1660
const manager = new URLManager();
1761
expect(manager).toBeInstanceOf(URLManager);
@@ -58,20 +102,7 @@ describe("#getStateFromURL", () => {
58102
describe("#pushStateToURL", () => {
59103
it("will update the url with the url corresponding to the provided state", () => {
60104
const manager = createManager();
61-
manager.pushStateToURL({
62-
filters: [
63-
{
64-
dependencies: ["underscore", "another"]
65-
},
66-
{
67-
keywords: ["node"]
68-
}
69-
],
70-
resultsPerPage: 20,
71-
searchTerm: "node",
72-
sortDirection: "asc",
73-
sortField: "name"
74-
});
105+
manager.pushStateToURL(basicParameterState);
75106
const queryString = manager.history.push.mock.calls[0][0].search;
76107
expect(queryString).toEqual(
77108
"?fv-dependencies=underscore&fv-dependencies=another&fv-keywords=node&q=node&size=20&sort-field=name&sort-direction=asc"
@@ -80,31 +111,7 @@ describe("#pushStateToURL", () => {
80111

81112
it("will update the url with range filter types", () => {
82113
const manager = createManager();
83-
manager.pushStateToURL({
84-
filters: [
85-
{
86-
date: [
87-
{
88-
from: 12,
89-
to: 4000
90-
},
91-
{
92-
to: 4000
93-
}
94-
]
95-
},
96-
{
97-
cost: [
98-
{
99-
from: 50
100-
}
101-
]
102-
},
103-
{
104-
keywords: "node"
105-
}
106-
]
107-
});
114+
manager.pushStateToURL(parameterStateWithRangeFilters);
108115
const queryString = manager.history.push.mock.calls[0][0].search;
109116
expect(queryString).toEqual(
110117
"?fr-date=12_4000&fr-date=_4000&fr-cost=50_&fv-keywords=node"
@@ -113,20 +120,94 @@ describe("#pushStateToURL", () => {
113120
});
114121

115122
describe("#onURLStateChange", () => {
123+
let manager;
124+
125+
function setup() {
126+
manager = createManager();
127+
}
128+
129+
function pushStateToURL(state) {
130+
manager.pushStateToURL(state);
131+
return manager.history.push.mock.calls[0][0].search;
132+
}
133+
134+
function simulateBrowserHistoryEvent(newUrl) {
135+
// Since we're not in a real browser, we simulate the change event that
136+
// would have ocurred.
137+
manager.history.listen.mock.calls[0][0]({
138+
search: newUrl
139+
});
140+
}
141+
142+
function subject(onURLStateChangeHandler, newUrl) {
143+
// Set our handler, which we be called any time url state changes
144+
manager.onURLStateChange(onURLStateChangeHandler);
145+
146+
simulateBrowserHistoryEvent(newUrl);
147+
}
148+
116149
it("will call provided callback with updated state when url changes", () => {
117-
const manager = createManager();
150+
setup();
151+
let newState;
152+
153+
// Provide url state change handler
154+
subject(state => {
155+
newState = state;
156+
}, basicParameterStateAsUrl);
157+
158+
// Verify it is called when url state changes
159+
expect(newState).toEqual(basicParameterState);
160+
});
161+
162+
/*
163+
If we triggered the callback every time we pushed state to the url,
164+
we would have an infinite loop.
165+
166+
pushStateToURL -> UpdateUrl -> HistoryEvent -> Handler -> pushstateToURL
167+
168+
Instead, we just want:
169+
pushStateToURL -> UpdateUrl -> HistoryEvent -> Handler
118170
171+
*/
172+
it("will not trigger callback as a result of pushStateToURL", () => {
173+
setup();
174+
175+
// Push state to the url
176+
// newUrl is the url that the browser changed to
177+
const newUrl = pushStateToURL(basicParameterState);
119178
let newState;
120-
manager.onURLStateChange(state => {
179+
180+
// Provide url state change handler
181+
subject(state => {
121182
newState = state;
122-
});
183+
}, newUrl);
123184

124-
// Simulate state change
125-
manager.history.listen.mock.calls[0][0]({
126-
search:
127-
"?fv-dependencies=underscore&fv-keywords=node&q=node&size=20&sort-direction=asc&sort-field=name"
128-
});
185+
// Verify it is called when url state changes
186+
expect(newState).toBe(undefined);
187+
});
188+
189+
it("will trigger callback if navigating back to a url with browser buttons", () => {
190+
setup();
191+
192+
// Push state to the url
193+
// newUrl is the url that the browser changed to
194+
const newUrl = pushStateToURL(basicParameterState);
195+
let newState;
196+
197+
// Provide url state change handler
198+
subject(state => {
199+
newState = state;
200+
}, newUrl);
201+
202+
// Verify it is called when url state changes
203+
expect(newState).toBe(undefined);
204+
205+
// Back button to empty state
206+
simulateBrowserHistoryEvent("");
207+
expect(newState).toEqual({}); // Was called, but empty state
129208

130-
expect(newState).toMatchSnapshot();
209+
// Forward button to double back to the original "newUrl"
210+
simulateBrowserHistoryEvent(newUrl);
211+
expect(newState).toEqual(basicParameterState);
131212
});
132213
});

src/search-lib/__snapshots__/URLManager.test.js.snap

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -78,24 +78,3 @@ Object {
7878
"sortField": "name",
7979
}
8080
`;
81-
82-
exports[`#onURLStateChange will call provided callback with updated state when url changes 1`] = `
83-
Object {
84-
"filters": Array [
85-
Object {
86-
"dependencies": Array [
87-
"underscore",
88-
],
89-
},
90-
Object {
91-
"keywords": Array [
92-
"node",
93-
],
94-
},
95-
],
96-
"resultsPerPage": 20,
97-
"searchTerm": "node",
98-
"sortDirection": "asc",
99-
"sortField": "name",
100-
}
101-
`;

0 commit comments

Comments
 (0)