Fixes all TypeScript failures caused by enabling the `--strict` flag for test source files. We also want to enable the strict options for tests as the strictness enforcement improves the overall codehealth, unveiled common issues and additionally it allows us to enable `strict` in the `tsconfig.json` that is picked up by IDE's. PR Close #30993
707 lines
28 KiB
TypeScript
707 lines
28 KiB
TypeScript
/**
|
|
* @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 {Location} from '@angular/common';
|
|
import {TestBed, inject} from '@angular/core/testing';
|
|
import {RouterTestingModule} from '@angular/router/testing';
|
|
import {of } from 'rxjs';
|
|
|
|
import {Routes} from '../src/config';
|
|
import {ChildActivationStart} from '../src/events';
|
|
import {checkGuards as checkGuardsOperator} from '../src/operators/check_guards';
|
|
import {resolveData as resolveDataOperator} from '../src/operators/resolve_data';
|
|
import {NavigationTransition, Router} from '../src/router';
|
|
import {ChildrenOutletContexts} from '../src/router_outlet_context';
|
|
import {RouterStateSnapshot, createEmptyStateSnapshot} from '../src/router_state';
|
|
import {DefaultUrlSerializer, UrlTree} from '../src/url_tree';
|
|
import {getAllRouteGuards} from '../src/utils/preactivation';
|
|
import {TreeNode} from '../src/utils/tree';
|
|
|
|
import {Logger, createActivatedRouteSnapshot, provideTokenLogger} from './helpers';
|
|
|
|
describe('Router', () => {
|
|
|
|
describe('resetConfig', () => {
|
|
class TestComponent {}
|
|
|
|
beforeEach(() => { TestBed.configureTestingModule({imports: [RouterTestingModule]}); });
|
|
|
|
it('should copy config to avoid mutations of user-provided objects', () => {
|
|
const r: Router = TestBed.get(Router);
|
|
const configs: Routes = [{
|
|
path: 'a',
|
|
component: TestComponent,
|
|
children: [{path: 'b', component: TestComponent}, {path: 'c', component: TestComponent}]
|
|
}];
|
|
const children = configs[0].children !;
|
|
|
|
r.resetConfig(configs);
|
|
|
|
const rConfigs = r.config;
|
|
const rChildren = rConfigs[0].children !;
|
|
|
|
// routes array and shallow copy
|
|
expect(configs).not.toBe(rConfigs);
|
|
expect(configs[0]).not.toBe(rConfigs[0]);
|
|
expect(configs[0].path).toBe(rConfigs[0].path);
|
|
expect(configs[0].component).toBe(rConfigs[0].component);
|
|
|
|
// children should be new array and routes shallow copied
|
|
expect(children).not.toBe(rChildren);
|
|
expect(children[0]).not.toBe(rChildren[0]);
|
|
expect(children[0].path).toBe(rChildren[0].path);
|
|
expect(children[1]).not.toBe(rChildren[1]);
|
|
expect(children[1].path).toBe(rChildren[1].path);
|
|
});
|
|
});
|
|
|
|
describe('resetRootComponentType', () => {
|
|
class NewRootComponent {}
|
|
|
|
beforeEach(() => { TestBed.configureTestingModule({imports: [RouterTestingModule]}); });
|
|
|
|
it('should not change root route when updating the root component', () => {
|
|
const r: Router = TestBed.get(Router);
|
|
const root = r.routerState.root;
|
|
|
|
(r as any).resetRootComponentType(NewRootComponent);
|
|
|
|
expect(r.routerState.root).toBe(root);
|
|
});
|
|
});
|
|
|
|
describe('setUpLocationChangeListener', () => {
|
|
beforeEach(() => { TestBed.configureTestingModule({imports: [RouterTestingModule]}); });
|
|
|
|
it('should be idempotent', inject([Router, Location], (r: Router, location: Location) => {
|
|
r.setUpLocationChangeListener();
|
|
const a = (<any>r).locationSubscription;
|
|
r.setUpLocationChangeListener();
|
|
const b = (<any>r).locationSubscription;
|
|
|
|
expect(a).toBe(b);
|
|
|
|
r.dispose();
|
|
r.setUpLocationChangeListener();
|
|
const c = (<any>r).locationSubscription;
|
|
|
|
expect(c).not.toBe(b);
|
|
}));
|
|
});
|
|
|
|
describe('PreActivation', () => {
|
|
const serializer = new DefaultUrlSerializer();
|
|
const inj = {get: (token: any) => () => `${token}_value`};
|
|
let empty: RouterStateSnapshot;
|
|
let logger: Logger;
|
|
let events: any[];
|
|
|
|
const CA_CHILD = 'canActivate_child';
|
|
const CA_CHILD_FALSE = 'canActivate_child_false';
|
|
const CA_CHILD_REDIRECT = 'canActivate_child_redirect';
|
|
const CAC_CHILD = 'canActivateChild_child';
|
|
const CAC_CHILD_FALSE = 'canActivateChild_child_false';
|
|
const CAC_CHILD_REDIRECT = 'canActivateChild_child_redirect';
|
|
const CA_GRANDCHILD = 'canActivate_grandchild';
|
|
const CA_GRANDCHILD_FALSE = 'canActivate_grandchild_false';
|
|
const CA_GRANDCHILD_REDIRECT = 'canActivate_grandchild_redirect';
|
|
const CDA_CHILD = 'canDeactivate_child';
|
|
const CDA_CHILD_FALSE = 'canDeactivate_child_false';
|
|
const CDA_CHILD_REDIRECT = 'canDeactivate_child_redirect';
|
|
const CDA_GRANDCHILD = 'canDeactivate_grandchild';
|
|
const CDA_GRANDCHILD_FALSE = 'canDeactivate_grandchild_false';
|
|
const CDA_GRANDCHILD_REDIRECT = 'canDeactivate_grandchild_redirect';
|
|
|
|
beforeEach(() => {
|
|
|
|
TestBed.configureTestingModule({
|
|
imports: [RouterTestingModule],
|
|
providers: [
|
|
Logger, provideTokenLogger(CA_CHILD), provideTokenLogger(CA_CHILD_FALSE, false),
|
|
provideTokenLogger(CA_CHILD_REDIRECT, serializer.parse('/canActivate_child_redirect')),
|
|
provideTokenLogger(CAC_CHILD), provideTokenLogger(CAC_CHILD_FALSE, false),
|
|
provideTokenLogger(
|
|
CAC_CHILD_REDIRECT, serializer.parse('/canActivateChild_child_redirect')),
|
|
provideTokenLogger(CA_GRANDCHILD), provideTokenLogger(CA_GRANDCHILD_FALSE, false),
|
|
provideTokenLogger(
|
|
CA_GRANDCHILD_REDIRECT, serializer.parse('/canActivate_grandchild_redirect')),
|
|
provideTokenLogger(CDA_CHILD), provideTokenLogger(CDA_CHILD_FALSE, false),
|
|
provideTokenLogger(CDA_CHILD_REDIRECT, serializer.parse('/canDeactivate_child_redirect')),
|
|
provideTokenLogger(CDA_GRANDCHILD), provideTokenLogger(CDA_GRANDCHILD_FALSE, false),
|
|
provideTokenLogger(
|
|
CDA_GRANDCHILD_REDIRECT, serializer.parse('/canDeactivate_grandchild_redirect'))
|
|
]
|
|
});
|
|
|
|
});
|
|
|
|
beforeEach(inject([Logger], (_logger: Logger) => {
|
|
empty = createEmptyStateSnapshot(serializer.parse('/'), null !);
|
|
logger = _logger;
|
|
events = [];
|
|
}));
|
|
|
|
describe('ChildActivation', () => {
|
|
it('should run', () => {
|
|
/**
|
|
* R --> R (ChildActivationStart)
|
|
* \
|
|
* child
|
|
*/
|
|
let result = false;
|
|
const childSnapshot =
|
|
createActivatedRouteSnapshot({component: 'child', routeConfig: {path: 'child'}});
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url', new TreeNode(empty.root, [new TreeNode(childSnapshot, [])]));
|
|
// Since we only test the guards, we don't need to provide a full navigation
|
|
// transition object with all properties set.
|
|
const testTransition = {
|
|
guards: getAllRouteGuards(futureState, empty, new ChildrenOutletContexts())
|
|
} as NavigationTransition;
|
|
|
|
of (testTransition).pipe(checkGuardsOperator(TestBed, (evt) => {
|
|
events.push(evt);
|
|
})).subscribe((x) => result = !!x.guardsResult, (e) => { throw e; });
|
|
|
|
expect(result).toBe(true);
|
|
expect(events.length).toEqual(2);
|
|
expect(events[0].snapshot).toBe(events[0].snapshot.root);
|
|
expect(events[1].snapshot.routeConfig.path).toBe('child');
|
|
});
|
|
|
|
it('should run from top to bottom', () => {
|
|
/**
|
|
* R --> R (ChildActivationStart)
|
|
* \
|
|
* child (ChildActivationStart)
|
|
* \
|
|
* grandchild (ChildActivationStart)
|
|
* \
|
|
* great grandchild
|
|
*/
|
|
let result = false;
|
|
const childSnapshot =
|
|
createActivatedRouteSnapshot({component: 'child', routeConfig: {path: 'child'}});
|
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'grandchild', routeConfig: {path: 'grandchild'}});
|
|
const greatGrandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'great-grandchild', routeConfig: {path: 'great-grandchild'}});
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url',
|
|
new TreeNode(
|
|
empty.root, [new TreeNode(childSnapshot, [
|
|
new TreeNode(grandchildSnapshot, [new TreeNode(greatGrandchildSnapshot, [])])
|
|
])]));
|
|
// Since we only test the guards, we don't need to provide a full navigation
|
|
// transition object with all properties set.
|
|
const testTransition = {
|
|
guards: getAllRouteGuards(futureState, empty, new ChildrenOutletContexts())
|
|
} as NavigationTransition;
|
|
|
|
of (testTransition).pipe(checkGuardsOperator(TestBed, (evt) => {
|
|
events.push(evt);
|
|
})).subscribe((x) => result = !!x.guardsResult, (e) => { throw e; });
|
|
|
|
expect(result).toBe(true);
|
|
expect(events.length).toEqual(6);
|
|
expect(events[0].snapshot).toBe(events[0].snapshot.root);
|
|
expect(events[2].snapshot.routeConfig.path).toBe('child');
|
|
expect(events[4].snapshot.routeConfig.path).toBe('grandchild');
|
|
expect(events[5].snapshot.routeConfig.path).toBe('great-grandchild');
|
|
});
|
|
|
|
it('should not run for unchanged routes', () => {
|
|
/**
|
|
* R --> R
|
|
* / \
|
|
* child child (ChildActivationStart)
|
|
* \
|
|
* grandchild
|
|
*/
|
|
let result = false;
|
|
const childSnapshot =
|
|
createActivatedRouteSnapshot({component: 'child', routeConfig: {path: 'child'}});
|
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'grandchild', routeConfig: {path: 'grandchild'}});
|
|
const currentState = new (RouterStateSnapshot as any)(
|
|
'url', new TreeNode(empty.root, [new TreeNode(childSnapshot, [])]));
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url',
|
|
new TreeNode(
|
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
|
// Since we only test the guards, we don't need to provide a full navigation
|
|
// transition object with all properties set.
|
|
const testTransition = {
|
|
guards: getAllRouteGuards(futureState, currentState, new ChildrenOutletContexts())
|
|
} as NavigationTransition;
|
|
|
|
of (testTransition).pipe(checkGuardsOperator(TestBed, (evt) => {
|
|
events.push(evt);
|
|
})).subscribe((x) => result = !!x.guardsResult, (e) => { throw e; });
|
|
|
|
expect(result).toBe(true);
|
|
expect(events.length).toEqual(2);
|
|
expect(events[0].snapshot).not.toBe(events[0].snapshot.root);
|
|
expect(events[0].snapshot.routeConfig.path).toBe('child');
|
|
});
|
|
|
|
it('should skip multiple unchanged routes but fire for all changed routes', () => {
|
|
/**
|
|
* R --> R
|
|
* / \
|
|
* child child
|
|
* / \
|
|
* grandchild grandchild (ChildActivationStart)
|
|
* \
|
|
* greatgrandchild (ChildActivationStart)
|
|
* \
|
|
* great-greatgrandchild
|
|
*/
|
|
let result = false;
|
|
const childSnapshot =
|
|
createActivatedRouteSnapshot({component: 'child', routeConfig: {path: 'child'}});
|
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'grandchild', routeConfig: {path: 'grandchild'}});
|
|
const greatGrandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'greatgrandchild', routeConfig: {path: 'greatgrandchild'}});
|
|
const greatGreatGrandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'great-greatgrandchild', routeConfig: {path: 'great-greatgrandchild'}});
|
|
const currentState = new (RouterStateSnapshot as any)(
|
|
'url',
|
|
new TreeNode(
|
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url',
|
|
new TreeNode(
|
|
empty.root,
|
|
[new TreeNode(
|
|
childSnapshot, [new TreeNode(grandchildSnapshot, [
|
|
new TreeNode(
|
|
greatGrandchildSnapshot, [new TreeNode(greatGreatGrandchildSnapshot, [])])
|
|
])])]));
|
|
// Since we only test the guards, we don't need to provide a full navigation
|
|
// transition object with all properties set.
|
|
const testTransition = {
|
|
guards: getAllRouteGuards(futureState, currentState, new ChildrenOutletContexts())
|
|
} as NavigationTransition;
|
|
|
|
of (testTransition).pipe(checkGuardsOperator(TestBed, (evt) => {
|
|
events.push(evt);
|
|
})).subscribe((x) => result = !!x.guardsResult, (e) => { throw e; });
|
|
|
|
expect(result).toBe(true);
|
|
expect(events.length).toEqual(4);
|
|
expect(events[0] instanceof ChildActivationStart).toBe(true);
|
|
expect(events[0].snapshot).not.toBe(events[0].snapshot.root);
|
|
expect(events[0].snapshot.routeConfig.path).toBe('grandchild');
|
|
expect(events[2].snapshot.routeConfig.path).toBe('greatgrandchild');
|
|
});
|
|
});
|
|
|
|
describe('guards', () => {
|
|
it('should run CanActivate checks', () => {
|
|
/**
|
|
* R --> R
|
|
* \
|
|
* child (CA, CAC)
|
|
* \
|
|
* grandchild (CA)
|
|
*/
|
|
|
|
const childSnapshot = createActivatedRouteSnapshot({
|
|
component: 'child',
|
|
routeConfig: {
|
|
|
|
canActivate: [CA_CHILD],
|
|
canActivateChild: [CAC_CHILD]
|
|
}
|
|
});
|
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
|
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url',
|
|
new TreeNode(
|
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
|
|
|
checkGuards(futureState, empty, TestBed, (result) => {
|
|
expect(result).toBe(true);
|
|
expect(logger.logs).toEqual([CA_CHILD, CAC_CHILD, CA_GRANDCHILD]);
|
|
});
|
|
});
|
|
|
|
it('should not run grandchild guards if child fails', () => {
|
|
/**
|
|
* R --> R
|
|
* \
|
|
* child (CA: x, CAC)
|
|
* \
|
|
* grandchild (CA)
|
|
*/
|
|
|
|
const childSnapshot = createActivatedRouteSnapshot({
|
|
component: 'child',
|
|
routeConfig: {canActivate: [CA_CHILD_FALSE], canActivateChild: [CAC_CHILD]}
|
|
});
|
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
|
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url',
|
|
new TreeNode(
|
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
|
|
|
checkGuards(futureState, empty, TestBed, (result) => {
|
|
expect(result).toBe(false);
|
|
expect(logger.logs).toEqual([CA_CHILD_FALSE]);
|
|
});
|
|
});
|
|
|
|
it('should not run grandchild guards if child canActivateChild fails', () => {
|
|
/**
|
|
* R --> R
|
|
* \
|
|
* child (CA, CAC: x)
|
|
* \
|
|
* grandchild (CA)
|
|
*/
|
|
|
|
const childSnapshot = createActivatedRouteSnapshot({
|
|
component: 'child',
|
|
routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD_FALSE]}
|
|
});
|
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
|
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url',
|
|
new TreeNode(
|
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
|
|
|
checkGuards(futureState, empty, TestBed, (result) => {
|
|
expect(result).toBe(false);
|
|
expect(logger.logs).toEqual([CA_CHILD, CAC_CHILD_FALSE]);
|
|
});
|
|
});
|
|
|
|
it('should run deactivate guards before activate guards', () => {
|
|
/**
|
|
* R --> R
|
|
* / \
|
|
* prev (CDA) child (CA)
|
|
* \
|
|
* grandchild (CA)
|
|
*/
|
|
|
|
const prevSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'prev', routeConfig: {canDeactivate: [CDA_CHILD]}});
|
|
|
|
const childSnapshot = createActivatedRouteSnapshot({
|
|
component: 'child',
|
|
routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
|
|
});
|
|
|
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
|
|
|
const currentState = new (RouterStateSnapshot as any)(
|
|
'prev', new TreeNode(empty.root, [new TreeNode(prevSnapshot, [])]));
|
|
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url',
|
|
new TreeNode(
|
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
|
|
|
checkGuards(futureState, currentState, TestBed, (result) => {
|
|
expect(logger.logs).toEqual([CDA_CHILD, CA_CHILD, CAC_CHILD, CA_GRANDCHILD]);
|
|
});
|
|
});
|
|
|
|
it('should not run activate if deactivate fails guards', () => {
|
|
/**
|
|
* R --> R
|
|
* / \
|
|
* prev (CDA: x) child (CA)
|
|
* \
|
|
* grandchild (CA)
|
|
*/
|
|
|
|
const prevSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'prev', routeConfig: {canDeactivate: [CDA_CHILD_FALSE]}});
|
|
const childSnapshot = createActivatedRouteSnapshot({
|
|
component: 'child',
|
|
routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
|
|
});
|
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
|
|
|
const currentState = new (RouterStateSnapshot as any)(
|
|
'prev', new TreeNode(empty.root, [new TreeNode(prevSnapshot, [])]));
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url',
|
|
new TreeNode(
|
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
|
|
|
checkGuards(futureState, currentState, TestBed, (result) => {
|
|
expect(result).toBe(false);
|
|
expect(logger.logs).toEqual([CDA_CHILD_FALSE]);
|
|
});
|
|
});
|
|
it('should deactivate from bottom up, then activate top down', () => {
|
|
/**
|
|
* R --> R
|
|
* / \
|
|
* prevChild (CDA) child (CA)
|
|
* / \
|
|
* prevGrandchild(CDA) grandchild (CA)
|
|
*/
|
|
|
|
const prevChildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'prev_child', routeConfig: {canDeactivate: [CDA_CHILD]}});
|
|
const prevGrandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'prev_grandchild', routeConfig: {canDeactivate: [CDA_GRANDCHILD]}});
|
|
const childSnapshot = createActivatedRouteSnapshot({
|
|
component: 'child',
|
|
routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
|
|
});
|
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
|
|
|
const currentState = new (RouterStateSnapshot as any)(
|
|
'prev', new TreeNode(empty.root, [
|
|
new TreeNode(prevChildSnapshot, [new TreeNode(prevGrandchildSnapshot, [])])
|
|
]));
|
|
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url',
|
|
new TreeNode(
|
|
empty.root, [new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
|
|
|
checkGuards(futureState, currentState, TestBed, (result) => {
|
|
expect(result).toBe(true);
|
|
expect(logger.logs).toEqual([
|
|
CDA_GRANDCHILD, CDA_CHILD, CA_CHILD, CAC_CHILD, CA_GRANDCHILD
|
|
]);
|
|
});
|
|
|
|
logger.empty();
|
|
checkGuards(currentState, futureState, TestBed, (result) => {
|
|
expect(result).toBe(true);
|
|
expect(logger.logs).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('UrlTree', () => {
|
|
it('should allow return of UrlTree from CanActivate', () => {
|
|
/**
|
|
* R --> R
|
|
* \
|
|
* child (CA: redirect)
|
|
*/
|
|
|
|
const childSnapshot = createActivatedRouteSnapshot({
|
|
component: 'child',
|
|
routeConfig: {
|
|
|
|
canActivate: [CA_CHILD_REDIRECT]
|
|
}
|
|
});
|
|
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url', new TreeNode(empty.root, [new TreeNode(childSnapshot, [])]));
|
|
|
|
checkGuards(futureState, empty, TestBed, (result) => {
|
|
expect(serializer.serialize(result as UrlTree)).toBe('/' + CA_CHILD_REDIRECT);
|
|
expect(logger.logs).toEqual([CA_CHILD_REDIRECT]);
|
|
});
|
|
});
|
|
|
|
it('should allow return of UrlTree from CanActivateChild', () => {
|
|
/**
|
|
* R --> R
|
|
* \
|
|
* child (CAC: redirect)
|
|
* \
|
|
* grandchild (CA)
|
|
*/
|
|
|
|
const childSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'child', routeConfig: {canActivateChild: [CAC_CHILD_REDIRECT]}});
|
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
|
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url', new TreeNode(
|
|
empty.root,
|
|
[new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
|
|
|
checkGuards(futureState, empty, TestBed, (result) => {
|
|
expect(serializer.serialize(result as UrlTree)).toBe('/' + CAC_CHILD_REDIRECT);
|
|
expect(logger.logs).toEqual([CAC_CHILD_REDIRECT]);
|
|
});
|
|
});
|
|
|
|
it('should allow return of UrlTree from a child CanActivate', () => {
|
|
/**
|
|
* R --> R
|
|
* \
|
|
* child (CAC)
|
|
* \
|
|
* grandchild (CA: redirect)
|
|
*/
|
|
|
|
const childSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'child', routeConfig: {canActivateChild: [CAC_CHILD]}});
|
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD_REDIRECT]}});
|
|
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url', new TreeNode(
|
|
empty.root,
|
|
[new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
|
|
|
checkGuards(futureState, empty, TestBed, (result) => {
|
|
expect(serializer.serialize(result as UrlTree)).toBe('/' + CA_GRANDCHILD_REDIRECT);
|
|
expect(logger.logs).toEqual([CAC_CHILD, CA_GRANDCHILD_REDIRECT]);
|
|
});
|
|
});
|
|
|
|
it('should allow return of UrlTree from a child CanDeactivate', () => {
|
|
/**
|
|
* R --> R
|
|
* / \
|
|
* prev (CDA: redirect) child (CA)
|
|
* \
|
|
* grandchild (CA)
|
|
*/
|
|
|
|
const prevSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'prev', routeConfig: {canDeactivate: [CDA_CHILD_REDIRECT]}});
|
|
const childSnapshot = createActivatedRouteSnapshot({
|
|
component: 'child',
|
|
routeConfig: {canActivate: [CA_CHILD], canActivateChild: [CAC_CHILD]}
|
|
});
|
|
const grandchildSnapshot = createActivatedRouteSnapshot(
|
|
{component: 'grandchild', routeConfig: {canActivate: [CA_GRANDCHILD]}});
|
|
|
|
const currentState = new (RouterStateSnapshot as any)(
|
|
'prev', new TreeNode(empty.root, [new TreeNode(prevSnapshot, [])]));
|
|
const futureState = new (RouterStateSnapshot as any)(
|
|
'url', new TreeNode(
|
|
empty.root,
|
|
[new TreeNode(childSnapshot, [new TreeNode(grandchildSnapshot, [])])]));
|
|
|
|
checkGuards(futureState, currentState, TestBed, (result) => {
|
|
expect(serializer.serialize(result as UrlTree)).toBe('/' + CDA_CHILD_REDIRECT);
|
|
expect(logger.logs).toEqual([CDA_CHILD_REDIRECT]);
|
|
});
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
describe('resolve', () => {
|
|
|
|
it('should resolve data', () => {
|
|
/**
|
|
* R --> R
|
|
* \
|
|
* a
|
|
*/
|
|
const r = {data: 'resolver'};
|
|
const n = createActivatedRouteSnapshot({component: 'a', resolve: r});
|
|
const s = new (RouterStateSnapshot as any)(
|
|
'url', new TreeNode(empty.root, [new TreeNode(n, [])]));
|
|
|
|
checkResolveData(s, empty, inj, () => {
|
|
expect(s.root.firstChild !.data).toEqual({data: 'resolver_value'});
|
|
});
|
|
});
|
|
|
|
it('should wait for the parent resolve to complete', () => {
|
|
/**
|
|
* R --> R
|
|
* \
|
|
* null (resolve: parentResolve)
|
|
* \
|
|
* b (resolve: childResolve)
|
|
*/
|
|
const parentResolve = {data: 'resolver'};
|
|
const childResolve = {};
|
|
|
|
const parent = createActivatedRouteSnapshot({component: null !, resolve: parentResolve});
|
|
const child = createActivatedRouteSnapshot({component: 'b', resolve: childResolve});
|
|
|
|
const s = new (RouterStateSnapshot as any)(
|
|
'url', new TreeNode(empty.root, [new TreeNode(parent, [new TreeNode(child, [])])]));
|
|
|
|
const inj = {get: (token: any) => () => Promise.resolve(`${token}_value`)};
|
|
|
|
checkResolveData(s, empty, inj, () => {
|
|
expect(s.root.firstChild !.firstChild !.data).toEqual({data: 'resolver_value'});
|
|
});
|
|
});
|
|
|
|
it('should copy over data when creating a snapshot', () => {
|
|
/**
|
|
* R --> R --> R
|
|
* \ \
|
|
* n1 (resolve: r1) n21 (resolve: r1)
|
|
* \
|
|
* n22 (resolve: r2)
|
|
*/
|
|
const r1 = {data: 'resolver1'};
|
|
const r2 = {data: 'resolver2'};
|
|
|
|
const n1 = createActivatedRouteSnapshot({component: 'a', resolve: r1});
|
|
const s1 = new (RouterStateSnapshot as any)(
|
|
'url', new TreeNode(empty.root, [new TreeNode(n1, [])]));
|
|
checkResolveData(s1, empty, inj, () => {});
|
|
|
|
const n21 = createActivatedRouteSnapshot({component: 'a', resolve: r1});
|
|
const n22 = createActivatedRouteSnapshot({component: 'b', resolve: r2});
|
|
const s2 = new (RouterStateSnapshot as any)(
|
|
'url', new TreeNode(empty.root, [new TreeNode(n21, [new TreeNode(n22, [])])]));
|
|
checkResolveData(s2, s1, inj, () => {
|
|
expect(s2.root.firstChild !.data).toEqual({data: 'resolver1_value'});
|
|
expect(s2.root.firstChild !.firstChild !.data).toEqual({data: 'resolver2_value'});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
function checkResolveData(
|
|
future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any, check: any): void {
|
|
// Since we only test the guards and their resolve data function, we don't need to provide
|
|
// a full navigation transition object with all properties set.
|
|
of ({
|
|
guards: getAllRouteGuards(future, curr, new ChildrenOutletContexts())
|
|
} as NavigationTransition)
|
|
.pipe(resolveDataOperator('emptyOnly', injector))
|
|
.subscribe(check, (e) => { throw e; });
|
|
}
|
|
|
|
function checkGuards(
|
|
future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any,
|
|
check: (result: boolean | UrlTree) => void): void {
|
|
// Since we only test the guards, we don't need to provide a full navigation
|
|
// transition object with all properties set.
|
|
of ({
|
|
guards: getAllRouteGuards(future, curr, new ChildrenOutletContexts())
|
|
} as NavigationTransition)
|
|
.pipe(checkGuardsOperator(injector))
|
|
.subscribe({
|
|
next(t) {
|
|
if (t.guardsResult === null) throw new Error('Guard result expected');
|
|
return check(t.guardsResult);
|
|
},
|
|
error(e) { throw e; }
|
|
});
|
|
}
|