refactor(router): update test based on router quick-cancelling ongoing navigations (#25740)
PR Close #25740
This commit is contained in:
parent
c634176035
commit
12ccf57340
|
@ -1,23 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {EMPTY, MonoTypeOperatorFunction, Observable, of } from 'rxjs';
|
|
||||||
import {mergeMap} from 'rxjs/operators';
|
|
||||||
|
|
||||||
export function mergeMapIf<T>(
|
|
||||||
predicate: (value: T) => boolean, tap: (value: T) => any): MonoTypeOperatorFunction<T> {
|
|
||||||
return (source: Observable<T>) => {
|
|
||||||
return source.pipe(mergeMap(s => {
|
|
||||||
if (predicate(s)) {
|
|
||||||
tap(s);
|
|
||||||
return EMPTY;
|
|
||||||
}
|
|
||||||
return of (s);
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -11,8 +11,8 @@ import {map, mergeMap} from 'rxjs/operators';
|
||||||
|
|
||||||
import {NavigationTransition} from '../router';
|
import {NavigationTransition} from '../router';
|
||||||
|
|
||||||
export function resolveData(
|
export function resolveData(paramsInheritanceStrategy: 'emptyOnly' | 'always'):
|
||||||
paramsInheritanceStrategy: 'emptyOnly' | 'always'): MonoTypeOperatorFunction<NavigationTransition> {
|
MonoTypeOperatorFunction<NavigationTransition> {
|
||||||
return function(source: Observable<NavigationTransition>) {
|
return function(source: Observable<NavigationTransition>) {
|
||||||
return source.pipe(mergeMap(t => {
|
return source.pipe(mergeMap(t => {
|
||||||
if (!t.preActivation) {
|
if (!t.preActivation) {
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {MonoTypeOperatorFunction, Observable} from 'rxjs';
|
|
||||||
import {tap} from 'rxjs/operators';
|
|
||||||
|
|
||||||
export function throwIf<T>(
|
|
||||||
predicate: (value: T) => boolean,
|
|
||||||
errorFactory: (() => any) = defaultErrorFactory): MonoTypeOperatorFunction<T> {
|
|
||||||
return (source: Observable<T>) => {
|
|
||||||
return source.pipe(tap(s => {
|
|
||||||
if (predicate(s)) {
|
|
||||||
throw errorFactory();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultErrorFactory() {
|
|
||||||
return new Error();
|
|
||||||
}
|
|
|
@ -19,7 +19,6 @@ import {afterPreactivation} from './operators/after_preactivation';
|
||||||
import {applyRedirects} from './operators/apply_redirects';
|
import {applyRedirects} from './operators/apply_redirects';
|
||||||
import {beforePreactivation} from './operators/before_preactivation';
|
import {beforePreactivation} from './operators/before_preactivation';
|
||||||
import {checkGuards} from './operators/check_guards';
|
import {checkGuards} from './operators/check_guards';
|
||||||
import {mergeMapIf} from './operators/mergeMapIf';
|
|
||||||
import {recognize} from './operators/recognize';
|
import {recognize} from './operators/recognize';
|
||||||
import {resolveData} from './operators/resolve_data';
|
import {resolveData} from './operators/resolve_data';
|
||||||
import {PreActivation} from './pre_activation';
|
import {PreActivation} from './pre_activation';
|
||||||
|
@ -30,7 +29,6 @@ import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot
|
||||||
import {Params, isNavigationCancelingError} from './shared';
|
import {Params, isNavigationCancelingError} from './shared';
|
||||||
import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy';
|
import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy';
|
||||||
import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
|
import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
|
||||||
import {assertDefined} from './utils/assert';
|
|
||||||
import {forEach} from './utils/collection';
|
import {forEach} from './utils/collection';
|
||||||
import {TreeNode, nodeChildrenAsMap} from './utils/tree';
|
import {TreeNode, nodeChildrenAsMap} from './utils/tree';
|
||||||
|
|
||||||
|
@ -370,179 +368,184 @@ export class Router {
|
||||||
this.processNavigations();
|
this.processNavigations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
private setupNavigations(transitions: Observable<NavigationTransition>):
|
private setupNavigations(transitions: Observable<NavigationTransition>):
|
||||||
Observable<NavigationTransition> {
|
Observable<NavigationTransition> {
|
||||||
return transitions.pipe(
|
return transitions.pipe(
|
||||||
filter(t => t.id !== 0), mergeMap(t => Promise.resolve(t)),
|
filter(t => t.id !== 0), mergeMap(t => Promise.resolve(t)),
|
||||||
// Extract URL
|
|
||||||
map(t => ({
|
|
||||||
...t, //
|
|
||||||
extractedUrl: this.urlHandlingStrategy.extract(t.rawUrl), //
|
|
||||||
} as NavigationTransition)),
|
|
||||||
|
|
||||||
// Using switchMap so we cancel executing navigations when a new one comes in
|
// Extract URL
|
||||||
switchMap(t => {
|
map(t => ({
|
||||||
let completed = false;
|
...t,
|
||||||
let errored = false;
|
extractedUrl: this.urlHandlingStrategy.extract(t.rawUrl)} as NavigationTransition)),
|
||||||
return of (t).pipe(
|
|
||||||
|
// Using switchMap so we cancel executing navigations when a new one comes in
|
||||||
|
switchMap(t => {
|
||||||
|
let completed = false;
|
||||||
|
let errored = false;
|
||||||
|
return of (t).pipe(mergeMap(t => {
|
||||||
|
const urlTransition = !this.navigated ||
|
||||||
|
t.extractedUrl.toString() !== this.currentUrlTree.toString();
|
||||||
|
if ((this.onSameUrlNavigation === 'reload' ? true : urlTransition) &&
|
||||||
|
this.urlHandlingStrategy.shouldProcessUrl(t.rawUrl)) {
|
||||||
|
return of (t).pipe(
|
||||||
|
// Update URL if in `eager` update mode
|
||||||
|
tap(t => this.urlUpdateStrategy === 'eager' && !t.extras.skipLocationChange &&
|
||||||
|
this.setBrowserUrl(t.rawUrl, !!t.extras.replaceUrl, t.id)),
|
||||||
|
// Fire NavigationStart event
|
||||||
mergeMap(t => {
|
mergeMap(t => {
|
||||||
const urlTransition =
|
const transition = this.transitions.getValue();
|
||||||
!this.navigated || t.extractedUrl.toString() !== this.currentUrlTree.toString();
|
(this.events as Subject<Event>).next(new NavigationStart(
|
||||||
if ((this.onSameUrlNavigation === 'reload' ? true : urlTransition) &&
|
t.id, this.serializeUrl(t.extractedUrl), t.source, t.state));
|
||||||
this.urlHandlingStrategy.shouldProcessUrl(t.rawUrl)) {
|
if (transition !== this.transitions.getValue()) {
|
||||||
return of (t).pipe(
|
|
||||||
// Update URL if in `eager` update mode
|
|
||||||
tap(t => this.urlUpdateStrategy === 'eager' && !t.extras.skipLocationChange &&
|
|
||||||
this.setBrowserUrl(t.rawUrl, !!t.extras.replaceUrl, t.id)),
|
|
||||||
// Fire NavigationStart event
|
|
||||||
mergeMap(t => {
|
|
||||||
const transition = this.transitions.getValue();
|
|
||||||
(this.events as Subject<Event>)
|
|
||||||
.next(new NavigationStart(
|
|
||||||
t.id, this.serializeUrl(t.extractedUrl), t.source, t.state));
|
|
||||||
if (transition !== this.transitions.getValue()) {
|
|
||||||
EMPTY;
|
|
||||||
}
|
|
||||||
return [t];
|
|
||||||
}),
|
|
||||||
|
|
||||||
// This delay is required to match old behavior that forced navigation to
|
|
||||||
// always be async
|
|
||||||
mergeMap(t => Promise.resolve(t)),
|
|
||||||
|
|
||||||
// ApplyRedirects
|
|
||||||
applyRedirects(
|
|
||||||
this.ngModule.injector, this.configLoader, this.urlSerializer,
|
|
||||||
this.config),
|
|
||||||
// Recognize
|
|
||||||
recognize(
|
|
||||||
this.rootComponentType, this.config, (url) => this.serializeUrl(url),
|
|
||||||
this.paramsInheritanceStrategy),
|
|
||||||
// Throw if there's no snapshot
|
|
||||||
tap(t => assertDefined(t.targetSnapshot, 'snapshot must be defined')),
|
|
||||||
// Fire RoutesRecognized
|
|
||||||
tap(t => (this.events as Subject<Event>)
|
|
||||||
.next(new RoutesRecognized(
|
|
||||||
t.id, this.serializeUrl(t.extractedUrl),
|
|
||||||
this.serializeUrl(t.urlAfterRedirects),
|
|
||||||
t.targetSnapshot !))), );
|
|
||||||
} else if (
|
|
||||||
urlTransition && this.rawUrlTree &&
|
|
||||||
this.urlHandlingStrategy.shouldProcessUrl(this.rawUrlTree)) {
|
|
||||||
(this.events as Subject<Event>)
|
|
||||||
.next(new NavigationStart(
|
|
||||||
t.id, this.serializeUrl(t.extractedUrl), t.source, t.state));
|
|
||||||
|
|
||||||
return of ({
|
|
||||||
...t,
|
|
||||||
urlAfterRedirects: t.extractedUrl,
|
|
||||||
extras: {...t.extras, skipLocationChange: false, replaceUrl: false},
|
|
||||||
targetSnapshot:
|
|
||||||
createEmptyState(t.extractedUrl, this.rootComponentType).snapshot
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.rawUrlTree = t.rawUrl;
|
|
||||||
t.resolve(null);
|
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
return [t];
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Before Preactivation
|
// This delay is required to match old behavior that forced navigation to
|
||||||
beforePreactivation(this.hooks.beforePreactivation),
|
// always be async
|
||||||
// --- GUARDS ---
|
mergeMap(t => Promise.resolve(t)),
|
||||||
tap(t => this.triggerEvent(new GuardsCheckStart(
|
|
||||||
t.id, this.serializeUrl(t.extractedUrl),
|
|
||||||
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !))),
|
|
||||||
map(t => {
|
|
||||||
const preActivation = new PreActivation(
|
|
||||||
t.targetSnapshot !, t.currentSnapshot, this.ngModule.injector,
|
|
||||||
(evt: Event) => this.triggerEvent(evt));
|
|
||||||
preActivation.initialize(this.rootContexts);
|
|
||||||
return {...t, preActivation};
|
|
||||||
}),
|
|
||||||
checkGuards(), tap(t => this.triggerEvent(new GuardsCheckEnd(
|
|
||||||
t.id, this.serializeUrl(t.extractedUrl),
|
|
||||||
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !,
|
|
||||||
!!t.guardsResult))),
|
|
||||||
|
|
||||||
mergeMapIf(
|
// ApplyRedirects
|
||||||
t => !t.guardsResult,
|
applyRedirects(
|
||||||
t => {
|
this.ngModule.injector, this.configLoader, this.urlSerializer,this.config),
|
||||||
this.resetUrlToCurrentUrlTree();
|
// Recognize
|
||||||
(this.events as Subject<Event>)
|
recognize(
|
||||||
.next(new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), ''));
|
this.rootComponentType, this.config, (url) => this.serializeUrl(url),
|
||||||
t.resolve(false);
|
this.paramsInheritanceStrategy),
|
||||||
}),
|
|
||||||
|
|
||||||
// --- RESOLVE ---
|
// Fire RoutesRecognized
|
||||||
mergeMap(t => {
|
tap(t => (this.events as Subject<Event>).next(new RoutesRecognized(
|
||||||
if (t.preActivation !.isActivating()) {
|
t.id, this.serializeUrl(t.extractedUrl), this.serializeUrl(t.urlAfterRedirects),
|
||||||
return of (t).pipe(
|
t.targetSnapshot !)))
|
||||||
tap(t => this.triggerEvent(new ResolveStart(
|
);
|
||||||
t.id, this.serializeUrl(t.extractedUrl),
|
} else if (
|
||||||
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !))),
|
urlTransition && this.rawUrlTree &&
|
||||||
resolveData(this.paramsInheritanceStrategy),
|
this.urlHandlingStrategy.shouldProcessUrl(this.rawUrlTree)) {
|
||||||
tap(t => this.triggerEvent(new ResolveEnd(
|
(this.events as Subject<Event>).next(new NavigationStart(
|
||||||
t.id, this.serializeUrl(t.extractedUrl),
|
t.id, this.serializeUrl(t.extractedUrl), t.source, t.state));
|
||||||
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !))), );
|
|
||||||
}
|
|
||||||
return of (t);
|
|
||||||
}),
|
|
||||||
|
|
||||||
// --- AFTER PREACTIVATION ---
|
return of ({
|
||||||
afterPreactivation(this.hooks.afterPreactivation), map(t => {
|
...t,
|
||||||
const targetRouterState = createRouterState(
|
urlAfterRedirects: t.extractedUrl,
|
||||||
this.routeReuseStrategy, t.targetSnapshot !, t.currentRouterState);
|
extras: {...t.extras, skipLocationChange: false, replaceUrl: false},
|
||||||
return ({...t, targetRouterState});
|
targetSnapshot: createEmptyState(t.extractedUrl, this.rootComponentType).snapshot
|
||||||
}),
|
});
|
||||||
|
} else {
|
||||||
|
this.rawUrlTree = t.rawUrl;
|
||||||
|
t.resolve(null);
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
// Side effects of resetting Router instance
|
// Before Preactivation
|
||||||
tap(t => {
|
beforePreactivation(this.hooks.beforePreactivation),
|
||||||
this.currentUrlTree = t.urlAfterRedirects;
|
|
||||||
this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, t.rawUrl);
|
|
||||||
|
|
||||||
(this as{routerState: RouterState}).routerState = t.targetRouterState !;
|
// --- GUARDS ---
|
||||||
|
tap(t => this.triggerEvent(new GuardsCheckStart(
|
||||||
|
t.id, this.serializeUrl(t.extractedUrl),
|
||||||
|
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !))),
|
||||||
|
map(t => {
|
||||||
|
const preActivation = new PreActivation(
|
||||||
|
t.targetSnapshot !, t.currentSnapshot, this.ngModule.injector,
|
||||||
|
(evt: Event) => this.triggerEvent(evt));
|
||||||
|
preActivation.initialize(this.rootContexts);
|
||||||
|
return {...t, preActivation};
|
||||||
|
}),
|
||||||
|
|
||||||
if (this.urlUpdateStrategy === 'deferred' && !t.extras.skipLocationChange) {
|
checkGuards(),
|
||||||
this.setBrowserUrl(this.rawUrlTree, !!t.extras.replaceUrl, t.id);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
activate(
|
tap(t => this.triggerEvent(new GuardsCheckEnd(
|
||||||
this.rootContexts, this.routeReuseStrategy,
|
t.id, this.serializeUrl(t.extractedUrl),
|
||||||
(evt: Event) => this.triggerEvent(evt)),
|
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !,
|
||||||
|
!!t.guardsResult))),
|
||||||
|
|
||||||
tap({next: () => completed = true, complete: () => completed = true}),
|
mergeMap(t => {
|
||||||
finalize(() => {
|
if (!t.guardsResult) {
|
||||||
if (!completed && !errored) {
|
this.resetUrlToCurrentUrlTree();
|
||||||
(this.events as Subject<Event>)
|
(this.events as Subject<Event>)
|
||||||
.next(new NavigationCancel(
|
.next(new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), ''));
|
||||||
t.id, this.serializeUrl(t.extractedUrl),
|
t.resolve(false);
|
||||||
`Navigation ID ${t.id} is not equal to the current navigation id ${this.navigationId}`));
|
return EMPTY;
|
||||||
t.resolve(false);
|
}
|
||||||
}
|
return of(t);
|
||||||
}),
|
}),
|
||||||
catchError((e) => {
|
|
||||||
errored = true;
|
// --- RESOLVE ---
|
||||||
if (isNavigationCancelingError(e)) {
|
mergeMap(t => {
|
||||||
this.navigated = true;
|
if (t.preActivation !.isActivating()) {
|
||||||
this.resetStateAndUrl(t.currentRouterState, t.currentUrlTree, t.rawUrl);
|
return of (t).pipe(
|
||||||
(this.events as Subject<Event>)
|
tap(t => this.triggerEvent(new ResolveStart(
|
||||||
.next(
|
t.id, this.serializeUrl(t.extractedUrl),
|
||||||
new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), e.message));
|
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !))),
|
||||||
} else {
|
resolveData(this.paramsInheritanceStrategy),
|
||||||
this.resetStateAndUrl(t.currentRouterState, t.currentUrlTree, t.rawUrl);
|
tap(t => this.triggerEvent(new ResolveEnd(
|
||||||
(this.events as Subject<Event>)
|
t.id, this.serializeUrl(t.extractedUrl),
|
||||||
.next(new NavigationError(t.id, this.serializeUrl(t.extractedUrl), e));
|
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !))), );
|
||||||
try {
|
}
|
||||||
t.resolve(this.errorHandler(e));
|
return of (t);
|
||||||
} catch (ee) {
|
}),
|
||||||
t.reject(ee);
|
|
||||||
}
|
// --- AFTER PREACTIVATION ---
|
||||||
}
|
afterPreactivation(this.hooks.afterPreactivation), map(t => {
|
||||||
return EMPTY;
|
const targetRouterState = createRouterState(
|
||||||
}), );
|
this.routeReuseStrategy, t.targetSnapshot !, t.currentRouterState);
|
||||||
})) as any as Observable<NavigationTransition>;
|
return ({...t, targetRouterState});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Side effects of resetting Router instance
|
||||||
|
afterPreactivation(this.hooks.afterPreactivation), map(t => {
|
||||||
|
const targetRouterState = createRouterState(
|
||||||
|
this.routeReuseStrategy, t.targetSnapshot !, t.currentRouterState);
|
||||||
|
return ({...t, targetRouterState});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Side effects of resetting Router instance
|
||||||
|
tap(t => {
|
||||||
|
this.currentUrlTree = t.urlAfterRedirects;
|
||||||
|
this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, t.rawUrl);
|
||||||
|
|
||||||
|
(this as{routerState: RouterState}).routerState = t.targetRouterState !;
|
||||||
|
|
||||||
|
if (this.urlUpdateStrategy === 'deferred' && !t.extras.skipLocationChange) {
|
||||||
|
this.setBrowserUrl(this.rawUrlTree, !!t.extras.replaceUrl, t.id);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
activate(
|
||||||
|
this.rootContexts, this.routeReuseStrategy,
|
||||||
|
(evt: Event) => this.triggerEvent(evt)),
|
||||||
|
|
||||||
|
tap({next: () => completed = true, complete: () => completed = true}),
|
||||||
|
finalize(() => {
|
||||||
|
if (!completed && !errored) {
|
||||||
|
(this.events as Subject<Event>).next(new NavigationCancel(
|
||||||
|
t.id, this.serializeUrl(t.extractedUrl),
|
||||||
|
`Navigation ID ${t.id} is not equal to the current navigation id ${this.navigationId}`));
|
||||||
|
t.resolve(false);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError((e) => {
|
||||||
|
errored = true;
|
||||||
|
if (isNavigationCancelingError(e)) {
|
||||||
|
this.navigated = true;
|
||||||
|
this.resetStateAndUrl(t.currentRouterState, t.currentUrlTree, t.rawUrl);
|
||||||
|
(this.events as Subject<Event>).next(
|
||||||
|
new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), e.message));
|
||||||
|
} else {
|
||||||
|
this.resetStateAndUrl(t.currentRouterState, t.currentUrlTree, t.rawUrl);
|
||||||
|
(this.events as Subject<Event>).next(new NavigationError(
|
||||||
|
t.id, this.serializeUrl(t.extractedUrl), e));
|
||||||
|
try {
|
||||||
|
t.resolve(this.errorHandler(e));
|
||||||
|
} catch (ee) {
|
||||||
|
t.reject(ee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EMPTY;
|
||||||
|
}), );
|
||||||
|
})) as any as Observable<NavigationTransition>;
|
||||||
|
|
||||||
function activate(
|
function activate(
|
||||||
rootContexts: ChildrenOutletContexts, routeReuseStrategy: RouteReuseStrategy,
|
rootContexts: ChildrenOutletContexts, routeReuseStrategy: RouteReuseStrategy,
|
||||||
|
@ -557,6 +560,7 @@ export class Router {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function assertDefined<T>(actual: T, msg: string) {
|
|
||||||
if (actual == null) {
|
|
||||||
throwError(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function throwError(msg: string): never {
|
|
||||||
// tslint:disable-next-line
|
|
||||||
debugger; // Left intentionally for better debugger experience.
|
|
||||||
throw new Error(`ASSERTION ERROR: ${msg}`);
|
|
||||||
}
|
|
|
@ -291,49 +291,35 @@ describe('Integration', () => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO(jasonaden): This test now fails because it relies on waiting on a guard to finish
|
it('should not wait for prior navigations to start a new navigation',
|
||||||
// executing even after a new navigation has been scheduled. The previous implementation
|
fakeAsync(inject([Router, Location], (router: Router) => {
|
||||||
// would do so, but ignore the result of any guards that are executing when a new navigation
|
const fixture = createRoot(router, RootCmp);
|
||||||
// is scheduled.
|
|
||||||
|
|
||||||
// With new implementation, the current navigation will be unrolled and cleaned up so the
|
router.resetConfig([
|
||||||
// new navigation can start immediately. This test therefore fails as it relies on that
|
{path: 'a', component: SimpleCmp, canActivate: ['trueRightAway', 'trueIn2Seconds']},
|
||||||
// previous incorrect behavior.
|
{path: 'b', component: SimpleCmp, canActivate: ['trueRightAway', 'trueIn2Seconds']}
|
||||||
xit('should execute navigations serialy',
|
]);
|
||||||
fakeAsync(inject([Router, Location], (router: Router) => {
|
|
||||||
const fixture = createRoot(router, RootCmp);
|
|
||||||
|
|
||||||
router.resetConfig([
|
router.navigateByUrl('/a');
|
||||||
{path: 'a', component: SimpleCmp, canActivate: ['trueRightAway', 'trueIn2Seconds']},
|
tick(100);
|
||||||
{path: 'b', component: SimpleCmp, canActivate: ['trueRightAway', 'trueIn2Seconds']}
|
fixture.detectChanges();
|
||||||
]);
|
|
||||||
|
|
||||||
router.navigateByUrl('/a');
|
router.navigateByUrl('/b');
|
||||||
tick(100);
|
tick(100); // 200
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
router.navigateByUrl('/b');
|
expect(log).toEqual(
|
||||||
tick(100); // 200
|
['trueRightAway', 'trueIn2Seconds-start', 'trueRightAway', 'trueIn2Seconds-start']);
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(log).toEqual(['trueRightAway', 'trueIn2Seconds-start']);
|
tick(2000); // 2200
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
tick(2000); // 2200
|
expect(log).toEqual([
|
||||||
fixture.detectChanges();
|
'trueRightAway', 'trueIn2Seconds-start', 'trueRightAway', 'trueIn2Seconds-start',
|
||||||
|
'trueIn2Seconds-end', 'trueIn2Seconds-end'
|
||||||
|
]);
|
||||||
|
|
||||||
expect(log).toEqual([
|
})));
|
||||||
'trueRightAway', 'trueIn2Seconds-start', 'trueIn2Seconds-end', 'trueRightAway',
|
|
||||||
'trueIn2Seconds-start'
|
|
||||||
]);
|
|
||||||
|
|
||||||
tick(2000); // 4200
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(log).toEqual([
|
|
||||||
'trueRightAway', 'trueIn2Seconds-start', 'trueIn2Seconds-end', 'trueRightAway',
|
|
||||||
'trueIn2Seconds-start', 'trueIn2Seconds-end'
|
|
||||||
]);
|
|
||||||
})));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should work inside ChangeDetectionStrategy.OnPush components', fakeAsync(() => {
|
it('Should work inside ChangeDetectionStrategy.OnPush components', fakeAsync(() => {
|
||||||
|
@ -2971,32 +2957,31 @@ describe('Integration', () => {
|
||||||
]);
|
]);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should allow redirection in NavigationStart', fakeAsync(inject([Router], (router: Router) => {
|
it('should allow redirection in NavigationStart',
|
||||||
const fixture = createRoot(router, RootCmp);
|
fakeAsync(inject([Router], (router: Router) => {
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
router.resetConfig([
|
router.resetConfig([
|
||||||
{path: 'blank', component: UserCmp},
|
{path: 'blank', component: UserCmp},
|
||||||
{path: 'user/:name', component: BlankCmp},
|
{path: 'user/:name', component: BlankCmp},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const navigateSpy = spyOn(router, 'navigate').and.callThrough();
|
const navigateSpy = spyOn(router, 'navigate').and.callThrough();
|
||||||
const recordedEvents: any[] = [];
|
const recordedEvents: any[] = [];
|
||||||
|
|
||||||
const navStart$ = router.events.pipe(
|
const navStart$ = router.events.pipe(
|
||||||
tap(e => recordedEvents.push(e)),
|
tap(e => recordedEvents.push(e)), filter(e => e instanceof NavigationStart), first());
|
||||||
filter(e => e instanceof NavigationStart),
|
|
||||||
first()
|
|
||||||
);
|
|
||||||
|
|
||||||
navStart$.subscribe((e: NavigationStart | NavigationError) => {
|
navStart$.subscribe((e: NavigationStart | NavigationError) => {
|
||||||
router.navigate(['/blank'], {queryParams: {state: 'redirected'}, queryParamsHandling: 'merge'});
|
router.navigate(
|
||||||
advance(fixture);
|
['/blank'], {queryParams: {state: 'redirected'}, queryParamsHandling: 'merge'});
|
||||||
});
|
advance(fixture);
|
||||||
|
});
|
||||||
|
|
||||||
router.navigate(['/user/:fedor']);
|
router.navigate(['/user/:fedor']);
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(navigateSpy.calls.mostRecent().args[1].queryParams);
|
expect(navigateSpy.calls.mostRecent().args[1].queryParams);
|
||||||
|
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue