forked from help-me-mom/ng-mocks
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: can-load guard help-me-mom#1008
- Loading branch information
Michael Wind
committed
Jul 25, 2024
1 parent
f2e7406
commit d0eadc8
Showing
2 changed files
with
174 additions
and
2 deletions.
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
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,172 @@ | ||
import { Location } from '@angular/common'; | ||
import { | ||
Component, | ||
inject, | ||
Injectable, | ||
NgModule, | ||
VERSION, | ||
} from '@angular/core'; | ||
import { | ||
CanLoadFn, | ||
Router, | ||
RouterModule, | ||
RouterOutlet, | ||
} from '@angular/router'; | ||
import { RouterTestingModule } from '@angular/router/testing'; | ||
import { from } from 'rxjs'; | ||
import { mapTo } from 'rxjs/operators'; | ||
|
||
import { | ||
MockBuilder, | ||
MockRender, | ||
NG_MOCKS_GUARDS, | ||
NG_MOCKS_ROOT_PROVIDERS, | ||
ngMocks, | ||
} from 'ng-mocks'; | ||
|
||
// A simple service simulating login check. | ||
// It will be replaced with its mock copy. | ||
@Injectable() | ||
class LoginService { | ||
public isLoggedIn = false; | ||
} | ||
|
||
// The canLoad guard we want to test. | ||
const canLoadGuard: CanLoadFn = (route, segments) => { | ||
if (route && segments && inject(LoginService).isLoggedIn) { | ||
return true; | ||
} | ||
|
||
return from(inject(Router).navigate(['/login'])).pipe(mapTo(false)); | ||
}; | ||
|
||
// Another canLoad guard like in a real world example, | ||
// which should be removed from testing to avoid side effects on the route. | ||
const sideEffectCanLoadGuard: CanLoadFn = () => false; | ||
|
||
// A simple component pretending a login form. | ||
// It will be replaced with a mock copy. | ||
@Component({ | ||
selector: 'login', | ||
template: 'login', | ||
}) | ||
class LoginComponent {} | ||
|
||
// A simple component pretending a protected dashboard. | ||
// It will be replaced with a mock copy. | ||
@Component({ | ||
selector: 'dashboard', | ||
template: 'dashboard', | ||
}) | ||
class DashboardComponent {} | ||
|
||
@NgModule({ | ||
declarations: [DashboardComponent], | ||
imports: [ | ||
RouterModule.forChild([ | ||
{ | ||
path: 'dashboard', | ||
component: DashboardComponent, | ||
}, | ||
{ | ||
path: '**', | ||
redirectTo: 'dashboard', | ||
}, | ||
]), | ||
], | ||
exports: [], | ||
}) | ||
class DashboardModule {} | ||
|
||
// Definition of the routing module. | ||
@NgModule({ | ||
declarations: [LoginComponent, DashboardComponent], | ||
exports: [RouterModule], | ||
imports: [ | ||
RouterModule.forRoot([ | ||
{ | ||
component: LoginComponent, | ||
path: 'login', | ||
}, | ||
{ | ||
canLoad: [canLoadGuard, sideEffectCanLoadGuard], | ||
path: '', | ||
loadChildren: () => DashboardModule, | ||
}, | ||
]), | ||
], | ||
providers: [LoginService], | ||
}) | ||
class TargetModule {} | ||
|
||
describe('TestRoutingGuard:canLoad', () => { | ||
// Because we want to test a canLoad guard, it means that we want to | ||
// test its integration with RouterModule. | ||
// Therefore, RouterModule and the guard should be kept, | ||
// and the rest of the module which defines the route can be mocked. | ||
// To configure RouterModule for the test, | ||
// RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS | ||
// should be specified as the first parameter of MockBuilder (yes, with empty routes). | ||
// The module with routes and the guard should be specified | ||
// as the second parameter of MockBuilder. | ||
// Then `NG_MOCKS_GUARDS` should be excluded to remove all guards, | ||
// and `canLoadGuard` should be kept to let you test it. | ||
beforeEach(() => { | ||
return MockBuilder( | ||
[ | ||
RouterModule, | ||
RouterTestingModule.withRoutes([]), | ||
NG_MOCKS_ROOT_PROVIDERS, | ||
], | ||
[TargetModule], | ||
) | ||
.exclude(NG_MOCKS_GUARDS) | ||
.keep(canLoadGuard); | ||
}); | ||
|
||
it('redirects to login', async () => { | ||
if (Number.parseInt(VERSION.major, 10) < 7) { | ||
pending('Need Angular 7+'); | ||
|
||
return; | ||
} | ||
|
||
const fixture = MockRender(RouterOutlet, {}); | ||
const router = ngMocks.get(Router); | ||
const location = ngMocks.get(Location); | ||
|
||
// First we need to initialize navigation. | ||
if (fixture.ngZone) { | ||
fixture.ngZone.run(() => router.initialNavigation()); | ||
// is needed to wait until routing is finished. | ||
await fixture.whenStable(); | ||
} | ||
|
||
// Because by default we are not logged, the guard should | ||
// redirect us /login page. | ||
expect(location.path()).toEqual('/login'); | ||
expect(() => ngMocks.find(LoginComponent)).not.toThrow(); | ||
}); | ||
|
||
it('loads dashboard', async () => { | ||
const fixture = MockRender(RouterOutlet, {}); | ||
const router = ngMocks.get(Router); | ||
const location = ngMocks.get(Location); | ||
const loginService = ngMocks.get(LoginService); | ||
|
||
// Letting the guard know we have been logged in. | ||
loginService.isLoggedIn = true; | ||
|
||
// First we need to initialize navigation. | ||
if (fixture.ngZone) { | ||
fixture.ngZone.run(() => router.initialNavigation()); | ||
// is needed to wait until routing is finished. | ||
await fixture.whenStable(); | ||
} | ||
|
||
// Because now we are logged in, the guard should let us land on | ||
// the dashboard. | ||
expect(location.path()).toEqual('/dashboard'); | ||
expect(() => ngMocks.find(DashboardComponent)).not.toThrow(); | ||
}); | ||
}); |