git.fiddlerwoaroof.com
Browse code

Finally fix receiveAction/receiveLocation cycle

Ed Langley authored on 21/06/2017 00:33:20
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);
... ...
@@ -47,4 +47,4 @@ export default function addMissingHistoryEvents(window, history) {
47 47
       return result;
48 48
     };
49 49
   });
50
-}
51 50
\ No newline at end of file
51
+}