Browse code
Finally fix receiveAction/receiveLocation cycle
Ed Langley authored on 21/06/2017 00:33:20
Showing 4 changed files
Showing 4 changed files
... | ... |
@@ -305,26 +305,35 @@ function createActionDispatcher(routesConfig, window) { |
305 | 305 |
const location = ev.detail; |
306 | 306 |
this.receiveLocation(location); |
307 | 307 |
}, |
308 |
+ |
|
309 |
+ onLocationChanged(newLoc, cb) { |
|
310 |
+ if (this.currentLocation !== newLoc) { |
|
311 |
+ this.currentLocation = newLoc; |
|
312 |
+ return cb(); |
|
313 |
+ } |
|
314 |
+ }, |
|
315 |
+ |
|
308 | 316 |
receiveLocation(location) { |
309 |
- if (this.currentLocation !== location.pathname) { |
|
310 |
- this.currentLocation = location.pathname; |
|
317 |
+ this.onLocationChanged(location.pathname, () => { |
|
311 | 318 |
const match = matchRoute(location, compiledRouteMatchers); |
312 | 319 |
if(match) { |
313 | 320 |
const action = constructAction(match); |
314 | 321 |
|
315 | 322 |
this.store.dispatch(action); |
316 | 323 |
} |
317 |
- } |
|
324 |
+ }); |
|
318 | 325 |
}, |
326 |
+ |
|
319 | 327 |
receiveAction(action) { |
320 | 328 |
let matcher = matchAction(action, compiledActionMatchers); |
321 | 329 |
if(matcher) { |
322 | 330 |
let path = constructPath(matcher); |
323 |
- |
|
324 |
- this.currentLocation = path; |
|
325 |
- window.history.pushState({}, '', path); |
|
331 |
+ this.onLocationChanged(path, () => { |
|
332 |
+ window.history.pushState({}, '', path); |
|
333 |
+ }); |
|
326 | 334 |
} |
327 | 335 |
}, |
336 |
+ |
|
328 | 337 |
handlesAction(action) { |
329 | 338 |
return matchesAction(action, compiledActionMatchers); |
330 | 339 |
} |
... | ... |
@@ -4,6 +4,19 @@ import installBrowserRouter from './action-router'; |
4 | 4 |
import addChangeUrlEvent from './change-url-event.js'; |
5 | 5 |
import addMissingHistoryEvents from './history-events.js'; |
6 | 6 |
|
7 |
+const console_log = console.log; |
|
8 |
+console.log = () => {}; |
|
9 |
+function with_console(cb) { |
|
10 |
+ console.log = console_log; |
|
11 |
+ try { |
|
12 |
+ cb(); |
|
13 |
+ } catch (e) { |
|
14 |
+ console.log = () => {}; |
|
15 |
+ throw e; |
|
16 |
+ } |
|
17 |
+ console.log = () => {}; |
|
18 |
+} |
|
19 |
+ |
|
7 | 20 |
function createLocation(path) { |
8 | 21 |
return { |
9 | 22 |
hash: '#hash', |
... | ... |
@@ -18,23 +31,45 @@ function createLocation(path) { |
18 | 31 |
} |
19 | 32 |
|
20 | 33 |
function createFakeWindow(path='/path/to/thing') { |
34 |
+ let locations = [createLocation('(root)')]; |
|
35 |
+ function pushLocation(window, path) { |
|
36 |
+ let newLoc = createLocation(path); |
|
37 |
+ locations.push(newLoc); |
|
38 |
+ window.location = newLoc; |
|
39 |
+ return newLoc; |
|
40 |
+ } |
|
41 |
+ function popLocation(window) { |
|
42 |
+ locations.pop(); |
|
43 |
+ let newLoc = locations[locations.length-1]; |
|
44 |
+ window.location = newLoc; |
|
45 |
+ return newLoc; |
|
46 |
+ } |
|
47 |
+ |
|
21 | 48 |
const window = { |
22 |
- location: createLocation(path), |
|
23 | 49 |
history: { |
24 |
- pushState: jest.fn((_, __, path) => {window.location = createLocation(path)}), |
|
25 |
- replaceState: jest.fn() |
|
50 |
+ pushState: jest.fn((_, __, path) => |
|
51 |
+ {window.location = pushLocation(window, path)}), |
|
52 |
+ replaceState: jest.fn(), |
|
26 | 53 |
} |
27 | 54 |
}; |
28 | 55 |
|
56 |
+ pushLocation(window, path); |
|
29 | 57 |
const map = {}; |
30 | 58 |
|
31 | 59 |
window.addEventListener = jest.fn((event, cb) => { |
32 | 60 |
map[event] = cb; |
33 | 61 |
}); |
34 | 62 |
|
63 |
+ function prepareEvent(window, evName) { |
|
64 |
+ if (evName == 'popstate') { |
|
65 |
+ window.location = popLocation(window); |
|
66 |
+ } |
|
67 |
+ } |
|
68 |
+ |
|
35 | 69 |
window.dispatchEvent = jest.fn(ev => { |
36 | 70 |
const evName = ev.type; |
37 | 71 |
if(map[evName]) { |
72 |
+ prepareEvent(window, evName); |
|
38 | 73 |
map[evName].handleEvent(ev); |
39 | 74 |
} |
40 | 75 |
}); |
... | ... |
@@ -73,6 +108,7 @@ function setupTest(routesConfig, path='/path/to/thing') { |
73 | 108 |
return {store, reduce, window, urlChanges, actionsDispatched, fireUrlChange, init}; |
74 | 109 |
} |
75 | 110 |
|
111 |
+ |
|
76 | 112 |
it("router handles exact match in preference to wildcard match", () => { |
77 | 113 |
//given |
78 | 114 |
const actionType = 'THE_ACTION'; |
... | ... |
@@ -110,6 +146,37 @@ it("router doees not dispatch an action from url change that is caused by action |
110 | 146 |
expect(actionsDispatched()).toEqual([action]); |
111 | 147 |
}); |
112 | 148 |
|
149 |
+it("popstate doesn't cause a pushstate", () => { |
|
150 |
+ //given |
|
151 |
+ const actionType = 'THE_ACTION'; |
|
152 |
+ const id = "1"; |
|
153 |
+ const view = "home"; |
|
154 |
+ const action = {type: actionType, id, view}; |
|
155 |
+ const routesConfig = [ |
|
156 |
+ ["/somewhere/:id/:view", actionType, {}], |
|
157 |
+ ["/somewhere/:id/default", actionType, {view: "home"}], |
|
158 |
+ ]; |
|
159 |
+ |
|
160 |
+ const { |
|
161 |
+ urlChanges, |
|
162 |
+ store, |
|
163 |
+ actionsDispatched, |
|
164 |
+ fireUrlChange, |
|
165 |
+ init, |
|
166 |
+ window |
|
167 |
+ } = setupTest(routesConfig, '/somewhere/foo/default'); |
|
168 |
+ |
|
169 |
+ init(); |
|
170 |
+ window.history.pushState({}, '', '/somwhere/bar/default'); |
|
171 |
+ |
|
172 |
+ |
|
173 |
+ // when |
|
174 |
+ window.dispatchEvent(new CustomEvent('popstate', {})); |
|
175 |
+ |
|
176 |
+ // then |
|
177 |
+ expect(urlChanges().length).toEqual(1); |
|
178 |
+}); |
|
179 |
+ |
|
113 | 180 |
it("router handles wildcard with extra args correctly", () => { |
114 | 181 |
|
115 | 182 |
//given |
... | ... |
@@ -320,3 +387,5 @@ it("pathForAction should render a route", () => { |
320 | 387 |
// then |
321 | 388 |
expect(actual).toEqual('/something/hooray'); |
322 | 389 |
}); |
390 |
+ |
|
391 |
+console.log = console_log; |
... | ... |
@@ -16,10 +16,12 @@ let MISSING_CHANGE_URL = Symbol("missing_change_url"); |
16 | 16 |
export default function addChangeUrlEvent(window) { |
17 | 17 |
|
18 | 18 |
debounce(window, MISSING_CHANGE_URL, () => { |
19 |
- const changeUrlEventCreator = { |
|
19 |
+ const changeUrlEventCreator = ({ |
|
20 | 20 |
lastLocation: null, |
21 |
- handleEvent() { // interface for EventListener |
|
22 |
- let {hash, host, hostname, origin, href, pathname, port, protocol} = window.location; |
|
21 |
+ handleEvent(ev) { // interface for EventListener |
|
22 |
+ |
|
23 |
+ let {hash, host, hostname, origin, href, pathname, port, protocol} = |
|
24 |
+ window.location || {}; |
|
23 | 25 |
// store in object for comparison |
24 | 26 |
const pushedLocation = {hash, host, hostname, origin, href, pathname, port, protocol}; |
25 | 27 |
|
... | ... |
@@ -30,7 +32,7 @@ export default function addChangeUrlEvent(window) { |
30 | 32 |
this.lastLocation = pushedLocation; |
31 | 33 |
} |
32 | 34 |
} |
33 |
- }; |
|
35 |
+ }); |
|
34 | 36 |
|
35 | 37 |
// / make sure we fire urlchanged for these |
36 | 38 |
wrapEvent(window, 'popstate', changeUrlEventCreator); |