From cae47b82a71ac38a7f8eb0c355e7c094949a26ea Mon Sep 17 00:00:00 2001 From: Michael Wind Date: Thu, 25 Jul 2024 21:40:18 +0200 Subject: [PATCH] docs: can-load guard #1008 --- docs/articles/guides/routing-guard.md | 4 +- examples/TestRoutingGuard/can-load.spec.ts | 172 +++++++++++++++++++++ 2 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 examples/TestRoutingGuard/can-load.spec.ts diff --git a/docs/articles/guides/routing-guard.md b/docs/articles/guides/routing-guard.md index f0b5a41766..da75e86149 100644 --- a/docs/articles/guides/routing-guard.md +++ b/docs/articles/guides/routing-guard.md @@ -26,8 +26,8 @@ The example below is applicable for all types of guards: [CodeSandbox](https://codesandbox.io/p/sandbox/github/help-me-mom/ng-mocks-sandbox/tree/tests/?file=/src/examples/TestRoutingGuard/can-match.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanMatch), [StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingGuard/can-match.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanMatch) - `canLoad` - - [CodeSandbox](https://codesandbox.io/p/sandbox/github/help-me-mom/ng-mocks-sandbox/tree/tests/?file=/src/examples/TestRoutingGuard/can-match.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanMatch), - [StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingGuard/can-match.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanMatch) + [CodeSandbox](https://codesandbox.io/p/sandbox/github/help-me-mom/ng-mocks-sandbox/tree/tests/?file=/src/examples/TestRoutingGuard/can-load.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanLoad), + [StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingGuard/can-load.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3AcanLoad) - class guards (legacy) - [CodeSandbox](https://codesandbox.io/p/sandbox/github/help-me-mom/ng-mocks-sandbox/tree/tests/?file=/src/examples/TestRoutingGuard/test.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3Atest), [StackBlitz](https://stackblitz.com/github/help-me-mom/ng-mocks-sandbox/tree/tests?file=src/examples/TestRoutingGuard/test.spec.ts&initialpath=%3Fspec%3DTestRoutingGuard%3Atest) diff --git a/examples/TestRoutingGuard/can-load.spec.ts b/examples/TestRoutingGuard/can-load.spec.ts new file mode 100644 index 0000000000..dafdb229d3 --- /dev/null +++ b/examples/TestRoutingGuard/can-load.spec.ts @@ -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 it's 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 to be a login form. +// It will be replaced with a mock copy. +@Component({ + selector: 'login', + template: 'login', +}) +class LoginComponent {} + +// A simple component pretending to be 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 it's integration with RouterModule. + // Therefore, RouterModule and guard should be kept, + // and the rest of the module which defines the route can be mocked. + // To configure the RouterModule for the test, + // RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS + // should be specified as the first parameter of MockBuilder (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(); + }); +});