@@ -12,6 +12,7 @@ import {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/
12
12
import { By } from '@angular/platform-browser/src/dom/debug/by' ;
13
13
import { expect } from '@angular/platform-browser/testing/src/matchers' ;
14
14
import { ActivatedRoute , ActivatedRouteSnapshot , ActivationEnd , ActivationStart , CanActivate , CanDeactivate , ChildActivationEnd , ChildActivationStart , DetachedRouteHandle , Event , GuardsCheckEnd , GuardsCheckStart , NavigationCancel , NavigationEnd , NavigationError , NavigationStart , PRIMARY_OUTLET , ParamMap , Params , PreloadAllModules , PreloadingStrategy , Resolve , ResolveEnd , ResolveStart , RouteConfigLoadEnd , RouteConfigLoadStart , RouteReuseStrategy , Router , RouterEvent , RouterModule , RouterPreloader , RouterStateSnapshot , RoutesRecognized , RunGuardsAndResolvers , UrlHandlingStrategy , UrlSegmentGroup , UrlTree } from '@angular/router' ;
15
+ import { SpyLocation } from 'common/testing' ;
15
16
import { Observable } from 'rxjs/Observable' ;
16
17
import { Observer } from 'rxjs/Observer' ;
17
18
import { of } from 'rxjs/observable/of' ;
@@ -50,28 +51,87 @@ describe('Integration', () => {
50
51
expect ( fixture . nativeElement ) . toHaveText ( 'route' ) ;
51
52
} ) ) ) ;
52
53
53
- it ( 'should navigate to the current URL' ,
54
- fakeAsync ( inject ( [ Router , Location ] , ( router : Router ) => {
55
- router . resetConfig ( [
56
- { path : '' , component : SimpleCmp } ,
57
- { path : 'simple' , component : SimpleCmp } ,
58
- ] ) ;
54
+ describe ( 'navigation' , function ( ) {
55
+ it ( 'should navigate to the current URL' , fakeAsync ( inject ( [ Router ] , ( router : Router ) => {
56
+ router . resetConfig ( [
57
+ { path : '' , component : SimpleCmp } ,
58
+ { path : 'simple' , component : SimpleCmp } ,
59
+ ] ) ;
59
60
60
- const fixture = createRoot ( router , RootCmp ) ;
61
- const events : Event [ ] = [ ] ;
62
- router . events . subscribe ( e => onlyNavigationStartAndEnd ( e ) && events . push ( e ) ) ;
61
+ const fixture = createRoot ( router , RootCmp ) ;
62
+ const events : Event [ ] = [ ] ;
63
+ router . events . subscribe ( e => onlyNavigationStartAndEnd ( e ) && events . push ( e ) ) ;
63
64
64
- router . navigateByUrl ( '/simple' ) ;
65
- tick ( ) ;
65
+ router . navigateByUrl ( '/simple' ) ;
66
+ tick ( ) ;
66
67
67
- router . navigateByUrl ( '/simple' ) ;
68
- tick ( ) ;
68
+ router . navigateByUrl ( '/simple' ) ;
69
+ tick ( ) ;
69
70
70
- expectEvents ( events , [
71
- [ NavigationStart , '/simple' ] , [ NavigationEnd , '/simple' ] , [ NavigationStart , '/simple' ] ,
72
- [ NavigationEnd , '/simple' ]
73
- ] ) ;
74
- } ) ) ) ;
71
+ expectEvents ( events , [
72
+ [ NavigationStart , '/simple' ] , [ NavigationEnd , '/simple' ] , [ NavigationStart , '/simple' ] ,
73
+ [ NavigationEnd , '/simple' ]
74
+ ] ) ;
75
+ } ) ) ) ;
76
+
77
+ it ( 'should not pollute browser history when replaceUrl is set to true' ,
78
+ fakeAsync ( inject ( [ Router , Location ] , ( router : Router , location : SpyLocation ) => {
79
+ router . resetConfig ( [
80
+ { path : '' , component : SimpleCmp } , { path : 'a' , component : SimpleCmp } ,
81
+ { path : 'b' , component : SimpleCmp }
82
+ ] ) ;
83
+
84
+ const fixture = createRoot ( router , RootCmp ) ;
85
+
86
+ router . navigateByUrl ( '/a' , { replaceUrl : true } ) ;
87
+ router . navigateByUrl ( '/b' , { replaceUrl : true } ) ;
88
+ tick ( ) ;
89
+
90
+ expect ( location . urlChanges ) . toEqual ( [ 'replace: /' , 'replace: /b' ] ) ;
91
+ } ) ) ) ;
92
+
93
+ it ( 'should skip navigation if another navigation is already scheduled' ,
94
+ fakeAsync ( inject ( [ Router , Location ] , ( router : Router , location : SpyLocation ) => {
95
+ router . resetConfig ( [
96
+ { path : '' , component : SimpleCmp } , { path : 'a' , component : SimpleCmp } ,
97
+ { path : 'b' , component : SimpleCmp }
98
+ ] ) ;
99
+
100
+ const fixture = createRoot ( router , RootCmp ) ;
101
+
102
+ router . navigate (
103
+ [ '/a' ] , { queryParams : { a : true } , queryParamsHandling : 'merge' , replaceUrl : true } ) ;
104
+ router . navigate (
105
+ [ '/b' ] , { queryParams : { b : true } , queryParamsHandling : 'merge' , replaceUrl : true } ) ;
106
+ tick ( ) ;
107
+
108
+ /**
109
+ * Why do we have '/b?b=true' and not '/b?a=true&b=true'?
110
+ *
111
+ * This is because the router has the right to stop a navigation mid-flight if another
112
+ * navigation has been already scheduled. This is why we can use a top-level guard
113
+ * to perform redirects. Calling `navigate` in such a guard will stop the navigation, and
114
+ * the components won't be instantiated.
115
+ *
116
+ * This is a fundamental property of the router: it only cares about its latest state.
117
+ *
118
+ * This means that components should only map params to something else, not reduce them.
119
+ * In other words, the following component is asking for trouble:
120
+ *
121
+ * ```
122
+ * class MyComponent {
123
+ * constructor(a: ActivatedRoute) {
124
+ * a.params.scan(...)
125
+ * }
126
+ * }
127
+ * ```
128
+ *
129
+ * This also means "queryParamsHandling: 'merge'" should only be used to merge with
130
+ * long-living query parameters (e.g., debug).
131
+ */
132
+ expect ( router . url ) . toEqual ( '/b?b=true' ) ;
133
+ } ) ) ) ;
134
+ } ) ;
75
135
76
136
describe ( 'should execute navigations serially' , ( ) => {
77
137
let log : any [ ] = [ ] ;
0 commit comments