1
+ import fakePromise from 'faked-promise'
1
2
import { createRouter as newRouter , createMemoryHistory } from '../src'
2
- import { ErrorTypes } from '../src/errors'
3
- import { components } from './utils'
4
- import { RouteRecordRaw } from '../src/types'
3
+ import { NavigationFailure , NavigationFailureType } from '../src/errors'
4
+ import { components , tick } from './utils'
5
+ import { RouteRecordRaw , NavigationGuard , RouteLocationRaw } from '../src/types'
5
6
6
7
const routes : RouteRecordRaw [ ] = [
7
8
{ path : '/' , component : components . Home } ,
9
+ { path : '/redirect' , redirect : '/' } ,
8
10
{ path : '/foo' , component : components . Foo , name : 'Foo' } ,
9
- { path : '/to-foo' , redirect : '/foo' } ,
10
- { path : '/to-foo-named' , redirect : { name : 'Foo' } } ,
11
- { path : '/to-foo2' , redirect : '/to-foo' } ,
12
- { path : '/p/:p' , name : 'Param' , component : components . Bar } ,
13
- { path : '/to-p/:p' , redirect : to => `/p/${ to . params . p } ` } ,
14
- {
15
- path : '/inc-query-hash' ,
16
- redirect : to => ( {
17
- name : 'Foo' ,
18
- query : { n : to . query . n + '-2' } ,
19
- hash : to . hash + '-2' ,
20
- } ) ,
21
- } ,
22
11
]
23
12
24
13
const onError = jest . fn ( )
14
+ const afterEach = jest . fn ( )
25
15
function createRouter ( ) {
26
16
const history = createMemoryHistory ( )
27
17
const router = newRouter ( {
@@ -30,27 +20,298 @@ function createRouter() {
30
20
} )
31
21
32
22
router . onError ( onError )
23
+ router . afterEach ( afterEach )
33
24
return { router, history }
34
25
}
35
26
36
- describe ( 'Errors' , ( ) => {
27
+ describe ( 'Errors & Navigation failures ' , ( ) => {
37
28
beforeEach ( ( ) => {
38
29
onError . mockReset ( )
30
+ afterEach . mockReset ( )
39
31
} )
40
32
41
- it ( 'triggers onError when navigation is aborted' , async ( ) => {
33
+ it ( 'next(false) triggers afterEach' , async ( ) => {
34
+ await testNavigation (
35
+ false ,
36
+ expect . objectContaining ( {
37
+ type : NavigationFailureType . aborted ,
38
+ } )
39
+ )
40
+ } )
41
+
42
+ it ( 'next("/location") triggers afterEach' , async ( ) => {
43
+ await testNavigation (
44
+ ( ( to , from , next ) => {
45
+ if ( to . path === '/location' ) next ( )
46
+ else next ( '/location' )
47
+ } ) as NavigationGuard ,
48
+ undefined
49
+ )
50
+ } )
51
+
52
+ it ( 'redirect triggers afterEach' , async ( ) => {
53
+ await testNavigation ( undefined , undefined , '/redirect' )
54
+ } )
55
+
56
+ it ( 'next() triggers afterEach' , async ( ) => {
57
+ await testNavigation ( undefined , undefined )
58
+ } )
59
+
60
+ it ( 'next(true) triggers afterEach' , async ( ) => {
61
+ await testNavigation ( true , undefined )
62
+ } )
63
+
64
+ it ( 'triggers afterEach if a new navigation happens' , async ( ) => {
42
65
const { router } = createRouter ( )
66
+ const [ promise , resolve ] = fakePromise ( )
43
67
router . beforeEach ( ( to , from , next ) => {
44
- next ( false )
68
+ // let it hang otherwise
69
+ if ( to . path === '/' ) next ( )
70
+ else promise . then ( ( ) => next ( ) )
45
71
} )
46
72
47
- try {
48
- await router . push ( '/foo' )
49
- } catch ( err ) {
50
- expect ( err . type ) . toBe ( ErrorTypes . NAVIGATION_ABORTED )
51
- }
52
- expect ( onError ) . toHaveBeenCalledWith (
53
- expect . objectContaining ( { type : ErrorTypes . NAVIGATION_ABORTED } )
73
+ let from = router . currentRoute . value
74
+
75
+ // should hang
76
+ let navigationPromise = router . push ( '/foo' )
77
+
78
+ await expect ( router . push ( '/' ) ) . resolves . toEqual ( undefined )
79
+ expect ( afterEach ) . toHaveBeenCalledTimes ( 1 )
80
+ expect ( onError ) . toHaveBeenCalledTimes ( 0 )
81
+
82
+ resolve ( )
83
+ await navigationPromise
84
+ expect ( afterEach ) . toHaveBeenCalledTimes ( 2 )
85
+ expect ( onError ) . toHaveBeenCalledTimes ( 0 )
86
+
87
+ expect ( afterEach ) . toHaveBeenLastCalledWith (
88
+ expect . objectContaining ( { path : '/foo' } ) ,
89
+ from ,
90
+ expect . objectContaining ( { type : NavigationFailureType . cancelled } )
54
91
)
55
92
} )
93
+
94
+ it ( 'next(new Error()) triggers onError' , async ( ) => {
95
+ let error = new Error ( )
96
+ await testError ( error , error )
97
+ } )
98
+
99
+ it ( 'triggers onError with thrown errors' , async ( ) => {
100
+ let error = new Error ( )
101
+ await testError ( ( ) => {
102
+ throw error
103
+ } , error )
104
+ } )
105
+
106
+ it ( 'triggers onError with rejected promises' , async ( ) => {
107
+ let error = new Error ( )
108
+ await testError ( async ( ) => {
109
+ throw error
110
+ } , error )
111
+ } )
112
+
113
+ describe ( 'history navigation' , ( ) => {
114
+ it ( 'triggers afterEach with history.back' , async ( ) => {
115
+ const { router, history } = createRouter ( )
116
+
117
+ await router . push ( '/' )
118
+ await router . push ( '/foo' )
119
+
120
+ afterEach . mockReset ( )
121
+ onError . mockReset ( )
122
+
123
+ const [ promise , resolve ] = fakePromise ( )
124
+ router . beforeEach ( ( to , from , next ) => {
125
+ // let it hang otherwise
126
+ if ( to . path === '/' ) next ( )
127
+ else promise . then ( ( ) => next ( ) )
128
+ } )
129
+
130
+ let from = router . currentRoute . value
131
+
132
+ // should hang
133
+ let navigationPromise = router . push ( '/bar' )
134
+
135
+ // goes from /foo to /
136
+ history . go ( - 1 )
137
+
138
+ await tick ( )
139
+
140
+ expect ( afterEach ) . toHaveBeenCalledTimes ( 1 )
141
+ expect ( onError ) . toHaveBeenCalledTimes ( 0 )
142
+ expect ( afterEach ) . toHaveBeenLastCalledWith (
143
+ expect . objectContaining ( { path : '/' } ) ,
144
+ from ,
145
+ undefined
146
+ )
147
+
148
+ resolve ( )
149
+ await expect ( navigationPromise ) . resolves . toEqual (
150
+ expect . objectContaining ( { type : NavigationFailureType . cancelled } )
151
+ )
152
+
153
+ expect ( afterEach ) . toHaveBeenCalledTimes ( 2 )
154
+ expect ( onError ) . toHaveBeenCalledTimes ( 0 )
155
+
156
+ expect ( afterEach ) . toHaveBeenLastCalledWith (
157
+ expect . objectContaining ( { path : '/bar' } ) ,
158
+ from ,
159
+ expect . objectContaining ( { type : NavigationFailureType . cancelled } )
160
+ )
161
+ } )
162
+
163
+ it ( 'next(false) triggers afterEach with history.back' , async ( ) => {
164
+ await testHistoryNavigation (
165
+ false ,
166
+ expect . objectContaining ( { type : NavigationFailureType . aborted } )
167
+ )
168
+ } )
169
+
170
+ it ( 'next("/location") triggers afterEach with history.back' , async ( ) => {
171
+ await testHistoryNavigation (
172
+ ( ( to , from , next ) => {
173
+ if ( to . path === '/location' ) next ( )
174
+ else next ( '/location' )
175
+ } ) as NavigationGuard ,
176
+ undefined
177
+ )
178
+ } )
179
+
180
+ it ( 'next() triggers afterEach with history.back' , async ( ) => {
181
+ await testHistoryNavigation ( undefined , undefined )
182
+ } )
183
+
184
+ it ( 'next(true) triggers afterEach with history.back' , async ( ) => {
185
+ await testHistoryNavigation ( true , undefined )
186
+ } )
187
+
188
+ it ( 'next(new Error()) triggers onError with history.back' , async ( ) => {
189
+ let error = new Error ( )
190
+ await testHistoryError ( error , error )
191
+ } )
192
+
193
+ it ( 'triggers onError with thrown errors with history.back' , async ( ) => {
194
+ let error = new Error ( )
195
+ await testHistoryError ( ( ) => {
196
+ throw error
197
+ } , error )
198
+ } )
199
+
200
+ it ( 'triggers onError with rejected promises with history.back' , async ( ) => {
201
+ let error = new Error ( )
202
+ await testHistoryError ( async ( ) => {
203
+ throw error
204
+ } , error )
205
+ } )
206
+ } )
56
207
} )
208
+
209
+ async function testError (
210
+ nextArgument : any | NavigationGuard ,
211
+ expectedError : Error | void = undefined ,
212
+ to : RouteLocationRaw = '/foo'
213
+ ) {
214
+ const { router } = createRouter ( )
215
+ router . beforeEach (
216
+ typeof nextArgument === 'function'
217
+ ? nextArgument
218
+ : ( to , from , next ) => {
219
+ next ( nextArgument )
220
+ }
221
+ )
222
+
223
+ await expect ( router . push ( to ) ) . rejects . toEqual ( expectedError )
224
+
225
+ expect ( afterEach ) . toHaveBeenCalledTimes ( 0 )
226
+ expect ( onError ) . toHaveBeenCalledTimes ( 1 )
227
+
228
+ expect ( onError ) . toHaveBeenCalledWith ( expectedError )
229
+ }
230
+
231
+ async function testNavigation (
232
+ nextArgument : any | NavigationGuard ,
233
+ expectedFailure : NavigationFailure | void = undefined ,
234
+ to : RouteLocationRaw = '/foo'
235
+ ) {
236
+ const { router } = createRouter ( )
237
+ router . beforeEach (
238
+ typeof nextArgument === 'function'
239
+ ? nextArgument
240
+ : ( to , from , next ) => {
241
+ next ( nextArgument )
242
+ }
243
+ )
244
+
245
+ await expect ( router . push ( to ) ) . resolves . toEqual ( expectedFailure )
246
+
247
+ expect ( afterEach ) . toHaveBeenCalledTimes ( 1 )
248
+ expect ( onError ) . toHaveBeenCalledTimes ( 0 )
249
+
250
+ expect ( afterEach ) . toHaveBeenCalledWith (
251
+ expect . any ( Object ) ,
252
+ expect . any ( Object ) ,
253
+ expectedFailure
254
+ )
255
+ }
256
+
257
+ async function testHistoryNavigation (
258
+ nextArgument : any | NavigationGuard ,
259
+ expectedFailure : NavigationFailure | void = undefined ,
260
+ to : RouteLocationRaw = '/foo'
261
+ ) {
262
+ const { router, history } = createRouter ( )
263
+ await router . push ( to )
264
+
265
+ router . beforeEach (
266
+ typeof nextArgument === 'function'
267
+ ? nextArgument
268
+ : ( to , from , next ) => {
269
+ next ( nextArgument )
270
+ }
271
+ )
272
+
273
+ afterEach . mockReset ( )
274
+ onError . mockReset ( )
275
+
276
+ history . go ( - 1 )
277
+
278
+ await tick ( )
279
+
280
+ expect ( afterEach ) . toHaveBeenCalledTimes ( 1 )
281
+ expect ( onError ) . toHaveBeenCalledTimes ( 0 )
282
+
283
+ expect ( afterEach ) . toHaveBeenCalledWith (
284
+ expect . any ( Object ) ,
285
+ expect . any ( Object ) ,
286
+ expectedFailure
287
+ )
288
+ }
289
+
290
+ async function testHistoryError (
291
+ nextArgument : any | NavigationGuard ,
292
+ expectedError : Error | void = undefined ,
293
+ to : RouteLocationRaw = '/foo'
294
+ ) {
295
+ const { router, history } = createRouter ( )
296
+ await router . push ( to )
297
+
298
+ router . beforeEach (
299
+ typeof nextArgument === 'function'
300
+ ? nextArgument
301
+ : ( to , from , next ) => {
302
+ next ( nextArgument )
303
+ }
304
+ )
305
+
306
+ afterEach . mockReset ( )
307
+ onError . mockReset ( )
308
+
309
+ history . go ( - 1 )
310
+
311
+ await tick ( )
312
+
313
+ expect ( afterEach ) . toHaveBeenCalledTimes ( 0 )
314
+ expect ( onError ) . toHaveBeenCalledTimes ( 1 )
315
+
316
+ expect ( onError ) . toHaveBeenCalledWith ( expectedError )
317
+ }
0 commit comments