refactor(router): Adjust behavior for computed navigation restoration (#42751)
When another navigation is triggered during an in-process navigation and the `canceledNavigationResolution` is `'computed'`, we should not attempt to restore the browser history using `history.go`. Doing that would trigger a third navigation through the router which would conflict with the new navigation that we were trying to process. Instead, we treat this as a redirect and skip the history restoration attempt. This acts similarly to returning `UrlTree` from a guard. Fixes issue described in https://github.com/angular/angular/pull/38884#issuecomment-863767152 PR Close #42751
This commit is contained in:
parent
5356796250
commit
3791ae0c95
|
@ -33,7 +33,6 @@ import {Checks, getAllRouteGuards} from './utils/preactivation';
|
||||||
import {isUrlTree} from './utils/type_guards';
|
import {isUrlTree} from './utils/type_guards';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
*
|
*
|
||||||
|
@ -384,7 +383,6 @@ export const subsetMatchOptions: IsActiveMatchOptions = {
|
||||||
queryParams: 'subset'
|
queryParams: 'subset'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
*
|
*
|
||||||
|
@ -419,8 +417,20 @@ export class Router {
|
||||||
/**
|
/**
|
||||||
* The id of the currently active page in the router.
|
* The id of the currently active page in the router.
|
||||||
* Updated to the transition's target id on a successful navigation.
|
* Updated to the transition's target id on a successful navigation.
|
||||||
|
*
|
||||||
|
* This is used to track what page the router last activated. When an attempted navigation fails,
|
||||||
|
* the router can then use this to compute how to restore the state back to the previously active
|
||||||
|
* page.
|
||||||
*/
|
*/
|
||||||
private currentPageId: number = 0;
|
private currentPageId: number = 0;
|
||||||
|
/**
|
||||||
|
* The ɵrouterPageId of whatever page is currently active in the browser history. This is
|
||||||
|
* important for computing the target page id for new navigations because we need to ensure each
|
||||||
|
* page id in the browser history is 1 more than the previous entry.
|
||||||
|
*/
|
||||||
|
private get browserPageId(): number|undefined {
|
||||||
|
return (this.location.getState() as RestoredState | null)?.ɵrouterPageId;
|
||||||
|
}
|
||||||
private configLoader: RouterConfigLoader;
|
private configLoader: RouterConfigLoader;
|
||||||
private ngModule: NgModuleRef<any>;
|
private ngModule: NgModuleRef<any>;
|
||||||
private console: Console;
|
private console: Console;
|
||||||
|
@ -761,7 +771,7 @@ export class Router {
|
||||||
|
|
||||||
filter(t => {
|
filter(t => {
|
||||||
if (!t.guardsResult) {
|
if (!t.guardsResult) {
|
||||||
this.cancelNavigationTransition(t, '');
|
this.cancelNavigationTransitionRestoreHistory(t, '');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -786,7 +796,7 @@ export class Router {
|
||||||
next: () => dataResolved = true,
|
next: () => dataResolved = true,
|
||||||
complete: () => {
|
complete: () => {
|
||||||
if (!dataResolved) {
|
if (!dataResolved) {
|
||||||
this.cancelNavigationTransition(
|
this.cancelNavigationTransitionRestoreHistory(
|
||||||
t,
|
t,
|
||||||
`At least one route resolver didn't emit any value.`);
|
`At least one route resolver didn't emit any value.`);
|
||||||
}
|
}
|
||||||
|
@ -869,17 +879,28 @@ export class Router {
|
||||||
* event is fired when a navigation gets cancelled but not caught by other
|
* event is fired when a navigation gets cancelled but not caught by other
|
||||||
* means. */
|
* means. */
|
||||||
if (!completed && !errored) {
|
if (!completed && !errored) {
|
||||||
// Must reset to current URL tree here to ensure history.state is set. On a
|
const cancelationReason = `Navigation ID ${
|
||||||
// fresh page load, if a new navigation comes in before a successful
|
t.id} is not equal to the current navigation id ${this.navigationId}`;
|
||||||
// navigation completes, there will be nothing in
|
if (this.canceledNavigationResolution === 'replace') {
|
||||||
// history.state.navigationId. This can cause sync problems with AngularJS
|
// Must reset to current URL tree here to ensure history.state is set. On
|
||||||
// sync code which looks for a value here in order to determine whether or
|
// a fresh page load, if a new navigation comes in before a successful
|
||||||
// not to handle a given popstate event or to leave it to the Angular
|
// navigation completes, there will be nothing in
|
||||||
// router.
|
// history.state.navigationId. This can cause sync problems with
|
||||||
this.cancelNavigationTransition(
|
// AngularJS sync code which looks for a value here in order to determine
|
||||||
t,
|
// whether or not to handle a given popstate event or to leave it to the
|
||||||
`Navigation ID ${t.id} is not equal to the current navigation id ${
|
// Angular router.
|
||||||
this.navigationId}`);
|
this.cancelNavigationTransitionRestoreHistory(t, cancelationReason);
|
||||||
|
} else {
|
||||||
|
// We cannot trigger a `location.historyGo` if the
|
||||||
|
// cancellation was due to a new navigation before the previous could
|
||||||
|
// complete. This is because `location.historyGo` triggers a `popstate`
|
||||||
|
// which would also trigger another navigation. Instead, treat this as a
|
||||||
|
// redirect and do not reset the state.
|
||||||
|
this.cancelNavigationTransition(t, cancelationReason);
|
||||||
|
// TODO(atscott): The same problem happens here with a fresh page load
|
||||||
|
// and a new navigation before that completes where we won't set a page
|
||||||
|
// id.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// currentNavigation should always be reset to null here. If navigation was
|
// currentNavigation should always be reset to null here. If navigation was
|
||||||
// successful, lastSuccessfulTransition will have already been set. Therefore
|
// successful, lastSuccessfulTransition will have already been set. Therefore
|
||||||
|
@ -993,9 +1014,9 @@ export class Router {
|
||||||
if (!this.locationSubscription) {
|
if (!this.locationSubscription) {
|
||||||
this.locationSubscription = this.location.subscribe(event => {
|
this.locationSubscription = this.location.subscribe(event => {
|
||||||
const currentChange = this.extractLocationChangeInfoFromEvent(event);
|
const currentChange = this.extractLocationChangeInfoFromEvent(event);
|
||||||
|
// The `setTimeout` was added in #12160 and is likely to support Angular/AngularJS
|
||||||
|
// hybrid apps.
|
||||||
if (this.shouldScheduleNavigation(this.lastLocationChangeInfo, currentChange)) {
|
if (this.shouldScheduleNavigation(this.lastLocationChangeInfo, currentChange)) {
|
||||||
// The `setTimeout` was added in #12160 and is likely to support Angular/AngularJS
|
|
||||||
// hybrid apps.
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const {source, state, urlTree} = currentChange;
|
const {source, state, urlTree} = currentChange;
|
||||||
const extras: NavigationExtras = {replaceUrl: true};
|
const extras: NavigationExtras = {replaceUrl: true};
|
||||||
|
@ -1213,15 +1234,7 @@ export class Router {
|
||||||
const urlTree = isUrlTree(url) ? url : this.parseUrl(url);
|
const urlTree = isUrlTree(url) ? url : this.parseUrl(url);
|
||||||
const mergedTree = this.urlHandlingStrategy.merge(urlTree, this.rawUrlTree);
|
const mergedTree = this.urlHandlingStrategy.merge(urlTree, this.rawUrlTree);
|
||||||
|
|
||||||
let restoredState: RestoredState|null = null;
|
return this.scheduleNavigation(mergedTree, 'imperative', null, extras);
|
||||||
if (this.canceledNavigationResolution === 'computed') {
|
|
||||||
const isInitialPage = this.currentPageId === 0;
|
|
||||||
if (isInitialPage || extras.skipLocationChange || extras.replaceUrl) {
|
|
||||||
restoredState = this.location.getState() as RestoredState | null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.scheduleNavigation(mergedTree, 'imperative', restoredState, extras);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1345,6 +1358,7 @@ export class Router {
|
||||||
if (this.disposed) {
|
if (this.disposed) {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// * Imperative navigations (router.navigate) might trigger additional navigations to the same
|
// * Imperative navigations (router.navigate) might trigger additional navigations to the same
|
||||||
// URL via a popstate event and the locationChangeListener. We should skip these duplicate
|
// URL via a popstate event and the locationChangeListener. We should skip these duplicate
|
||||||
// navs. Duplicates may also be triggered by attempts to sync AngularJS and Angular router
|
// navs. Duplicates may also be triggered by attempts to sync AngularJS and Angular router
|
||||||
|
@ -1388,12 +1402,23 @@ export class Router {
|
||||||
const id = ++this.navigationId;
|
const id = ++this.navigationId;
|
||||||
let targetPageId: number;
|
let targetPageId: number;
|
||||||
if (this.canceledNavigationResolution === 'computed') {
|
if (this.canceledNavigationResolution === 'computed') {
|
||||||
|
const isInitialPage = this.currentPageId === 0;
|
||||||
|
if (isInitialPage) {
|
||||||
|
restoredState = this.location.getState() as RestoredState | null;
|
||||||
|
}
|
||||||
// If the `ɵrouterPageId` exist in the state then `targetpageId` should have the value of
|
// If the `ɵrouterPageId` exist in the state then `targetpageId` should have the value of
|
||||||
// `ɵrouterPageId`
|
// `ɵrouterPageId`. This is the case for something like a page refresh where we assign the
|
||||||
|
// target id to the previously set value for that page.
|
||||||
if (restoredState && restoredState.ɵrouterPageId) {
|
if (restoredState && restoredState.ɵrouterPageId) {
|
||||||
targetPageId = restoredState.ɵrouterPageId;
|
targetPageId = restoredState.ɵrouterPageId;
|
||||||
} else {
|
} else {
|
||||||
targetPageId = this.currentPageId + 1;
|
// If we're replacing the URL or doing a silent navigation, we do not want to increment the
|
||||||
|
// page id because we aren't pushing a new entry to history.
|
||||||
|
if (extras.replaceUrl || extras.skipLocationChange) {
|
||||||
|
targetPageId = this.browserPageId ?? 0;
|
||||||
|
} else {
|
||||||
|
targetPageId = (this.browserPageId ?? 0) + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// This is unused when `canceledNavigationResolution` is not computed.
|
// This is unused when `canceledNavigationResolution` is not computed.
|
||||||
|
@ -1453,7 +1478,7 @@ export class Router {
|
||||||
* - triggers the `NavigationCancel` event
|
* - triggers the `NavigationCancel` event
|
||||||
* - resolves the transition promise with `false`
|
* - resolves the transition promise with `false`
|
||||||
*/
|
*/
|
||||||
private cancelNavigationTransition(t: NavigationTransition, reason: string) {
|
private cancelNavigationTransitionRestoreHistory(t: NavigationTransition, reason: string) {
|
||||||
if (this.canceledNavigationResolution === 'computed') {
|
if (this.canceledNavigationResolution === 'computed') {
|
||||||
// The navigator change the location before triggered the browser event,
|
// The navigator change the location before triggered the browser event,
|
||||||
// so we need to go back to the current url if the navigation is canceled.
|
// so we need to go back to the current url if the navigation is canceled.
|
||||||
|
@ -1470,6 +1495,10 @@ export class Router {
|
||||||
} else {
|
} else {
|
||||||
this.resetUrlToCurrentUrlTree();
|
this.resetUrlToCurrentUrlTree();
|
||||||
}
|
}
|
||||||
|
this.cancelNavigationTransition(t, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
private cancelNavigationTransition(t: NavigationTransition, reason: string) {
|
||||||
const navCancel = new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), reason);
|
const navCancel = new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), reason);
|
||||||
this.triggerEvent(navCancel);
|
this.triggerEvent(navCancel);
|
||||||
t.resolve(false);
|
t.resolve(false);
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {delay, filter, first, map, mapTo, tap} from 'rxjs/operators';
|
||||||
|
|
||||||
import {RouterInitializer} from '../src/router_module';
|
import {RouterInitializer} from '../src/router_module';
|
||||||
import {forEach} from '../src/utils/collection';
|
import {forEach} from '../src/utils/collection';
|
||||||
|
import {isUrlTree} from '../src/utils/type_guards';
|
||||||
import {RouterTestingModule, SpyNgModuleFactoryLoader} from '../testing';
|
import {RouterTestingModule, SpyNgModuleFactoryLoader} from '../testing';
|
||||||
|
|
||||||
describe('Integration', () => {
|
describe('Integration', () => {
|
||||||
|
@ -2740,7 +2741,6 @@ describe('Integration', () => {
|
||||||
|
|
||||||
const url = path + (query.length > 0 ? ('?' + query) : '');
|
const url = path + (query.length > 0 ? ('?' + query) : '');
|
||||||
this.urlChanges.push(url);
|
this.urlChanges.push(url);
|
||||||
this._subject.emit({'url': url, 'pop': false});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceState(path: string, query: string = '', state: any = null) {
|
replaceState(path: string, query: string = '', state: any = null) {
|
||||||
|
@ -2825,7 +2825,16 @@ describe('Integration', () => {
|
||||||
@Injectable({providedIn: 'root'})
|
@Injectable({providedIn: 'root'})
|
||||||
class MyCanActivateGuard implements CanActivate {
|
class MyCanActivateGuard implements CanActivate {
|
||||||
allow: boolean = true;
|
allow: boolean = true;
|
||||||
canActivate(): boolean {
|
redirectTo: string|null|UrlTree = null;
|
||||||
|
|
||||||
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
|
canActivate(): boolean|UrlTree {
|
||||||
|
if (typeof this.redirectTo === 'string') {
|
||||||
|
this.router.navigateByUrl(this.redirectTo);
|
||||||
|
} else if (isUrlTree(this.redirectTo)) {
|
||||||
|
return this.redirectTo;
|
||||||
|
}
|
||||||
return this.allow;
|
return this.allow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2879,16 +2888,23 @@ describe('Integration', () => {
|
||||||
canActivate: [MyCanActivateGuard],
|
canActivate: [MyCanActivateGuard],
|
||||||
resolve: [MyResolve]
|
resolve: [MyResolve]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'unguarded',
|
||||||
|
component: SimpleCmp,
|
||||||
|
},
|
||||||
{path: 'loaded', loadChildren: () => of(LoadedModule), canLoad: ['alwaysFalse']}
|
{path: 'loaded', loadChildren: () => of(LoadedModule), canLoad: ['alwaysFalse']}
|
||||||
]);
|
]);
|
||||||
router.navigateByUrl('/first');
|
router.navigateByUrl('/first');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 1}));
|
||||||
|
|
||||||
router.navigateByUrl('/second');
|
router.navigateByUrl('/second');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
router.navigateByUrl('/third');
|
router.navigateByUrl('/third');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
||||||
|
|
||||||
location.back();
|
location.back();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
@ -2898,30 +2914,30 @@ describe('Integration', () => {
|
||||||
const location = TestBed.inject(Location);
|
const location = TestBed.inject(Location);
|
||||||
const router = TestBed.inject(Router);
|
const router = TestBed.inject(Router);
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
TestBed.inject(MyCanActivateGuard).allow = false;
|
TestBed.inject(MyCanActivateGuard).allow = false;
|
||||||
location.back();
|
location.back();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
TestBed.inject(MyCanActivateGuard).allow = true;
|
TestBed.inject(MyCanActivateGuard).allow = true;
|
||||||
location.back();
|
location.back();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/first');
|
expect(location.path()).toEqual('/first');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 1}));
|
||||||
|
|
||||||
TestBed.inject(MyCanActivateGuard).allow = false;
|
TestBed.inject(MyCanActivateGuard).allow = false;
|
||||||
location.forward();
|
location.forward();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/first');
|
expect(location.path()).toEqual('/first');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 1}));
|
||||||
|
|
||||||
router.navigateByUrl('/second');
|
router.navigateByUrl('/second');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/first');
|
expect(location.path()).toEqual('/first');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 1}));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
@ -2933,24 +2949,24 @@ describe('Integration', () => {
|
||||||
location.back();
|
location.back();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
location.forward();
|
location.forward();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
router.navigateByUrl('third');
|
router.navigateByUrl('third');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
|
|
||||||
TestBed.inject(MyCanDeactivateGuard).allow = true;
|
TestBed.inject(MyCanDeactivateGuard).allow = true;
|
||||||
location.forward();
|
location.forward();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/third');
|
expect(location.path()).toEqual('/third');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 4}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should work when using `NavigationExtras.skipLocationChange`', fakeAsync(() => {
|
it('should work when using `NavigationExtras.skipLocationChange`', fakeAsync(() => {
|
||||||
|
@ -2958,19 +2974,19 @@ describe('Integration', () => {
|
||||||
const router = TestBed.inject(Router);
|
const router = TestBed.inject(Router);
|
||||||
|
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
router.navigateByUrl('/first', {skipLocationChange: true});
|
router.navigateByUrl('/first', {skipLocationChange: true});
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
router.navigateByUrl('/third');
|
router.navigateByUrl('/third');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 4}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
||||||
|
|
||||||
location.back();
|
location.back();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should work when using `NavigationExtras.replaceUrl`', fakeAsync(() => {
|
it('should work when using `NavigationExtras.replaceUrl`', fakeAsync(() => {
|
||||||
|
@ -2978,11 +2994,11 @@ describe('Integration', () => {
|
||||||
const router = TestBed.inject(Router);
|
const router = TestBed.inject(Router);
|
||||||
|
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
router.navigateByUrl('/first', {replaceUrl: true});
|
router.navigateByUrl('/first', {replaceUrl: true});
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
expect(location.path()).toEqual('/first');
|
expect(location.path()).toEqual('/first');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -2993,7 +3009,7 @@ describe('Integration', () => {
|
||||||
router.navigateByUrl('/loaded');
|
router.navigateByUrl('/loaded');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should work when resolve empty', fakeAsync(() => {
|
it('should work when resolve empty', fakeAsync(() => {
|
||||||
|
@ -3001,20 +3017,20 @@ describe('Integration', () => {
|
||||||
const router = TestBed.inject(Router);
|
const router = TestBed.inject(Router);
|
||||||
|
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
TestBed.inject(MyResolve).myresolve = EMPTY;
|
TestBed.inject(MyResolve).myresolve = EMPTY;
|
||||||
|
|
||||||
location.back();
|
location.back();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
|
|
||||||
TestBed.inject(MyResolve).myresolve = of(2);
|
TestBed.inject(MyResolve).myresolve = of(2);
|
||||||
|
|
||||||
location.back();
|
location.back();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 1}));
|
||||||
expect(location.path()).toEqual('/first');
|
expect(location.path()).toEqual('/first');
|
||||||
|
|
||||||
TestBed.inject(MyResolve).myresolve = EMPTY;
|
TestBed.inject(MyResolve).myresolve = EMPTY;
|
||||||
|
@ -3022,24 +3038,24 @@ describe('Integration', () => {
|
||||||
// We should cancel the navigation to `/third` when myresolve is empty
|
// We should cancel the navigation to `/third` when myresolve is empty
|
||||||
router.navigateByUrl('/third');
|
router.navigateByUrl('/third');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 1}));
|
||||||
expect(location.path()).toEqual('/first');
|
expect(location.path()).toEqual('/first');
|
||||||
|
|
||||||
location.historyGo(2);
|
location.historyGo(2);
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 1}));
|
||||||
expect(location.path()).toEqual('/first');
|
expect(location.path()).toEqual('/first');
|
||||||
|
|
||||||
TestBed.inject(MyResolve).myresolve = of(2);
|
TestBed.inject(MyResolve).myresolve = of(2);
|
||||||
location.historyGo(2);
|
location.historyGo(2);
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 4}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
||||||
expect(location.path()).toEqual('/third');
|
expect(location.path()).toEqual('/third');
|
||||||
|
|
||||||
TestBed.inject(MyResolve).myresolve = EMPTY;
|
TestBed.inject(MyResolve).myresolve = EMPTY;
|
||||||
location.historyGo(-2);
|
location.historyGo(-2);
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 4}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
||||||
expect(location.path()).toEqual('/third');
|
expect(location.path()).toEqual('/third');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -3049,38 +3065,97 @@ describe('Integration', () => {
|
||||||
const router = TestBed.inject(Router);
|
const router = TestBed.inject(Router);
|
||||||
|
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
|
|
||||||
router.navigateByUrl('/invalid').catch(() => null);
|
router.navigateByUrl('/invalid').catch(() => null);
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
location.back();
|
location.back();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/first');
|
expect(location.path()).toEqual('/first');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 1}));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should work when urlUpdateStrategy="eagar"', fakeAsync(() => {
|
it('should work when urlUpdateStrategy="eager"', fakeAsync(() => {
|
||||||
const location = TestBed.inject(Location) as SpyLocation;
|
const location = TestBed.inject(Location) as SpyLocation;
|
||||||
const router = TestBed.inject(Router);
|
const router = TestBed.inject(Router);
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
router.urlUpdateStrategy = 'eager';
|
router.urlUpdateStrategy = 'eager';
|
||||||
|
|
||||||
TestBed.inject(MyCanActivateGuard).allow = false;
|
TestBed.inject(MyCanActivateGuard).allow = false;
|
||||||
router.navigateByUrl('/first');
|
router.navigateByUrl('/first');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
location.back();
|
location.back();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/second');
|
expect(location.path()).toEqual('/second');
|
||||||
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should work when CanActivate redirects', fakeAsync(() => {
|
||||||
|
const location = TestBed.inject(Location);
|
||||||
|
|
||||||
|
TestBed.inject(MyCanActivateGuard).redirectTo = '/unguarded';
|
||||||
|
location.back();
|
||||||
|
advance(fixture);
|
||||||
|
expect(location.path()).toEqual('/unguarded');
|
||||||
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
|
||||||
|
TestBed.inject(MyCanActivateGuard).redirectTo = null;
|
||||||
|
|
||||||
|
location.back();
|
||||||
|
advance(fixture);
|
||||||
|
expect(location.path()).toEqual('/first');
|
||||||
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 1}));
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should work when CanActivate redirects and urlUpdateStrategy="eager"', fakeAsync(() => {
|
||||||
|
const location = TestBed.inject(Location);
|
||||||
|
const router = TestBed.inject(Router);
|
||||||
|
router.urlUpdateStrategy = 'eager';
|
||||||
|
|
||||||
|
TestBed.inject(MyCanActivateGuard).redirectTo = '/unguarded';
|
||||||
|
router.navigateByUrl('/third');
|
||||||
|
advance(fixture);
|
||||||
|
expect(location.path()).toEqual('/unguarded');
|
||||||
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 4}));
|
||||||
|
|
||||||
|
TestBed.inject(MyCanActivateGuard).redirectTo = null;
|
||||||
|
|
||||||
|
location.back();
|
||||||
|
advance(fixture);
|
||||||
|
expect(location.path()).toEqual('/third');
|
||||||
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should work when CanActivate redirects with UrlTree and urlUpdateStrategy="eager"',
|
||||||
|
fakeAsync(() => {
|
||||||
|
// Note that this test is different from the above case because we are able to specifically
|
||||||
|
// handle the `UrlTree` case as a proper redirect and set `replaceUrl: true` on the
|
||||||
|
// follow-up navigation.
|
||||||
|
const location = TestBed.inject(Location);
|
||||||
|
const router = TestBed.inject(Router);
|
||||||
|
router.urlUpdateStrategy = 'eager';
|
||||||
|
|
||||||
|
TestBed.inject(MyCanActivateGuard).redirectTo = router.createUrlTree(['unguarded']);
|
||||||
|
router.navigateByUrl('/third');
|
||||||
|
advance(fixture);
|
||||||
|
expect(location.path()).toEqual('/unguarded');
|
||||||
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 3}));
|
||||||
|
|
||||||
|
TestBed.inject(MyCanActivateGuard).redirectTo = null;
|
||||||
|
|
||||||
|
location.back();
|
||||||
|
advance(fixture);
|
||||||
|
expect(location.path()).toEqual('/second');
|
||||||
|
expect(location.getState()).toEqual(jasmine.objectContaining({ɵrouterPageId: 2}));
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
describe('guards', () => {
|
describe('guards', () => {
|
||||||
describe('CanActivate', () => {
|
describe('CanActivate', () => {
|
||||||
|
|
Loading…
Reference in New Issue