feat(router): allow guards to return UrlTree as well as boolean (#26521)

* Removed `andObservable` helper function in favor of inline implementation
* Flow `boolean | UrlTree` through guards check
* Add tests to verify behavior of `checkGuards` function flowing `UrlTree` properly

PR Close #26521
This commit is contained in:
Jason Aden
2018-10-16 19:50:48 -07:00
committed by Matias Niemelä
parent 152ca66eba
commit 081f95c812
7 changed files with 213 additions and 77 deletions

View File

@ -17,7 +17,7 @@ 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} from '../src/url_tree';
import {DefaultUrlSerializer, UrlTree} from '../src/url_tree';
import {getAllRouteGuards} from '../src/utils/preactivation';
import {TreeNode} from '../src/utils/tree';
import {RouterTestingModule} from '../testing/src/router_testing_module';
@ -103,23 +103,38 @@ describe('Router', () => {
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_GRANDCHILD), provideTokenLogger(CDA_GRANDCHILD_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'))
]
});
@ -389,11 +404,11 @@ describe('Router', () => {
it('should not run activate if deactivate fails guards', () => {
/**
* R --> R
* / \
* prev (CDA) child (CA)
* \
* grandchild (CA)
* R --> R
* / \
* prev (CDA: x) child (CA)
* \
* grandchild (CA)
*/
const prevSnapshot = createActivatedRouteSnapshot(
@ -460,6 +475,114 @@ describe('Router', () => {
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', () => {
@ -544,10 +667,15 @@ function checkResolveData(
function checkGuards(
future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any,
check: (result: boolean) => void): void {
check: (result: boolean | UrlTree) => void): void {
of ({
guards: getAllRouteGuards(future, curr, new ChildrenOutletContexts())
} as Partial<NavigationTransition>)
.pipe(checkGuardsOperator(injector))
.subscribe(t => check(!!t.guardsResult), (e) => { throw e; });
.subscribe(
t => {
if (t.guardsResult === null) throw new Error('Guard result expected');
return check(t.guardsResult);
},
(e) => { throw e; });
}