230 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			230 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
|  | import {HeroesComponent} from './heroes.component'; | ||
|  | import {Hero} from './hero'; | ||
|  | import {HeroService} from './hero.service'; | ||
|  | import {User} from './user'; | ||
|  | 
 | ||
|  | describe('HeroesComponent (Test Plan)', () => { | ||
|  |   xit('can be created'); | ||
|  |   xit('has expected userName'); | ||
|  | 
 | ||
|  |   describe('#onInit', () => { | ||
|  |     xit('HeroService.refresh not called immediately'); | ||
|  |     xit('onInit calls HeroService.refresh'); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe('#heroes', () => { | ||
|  |     xit('lacks heroes when created'); | ||
|  |     xit('has heroes after cache loaded'); | ||
|  |     xit('restores heroes after refresh called again'); | ||
|  | 
 | ||
|  |     xit('binds view to heroes'); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe('#onSelected', () => { | ||
|  |     xit('no hero is selected by default'); | ||
|  |     xit('sets the "currentHero"'); | ||
|  |     xit('no hero is selected after onRefresh() called'); | ||
|  | 
 | ||
|  |     xit('the view of the "currentHero" has the "selected" class (NG2 BUG)'); | ||
|  |     xit('the view of a non-selected hero does NOT have the "selected" class'); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe('#onDelete', () => { | ||
|  |     xit('removes the supplied hero (only) from the list'); | ||
|  |     xit('removes the currentHero from the list if no hero argument'); | ||
|  |     xit('is harmless if no supplied or current hero'); | ||
|  |     xit('is harmless if hero not in list'); | ||
|  |     xit('is harmless if the list is empty'); | ||
|  |     xit('the new currentHero is the one after the removed hero'); | ||
|  |     xit('the new currentHero is the one before the removed hero if none after'); | ||
|  | 
 | ||
|  |     xit('the list view does not contain the "deleted" currentHero'); | ||
|  |   }); | ||
|  | }); | ||
|  | 
 | ||
|  | let hc:HeroesComponent; | ||
|  | let heroData: Hero[]; // fresh heroes for each test | ||
|  | let mockUser: User; | ||
|  | let service: HeroService; | ||
|  | 
 | ||
|  | // get the promise from the refresh spy; | ||
|  | // casting required because of inadequate d.ts for Jasmine | ||
|  | let refreshPromise = () => (<any>service.refresh).calls.mostRecent().returnValue; | ||
|  | 
 | ||
|  | describe('HeroesComponent (no Angular)', () => { | ||
|  | 
 | ||
|  |   beforeEach(()=> { | ||
|  |     heroData = [new Hero(1, 'Foo'), new Hero(2, 'Bar'), new Hero(3, 'Baz')]; | ||
|  |     mockUser = new User(); | ||
|  |   }); | ||
|  | 
 | ||
|  |   beforeEach(()=> { | ||
|  |     service = <any> new HappyHeroService(); | ||
|  |     hc = new HeroesComponent(service, mockUser) | ||
|  |   }); | ||
|  | 
 | ||
|  |   it('can be created', () => { | ||
|  |     expect(hc instanceof HeroesComponent).toEqual(true); // proof of life | ||
|  |   }); | ||
|  | 
 | ||
|  |   it('has expected userName', () => { | ||
|  |     expect(hc.userName).toEqual(mockUser.name); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe('#onInit', () => { | ||
|  |     it('HeroService.refresh not called immediately', () => { | ||
|  |       let spy = <jasmine.Spy><any> service.refresh; | ||
|  |       expect(spy.calls.count()).toEqual(0); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('onInit calls HeroService.refresh', () => { | ||
|  |       let spy = <jasmine.Spy><any> service.refresh; | ||
|  |       hc.ngOnInit(); // Angular framework calls when it creates the component | ||
|  |       expect(spy.calls.count()).toEqual(1); | ||
|  |     }); | ||
|  |   }) | ||
|  | 
 | ||
|  |   describe('#heroes', () => { | ||
|  | 
 | ||
|  |     it('lacks heroes when created', () => { | ||
|  |       let heroes = hc.heroes; | ||
|  |       expect(heroes.length).toEqual(0); // not filled yet | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('has heroes after cache loaded', done => { | ||
|  |       hc.ngOnInit(); // Angular framework calls when it creates the component | ||
|  | 
 | ||
|  |       refreshPromise().then(() => { | ||
|  |           let heroes = hc.heroes; // now the component has heroes to show | ||
|  |           expect(heroes.length).toEqual(heroData.length); | ||
|  |         }) | ||
|  |         .then(done, done.fail); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('restores heroes after refresh called again', done => { | ||
|  |       hc.ngOnInit(); // component initialization triggers service | ||
|  |       let heroes: Hero[]; | ||
|  | 
 | ||
|  |       refreshPromise().then(() => { | ||
|  |           heroes = hc.heroes; // now the component has heroes to show | ||
|  |           heroes[0].name = 'Wotan'; | ||
|  |           heroes.push(new Hero(33, 'Thor')); | ||
|  |           hc.onRefresh(); | ||
|  |         }) | ||
|  |         .then(() => { | ||
|  |           heroes = hc.heroes; // get it again (don't reuse old array!) | ||
|  |           expect(heroes[0]).not.toEqual('Wotan'); // change reversed | ||
|  |           expect(heroes.length).toEqual(heroData.length); // orig num of heroes | ||
|  |         }) | ||
|  |         .then(done, done.fail); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe('#onSelected', () => { | ||
|  | 
 | ||
|  |     it('no hero is selected by default', () => { | ||
|  |       expect(hc.currentHero).not.toBeDefined(); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('sets the "currentHero"', () => { | ||
|  |       hc.onSelect(heroData[1]); // select the second hero | ||
|  |       expect(hc.currentHero).toEqual(heroData[1]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('no hero is selected after onRefresh() called', () => { | ||
|  |       hc.onSelect(heroData[1]); // select the second hero | ||
|  |       hc.onRefresh(); | ||
|  |       expect(hc.currentHero).not.toBeDefined(); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  | 
 | ||
|  |   describe('#onDelete', () => { | ||
|  | 
 | ||
|  |     // Load the heroes asynchronously before each test | ||
|  |     // Getting the async out of the way in the beforeEach | ||
|  |     // means tests can be synchronous | ||
|  |     // Note: could have cheated and simply plugged hc.heroes with fake data | ||
|  |     //       that trick would fail if we reimplemented hc.heroes as a readonly property | ||
|  |     beforeEach(done => { | ||
|  |       hc.ngOnInit(); // Angular framework calls when it creates the component | ||
|  |       refreshPromise().then(done, done.fail); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('removes the supplied hero (only) from the list', () => { | ||
|  |       hc.currentHero = heroData[1]; | ||
|  |       let hero = heroData[2]; | ||
|  |       hc.onDelete(hero); | ||
|  | 
 | ||
|  |       expect(hc.heroes).not.toContain(hero); | ||
|  |       expect(hc.heroes).toContain(heroData[1]); // left current in place | ||
|  |       expect(hc.heroes.length).toEqual(heroData.length - 1); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('removes the currentHero from the list if no hero argument', () => { | ||
|  |       hc.currentHero = heroData[1]; | ||
|  |       hc.onDelete(); | ||
|  |       expect(hc.heroes).not.toContain(heroData[1]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is harmless if no supplied or current hero', () => { | ||
|  |       hc.currentHero = null; | ||
|  |       hc.onDelete(); | ||
|  |       expect(hc.heroes.length).toEqual(heroData.length); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('is harmless if hero not in list', () => { | ||
|  |       let hero = heroData[1].clone(); // object reference matters, not id | ||
|  |       hc.onDelete(hero); | ||
|  |       expect(hc.heroes.length).toEqual(heroData.length); | ||
|  |     }); | ||
|  | 
 | ||
|  |     // must go async to get hc to clear its heroes list | ||
|  |     it('is harmless if the list is empty', done => { | ||
|  |       let hero = heroData[1]; | ||
|  |       heroData = []; | ||
|  |       hc.onRefresh(); | ||
|  |       refreshPromise().then(() => { | ||
|  |         hc.onDelete(hero); // shouldn't fail | ||
|  |       }) | ||
|  |       .then(done, done.fail); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('the new currentHero is the one after the removed hero', () => { | ||
|  |       hc.currentHero = heroData[1]; | ||
|  |       let expectedCurrent = heroData[2]; | ||
|  |       hc.onDelete(); | ||
|  |       expect(hc.currentHero).toBe(expectedCurrent); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('the new currentHero is the one before the removed hero if none after', () => { | ||
|  |       hc.currentHero = heroData[heroData.length - 1]; // last hero | ||
|  |       let expectedCurrent = heroData[heroData.length - 2]; // penultimate hero | ||
|  |       hc.onDelete(); | ||
|  |       expect(hc.currentHero).toBe(expectedCurrent); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  | }); | ||
|  | 
 | ||
|  | 
 | ||
|  | ////// Helpers ////// | ||
|  | 
 | ||
|  | class HappyHeroService { | ||
|  | 
 | ||
|  |   constructor() { | ||
|  |     spyOn(this, 'refresh').and.callThrough(); | ||
|  |   } | ||
|  | 
 | ||
|  |   heroes: Hero[]; | ||
|  | 
 | ||
|  |   refresh() { | ||
|  |     this.heroes = []; | ||
|  |     // updates cached heroes after one JavaScript cycle | ||
|  |     return new Promise((resolve, reject) => { | ||
|  |       this.heroes.push(...heroData); | ||
|  |       resolve(this.heroes); | ||
|  |     }); | ||
|  |   } | ||
|  | } |