1
- import { Component , Provider , Injectable } from '@angular/core' ;
1
+ import { Component , Provider , Injectable , ErrorHandler } from '@angular/core' ;
2
2
import { TestBed } from '@angular/core/testing' ;
3
- import { NavigationEnd , Router , RouterStateSnapshot } from '@angular/router' ;
3
+ import {
4
+ NavigationEnd ,
5
+ Router ,
6
+ RouterStateSnapshot ,
7
+ NavigationCancel ,
8
+ NavigationError ,
9
+ } from '@angular/router' ;
4
10
import { RouterTestingModule } from '@angular/router/testing' ;
5
11
import { Store , StoreModule , ScannedActionsSubject } from '@ngrx/store' ;
6
12
import { filter , first , mapTo , take } from 'rxjs/operators' ;
@@ -19,6 +25,7 @@ import {
19
25
ROUTER_REQUEST ,
20
26
ROUTER_NAVIGATED ,
21
27
NavigationActionTiming ,
28
+ RouterReducerState ,
22
29
} from '../src/router_store_module' ;
23
30
24
31
describe ( 'integration spec' , ( ) => {
@@ -119,7 +126,7 @@ describe('integration spec', () => {
119
126
} ) ;
120
127
} ) ;
121
128
122
- it ( 'should support rolling back if navigation gets canceled' , ( done : any ) => {
129
+ it ( 'should support rolling back if navigation gets canceled (navigation initialized through router) ' , ( done : any ) => {
123
130
const reducer = ( state : string = '' , action : RouterAction < any > ) : any => {
124
131
if ( action . type === ROUTER_NAVIGATION ) {
125
132
return {
@@ -176,9 +183,9 @@ describe('integration spec', () => {
176
183
{
177
184
type : 'store' ,
178
185
state : {
179
- url : '/next ' ,
186
+ url : '/' ,
180
187
lastAction : ROUTER_CANCEL ,
181
- storeState : { url : '/next ' , lastAction : ROUTER_NAVIGATION } ,
188
+ storeState : { url : '/' , lastAction : ROUTER_NAVIGATION } ,
182
189
} ,
183
190
} ,
184
191
{ type : 'action' , action : ROUTER_CANCEL } ,
@@ -189,7 +196,67 @@ describe('integration spec', () => {
189
196
} ) ;
190
197
} ) ;
191
198
192
- it ( 'should support rolling back if navigation errors' , ( done : any ) => {
199
+ it ( 'should support rolling back if navigation gets canceled (navigation initialized through store)' , ( done : any ) => {
200
+ const CHANGE_ROUTE = 'CHANGE_ROUTE' ;
201
+ const reducer = (
202
+ state : RouterReducerState ,
203
+ action : any
204
+ ) : RouterReducerState => {
205
+ if ( action . type === CHANGE_ROUTE ) {
206
+ return {
207
+ state : { url : '/next' , root : < any > { } } ,
208
+ navigationId : 123 ,
209
+ } ;
210
+ } else {
211
+ const nextState = routerReducer ( state , action ) ;
212
+ if ( nextState && nextState . state ) {
213
+ nextState . state . root = < any > { } ;
214
+ }
215
+ return nextState ;
216
+ }
217
+ } ;
218
+
219
+ createTestModule ( {
220
+ reducers : { reducer } ,
221
+ canActivate : ( ) => false ,
222
+ config : { stateKey : 'reducer' } ,
223
+ } ) ;
224
+
225
+ const router : Router = TestBed . get ( Router ) ;
226
+ const store : Store < any > = TestBed . get ( Store ) ;
227
+ const log = logOfRouterAndActionsAndStore ( ) ;
228
+
229
+ router
230
+ . navigateByUrl ( '/' )
231
+ . then ( ( ) => {
232
+ log . splice ( 0 ) ;
233
+ store . dispatch ( { type : CHANGE_ROUTE } ) ;
234
+ return waitForNavigation ( router , NavigationCancel ) ;
235
+ } )
236
+ . then ( ( ) => {
237
+ expect ( log ) . toEqual ( [
238
+ { type : 'router' , event : 'NavigationStart' , url : '/next' } ,
239
+ {
240
+ type : 'store' ,
241
+ state : { state : { url : '/next' , root : { } } , navigationId : 123 } ,
242
+ } ,
243
+ { type : 'action' , action : CHANGE_ROUTE } ,
244
+ { type : 'router' , event : 'RoutesRecognized' , url : '/next' } ,
245
+ { type : 'router' , event : 'GuardsCheckStart' , url : '/next' } ,
246
+ { type : 'router' , event : 'GuardsCheckEnd' , url : '/next' } ,
247
+ {
248
+ type : 'store' ,
249
+ state : { state : { url : '/' , root : { } } , navigationId : 2 } ,
250
+ } ,
251
+ { type : 'action' , action : ROUTER_CANCEL } ,
252
+ { type : 'router' , event : 'NavigationCancel' , url : '/next' } ,
253
+ ] ) ;
254
+
255
+ done ( ) ;
256
+ } ) ;
257
+ } ) ;
258
+
259
+ it ( 'should support rolling back if navigation errors (navigation initialized through router)' , ( done : any ) => {
193
260
const reducer = ( state : string = '' , action : RouterAction < any > ) : any => {
194
261
if ( action . type === ROUTER_NAVIGATION ) {
195
262
return {
@@ -246,9 +313,9 @@ describe('integration spec', () => {
246
313
{
247
314
type : 'store' ,
248
315
state : {
249
- url : '/next ' ,
316
+ url : '/' ,
250
317
lastAction : ROUTER_ERROR ,
251
- storeState : { url : '/next ' , lastAction : ROUTER_NAVIGATION } ,
318
+ storeState : { url : '/' , lastAction : ROUTER_NAVIGATION } ,
252
319
} ,
253
320
} ,
254
321
{ type : 'action' , action : ROUTER_ERROR } ,
@@ -259,6 +326,75 @@ describe('integration spec', () => {
259
326
} ) ;
260
327
} ) ;
261
328
329
+ it ( 'should support rolling back if navigation errors and hand error to error handler (navigation initialized through store)' , ( done : any ) => {
330
+ const CHANGE_ROUTE = 'CHANGE_ROUTE' ;
331
+ const reducer = (
332
+ state : RouterReducerState ,
333
+ action : any
334
+ ) : RouterReducerState => {
335
+ if ( action . type === CHANGE_ROUTE ) {
336
+ return {
337
+ state : { url : '/next' , root : < any > { } } ,
338
+ navigationId : 123 ,
339
+ } ;
340
+ } else {
341
+ const nextState = routerReducer ( state , action ) ;
342
+ if ( nextState && nextState . state ) {
343
+ nextState . state . root = < any > { } ;
344
+ }
345
+ return nextState ;
346
+ }
347
+ } ;
348
+
349
+ const routerError = new Error ( 'BOOM!' ) ;
350
+ class SilentErrorHandler implements ErrorHandler {
351
+ handleError ( error : any ) {
352
+ expect ( error ) . toBe ( routerError ) ;
353
+ }
354
+ }
355
+
356
+ createTestModule ( {
357
+ reducers : { reducer } ,
358
+ canActivate : ( ) => {
359
+ throw routerError ;
360
+ } ,
361
+ providers : [ { provide : ErrorHandler , useClass : SilentErrorHandler } ] ,
362
+ config : { stateKey : 'reducer' } ,
363
+ } ) ;
364
+
365
+ const router : Router = TestBed . get ( Router ) ;
366
+ const store : Store < any > = TestBed . get ( Store ) ;
367
+ const log = logOfRouterAndActionsAndStore ( ) ;
368
+
369
+ router
370
+ . navigateByUrl ( '/' )
371
+ . then ( ( ) => {
372
+ log . splice ( 0 ) ;
373
+ store . dispatch ( { type : CHANGE_ROUTE } ) ;
374
+ return waitForNavigation ( router , NavigationError ) ;
375
+ } )
376
+ . then ( ( ) => {
377
+ expect ( log ) . toEqual ( [
378
+ { type : 'router' , event : 'NavigationStart' , url : '/next' } ,
379
+ {
380
+ type : 'store' ,
381
+ state : { state : { url : '/next' , root : { } } , navigationId : 123 } ,
382
+ } ,
383
+ { type : 'action' , action : CHANGE_ROUTE } ,
384
+ { type : 'router' , event : 'RoutesRecognized' , url : '/next' } ,
385
+ { type : 'router' , event : 'GuardsCheckStart' , url : '/next' } ,
386
+ {
387
+ type : 'store' ,
388
+ state : { state : { url : '/' , root : { } } , navigationId : 2 } ,
389
+ } ,
390
+ { type : 'action' , action : ROUTER_ERROR } ,
391
+ { type : 'router' , event : 'NavigationError' , url : '/next' } ,
392
+ ] ) ;
393
+
394
+ done ( ) ;
395
+ } ) ;
396
+ } ) ;
397
+
262
398
it ( 'should call navigateByUrl when resetting state of the routerReducer' , ( done : any ) => {
263
399
const reducer = ( state : any , action : RouterAction < any > ) => {
264
400
const r = routerReducer ( state , action ) ;
@@ -385,7 +521,7 @@ describe('integration spec', () => {
385
521
{ type : 'store' , state : null } , // ROUTER_REQEST event in the store
386
522
{ type : 'action' , action : ROUTER_REQUEST } ,
387
523
{ type : 'router' , event : 'NavigationStart' , url : '/load' } ,
388
- { type : 'store' , state : null } ,
524
+ { type : 'store' , state : { url : '' , navigationId : 1 } } ,
389
525
{ type : 'action' , action : ROUTER_CANCEL } ,
390
526
{ type : 'router' , event : 'NavigationCancel' , url : '/load' } ,
391
527
] ) ;
@@ -744,12 +880,9 @@ function createTestModule(
744
880
TestBed . createComponent ( AppCmp ) ;
745
881
}
746
882
747
- function waitForNavigation ( router : Router ) {
883
+ function waitForNavigation ( router : Router , event : any = NavigationEnd ) {
748
884
return router . events
749
- . pipe (
750
- filter ( ( e ) : e is NavigationEnd => e instanceof NavigationEnd ) ,
751
- first ( )
752
- )
885
+ . pipe ( filter ( e => e instanceof event ) , first ( ) )
753
886
. toPromise ( ) ;
754
887
}
755
888
0 commit comments