navigation-stack 0.1.3 → 0.3.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.
- package/.github/workflows/main.yml +39 -39
- package/README.md +128 -27
- package/lib/cjs/{LocationStateStorage.js → LocationDataStorage.js} +5 -5
- package/lib/cjs/addBeforeLocationChangeListener.js +7 -0
- package/lib/cjs/beforeLocationChangeListeners.js +51 -0
- package/lib/cjs/createMiddlewares.js +21 -17
- package/lib/cjs/index.js +9 -9
- package/lib/cjs/middleware/createBasePathMiddleware.js +2 -2
- package/lib/cjs/middleware/createBeforeLocationChangeListenerMiddleware.js +39 -0
- package/lib/cjs/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +12 -14
- package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +62 -29
- package/lib/cjs/middleware/createTransformLocationMiddleware.js +2 -2
- package/lib/cjs/navigationBlockers.js +55 -47
- package/lib/cjs/normalizeInputLocation.js +1 -0
- package/lib/cjs/parseLocationUrl.js +2 -0
- package/lib/cjs/parseQueryFromSearch.js +1 -1
- package/lib/cjs/session/BrowserSession.js +235 -0
- package/lib/cjs/session/MemorySession.js +223 -0
- package/lib/cjs/{environment/ServerEnvironment.js → session/ServerSession.js} +28 -16
- package/lib/esm/{LocationStateStorage.js → LocationDataStorage.js} +4 -4
- package/lib/esm/addBeforeLocationChangeListener.js +2 -0
- package/lib/esm/beforeLocationChangeListeners.js +44 -0
- package/lib/esm/createMiddlewares.js +21 -17
- package/lib/esm/index.js +4 -4
- package/lib/esm/middleware/createBasePathMiddleware.js +2 -2
- package/lib/esm/middleware/createBeforeLocationChangeListenerMiddleware.js +34 -0
- package/lib/esm/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +11 -13
- package/lib/esm/middleware/createNavigationBlockerMiddleware.js +62 -29
- package/lib/esm/middleware/createTransformLocationMiddleware.js +2 -2
- package/lib/esm/navigationBlockers.js +55 -47
- package/lib/esm/normalizeInputLocation.js +1 -0
- package/lib/esm/parseLocationUrl.js +2 -0
- package/lib/esm/parseQueryFromSearch.js +1 -1
- package/lib/esm/session/BrowserSession.js +229 -0
- package/lib/esm/session/MemorySession.js +217 -0
- package/lib/esm/{environment/ServerEnvironment.js → session/ServerSession.js} +27 -15
- package/lib/index.d.ts +66 -65
- package/package.json +2 -7
- package/src/{LocationStateStorage.js → LocationDataStorage.js} +4 -4
- package/src/addBeforeLocationChangeListener.js +2 -0
- package/src/beforeLocationChangeListeners.js +54 -0
- package/src/createMiddlewares.js +21 -17
- package/src/index.js +4 -4
- package/src/middleware/createBasePathMiddleware.js +2 -2
- package/src/middleware/createBeforeLocationChangeListenerMiddleware.js +40 -0
- package/src/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +12 -14
- package/src/middleware/createNavigationBlockerMiddleware.js +68 -28
- package/src/middleware/createTransformLocationMiddleware.js +2 -2
- package/src/navigationBlockers.js +68 -49
- package/src/normalizeInputLocation.js +1 -0
- package/src/parseLocationUrl.js +2 -0
- package/src/parseQueryFromSearch.js +1 -1
- package/src/session/BrowserSession.js +235 -0
- package/src/session/MemorySession.js +219 -0
- package/src/{environment/ServerEnvironment.js → session/ServerSession.js} +28 -15
- package/test/{LocationStateStorage.test.js → LocationDataStorage.test.js} +6 -6
- package/test/createMiddlewares.test.js +2 -2
- package/test/helpers.js +1 -1
- package/test/index.test.js +3 -3
- package/test/middleware/createBasePathMiddleware.test.js +7 -7
- package/test/middleware/createBeforeLocationChangeListenerMiddleware.test.js +141 -0
- package/test/middleware/createNavigationBlockerMiddleware.test.js +100 -101
- package/test/middleware/createTransformLocationMiddleware.test.js +1 -1
- package/test/normalizeInputLocation.test.js +3 -0
- package/test/parseLocationUrl.test.js +2 -0
- package/test/{environment/BrowserEnvironment.test.js → session/BrowserSession.test.js} +38 -21
- package/test/session/MemorySession.test.js +244 -0
- package/test/session/ServerSession.test.js +23 -0
- package/types/index.d.ts +66 -65
- package/lib/cjs/environment/BrowserEnvironment.js +0 -111
- package/lib/cjs/environment/MemoryEnvironment.js +0 -150
- package/lib/esm/environment/BrowserEnvironment.js +0 -104
- package/lib/esm/environment/MemoryEnvironment.js +0 -143
- package/src/environment/BrowserEnvironment.js +0 -109
- package/src/environment/MemoryEnvironment.js +0 -151
- package/test/environment/MemoryEnvironment.test.js +0 -218
- package/test/environment/ServerEnvironment.test.js +0 -23
|
@@ -1,39 +1,54 @@
|
|
|
1
1
|
import delay from 'delay';
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import BrowserSession from '../../src/session/BrowserSession';
|
|
4
4
|
|
|
5
|
-
describe('
|
|
5
|
+
describe('BrowserSession', () => {
|
|
6
6
|
beforeEach(() => {
|
|
7
7
|
window.history.replaceState(null, null, '/');
|
|
8
8
|
});
|
|
9
9
|
|
|
10
10
|
it('should parse the initial location', () => {
|
|
11
11
|
window.history.replaceState(null, null, '/foo?bar=baz#qux');
|
|
12
|
-
const
|
|
12
|
+
const session = new BrowserSession();
|
|
13
13
|
|
|
14
|
-
expect(
|
|
15
|
-
action: '
|
|
14
|
+
expect(session.navigation.init()).to.deep.include({
|
|
15
|
+
action: 'INIT',
|
|
16
16
|
pathname: '/foo',
|
|
17
17
|
search: '?bar=baz',
|
|
18
18
|
query: {
|
|
19
19
|
bar: 'baz',
|
|
20
20
|
},
|
|
21
21
|
hash: '#qux',
|
|
22
|
-
key: undefined,
|
|
23
22
|
index: 0,
|
|
24
23
|
delta: 0,
|
|
25
24
|
state: undefined,
|
|
26
25
|
});
|
|
27
26
|
});
|
|
28
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
|
+
|
|
29
42
|
it('should support basic navigation', async () => {
|
|
30
43
|
window.history.replaceState(null, null, '/foo');
|
|
31
|
-
const
|
|
44
|
+
const session = new BrowserSession();
|
|
32
45
|
|
|
33
46
|
const listener = sinon.spy();
|
|
34
|
-
|
|
47
|
+
session.navigation.subscribe(listener);
|
|
48
|
+
|
|
49
|
+
session.navigation.init();
|
|
35
50
|
|
|
36
|
-
const barLocation =
|
|
51
|
+
const barLocation = session.navigation.navigate({
|
|
37
52
|
action: 'PUSH',
|
|
38
53
|
pathname: '/bar',
|
|
39
54
|
search: '?search',
|
|
@@ -58,7 +73,7 @@ describe('BrowserEnvironment', () => {
|
|
|
58
73
|
expect(barLocation.key).not.to.be.empty();
|
|
59
74
|
|
|
60
75
|
expect(
|
|
61
|
-
|
|
76
|
+
session.navigation.navigate({
|
|
62
77
|
action: 'PUSH',
|
|
63
78
|
pathname: '/baz',
|
|
64
79
|
search: '',
|
|
@@ -74,7 +89,7 @@ describe('BrowserEnvironment', () => {
|
|
|
74
89
|
expect(window.location.pathname).to.equal('/baz');
|
|
75
90
|
|
|
76
91
|
expect(
|
|
77
|
-
|
|
92
|
+
session.navigation.navigate({
|
|
78
93
|
action: 'REPLACE',
|
|
79
94
|
pathname: '/qux',
|
|
80
95
|
search: '',
|
|
@@ -91,7 +106,7 @@ describe('BrowserEnvironment', () => {
|
|
|
91
106
|
expect(window.location.pathname).to.equal('/qux');
|
|
92
107
|
expect(listener).not.to.have.been.called();
|
|
93
108
|
|
|
94
|
-
|
|
109
|
+
session.navigation.shift(-1);
|
|
95
110
|
await delay(20);
|
|
96
111
|
|
|
97
112
|
expect(window.location).to.include({
|
|
@@ -101,7 +116,7 @@ describe('BrowserEnvironment', () => {
|
|
|
101
116
|
});
|
|
102
117
|
expect(listener).to.have.been.calledOnce();
|
|
103
118
|
expect(listener.firstCall.args[0]).to.deep.include({
|
|
104
|
-
action: '
|
|
119
|
+
action: 'SHIFT',
|
|
105
120
|
pathname: '/bar',
|
|
106
121
|
search: '?search',
|
|
107
122
|
hash: '#hash',
|
|
@@ -118,24 +133,26 @@ describe('BrowserEnvironment', () => {
|
|
|
118
133
|
expect(window.location.pathname).to.equal('/foo');
|
|
119
134
|
expect(listener).to.have.been.calledOnce();
|
|
120
135
|
expect(listener.firstCall.args[0]).to.deep.include({
|
|
121
|
-
action: '
|
|
136
|
+
action: 'SHIFT',
|
|
122
137
|
pathname: '/foo',
|
|
123
138
|
index: 0,
|
|
124
139
|
delta: -1,
|
|
125
140
|
state: undefined,
|
|
126
141
|
});
|
|
142
|
+
|
|
127
143
|
listener.resetHistory();
|
|
128
144
|
});
|
|
129
145
|
|
|
130
146
|
it('should support subscribing and unsubscribing', async () => {
|
|
131
|
-
const
|
|
132
|
-
|
|
147
|
+
const session = new BrowserSession();
|
|
148
|
+
session.navigation.init();
|
|
149
|
+
session.navigation.navigate({
|
|
133
150
|
action: 'PUSH',
|
|
134
151
|
pathname: '/bar',
|
|
135
152
|
search: '',
|
|
136
153
|
hash: '',
|
|
137
154
|
});
|
|
138
|
-
|
|
155
|
+
session.navigation.navigate({
|
|
139
156
|
action: 'PUSH',
|
|
140
157
|
pathname: '/baz',
|
|
141
158
|
search: '',
|
|
@@ -143,21 +160,21 @@ describe('BrowserEnvironment', () => {
|
|
|
143
160
|
});
|
|
144
161
|
|
|
145
162
|
const listener = sinon.spy();
|
|
146
|
-
const unsubscribe =
|
|
163
|
+
const unsubscribe = session.navigation.subscribe(listener);
|
|
147
164
|
|
|
148
|
-
|
|
165
|
+
session.navigation.shift(-1);
|
|
149
166
|
await delay(20);
|
|
150
167
|
|
|
151
168
|
expect(listener).to.have.been.calledOnce();
|
|
152
169
|
expect(listener.firstCall.args[0]).to.include({
|
|
153
|
-
action: '
|
|
170
|
+
action: 'SHIFT',
|
|
154
171
|
pathname: '/bar',
|
|
155
172
|
});
|
|
156
173
|
listener.resetHistory();
|
|
157
174
|
|
|
158
175
|
unsubscribe();
|
|
159
176
|
|
|
160
|
-
|
|
177
|
+
session.navigation.shift(-1);
|
|
161
178
|
await delay(20);
|
|
162
179
|
|
|
163
180
|
expect(listener).not.to.have.been.called();
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import MemorySession from '../../src/session/MemorySession';
|
|
2
|
+
|
|
3
|
+
describe('MemorySession', () => {
|
|
4
|
+
it('should parse the initial location', () => {
|
|
5
|
+
const session = new MemorySession('/foo?bar=baz#qux');
|
|
6
|
+
|
|
7
|
+
expect(session.navigation.init()).to.deep.include({
|
|
8
|
+
action: 'INIT',
|
|
9
|
+
pathname: '/foo',
|
|
10
|
+
search: '?bar=baz',
|
|
11
|
+
query: {
|
|
12
|
+
bar: 'baz',
|
|
13
|
+
},
|
|
14
|
+
hash: '#qux',
|
|
15
|
+
index: 0,
|
|
16
|
+
delta: 0,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should support basic navigation', () => {
|
|
21
|
+
const session = new MemorySession('/foo');
|
|
22
|
+
|
|
23
|
+
const listener = sinon.spy();
|
|
24
|
+
session.navigation.subscribe(listener);
|
|
25
|
+
|
|
26
|
+
const barLocation = session.navigation.navigate({
|
|
27
|
+
action: 'PUSH',
|
|
28
|
+
pathname: '/bar',
|
|
29
|
+
state: { the: 'state' },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(barLocation).to.deep.include({
|
|
33
|
+
action: 'PUSH',
|
|
34
|
+
pathname: '/bar',
|
|
35
|
+
index: 1,
|
|
36
|
+
delta: 1,
|
|
37
|
+
state: { the: 'state' },
|
|
38
|
+
});
|
|
39
|
+
expect(barLocation.key).not.to.be.empty();
|
|
40
|
+
|
|
41
|
+
expect(
|
|
42
|
+
session.navigation.navigate({ action: 'PUSH', pathname: '/baz' }),
|
|
43
|
+
).to.include({
|
|
44
|
+
action: 'PUSH',
|
|
45
|
+
pathname: '/baz',
|
|
46
|
+
index: 2,
|
|
47
|
+
delta: 1,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(
|
|
51
|
+
session.navigation.navigate({ action: 'REPLACE', pathname: '/qux' }),
|
|
52
|
+
).to.include({
|
|
53
|
+
action: 'REPLACE',
|
|
54
|
+
pathname: '/qux',
|
|
55
|
+
index: 2,
|
|
56
|
+
delta: 0,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(listener).not.to.have.been.called();
|
|
60
|
+
|
|
61
|
+
session.navigation.shift(-1);
|
|
62
|
+
|
|
63
|
+
expect(listener).to.have.been.calledOnce();
|
|
64
|
+
expect(listener.firstCall.args[0]).to.deep.include({
|
|
65
|
+
action: 'SHIFT',
|
|
66
|
+
pathname: '/bar',
|
|
67
|
+
key: barLocation.key,
|
|
68
|
+
index: 1,
|
|
69
|
+
delta: -1,
|
|
70
|
+
state: { the: 'state' },
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should support subscribing and unsubscribing', () => {
|
|
75
|
+
const session = new MemorySession('/foo');
|
|
76
|
+
session.navigation.navigate({ action: 'PUSH', pathname: '/bar' });
|
|
77
|
+
session.navigation.navigate({ action: 'PUSH', pathname: '/baz' });
|
|
78
|
+
|
|
79
|
+
const listener = sinon.spy();
|
|
80
|
+
const unsubscribe = session.navigation.subscribe(listener);
|
|
81
|
+
|
|
82
|
+
session.navigation.shift(-1);
|
|
83
|
+
|
|
84
|
+
expect(listener).to.have.been.calledOnce();
|
|
85
|
+
expect(listener.firstCall.args[0]).to.include({
|
|
86
|
+
action: 'SHIFT',
|
|
87
|
+
pathname: '/bar',
|
|
88
|
+
});
|
|
89
|
+
listener.resetHistory();
|
|
90
|
+
|
|
91
|
+
unsubscribe();
|
|
92
|
+
|
|
93
|
+
session.navigation.shift(-1);
|
|
94
|
+
|
|
95
|
+
expect(listener).not.to.have.been.called();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should respect stack bounds', () => {
|
|
99
|
+
const session = new MemorySession('/foo');
|
|
100
|
+
session.navigation.navigate({ action: 'PUSH', pathname: '/bar' });
|
|
101
|
+
session.navigation.navigate({ action: 'PUSH', pathname: '/baz' });
|
|
102
|
+
|
|
103
|
+
const listener = sinon.spy();
|
|
104
|
+
session.navigation.subscribe(listener);
|
|
105
|
+
|
|
106
|
+
session.navigation.shift(-390);
|
|
107
|
+
|
|
108
|
+
expect(listener).to.have.been.calledOnce();
|
|
109
|
+
expect(listener.firstCall.args[0]).to.include({
|
|
110
|
+
action: 'SHIFT',
|
|
111
|
+
pathname: '/foo',
|
|
112
|
+
delta: -2,
|
|
113
|
+
});
|
|
114
|
+
listener.resetHistory();
|
|
115
|
+
|
|
116
|
+
session.navigation.shift(-1);
|
|
117
|
+
|
|
118
|
+
expect(listener).not.to.have.been.called();
|
|
119
|
+
|
|
120
|
+
session.navigation.shift(+22);
|
|
121
|
+
|
|
122
|
+
expect(listener).to.have.been.calledOnce();
|
|
123
|
+
expect(listener.firstCall.args[0]).to.include({
|
|
124
|
+
action: 'SHIFT',
|
|
125
|
+
pathname: '/baz',
|
|
126
|
+
delta: 2,
|
|
127
|
+
});
|
|
128
|
+
listener.resetHistory();
|
|
129
|
+
|
|
130
|
+
session.navigation.shift(+1);
|
|
131
|
+
|
|
132
|
+
expect(listener).not.to.have.been.called();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should reset forward entries on push', () => {
|
|
136
|
+
const session = new MemorySession('/foo');
|
|
137
|
+
session.navigation.navigate({ action: 'PUSH', pathname: '/bar' });
|
|
138
|
+
session.navigation.navigate({ action: 'PUSH', pathname: '/baz' });
|
|
139
|
+
session.navigation.shift(-2);
|
|
140
|
+
session.navigation.navigate({ action: 'REPLACE', pathname: '/qux' });
|
|
141
|
+
|
|
142
|
+
const listener = sinon.spy();
|
|
143
|
+
session.navigation.subscribe(listener);
|
|
144
|
+
|
|
145
|
+
session.navigation.shift(+1);
|
|
146
|
+
|
|
147
|
+
expect(listener).to.have.been.calledOnce();
|
|
148
|
+
expect(listener.firstCall.args[0]).to.include({
|
|
149
|
+
action: 'SHIFT',
|
|
150
|
+
pathname: '/bar',
|
|
151
|
+
delta: 1,
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should not reset forward entries on replace', () => {
|
|
156
|
+
const session = new MemorySession('/foo');
|
|
157
|
+
session.navigation.navigate({ action: 'PUSH', pathname: '/bar' });
|
|
158
|
+
session.navigation.navigate({ action: 'PUSH', pathname: '/baz' });
|
|
159
|
+
session.navigation.shift(-2);
|
|
160
|
+
session.navigation.navigate({ action: 'PUSH', pathname: '/qux' });
|
|
161
|
+
|
|
162
|
+
const listener = sinon.spy();
|
|
163
|
+
session.navigation.subscribe(listener);
|
|
164
|
+
|
|
165
|
+
session.navigation.shift(+1);
|
|
166
|
+
|
|
167
|
+
expect(listener).not.to.have.been.called();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('persistence', () => {
|
|
171
|
+
// beforeEach(() => {
|
|
172
|
+
// window.sessionStorage.clear();
|
|
173
|
+
// });
|
|
174
|
+
|
|
175
|
+
it('should support persistence', () => {
|
|
176
|
+
// function save(key, data) {
|
|
177
|
+
// window.sessionStorage.setItem(key, data);
|
|
178
|
+
// }
|
|
179
|
+
//
|
|
180
|
+
// function load(key) {
|
|
181
|
+
// return window.sessionStorage.getItem(key);
|
|
182
|
+
// }
|
|
183
|
+
|
|
184
|
+
const storage = {};
|
|
185
|
+
|
|
186
|
+
const save = (key, data) => {
|
|
187
|
+
storage[key] = data;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const load = (key) => {
|
|
191
|
+
return storage[key];
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const session1 = new MemorySession('/foo', { save, load });
|
|
195
|
+
expect(session1.navigation.init()).to.include({
|
|
196
|
+
pathname: '/foo',
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
session1.navigation.navigate({ action: 'PUSH', pathname: '/bar' });
|
|
200
|
+
session1.navigation.navigate({ action: 'PUSH', pathname: '/baz' });
|
|
201
|
+
session1.navigation.shift(-1);
|
|
202
|
+
|
|
203
|
+
const session2 = new MemorySession('/foo', { save, load });
|
|
204
|
+
expect(session2.navigation.init()).to.include({
|
|
205
|
+
pathname: '/bar',
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
session2.navigation.shift(+1);
|
|
209
|
+
expect(session2.navigation.init()).to.include({
|
|
210
|
+
pathname: '/baz',
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should ignore broken session storage entry', () => {
|
|
215
|
+
// function save(key, data) {
|
|
216
|
+
// window.sessionStorage.setItem(key, data);
|
|
217
|
+
// }
|
|
218
|
+
//
|
|
219
|
+
// function load(key) {
|
|
220
|
+
// return window.sessionStorage.getItem(key);
|
|
221
|
+
// }
|
|
222
|
+
|
|
223
|
+
const storage = {};
|
|
224
|
+
|
|
225
|
+
const save = (key, data) => {
|
|
226
|
+
storage[key] = data;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const load = (key) => {
|
|
230
|
+
return storage[key];
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
save(
|
|
234
|
+
// `stack` should have sufficient items so that the `index` wouldn't be out of bounds.
|
|
235
|
+
JSON.stringify({ stack: [], index: 2 }),
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
const session = new MemorySession('/foo', { save, load });
|
|
239
|
+
expect(session.navigation.init()).to.include({
|
|
240
|
+
pathname: '/foo',
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import ServerSession from '../../src/session/ServerSession';
|
|
2
|
+
|
|
3
|
+
describe('ServerSession', () => {
|
|
4
|
+
it('should parse the initial location', () => {
|
|
5
|
+
const session = new ServerSession('/foo?bar=baz#qux');
|
|
6
|
+
|
|
7
|
+
expect(session.navigation.init()).to.deep.include({
|
|
8
|
+
action: 'INIT',
|
|
9
|
+
pathname: '/foo',
|
|
10
|
+
search: '?bar=baz',
|
|
11
|
+
query: {
|
|
12
|
+
bar: 'baz',
|
|
13
|
+
},
|
|
14
|
+
hash: '#qux',
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should have dummy support for subscriptions', () => {
|
|
19
|
+
const session = new ServerSession('/foo?bar=baz#qux');
|
|
20
|
+
const unsubscribe = session.navigation.subscribe();
|
|
21
|
+
expect(unsubscribe).to.not.throw();
|
|
22
|
+
});
|
|
23
|
+
});
|
package/types/index.d.ts
CHANGED
|
@@ -13,12 +13,9 @@ export type InputLocationQuery = Record<
|
|
|
13
13
|
|
|
14
14
|
export interface Location<TState = any> {
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
* FarceActions.replace respectively; 'POP' on the initial location, or if
|
|
18
|
-
* the location was reached via the browser back or forward buttons or
|
|
19
|
-
* via FarceActions.shift
|
|
16
|
+
* See the README on the `action` property of `location`.
|
|
20
17
|
*/
|
|
21
|
-
action: 'PUSH' | 'REPLACE' | '
|
|
18
|
+
action: 'PUSH' | 'REPLACE' | 'SHIFT' | 'INIT';
|
|
22
19
|
/**
|
|
23
20
|
* the path name; as on window.location e.g. '/foo'
|
|
24
21
|
*/
|
|
@@ -36,9 +33,9 @@ export interface Location<TState = any> {
|
|
|
36
33
|
*/
|
|
37
34
|
hash: string;
|
|
38
35
|
/**
|
|
39
|
-
*
|
|
36
|
+
* a unique key identifying the current history entry
|
|
40
37
|
*/
|
|
41
|
-
key
|
|
38
|
+
key: string;
|
|
42
39
|
/**
|
|
43
40
|
* the current index of the history entry, starting at 0 for the initial
|
|
44
41
|
* entry; this increments on FarceActions.push but not on
|
|
@@ -46,12 +43,11 @@ export interface Location<TState = any> {
|
|
|
46
43
|
*/
|
|
47
44
|
index: number;
|
|
48
45
|
/**
|
|
49
|
-
* the difference between the current index and the index of the previous
|
|
50
|
-
* location
|
|
46
|
+
* the difference between the current index and the index of the previous location
|
|
51
47
|
*/
|
|
52
48
|
delta: number;
|
|
53
49
|
/**
|
|
54
|
-
* additional location state that
|
|
50
|
+
* any additional location state that the application might explicitly define and store
|
|
55
51
|
*/
|
|
56
52
|
state: TState;
|
|
57
53
|
}
|
|
@@ -70,11 +66,15 @@ export interface InputLocationObject {
|
|
|
70
66
|
export interface LocationBase {
|
|
71
67
|
pathname: Location['pathname'];
|
|
72
68
|
search: Location['search'];
|
|
73
|
-
query
|
|
69
|
+
query: Query;
|
|
74
70
|
hash: Location['hash'];
|
|
75
71
|
state?: Location['state'];
|
|
76
72
|
}
|
|
77
73
|
|
|
74
|
+
export interface NavigationLocation extends LocationBase {
|
|
75
|
+
action: 'PUSH' | 'REPLACE';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
78
|
/**
|
|
79
79
|
* Location descriptor string:
|
|
80
80
|
* store.dispatch(FarceActions.push('/foo?bar=baz#qux'));
|
|
@@ -109,13 +109,20 @@ export type NavigationBlockerResult =
|
|
|
109
109
|
| Promise<NavigationBlockerSyncResult>;
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
|
-
*
|
|
113
|
-
* is attempting to navigate.
|
|
112
|
+
* Navigation blocker function receives a `location` to which the application (or the user) is attempting to navigate.
|
|
114
113
|
*
|
|
115
|
-
* The `location` argument is `null` when the web browser tab is about to be closed.
|
|
114
|
+
* * The `location` argument is `null` when the web browser tab is about to be closed.
|
|
115
|
+
* * The `location` argument is of type `NavigationLocation` when a `.push()` or `.replace()` action is blocked.
|
|
116
|
+
* * The `location` argument is of type `Location` when blocking a navigation that was initiated outside of the application code.
|
|
117
|
+
* For example, when the user clicks "Back" or "Forward" button in a web browser.
|
|
116
118
|
*/
|
|
117
119
|
export interface NavigationBlocker {
|
|
118
|
-
(location: Location |
|
|
120
|
+
(location: Location | NavigationLocation | null): NavigationBlockerResult;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// I dunno why did they use an `interface` here.
|
|
124
|
+
export interface BeforeLocationChangeListener {
|
|
125
|
+
(location: Location): void;
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
export function addBasePath<L extends InputLocation>(
|
|
@@ -131,15 +138,20 @@ export function getLocationUrl(location: InputLocationObject): string;
|
|
|
131
138
|
export function parseLocationUrl(locationUrl: string): LocationBase;
|
|
132
139
|
|
|
133
140
|
export function createMiddlewares(
|
|
134
|
-
|
|
141
|
+
session: SessionBase,
|
|
135
142
|
options?: CreateMiddlewaresOptions,
|
|
136
143
|
): Middleware[];
|
|
137
144
|
|
|
138
145
|
export function addNavigationBlocker(
|
|
139
|
-
|
|
146
|
+
session: SessionBase,
|
|
140
147
|
blocker: NavigationBlocker,
|
|
141
148
|
): () => void;
|
|
142
149
|
|
|
150
|
+
export function addBeforeLocationChangeListener(
|
|
151
|
+
session: SessionBase,
|
|
152
|
+
listener: BeforeLocationChangeListener,
|
|
153
|
+
): () => void;
|
|
154
|
+
|
|
143
155
|
export const ActionTypes: {
|
|
144
156
|
INIT: '@@navigation-stack/INIT';
|
|
145
157
|
PUSH: '@@navigation-stack/PUSH';
|
|
@@ -190,80 +202,73 @@ export const Actions: {
|
|
|
190
202
|
|
|
191
203
|
type BeforeDestroyListener = () => boolean | undefined;
|
|
192
204
|
|
|
193
|
-
|
|
205
|
+
interface SessionNavigation {
|
|
194
206
|
init(): void;
|
|
195
207
|
|
|
196
208
|
// Subscribes to changes in location,
|
|
197
209
|
// excluding ones that happened as a result of calling `.navigate()`.
|
|
198
210
|
subscribe(listener: (location: Location) => void): () => void;
|
|
199
211
|
|
|
200
|
-
navigate(location:
|
|
212
|
+
navigate(location: NavigationLocation): Location;
|
|
201
213
|
|
|
202
214
|
shift(delta: number): void;
|
|
215
|
+
}
|
|
203
216
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
setState(key: string, value: string): void;
|
|
217
|
+
interface SessionDataStorage {
|
|
218
|
+
get(key: string): string | null;
|
|
219
|
+
remove(key: string): void;
|
|
220
|
+
set(key: string, value: string): void;
|
|
209
221
|
}
|
|
210
222
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
223
|
+
export interface Session {
|
|
224
|
+
navigation: SessionNavigation;
|
|
225
|
+
dataStorage: SessionDataStorage;
|
|
226
|
+
addBeforeDestroyListener(listener: BeforeDestroyListener): void;
|
|
214
227
|
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
|
|
228
|
+
// These're internal variables that're manually set under the hood.
|
|
229
|
+
// _beforeLocationChangeListenersList?: Array<BeforeLocationChangeListener>;
|
|
230
|
+
// _navigationBlockersList?: Array<NavigationBlocker>;
|
|
231
|
+
// _removeBeforeDestroyListener?: () => void;
|
|
232
|
+
// _navigationBlockersEvaluationStatus?: { cancelled?: boolean };
|
|
233
|
+
}
|
|
218
234
|
|
|
219
|
-
|
|
235
|
+
// This is just a copy-paste of the `session` interface above.
|
|
236
|
+
declare abstract class SessionBase implements Session {
|
|
237
|
+
navigation: SessionNavigation;
|
|
220
238
|
|
|
221
|
-
|
|
239
|
+
dataStorage: SessionDataStorage;
|
|
222
240
|
|
|
223
241
|
addBeforeDestroyListener(listener: BeforeDestroyListener): void;
|
|
224
242
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
243
|
+
// These're internal variables that're manually set under the hood.
|
|
244
|
+
// _beforeLocationChangeListenersList?: Array<BeforeLocationChangeListener>;
|
|
245
|
+
// _navigationBlockersList?: Array<NavigationBlocker>;
|
|
246
|
+
// _removeBeforeDestroyListener?: () => void;
|
|
247
|
+
// _navigationBlockersEvaluationStatus?: { cancelled?: boolean };
|
|
228
248
|
}
|
|
229
249
|
|
|
230
|
-
export class
|
|
250
|
+
export class BrowserSession extends SessionBase {}
|
|
231
251
|
|
|
232
|
-
export interface
|
|
233
|
-
save?: (
|
|
234
|
-
load?: () =>
|
|
252
|
+
export interface MemorySessionOptions {
|
|
253
|
+
save?: (data: string) => void;
|
|
254
|
+
load?: () => string | undefined | null;
|
|
235
255
|
}
|
|
236
256
|
|
|
237
|
-
export class
|
|
257
|
+
export class ServerSession extends SessionBase {
|
|
238
258
|
constructor(initialLocation: InputLocation);
|
|
239
259
|
}
|
|
240
260
|
|
|
241
|
-
export class
|
|
242
|
-
constructor(
|
|
243
|
-
initialLocation: InputLocation,
|
|
244
|
-
options?: MemoryEnvironmentOptions,
|
|
245
|
-
);
|
|
261
|
+
export class MemorySession extends SessionBase {
|
|
262
|
+
constructor(initialLocation: InputLocation, options?: MemorySessionOptions);
|
|
246
263
|
}
|
|
247
264
|
|
|
248
|
-
export interface QueryMiddlewareOptions {
|
|
249
|
-
stringify(query: InputLocationQuery): string;
|
|
250
|
-
parse(str: string): Query;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export function createQueryMiddleware(
|
|
254
|
-
options: QueryMiddlewareOptions,
|
|
255
|
-
): Middleware;
|
|
256
|
-
|
|
257
|
-
export const queryMiddleware: Middleware;
|
|
258
|
-
|
|
259
|
-
export function createBasePathMiddleware(basePath?: string): Middleware;
|
|
260
|
-
|
|
261
265
|
export const locationReducer: Reducer<Location, Action>;
|
|
262
266
|
|
|
263
|
-
export class
|
|
264
|
-
constructor(
|
|
267
|
+
export class LocationDataStorage {
|
|
268
|
+
constructor(session: Session, options?: { namespace?: string });
|
|
265
269
|
|
|
266
270
|
get(location: Location, key: string): any;
|
|
271
|
+
|
|
267
272
|
set(location: Location, key: string, value: any): void;
|
|
268
273
|
}
|
|
269
274
|
|
|
@@ -287,11 +292,7 @@ interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {
|
|
|
287
292
|
getState(): S;
|
|
288
293
|
}
|
|
289
294
|
|
|
290
|
-
interface Middleware<
|
|
291
|
-
DispatchExt = {},
|
|
292
|
-
S = any,
|
|
293
|
-
D extends Dispatch = Dispatch,
|
|
294
|
-
> {
|
|
295
|
+
interface Middleware<S = any, D extends Dispatch = Dispatch> {
|
|
295
296
|
(api: MiddlewareAPI<D, S>): (next: Dispatch) => (action: any) => any;
|
|
296
297
|
}
|
|
297
298
|
|