navigation-stack 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/README.md +603 -163
  2. package/data-storage/package.json +6 -0
  3. package/karma.conf.cjs +21 -4
  4. package/lib/cjs/NavigationStack.js +73 -0
  5. package/lib/cjs/data-storage/DataStorage.js +71 -0
  6. package/lib/cjs/data-storage/LocationDataStorage.js +29 -0
  7. package/lib/cjs/data-storage/index.js +9 -0
  8. package/lib/cjs/environment/InMemoryEnvironment.js +15 -0
  9. package/lib/cjs/environment/WebBrowserEnvironment.js +15 -0
  10. package/lib/cjs/environment/data-storage/InMemoryDataStorage.js +27 -0
  11. package/lib/cjs/environment/data-storage/WebBrowserDataStorage.js +21 -0
  12. package/lib/cjs/environment/scroll-position/InMemoryScrollPosition.js +44 -0
  13. package/lib/cjs/environment/scroll-position/WebBrowserScrollPosition.js +60 -0
  14. package/lib/cjs/getLocationFromInternalLocation.js +14 -0
  15. package/lib/cjs/index.js +20 -16
  16. package/lib/cjs/navigationBlockers.js +25 -23
  17. package/lib/cjs/{normalizeInputLocation.js → parseInputLocation.js} +25 -9
  18. package/lib/cjs/{ActionTypes.js → redux/ActionTypes.js} +1 -1
  19. package/lib/cjs/redux/ActionTypesInternal.js +8 -0
  20. package/lib/cjs/{Actions.js → redux/Actions.js} +5 -4
  21. package/lib/cjs/redux/createMiddlewares.js +60 -0
  22. package/lib/cjs/redux/index.js +13 -0
  23. package/lib/cjs/redux/internalLocationReducer.js +14 -0
  24. package/lib/cjs/redux/middleware/createAddInputLocationBasePathMiddleware.js +32 -0
  25. package/lib/cjs/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +113 -0
  26. package/lib/cjs/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +94 -0
  27. package/lib/cjs/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +30 -0
  28. package/lib/cjs/redux/middleware/createUpdateInternalLocationMiddleware.js +73 -0
  29. package/lib/cjs/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +11 -8
  30. package/lib/cjs/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +6 -4
  31. package/lib/cjs/redux/middleware/updateLocationMiddleware.js +34 -0
  32. package/lib/cjs/scroll-position/PageScrollPositionSetter.js +97 -0
  33. package/lib/cjs/scroll-position/ScrollPositionAutoSaver.js +130 -0
  34. package/lib/cjs/scroll-position/ScrollPositionRestoration.js +383 -0
  35. package/lib/cjs/scroll-position/ScrollPositionSaver.js +81 -0
  36. package/lib/cjs/scroll-position/ScrollPositionSetter.js +16 -0
  37. package/lib/cjs/scroll-position/constants.js +5 -0
  38. package/lib/cjs/scroll-position/index.js +7 -0
  39. package/lib/cjs/scroll-position/scheduleNextTick.js +11 -0
  40. package/lib/cjs/session/InMemorySession.js +22 -0
  41. package/lib/cjs/session/ServerSideRenderSession.js +17 -0
  42. package/lib/cjs/session/Session.js +196 -0
  43. package/lib/cjs/session/WebBrowserSession.js +20 -0
  44. package/lib/cjs/session/key/createSessionKey.js +23 -0
  45. package/lib/cjs/session/lifecycle/InMemorySessionLifecycle.js +19 -0
  46. package/lib/cjs/session/lifecycle/WebBrowserSessionLifecycle.js +128 -0
  47. package/lib/cjs/session/lifecycle/page-lifecycle/PageLifecycle.js +269 -0
  48. package/lib/cjs/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +8 -0
  49. package/lib/cjs/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +33 -0
  50. package/lib/cjs/session/navigation/InMemoryNavigation.js +104 -0
  51. package/lib/cjs/session/navigation/ServerSideNavigation.js +61 -0
  52. package/lib/cjs/session/navigation/WebBrowserNavigation.js +221 -0
  53. package/lib/cjs/session/navigation/error/NavigationOutOfBoundsError.js +12 -0
  54. package/lib/cjs/session/navigation/error/ServerSideNavigationError.js +21 -0
  55. package/lib/cjs/session/navigation/operation/operations.js +11 -0
  56. package/lib/cjs/session/subscription/Subscription.js +81 -0
  57. package/lib/data-storage/index.d.ts +35 -0
  58. package/lib/esm/NavigationStack.js +66 -0
  59. package/lib/esm/data-storage/DataStorage.js +65 -0
  60. package/lib/esm/data-storage/LocationDataStorage.js +22 -0
  61. package/lib/esm/data-storage/index.js +2 -0
  62. package/lib/esm/environment/InMemoryEnvironment.js +8 -0
  63. package/lib/esm/environment/WebBrowserEnvironment.js +8 -0
  64. package/lib/esm/environment/data-storage/InMemoryDataStorage.js +21 -0
  65. package/lib/esm/environment/data-storage/WebBrowserDataStorage.js +15 -0
  66. package/lib/esm/environment/scroll-position/InMemoryScrollPosition.js +38 -0
  67. package/lib/esm/environment/scroll-position/WebBrowserScrollPosition.js +54 -0
  68. package/lib/esm/getLocationFromInternalLocation.js +9 -0
  69. package/lib/esm/index.js +10 -8
  70. package/lib/esm/navigationBlockers.js +25 -23
  71. package/lib/esm/{normalizeInputLocation.js → parseInputLocation.js} +24 -8
  72. package/lib/esm/{ActionTypes.js → redux/ActionTypes.js} +1 -1
  73. package/lib/esm/redux/ActionTypesInternal.js +3 -0
  74. package/lib/esm/{Actions.js → redux/Actions.js} +5 -4
  75. package/lib/esm/redux/createMiddlewares.js +54 -0
  76. package/lib/esm/redux/index.js +4 -0
  77. package/lib/esm/redux/internalLocationReducer.js +8 -0
  78. package/lib/esm/redux/middleware/createAddInputLocationBasePathMiddleware.js +27 -0
  79. package/lib/esm/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +108 -0
  80. package/lib/esm/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +88 -0
  81. package/lib/esm/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +25 -0
  82. package/lib/esm/redux/middleware/createUpdateInternalLocationMiddleware.js +68 -0
  83. package/lib/esm/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +10 -7
  84. package/lib/esm/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +5 -3
  85. package/lib/esm/redux/middleware/updateLocationMiddleware.js +28 -0
  86. package/lib/esm/scroll-position/PageScrollPositionSetter.js +91 -0
  87. package/lib/esm/scroll-position/ScrollPositionAutoSaver.js +123 -0
  88. package/lib/esm/scroll-position/ScrollPositionRestoration.js +376 -0
  89. package/lib/esm/scroll-position/ScrollPositionSaver.js +74 -0
  90. package/lib/esm/scroll-position/ScrollPositionSetter.js +10 -0
  91. package/lib/esm/scroll-position/constants.js +1 -0
  92. package/lib/esm/scroll-position/index.js +1 -0
  93. package/lib/esm/scroll-position/scheduleNextTick.js +6 -0
  94. package/lib/esm/session/InMemorySession.js +15 -0
  95. package/lib/esm/session/ServerSideRenderSession.js +11 -0
  96. package/lib/esm/session/Session.js +189 -0
  97. package/lib/esm/session/WebBrowserSession.js +13 -0
  98. package/lib/esm/session/key/createSessionKey.js +18 -0
  99. package/lib/esm/session/lifecycle/InMemorySessionLifecycle.js +13 -0
  100. package/lib/esm/session/lifecycle/WebBrowserSessionLifecycle.js +120 -0
  101. package/lib/esm/session/lifecycle/page-lifecycle/PageLifecycle.js +263 -0
  102. package/lib/esm/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +2 -0
  103. package/lib/esm/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +30 -0
  104. package/lib/esm/session/navigation/InMemoryNavigation.js +97 -0
  105. package/lib/esm/session/navigation/ServerSideNavigation.js +54 -0
  106. package/lib/esm/session/navigation/WebBrowserNavigation.js +213 -0
  107. package/lib/esm/session/navigation/error/NavigationOutOfBoundsError.js +6 -0
  108. package/lib/esm/session/navigation/error/ServerSideNavigationError.js +14 -0
  109. package/lib/esm/session/navigation/operation/operations.js +6 -0
  110. package/lib/esm/session/subscription/Subscription.js +75 -0
  111. package/lib/index.d.ts +178 -157
  112. package/lib/redux/index.d.ts +90 -0
  113. package/lib/scroll-position/index.d.ts +107 -0
  114. package/package.json +9 -5
  115. package/redux/package.json +6 -0
  116. package/scroll-position/package.json +6 -0
  117. package/src/NavigationStack.js +84 -0
  118. package/src/data-storage/DataStorage.js +69 -0
  119. package/src/data-storage/LocationDataStorage.js +23 -0
  120. package/src/data-storage/index.js +2 -0
  121. package/src/environment/InMemoryEnvironment.js +9 -0
  122. package/src/environment/WebBrowserEnvironment.js +9 -0
  123. package/src/environment/data-storage/InMemoryDataStorage.js +23 -0
  124. package/src/environment/data-storage/WebBrowserDataStorage.js +17 -0
  125. package/src/environment/scroll-position/InMemoryScrollPosition.js +45 -0
  126. package/src/environment/scroll-position/WebBrowserScrollPosition.js +72 -0
  127. package/src/getLocationFromInternalLocation.js +7 -0
  128. package/src/index.js +10 -8
  129. package/src/navigationBlockers.js +28 -27
  130. package/src/{normalizeInputLocation.js → parseInputLocation.js} +23 -8
  131. package/src/{ActionTypes.js → redux/ActionTypes.js} +1 -1
  132. package/src/redux/ActionTypesInternal.js +3 -0
  133. package/src/{Actions.js → redux/Actions.js} +4 -3
  134. package/src/redux/createMiddlewares.js +65 -0
  135. package/src/redux/index.js +4 -0
  136. package/src/redux/internalLocationReducer.js +9 -0
  137. package/src/redux/middleware/createAddInputLocationBasePathMiddleware.js +27 -0
  138. package/src/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +119 -0
  139. package/src/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +94 -0
  140. package/src/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +26 -0
  141. package/src/redux/middleware/createUpdateInternalLocationMiddleware.js +72 -0
  142. package/src/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +10 -3
  143. package/src/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +5 -3
  144. package/src/redux/middleware/updateLocationMiddleware.js +28 -0
  145. package/src/scroll-position/PageScrollPositionSetter.js +110 -0
  146. package/src/scroll-position/ScrollPositionAutoSaver.js +151 -0
  147. package/src/scroll-position/ScrollPositionRestoration.js +506 -0
  148. package/src/scroll-position/ScrollPositionSaver.js +100 -0
  149. package/src/scroll-position/ScrollPositionSetter.js +16 -0
  150. package/src/scroll-position/constants.js +1 -0
  151. package/src/scroll-position/index.js +1 -0
  152. package/src/scroll-position/scheduleNextTick.js +6 -0
  153. package/src/session/InMemorySession.js +13 -0
  154. package/src/session/ServerSideRenderSession.js +9 -0
  155. package/src/session/Session.js +216 -0
  156. package/src/session/WebBrowserSession.js +13 -0
  157. package/src/session/key/createSessionKey.js +18 -0
  158. package/src/session/lifecycle/InMemorySessionLifecycle.js +13 -0
  159. package/src/session/lifecycle/WebBrowserSessionLifecycle.js +126 -0
  160. package/src/session/lifecycle/page-lifecycle/PageLifecycle.js +291 -0
  161. package/src/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +3 -0
  162. package/src/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +32 -0
  163. package/src/session/navigation/InMemoryNavigation.js +78 -0
  164. package/src/session/navigation/ServerSideNavigation.js +43 -0
  165. package/src/session/navigation/WebBrowserNavigation.js +224 -0
  166. package/src/session/navigation/error/NavigationOutOfBoundsError.js +7 -0
  167. package/src/session/navigation/error/ServerSideNavigationError.js +18 -0
  168. package/src/session/navigation/operation/operations.js +6 -0
  169. package/src/session/subscription/Subscription.js +76 -0
  170. package/test/NavigationStack.test.js +296 -0
  171. package/test/{LocationDataStorage.test.js → data-storage/LocationDataStorage.test.js} +3 -3
  172. package/test/data-storage/index.test.js +8 -0
  173. package/test/index.js +12 -0
  174. package/test/index.test.js +8 -7
  175. package/test/{helpers.js → middlewareTestUtil.js} +9 -12
  176. package/test/{normalizeInputLocation.test.js → parseInputLocationMiddleware.test.js} +9 -9
  177. package/test/{Action.test.js → redux/Action.test.js} +7 -6
  178. package/test/{ActionTypes.test.js → redux/ActionTypes.test.js} +2 -2
  179. package/test/redux/createMiddlewares.test.js +96 -0
  180. package/test/redux/index.test.js +10 -0
  181. package/test/{locationReducer.test.js → redux/locationReducer.test.js} +4 -7
  182. package/test/redux/middleware/createAddInputLocationBasePathMiddleware.test.js +40 -0
  183. package/test/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.test.js +264 -0
  184. package/test/redux/middleware/createProgrammaticNavigationBlockerMiddleware.test.js +312 -0
  185. package/test/redux/middleware/createRemoveOutputLocationBasePathMiddleware.test.js +51 -0
  186. package/test/{middleware/navigationActionMiddleware.test.js → redux/middleware/navigationOperationMiddleware.test.js} +16 -12
  187. package/test/{middleware/normalizeInputLocationMiddleware.test.js → redux/middleware/parseInputLocationMiddleware.test.js} +4 -4
  188. package/test/scroll-position/ScrollPositionRestoration.test.js +418 -0
  189. package/test/scroll-position/addScrollableContainer.js +36 -0
  190. package/test/scroll-position/addScrollableContainerWithHyperlink.js +50 -0
  191. package/test/scroll-position/createApp.js +112 -0
  192. package/test/scroll-position/delay.js +9 -0
  193. package/test/scroll-position/mockPageLifecycle.js +17 -0
  194. package/test/scroll-position/runApp.js +24 -0
  195. package/test/scroll-position/withScrollableContainerAtIndexPage.js +62 -0
  196. package/test/session/InMemorySession.test.js +348 -0
  197. package/test/session/ServerSession.test.js +17 -9
  198. package/test/session/WebBrowserSession.test.js +265 -0
  199. package/test/testUtil.js +3 -0
  200. package/types/data-storage/index.d.ts +35 -0
  201. package/types/index.d.ts +178 -157
  202. package/types/redux/index.d.ts +90 -0
  203. package/types/scroll-position/index.d.ts +107 -0
  204. package/types/tsconfig.json +1 -1
  205. package/lib/cjs/LocationDataStorage.js +0 -60
  206. package/lib/cjs/addBeforeLocationChangeListener.js +0 -7
  207. package/lib/cjs/beforeLocationChangeListeners.js +0 -51
  208. package/lib/cjs/createMiddlewares.js +0 -47
  209. package/lib/cjs/middleware/createBasePathMiddleware.js +0 -24
  210. package/lib/cjs/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -39
  211. package/lib/cjs/middleware/createLocationMiddleware.js +0 -56
  212. package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +0 -161
  213. package/lib/cjs/middleware/createTransformLocationMiddleware.js +0 -38
  214. package/lib/cjs/onlyAllowedOnClientSide.js +0 -10
  215. package/lib/cjs/session/BrowserSession.js +0 -235
  216. package/lib/cjs/session/MemorySession.js +0 -223
  217. package/lib/cjs/session/ServerSession.js +0 -65
  218. package/lib/esm/LocationDataStorage.js +0 -53
  219. package/lib/esm/addBeforeLocationChangeListener.js +0 -2
  220. package/lib/esm/beforeLocationChangeListeners.js +0 -44
  221. package/lib/esm/createMiddlewares.js +0 -41
  222. package/lib/esm/middleware/createBasePathMiddleware.js +0 -19
  223. package/lib/esm/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -34
  224. package/lib/esm/middleware/createLocationMiddleware.js +0 -50
  225. package/lib/esm/middleware/createNavigationBlockerMiddleware.js +0 -156
  226. package/lib/esm/middleware/createTransformLocationMiddleware.js +0 -33
  227. package/lib/esm/onlyAllowedOnClientSide.js +0 -5
  228. package/lib/esm/session/BrowserSession.js +0 -229
  229. package/lib/esm/session/MemorySession.js +0 -217
  230. package/lib/esm/session/ServerSession.js +0 -58
  231. package/src/LocationDataStorage.js +0 -59
  232. package/src/addBeforeLocationChangeListener.js +0 -2
  233. package/src/beforeLocationChangeListeners.js +0 -54
  234. package/src/createMiddlewares.js +0 -45
  235. package/src/middleware/createBasePathMiddleware.js +0 -20
  236. package/src/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -40
  237. package/src/middleware/createLocationMiddleware.js +0 -55
  238. package/src/middleware/createNavigationBlockerMiddleware.js +0 -168
  239. package/src/middleware/createTransformLocationMiddleware.js +0 -29
  240. package/src/onlyAllowedOnClientSide.js +0 -5
  241. package/src/session/BrowserSession.js +0 -235
  242. package/src/session/MemorySession.js +0 -219
  243. package/src/session/ServerSession.js +0 -67
  244. package/test/createMiddlewares.test.js +0 -62
  245. package/test/middleware/createBasePathMiddleware.test.js +0 -67
  246. package/test/middleware/createBeforeLocationChangeListenerMiddleware.test.js +0 -141
  247. package/test/middleware/createNavigationBlockerMiddleware.test.js +0 -471
  248. package/test/middleware/createTransformLocationMiddleware.test.js +0 -44
  249. package/test/session/BrowserSession.test.js +0 -182
  250. package/test/session/MemorySession.test.js +0 -244
  251. /package/lib/cjs/{locationReducer.js → redux/locationReducer.js} +0 -0
  252. /package/lib/esm/{locationReducer.js → redux/locationReducer.js} +0 -0
  253. /package/src/{locationReducer.js → redux/locationReducer.js} +0 -0
