Last active
February 8, 2023 23:16
-
-
Save LayZeeDK/e64005b9ce11d864cf084fae5f2b7837 to your computer and use it in GitHub Desktop.
Dashboard: Shallow and integrated routing component test suites.
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
<h3>Top Heroes</h3> | |
<div class="grid grid-pad"> | |
<a *ngFor="let hero of heroes" class="col-1-4" | |
routerLink="/detail/{{hero.id}}"> | |
<div class="module hero"> | |
<h4>{{hero.name}}</h4> | |
</div> | |
</a> | |
</div> | |
<app-hero-search></app-hero-search> |
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
import { Location } from '@angular/common'; | |
import { Component, ViewChild } from '@angular/core'; | |
import { | |
ComponentFixture, | |
fakeAsync, | |
TestBed, | |
tick, | |
} from '@angular/core/testing'; | |
import { By } from '@angular/platform-browser'; | |
import { Router, RouterOutlet } from '@angular/router'; | |
import { RouterTestingModule } from '@angular/router/testing'; | |
import { asapScheduler, of } from 'rxjs'; | |
import { observeOn } from 'rxjs/operators'; | |
import { HeroSearchComponent } from '../hero-search/hero-search.component'; | |
import { HeroService } from '../hero.service'; | |
import { HEROES } from '../mock-heroes'; | |
import { DashboardComponent } from './dashboard.component'; | |
@Component({ | |
template: '<router-outlet></router-outlet>', | |
}) | |
class TestRootComponent { | |
@ViewChild(RouterOutlet) | |
routerOutlet: RouterOutlet; | |
} | |
@Component({ | |
template: '', | |
}) | |
class TestHeroDetailComponent {} | |
const leftMouseButton = 0; | |
describe('DashboardComponent (integrated)', () => { | |
function advance() { | |
tick(); | |
rootFixture.detectChanges(); | |
} | |
function clickTopHero() { | |
const firstHeroLink = rootFixture.debugElement.query(By.css('a')); | |
rootFixture.ngZone.run(() => | |
firstHeroLink.triggerEventHandler('click', { button: leftMouseButton })); | |
} | |
function getActiveComponent<T>(): T { | |
return rootComponent.routerOutlet.component as T; | |
} | |
beforeEach(async () => { | |
const fakeService = { | |
getHeroes() { | |
return of([...HEROES]).pipe(observeOn(asapScheduler)); | |
}, | |
} as Partial<HeroService>; | |
TestBed.configureTestingModule({ | |
declarations: [ | |
TestRootComponent, | |
TestHeroDetailComponent, | |
DashboardComponent, | |
HeroSearchComponent, | |
], | |
imports: [ | |
RouterTestingModule.withRoutes([ | |
{ path: '', pathMatch: 'full', component: DashboardComponent }, | |
{ path: 'detail/:id', component: TestHeroDetailComponent }, | |
]), | |
], | |
providers: [ | |
{ provide: HeroService, useValue: fakeService }, | |
], | |
}); | |
await TestBed.compileComponents(); | |
rootFixture = TestBed.createComponent(TestRootComponent); | |
rootComponent = rootFixture.componentInstance; | |
location = TestBed.inject(Location); | |
}); | |
beforeEach(fakeAsync(() => { | |
const router = TestBed.inject(Router); | |
rootFixture.ngZone.run(() => router.initialNavigation()); | |
advance(); | |
advance(); | |
})); | |
let location: Location; | |
let rootComponent: TestRootComponent; | |
let rootFixture: ComponentFixture<TestRootComponent>; | |
it('navigates to the detail view when a hero link is clicked', fakeAsync(() => { | |
const component: DashboardComponent = getActiveComponent(); | |
const [topHero] = component.heroes; | |
clickTopHero(); | |
advance(); | |
const expectedPath = '/detail/' + topHero.id; | |
expect(location.path()).toBe( | |
expectedPath, | |
'must navigate to the detail view for the top hero'); | |
})); | |
}); |
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
import { | |
CUSTOM_ELEMENTS_SCHEMA, | |
Directive, | |
HostListener, | |
Input, | |
} from '@angular/core'; | |
import { | |
ComponentFixture, | |
fakeAsync, | |
TestBed, | |
tick, | |
} from '@angular/core/testing'; | |
import { By } from '@angular/platform-browser'; | |
import { Router } from '@angular/router'; | |
import { asapScheduler, of } from 'rxjs'; | |
import { observeOn } from 'rxjs/operators'; | |
import { HeroService } from '../hero.service'; | |
import { HEROES } from '../mock-heroes'; | |
import { DashboardComponent } from './dashboard.component'; | |
@Directive({ | |
selector: '[routerLink]' | |
}) | |
class FakeRouterLink { | |
@Input() | |
routerLink = ''; | |
constructor( | |
private router: Router, | |
) { } | |
@HostListener('click') | |
onClick() { | |
this.router.navigateByUrl(this.routerLink); | |
} | |
} | |
const leftMouseButton = 0; | |
describe('DashboardComponent (shallow)', () => { | |
function advance() { | |
tick(); | |
fixture.detectChanges(); | |
} | |
function clickTopHero() { | |
const firstLink = fixture.debugElement.query(By.css('a')); | |
firstLink.triggerEventHandler('click', { button: leftMouseButton }); | |
} | |
beforeEach(async () => { | |
const fakeService = { | |
getHeroes() { | |
return of([...HEROES]).pipe(observeOn(asapScheduler)); | |
}, | |
} as Partial<HeroService>; | |
routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']); | |
TestBed.configureTestingModule({ | |
declarations: [ | |
DashboardComponent, | |
FakeRouterLink, | |
], | |
providers: [ | |
{ provide: HeroService, useValue: fakeService }, | |
{ provide: Router, useValue: routerSpy }, | |
], | |
schemas: [CUSTOM_ELEMENTS_SCHEMA], | |
}); | |
await TestBed.compileComponents(); | |
}); | |
beforeEach(fakeAsync(() => { | |
fixture = TestBed.createComponent(DashboardComponent); | |
component = fixture.componentInstance; | |
advance(); | |
advance(); | |
})); | |
let component: DashboardComponent; | |
let fixture: ComponentFixture<DashboardComponent>; | |
let routerSpy: jasmine.SpyObj<Router>; | |
it('navigates to hero detail when a hero link is clicked', fakeAsync(() => { | |
const [topHero] = component.heroes; | |
clickTopHero(); | |
advance(); | |
const expectedPath = '/detail/' + topHero.id; | |
const [actualPath] = routerSpy.navigateByUrl.calls.first().args; | |
expect(actualPath).toBe( | |
expectedPath, | |
'must navigate to the detail view for the top hero'); | |
})); | |
}); |
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
import { Component, OnInit } from '@angular/core'; | |
import { Hero } from '../hero'; | |
import { HeroService } from '../hero.service'; | |
@Component({ | |
selector: 'app-dashboard', | |
templateUrl: './dashboard.component.html', | |
styleUrls: ['./dashboard.component.css'] | |
}) | |
export class DashboardComponent implements OnInit { | |
heroes: Hero[] = []; | |
constructor(private heroService: HeroService) {} | |
ngOnInit() { | |
this.getHeroes(); | |
} | |
getHeroes(): void { | |
this.heroService.getHeroes() | |
.subscribe(heroes => this.heroes = heroes.slice(1, 5)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment