navigation-stack 0.1.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/.babelrc.cjs +17 -0
- package/.eslintignore +8 -0
- package/.eslintrc.cjs +10 -0
- package/.github/workflows/main.yml +39 -0
- package/.yarn/install-state.gz +0 -0
- package/.yarnrc.yml +1 -0
- package/CODE_OF_CONDUCT.md +77 -0
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/codecov.yml +1 -0
- package/karma.conf.cjs +63 -0
- package/lib/cjs/ActionTypes.js +14 -0
- package/lib/cjs/Actions.js +27 -0
- package/lib/cjs/LocationStateStorage.js +60 -0
- package/lib/cjs/addNavigationBlocker.js +7 -0
- package/lib/cjs/basePath.js +58 -0
- package/lib/cjs/createMiddlewares.js +43 -0
- package/lib/cjs/createSearchFromQuery.js +13 -0
- package/lib/cjs/environment/BrowserEnvironment.js +111 -0
- package/lib/cjs/environment/MemoryEnvironment.js +150 -0
- package/lib/cjs/environment/ServerEnvironment.js +53 -0
- package/lib/cjs/getLocationUrl.js +20 -0
- package/lib/cjs/index.js +30 -0
- package/lib/cjs/isPromise.js +9 -0
- package/lib/cjs/locationReducer.js +13 -0
- package/lib/cjs/middleware/createBasePathMiddleware.js +24 -0
- package/lib/cjs/middleware/createEnvironmentMiddleware.js +58 -0
- package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +128 -0
- package/lib/cjs/middleware/createTransformLocationMiddleware.js +38 -0
- package/lib/cjs/middleware/navigationActionMiddleware.js +37 -0
- package/lib/cjs/middleware/normalizeInputLocationMiddleware.js +27 -0
- package/lib/cjs/navigationBlockers.js +146 -0
- package/lib/cjs/normalizeInputLocation.js +46 -0
- package/lib/cjs/onlyAllowedOnClientSide.js +10 -0
- package/lib/cjs/parseLocationUrl.js +39 -0
- package/lib/cjs/parseQueryFromSearch.js +16 -0
- package/lib/esm/ActionTypes.js +9 -0
- package/lib/esm/Actions.js +21 -0
- package/lib/esm/LocationStateStorage.js +53 -0
- package/lib/esm/addNavigationBlocker.js +2 -0
- package/lib/esm/basePath.js +53 -0
- package/lib/esm/createMiddlewares.js +37 -0
- package/lib/esm/createSearchFromQuery.js +8 -0
- package/lib/esm/environment/BrowserEnvironment.js +104 -0
- package/lib/esm/environment/MemoryEnvironment.js +143 -0
- package/lib/esm/environment/ServerEnvironment.js +46 -0
- package/lib/esm/getLocationUrl.js +15 -0
- package/lib/esm/index.js +12 -0
- package/lib/esm/isPromise.js +4 -0
- package/lib/esm/locationReducer.js +7 -0
- package/lib/esm/middleware/createBasePathMiddleware.js +19 -0
- package/lib/esm/middleware/createEnvironmentMiddleware.js +52 -0
- package/lib/esm/middleware/createNavigationBlockerMiddleware.js +123 -0
- package/lib/esm/middleware/createTransformLocationMiddleware.js +33 -0
- package/lib/esm/middleware/navigationActionMiddleware.js +32 -0
- package/lib/esm/middleware/normalizeInputLocationMiddleware.js +22 -0
- package/lib/esm/navigationBlockers.js +138 -0
- package/lib/esm/normalizeInputLocation.js +41 -0
- package/lib/esm/onlyAllowedOnClientSide.js +5 -0
- package/lib/esm/parseLocationUrl.js +33 -0
- package/lib/esm/parseQueryFromSearch.js +11 -0
- package/lib/index.d.ts +301 -0
- package/package.json +100 -0
- package/renovate.json +3 -0
- package/src/ActionTypes.js +9 -0
- package/src/Actions.js +26 -0
- package/src/LocationStateStorage.js +59 -0
- package/src/addNavigationBlocker.js +2 -0
- package/src/basePath.js +65 -0
- package/src/createMiddlewares.js +41 -0
- package/src/createSearchFromQuery.js +9 -0
- package/src/environment/BrowserEnvironment.js +109 -0
- package/src/environment/MemoryEnvironment.js +151 -0
- package/src/environment/ServerEnvironment.js +54 -0
- package/src/getLocationUrl.js +12 -0
- package/src/index.js +12 -0
- package/src/isPromise.js +8 -0
- package/src/locationReducer.js +8 -0
- package/src/middleware/createBasePathMiddleware.js +20 -0
- package/src/middleware/createEnvironmentMiddleware.js +57 -0
- package/src/middleware/createNavigationBlockerMiddleware.js +128 -0
- package/src/middleware/createTransformLocationMiddleware.js +29 -0
- package/src/middleware/navigationActionMiddleware.js +27 -0
- package/src/middleware/normalizeInputLocationMiddleware.js +21 -0
- package/src/navigationBlockers.js +158 -0
- package/src/normalizeInputLocation.js +44 -0
- package/src/onlyAllowedOnClientSide.js +5 -0
- package/src/parseLocationUrl.js +40 -0
- package/src/parseQueryFromSearch.js +12 -0
- package/test/.eslintrc.cjs +17 -0
- package/test/Action.test.js +72 -0
- package/test/ActionTypes.test.js +13 -0
- package/test/LocationStateStorage.test.js +75 -0
- package/test/basePath.test.js +158 -0
- package/test/createMiddlewares.test.js +62 -0
- package/test/environment/BrowserEnvironment.test.js +165 -0
- package/test/environment/MemoryEnvironment.test.js +218 -0
- package/test/environment/ServerEnvironment.test.js +23 -0
- package/test/getLocationUrl.test.js +33 -0
- package/test/helpers.js +34 -0
- package/test/index.js +44 -0
- package/test/index.test.js +20 -0
- package/test/locationReducer.test.js +42 -0
- package/test/middleware/createBasePathMiddleware.test.js +67 -0
- package/test/middleware/createNavigationBlockerMiddleware.test.js +472 -0
- package/test/middleware/createTransformLocationMiddleware.test.js +44 -0
- package/test/middleware/navigationActionMiddleware.test.js +74 -0
- package/test/middleware/normalizeInputLocationMiddleware.test.js +62 -0
- package/test/normalizeInputLocation.test.js +81 -0
- package/test/parseLocationUrl.test.js +30 -0
- package/types/.eslintrc.cjs +3 -0
- package/types/index.d.ts +301 -0
- package/types/tsconfig.json +14 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import delay from 'delay';
|
|
2
|
+
|
|
3
|
+
import BrowserEnvironment from '../../src/environment/BrowserEnvironment';
|
|
4
|
+
|
|
5
|
+
describe('BrowserEnvironment', () => {
|
|
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 environment = new BrowserEnvironment();
|
|
13
|
+
|
|
14
|
+
expect(environment.init()).to.eql({
|
|
15
|
+
action: 'POP',
|
|
16
|
+
pathname: '/foo',
|
|
17
|
+
search: '?bar=baz',
|
|
18
|
+
query: {
|
|
19
|
+
bar: 'baz',
|
|
20
|
+
},
|
|
21
|
+
hash: '#qux',
|
|
22
|
+
key: undefined,
|
|
23
|
+
index: 0,
|
|
24
|
+
delta: 0,
|
|
25
|
+
state: undefined,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should support basic navigation', async () => {
|
|
30
|
+
window.history.replaceState(null, null, '/foo');
|
|
31
|
+
const environment = new BrowserEnvironment();
|
|
32
|
+
|
|
33
|
+
const listener = sinon.spy();
|
|
34
|
+
environment.subscribe(listener);
|
|
35
|
+
|
|
36
|
+
const barLocation = environment.navigate({
|
|
37
|
+
action: 'PUSH',
|
|
38
|
+
pathname: '/bar',
|
|
39
|
+
search: '?search',
|
|
40
|
+
hash: '#hash',
|
|
41
|
+
state: { the: 'state' },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expect(window.location).to.include({
|
|
45
|
+
pathname: '/bar',
|
|
46
|
+
search: '?search',
|
|
47
|
+
hash: '#hash',
|
|
48
|
+
});
|
|
49
|
+
expect(barLocation).to.deep.include({
|
|
50
|
+
action: 'PUSH',
|
|
51
|
+
pathname: '/bar',
|
|
52
|
+
search: '?search',
|
|
53
|
+
hash: '#hash',
|
|
54
|
+
index: 1,
|
|
55
|
+
delta: 1,
|
|
56
|
+
state: { the: 'state' },
|
|
57
|
+
});
|
|
58
|
+
expect(barLocation.key).not.to.be.empty();
|
|
59
|
+
|
|
60
|
+
expect(
|
|
61
|
+
environment.navigate({
|
|
62
|
+
action: 'PUSH',
|
|
63
|
+
pathname: '/baz',
|
|
64
|
+
search: '',
|
|
65
|
+
hash: '',
|
|
66
|
+
}),
|
|
67
|
+
).to.include({
|
|
68
|
+
action: 'PUSH',
|
|
69
|
+
pathname: '/baz',
|
|
70
|
+
index: 2,
|
|
71
|
+
delta: 1,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(window.location.pathname).to.equal('/baz');
|
|
75
|
+
|
|
76
|
+
expect(
|
|
77
|
+
environment.navigate({
|
|
78
|
+
action: 'REPLACE',
|
|
79
|
+
pathname: '/qux',
|
|
80
|
+
search: '',
|
|
81
|
+
hash: '',
|
|
82
|
+
}),
|
|
83
|
+
).to.include({
|
|
84
|
+
action: 'REPLACE',
|
|
85
|
+
pathname: '/qux',
|
|
86
|
+
index: 2,
|
|
87
|
+
delta: 0,
|
|
88
|
+
});
|
|
89
|
+
await delay(20);
|
|
90
|
+
|
|
91
|
+
expect(window.location.pathname).to.equal('/qux');
|
|
92
|
+
expect(listener).not.to.have.been.called();
|
|
93
|
+
|
|
94
|
+
environment.shift(-1);
|
|
95
|
+
await delay(20);
|
|
96
|
+
|
|
97
|
+
expect(window.location).to.include({
|
|
98
|
+
pathname: '/bar',
|
|
99
|
+
search: '?search',
|
|
100
|
+
hash: '#hash',
|
|
101
|
+
});
|
|
102
|
+
expect(listener).to.have.been.calledOnce();
|
|
103
|
+
expect(listener.firstCall.args[0]).to.deep.include({
|
|
104
|
+
action: 'POP',
|
|
105
|
+
pathname: '/bar',
|
|
106
|
+
search: '?search',
|
|
107
|
+
hash: '#hash',
|
|
108
|
+
key: barLocation.key,
|
|
109
|
+
index: 1,
|
|
110
|
+
delta: -1,
|
|
111
|
+
state: { the: 'state' },
|
|
112
|
+
});
|
|
113
|
+
listener.resetHistory();
|
|
114
|
+
|
|
115
|
+
window.history.back();
|
|
116
|
+
await delay(20);
|
|
117
|
+
|
|
118
|
+
expect(window.location.pathname).to.equal('/foo');
|
|
119
|
+
expect(listener).to.have.been.calledOnce();
|
|
120
|
+
expect(listener.firstCall.args[0]).to.deep.include({
|
|
121
|
+
action: 'POP',
|
|
122
|
+
pathname: '/foo',
|
|
123
|
+
index: 0,
|
|
124
|
+
delta: -1,
|
|
125
|
+
state: undefined,
|
|
126
|
+
});
|
|
127
|
+
listener.resetHistory();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should support subscribing and unsubscribing', async () => {
|
|
131
|
+
const environment = new BrowserEnvironment();
|
|
132
|
+
environment.navigate({
|
|
133
|
+
action: 'PUSH',
|
|
134
|
+
pathname: '/bar',
|
|
135
|
+
search: '',
|
|
136
|
+
hash: '',
|
|
137
|
+
});
|
|
138
|
+
environment.navigate({
|
|
139
|
+
action: 'PUSH',
|
|
140
|
+
pathname: '/baz',
|
|
141
|
+
search: '',
|
|
142
|
+
hash: '',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const listener = sinon.spy();
|
|
146
|
+
const unsubscribe = environment.subscribe(listener);
|
|
147
|
+
|
|
148
|
+
environment.shift(-1);
|
|
149
|
+
await delay(20);
|
|
150
|
+
|
|
151
|
+
expect(listener).to.have.been.calledOnce();
|
|
152
|
+
expect(listener.firstCall.args[0]).to.include({
|
|
153
|
+
action: 'POP',
|
|
154
|
+
pathname: '/bar',
|
|
155
|
+
});
|
|
156
|
+
listener.resetHistory();
|
|
157
|
+
|
|
158
|
+
unsubscribe();
|
|
159
|
+
|
|
160
|
+
environment.shift(-1);
|
|
161
|
+
await delay(20);
|
|
162
|
+
|
|
163
|
+
expect(listener).not.to.have.been.called();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import MemoryEnvironment from '../../src/environment/MemoryEnvironment';
|
|
2
|
+
|
|
3
|
+
const STATE_KEY = '@@navigation-stack/environment-state';
|
|
4
|
+
|
|
5
|
+
function save(state) {
|
|
6
|
+
window.sessionStorage.setItem(STATE_KEY, state);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function load() {
|
|
10
|
+
return window.sessionStorage.getItem(STATE_KEY);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('MemoryEnvironment', () => {
|
|
14
|
+
it('should parse the initial location', () => {
|
|
15
|
+
const environment = new MemoryEnvironment('/foo?bar=baz#qux');
|
|
16
|
+
|
|
17
|
+
expect(environment.init()).to.eql({
|
|
18
|
+
action: 'POP',
|
|
19
|
+
pathname: '/foo',
|
|
20
|
+
search: '?bar=baz',
|
|
21
|
+
query: {
|
|
22
|
+
bar: 'baz',
|
|
23
|
+
},
|
|
24
|
+
hash: '#qux',
|
|
25
|
+
index: 0,
|
|
26
|
+
delta: 0,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should support basic navigation', () => {
|
|
31
|
+
const environment = new MemoryEnvironment('/foo');
|
|
32
|
+
|
|
33
|
+
const listener = sinon.spy();
|
|
34
|
+
environment.subscribe(listener);
|
|
35
|
+
|
|
36
|
+
const barLocation = environment.navigate({
|
|
37
|
+
action: 'PUSH',
|
|
38
|
+
pathname: '/bar',
|
|
39
|
+
state: { the: 'state' },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(barLocation).to.deep.include({
|
|
43
|
+
action: 'PUSH',
|
|
44
|
+
pathname: '/bar',
|
|
45
|
+
index: 1,
|
|
46
|
+
delta: 1,
|
|
47
|
+
state: { the: 'state' },
|
|
48
|
+
});
|
|
49
|
+
expect(barLocation.key).not.to.be.empty();
|
|
50
|
+
|
|
51
|
+
expect(
|
|
52
|
+
environment.navigate({ action: 'PUSH', pathname: '/baz' }),
|
|
53
|
+
).to.include({
|
|
54
|
+
action: 'PUSH',
|
|
55
|
+
pathname: '/baz',
|
|
56
|
+
index: 2,
|
|
57
|
+
delta: 1,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(
|
|
61
|
+
environment.navigate({ action: 'REPLACE', pathname: '/qux' }),
|
|
62
|
+
).to.include({
|
|
63
|
+
action: 'REPLACE',
|
|
64
|
+
pathname: '/qux',
|
|
65
|
+
index: 2,
|
|
66
|
+
delta: 0,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(listener).not.to.have.been.called();
|
|
70
|
+
|
|
71
|
+
environment.shift(-1);
|
|
72
|
+
|
|
73
|
+
expect(listener).to.have.been.calledOnce();
|
|
74
|
+
expect(listener.firstCall.args[0]).to.deep.include({
|
|
75
|
+
action: 'POP',
|
|
76
|
+
pathname: '/bar',
|
|
77
|
+
key: barLocation.key,
|
|
78
|
+
index: 1,
|
|
79
|
+
delta: -1,
|
|
80
|
+
state: { the: 'state' },
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should support subscribing and unsubscribing', () => {
|
|
85
|
+
const environment = new MemoryEnvironment('/foo');
|
|
86
|
+
environment.navigate({ action: 'PUSH', pathname: '/bar' });
|
|
87
|
+
environment.navigate({ action: 'PUSH', pathname: '/baz' });
|
|
88
|
+
|
|
89
|
+
const listener = sinon.spy();
|
|
90
|
+
const unsubscribe = environment.subscribe(listener);
|
|
91
|
+
|
|
92
|
+
environment.shift(-1);
|
|
93
|
+
|
|
94
|
+
expect(listener).to.have.been.calledOnce();
|
|
95
|
+
expect(listener.firstCall.args[0]).to.include({
|
|
96
|
+
action: 'POP',
|
|
97
|
+
pathname: '/bar',
|
|
98
|
+
});
|
|
99
|
+
listener.resetHistory();
|
|
100
|
+
|
|
101
|
+
unsubscribe();
|
|
102
|
+
|
|
103
|
+
environment.shift(-1);
|
|
104
|
+
|
|
105
|
+
expect(listener).not.to.have.been.called();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should respect stack bounds', () => {
|
|
109
|
+
const environment = new MemoryEnvironment('/foo');
|
|
110
|
+
environment.navigate({ action: 'PUSH', pathname: '/bar' });
|
|
111
|
+
environment.navigate({ action: 'PUSH', pathname: '/baz' });
|
|
112
|
+
|
|
113
|
+
const listener = sinon.spy();
|
|
114
|
+
environment.subscribe(listener);
|
|
115
|
+
|
|
116
|
+
environment.shift(-390);
|
|
117
|
+
|
|
118
|
+
expect(listener).to.have.been.calledOnce();
|
|
119
|
+
expect(listener.firstCall.args[0]).to.include({
|
|
120
|
+
action: 'POP',
|
|
121
|
+
pathname: '/foo',
|
|
122
|
+
delta: -2,
|
|
123
|
+
});
|
|
124
|
+
listener.resetHistory();
|
|
125
|
+
|
|
126
|
+
environment.shift(-1);
|
|
127
|
+
|
|
128
|
+
expect(listener).not.to.have.been.called();
|
|
129
|
+
|
|
130
|
+
environment.shift(+22);
|
|
131
|
+
|
|
132
|
+
expect(listener).to.have.been.calledOnce();
|
|
133
|
+
expect(listener.firstCall.args[0]).to.include({
|
|
134
|
+
action: 'POP',
|
|
135
|
+
pathname: '/baz',
|
|
136
|
+
delta: 2,
|
|
137
|
+
});
|
|
138
|
+
listener.resetHistory();
|
|
139
|
+
|
|
140
|
+
environment.shift(+1);
|
|
141
|
+
|
|
142
|
+
expect(listener).not.to.have.been.called();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should reset forward entries on push', () => {
|
|
146
|
+
const environment = new MemoryEnvironment('/foo');
|
|
147
|
+
environment.navigate({ action: 'PUSH', pathname: '/bar' });
|
|
148
|
+
environment.navigate({ action: 'PUSH', pathname: '/baz' });
|
|
149
|
+
environment.shift(-2);
|
|
150
|
+
environment.navigate({ action: 'REPLACE', pathname: '/qux' });
|
|
151
|
+
|
|
152
|
+
const listener = sinon.spy();
|
|
153
|
+
environment.subscribe(listener);
|
|
154
|
+
|
|
155
|
+
environment.shift(+1);
|
|
156
|
+
|
|
157
|
+
expect(listener).to.have.been.calledOnce();
|
|
158
|
+
expect(listener.firstCall.args[0]).to.include({
|
|
159
|
+
action: 'POP',
|
|
160
|
+
pathname: '/bar',
|
|
161
|
+
delta: 1,
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should not reset forward entries on replace', () => {
|
|
166
|
+
const environment = new MemoryEnvironment('/foo');
|
|
167
|
+
environment.navigate({ action: 'PUSH', pathname: '/bar' });
|
|
168
|
+
environment.navigate({ action: 'PUSH', pathname: '/baz' });
|
|
169
|
+
environment.shift(-2);
|
|
170
|
+
environment.navigate({ action: 'PUSH', pathname: '/qux' });
|
|
171
|
+
|
|
172
|
+
const listener = sinon.spy();
|
|
173
|
+
environment.subscribe(listener);
|
|
174
|
+
|
|
175
|
+
environment.shift(+1);
|
|
176
|
+
|
|
177
|
+
expect(listener).not.to.have.been.called();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('persistence', () => {
|
|
181
|
+
beforeEach(() => {
|
|
182
|
+
window.sessionStorage.clear();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should support persistence', () => {
|
|
186
|
+
const environment1 = new MemoryEnvironment('/foo', { save, load });
|
|
187
|
+
expect(environment1.init()).to.include({
|
|
188
|
+
pathname: '/foo',
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
environment1.navigate({ action: 'PUSH', pathname: '/bar' });
|
|
192
|
+
environment1.navigate({ action: 'PUSH', pathname: '/baz' });
|
|
193
|
+
environment1.shift(-1);
|
|
194
|
+
|
|
195
|
+
const environment2 = new MemoryEnvironment('/foo', { save, load });
|
|
196
|
+
expect(environment2.init()).to.include({
|
|
197
|
+
pathname: '/bar',
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
environment2.shift(+1);
|
|
201
|
+
expect(environment2.init()).to.include({
|
|
202
|
+
pathname: '/baz',
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should ignore broken session storage entry', () => {
|
|
207
|
+
sessionStorage.setItem(
|
|
208
|
+
'@@navigation-stack/state',
|
|
209
|
+
JSON.stringify({ stack: [], index: 2 }),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const environment = new MemoryEnvironment('/foo', { save, load });
|
|
213
|
+
expect(environment.init()).to.include({
|
|
214
|
+
pathname: '/foo',
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import ServerEnvironment from '../../src/environment/ServerEnvironment';
|
|
2
|
+
|
|
3
|
+
describe('ServerEnvironment', () => {
|
|
4
|
+
it('should parse the initial location', () => {
|
|
5
|
+
const environment = new ServerEnvironment('/foo?bar=baz#qux');
|
|
6
|
+
|
|
7
|
+
expect(environment.init()).to.eql({
|
|
8
|
+
action: 'POP',
|
|
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 environment = new ServerEnvironment('/foo?bar=baz#qux');
|
|
20
|
+
const unsubscribe = environment.subscribe();
|
|
21
|
+
expect(unsubscribe).to.not.throw();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import getLocationUrl from '../src/getLocationUrl';
|
|
2
|
+
|
|
3
|
+
describe('getLocationUrl', () => {
|
|
4
|
+
it('should get location URL (`pathname`, `search`, and `hash`)', () => {
|
|
5
|
+
expect(
|
|
6
|
+
getLocationUrl({
|
|
7
|
+
pathname: '/foo',
|
|
8
|
+
search: '?bar=baz',
|
|
9
|
+
hash: '#qux',
|
|
10
|
+
}),
|
|
11
|
+
).to.equal('/foo?bar=baz#qux');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should get location URL (no `search` but with `query`)', () => {
|
|
15
|
+
expect(
|
|
16
|
+
getLocationUrl({
|
|
17
|
+
pathname: '/foo',
|
|
18
|
+
query: {
|
|
19
|
+
bar: 'baz',
|
|
20
|
+
},
|
|
21
|
+
hash: '#qux',
|
|
22
|
+
}),
|
|
23
|
+
).to.equal('/foo?bar=baz#qux');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should get location URL (just `pathname`)', () => {
|
|
27
|
+
expect(
|
|
28
|
+
getLocationUrl({
|
|
29
|
+
pathname: '/foo',
|
|
30
|
+
}),
|
|
31
|
+
).to.equal('/foo');
|
|
32
|
+
});
|
|
33
|
+
});
|
package/test/helpers.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import ActionTypes from '../src/ActionTypes';
|
|
2
|
+
|
|
3
|
+
export function shouldWarn(about) {
|
|
4
|
+
console.warn.expected.push(about); // eslint-disable-line no-console
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function invokeLocationMiddleware(middleware, action) {
|
|
8
|
+
let result;
|
|
9
|
+
|
|
10
|
+
function next(nextAction) {
|
|
11
|
+
result = nextAction;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
middleware()(next)(action);
|
|
15
|
+
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function transformInputLocationUsingMiddleware(middleware, location) {
|
|
20
|
+
return invokeLocationMiddleware(middleware, {
|
|
21
|
+
type: ActionTypes.NAVIGATE,
|
|
22
|
+
payload: location,
|
|
23
|
+
}).payload;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function transformEnvironmentLocationUsingMiddleware(
|
|
27
|
+
middleware,
|
|
28
|
+
location,
|
|
29
|
+
) {
|
|
30
|
+
return invokeLocationMiddleware(middleware, {
|
|
31
|
+
type: ActionTypes.UPDATE,
|
|
32
|
+
payload: location,
|
|
33
|
+
}).payload;
|
|
34
|
+
}
|
package/test/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import dirtyChai from 'dirty-chai';
|
|
2
|
+
|
|
3
|
+
global.chai.use(dirtyChai);
|
|
4
|
+
|
|
5
|
+
// const testsContext = import.meta.webpackContext('.', true, /\.test\.js$/);
|
|
6
|
+
const testsContext = require.context('.', true, /\.test\.js$/);
|
|
7
|
+
testsContext.keys().forEach(testsContext);
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
/* eslint-disable no-console */
|
|
11
|
+
sinon.stub(console, 'warn').callsFake((message) => {
|
|
12
|
+
let expected = false;
|
|
13
|
+
|
|
14
|
+
console.warn.expected.forEach((about) => {
|
|
15
|
+
if (message.includes(about)) {
|
|
16
|
+
console.warn.warned[about] = true;
|
|
17
|
+
expected = true;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (expected) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.warn.threw = true;
|
|
26
|
+
throw new Error(message);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
console.warn.expected = [];
|
|
30
|
+
console.warn.warned = Object.create(null);
|
|
31
|
+
console.warn.threw = false;
|
|
32
|
+
/* eslint-enable no-console */
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
/* eslint-disable no-console */
|
|
37
|
+
const { expected, warned, threw } = console.warn;
|
|
38
|
+
console.warn.restore();
|
|
39
|
+
|
|
40
|
+
if (!threw && expected.length) {
|
|
41
|
+
expect(warned).to.have.keys(expected);
|
|
42
|
+
}
|
|
43
|
+
/* eslint-enable no-console */
|
|
44
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as exports from '../src';
|
|
2
|
+
|
|
3
|
+
describe('index', () => {
|
|
4
|
+
it('should export top level correctly', () => {
|
|
5
|
+
expect(exports.Actions).to.exist();
|
|
6
|
+
expect(exports.ActionTypes).to.exist();
|
|
7
|
+
expect(exports.addBasePath).to.exist();
|
|
8
|
+
expect(exports.removeBasePath).to.exist();
|
|
9
|
+
expect(exports.getLocationUrl).to.exist();
|
|
10
|
+
expect(exports.parseLocationUrl).to.exist();
|
|
11
|
+
expect(exports.addNavigationBlocker).to.exist();
|
|
12
|
+
expect(exports.getLocationUrl).to.exist();
|
|
13
|
+
expect(exports.parseLocationUrl).to.exist();
|
|
14
|
+
expect(exports.createMiddlewares).to.exist();
|
|
15
|
+
expect(exports.locationReducer).to.exist();
|
|
16
|
+
expect(exports.BrowserEnvironment).to.exist();
|
|
17
|
+
expect(exports.MemoryEnvironment).to.exist();
|
|
18
|
+
expect(exports.ServerEnvironment).to.exist();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import ActionTypes from '../src/ActionTypes';
|
|
2
|
+
import locationReducer from '../src/locationReducer';
|
|
3
|
+
|
|
4
|
+
describe('locationReducer', () => {
|
|
5
|
+
const prevState = {
|
|
6
|
+
action: 'PUSH',
|
|
7
|
+
delta: 1,
|
|
8
|
+
hash: '',
|
|
9
|
+
index: 5,
|
|
10
|
+
key: 'h0j8qq:4',
|
|
11
|
+
pathname: '/new/path',
|
|
12
|
+
query: {},
|
|
13
|
+
search: '',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
it('should handle UPDATE', () => {
|
|
17
|
+
const newLocation = {
|
|
18
|
+
action: 'PUSH',
|
|
19
|
+
delta: 1,
|
|
20
|
+
hash: '#qux',
|
|
21
|
+
index: 6,
|
|
22
|
+
key: 'h0j8qq:5',
|
|
23
|
+
pathname: '/foo',
|
|
24
|
+
query: {
|
|
25
|
+
bar: 'baz',
|
|
26
|
+
},
|
|
27
|
+
search: '?bar=baz',
|
|
28
|
+
};
|
|
29
|
+
const action = {
|
|
30
|
+
type: ActionTypes.UPDATE,
|
|
31
|
+
payload: newLocation,
|
|
32
|
+
};
|
|
33
|
+
expect(locationReducer(prevState, action)).to.eql(newLocation);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should not handle unknown action', () => {
|
|
37
|
+
const unknownAction = {
|
|
38
|
+
type: 'UNKNOWN',
|
|
39
|
+
};
|
|
40
|
+
expect(locationReducer(prevState, unknownAction)).to.equal(prevState);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import createBasePathMiddleware from '../../src/middleware/createBasePathMiddleware';
|
|
2
|
+
import {
|
|
3
|
+
transformEnvironmentLocationUsingMiddleware,
|
|
4
|
+
transformInputLocationUsingMiddleware,
|
|
5
|
+
} from '../helpers';
|
|
6
|
+
|
|
7
|
+
describe('createBasePathMiddleware', () => {
|
|
8
|
+
[
|
|
9
|
+
['/foo', 'generic `basePath`'],
|
|
10
|
+
['/foo/', '`basePath` with a trailing slash'],
|
|
11
|
+
].forEach(([basePath, title]) => {
|
|
12
|
+
describe(title, () => {
|
|
13
|
+
const basePathMiddleware = createBasePathMiddleware(basePath);
|
|
14
|
+
|
|
15
|
+
it('should prepend `basePath` to `location.pathname` on input locations', () => {
|
|
16
|
+
expect(
|
|
17
|
+
transformInputLocationUsingMiddleware(basePathMiddleware, {
|
|
18
|
+
pathname: '/path',
|
|
19
|
+
}),
|
|
20
|
+
).to.eql({
|
|
21
|
+
pathname: '/foo/path',
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should strip `basePath` from `location.pathname` on environment locations', () => {
|
|
26
|
+
expect(
|
|
27
|
+
transformEnvironmentLocationUsingMiddleware(basePathMiddleware, {
|
|
28
|
+
pathname: '/foo/path',
|
|
29
|
+
}),
|
|
30
|
+
).to.eql({
|
|
31
|
+
pathname: '/path',
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should handle unrecognized paths on environment locations', () => {
|
|
36
|
+
expect(
|
|
37
|
+
transformEnvironmentLocationUsingMiddleware(basePathMiddleware, {
|
|
38
|
+
pathname: '/bar/path',
|
|
39
|
+
}),
|
|
40
|
+
).to.eql({
|
|
41
|
+
pathname: '/bar/path',
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('No `basePath` specified', () => {
|
|
48
|
+
const basePathMiddleware = createBasePathMiddleware('/');
|
|
49
|
+
|
|
50
|
+
it('should not modify `location.pathname` of input locations', () => {
|
|
51
|
+
const location = { pathname: '/path' };
|
|
52
|
+
expect(
|
|
53
|
+
transformInputLocationUsingMiddleware(basePathMiddleware, location),
|
|
54
|
+
).to.equal(location);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should not modify `location.pathname` of environment locations', () => {
|
|
58
|
+
const location = { pathname: '/path' };
|
|
59
|
+
expect(
|
|
60
|
+
transformEnvironmentLocationUsingMiddleware(
|
|
61
|
+
basePathMiddleware,
|
|
62
|
+
location,
|
|
63
|
+
),
|
|
64
|
+
).to.equal(location);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|