@@ -1,471 +0,0 @@
1
- import delay from 'delay';
2
- import pDefer from 'p-defer';
3
- import { applyMiddleware, createStore } from 'redux';
4
-
5
- import Actions from '../../src/Actions';
6
- import addNavigationBlockerOriginal from '../../src/addNavigationBlocker';
7
- import createMiddlewares from '../../src/createMiddlewares';
8
- import locationReducer from '../../src/locationReducer';
9
- import MemorySession from '../../src/session/MemorySession';
10
- import { shouldWarn } from '../helpers';
11
-
12
- describe('createNavigationBlockerMiddleware', () => {
13
- const sandbox = sinon.createSandbox();
14
-
15
- let session;
16
- let store;
17
-
18
- function addNavigationBlocker(blocker) {
19
- return addNavigationBlockerOriginal(session, blocker);
20
- }
21
-
22
- beforeEach(() => {
23
- session = new MemorySession('/foo');
24
-
25
- store = createStore(
26
- locationReducer,
27
- applyMiddleware(...createMiddlewares(session)),
28
- );
29
- store.dispatch(Actions.init());
30
-
31
- sinon.spy(session, 'addBeforeDestroyListener');
32
- // sinon.spy(session, '_removeBeforeDestroyListener');
33
- });
34
-
35
- afterEach(() => {
36
- store.dispatch(Actions.dispose());
37
-
38
- sandbox.restore();
39
- });
40
-
41
- describe('PUSH navigations', () => {
42
- it('should block navigation when blocker returns `true`', () => {
43
- const blocker = sinon.stub().returns(true);
44
- addNavigationBlocker(blocker);
45
-
46
- store.dispatch(Actions.push('/bar'));
47
- expect(store.getState().pathname).to.equal('/foo');
48
-
49
- expect(blocker).to.have.been.calledOnce();
50
-
51
- expect(blocker.firstCall.args[0]).to.include({
52
- action: 'PUSH',
53
- pathname: '/bar',
54
- });
55
- });
56
-
57
- it('should allow navigation when blocker returns `undefined`', () => {
58
- addNavigationBlocker(() => undefined);
59
-
60
- store.dispatch(Actions.push('/bar'));
61
- expect(store.getState().pathname).to.equal('/bar');
62
- });
63
-
64
- it("should fall through when first blocker doesn't return `true`", () => {
65
- const blocker1 = sinon.stub().returns(undefined);
66
- const blocker2 = sinon.stub().returns(true);
67
-
68
- addNavigationBlocker(blocker1);
69
- addNavigationBlocker(blocker2);
70
-
71
- store.dispatch(Actions.push('/bar'));
72
- expect(store.getState().pathname).to.equal('/foo');
73
-
74
- expect(blocker1).to.have.been.calledOnce();
75
- expect(blocker2).to.have.been.calledOnce();
76
- });
77
-
78
- it('should not fall through when first blocker returns `true`', () => {
79
- const blocker1 = sinon.stub().returns(true);
80
- const blocker2 = sinon.stub().returns(undefined);
81
-
82
- addNavigationBlocker(blocker1);
83
- addNavigationBlocker(blocker2);
84
-
85
- store.dispatch(Actions.push('/bar'));
86
- expect(store.getState().pathname).to.equal('/foo');
87
-
88
- expect(blocker1).to.have.been.calledOnce();
89
- expect(blocker2).not.to.have.been.called();
90
- });
91
-
92
- it('should warn on and ignore blockers that throw', () => {
93
- shouldWarn(
94
- 'Ignoring navigation blocker `syncblocker` that failed with `Error: foo`.',
95
- );
96
-
97
- const syncblocker = () => {
98
- throw new Error('foo');
99
- };
100
-
101
- addNavigationBlocker(syncblocker);
102
-
103
- store.dispatch(Actions.push('/bar'));
104
- expect(store.getState().pathname).to.equal('/bar');
105
- });
106
-
107
- // it('should show a confirmation dialog and allow navigation on string', () => {
108
- // sandbox.stub(window, 'confirm').returns(true);
109
- //
110
- // addNavigationBlocker(({ pathname }) => pathname);
111
- //
112
- // store.dispatch(Actions.push('/bar'));
113
- // expect(store.getState().pathname).to.equal('/bar');
114
- //
115
- // expect(window.confirm)
116
- // .to.have.been.calledOnce()
117
- // .and.to.have.been.called.with('/bar');
118
- // });
119
-
120
- // it('should show a confirmation dialog and block navigation on string', () => {
121
- // sandbox.stub(window, 'confirm').returns(false);
122
- //
123
- // addNavigationBlocker(({ pathname }) => pathname);
124
- //
125
- // store.dispatch(Actions.push('/bar'));
126
- // expect(store.getState().pathname).to.equal('/foo');
127
- //
128
- // expect(window.confirm)
129
- // .to.have.been.calledOnce()
130
- // .and.to.have.been.called.with('/bar');
131
- // });
132
-
133
- it('should allow navigation when blocker returns `undefined` (async)', async () => {
134
- const deferred = pDefer();
135
- addNavigationBlocker(() => deferred.promise);
136
-
137
- store.dispatch(Actions.push('/bar'));
138
- expect(store.getState().pathname).to.equal('/foo');
139
-
140
- deferred.resolve(undefined);
141
- await delay(10);
142
-
143
- expect(store.getState().pathname).to.equal('/bar');
144
- });
145
-
146
- it('should block navigation when blocker returns `true` (async)', async () => {
147
- const deferred = pDefer();
148
- addNavigationBlocker(() => deferred.promise);
149
-
150
- store.dispatch(Actions.push('/bar'));
151
- expect(store.getState().pathname).to.equal('/foo');
152
-
153
- deferred.resolve(true);
154
- await delay(10);
155
-
156
- expect(store.getState().pathname).to.equal('/foo');
157
- });
158
-
159
- it('should allow chaining async blockers', async () => {
160
- const deferred1 = pDefer();
161
- const deferred2 = pDefer();
162
-
163
- addNavigationBlocker(() => deferred1.promise);
164
- addNavigationBlocker(() => deferred2.promise);
165
-
166
- store.dispatch(Actions.push('/bar'));
167
- expect(store.getState().pathname).to.equal('/foo');
168
-
169
- deferred1.resolve(undefined);
170
- await delay(10);
171
-
172
- expect(store.getState().pathname).to.equal('/foo');
173
-
174
- deferred2.resolve(undefined);
175
- await delay(10);
176
-
177
- expect(store.getState().pathname).to.equal('/bar');
178
- });
179
-
180
- it('should warn on and ignore async blockers that throw an error', async () => {
181
- shouldWarn(
182
- 'Ignoring navigation blocker `asyncblocker` that failed with `Error: foo`.',
183
- );
184
-
185
- // eslint-disable-next-line require-await
186
- const asyncblocker = async () => {
187
- throw new Error('foo');
188
- };
189
-
190
- addNavigationBlocker(asyncblocker);
191
-
192
- store.dispatch(Actions.push('/bar'));
193
- expect(store.getState().pathname).to.equal('/foo');
194
-
195
- await delay(10);
196
-
197
- expect(store.getState().pathname).to.equal('/bar');
198
- });
199
-
200
- it('should allow removing blockers', () => {
201
- const removeNavigationBlocker = addNavigationBlocker(() => true);
202
-
203
- store.dispatch(Actions.push('/bar'));
204
- expect(store.getState().pathname).to.equal('/foo');
205
-
206
- removeNavigationBlocker();
207
-
208
- store.dispatch(Actions.push('/bar'));
209
- expect(store.getState().pathname).to.equal('/bar');
210
- });
211
- });
212
-
213
- describe('SHIFT navigations', () => {
214
- beforeEach(() => {
215
- store.dispatch(Actions.push('/bar'));
216
- });
217
-
218
- it('should allow navigation when blocker returns `undefined`', () => {
219
- const blocker = sinon.stub().returns(undefined);
220
- addNavigationBlocker(blocker);
221
-
222
- store.dispatch(Actions.shift(-1));
223
- expect(store.getState().pathname).to.equal('/foo');
224
-
225
- expect(blocker.firstCall.args[0]).to.include({
226
- action: 'SHIFT',
227
- pathname: '/foo',
228
- delta: -1,
229
- });
230
- });
231
-
232
- it('should block navigation when blocker returns `true`', () => {
233
- addNavigationBlocker(() => true);
234
-
235
- store.dispatch(Actions.shift(-1));
236
- expect(store.getState().pathname).to.equal('/bar');
237
- });
238
-
239
- it('should allow navigation when blocker returns `undefined` (async)', async () => {
240
- const deferred = pDefer();
241
- addNavigationBlocker(() => deferred.promise);
242
-
243
- store.dispatch(Actions.shift(-1));
244
- expect(store.getState().pathname).to.equal('/bar');
245
-
246
- deferred.resolve(undefined);
247
- await delay(10);
248
-
249
- expect(store.getState().pathname).to.equal('/foo');
250
- });
251
-
252
- it('should block navigation when blocker returns `true` (async)', async () => {
253
- const deferred = pDefer();
254
- addNavigationBlocker(() => deferred.promise);
255
-
256
- store.dispatch(Actions.shift(-1));
257
- expect(store.getState().pathname).to.equal('/bar');
258
-
259
- deferred.resolve(true);
260
- await delay(10);
261
-
262
- expect(store.getState().pathname).to.equal('/bar');
263
- });
264
-
265
- // it('should show a confirmation dialog and allow navigation on string', () => {
266
- // sandbox.stub(window, 'confirm').returns(true);
267
- //
268
- // addNavigationBlocker(({ pathname }) => pathname);
269
- //
270
- // store.dispatch(Actions.shift(-1));
271
- // expect(store.getState().pathname).to.equal('/foo');
272
- //
273
- // expect(window.confirm)
274
- // .to.have.been.calledOnce()
275
- // .and.to.have.been.called.with('/bar');
276
- // });
277
-
278
- it('should ignore the initial load when blocker returns `true`', () => {
279
- // Get rid of the old store. We'll replace it with a new one.
280
- store.dispatch(Actions.dispose());
281
-
282
- store = createStore(
283
- locationReducer,
284
- applyMiddleware(...createMiddlewares(new MemorySession('/foo'))),
285
- );
286
- addNavigationBlocker(() => true);
287
-
288
- expect(store.getState()).to.be.undefined();
289
- store.dispatch(Actions.init());
290
- expect(store.getState().pathname).to.equal('/foo');
291
- });
292
-
293
- it('should support async rewinding', async () => {
294
- // eslint-disable-next-line no-underscore-dangle
295
- const blocker = session.navigation._subscriptionListener;
296
-
297
- let sessionDeferred;
298
-
299
- // eslint-disable-next-line no-underscore-dangle
300
- session.navigation._subscriptionListener = async (location) => {
301
- sessionDeferred = pDefer();
302
- await sessionDeferred.promise;
303
-
304
- blocker(location);
305
- };
306
-
307
- const deferred = pDefer();
308
- addNavigationBlocker(() => deferred.promise);
309
-
310
- store.dispatch(Actions.shift(-1));
311
-
312
- // session shifted, update to store blocked.
313
- expect(session.navigation.init().pathname).to.equal('/foo');
314
- expect(store.getState().pathname).to.equal('/bar');
315
-
316
- sessionDeferred.resolve();
317
- await delay(10);
318
-
319
- // session rewinded.
320
- expect(session.navigation.init().pathname).to.equal('/bar');
321
- expect(store.getState().pathname).to.equal('/bar');
322
-
323
- deferred.resolve(undefined);
324
- await delay(10);
325
-
326
- sessionDeferred.resolve();
327
- await delay(10);
328
-
329
- // session re-shifted (the rewind was undone), update to store delayed.
330
- expect(session.navigation.init().pathname).to.equal('/foo');
331
- expect(store.getState().pathname).to.equal('/foo');
332
- });
333
-
334
- it('should allow navigation without calling any blockers when `location.delta` is `null`', async () => {
335
- const deferred = pDefer();
336
- addNavigationBlocker(() => deferred.promise);
337
-
338
- // Update location with a `SHIFT` action.
339
- /* eslint-disable no-underscore-dangle */
340
- session.navigation._index = 0;
341
- session.navigation._subscriptionListener(session.navigation.init(null));
342
- /* eslint-enable no-underscore-dangle */
343
-
344
- deferred.resolve(undefined);
345
- await delay(10);
346
-
347
- // Without delta, we can't rewind on the session,
348
- // so navigation is allowed without calling any blockers.
349
- expect(session.navigation.init().pathname).to.equal('/foo');
350
- expect(store.getState().pathname).to.equal('/foo');
351
- });
352
-
353
- // it('should allow navigation when blocker returns `undefined` and `location.delta` is `null`', async () => {
354
- // const deferred = pDefer();
355
- // addNavigationBlocker(() => deferred.promise);
356
- //
357
- // // Update location with a `SHIFT` action.
358
- // /* eslint-disable no-underscore-dangle */
359
- // session.navigation._index = 0;
360
- // session.navigation._subscriptionListener(session.navigation.init(null));
361
- // /* eslint-enable no-underscore-dangle */
362
- //
363
- // // Without delta, we can't rewind on the session.
364
- // expect(session.navigation.init().pathname).to.equal('/foo');
365
- // expect(store.getState().pathname).to.equal('/bar');
366
- //
367
- // deferred.resolve(undefined);
368
- // await delay(10);
369
- //
370
- // expect(session.navigation.init().pathname).to.equal('/foo');
371
- // expect(store.getState().pathname).to.equal('/foo');
372
- // });
373
-
374
- // it('should block store update when blocker returns `true` and `location.delta` is `null`', async () => {
375
- // const deferred = pDefer();
376
- // addNavigationBlocker(() => deferred.promise);
377
- //
378
- // /* eslint-disable no-underscore-dangle */
379
- // session.navigation._index = 0;
380
- // session.navigation._subscriptionListener(session.navigation.init(null));
381
- // /* eslint-enable no-underscore-dangle */
382
- //
383
- // expect(session.navigation.init().pathname).to.equal('/foo');
384
- // expect(store.getState().pathname).to.equal('/bar');
385
- //
386
- // deferred.resolve(true);
387
- // await delay(10);
388
- //
389
- // // These are out-of-sync now, but it's the best we can do.
390
- // expect(session.navigation.init().pathname).to.equal('/foo');
391
- // expect(store.getState().pathname).to.equal('/bar');
392
- // });
393
- });
394
-
395
- describe('beforeUnload', () => {
396
- beforeEach(() => {
397
- // Get rid of the old store. We'll replace it with a new one.
398
- store.dispatch(Actions.dispose());
399
-
400
- sandbox.stub(window, 'addEventListener');
401
- sandbox.stub(window, 'removeEventListener');
402
-
403
- store = createStore(
404
- () => null,
405
- applyMiddleware(...createMiddlewares(session)),
406
- );
407
-
408
- store.dispatch(Actions.init());
409
- });
410
-
411
- it('should manage event blocker', () => {
412
- expect(session.addBeforeDestroyListener).not.to.have.been.called();
413
- // expect(window.addEventListener).not.to.have.been.called();
414
-
415
- const removeNavigationBlocker1 = addNavigationBlocker(() => null, {
416
- beforeUnload: true,
417
- });
418
- expect(session.addBeforeDestroyListener).to.have.been.calledOnce();
419
- // expect(window.addEventListener)
420
- // .to.have.been.calledOnce()
421
- // .and.to.have.been.called.with('beforeunload');
422
-
423
- const removeNavigationBlocker2 = addNavigationBlocker(() => null, {
424
- beforeUnload: true,
425
- });
426
- expect(session.addBeforeDestroyListener).to.have.been.calledOnce();
427
- // expect(window.addEventListener)
428
- // .to.have.been.calledOnce()
429
- // .and.to.have.been.called.with('beforeunload');
430
-
431
- const removeBeforeDestroyListener = sinon.stub();
432
- // eslint-disable-next-line no-underscore-dangle
433
- session._removeBeforeDestroyListener = removeBeforeDestroyListener;
434
-
435
- removeNavigationBlocker1();
436
- // expect(window.removeEventListener).not.to.have.been.called();
437
- expect(removeBeforeDestroyListener).not.to.have.been.called();
438
-
439
- removeNavigationBlocker2();
440
- // expect(window.removeEventListener)
441
- // .to.have.been.calledOnce()
442
- // .and.to.have.been.called.with('beforeunload');
443
- expect(removeBeforeDestroyListener).to.have.been.calledOnce();
444
- });
445
-
446
- it('should remove event blocker on dispose', () => {
447
- addNavigationBlocker(() => null, { beforeUnload: true });
448
-
449
- const removeBeforeDestroyListener = sinon.stub();
450
- // eslint-disable-next-line no-underscore-dangle
451
- session._removeBeforeDestroyListener = removeBeforeDestroyListener;
452
-
453
- // expect(window.removeEventListener).not.to.have.been.called();
454
- expect(removeBeforeDestroyListener).not.to.have.been.called();
455
-
456
- store.dispatch(Actions.dispose());
457
- // expect(window.removeEventListener)
458
- // .to.have.been.calledOnce()
459
- // .and.to.have.been.called.with('beforeunload');
460
- expect(removeBeforeDestroyListener).to.have.been.calledOnce();
461
- });
462
-
463
- // it('should not add a global "before destroy" listener when no `beforeDestroy` blocker has been added', () => {
464
- // const removeNavigationBlocker = addNavigationBlocker(() => null);
465
- // expect(window.addEventListener).not.to.have.been.called();
466
- //
467
- // removeNavigationBlocker();
468
- // expect(window.removeEventListener).not.to.have.been.called();
469
- // });
470
- });
471
- });
@@ -1,44 +0,0 @@
1
- import ActionTypes from '../../src/ActionTypes';
2
- import createTransformLocationMiddleware from '../../src/middleware/createTransformLocationMiddleware';
3
-
4
- describe('createTransformLocationMiddleware', () => {
5
- const middleware = createTransformLocationMiddleware({
6
- transformInputLocation: (locationInput) => ({ locationInput }),
7
- transformSubscriptionLocation: (location) => ({ location }),
8
- });
9
-
10
- const dispatch = middleware()((action) => action.payload);
11
-
12
- it('should handle location descriptors for NAVIGATE', () => {
13
- expect(
14
- dispatch({
15
- type: ActionTypes.NAVIGATE,
16
- payload: {},
17
- }),
18
- ).to.eql({
19
- locationInput: {},
20
- });
21
- });
22
-
23
- it('should handle locations for UPDATE', () => {
24
- expect(
25
- dispatch({
26
- type: ActionTypes.UPDATE,
27
- payload: {},
28
- }),
29
- ).to.eql({
30
- location: {},
31
- });
32
- });
33
-
34
- it('should ignore other actions', () => {
35
- expect(
36
- dispatch({
37
- type: 'UNKNOWN',
38
- payload: { unknown: {} },
39
- }),
40
- ).to.eql({
41
- unknown: {},
42
- });
43
- });
44
- });
@@ -1,182 +0,0 @@
1
- import delay from 'delay';
2
-
3
- import BrowserSession from '../../src/session/BrowserSession';
4
-
5
- describe('BrowserSession', () => {
6
- beforeEach(() => {
7
- window.history.replaceState(null, null, '/');
8
- });
9
-
10
- it('should parse the initial location', () => {
11
- window.history.replaceState(null, null, '/foo?bar=baz#qux');
12
- const session = new BrowserSession();
13
-
14
- expect(session.navigation.init()).to.deep.include({
15
- action: 'INIT',
16
- pathname: '/foo',
17
- search: '?bar=baz',
18
- query: {
19
- bar: 'baz',
20
- },
21
- hash: '#qux',
22
- index: 0,
23
- delta: 0,
24
- state: undefined,
25
- });
26
- });
27
-
28
- it('should require initialization', () => {
29
- const session = new BrowserSession();
30
-
31
- expect(() =>
32
- session.navigation.navigate({
33
- action: 'PUSH',
34
- pathname: '/bar',
35
- search: '?search',
36
- hash: '#hash',
37
- state: { the: 'state' },
38
- }),
39
- ).to.throw('Browser session must be initialized before navigation');
40
- });
41
-
42
- it('should support basic navigation', async () => {
43
- window.history.replaceState(null, null, '/foo');
44
- const session = new BrowserSession();
45
-
46
- const listener = sinon.spy();
47
- session.navigation.subscribe(listener);
48
-
49
- session.navigation.init();
50
-
51
- const barLocation = session.navigation.navigate({
52
- action: 'PUSH',
53
- pathname: '/bar',
54
- search: '?search',
55
- hash: '#hash',
56
- state: { the: 'state' },
57
- });
58
-
59
- expect(window.location).to.include({
60
- pathname: '/bar',
61
- search: '?search',
62
- hash: '#hash',
63
- });
64
- expect(barLocation).to.deep.include({
65
- action: 'PUSH',
66
- pathname: '/bar',
67
- search: '?search',
68
- hash: '#hash',
69
- index: 1,
70
- delta: 1,
71
- state: { the: 'state' },
72
- });
73
- expect(barLocation.key).not.to.be.empty();
74
-
75
- expect(
76
- session.navigation.navigate({
77
- action: 'PUSH',
78
- pathname: '/baz',
79
- search: '',
80
- hash: '',
81
- }),
82
- ).to.include({
83
- action: 'PUSH',
84
- pathname: '/baz',
85
- index: 2,
86
- delta: 1,
87
- });
88
-
89
- expect(window.location.pathname).to.equal('/baz');
90
-
91
- expect(
92
- session.navigation.navigate({
93
- action: 'REPLACE',
94
- pathname: '/qux',
95
- search: '',
96
- hash: '',
97
- }),
98
- ).to.include({
99
- action: 'REPLACE',
100
- pathname: '/qux',
101
- index: 2,
102
- delta: 0,
103
- });
104
- await delay(20);
105
-
106
- expect(window.location.pathname).to.equal('/qux');
107
- expect(listener).not.to.have.been.called();
108
-
109
- session.navigation.shift(-1);
110
- await delay(20);
111
-
112
- expect(window.location).to.include({
113
- pathname: '/bar',
114
- search: '?search',
115
- hash: '#hash',
116
- });
117
- expect(listener).to.have.been.calledOnce();
118
- expect(listener.firstCall.args[0]).to.deep.include({
119
- action: 'SHIFT',
120
- pathname: '/bar',
121
- search: '?search',
122
- hash: '#hash',
123
- key: barLocation.key,
124
- index: 1,
125
- delta: -1,
126
- state: { the: 'state' },
127
- });
128
- listener.resetHistory();
129
-
130
- window.history.back();
131
- await delay(20);
132
-
133
- expect(window.location.pathname).to.equal('/foo');
134
- expect(listener).to.have.been.calledOnce();
135
- expect(listener.firstCall.args[0]).to.deep.include({
136
- action: 'SHIFT',
137
- pathname: '/foo',
138
- index: 0,
139
- delta: -1,
140
- state: undefined,
141
- });
142
-
143
- listener.resetHistory();
144
- });
145
-
146
- it('should support subscribing and unsubscribing', async () => {
147
- const session = new BrowserSession();
148
- session.navigation.init();
149
- session.navigation.navigate({
150
- action: 'PUSH',
151
- pathname: '/bar',
152
- search: '',
153
- hash: '',
154
- });
155
- session.navigation.navigate({
156
- action: 'PUSH',
157
- pathname: '/baz',
158
- search: '',
159
- hash: '',
160
- });
161
-
162
- const listener = sinon.spy();
163
- const unsubscribe = session.navigation.subscribe(listener);
164
-
165
- session.navigation.shift(-1);
166
- await delay(20);
167
-
168
- expect(listener).to.have.been.calledOnce();
169
- expect(listener.firstCall.args[0]).to.include({
170
- action: 'SHIFT',
171
- pathname: '/bar',
172
- });
173
- listener.resetHistory();
174
-
175
- unsubscribe();
176
-
177
- session.navigation.shift(-1);
178
- await delay(20);
179
-
180
- expect(listener).not.to.have.been.called();
181
- });
182
- });