-
-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(router): add
daffRouterComposeGuards
(#2809)
- Loading branch information
Showing
5 changed files
with
230 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
export { daffRouterNamedViewsCollect } from './collect-data'; | ||
export { daffRouterDataCollect } from './collect-data'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import { | ||
ActivatedRouteSnapshot, | ||
CanActivateFn, | ||
RouterStateSnapshot, | ||
} from '@angular/router'; | ||
import { of } from 'rxjs'; | ||
|
||
import { observe } from '@daffodil/core'; | ||
|
||
import { daffRouterComposeGuards } from './compose'; | ||
|
||
describe('@daffodil/router | daffRouterComposeGuards', () => { | ||
let blockingGuard0: jasmine.Spy<CanActivateFn>; | ||
let blockingGuard1: jasmine.Spy<CanActivateFn>; | ||
let blockingGuard2: jasmine.Spy<CanActivateFn>; | ||
let nonBlockingGuard0: jasmine.Spy<CanActivateFn>; | ||
let nonBlockingGuard1: jasmine.Spy<CanActivateFn>; | ||
let nonBlockingGuard2: jasmine.Spy<CanActivateFn>; | ||
let result: CanActivateFn; | ||
const args = <const>[new ActivatedRouteSnapshot(), <RouterStateSnapshot>{}]; | ||
|
||
beforeEach(() => { | ||
blockingGuard0 = jasmine.createSpy().and.returnValue(of(true)); | ||
blockingGuard1 = jasmine.createSpy().and.returnValue(of(true)); | ||
blockingGuard2 = jasmine.createSpy().and.returnValue(of(true)); | ||
nonBlockingGuard0 = jasmine.createSpy().and.returnValue(of(true)); | ||
nonBlockingGuard1 = jasmine.createSpy().and.returnValue(of(true)); | ||
nonBlockingGuard2 = jasmine.createSpy().and.returnValue(of(true)); | ||
|
||
result = daffRouterComposeGuards([ | ||
blockingGuard0, | ||
blockingGuard1, | ||
blockingGuard2, | ||
], [ | ||
nonBlockingGuard0, | ||
nonBlockingGuard1, | ||
nonBlockingGuard2, | ||
]); | ||
}); | ||
|
||
describe('when all guards return true', () => { | ||
it('should return true', (done) => { | ||
observe(result(...args)).subscribe((res) => { | ||
expect(res).toBeTrue(); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('should call the all the guards', (done) => { | ||
observe(result(...args)).subscribe((res) => { | ||
expect(blockingGuard0).toHaveBeenCalledWith(...args); | ||
expect(blockingGuard1).toHaveBeenCalledWith(...args); | ||
expect(blockingGuard2).toHaveBeenCalledWith(...args); | ||
expect(nonBlockingGuard0).toHaveBeenCalledWith(...args); | ||
expect(nonBlockingGuard1).toHaveBeenCalledWith(...args); | ||
expect(nonBlockingGuard2).toHaveBeenCalledWith(...args); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('should call the first blocking guard before all the other guards', (done) => { | ||
blockingGuard0.and.callFake(() => { | ||
expect(blockingGuard1).not.toHaveBeenCalled(); | ||
expect(blockingGuard2).not.toHaveBeenCalled(); | ||
expect(nonBlockingGuard0).not.toHaveBeenCalled(); | ||
expect(nonBlockingGuard1).not.toHaveBeenCalled(); | ||
expect(nonBlockingGuard2).not.toHaveBeenCalled(); | ||
return true; | ||
}); | ||
observe(result(...args)).subscribe((res) => { | ||
done(); | ||
}); | ||
}); | ||
|
||
it('should call the second blocking guard after the first and before all the other guards', (done) => { | ||
blockingGuard1.and.callFake(() => { | ||
expect(blockingGuard0).toHaveBeenCalledWith(...args); | ||
|
||
expect(blockingGuard2).not.toHaveBeenCalled(); | ||
expect(nonBlockingGuard0).not.toHaveBeenCalled(); | ||
expect(nonBlockingGuard1).not.toHaveBeenCalled(); | ||
expect(nonBlockingGuard2).not.toHaveBeenCalled(); | ||
return true; | ||
}); | ||
observe(result(...args)).subscribe((res) => { | ||
done(); | ||
}); | ||
}); | ||
|
||
it('should call the third blocking guard after the first and second and before all the other guards', (done) => { | ||
blockingGuard2.and.callFake(() => { | ||
expect(blockingGuard0).toHaveBeenCalledWith(...args); | ||
expect(blockingGuard1).toHaveBeenCalledWith(...args); | ||
|
||
expect(nonBlockingGuard0).not.toHaveBeenCalled(); | ||
expect(nonBlockingGuard1).not.toHaveBeenCalled(); | ||
expect(nonBlockingGuard2).not.toHaveBeenCalled(); | ||
return true; | ||
}); | ||
observe(result(...args)).subscribe((res) => { | ||
done(); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when the first blocking guard returns false', () => { | ||
beforeEach(() => { | ||
blockingGuard0.and.returnValue(of(false)); | ||
}); | ||
|
||
it('should return false', (done) => { | ||
observe(result(...args)).subscribe((res) => { | ||
expect(res).toBeFalse(); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('should not call any other guards', (done) => { | ||
observe(result(...args)).subscribe((res) => { | ||
expect(blockingGuard1).not.toHaveBeenCalled(); | ||
expect(blockingGuard2).not.toHaveBeenCalled(); | ||
expect(nonBlockingGuard0).not.toHaveBeenCalled(); | ||
expect(nonBlockingGuard1).not.toHaveBeenCalled(); | ||
expect(nonBlockingGuard2).not.toHaveBeenCalled(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when the second blocking guard returns false', () => { | ||
beforeEach(() => { | ||
blockingGuard1.and.returnValue(of(false)); | ||
}); | ||
|
||
it('should return false', (done) => { | ||
observe(result(...args)).subscribe((res) => { | ||
expect(res).toBeFalse(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when the third blocking guard returns false', () => { | ||
beforeEach(() => { | ||
blockingGuard2.and.returnValue(of(false)); | ||
}); | ||
|
||
it('should return false', (done) => { | ||
observe(result(...args)).subscribe((res) => { | ||
expect(res).toBeFalse(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when a single non-blocking guard returns false', () => { | ||
beforeEach(() => { | ||
nonBlockingGuard1.and.returnValue(of(false)); | ||
}); | ||
|
||
it('should return false', (done) => { | ||
observe(result(...args)).subscribe((res) => { | ||
expect(res).toBeFalse(); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('should call the all the guards', (done) => { | ||
observe(result(...args)).subscribe((res) => { | ||
expect(blockingGuard0).toHaveBeenCalledWith(...args); | ||
expect(blockingGuard1).toHaveBeenCalledWith(...args); | ||
expect(blockingGuard2).toHaveBeenCalledWith(...args); | ||
expect(nonBlockingGuard0).toHaveBeenCalledWith(...args); | ||
expect(nonBlockingGuard1).toHaveBeenCalledWith(...args); | ||
expect(nonBlockingGuard2).toHaveBeenCalledWith(...args); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { | ||
GuardResult, | ||
UrlTree, | ||
CanActivateChildFn, | ||
CanActivateFn, | ||
} from '@angular/router'; | ||
import { | ||
of, | ||
switchMap, | ||
combineLatest, | ||
OperatorFunction, | ||
map, | ||
} from 'rxjs'; | ||
|
||
import { observe } from '@daffodil/core'; | ||
|
||
function guardFailure(val: GuardResult): boolean { | ||
return !val || val instanceof UrlTree; | ||
} | ||
|
||
/** | ||
* Composes functional guards together into a single functional guard. | ||
* Both blocking and non-blocking guards may be specified. | ||
* Blocking guards run in serial, waiting for a response before calling the next blocking guard. | ||
* If a blocking guard returns a failure condition (falsy or a `UrlTree`), all future guards will be skipped and not called. The failure condition return will be returned from the composed guard. | ||
* Non-blocking guards are run in parallel after all of the blocking guards have finished. | ||
*/ | ||
export function daffRouterComposeGuards(blockingGuards: Array<CanActivateFn | CanActivateChildFn>, nonBlockingGuards: Array<CanActivateFn | CanActivateChildFn> = []): CanActivateFn | CanActivateChildFn { | ||
return (...args) => of(true).pipe( | ||
// @ts-expect-error rxjs has not written a function overload for only rest param...so this errors https://github.com/ReactiveX/rxjs/issues/4177#issuecomment-2125328922 | ||
...blockingGuards.map<OperatorFunction<GuardResult, GuardResult>>((guard) => | ||
switchMap((prevGuardResult) => | ||
guardFailure(prevGuardResult) | ||
? of(prevGuardResult) | ||
: observe(guard(...args)), | ||
), | ||
), | ||
switchMap((prevGuardResult) => | ||
guardFailure(prevGuardResult) | ||
? of(prevGuardResult) | ||
: combineLatest(nonBlockingGuards.map((guard) => observe(guard(...args)))).pipe( | ||
map((results) => results.reduce((acc, res) => res ? acc && res : res)), | ||
), | ||
), | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './compose'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
export * from './named-view/public_api'; | ||
export * from './data/public_api'; | ||
export * from './guards/public_api'; |