navigation-stack 0.5.3 → 0.6.1
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.
- package/CHANGELOG.md +16 -0
- package/README.md +144 -282
- package/karma.conf.cjs +1 -1
- package/lib/cjs/NavigationStack.js +138 -49
- package/lib/cjs/data-storage/DataStorage.js +7 -6
- package/lib/cjs/environment/InMemoryEnvironment.js +6 -0
- package/lib/cjs/{session/ServerSideRenderSession.js → environment/ServerSideRenderEnvironment.js} +5 -6
- package/lib/cjs/environment/WebBrowserEnvironment.js +6 -0
- package/lib/cjs/environment/log/InMemoryLog.js +23 -0
- package/lib/cjs/environment/log/WebBrowserLog.js +22 -0
- package/lib/cjs/{session → environment}/navigation/InMemoryNavigation.js +16 -5
- package/lib/cjs/{session → environment}/navigation/ServerSideNavigation.js +16 -7
- package/lib/cjs/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
- package/lib/cjs/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +2 -2
- package/lib/cjs/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
- package/lib/cjs/getLocationBaseFromLocation.js +14 -0
- package/lib/cjs/getLocationUrl.js +3 -5
- package/lib/cjs/index.js +10 -16
- package/lib/cjs/navigationBlockers.js +34 -32
- package/lib/cjs/navigationBlockersEvaluation.js +150 -0
- package/lib/cjs/parseInputLocation.js +2 -2
- package/lib/cjs/parseQueryFromSearch.js +3 -6
- package/lib/cjs/parseQueryString.js +77 -0
- package/lib/cjs/scroll-position/ScrollPositionAutoSaver.js +7 -6
- package/lib/cjs/scroll-position/ScrollPositionRestoration.js +31 -27
- package/lib/cjs/scroll-position/ScrollPositionSaver.js +6 -4
- package/lib/cjs/session/Session.js +61 -26
- package/lib/cjs/session/subscription/Subscription.js +36 -18
- package/lib/cjs/stringifyQuery.js +66 -0
- package/lib/cjs/stringifyQueryAsSearch.js +14 -0
- package/lib/esm/NavigationStack.js +138 -49
- package/lib/esm/data-storage/DataStorage.js +7 -6
- package/lib/esm/environment/InMemoryEnvironment.js +6 -0
- package/lib/esm/environment/ServerSideRenderEnvironment.js +10 -0
- package/lib/esm/environment/WebBrowserEnvironment.js +6 -0
- package/lib/esm/environment/log/InMemoryLog.js +17 -0
- package/lib/esm/environment/log/WebBrowserLog.js +16 -0
- package/lib/esm/{session → environment}/navigation/InMemoryNavigation.js +16 -5
- package/lib/esm/{session → environment}/navigation/ServerSideNavigation.js +16 -7
- package/lib/esm/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
- package/lib/esm/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +1 -1
- package/lib/esm/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
- package/lib/esm/getLocationBaseFromLocation.js +9 -0
- package/lib/esm/getLocationUrl.js +2 -5
- package/lib/esm/index.js +5 -8
- package/lib/esm/navigationBlockers.js +34 -32
- package/lib/esm/navigationBlockersEvaluation.js +145 -0
- package/lib/esm/parseInputLocation.js +2 -2
- package/lib/esm/parseQueryFromSearch.js +2 -6
- package/lib/esm/parseQueryString.js +72 -0
- package/lib/esm/scroll-position/ScrollPositionAutoSaver.js +7 -6
- package/lib/esm/scroll-position/ScrollPositionRestoration.js +31 -27
- package/lib/esm/scroll-position/ScrollPositionSaver.js +6 -4
- package/lib/esm/session/Session.js +61 -26
- package/lib/esm/session/subscription/Subscription.js +36 -18
- package/lib/esm/stringifyQuery.js +61 -0
- package/lib/esm/stringifyQueryAsSearch.js +8 -0
- package/lib/index.d.ts +180 -34
- package/package.json +4 -7
- package/src/NavigationStack.js +166 -56
- package/src/data-storage/DataStorage.js +9 -6
- package/src/environment/InMemoryEnvironment.js +6 -0
- package/src/environment/ServerSideRenderEnvironment.js +10 -0
- package/src/environment/WebBrowserEnvironment.js +6 -0
- package/src/environment/log/InMemoryLog.js +20 -0
- package/src/environment/log/WebBrowserLog.js +18 -0
- package/src/{session → environment}/navigation/InMemoryNavigation.js +16 -5
- package/src/{session → environment}/navigation/ServerSideNavigation.js +16 -7
- package/src/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
- package/src/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +1 -1
- package/src/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
- package/src/getLocationBaseFromLocation.js +7 -0
- package/src/getLocationUrl.js +2 -5
- package/src/index.js +10 -13
- package/src/navigationBlockers.js +55 -34
- package/src/navigationBlockersEvaluation.js +161 -0
- package/src/parseInputLocation.js +2 -2
- package/src/parseQueryFromSearch.js +2 -6
- package/src/parseQueryString.js +81 -0
- package/src/scroll-position/ScrollPositionAutoSaver.js +10 -6
- package/src/scroll-position/ScrollPositionRestoration.js +36 -30
- package/src/scroll-position/ScrollPositionSaver.js +6 -4
- package/src/scroll-position/index.js +1 -1
- package/src/session/Session.js +68 -24
- package/src/session/subscription/Subscription.js +36 -11
- package/src/stringifyQuery.js +71 -0
- package/src/stringifyQueryAsSearch.js +9 -0
- package/test/NavigationStack.addBasePath.test.js +50 -0
- package/test/{redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.test.js → NavigationStack.blockNonProgrammaticNavigationIfRequired.test.js} +51 -63
- package/test/{redux/middleware/createProgrammaticNavigationBlockerMiddleware.test.js → NavigationStack.blockProgrammaticNavigationIfRequired.test.js} +98 -78
- package/test/NavigationStack.general.test.js +68 -0
- package/test/NavigationStack.parseInputLocation.test.js +52 -0
- package/test/NavigationStack.removeBasePath.test.js +69 -0
- package/test/NavigationStack.test.js +97 -29
- package/test/data-storage/LocationDataStorage.test.js +3 -2
- package/test/index.js +7 -31
- package/test/index.test.js +4 -5
- package/test/parseQueryFromSearch.test.js +19 -0
- package/test/parseQueryString.test.js +18 -0
- package/test/scroll-position/ScrollPositionRestoration.test.js +34 -13
- package/test/scroll-position/createApp.js +8 -8
- package/test/scroll-position/withScrollableContainerAtIndexPageWithDisabledAutomaticScrollPositionRestoration.js +4 -4
- package/test/session/{InMemorySession.test.js → Session.InMemoryEnvironment.test.js} +10 -9
- package/test/session/{ServerSession.test.js → Session.ServerSideRenderEnvironment.test.js} +5 -4
- package/test/session/{WebBrowserSession.test.js → Session.WebBrowserEnvironment.test.js} +63 -13
- package/test/shouldWarn.js +44 -0
- package/test/stringifyQuery.test.js +65 -0
- package/types/index.d.ts +180 -34
- package/types/tsconfig.json +0 -1
- package/data-storage/package.json +0 -7
- package/lib/cjs/createSearchFromQuery.js +0 -13
- package/lib/cjs/debug.js +0 -12
- package/lib/cjs/redux/ActionTypes.js +0 -14
- package/lib/cjs/redux/ActionTypesInternal.js +0 -8
- package/lib/cjs/redux/Actions.js +0 -28
- package/lib/cjs/redux/createMiddlewares.js +0 -60
- package/lib/cjs/redux/index.js +0 -13
- package/lib/cjs/redux/internalLocationReducer.js +0 -14
- package/lib/cjs/redux/locationReducer.js +0 -13
- package/lib/cjs/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -32
- package/lib/cjs/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -113
- package/lib/cjs/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -94
- package/lib/cjs/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -30
- package/lib/cjs/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -73
- package/lib/cjs/redux/middleware/navigationOperationMiddleware.js +0 -40
- package/lib/cjs/redux/middleware/parseInputLocationMiddleware.js +0 -29
- package/lib/cjs/redux/middleware/updateLocationMiddleware.js +0 -34
- package/lib/cjs/session/InMemorySession.js +0 -22
- package/lib/cjs/session/WebBrowserSession.js +0 -20
- package/lib/data-storage/index.d.ts +0 -35
- package/lib/esm/createSearchFromQuery.js +0 -8
- package/lib/esm/debug.js +0 -7
- package/lib/esm/redux/ActionTypes.js +0 -9
- package/lib/esm/redux/ActionTypesInternal.js +0 -3
- package/lib/esm/redux/Actions.js +0 -22
- package/lib/esm/redux/createMiddlewares.js +0 -54
- package/lib/esm/redux/index.js +0 -4
- package/lib/esm/redux/internalLocationReducer.js +0 -8
- package/lib/esm/redux/locationReducer.js +0 -7
- package/lib/esm/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -27
- package/lib/esm/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -108
- package/lib/esm/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -88
- package/lib/esm/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -25
- package/lib/esm/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -68
- package/lib/esm/redux/middleware/navigationOperationMiddleware.js +0 -35
- package/lib/esm/redux/middleware/parseInputLocationMiddleware.js +0 -24
- package/lib/esm/redux/middleware/updateLocationMiddleware.js +0 -28
- package/lib/esm/session/InMemorySession.js +0 -15
- package/lib/esm/session/ServerSideRenderSession.js +0 -11
- package/lib/esm/session/WebBrowserSession.js +0 -13
- package/lib/redux/index.d.ts +0 -90
- package/lib/scroll-position/index.d.ts +0 -107
- package/redux/package.json +0 -7
- package/scroll-position/package.json +0 -7
- package/src/createSearchFromQuery.js +0 -9
- package/src/debug.js +0 -8
- package/src/redux/ActionTypes.js +0 -9
- package/src/redux/ActionTypesInternal.js +0 -3
- package/src/redux/Actions.js +0 -27
- package/src/redux/createMiddlewares.js +0 -65
- package/src/redux/index.js +0 -4
- package/src/redux/internalLocationReducer.js +0 -9
- package/src/redux/locationReducer.js +0 -8
- package/src/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -27
- package/src/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -119
- package/src/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -94
- package/src/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -26
- package/src/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -72
- package/src/redux/middleware/navigationOperationMiddleware.js +0 -34
- package/src/redux/middleware/parseInputLocationMiddleware.js +0 -23
- package/src/redux/middleware/updateLocationMiddleware.js +0 -28
- package/src/session/InMemorySession.js +0 -13
- package/src/session/ServerSideRenderSession.js +0 -9
- package/src/session/WebBrowserSession.js +0 -13
- package/test/middlewareTestUtil.js +0 -31
- package/test/redux/Action.test.js +0 -73
- package/test/redux/ActionTypes.test.js +0 -13
- package/test/redux/createMiddlewares.test.js +0 -96
- package/test/redux/index.test.js +0 -10
- package/test/redux/locationReducer.test.js +0 -39
- package/test/redux/middleware/createAddInputLocationBasePathMiddleware.test.js +0 -40
- package/test/redux/middleware/createRemoveOutputLocationBasePathMiddleware.test.js +0 -51
- package/test/redux/middleware/navigationOperationMiddleware.test.js +0 -78
- package/test/redux/middleware/parseInputLocationMiddleware.test.js +0 -62
- package/test/testUtil.js +0 -3
- package/types/data-storage/index.d.ts +0 -35
- package/types/redux/index.d.ts +0 -90
- package/types/scroll-position/index.d.ts +0 -107
- /package/lib/cjs/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
- /package/lib/cjs/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
- /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
- /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
- /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
- /package/lib/cjs/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
- /package/lib/cjs/{session → environment}/navigation/operation/operations.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
- /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
- /package/lib/esm/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
- /package/lib/esm/{session → environment}/navigation/operation/operations.js +0 -0
- /package/src/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
- /package/src/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
- /package/src/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
- /package/src/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
- /package/src/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
- /package/src/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
- /package/src/{session → environment}/navigation/operation/operations.js +0 -0
- /package/test/{parseInputLocationMiddleware.test.js → parseInputLocation.test.js} +0 -0
package/src/session/Session.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import debug from '../debug';
|
|
2
1
|
import parseInputLocation from '../parseInputLocation';
|
|
3
2
|
import createSessionKey from './key/createSessionKey';
|
|
4
|
-
import NavigationOutOfBoundsError from './navigation/error/NavigationOutOfBoundsError';
|
|
5
|
-
import NavigationOperations from './navigation/operation/operations';
|
|
6
3
|
import Subscription from './subscription/Subscription';
|
|
4
|
+
import NavigationOutOfBoundsError from '../environment/navigation/error/NavigationOutOfBoundsError';
|
|
5
|
+
import NavigationOperations from '../environment/navigation/operation/operations';
|
|
7
6
|
|
|
8
7
|
const INITIAL_KEY_INDEX = -1;
|
|
9
8
|
const INITIAL_INDEX = -1;
|
|
@@ -11,12 +10,15 @@ const INITIAL_INDEX = -1;
|
|
|
11
10
|
const INIT_LOCATION_DELTA = 0;
|
|
12
11
|
|
|
13
12
|
export default class Session {
|
|
14
|
-
constructor(
|
|
13
|
+
constructor(EnvironmentClass) {
|
|
15
14
|
// `key` is used in `WebBrowserSession` to uniquely identify a session
|
|
16
15
|
// when storing data in a `WebBrowserDataStorage` which uses `window.sessionStorage`
|
|
17
16
|
// under the hood, and `window.sessionStorage` is shared between different sessions.
|
|
18
17
|
this.key = createSessionKey();
|
|
19
18
|
|
|
19
|
+
// Create an environment instance.
|
|
20
|
+
this.environment = new EnvironmentClass();
|
|
21
|
+
|
|
20
22
|
// `this._locationKeyIndex` is incremented every time the current location changes.
|
|
21
23
|
this._locationKeyIndex = INITIAL_KEY_INDEX;
|
|
22
24
|
|
|
@@ -28,18 +30,36 @@ export default class Session {
|
|
|
28
30
|
// In other words, this is the last location index that it can `.shift()` to.
|
|
29
31
|
this._terminalLocationIndex = this._currentLocationIndex;
|
|
30
32
|
|
|
31
|
-
//
|
|
32
|
-
this.
|
|
33
|
+
// Allows subscribing to location updates.
|
|
34
|
+
this._subscription = new Subscription();
|
|
33
35
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
// Subscribing to location changes means subscribing to both "synchronous"
|
|
37
|
+
// and "asynchronous" location changes. "synchronous" location changes
|
|
38
|
+
// happen immediately when the code triggers them."asynchronous" location changes
|
|
39
|
+
// either happen after an arbitrary delay or are even triggered from outside the code.
|
|
40
|
+
//
|
|
41
|
+
// Subscribing to "asynchronous" location changes is not necessary when
|
|
42
|
+
// there're no actual subscribers, in order to not unnecessarily "waste" any resources.
|
|
43
|
+
// Of course, this statement is rather far-fetched and in reality no one would ever tell any difference.
|
|
44
|
+
// Still, I felt like randomly introducing this seemingly unnecessary minor optimization.
|
|
45
|
+
//
|
|
46
|
+
// So it only subscribes to "asynchronous" location changes if there's at least one active subscriber.
|
|
47
|
+
// And in case all subscribers get unsubscribed, it will unsubscribe from "asynchronous" location changes too.
|
|
48
|
+
// One might think of it as some form of "mental masturbation", but what can I do — I already wrote the code.
|
|
49
|
+
//
|
|
50
|
+
this._subscription.onFirstSubscriber(() => {
|
|
51
|
+
return this.environment.navigation.subscribeToAsyncrhonousLocationUpdates(
|
|
52
|
+
(location) => {
|
|
53
|
+
// Notify all subscribers about this "asynchronous" location change.
|
|
54
|
+
this._subscription.notifySubscribers(location);
|
|
55
|
+
},
|
|
56
|
+
);
|
|
39
57
|
});
|
|
40
58
|
|
|
41
|
-
//
|
|
42
|
-
//
|
|
59
|
+
// This subscription is triggered in two cases:
|
|
60
|
+
// * Set initial current location index at initial page load.
|
|
61
|
+
// * Update current location index whenever a location change is not initiated
|
|
62
|
+
// by this session but rather by the user clicking "Back" or "Forward" button.
|
|
43
63
|
this._unsubscribe = this.subscribe((location) => {
|
|
44
64
|
// Update `this._currentLocationIndex` when the location change was not initiated
|
|
45
65
|
// by this session but rather by the user clicking "Back" or "Forward" button.
|
|
@@ -50,7 +70,7 @@ export default class Session {
|
|
|
50
70
|
// by `navigation` to call `session.getNextKey()` function to increment `locationKeyIndex`.
|
|
51
71
|
this._updateTerminalLocationIndex(location);
|
|
52
72
|
|
|
53
|
-
debug(
|
|
73
|
+
this.environment.log.debug(
|
|
54
74
|
'current location',
|
|
55
75
|
location.pathname,
|
|
56
76
|
'index',
|
|
@@ -60,14 +80,18 @@ export default class Session {
|
|
|
60
80
|
}
|
|
61
81
|
|
|
62
82
|
// Subscribes to changes in location.
|
|
83
|
+
// The first subscriber is always the `Session` itself:
|
|
84
|
+
// its listener keeps the current location index up-to-date.
|
|
85
|
+
// Any additional application-specific listeners could be added, if required.
|
|
86
|
+
// Applications should prefer adding any such listeners by calling `NavigationStack.subscribe()`
|
|
87
|
+
// method instead of calling this method directly, in order to "normalize" the `location` argument.
|
|
63
88
|
subscribe(listener) {
|
|
64
89
|
return this._subscription.subscribe((location) => {
|
|
65
90
|
if (
|
|
66
91
|
!this._isStarted() &&
|
|
67
92
|
location.operation !== NavigationOperations.INIT
|
|
68
93
|
) {
|
|
69
|
-
|
|
70
|
-
console.error('Unexpected location change', location);
|
|
94
|
+
this.environment.log.error('Unexpected location change', location);
|
|
71
95
|
throw new Error('Not started');
|
|
72
96
|
} else {
|
|
73
97
|
// Call the listener.
|
|
@@ -76,6 +100,17 @@ export default class Session {
|
|
|
76
100
|
});
|
|
77
101
|
}
|
|
78
102
|
|
|
103
|
+
// Starts a navigation session.
|
|
104
|
+
//
|
|
105
|
+
// When run in a web browser, it could not only "start" a new session
|
|
106
|
+
// but also "resume" a previously-started session. That could happen
|
|
107
|
+
// when the user refreshes a page in a web browser which still retains
|
|
108
|
+
// the previous session's data but at the same time restarts the javascript code
|
|
109
|
+
// from scratch.
|
|
110
|
+
//
|
|
111
|
+
// So this `start()` method handles both cases: when there's previous session's data
|
|
112
|
+
// that should be restored and when there's no previous session's data.
|
|
113
|
+
//
|
|
79
114
|
start(initialLocation) {
|
|
80
115
|
if (this._stopped) {
|
|
81
116
|
throw new Error('Can not be restarted');
|
|
@@ -88,7 +123,7 @@ export default class Session {
|
|
|
88
123
|
// the initial location by the time javascript code starts execution.
|
|
89
124
|
//
|
|
90
125
|
if (!initialLocation) {
|
|
91
|
-
initialLocation = this.
|
|
126
|
+
initialLocation = this.environment.navigation.getInitialLocation();
|
|
92
127
|
if (initialLocation) {
|
|
93
128
|
initialLocation = parseInputLocation(initialLocation);
|
|
94
129
|
}
|
|
@@ -102,7 +137,7 @@ export default class Session {
|
|
|
102
137
|
throw new Error('Already started');
|
|
103
138
|
}
|
|
104
139
|
|
|
105
|
-
debug('▶ start session', initialLocation.pathname);
|
|
140
|
+
this.environment.log.debug('▶ start session', initialLocation.pathname);
|
|
106
141
|
|
|
107
142
|
this._started = true;
|
|
108
143
|
|
|
@@ -110,7 +145,7 @@ export default class Session {
|
|
|
110
145
|
const index = INITIAL_INDEX + 1;
|
|
111
146
|
const delta = INIT_LOCATION_DELTA;
|
|
112
147
|
|
|
113
|
-
const locationResult = this.
|
|
148
|
+
const locationResult = this.environment.navigation.init(initialLocation, {
|
|
114
149
|
operation: NavigationOperations.INIT,
|
|
115
150
|
key,
|
|
116
151
|
index,
|
|
@@ -118,6 +153,7 @@ export default class Session {
|
|
|
118
153
|
});
|
|
119
154
|
|
|
120
155
|
if (locationResult) {
|
|
156
|
+
// Notify all subscribers about this "synchronous" location change.
|
|
121
157
|
this._subscription.notifySubscribers(locationResult);
|
|
122
158
|
}
|
|
123
159
|
}
|
|
@@ -127,7 +163,7 @@ export default class Session {
|
|
|
127
163
|
throw Error('Already stopped');
|
|
128
164
|
}
|
|
129
165
|
|
|
130
|
-
debug('⏹ stop session');
|
|
166
|
+
this.environment.log.debug('⏹ stop session');
|
|
131
167
|
|
|
132
168
|
// Once stopped, it won't be able to be restarted.
|
|
133
169
|
this._stopped = true;
|
|
@@ -160,7 +196,7 @@ export default class Session {
|
|
|
160
196
|
const key = this._getNextLocationKey();
|
|
161
197
|
const index = this._currentLocationIndex + delta;
|
|
162
198
|
|
|
163
|
-
debug(
|
|
199
|
+
this.environment.log.debug(
|
|
164
200
|
operation === NavigationOperations.PUSH ? '↓' : '⇅',
|
|
165
201
|
operation,
|
|
166
202
|
location.pathname,
|
|
@@ -169,7 +205,7 @@ export default class Session {
|
|
|
169
205
|
);
|
|
170
206
|
|
|
171
207
|
// Navigate to the location.
|
|
172
|
-
const locationResult = this.
|
|
208
|
+
const locationResult = this.environment.navigation.navigate(location, {
|
|
173
209
|
operation,
|
|
174
210
|
key,
|
|
175
211
|
index,
|
|
@@ -177,6 +213,7 @@ export default class Session {
|
|
|
177
213
|
});
|
|
178
214
|
|
|
179
215
|
if (locationResult) {
|
|
216
|
+
// Notify all subscribers about this "synchronous" location change.
|
|
180
217
|
this._subscription.notifySubscribers(locationResult);
|
|
181
218
|
}
|
|
182
219
|
}
|
|
@@ -193,7 +230,13 @@ export default class Session {
|
|
|
193
230
|
|
|
194
231
|
const index = this._currentLocationIndex + delta;
|
|
195
232
|
|
|
196
|
-
debug(
|
|
233
|
+
this.environment.log.debug(
|
|
234
|
+
delta > 0 ? '→' : '←',
|
|
235
|
+
'shift',
|
|
236
|
+
delta,
|
|
237
|
+
'index',
|
|
238
|
+
index,
|
|
239
|
+
);
|
|
197
240
|
|
|
198
241
|
// Validate that the new `index` is not out of bounds.
|
|
199
242
|
if (index < 0 || index > this._terminalLocationIndex) {
|
|
@@ -201,13 +244,14 @@ export default class Session {
|
|
|
201
244
|
}
|
|
202
245
|
|
|
203
246
|
// Navigate to the location.
|
|
204
|
-
const locationResult = this.
|
|
247
|
+
const locationResult = this.environment.navigation.shift({
|
|
205
248
|
operation: NavigationOperations.SHIFT,
|
|
206
249
|
index,
|
|
207
250
|
delta,
|
|
208
251
|
});
|
|
209
252
|
|
|
210
253
|
if (locationResult) {
|
|
254
|
+
// Notify all subscribers about this "synchronous" location change.
|
|
211
255
|
this._subscription.notifySubscribers(locationResult);
|
|
212
256
|
}
|
|
213
257
|
}
|
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
export default class Subscription {
|
|
2
|
-
constructor(
|
|
3
|
-
this._activateSubscription = activateSubscription;
|
|
4
|
-
|
|
2
|
+
constructor() {
|
|
5
3
|
// This property is accessed in tests.
|
|
6
4
|
this._listeners = [];
|
|
5
|
+
|
|
6
|
+
// These listeners will be called when the subscription enters "active" or "inactive" state.
|
|
7
|
+
// A subscription enters "active" state when it has at least one listener rather than zero.
|
|
8
|
+
// A subscription enters "inactive" state when it has no more listeners.
|
|
9
|
+
this._subscriptionActiveStateListeners = [];
|
|
10
|
+
this._subscriptionInactiveStateListeners = [];
|
|
7
11
|
}
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
// Adds a subscription active state listener.
|
|
14
|
+
// Returns a function that removes the subscription active state listener.
|
|
15
|
+
onFirstSubscriber(activeStateListener) {
|
|
16
|
+
this._subscriptionActiveStateListeners.push(activeStateListener);
|
|
17
|
+
// Return a function that removes the subscription active state listener.
|
|
18
|
+
return () => {
|
|
19
|
+
this._subscriptionActiveStateListeners =
|
|
20
|
+
this._subscriptionActiveStateListeners.filter(
|
|
21
|
+
(_) => _ !== activeStateListener,
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
notifySubscribers(argument) {
|
|
10
27
|
// `._latest` is only used in tests.
|
|
11
28
|
this._latest = argument;
|
|
12
29
|
for (const { listener } of this._listeners) {
|
|
13
30
|
listener(argument);
|
|
14
31
|
}
|
|
15
|
-
}
|
|
32
|
+
}
|
|
16
33
|
|
|
17
34
|
subscribe(listener) {
|
|
18
35
|
// If subscriptions are stopped, i.e. no new subscriptions are to be added,
|
|
@@ -30,9 +47,12 @@ export default class Subscription {
|
|
|
30
47
|
|
|
31
48
|
// If it's the first listener, activate subscription.
|
|
32
49
|
if (this._listeners.length === 0) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
50
|
+
// Run all subscription active state listeners.
|
|
51
|
+
// The functions returned from those will become subscription inactive state listeners.
|
|
52
|
+
this._subscriptionInactiveStateListeners =
|
|
53
|
+
this._subscriptionActiveStateListeners.map((activeStateListener) =>
|
|
54
|
+
activeStateListener(),
|
|
55
|
+
);
|
|
36
56
|
}
|
|
37
57
|
|
|
38
58
|
// Add the `listener` to the list.
|
|
@@ -67,10 +87,15 @@ export default class Subscription {
|
|
|
67
87
|
// Remove the `listener` from the list.
|
|
68
88
|
this._listeners = this._listeners.filter((_) => _ !== listenerEntry);
|
|
69
89
|
|
|
70
|
-
// If it was the last listener
|
|
90
|
+
// If it was the last listener.
|
|
71
91
|
if (this._listeners.length === 0) {
|
|
72
|
-
|
|
73
|
-
|
|
92
|
+
// Run any subscription inactive state listeners,
|
|
93
|
+
// after which clear the list of such listeners.
|
|
94
|
+
for (const inactiveStateListener of this
|
|
95
|
+
._subscriptionInactiveStateListeners) {
|
|
96
|
+
inactiveStateListener();
|
|
97
|
+
}
|
|
98
|
+
this._subscriptionInactiveStateListeners = [];
|
|
74
99
|
}
|
|
75
100
|
}
|
|
76
101
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// "The more recent RFC3986 reserves !, ', (, ), and *,
|
|
2
|
+
// even though these characters have no formalized URI delimiting uses.
|
|
3
|
+
//
|
|
4
|
+
// https://datatracker.ietf.org/doc/html/rfc3986
|
|
5
|
+
//
|
|
6
|
+
// The following function encodes a string for RFC3986-compliant URL component format.
|
|
7
|
+
// It also encodes [ and ], which are part of the IPv6 URI syntax.
|
|
8
|
+
//
|
|
9
|
+
// An RFC3986-compliant encodeURI implementation should not escape them,
|
|
10
|
+
// which is demonstrated in the encodeURI() example.
|
|
11
|
+
//
|
|
12
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#encoding_for_rfc3986
|
|
13
|
+
//
|
|
14
|
+
// Can throw a `URIError` if the `string` contains a "lone surrogate".
|
|
15
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#utf-16_characters_unicode_code_points_and_grapheme_clusters
|
|
16
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError
|
|
17
|
+
// Example: "URIError: malformed URI sequence"
|
|
18
|
+
//
|
|
19
|
+
function encode(string) {
|
|
20
|
+
return encodeURIComponent(string).replace(
|
|
21
|
+
/[!'()*]/g,
|
|
22
|
+
(character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function stringifyQuery(query) {
|
|
27
|
+
let queryString = '';
|
|
28
|
+
|
|
29
|
+
if (!query) {
|
|
30
|
+
return queryString;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const key of Object.keys(query)) {
|
|
34
|
+
let value = query[key];
|
|
35
|
+
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
throw new Error('Array values are not supported');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Ignore `value: undefined`.
|
|
41
|
+
if (value === undefined) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Stringify `value`.
|
|
46
|
+
if (value === null) {
|
|
47
|
+
value = '';
|
|
48
|
+
} else {
|
|
49
|
+
value = String(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Can throw a `URIError` if the `string` contains a "lone surrogate".
|
|
53
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#utf-16_characters_unicode_code_points_and_grapheme_clusters
|
|
54
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError
|
|
55
|
+
// Example: "URIError: malformed URI sequence"
|
|
56
|
+
try {
|
|
57
|
+
const keyValuePair = `${encode(key)}${value ? '=' : ''}${encode(value)}`;
|
|
58
|
+
|
|
59
|
+
if (queryString.length > 1) {
|
|
60
|
+
queryString += '&';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
queryString += keyValuePair;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
// Simply ignore an invalid query parameter.
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return queryString;
|
|
71
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import NavigationStack from '../src/NavigationStack';
|
|
2
|
+
import InMemoryEnvironment from '../src/environment/InMemoryEnvironment';
|
|
3
|
+
|
|
4
|
+
describe('NavigationStack (addBasePath)', () => {
|
|
5
|
+
it('should add `basePath` to `location.pathname`', () => {
|
|
6
|
+
const navigationStack = new NavigationStack(InMemoryEnvironment, {
|
|
7
|
+
basePath: '/base',
|
|
8
|
+
});
|
|
9
|
+
navigationStack.init('/path');
|
|
10
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
11
|
+
expect(navigationStack._session._subscription._latest.pathname).to.equal(
|
|
12
|
+
'/base/path',
|
|
13
|
+
);
|
|
14
|
+
navigationStack.stop();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should add `basePath` (with a trailing slash) to `location.pathname`', () => {
|
|
18
|
+
const navigationStack = new NavigationStack(InMemoryEnvironment, {
|
|
19
|
+
basePath: '/base/',
|
|
20
|
+
});
|
|
21
|
+
navigationStack.init('/path');
|
|
22
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
23
|
+
expect(navigationStack._session._subscription._latest.pathname).to.equal(
|
|
24
|
+
'/base/path',
|
|
25
|
+
);
|
|
26
|
+
navigationStack.stop();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should not modify `location.pathname` when no `basePath` was specified', () => {
|
|
30
|
+
const navigationStack = new NavigationStack(InMemoryEnvironment);
|
|
31
|
+
navigationStack.init('/path');
|
|
32
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
33
|
+
expect(navigationStack._session._subscription._latest.pathname).to.equal(
|
|
34
|
+
'/path',
|
|
35
|
+
);
|
|
36
|
+
navigationStack.stop();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should not modify `location.pathname` when `basePath: "/"` was specified', () => {
|
|
40
|
+
const navigationStack = new NavigationStack(InMemoryEnvironment, {
|
|
41
|
+
basePath: '/',
|
|
42
|
+
});
|
|
43
|
+
navigationStack.init('/path');
|
|
44
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
45
|
+
expect(navigationStack._session._subscription._latest.pathname).to.equal(
|
|
46
|
+
'/path',
|
|
47
|
+
);
|
|
48
|
+
navigationStack.stop();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -1,56 +1,48 @@
|
|
|
1
1
|
import delay from 'delay';
|
|
2
2
|
import pDefer from 'p-defer';
|
|
3
|
-
import { applyMiddleware, createStore } from 'redux';
|
|
4
3
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import internalLocationReducer from '../../../src/redux/internalLocationReducer';
|
|
9
|
-
import InMemorySession from '../../../src/session/InMemorySession';
|
|
4
|
+
import NavigationStack from '../src/NavigationStack';
|
|
5
|
+
import addNavigationBlockerOriginal from '../src/addNavigationBlocker';
|
|
6
|
+
import InMemoryEnvironment from '../src/environment/InMemoryEnvironment';
|
|
10
7
|
|
|
11
|
-
describe('
|
|
8
|
+
describe('NavigationStack (blockNonProgrammaticNavigationIfRequired)', () => {
|
|
12
9
|
// const sandbox = sinon.createSandbox();
|
|
13
10
|
|
|
14
11
|
let session;
|
|
15
|
-
let
|
|
12
|
+
let navigationStack;
|
|
16
13
|
|
|
17
14
|
function addNavigationBlocker(blocker) {
|
|
18
15
|
return addNavigationBlockerOriginal(session, blocker);
|
|
19
16
|
}
|
|
20
17
|
|
|
21
18
|
beforeEach(() => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
),
|
|
31
|
-
);
|
|
32
|
-
store.dispatch(Actions.init('/initial'));
|
|
33
|
-
|
|
34
|
-
sinon.spy(session.lifecycle, 'addTerminationBlocker');
|
|
19
|
+
navigationStack = new NavigationStack(InMemoryEnvironment);
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
22
|
+
session = navigationStack._session;
|
|
23
|
+
|
|
24
|
+
navigationStack.init('/initial');
|
|
25
|
+
|
|
26
|
+
sinon.spy(session.environment.lifecycle, 'addTerminationBlocker');
|
|
35
27
|
});
|
|
36
28
|
|
|
37
29
|
afterEach(() => {
|
|
38
|
-
|
|
30
|
+
navigationStack.stop();
|
|
39
31
|
|
|
40
32
|
// sandbox.restore();
|
|
41
33
|
});
|
|
42
34
|
|
|
43
35
|
describe('shift navigation', () => {
|
|
44
36
|
beforeEach(() => {
|
|
45
|
-
|
|
37
|
+
navigationStack.push('/new');
|
|
46
38
|
});
|
|
47
39
|
|
|
48
40
|
it('should allow navigation when blocker returns `undefined`', () => {
|
|
49
41
|
const blocker = sinon.stub().returns(undefined);
|
|
50
42
|
addNavigationBlocker(blocker);
|
|
51
43
|
|
|
52
|
-
|
|
53
|
-
expect(
|
|
44
|
+
navigationStack.shift(-1);
|
|
45
|
+
expect(navigationStack.current().pathname).to.equal('/initial');
|
|
54
46
|
|
|
55
47
|
expect(blocker.firstCall.args[0]).to.include({
|
|
56
48
|
// operation: 'shift',
|
|
@@ -62,34 +54,34 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
|
|
|
62
54
|
it('should block navigation when blocker returns `true`', () => {
|
|
63
55
|
addNavigationBlocker(() => true);
|
|
64
56
|
|
|
65
|
-
|
|
66
|
-
expect(
|
|
57
|
+
navigationStack.shift(-1);
|
|
58
|
+
expect(navigationStack.current().pathname).to.equal('/new');
|
|
67
59
|
});
|
|
68
60
|
|
|
69
61
|
it('should allow navigation when blocker returns `undefined` (async)', async () => {
|
|
70
62
|
const navigationBlockerDeferred = pDefer();
|
|
71
63
|
addNavigationBlocker(() => navigationBlockerDeferred.promise);
|
|
72
64
|
|
|
73
|
-
|
|
74
|
-
expect(
|
|
65
|
+
navigationStack.shift(-1);
|
|
66
|
+
expect(navigationStack.current().pathname).to.equal('/new');
|
|
75
67
|
|
|
76
68
|
navigationBlockerDeferred.resolve(undefined);
|
|
77
69
|
await delay(10);
|
|
78
70
|
|
|
79
|
-
expect(
|
|
71
|
+
expect(navigationStack.current().pathname).to.equal('/initial');
|
|
80
72
|
});
|
|
81
73
|
|
|
82
74
|
it('should block navigation when blocker returns `true` (async)', async () => {
|
|
83
75
|
const navigationBlockerDeferred = pDefer();
|
|
84
76
|
addNavigationBlocker(() => navigationBlockerDeferred.promise);
|
|
85
77
|
|
|
86
|
-
|
|
87
|
-
expect(
|
|
78
|
+
navigationStack.shift(-1);
|
|
79
|
+
expect(navigationStack.current().pathname).to.equal('/new');
|
|
88
80
|
|
|
89
81
|
navigationBlockerDeferred.resolve(true);
|
|
90
82
|
await delay(10);
|
|
91
83
|
|
|
92
|
-
expect(
|
|
84
|
+
expect(navigationStack.current().pathname).to.equal('/new');
|
|
93
85
|
});
|
|
94
86
|
|
|
95
87
|
// it('should show a confirmation dialog and allow navigation on string', () => {
|
|
@@ -97,33 +89,14 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
|
|
|
97
89
|
//
|
|
98
90
|
// addNavigationBlocker(({ pathname }) => pathname);
|
|
99
91
|
//
|
|
100
|
-
//
|
|
101
|
-
// expect(
|
|
92
|
+
// navigationStack.shift(-1));
|
|
93
|
+
// expect(navigationStack.current().pathname).to.equal('/initial');
|
|
102
94
|
//
|
|
103
95
|
// expect(window.confirm)
|
|
104
96
|
// .to.have.been.calledOnce()
|
|
105
97
|
// .and.to.have.been.called.with('/new');
|
|
106
98
|
// });
|
|
107
99
|
|
|
108
|
-
it('should ignore the initial load when blocker returns `true`', () => {
|
|
109
|
-
// Get rid of the old store. We'll replace it with a new one.
|
|
110
|
-
store.dispatch(Actions.stop());
|
|
111
|
-
|
|
112
|
-
store = createStore(
|
|
113
|
-
internalLocationReducer,
|
|
114
|
-
applyMiddleware(
|
|
115
|
-
...createMiddlewares(new InMemorySession(), {
|
|
116
|
-
_internalLocationReducer: true,
|
|
117
|
-
}),
|
|
118
|
-
),
|
|
119
|
-
);
|
|
120
|
-
addNavigationBlocker(() => true);
|
|
121
|
-
|
|
122
|
-
expect(store.getState()).to.be.undefined();
|
|
123
|
-
store.dispatch(Actions.init('/initial'));
|
|
124
|
-
expect(store.getState().pathname).to.equal('/initial');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
100
|
it('should support async rewinding', async () => {
|
|
128
101
|
// eslint-disable-next-line no-underscore-dangle
|
|
129
102
|
if (session._subscription._listeners.length !== 2) {
|
|
@@ -152,7 +125,7 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
|
|
|
152
125
|
const navigationBlockerDeferred = pDefer();
|
|
153
126
|
addNavigationBlocker(() => navigationBlockerDeferred.promise);
|
|
154
127
|
|
|
155
|
-
|
|
128
|
+
navigationStack.shift(-1);
|
|
156
129
|
|
|
157
130
|
// current location was updated immediately.
|
|
158
131
|
// Any `.subscribe()` listeners haven't yet been called,
|
|
@@ -162,7 +135,7 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
|
|
|
162
135
|
// eslint-disable-next-line no-underscore-dangle
|
|
163
136
|
expect(session._subscription._latest.pathname).to.equal('/initial');
|
|
164
137
|
// navigation is waiting.
|
|
165
|
-
expect(
|
|
138
|
+
expect(navigationStack.current().pathname).to.equal('/new');
|
|
166
139
|
|
|
167
140
|
// proceed with navigation.
|
|
168
141
|
navigationDeferred.resolve();
|
|
@@ -172,7 +145,7 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
|
|
|
172
145
|
// eslint-disable-next-line no-underscore-dangle
|
|
173
146
|
expect(session._subscription._latest.pathname).to.equal('/new');
|
|
174
147
|
// navigation almost finished: navigation blockers are running.
|
|
175
|
-
expect(
|
|
148
|
+
expect(navigationStack.current().pathname).to.equal('/new');
|
|
176
149
|
|
|
177
150
|
// finish navigation blockers.
|
|
178
151
|
navigationBlockerDeferred.resolve(undefined);
|
|
@@ -187,7 +160,7 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
|
|
|
187
160
|
expect(session._subscription._latest.pathname).to.equal('/initial');
|
|
188
161
|
// navigation finished.
|
|
189
162
|
// wasn't blocked.
|
|
190
|
-
expect(
|
|
163
|
+
expect(navigationStack.current().pathname).to.equal('/initial');
|
|
191
164
|
});
|
|
192
165
|
|
|
193
166
|
// it('should allow navigation without calling any blockers when `location.delta` is `null`', async () => {
|
|
@@ -211,7 +184,7 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
|
|
|
211
184
|
// // Without delta, we can't rewind the location change,
|
|
212
185
|
// // so navigation is allowed without calling any blockers.
|
|
213
186
|
// expect(currentNavigationLocation.pathname).to.equal('/initial');
|
|
214
|
-
// expect(
|
|
187
|
+
// expect(navigationStack.current().pathname).to.equal('/initial');
|
|
215
188
|
// });
|
|
216
189
|
|
|
217
190
|
// it('should allow navigation when blocker returns `undefined` and `location.delta` is `null`', async () => {
|
|
@@ -229,13 +202,13 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
|
|
|
229
202
|
//
|
|
230
203
|
// // Without delta, we can't rewind on the session.
|
|
231
204
|
// expect(currentNavigationLocation.pathname).to.equal('/initial');
|
|
232
|
-
// expect(
|
|
205
|
+
// expect(navigationStack.current().pathname).to.equal('/new');
|
|
233
206
|
//
|
|
234
207
|
// navigationBlockerDeferred.resolve(undefined);
|
|
235
208
|
// await delay(10);
|
|
236
209
|
//
|
|
237
210
|
// expect(currentNavigationLocation.pathname).to.equal('/initial');
|
|
238
|
-
// expect(
|
|
211
|
+
// expect(navigationStack.current().pathname).to.equal('/initial');
|
|
239
212
|
// });
|
|
240
213
|
|
|
241
214
|
// it('should block store update when blocker returns `true` and `location.delta` is `null`', async () => {
|
|
@@ -251,14 +224,29 @@ describe('createNonProgrammaticNavigationBlockerMiddleware', () => {
|
|
|
251
224
|
// /* eslint-enable no-underscore-dangle */
|
|
252
225
|
//
|
|
253
226
|
// expect(session._navigation.getInitialLocation().pathname).to.equal('/initial');
|
|
254
|
-
// expect(
|
|
227
|
+
// expect(navigationStack.current().pathname).to.equal('/new');
|
|
255
228
|
//
|
|
256
229
|
// navigationBlockerDeferred.resolve(true);
|
|
257
230
|
// await delay(10);
|
|
258
231
|
//
|
|
259
232
|
// // These are out-of-sync now, but it's the best we can do.
|
|
260
233
|
// expect(session._navigation.getInitialLocation().pathname).to.equal('/initial');
|
|
261
|
-
// expect(
|
|
234
|
+
// expect(navigationStack.current().pathname).to.equal('/new');
|
|
262
235
|
// });
|
|
263
236
|
});
|
|
264
237
|
});
|
|
238
|
+
|
|
239
|
+
describe('NavigationStack (blockNonProgrammaticNavigationIfRequired) (init)', () => {
|
|
240
|
+
it('should allow the initial load even when a navigation blocker returns `true`', () => {
|
|
241
|
+
const navigationStack = new NavigationStack(InMemoryEnvironment);
|
|
242
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
243
|
+
const session = navigationStack._session;
|
|
244
|
+
addNavigationBlockerOriginal(session, () => true);
|
|
245
|
+
|
|
246
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
247
|
+
expect(navigationStack._location).to.be.undefined();
|
|
248
|
+
navigationStack.init('/initial');
|
|
249
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
250
|
+
expect(navigationStack._location.pathname).to.equal('/initial');
|
|
251
|
+
});
|
|
252
|
+
});
|