diff --git a/jasmine/src/FivesArray.js b/jasmine/src/FivesArray.js index 9039b7d..3b7f276 100644 --- a/jasmine/src/FivesArray.js +++ b/jasmine/src/FivesArray.js @@ -1,7 +1,7 @@ function FivesArray() { } FivesArray.prototype.create = function(count) { - var a = new Array(count+2); + var a = new Array(count + 2); for(var i=0; i < a.length; i++) { a[i] = 5; } diff --git a/jasmine/src/FivesArray.spec.js b/jasmine/src/FivesArray.spec.js index 756b485..5385527 100644 --- a/jasmine/src/FivesArray.spec.js +++ b/jasmine/src/FivesArray.spec.js @@ -1,6 +1,5 @@ +describe('FivesArray', function() { -// this will be written up as demonstration. This is the finished code -describe("join", function() { var fives; beforeEach(function() { @@ -9,15 +8,12 @@ describe("join", function() { beforeEach(function () { jasmine.addMatchers({ - toBeEmptyArray: function () { + toBeEmptyArray: function toBeEmptyArrayMatcher() { return { - compare: function (actual, expected) { - var array = actual; - + compare: function isEmptyComparer(array) { return { pass: array.length === 0, message: 'Expected [' + array + '] to have no elements' - }; } }; @@ -25,20 +21,11 @@ describe("join", function() { }); }); - xit('should be empty if 0 is passed', function() { - var a = fives.create(0); - expect(a.length).toBe(0); - }) - - // with a custom matcher - xit('should be empty if 0 is passed [custom matcher]', function() { - var a = fives.create(0); - expect(a).toBeEmptyArray(); - }) + describe("create", function() { + xit('should be empty if 0 is passed [custom matcher]', function() { + var a = fives.create(0); + expect(a).toBeEmptyArray(); + }) + }); }); - - - - - diff --git a/jasmine/src/Joiner.spec.js b/jasmine/src/Joiner.spec.js index f1fea68..309838a 100644 --- a/jasmine/src/Joiner.spec.js +++ b/jasmine/src/Joiner.spec.js @@ -1,35 +1,36 @@ -// this will be written up as demonstration. This is the finished code -describe("join", function() { +describe('Joiner', function() { var joiner; beforeEach(function() { joiner = new Joiner(); }); - it("should be able to join an simple array with a simple string separator", function() { - var joined = joiner.join([1,2], '-'); - expect(joined).toEqual('1-2'); - }); + describe('join(array, separator)', function() { - it("should be able to join an empty array with something and return an empty string", function() { - var joined = joiner.join([], ','); - expect(joined).toEqual(''); - }); + it('should return a string with each array item joined by the separator', function() { + var joined = joiner.join([1, 2, 3], '-'); + expect(joined).toEqual('1-2-3'); + }); - it("should default to a comma string separator", function() { - var joined = joiner.join([3,4]); - expect(joined).toEqual('3,4'); - }); + it('should return an empty string if array is empty', function() { + var joined = joiner.join([], ','); + expect(joined).toEqual(''); + }); - it("should work with an empty string separator", function() { - var joined = joiner.join([3,4], ''); - expect(joined).toEqual('34'); - }); + it('should join with a comma if no separator is provided', function() { + var joined = joiner.join([3,4]); + expect(joined).toEqual('3,4'); + }); - it('should error when not passed an array', function() { - expect(function() { - joiner.join({}, ',') - }).toThrow(); - }); + it('should work with an empty string separator', function() { + var joined = joiner.join([3,4], ''); + expect(joined).toEqual('34'); + }); + it('should error when not passed an array', function() { + expect(function() { + joiner.join({}, ',') + }).toThrow(); + }); + }); }); diff --git a/jasmine/src/Order.spec.js b/jasmine/src/Order.spec.js index 503350d..4a7a132 100644 --- a/jasmine/src/Order.spec.js +++ b/jasmine/src/Order.spec.js @@ -1,5 +1,4 @@ -// this will be written up as demonstration. This is the finished code -describe("Order", function() { +describe('Order', function() { var order, customer; beforeEach(function() { @@ -7,18 +6,19 @@ describe("Order", function() { order = new Order(customer); }); - it("unpreferred customers get no discount", function() { - spyOn(customer, 'isPreferred').and.returnValue(false) - - order.addItem('foos', 10); - expect(order.getTotal()).toEqual(10); - }); + describe('addItem(name, cost)', function() { + it('should not discount unpreferred customers', function() { + spyOn(customer, 'isPreferred').and.returnValue(false) - it("preferred customers get a 10% discount", function() { - spyOn(customer, 'isPreferred').and.returnValue(true); - - order.addItem('foos', 10); - expect(order.getTotal()).toEqual(9); - }); + order.addItem('foos', 10); + expect(order.getTotal()).toEqual(10); + }); + it('should give preferred customers a 10% discount', function() { + spyOn(customer, 'isPreferred').and.returnValue(true); + + order.addItem('foos', 10); + expect(order.getTotal()).toEqual(9); + }); + }); }); diff --git a/jasmine/src/Player.js b/jasmine/src/Player.js index fcce826..1228e04 100644 --- a/jasmine/src/Player.js +++ b/jasmine/src/Player.js @@ -11,7 +11,7 @@ Player.prototype.pause = function() { Player.prototype.resume = function() { if (this.isPlaying) { - throw new Error("song is already playing"); + throw new Error('song is already playing'); } this.isPlaying = true; diff --git a/jasmine/src/Player.spec.js b/jasmine/src/Player.spec.js index 81d8439..5f7eabf 100644 --- a/jasmine/src/Player.spec.js +++ b/jasmine/src/Player.spec.js @@ -1,4 +1,3 @@ -// this code will also be written during the demonstration describe("Player", function() { var player; var song; @@ -8,32 +7,40 @@ describe("Player", function() { song = new Song(); }); - //demonstrates use of custom matcher - it("should be able to play a Song", function() { - player.play(song); - expect(player.currentlyPlayingSong).toEqual(song); + describe('play(song)', function() { - expect(player).toBePlaying(song); + it("should update currentlyPlayingSong and playing", function() { + player.play(song); + expect(player.currentlyPlayingSong).toEqual(song); + expect(player.isPlaying).toBe(true); + }); + + // demonstrates custom matcher + it('should play the song', function() { + player.play(song); + expect(player).toBePlaying(song); + }); }); - // demonstrates use of spies to intercept and test method calls - it("tells the current song if the user has made it a favorite", function() { - spyOn(song, 'persistFavoriteStatus'); + describe('makeFavorite()', function() { + // demonstrates use of spies to intercept and test method calls + it("should update the song that it is a favorite", function() { + spyOn(song, 'persistFavoriteStatus'); - player.play(song); - player.makeFavorite(); + player.play(song); + player.makeFavorite(); - expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true); - }); + expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true); + }); - // same as above except with createSpyObj() - it("tells the current song if the user has made it a favorite", function() { - var song = jasmine.createSpyObj('song', ['persistFavoriteStatus']); + // same as above except with createSpyObj() + it("should update the song that it is a favorite", function() { + var song = jasmine.createSpyObj('song', ['persistFavoriteStatus']); - player.play(song); - player.makeFavorite(); + player.play(song); + player.makeFavorite(); - expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true); + expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true); + }); }); - }); diff --git a/jasmine/src/Reverser.js b/jasmine/src/Reverser.js index 05fc23f..7c627bc 100644 --- a/jasmine/src/Reverser.js +++ b/jasmine/src/Reverser.js @@ -7,6 +7,6 @@ function Reverser() {} Reverser.prototype.reverseNumber = function(n) { - n = n + ""; - return n.split("").reverse().join(""); + n = n + ''; + return n.split('').reverse().join(''); } \ No newline at end of file diff --git a/jasmine/src/Reverser.spec.js b/jasmine/src/Reverser.spec.js new file mode 100644 index 0000000..22e6440 --- /dev/null +++ b/jasmine/src/Reverser.spec.js @@ -0,0 +1,30 @@ +describe('Reverser', function() { + var reverser; + + beforeEach(function() { + reverser = new Reverser(); + }); + + describe('reverseNumber(n)', function() { + + it('should return "0" for 0', function() { + expect(reverser.reverseNumber(0)).toBe("0"); + }); + + it('should return "333" for 333', function() { + expect(reverser.reverseNumber(333)).toBe("333"); + }); + + it('should return "123" for 321', function() { + expect(reverser.reverseNumber(321)).toBe("123"); + }); + + it('should return "12.3" for 3.21', function() { + expect(reverser.reverseNumber(3.21)).toBe("12.3"); + }); + + it('should return "5-" for -5', function() { + expect(reverser.reverseNumber(-5)).toBe("5-"); + }); + }); +}); diff --git a/jasmine/src/Song.js b/jasmine/src/Song.js index a8a3f2d..93e40a0 100644 --- a/jasmine/src/Song.js +++ b/jasmine/src/Song.js @@ -3,5 +3,5 @@ function Song() { Song.prototype.persistFavoriteStatus = function(value) { // something complicated - throw new Error("not yet implemented"); + throw new Error('not yet implemented'); }; \ No newline at end of file diff --git a/jasmine/src/squareRoot.spec.js b/jasmine/src/squareRoot.spec.js new file mode 100644 index 0000000..60b92b4 --- /dev/null +++ b/jasmine/src/squareRoot.spec.js @@ -0,0 +1,20 @@ +describe('toBeSquareRootOf', function() { + + beforeEach(function() { + jasmine.addMatchers({ + toBeSquareRootOf: function toBeSquareRootOfMatcher() { + return { + compare: function toBeSquareRootComparer(actual, expected) { + return { + pass: actual * actual === expected + } + } + } + } + }); + }); + + it('should match that 3 is the square root of 9', function() { + expect(3).toBeSquareRootOf(9); + }); +}); diff --git a/tour-of-heroes/README.md b/tour-of-heroes/README.md index 793ccc4..37d29ad 100644 --- a/tour-of-heroes/README.md +++ b/tour-of-heroes/README.md @@ -4,7 +4,7 @@ This project was generated with [angular-cli](https://github.com/angular/angular ## Getting Started -Install the Angular CLI globally: `npm install -g angular-cli@webpack` +Install the Angular CLI globally: `npm install -g angular-cli` Install the local dependencies: `npm install` ## Development server diff --git a/tour-of-heroes/e2e/app.e2e-spec.ts b/tour-of-heroes/e2e/app.e2e-spec.ts index f2ead28..d1f7142 100644 --- a/tour-of-heroes/e2e/app.e2e-spec.ts +++ b/tour-of-heroes/e2e/app.e2e-spec.ts @@ -1,4 +1,3 @@ -import {browser} from 'protractor'; import { AppPage } from './page-objects/app-page'; describe('App', function() { diff --git a/tour-of-heroes/e2e/heroes-2.e2e-spec.ts b/tour-of-heroes/e2e/heroes-2.e2e-spec.ts index 30236de..7d7885e 100644 --- a/tour-of-heroes/e2e/heroes-2.e2e-spec.ts +++ b/tour-of-heroes/e2e/heroes-2.e2e-spec.ts @@ -1,5 +1,4 @@ -import {browser, element, by} from 'protractor'; -// import {saveScreenshot} from './screenshot'; +import {browser} from 'protractor'; import {HeroesPage} from './page-objects/heroes-page'; describe('Heroes page (using Page Object)', () => { @@ -11,7 +10,6 @@ describe('Heroes page (using Page Object)', () => { it('should display the page title', () => { expect(heroesPage.getHeadingText()).toEqual('My Heroes'); - // saveScreenshot('hero-screenshot'); }); it('should show the Heroes nav link as active', () => { @@ -24,8 +22,8 @@ describe('Heroes page (using Page Object)', () => { expect(heroesPage.getHeroesList().count()).toEqual(10); const firstHero = heroesPage.getFirstHero(); - expect(firstHero.element(by.css('span.badge')).getText()).toEqual('11'); - expect(firstHero.element(by.css('span:not(.badge)')).getText()).toEqual('Mr. Nice'); + expect(heroesPage.getHeroBadgeText(firstHero)).toEqual('11'); + expect(heroesPage.getHeroName(firstHero)).toEqual('Mr. Nice'); }); it('should add a new hero when I enter a name and click the "add" button', () => { @@ -38,10 +36,8 @@ describe('Heroes page (using Page Object)', () => { }); it('should remove a hero from the list when I click its delete button', () => { - const heroesList = element.all(by.css('app-heroes li')); - heroesPage.deleteHero(heroesPage.getFirstHero()); - const firstHero = heroesList.first(); + const firstHero = heroesPage.getFirstHero(); expect(heroesPage.getHeroesList().count()).toEqual(9); expect(heroesPage.getHeroBadgeText(firstHero)).toEqual('12'); diff --git a/tour-of-heroes/e2e/heroes.e2e-spec.ts b/tour-of-heroes/e2e/heroes.e2e-spec.ts index a1e5050..6186bd8 100644 --- a/tour-of-heroes/e2e/heroes.e2e-spec.ts +++ b/tour-of-heroes/e2e/heroes.e2e-spec.ts @@ -1,5 +1,4 @@ import {browser, element, by} from 'protractor'; -// import {saveScreenshot} from './screenshot'; describe('Heroes page', () => { beforeEach(() => { @@ -8,7 +7,6 @@ describe('Heroes page', () => { it('should display the page title', () => { expect(element(by.css('app-heroes h2')).getText()).toEqual('My Heroes'); - // saveScreenshot('hero-screenshot'); }); it('should show the Heroes nav link as active', () => { diff --git a/tour-of-heroes/e2e/screenshot.ts b/tour-of-heroes/e2e/screenshot.ts index aff71e5..29c2fb2 100644 --- a/tour-of-heroes/e2e/screenshot.ts +++ b/tour-of-heroes/e2e/screenshot.ts @@ -4,6 +4,8 @@ import {browser} from 'protractor'; export function saveScreenshot(filename) { ensureDirSync('tmp/screenshots'); return browser.takeScreenshot().then((png) => { - outputFileSync('tmp/screenshots/' + filename + '-' + Date.now() + '.png', new Buffer(png, 'base64')); + filename = 'tmp/screenshots/' + filename + '-' + Date.now() + '.png'; + console.log('Saving screenshot:', filename); + outputFileSync(filename, new Buffer(png, 'base64')); }); } diff --git a/tour-of-heroes/src/app/app.component.shallow.spec.ts b/tour-of-heroes/src/app/app.component.shallow.spec.ts deleted file mode 100644 index 760e4f0..0000000 --- a/tour-of-heroes/src/app/app.component.shallow.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { TestBed, async } from '@angular/core/testing'; -import { AppComponent } from './app.component'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; - -describe('AppComponent (shallow tests)', () => { - - let fixture, component, element; - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ - AppComponent - ], - schemas: [NO_ERRORS_SCHEMA] - }); - fixture = TestBed.createComponent(AppComponent); - component = fixture.debugElement.componentInstance; - element = fixture.debugElement.nativeElement; - }); - - it(`should have as title 'Tour of Heroes'`, async(() => { - expect(component.title).toEqual('Tour of Heroes'); - })); - - it('should render title in a h1 tag', async(() => { - fixture.detectChanges(); - expect(element.querySelector('h1').textContent).toContain('Tour of Heroes'); - })); -}); diff --git a/tour-of-heroes/src/app/app.component.css b/tour-of-heroes/src/app/app.component/app.component.css similarity index 100% rename from tour-of-heroes/src/app/app.component.css rename to tour-of-heroes/src/app/app.component/app.component.css diff --git a/tour-of-heroes/src/app/app.component.html b/tour-of-heroes/src/app/app.component/app.component.html similarity index 100% rename from tour-of-heroes/src/app/app.component.html rename to tour-of-heroes/src/app/app.component/app.component.html diff --git a/tour-of-heroes/src/app/app.component.isolated.spec.ts b/tour-of-heroes/src/app/app.component/app.component.isolated.spec.ts similarity index 100% rename from tour-of-heroes/src/app/app.component.isolated.spec.ts rename to tour-of-heroes/src/app/app.component/app.component.isolated.spec.ts diff --git a/tour-of-heroes/src/app/app.component/app.component.shallow.spec.ts b/tour-of-heroes/src/app/app.component/app.component.shallow.spec.ts new file mode 100644 index 0000000..0c76319 --- /dev/null +++ b/tour-of-heroes/src/app/app.component/app.component.shallow.spec.ts @@ -0,0 +1,24 @@ +import { TestBed } from '@angular/core/testing'; +import { AppComponent } from './app.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; + +describe('AppComponent (shallow tests)', () => { + + let fixture; + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [AppComponent], + schemas: [NO_ERRORS_SCHEMA] + }); + fixture = TestBed.createComponent(AppComponent); + }); + + it(`should have as title 'Tour of Heroes'`, () => { + expect(fixture.componentInstance.title).toEqual('Tour of Heroes'); + }); + + it('should render title in a h1 tag', () => { + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('h1').textContent).toContain('Tour of Heroes'); + }); +}); diff --git a/tour-of-heroes/src/app/app.component.ts b/tour-of-heroes/src/app/app.component/app.component.ts similarity index 100% rename from tour-of-heroes/src/app/app.component.ts rename to tour-of-heroes/src/app/app.component/app.component.ts diff --git a/tour-of-heroes/src/app/app.module.spec.ts b/tour-of-heroes/src/app/app.module.spec.ts new file mode 100644 index 0000000..c374d50 --- /dev/null +++ b/tour-of-heroes/src/app/app.module.spec.ts @@ -0,0 +1,37 @@ +import { TestBed, inject, fakeAsync, tick, ComponentFixture } from '@angular/core/testing'; +import { LocationStrategy, Location } from '@angular/common'; +import { MockLocationStrategy, SpyLocation } from '@angular/common/testing'; +import { Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AppModule } from './app.module'; +import { AppComponent } from './app.component/app.component'; + +describe('app (deep integration tests)', () => { + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + { provide: LocationStrategy, useClass: MockLocationStrategy }, + { provide: Location, useClass: SpyLocation} + ], + imports: [ + AppModule, + RouterTestingModule + ] + }); + fixture = TestBed.createComponent(AppComponent); + }); + + describe('dashboard', () => { + it('should let us edit a hero', fakeAsync(inject([Router, Location], (router: Router, location: SpyLocation) => { + tick(); + fixture.detectChanges(); + console.log(router.url); + tick(); + fixture.detectChanges(); + console.log(router.url); + expect(location.path()).toEqual('/dashboard'); + }))); + }); +}); diff --git a/tour-of-heroes/src/app/app.module.ts b/tour-of-heroes/src/app/app.module.ts index 4e4d8fa..9f09dee 100644 --- a/tour-of-heroes/src/app/app.module.ts +++ b/tour-of-heroes/src/app/app.module.ts @@ -4,19 +4,20 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; +import { Location } from '@angular/common'; // Imports for loading & configuring the in-memory web api import { InMemoryWebApiModule } from 'angular2-in-memory-web-api'; -import { InMemoryDataService } from './in-memory-data.service'; +import { InMemoryDataService } from './shared/in-memory-data.service'; -import { AppComponent } from './app.component'; -import { DashboardComponent } from './dashboard.component'; -import { HeroesComponent } from './heroes.component'; -import { HeroDetailComponent } from './hero-detail.component'; -import { HeroService } from './hero.service'; -import { HeroSearchComponent } from './hero-search.component'; +import { AppComponent } from './app.component/app.component'; +import { DashboardComponent } from './dashboard.component/dashboard.component'; +import { HeroesComponent } from './heroes.component/heroes.component'; +import { HeroDetailComponent } from './hero-detail.component/hero-detail.component'; +import { HeroService } from './hero.service/hero.service'; +import { HeroSearchComponent } from './hero-search.component/hero-search.component'; +import { ExponentialStrengthPipe } from './exponential-strength.pipe/exponential-strength.pipe'; import { routing } from './app.routing'; -import { ExponentialStrengthPipe } from './exponential-strength.pipe'; @NgModule({ imports: [ @@ -36,6 +37,7 @@ import { ExponentialStrengthPipe } from './exponential-strength.pipe'; ], providers: [ HeroService, + Location ], bootstrap: [ AppComponent ] }) diff --git a/tour-of-heroes/src/app/app.routing.ts b/tour-of-heroes/src/app/app.routing.ts index 92f2f11..359ca8c 100644 --- a/tour-of-heroes/src/app/app.routing.ts +++ b/tour-of-heroes/src/app/app.routing.ts @@ -1,9 +1,9 @@ import { ModuleWithProviders } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -import { DashboardComponent } from './dashboard.component'; -import { HeroesComponent } from './heroes.component'; -import { HeroDetailComponent } from './hero-detail.component'; +import { DashboardComponent } from './dashboard.component/dashboard.component'; +import { HeroesComponent } from './heroes.component/heroes.component'; +import { HeroDetailComponent } from './hero-detail.component/hero-detail.component'; const appRoutes: Routes = [ { diff --git a/tour-of-heroes/src/app/dashboard.component.css b/tour-of-heroes/src/app/dashboard.component/dashboard.component.css similarity index 100% rename from tour-of-heroes/src/app/dashboard.component.css rename to tour-of-heroes/src/app/dashboard.component/dashboard.component.css diff --git a/tour-of-heroes/src/app/dashboard.component.html b/tour-of-heroes/src/app/dashboard.component/dashboard.component.html similarity index 100% rename from tour-of-heroes/src/app/dashboard.component.html rename to tour-of-heroes/src/app/dashboard.component/dashboard.component.html diff --git a/tour-of-heroes/src/app/dashboard.component.spec.ts b/tour-of-heroes/src/app/dashboard.component/dashboard.component.shallow.spec.ts similarity index 84% rename from tour-of-heroes/src/app/dashboard.component.spec.ts rename to tour-of-heroes/src/app/dashboard.component/dashboard.component.shallow.spec.ts index eb1b44e..e9fde0d 100644 --- a/tour-of-heroes/src/app/dashboard.component.spec.ts +++ b/tour-of-heroes/src/app/dashboard.component/dashboard.component.shallow.spec.ts @@ -1,12 +1,9 @@ -/* tslint:disable:no-unused-variable */ - import { TestBed, async, fakeAsync, tick } from '@angular/core/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { DashboardComponent } from './dashboard.component'; import { Router } from '@angular/router'; -import { RouterTestingModule, SpyNgModuleFactoryLoader } from '@angular/router/testing'; -import { HeroService } from './hero.service'; -import { ExponentialStrengthPipe } from './exponential-strength.pipe'; +import { HeroService } from '../hero.service/hero.service'; +import { ExponentialStrengthPipe } from '../exponential-strength.pipe/exponential-strength.pipe'; let mockHeroService = { getHeroes: () => {} }; let mockRouter = jasmine.createSpyObj('router', ['navigate']); @@ -14,9 +11,6 @@ let mockRouter = jasmine.createSpyObj('router', ['navigate']); describe('dashboard.component', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], declarations: [ DashboardComponent, ExponentialStrengthPipe @@ -35,6 +29,7 @@ describe('dashboard.component', () => { expect(app).toBeTruthy(); })); + // this test will point out the bug with the slice command in the dashboard.component.spec.ts file it(`should have the heroes its given`, fakeAsync(() => { let p = new Promise((resolve, reject) => { resolve([ 1, 2, 3, 4]); diff --git a/tour-of-heroes/src/app/dashboard.component.ts b/tour-of-heroes/src/app/dashboard.component/dashboard.component.ts similarity index 88% rename from tour-of-heroes/src/app/dashboard.component.ts rename to tour-of-heroes/src/app/dashboard.component/dashboard.component.ts index 48b36e8..e610f16 100644 --- a/tour-of-heroes/src/app/dashboard.component.ts +++ b/tour-of-heroes/src/app/dashboard.component/dashboard.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; -import { Hero } from './hero'; -import { HeroService } from './hero.service'; +import { Hero } from '../shared/hero'; +import { HeroService } from '../hero.service/hero.service'; @Component({ selector: 'app-dashboard', diff --git a/tour-of-heroes/src/app/exponential-strength.pipe.isolated.spec.ts b/tour-of-heroes/src/app/exponential-strength.pipe/exponential-strength.pipe.isolated.spec.ts similarity index 100% rename from tour-of-heroes/src/app/exponential-strength.pipe.isolated.spec.ts rename to tour-of-heroes/src/app/exponential-strength.pipe/exponential-strength.pipe.isolated.spec.ts diff --git a/tour-of-heroes/src/app/exponential-strength.pipe.shallow.spec.ts b/tour-of-heroes/src/app/exponential-strength.pipe/exponential-strength.pipe.shallow.spec.ts similarity index 79% rename from tour-of-heroes/src/app/exponential-strength.pipe.shallow.spec.ts rename to tour-of-heroes/src/app/exponential-strength.pipe/exponential-strength.pipe.shallow.spec.ts index 3d8ff4b..f135702 100644 --- a/tour-of-heroes/src/app/exponential-strength.pipe.shallow.spec.ts +++ b/tour-of-heroes/src/app/exponential-strength.pipe/exponential-strength.pipe.shallow.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@angular/core/testing'; -import { NO_ERRORS_SCHEMA, Component } from '@angular/core'; +import { Component } from '@angular/core'; import { ExponentialStrengthPipe } from './exponential-strength.pipe'; @Component({ @@ -14,14 +14,13 @@ describe('exponential-strength.pipe', () => { declarations: [ ExponentialStrengthPipe, ContainerComponent - ], - schemas: [NO_ERRORS_SCHEMA] + ] }); }); it('should show the strength', () => { let fixture = TestBed.createComponent(ContainerComponent); - let element = fixture.debugElement.nativeElement; + let element = fixture.nativeElement; fixture.detectChanges(); expect(element.textContent).toContain('25'); }); diff --git a/tour-of-heroes/src/app/exponential-strength.pipe.ts b/tour-of-heroes/src/app/exponential-strength.pipe/exponential-strength.pipe.ts similarity index 100% rename from tour-of-heroes/src/app/exponential-strength.pipe.ts rename to tour-of-heroes/src/app/exponential-strength.pipe/exponential-strength.pipe.ts diff --git a/tour-of-heroes/src/app/hero-detail.component.isolated.spec.ts b/tour-of-heroes/src/app/hero-detail.component.isolated.spec.ts deleted file mode 100644 index d823a8b..0000000 --- a/tour-of-heroes/src/app/hero-detail.component.isolated.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { HeroDetailComponent } from './hero-detail.component'; -import { tick, fakeAsync } from '@angular/core/testing'; - -describe('HeroDetailComponent (isolated tests)', () => { - let heroes = [ - {id: 3, name: 'Magneta', strength: 4}, - {id: 4, name: 'Dynama', strength: 2} - ]; - - beforeEach(() => { - this.mockHeroService = { getHero: () => {}, update: () => {} }; - this.mockActivatedRoute = {params: []}; - this.mockLocation = { back: () => {} }; - }); - - it('should grab the right hero on init', fakeAsync(() => { - this.mockActivatedRoute.params.push({id: '3'}); - let retPromise = Promise.resolve(heroes[0]); - spyOn(this.mockHeroService, 'getHero').and.returnValue(retPromise); - const component = new HeroDetailComponent(this.mockHeroService, this.mockActivatedRoute, this.mockLocation); - - component.ngOnInit(); - tick(); - - expect(component.hero).toEqual(heroes[0]); - })); - - // this test mocks the window.history.back method. The next test assumes that it's been fixed. - // it('should call update on the right hero when save is called', fakeAsync(() => { - // let retPromise = Promise.resolve(); - // spyOn(this.mockHeroService, 'update').and.returnValue(retPromise); - // spyOn(window.history, 'back'); - // const component = new HeroDetailComponent(this.mockHeroService, this.mockActivatedRoute); - // component.hero = heroes[1]; - - // component.save(); - // tick(); - - // expect(this.mockHeroService.update).toHaveBeenCalledWith(heroes[1]); - // })); - - // this test is based on having the hero-detail component fixed with regards to - // the use of the window global variable - it('should call update on the right hero when save is called', fakeAsync(() => { - let retPromise = Promise.resolve(); - spyOn(this.mockHeroService, 'update').and.returnValue(retPromise); - spyOn(this.mockLocation, 'back'); - const component = new HeroDetailComponent(this.mockHeroService, this.mockActivatedRoute, this.mockLocation); - component.hero = heroes[1]; - - component.save(); - tick(); - - expect(this.mockHeroService.update).toHaveBeenCalledWith(heroes[1]); - })); - - - -}); diff --git a/tour-of-heroes/src/app/hero-detail.component.shallow.spec.ts b/tour-of-heroes/src/app/hero-detail.component.shallow.spec.ts deleted file mode 100644 index 6138d56..0000000 --- a/tour-of-heroes/src/app/hero-detail.component.shallow.spec.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { HeroDetailComponent } from './hero-detail.component'; -import { HeroService } from './hero.service'; -import { ActivatedRoute } from '@angular/router'; -import { Location } from '@angular/common'; -import { By } from '@angular/platform-browser'; -// you must import this so that [(ngModel)] is recognized. -// otherwise the NO_ERRORS_SCHEMA will hide that angular doesn't -// know about ngModel -import { FormsModule } from '@angular/forms'; - -describe('HeroDetailComponent (shallow tests)', () => { - let fixture, component, element, templateHeroService, templateActivatedRoute, templateLocation, mockHeroService; - let heroes = [ - {id: 3, name: 'Magneta', strength: 4}, - {id: 4, name: 'Dynama', strength: 2} - ]; - - - beforeEach(() => { - templateHeroService = { getHero: () => {}, update: () => {} }; - templateActivatedRoute = {params: [{id: '3'}]}; - templateLocation = { back: () => {}}; - - TestBed.configureTestingModule({ - imports: [ - FormsModule - ], - declarations: [ - HeroDetailComponent - ], - providers: [ - { provide: HeroService, useValue: templateHeroService }, - { provide: ActivatedRoute, useValue: templateActivatedRoute }, - { provide: Location, useValue: templateLocation } - ] - }); - - fixture = TestBed.createComponent(HeroDetailComponent); - component = fixture.debugElement.componentInstance; - element = fixture.debugElement.nativeElement; - }); - - beforeEach(() => { - let retPromise = Promise.resolve(heroes[0]); - mockHeroService = TestBed.get(HeroService); - spyOn(mockHeroService, 'getHero').and.returnValue(retPromise); - spyOn(mockHeroService, 'update').and.returnValue(Promise.resolve()); - }); - - it(`should have the correct hero's name & id`, fakeAsync(() => { - - fixture.detectChanges(); - tick(); - fixture.detectChanges(); - - expect(element.querySelector('div').textContent).toContain('id: 3'); - expect(element.querySelector('div').textContent).toContain('Magneta'); - })); - - it(`should call update on the hero service when save is clicked`, fakeAsync(() => { - - fixture.detectChanges(); - tick(); - fixture.detectChanges(); - - fixture.debugElement.queryAll(By.css('button'))[1].triggerEventHandler('click', null); - - expect(mockHeroService.update).toHaveBeenCalledWith(heroes[0]); - })); - - it(`should change the hero's name when the input box is set`, fakeAsync(() => { - fixture.detectChanges(); - tick(); - fixture.detectChanges(); - - let el = fixture.debugElement.query(By.css('input')).nativeElement; - el.value = 'Mr. Nice'; - el.dispatchEvent(newEvent('input')); // this must be called so that detectChanges will know that something has changed - fixture.detectChanges(); - - expect(fixture.debugElement.query(By.css('h2')).nativeElement.textContent).toContain('Mr. Nice'); - })); - -}); - -function newEvent(eventName: string, bubbles = false, cancelable = false) { - let evt = document.createEvent('CustomEvent'); // MUST be 'CustomEvent' - evt.initCustomEvent(eventName, bubbles, cancelable, null); - return evt; -} - diff --git a/tour-of-heroes/src/app/hero-detail.component.css b/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.css similarity index 100% rename from tour-of-heroes/src/app/hero-detail.component.css rename to tour-of-heroes/src/app/hero-detail.component/hero-detail.component.css diff --git a/tour-of-heroes/src/app/hero-detail.component.html b/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.html similarity index 100% rename from tour-of-heroes/src/app/hero-detail.component.html rename to tour-of-heroes/src/app/hero-detail.component/hero-detail.component.html diff --git a/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.isolated.spec.ts b/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.isolated.spec.ts new file mode 100644 index 0000000..e7e1c66 --- /dev/null +++ b/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.isolated.spec.ts @@ -0,0 +1,51 @@ +import { HeroDetailComponent } from './hero-detail.component'; +import { tick, fakeAsync } from '@angular/core/testing'; + +describe('HeroDetailComponent (isolated tests)', () => { + const mockHero = {id: 3, name: 'Magneta', strength: 4}; + let mockHeroService; + let mockRoute; + let mockLocation; + let component: HeroDetailComponent; + + beforeEach(() => { + mockHeroService = jasmine.createSpyObj('heroService', ['getHero', 'update']); + mockLocation = jasmine.createSpyObj('location', ['back']); + mockRoute = {params: []}; + component = new HeroDetailComponent(mockHeroService, mockLocation, mockRoute); + }); + + describe('ngOnInit', () => { + it('should get the hero ', fakeAsync(() => { + mockRoute.params.push({id: '3'}); + mockHeroService.getHero.and.returnValue(Promise.resolve(mockHero)); + + component.ngOnInit(); + tick(); + + expect(mockHeroService.getHero).toHaveBeenCalledWith(3); + expect(component.hero).toEqual(mockHero); + })); + }); + + describe('save()', () => { + it('should update the heroService and then goBack', fakeAsync(() => { + mockHeroService.update.and.returnValue(Promise.resolve()); + spyOn(component, 'goBack'); + component.hero = mockHero; + + component.save(); + tick(); + + expect(mockHeroService.update).toHaveBeenCalledWith(mockHero); + expect(component.goBack).toHaveBeenCalled(); + })); + }); + + describe('goBack', () => { + it('should call back on the window.history object', () => { + component.goBack(); + expect(mockLocation.back).toHaveBeenCalled(); + }); + }); +}); diff --git a/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.shallow.spec.ts b/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.shallow.spec.ts new file mode 100644 index 0000000..043919c --- /dev/null +++ b/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.shallow.spec.ts @@ -0,0 +1,149 @@ +import { TestBed, fakeAsync, tick, async, ComponentFixture, inject } from '@angular/core/testing'; +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroService } from '../hero.service/hero.service'; +import { ActivatedRoute } from '@angular/router'; +import { By } from '@angular/platform-browser'; +import { FormsModule, NgModel } from '@angular/forms'; +import { Location } from '@angular/common'; +import { SpyLocation } from '@angular/common/testing'; + +describe('HeroDetailComponent (shallow tests)', () => { + let fixture: ComponentFixture; + let component: HeroDetailComponent; + let element; + let heroes = [ + {id: 3, name: 'Magneta', strength: 4}, + {id: 4, name: 'Dynama', strength: 2} + ]; + + beforeEach(() => { + + const mockHeroService = { + getHero: () => Promise.resolve(heroes[0]), + update: () => Promise.resolve() + }; + const mockActivatedRoute = { + params: [ { id: '3' } ] + }; + + TestBed.configureTestingModule({ + imports: [ + // you must import this so that [(ngModel)] is recognized. + FormsModule + ], + declarations: [ + HeroDetailComponent + ], + providers: [ + // useValue may create a clone of the objects passed + { provide: HeroService, useValue: mockHeroService }, + { provide: ActivatedRoute, useValue: mockActivatedRoute }, + { provide: Location, useFactory: () => new SpyLocation() } + ], + schemas: [ + // NO_ERRORS_SCHEMA will hide that angular doesn't know about ngModel + ] + }); + + fixture = TestBed.createComponent(HeroDetailComponent); + component = fixture.componentInstance; + element = fixture.nativeElement; + }); + + describe('initial display', () => { + + it('should show the correct hero name & id (using async and detectChanges)', async(() => { + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(element.querySelector('div').textContent).toContain('id: 3'); + expect(element.querySelector('div').textContent).toContain('Magneta'); + }); + })); + + it('should show the correct hero name & id (using async and autoDetectChanges)', async(() => { + + fixture.autoDetectChanges(); + fixture.whenStable().then(() => { + expect(element.querySelector('div').textContent).toContain('id: 3'); + expect(element.querySelector('div').textContent).toContain('Magneta'); + }); + })); + + it('should show the correct hero name & id (using fakeAsync and tick)', fakeAsync(() => { + + fixture.detectChanges(); + tick(); + fixture.detectChanges(); + + expect(element.querySelector('div').textContent).toContain('id: 3'); + expect(element.querySelector('div').textContent).toContain('Magneta'); + })); + }); + + describe('name input changing', () => { + beforeEach(fakeAsync(() => { + fixture.detectChanges(); + tick(); + fixture.detectChanges(); + })); + + it(`should change the hero's name (via nativeElement API)`, fakeAsync(() => { + const inputElement = fixture.debugElement.query(By.css('input')).nativeElement; + + inputElement.value = 'Mr. Nice'; + inputElement.dispatchEvent(createEvent('input')); // this must be called so that detectChanges will know that something has changed + fixture.detectChanges(); + + expect(getHeadingText(fixture)).toContain('Mr. Nice'); + })); + + it(`should change the hero's name (via debugElement API)`, () => { + const ngModel = fixture.debugElement.query(By.directive(NgModel)); + + ngModel.triggerEventHandler('ngModelChange', 'Mr. Nice'); + fixture.detectChanges(); + + expect(getHeadingText(fixture)).toContain('Mr. Nice'); + }); + }); + + describe('clicking save', () => { + let saveButton; + beforeEach(fakeAsync(() => { + fixture.detectChanges(); + tick(); + fixture.detectChanges(); + saveButton = getSaveButton(fixture); + })); + + it(`should update the hero service`, inject([HeroService], (heroService) => { + spyOn(heroService, 'update').and.callThrough(); + saveButton.triggerEventHandler('click', null); + expect(heroService.update).toHaveBeenCalledWith(heroes[0]); + })); + + it(`should navigate back`, fakeAsync(inject([Location], (location: Location) => { + spyOn(location, 'back'); + saveButton.triggerEventHandler('click', null); + // we need this `tick` because the location.back is called in a then handler for the promise returned by heroService.update() + tick(); + expect(location.back).toHaveBeenCalled(); + }))); + }); +}); + +function createEvent(eventName: string, bubbles = false, cancelable = false) { + let evt = document.createEvent('CustomEvent'); // MUST be 'CustomEvent' + evt.initCustomEvent(eventName, bubbles, cancelable, null); + return evt; +} + +function getHeadingText(fixture) { + return fixture.debugElement.query(By.css('h2')).nativeElement.textContent; +} + +function getSaveButton(fixture) { + return fixture.debugElement.queryAll(By.css('button'))[1]; +} diff --git a/tour-of-heroes/src/app/hero-detail.component.ts b/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.ts similarity index 68% rename from tour-of-heroes/src/app/hero-detail.component.ts rename to tour-of-heroes/src/app/hero-detail.component/hero-detail.component.ts index d65eed4..28503fc 100644 --- a/tour-of-heroes/src/app/hero-detail.component.ts +++ b/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.ts @@ -1,10 +1,9 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Params } from '@angular/router'; -// ANGULARCONNECT: REMOVE THIS FOR THE STARTING POINT import { Location } from '@angular/common'; -import { Hero } from './hero'; -import { HeroService } from './hero.service'; +import { Hero } from '../shared/hero'; +import { HeroService } from '../hero.service/hero.service'; @Component({ selector: 'app-hero-detail', @@ -16,8 +15,8 @@ export class HeroDetailComponent implements OnInit { constructor( private heroService: HeroService, - private route: ActivatedRoute, - private location: Location) { + private location: Location, + private route: ActivatedRoute) { } ngOnInit(): void { @@ -30,13 +29,11 @@ export class HeroDetailComponent implements OnInit { save(): void { this.heroService.update(this.hero) - .then(() => this.goBack); // ANGULARCONNECT: the lambda in here is to fix a context issue + .then(() => this.goBack()); } goBack(): void { - // ANGULARCONNECT: REMOVE THIS FOR THE STARTING POINT, COMMENT IN THE OTHER LINE this.location.back(); - // window.history.back(); } } diff --git a/tour-of-heroes/src/app/hero-search.component.css b/tour-of-heroes/src/app/hero-search.component/hero-search.component.css similarity index 100% rename from tour-of-heroes/src/app/hero-search.component.css rename to tour-of-heroes/src/app/hero-search.component/hero-search.component.css diff --git a/tour-of-heroes/src/app/hero-search.component.html b/tour-of-heroes/src/app/hero-search.component/hero-search.component.html similarity index 100% rename from tour-of-heroes/src/app/hero-search.component.html rename to tour-of-heroes/src/app/hero-search.component/hero-search.component.html diff --git a/tour-of-heroes/src/app/hero-search.component.ts b/tour-of-heroes/src/app/hero-search.component/hero-search.component.ts similarity index 93% rename from tour-of-heroes/src/app/hero-search.component.ts rename to tour-of-heroes/src/app/hero-search.component/hero-search.component.ts index 60e266d..ae0871f 100644 --- a/tour-of-heroes/src/app/hero-search.component.ts +++ b/tour-of-heroes/src/app/hero-search.component/hero-search.component.ts @@ -3,8 +3,8 @@ import { Router } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; -import { HeroSearchService } from './hero-search.service'; -import { Hero } from './hero'; +import { HeroSearchService } from '../hero-search.service/hero-search.service'; +import { Hero } from '../shared/hero'; @Component({ selector: 'app-hero-search', diff --git a/tour-of-heroes/src/app/hero-search.service.isolated.spec.ts b/tour-of-heroes/src/app/hero-search.service.isolated.spec.ts deleted file mode 100644 index 123d402..0000000 --- a/tour-of-heroes/src/app/hero-search.service.isolated.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/observable/of'; -import {ResponseOptions, Response} from '@angular/http'; -import { HeroSearchService } from './hero-search.service'; -import { Hero } from './hero'; - - -describe('HeroSearchService', () => { - let mockHttp: { get: jasmine.Spy }; - let service: HeroSearchService; - - beforeEach(() => { - mockHttp = jasmine.createSpyObj('http', ['get']); - service = new HeroSearchService(mockHttp as any); - }); - - it('should make a request via http', () => { - const expectedResults = [new Hero(), new Hero()]; - mockHttp.get.and.returnValue(createResponse(expectedResults)); - - let actualResults; - service.search('mysearch').subscribe((value) => { actualResults = value; }); - - expect(mockHttp.get).toHaveBeenCalledWith('app/heroes/?name=mysearch'); - expect(actualResults).toEqual(expectedResults); - }); -}); - -function createResponse(data) { - const response = new Response(new ResponseOptions({ body: { data } })); - return Observable.of(response); -} diff --git a/tour-of-heroes/src/app/hero-search.service/hero-search.service.isolated.spec.ts b/tour-of-heroes/src/app/hero-search.service/hero-search.service.isolated.spec.ts new file mode 100644 index 0000000..854baa0 --- /dev/null +++ b/tour-of-heroes/src/app/hero-search.service/hero-search.service.isolated.spec.ts @@ -0,0 +1,23 @@ +import { Observable } from 'rxjs/Observable'; +import { ResponseOptions, Response } from '@angular/http'; +import { HeroSearchService } from './hero-search.service'; +import { Hero } from '../shared/hero'; + +describe('HeroSearchService', () => { + + it('should make a request via http', () => { + const expectedResults = [new Hero(), new Hero()]; + const mockResponse = new Response(new ResponseOptions({ body: { data: expectedResults } })); + + const mockHttp = jasmine.createSpyObj('http', ['get']); + mockHttp.get.and.returnValue(Observable.of(mockResponse)); + + const service = new HeroSearchService(mockHttp as any); + + let actualResults; + service.search('mysearch').subscribe((value) => { actualResults = value; }); + + expect(mockHttp.get).toHaveBeenCalledWith('app/heroes/?name=mysearch'); + expect(actualResults).toEqual(expectedResults); + }); +}); diff --git a/tour-of-heroes/src/app/hero-search.service.ts b/tour-of-heroes/src/app/hero-search.service/hero-search.service.ts similarity index 92% rename from tour-of-heroes/src/app/hero-search.service.ts rename to tour-of-heroes/src/app/hero-search.service/hero-search.service.ts index e38df1a..6ef5edf 100644 --- a/tour-of-heroes/src/app/hero-search.service.ts +++ b/tour-of-heroes/src/app/hero-search.service/hero-search.service.ts @@ -3,7 +3,7 @@ import { Http, Response } from '@angular/http'; import { Observable } from 'rxjs'; import 'rxjs/add/operator/map'; -import { Hero } from './hero'; +import { Hero } from '../shared/hero'; @Injectable() export class HeroSearchService { diff --git a/tour-of-heroes/src/app/hero.service.isolated-obs.spec.ts b/tour-of-heroes/src/app/hero.service.isolated-obs.spec.ts deleted file mode 100644 index a594cec..0000000 --- a/tour-of-heroes/src/app/hero.service.isolated-obs.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { HeroService } from './hero.service'; -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/observable/of'; -import {ResponseOptions, Response} from '@angular/http'; - -describe('Service: HeroService', () => { - let heroes; - let mockHttp = jasmine.createSpyObj('mockHttp', ['get', 'post', 'put', 'delete']); - - beforeEach(() => { - heroes = [ - {id: 2, name: 'Rubberman'}, - {id: 4, name: 'Dynama'} - ]; - }); - - - it('should return the correct hero when called with a valid id', (done) => { - let service = new HeroService(mockHttp); - mockHttp.get.and.returnValue(Observable.of(createResponse({data: heroes}))); - - service.getHeroes().then(retval => { - expect(retval).toBe(heroes); - done(); - }); - }); -}); - -function createResponse(body) { - return new Response(new ResponseOptions({ body })); -} diff --git a/tour-of-heroes/src/app/hero.service.isolated.spec.ts b/tour-of-heroes/src/app/hero.service.isolated.spec.ts deleted file mode 100644 index 122e63c..0000000 --- a/tour-of-heroes/src/app/hero.service.isolated.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { HeroService } from './hero.service'; - -describe('Service: HeroService', () => { - let heroes; - let mockHttp = jasmine.createSpyObj('mockHttp', ['get', 'post', 'put', 'delete']); - - beforeEach(() => { - heroes = [ - {id: 2, name: 'Rubberman'}, - {id: 4, name: 'Dynama'} - ]; - }); - - - it('should return the correct hero when called with a valid id', (done) => { - let service = new HeroService(mockHttp); - mockHttp.get.and.returnValue(createMockGetCallForAllHeroes()); - - service.getHeroes().then(retval => { - expect(retval).toBe(heroes); - done(); - }); - - }); - - function createMockGetCallForAllHeroes() { - return { - toPromise: () => { - return new Promise((resolve, reject) => { - const fakeResponse = { - json: () => { - return { - data: heroes - }; - } - }; - resolve(fakeResponse); - }); - } - }; - } - -}); diff --git a/tour-of-heroes/src/app/hero.service.spec.ts b/tour-of-heroes/src/app/hero.service.spec.ts deleted file mode 100644 index af97e52..0000000 --- a/tour-of-heroes/src/app/hero.service.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* tslint:disable:no-unused-variable */ - -import { TestBed, async, fakeAsync, inject, tick } from '@angular/core/testing'; -import { MockBackend, MockConnection } from '@angular/http/testing'; -import { HeroService } from './hero.service'; -import { Http, ConnectionBackend, BaseRequestOptions, Response, ResponseOptions } from '@angular/http'; - -describe('Service: HeroService', () => { - let heroes, matchingHero, connection; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - HeroService, - { provide: Http, useFactory: (backend: ConnectionBackend, defaultOptions: BaseRequestOptions) => { - return new Http(backend, defaultOptions); - }, deps: [MockBackend, BaseRequestOptions] - }, - MockBackend, - BaseRequestOptions - ] - }); - - heroes = [ - {id: 2, name: 'Rubberman'}, - {id: 4, name: 'Dynama'} - ]; - }); - - describe('getHero', () => { - - it('should return the correct hero when called with a valid id', fakeAsync( - inject([HeroService, MockBackend, Http], (service: HeroService, backend: MockBackend, http: Http) => { - backend.connections.subscribe(c => connection = c); - - service.getHero(4).then(hero => { - matchingHero = hero; - }); - connection.mockRespond(createResponse(heroes)); - tick(); - - expect(matchingHero.id).toBe(4); - expect(matchingHero.name).toBe('Dynama'); - }))); - - it('should return an empty array when called with an invalid id', fakeAsync( - inject([HeroService, MockBackend, Http], (service: HeroService, backend: MockBackend, http: Http) => { - backend.connections.subscribe(c => connection = c); - - service.getHero(45).then(hero => { - matchingHero = hero; - }); - connection.mockRespond(createResponse(heroes)); - tick(); - - expect(matchingHero).toBeUndefined(); - }))); - - }); - - function createResponse(data) { - return new Response(new ResponseOptions({body: {data: data}, status: 200})); - } -}); diff --git a/tour-of-heroes/src/app/hero.service/hero.service.isolated.spec.ts b/tour-of-heroes/src/app/hero.service/hero.service.isolated.spec.ts new file mode 100644 index 0000000..7ae4ef0 --- /dev/null +++ b/tour-of-heroes/src/app/hero.service/hero.service.isolated.spec.ts @@ -0,0 +1,26 @@ +import { HeroService } from './hero.service'; +import { Observable } from 'rxjs/Observable'; +import { ResponseOptions, Response } from '@angular/http'; + +describe('Service: HeroService', () => { + + it('should return the correct hero when called with a valid id', (done) => { + + const heroes = [ + {id: 2, name: 'Rubberman'}, + {id: 4, name: 'Dynama'} + ]; + + const mockResponse = new Response(new ResponseOptions({ body: {data: heroes} })); + + const mockHttp = jasmine.createSpyObj('mockHttp', ['get', 'post', 'put', 'delete']); + mockHttp.get.and.returnValue(Observable.of(mockResponse)); + + let service = new HeroService(mockHttp); + + service.getHeroes().then(retval => { + expect(retval).toBe(heroes); + done(); + }); + }); +}); diff --git a/tour-of-heroes/src/app/hero.service/hero.service.pure-isolated.spec.ts b/tour-of-heroes/src/app/hero.service/hero.service.pure-isolated.spec.ts new file mode 100644 index 0000000..c54fd6f --- /dev/null +++ b/tour-of-heroes/src/app/hero.service/hero.service.pure-isolated.spec.ts @@ -0,0 +1,30 @@ +import { HeroService } from './hero.service'; + +describe('Service: HeroService (pure)', () => { + + it('should return the correct hero when called with a valid id', (done) => { + + const heroes = [ + {id: 2, name: 'Rubberman'}, + {id: 4, name: 'Dynama'} + ]; + + const mockResponse = { + toPromise: () => { + return new Promise((resolve, reject) => { + resolve({ json: () => ({ data: heroes }) }); + }); + } + }; + + const mockHttp = jasmine.createSpyObj('mockHttp', ['get', 'post', 'put', 'delete']); + mockHttp.get.and.returnValue(mockResponse); + + let service = new HeroService(mockHttp); + + service.getHeroes().then(retval => { + expect(retval).toBe(heroes); + done(); + }); + }); +}); diff --git a/tour-of-heroes/src/app/hero.service/hero.service.shallow.spec.ts b/tour-of-heroes/src/app/hero.service/hero.service.shallow.spec.ts new file mode 100644 index 0000000..48a1396 --- /dev/null +++ b/tour-of-heroes/src/app/hero.service/hero.service.shallow.spec.ts @@ -0,0 +1,80 @@ +import { TestBed, fakeAsync, inject, tick } from '@angular/core/testing'; +import { MockBackend } from '@angular/http/testing'; +import { Http, BaseRequestOptions, Response, ResponseOptions } from '@angular/http'; +import { HeroService } from './hero.service'; + +describe('HeroService', () => { + let mockResponse, matchingHero, connection; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + HeroService, + MockBackend, + BaseRequestOptions, + { + provide: Http, + useFactory: (backend, defaultOptions) => new Http(backend, defaultOptions), + deps: [MockBackend, BaseRequestOptions] + }, + ] + }); + + const heroes = [ + {id: 2, name: 'Rubberman'}, + {id: 4, name: 'Dynama'} + ]; + mockResponse = new Response(new ResponseOptions({body: {data: heroes}, status: 200})); + }); + + describe('getHero', () => { + + // Subscribing to the connection and storing it for later + it('should return the correct hero when called with a valid id', fakeAsync( + inject([HeroService, MockBackend], (service: HeroService, backend: MockBackend) => { + + backend.connections.subscribe(c => connection = c); + service.getHero(4).then(hero => matchingHero = hero); + connection.mockRespond(mockResponse); + tick(); + + expect(matchingHero.id).toBe(4); + expect(matchingHero.name).toBe('Dynama'); + }))); + + // Subscribing to the connection and responding inside the subscription + it('should return the correct hero when called with a valid id', fakeAsync( + inject([HeroService, MockBackend], (service: HeroService, backend: MockBackend) => { + + backend.connections.subscribe(c => c.mockRespond(mockResponse)); + service.getHero(4).then(hero => matchingHero = hero); + tick(); + + expect(matchingHero.id).toBe(4); + expect(matchingHero.name).toBe('Dynama'); + }))); + + // Using the MockBackend.connectionsArray to get the connect + it('should return the correct hero when called with a valid id', fakeAsync( + inject([HeroService, MockBackend], (service: HeroService, backend: MockBackend) => { + + service.getHero(4).then(hero => matchingHero = hero); + backend.connectionsArray[0].mockRespond(mockResponse); + tick(); + + expect(matchingHero.id).toBe(4); + expect(matchingHero.name).toBe('Dynama'); + }))); + + it('should return an empty array when called with an invalid id', fakeAsync( + inject([HeroService, MockBackend], (service: HeroService, backend: MockBackend) => { + backend.connections.subscribe(c => connection = c); + + service.getHero(45).then(hero => matchingHero = hero); + connection.mockRespond(mockResponse); + tick(); + + expect(matchingHero).toBeUndefined(); + }))); + }); +}); diff --git a/tour-of-heroes/src/app/hero.service.ts b/tour-of-heroes/src/app/hero.service/hero.service.ts similarity index 97% rename from tour-of-heroes/src/app/hero.service.ts rename to tour-of-heroes/src/app/hero.service/hero.service.ts index fef700e..33cb3a8 100644 --- a/tour-of-heroes/src/app/hero.service.ts +++ b/tour-of-heroes/src/app/hero.service/hero.service.ts @@ -3,7 +3,7 @@ import { Headers, Http } from '@angular/http'; import 'rxjs/add/operator/toPromise'; -import { Hero } from './hero'; +import { Hero } from '../shared/hero'; @Injectable() export class HeroService { diff --git a/tour-of-heroes/src/app/heroes.component.css b/tour-of-heroes/src/app/heroes.component/heroes.component.css similarity index 100% rename from tour-of-heroes/src/app/heroes.component.css rename to tour-of-heroes/src/app/heroes.component/heroes.component.css diff --git a/tour-of-heroes/src/app/heroes.component.html b/tour-of-heroes/src/app/heroes.component/heroes.component.html similarity index 100% rename from tour-of-heroes/src/app/heroes.component.html rename to tour-of-heroes/src/app/heroes.component/heroes.component.html diff --git a/tour-of-heroes/src/app/heroes.component.ts b/tour-of-heroes/src/app/heroes.component/heroes.component.ts similarity index 91% rename from tour-of-heroes/src/app/heroes.component.ts rename to tour-of-heroes/src/app/heroes.component/heroes.component.ts index 17683c0..7acda16 100644 --- a/tour-of-heroes/src/app/heroes.component.ts +++ b/tour-of-heroes/src/app/heroes.component/heroes.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; -import { Hero } from './hero'; -import { HeroService } from './hero.service'; +import { Hero } from '../shared/hero'; +import { HeroService } from '../hero.service/hero.service'; @Component({ selector: 'app-heroes', diff --git a/tour-of-heroes/src/app/index.ts b/tour-of-heroes/src/app/index.ts index 875bdb2..d9f3301 100644 --- a/tour-of-heroes/src/app/index.ts +++ b/tour-of-heroes/src/app/index.ts @@ -1,2 +1,2 @@ -export * from './app.component'; +export * from './app.component/app.component'; export * from './app.module'; diff --git a/tour-of-heroes/src/app/hero.ts b/tour-of-heroes/src/app/shared/hero.ts similarity index 100% rename from tour-of-heroes/src/app/hero.ts rename to tour-of-heroes/src/app/shared/hero.ts diff --git a/tour-of-heroes/src/app/in-memory-data.service.spec.ts b/tour-of-heroes/src/app/shared/in-memory-data.service.spec.ts similarity index 100% rename from tour-of-heroes/src/app/in-memory-data.service.spec.ts rename to tour-of-heroes/src/app/shared/in-memory-data.service.spec.ts diff --git a/tour-of-heroes/src/app/in-memory-data.service.ts b/tour-of-heroes/src/app/shared/in-memory-data.service.ts similarity index 100% rename from tour-of-heroes/src/app/in-memory-data.service.ts rename to tour-of-heroes/src/app/shared/in-memory-data.service.ts diff --git a/tour-of-heroes/src/app/shared/index.ts b/tour-of-heroes/src/app/shared/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/tour-of-heroes/src/test.ts b/tour-of-heroes/src/test.ts index 7727c8e..17232ce 100644 --- a/tour-of-heroes/src/test.ts +++ b/tour-of-heroes/src/test.ts @@ -1,4 +1,5 @@ import './polyfills.ts'; +import './app/rxjs-extensions'; import 'zone.js/dist/long-stack-trace-zone'; import 'zone.js/dist/proxy.js';