From f27c57a9a4d30970032e60380daa9c68a1728473 Mon Sep 17 00:00:00 2001 From: Joe Eames Date: Wed, 21 Sep 2016 22:22:46 -0600 Subject: [PATCH 01/31] added a note for the workshop --- tour-of-heroes/src/app/dashboard.component.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tour-of-heroes/src/app/dashboard.component.spec.ts b/tour-of-heroes/src/app/dashboard.component.spec.ts index eb1b44e..a7973d8 100644 --- a/tour-of-heroes/src/app/dashboard.component.spec.ts +++ b/tour-of-heroes/src/app/dashboard.component.spec.ts @@ -35,6 +35,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]); From 6fb5f540d397f21cda2ec5370d8e373d7eafa789 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 22 Sep 2016 08:14:54 +0100 Subject: [PATCH 02/31] README: fix CLI version --- tour-of-heroes/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 9739b8c4b89469cae381ecbb66d2807f019f10d7 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 22 Sep 2016 11:02:03 +0100 Subject: [PATCH 03/31] remove unnecessary references to Protractor globals --- tour-of-heroes/e2e/app.e2e-spec.ts | 1 - tour-of-heroes/e2e/heroes-2.e2e-spec.ts | 10 ++++------ 2 files changed, 4 insertions(+), 7 deletions(-) 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..0d95eb7 100644 --- a/tour-of-heroes/e2e/heroes-2.e2e-spec.ts +++ b/tour-of-heroes/e2e/heroes-2.e2e-spec.ts @@ -1,4 +1,4 @@ -import {browser, element, by} from 'protractor'; +import {browser} from 'protractor'; // import {saveScreenshot} from './screenshot'; import {HeroesPage} from './page-objects/heroes-page'; @@ -24,8 +24,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 +38,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'); From d5733816804bdf16e42a0f0a0c68cba6c93865ee Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 22 Sep 2016 11:31:20 +0100 Subject: [PATCH 04/31] e2e: remove unused screenshot code --- tour-of-heroes/e2e/heroes-2.e2e-spec.ts | 2 -- tour-of-heroes/e2e/heroes.e2e-spec.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/tour-of-heroes/e2e/heroes-2.e2e-spec.ts b/tour-of-heroes/e2e/heroes-2.e2e-spec.ts index 0d95eb7..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} from 'protractor'; -// import {saveScreenshot} from './screenshot'; 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', () => { 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', () => { From 61be1670f9a647c13ced2ff131b8e54bb4d5d76e Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 22 Sep 2016 13:16:29 +0100 Subject: [PATCH 05/31] add console log into screenshot capture --- tour-of-heroes/e2e/screenshot.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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')); }); } From 4932ad7176f5e24ead03edf74c78d09c9ff2d2c1 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 22 Sep 2016 13:36:20 +0100 Subject: [PATCH 06/31] jasmine: use single quotes --- jasmine/src/FivesArray.js | 2 +- jasmine/src/Joiner.spec.js | 10 +++++----- jasmine/src/Order.spec.js | 10 +++++----- jasmine/src/Player.js | 2 +- jasmine/src/Player.spec.js | 8 ++++---- jasmine/src/Reverser.js | 4 ++-- jasmine/src/Song.js | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) 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/Joiner.spec.js b/jasmine/src/Joiner.spec.js index f1fea68..ba5fae8 100644 --- a/jasmine/src/Joiner.spec.js +++ b/jasmine/src/Joiner.spec.js @@ -1,27 +1,27 @@ // this will be written up as demonstration. This is the finished code -describe("join", function() { +describe('join', function() { var joiner; beforeEach(function() { joiner = new Joiner(); }); - it("should be able to join an simple array with a simple string separator", function() { + 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'); }); - it("should be able to join an empty array with something and return an empty string", 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 default to a comma string separator", function() { + it('should default to a comma string separator', function() { var joined = joiner.join([3,4]); expect(joined).toEqual('3,4'); }); - it("should work with an empty string separator", function() { + it('should work with an empty string separator', function() { var joined = joiner.join([3,4], ''); expect(joined).toEqual('34'); }); diff --git a/jasmine/src/Order.spec.js b/jasmine/src/Order.spec.js index 503350d..48aa185 100644 --- a/jasmine/src/Order.spec.js +++ b/jasmine/src/Order.spec.js @@ -1,5 +1,5 @@ // this will be written up as demonstration. This is the finished code -describe("Order", function() { +describe('Order', function() { var order, customer; beforeEach(function() { @@ -7,16 +7,16 @@ describe("Order", function() { order = new Order(customer); }); - it("unpreferred customers get no discount", function() { + it('unpreferred customers get no discount', function() { spyOn(customer, 'isPreferred').and.returnValue(false) - + order.addItem('foos', 10); expect(order.getTotal()).toEqual(10); }); - it("preferred customers get a 10% discount", function() { + it('preferred customers get 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..64e7c01 100644 --- a/jasmine/src/Player.spec.js +++ b/jasmine/src/Player.spec.js @@ -1,5 +1,5 @@ // this code will also be written during the demonstration -describe("Player", function() { +describe('Player', function() { var player; var song; @@ -9,7 +9,7 @@ describe("Player", function() { }); //demonstrates use of custom matcher - it("should be able to play a Song", function() { + it('should be able to play a Song', function() { player.play(song); expect(player.currentlyPlayingSong).toEqual(song); @@ -17,7 +17,7 @@ describe("Player", function() { }); // demonstrates use of spies to intercept and test method calls - it("tells the current song if the user has made it a favorite", function() { + it('tells the current song if the user has made it a favorite', function() { spyOn(song, 'persistFavoriteStatus'); player.play(song); @@ -27,7 +27,7 @@ describe("Player", function() { }); // same as above except with createSpyObj() - it("tells the current song if the user has made it a favorite", function() { + it('tells the current song if the user has made it a favorite', function() { var song = jasmine.createSpyObj('song', ['persistFavoriteStatus']); player.play(song); 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/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 From e5247f057e76d3c26bfb063cf660b1e01fd2523a Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 22 Sep 2016 13:57:04 +0100 Subject: [PATCH 07/31] jasmine: tighten up spec description --- jasmine/src/Joiner.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jasmine/src/Joiner.spec.js b/jasmine/src/Joiner.spec.js index ba5fae8..a7eec69 100644 --- a/jasmine/src/Joiner.spec.js +++ b/jasmine/src/Joiner.spec.js @@ -6,12 +6,12 @@ describe('join', function() { joiner = new Joiner(); }); - it('should be able to join an simple array with a simple string separator', function() { + it('should join an array with a separator', function() { var joined = joiner.join([1,2], '-'); expect(joined).toEqual('1-2'); }); - it('should be able to join an empty array with something and return an empty string', function() { + it('should return an empty string when joining an empty array', function() { var joined = joiner.join([], ','); expect(joined).toEqual(''); }); From 7ff0d7746341446203f5ff289d6c1addc1f31405 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 09:16:35 +0100 Subject: [PATCH 08/31] jasmine: tighten up test descriptions --- jasmine/src/Joiner.spec.js | 47 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/jasmine/src/Joiner.spec.js b/jasmine/src/Joiner.spec.js index a7eec69..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 join an array with a separator', function() { - var joined = joiner.join([1,2], '-'); - expect(joined).toEqual('1-2'); - }); + describe('join(array, separator)', function() { - it('should return an empty string when joining an empty array', 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(); + }); + }); }); From 064e3c23e90adfa4099bdeecce6cbdd5d96b5187 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 09:23:05 +0100 Subject: [PATCH 09/31] jasmine: add Reverse.spec --- jasmine/src/Reverser.spec.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 jasmine/src/Reverser.spec.js 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-"); + }); + }); +}); From 607812eefab598462058604a025a6103d1e34678 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 09:34:52 +0100 Subject: [PATCH 10/31] jasmine: prepped FivesArray spec as solution --- jasmine/src/FivesArray.spec.js | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) 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(); + }) + }); }); - - - - - From 1dc20a2f0291f3ebd7058f305a8960ef0c593b67 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 09:47:44 +0100 Subject: [PATCH 11/31] jasmine: add squareRoot.spec solution --- jasmine/src/squareRoot.spec.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 jasmine/src/squareRoot.spec.js 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); + }); +}); From 33ba27b43247d2e5db06f4c7198d826a6158b108 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 11:11:47 +0100 Subject: [PATCH 12/31] jasmine: tighten up Player spec --- jasmine/src/Player.spec.js | 49 ++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/jasmine/src/Player.spec.js b/jasmine/src/Player.spec.js index 64e7c01..8f076cc 100644 --- a/jasmine/src/Player.spec.js +++ b/jasmine/src/Player.spec.js @@ -1,5 +1,4 @@ -// this code will also be written during the demonstration -describe('Player', function() { +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.playing).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); + }); }); - }); From e29cbab77534b69112e0ea94251e00eb93b59e05 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 11:17:51 +0100 Subject: [PATCH 13/31] jasmine: tighten up Order spec --- jasmine/src/Order.spec.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/jasmine/src/Order.spec.js b/jasmine/src/Order.spec.js index 48aa185..99d8b76 100644 --- a/jasmine/src/Order.spec.js +++ b/jasmine/src/Order.spec.js @@ -1,4 +1,3 @@ -// this will be written up as demonstration. This is the finished code describe('Order', function() { var order, customer; @@ -7,18 +6,19 @@ describe('Order', function() { order = new Order(customer); }); - it('unpreferred customers get no discount', function() { - spyOn(customer, 'isPreferred').and.returnValue(false) + describe('addItem', function() { + it('should not discount unpreferred customers', function() { + spyOn(customer, 'isPreferred').and.returnValue(false) - order.addItem('foos', 10); - expect(order.getTotal()).toEqual(10); - }); + order.addItem('foos', 10); + expect(order.getTotal()).toEqual(10); + }); - it('preferred customers get a 10% discount', function() { - spyOn(customer, 'isPreferred').and.returnValue(true); + it('should give preferred customers 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(9); + }); }); - }); From 25074da164056bc1a8d036ec003f7de5508bd64c Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 11:20:46 +0100 Subject: [PATCH 14/31] jasmine: improve description of Order.spec.js --- jasmine/src/Order.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jasmine/src/Order.spec.js b/jasmine/src/Order.spec.js index 99d8b76..4a7a132 100644 --- a/jasmine/src/Order.spec.js +++ b/jasmine/src/Order.spec.js @@ -6,7 +6,7 @@ describe('Order', function() { order = new Order(customer); }); - describe('addItem', function() { + describe('addItem(name, cost)', function() { it('should not discount unpreferred customers', function() { spyOn(customer, 'isPreferred').and.returnValue(false) From 561b5b0539d94aa1ad8a451f4192cee629576665 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 12:24:33 +0100 Subject: [PATCH 15/31] jasmine: fix broken player spec --- jasmine/src/Player.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jasmine/src/Player.spec.js b/jasmine/src/Player.spec.js index 8f076cc..5f7eabf 100644 --- a/jasmine/src/Player.spec.js +++ b/jasmine/src/Player.spec.js @@ -12,7 +12,7 @@ describe("Player", function() { it("should update currentlyPlayingSong and playing", function() { player.play(song); expect(player.currentlyPlayingSong).toEqual(song); - expect(player.playing).toBe(true); + expect(player.isPlaying).toBe(true); }); // demonstrates custom matcher From 8eda8008c6e6abda1aada0986c6e409bd064a61e Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 14:45:58 +0100 Subject: [PATCH 16/31] heroes: load RxJS extensions into tests --- tour-of-heroes/src/test.ts | 1 + 1 file changed, 1 insertion(+) 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'; From 7a613affd607b1570826396cf415389f7c764a0a Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 14:46:19 +0100 Subject: [PATCH 17/31] heroes: tidy up service tests --- .../app/hero-search.service.isolated.spec.ts | 23 +++------- .../src/app/hero.service.isolated-obs.spec.ts | 31 ------------- .../src/app/hero.service.isolated.spec.ts | 43 ++++++------------- .../app/hero.service.pure-isolated.spec.ts | 30 +++++++++++++ 4 files changed, 50 insertions(+), 77 deletions(-) delete mode 100644 tour-of-heroes/src/app/hero.service.isolated-obs.spec.ts create mode 100644 tour-of-heroes/src/app/hero.service.pure-isolated.spec.ts 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 index 123d402..ca0e84f 100644 --- a/tour-of-heroes/src/app/hero-search.service.isolated.spec.ts +++ b/tour-of-heroes/src/app/hero-search.service.isolated.spec.ts @@ -1,22 +1,18 @@ import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/observable/of'; -import {ResponseOptions, Response} from '@angular/http'; +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)); + 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; }); @@ -25,8 +21,3 @@ describe('HeroSearchService', () => { 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.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 index 122e63c..7ae4ef0 100644 --- a/tour-of-heroes/src/app/hero.service.isolated.spec.ts +++ b/tour-of-heroes/src/app/hero.service.isolated.spec.ts @@ -1,43 +1,26 @@ import { HeroService } from './hero.service'; +import { Observable } from 'rxjs/Observable'; +import { ResponseOptions, Response } from '@angular/http'; describe('Service: HeroService', () => { - let heroes; - let mockHttp = jasmine.createSpyObj('mockHttp', ['get', 'post', 'put', 'delete']); - beforeEach(() => { - heroes = [ + 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} })); - it('should return the correct hero when called with a valid id', (done) => { - let service = new HeroService(mockHttp); - mockHttp.get.and.returnValue(createMockGetCallForAllHeroes()); + const mockHttp = jasmine.createSpyObj('mockHttp', ['get', 'post', 'put', 'delete']); + mockHttp.get.and.returnValue(Observable.of(mockResponse)); - service.getHeroes().then(retval => { - expect(retval).toBe(heroes); - done(); - }); + let service = new HeroService(mockHttp); + 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.pure-isolated.spec.ts b/tour-of-heroes/src/app/hero.service.pure-isolated.spec.ts new file mode 100644 index 0000000..c54fd6f --- /dev/null +++ b/tour-of-heroes/src/app/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(); + }); + }); +}); From c6b1fc1b55e21dc23983015e153286d9c2ddca79 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 21:30:45 +0100 Subject: [PATCH 18/31] heroes: simplify HeroDetailComponent and spec --- .../hero-detail.component.isolated.spec.ts | 73 +++++++------------ .../src/app/hero-detail.component.ts | 11 +-- 2 files changed, 31 insertions(+), 53 deletions(-) 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 index d823a8b..6ba143c 100644 --- a/tour-of-heroes/src/app/hero-detail.component.isolated.spec.ts +++ b/tour-of-heroes/src/app/hero-detail.component.isolated.spec.ts @@ -2,58 +2,41 @@ 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} - ]; + const mockHero = {id: 3, name: 'Magneta', strength: 4}; + let mockHeroService; + let mockRoute; + let component: HeroDetailComponent; beforeEach(() => { - this.mockHeroService = { getHero: () => {}, update: () => {} }; - this.mockActivatedRoute = {params: []}; - this.mockLocation = { back: () => {} }; + mockHeroService = jasmine.createSpyObj('heroService', ['getHero', 'update']); + mockRoute = {params: []}; + component = new HeroDetailComponent(mockHeroService, mockRoute); }); - 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); + describe('ngOnInit', () => { + it('should get the hero ', fakeAsync(() => { + mockRoute.params.push({id: '3'}); + mockHeroService.getHero.and.returnValue(Promise.resolve(mockHero)); - component.ngOnInit(); - tick(); + 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]); - })); + 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(); + })); + }); }); diff --git a/tour-of-heroes/src/app/hero-detail.component.ts b/tour-of-heroes/src/app/hero-detail.component.ts index d65eed4..df10555 100644 --- a/tour-of-heroes/src/app/hero-detail.component.ts +++ b/tour-of-heroes/src/app/hero-detail.component.ts @@ -1,7 +1,5 @@ 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'; @@ -16,8 +14,7 @@ export class HeroDetailComponent implements OnInit { constructor( private heroService: HeroService, - private route: ActivatedRoute, - private location: Location) { + private route: ActivatedRoute) { } ngOnInit(): void { @@ -30,13 +27,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(); + window.history.back(); } } From 4190f678c35c68b5a23f0518e5840ddcd8f8009c Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 23:02:12 +0100 Subject: [PATCH 19/31] heroes: simplify AppComponent shallow test --- tour-of-heroes/src/app/app.component.shallow.spec.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tour-of-heroes/src/app/app.component.shallow.spec.ts b/tour-of-heroes/src/app/app.component.shallow.spec.ts index 760e4f0..bdef4eb 100644 --- a/tour-of-heroes/src/app/app.component.shallow.spec.ts +++ b/tour-of-heroes/src/app/app.component.shallow.spec.ts @@ -4,25 +4,21 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('AppComponent (shallow tests)', () => { - let fixture, component, element; + let fixture; beforeEach(() => { TestBed.configureTestingModule({ - declarations: [ - AppComponent - ], + 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'); + expect(fixture.componentInstance.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'); + expect(fixture.nativeElement.querySelector('h1').textContent).toContain('Tour of Heroes'); })); }); From f4938e2822e4dce0dfc83365c34d5681b40e1cbf Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 23:35:38 +0100 Subject: [PATCH 20/31] heroes: remove unnecessary async from AppComponent shallow test --- tour-of-heroes/src/app/app.component.shallow.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tour-of-heroes/src/app/app.component.shallow.spec.ts b/tour-of-heroes/src/app/app.component.shallow.spec.ts index bdef4eb..0c76319 100644 --- a/tour-of-heroes/src/app/app.component.shallow.spec.ts +++ b/tour-of-heroes/src/app/app.component.shallow.spec.ts @@ -1,4 +1,4 @@ -import { TestBed, async } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; import { AppComponent } from './app.component'; import { NO_ERRORS_SCHEMA } from '@angular/core'; @@ -13,12 +13,12 @@ describe('AppComponent (shallow tests)', () => { fixture = TestBed.createComponent(AppComponent); }); - it(`should have as title 'Tour of Heroes'`, async(() => { + it(`should have as title 'Tour of Heroes'`, () => { expect(fixture.componentInstance.title).toEqual('Tour of Heroes'); - })); + }); - it('should render title in a h1 tag', async(() => { + it('should render title in a h1 tag', () => { fixture.detectChanges(); expect(fixture.nativeElement.querySelector('h1').textContent).toContain('Tour of Heroes'); - })); + }); }); From f2410534204b81d2931a092f12bf37ef07ce1f3c Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 23 Sep 2016 23:43:00 +0100 Subject: [PATCH 21/31] heroes: remove unnecessary NO_ERRORS_SCHEMA from pipe test --- .../src/app/exponential-strength.pipe.shallow.spec.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tour-of-heroes/src/app/exponential-strength.pipe.shallow.spec.ts b/tour-of-heroes/src/app/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.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'); }); From d3616afb7a5d0ce894410e652c792bcbce8e7e19 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sat, 24 Sep 2016 06:17:53 +0100 Subject: [PATCH 22/31] heroes: remove unused variable --- tour-of-heroes/src/app/dashboard.component.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tour-of-heroes/src/app/dashboard.component.spec.ts b/tour-of-heroes/src/app/dashboard.component.spec.ts index a7973d8..948714c 100644 --- a/tour-of-heroes/src/app/dashboard.component.spec.ts +++ b/tour-of-heroes/src/app/dashboard.component.spec.ts @@ -1,10 +1,8 @@ -/* 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 { RouterTestingModule } from '@angular/router/testing'; import { HeroService } from './hero.service'; import { ExponentialStrengthPipe } from './exponential-strength.pipe'; From 567aa157e731cbe30146418d0588dd5c34e29635 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sat, 24 Sep 2016 06:18:48 +0100 Subject: [PATCH 23/31] heroes: added alternative test styles --- tour-of-heroes/src/app/hero.service.spec.ts | 72 +++++++++++++-------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/tour-of-heroes/src/app/hero.service.spec.ts b/tour-of-heroes/src/app/hero.service.spec.ts index af97e52..48a1396 100644 --- a/tour-of-heroes/src/app/hero.service.spec.ts +++ b/tour-of-heroes/src/app/hero.service.spec.ts @@ -1,42 +1,65 @@ -/* tslint:disable:no-unused-variable */ - -import { TestBed, async, fakeAsync, inject, tick } from '@angular/core/testing'; -import { MockBackend, MockConnection } from '@angular/http/testing'; +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'; -import { Http, ConnectionBackend, BaseRequestOptions, Response, ResponseOptions } from '@angular/http'; -describe('Service: HeroService', () => { - let heroes, matchingHero, connection; +describe('HeroService', () => { + let mockResponse, matchingHero, connection; beforeEach(() => { TestBed.configureTestingModule({ providers: [ HeroService, - { provide: Http, useFactory: (backend: ConnectionBackend, defaultOptions: BaseRequestOptions) => { - return new Http(backend, defaultOptions); - }, deps: [MockBackend, BaseRequestOptions] - }, MockBackend, - BaseRequestOptions + BaseRequestOptions, + { + provide: Http, + useFactory: (backend, defaultOptions) => new Http(backend, defaultOptions), + deps: [MockBackend, BaseRequestOptions] + }, ] }); - heroes = [ + 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, Http], (service: HeroService, backend: MockBackend, http: Http) => { + 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) => { - service.getHero(4).then(hero => { - matchingHero = hero; - }); - connection.mockRespond(createResponse(heroes)); + 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); @@ -44,21 +67,14 @@ describe('Service: HeroService', () => { }))); it('should return an empty array when called with an invalid id', fakeAsync( - inject([HeroService, MockBackend, Http], (service: HeroService, backend: MockBackend, http: Http) => { + inject([HeroService, MockBackend], (service: HeroService, backend: MockBackend) => { backend.connections.subscribe(c => connection = c); - service.getHero(45).then(hero => { - matchingHero = hero; - }); - connection.mockRespond(createResponse(heroes)); + service.getHero(45).then(hero => matchingHero = hero); + connection.mockRespond(mockResponse); tick(); expect(matchingHero).toBeUndefined(); }))); - }); - - function createResponse(data) { - return new Response(new ResponseOptions({body: {data: data}, status: 200})); - } }); From d2bb7b9a5a8d393836858e575a04143c3add1284 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sat, 24 Sep 2016 06:20:28 +0100 Subject: [PATCH 24/31] heroes: rename shallow spec file --- .../app/{hero.service.spec.ts => hero.service.shallow.spec.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tour-of-heroes/src/app/{hero.service.spec.ts => hero.service.shallow.spec.ts} (100%) diff --git a/tour-of-heroes/src/app/hero.service.spec.ts b/tour-of-heroes/src/app/hero.service.shallow.spec.ts similarity index 100% rename from tour-of-heroes/src/app/hero.service.spec.ts rename to tour-of-heroes/src/app/hero.service.shallow.spec.ts From 69c0522d74d11d76533fbdefbadbdf5ddc75bb1a Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sat, 24 Sep 2016 07:32:24 +0100 Subject: [PATCH 25/31] hereos: demonstrate detectChanges options --- .../app/hero-detail.component.shallow.spec.ts | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) 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 index 6138d56..fb79d52 100644 --- a/tour-of-heroes/src/app/hero-detail.component.shallow.spec.ts +++ b/tour-of-heroes/src/app/hero-detail.component.shallow.spec.ts @@ -1,4 +1,4 @@ -import { TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { TestBed, fakeAsync, tick, async, ComponentFixture } from '@angular/core/testing'; import { HeroDetailComponent } from './hero-detail.component'; import { HeroService } from './hero.service'; import { ActivatedRoute } from '@angular/router'; @@ -10,13 +10,13 @@ import { By } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; describe('HeroDetailComponent (shallow tests)', () => { - let fixture, component, element, templateHeroService, templateActivatedRoute, templateLocation, mockHeroService; + let fixture: ComponentFixture; + let 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'}]}; @@ -48,6 +48,28 @@ describe('HeroDetailComponent (shallow tests)', () => { spyOn(mockHeroService, 'update').and.returnValue(Promise.resolve()); }); + // Demonstrates using `async` and `detectChanges` + it(`should have the correct hero's name & id`, async(() => { + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(element.querySelector('div').textContent).toContain('id: 3'); + expect(element.querySelector('div').textContent).toContain('Magneta'); + }); + })); + + // Demonstrates using `async` and `autoDetectChanges` + it(`should have the correct hero's name & id`, async(() => { + + fixture.autoDetectChanges(); + fixture.whenStable().then(() => { + expect(element.querySelector('div').textContent).toContain('id: 3'); + expect(element.querySelector('div').textContent).toContain('Magneta'); + }); + })); + + // Demonstrates using fakeAsync it(`should have the correct hero's name & id`, fakeAsync(() => { fixture.detectChanges(); From bed5eba91c2237eeee763c82fcffe6cc4ad5a589 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sat, 24 Sep 2016 13:23:53 +0100 Subject: [PATCH 26/31] heroes: added to the HeroDetailComponent shallow spec --- .../app/hero-detail.component.shallow.spec.ts | 145 ++++++++++-------- 1 file changed, 82 insertions(+), 63 deletions(-) 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 index fb79d52..e6c398e 100644 --- a/tour-of-heroes/src/app/hero-detail.component.shallow.spec.ts +++ b/tour-of-heroes/src/app/hero-detail.component.shallow.spec.ts @@ -2,113 +2,132 @@ import { TestBed, fakeAsync, tick, async, ComponentFixture } from '@angular/core 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'; +import { FormsModule, NgModel } from '@angular/forms'; -describe('HeroDetailComponent (shallow tests)', () => { +fdescribe('HeroDetailComponent (shallow tests)', () => { let fixture: ComponentFixture; - let component, element, templateHeroService, templateActivatedRoute, templateLocation, mockHeroService; + let component: HeroDetailComponent; + let element, 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: () => {}}; + mockHeroService = { getHero: () => {}, update: () => {} }; + spyOn(mockHeroService, 'getHero').and.returnValue(Promise.resolve(heroes[0])); + spyOn(mockHeroService, 'update').and.returnValue(Promise.resolve()); + + const mockActiveRoute = {params: [{id: '3'}]}; TestBed.configureTestingModule({ imports: [ + // you must import this so that [(ngModel)] is recognized. FormsModule ], declarations: [ HeroDetailComponent ], providers: [ - { provide: HeroService, useValue: templateHeroService }, - { provide: ActivatedRoute, useValue: templateActivatedRoute }, - { provide: Location, useValue: templateLocation } + // useValue creates a clone of our service object + { provide: HeroService, useFactory: () => mockHeroService }, + { provide: ActivatedRoute, useFactory: () => mockActiveRoute } + ], + schemas: [ + // NO_ERRORS_SCHEMA will hide that angular doesn't know about ngModel ] }); fixture = TestBed.createComponent(HeroDetailComponent); - component = fixture.debugElement.componentInstance; - element = fixture.debugElement.nativeElement; + component = fixture.componentInstance; + element = fixture.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()); - }); + describe('initial display', () => { - // Demonstrates using `async` and `detectChanges` - it(`should have the correct hero's name & id`, async(() => { + 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'); - }); - })); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(element.querySelector('div').textContent).toContain('id: 3'); + expect(element.querySelector('div').textContent).toContain('Magneta'); + }); + })); - // Demonstrates using `async` and `autoDetectChanges` - it(`should have the correct hero's name & id`, async(() => { + 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'); - }); - })); + fixture.autoDetectChanges(); + fixture.whenStable().then(() => { + expect(element.querySelector('div').textContent).toContain('id: 3'); + expect(element.querySelector('div').textContent).toContain('Magneta'); + }); + })); - // Demonstrates using fakeAsync - it(`should have the correct hero's name & id`, fakeAsync(() => { + it('should show the correct hero name & id (using fakeAsync and tick)', fakeAsync(() => { + + fixture.detectChanges(); + tick(); + fixture.detectChanges(); - fixture.detectChanges(); - tick(); - fixture.detectChanges(); + expect(element.querySelector('div').textContent).toContain('id: 3'); + expect(element.querySelector('div').textContent).toContain('Magneta'); + })); + }); - 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 call update on the hero service when save is clicked`, fakeAsync(() => { + it(`should change the hero's name (via nativeElement API)`, fakeAsync(() => { + const inputElement = fixture.debugElement.query(By.css('input')).nativeElement; - fixture.detectChanges(); - tick(); - fixture.detectChanges(); + inputElement.value = 'Mr. Nice'; + inputElement.dispatchEvent(createEvent('input')); // this must be called so that detectChanges will know that something has changed + fixture.detectChanges(); - fixture.debugElement.queryAll(By.css('button'))[1].triggerEventHandler('click', null); + expect(getHeadingText(fixture)).toContain('Mr. Nice'); + })); - expect(mockHeroService.update).toHaveBeenCalledWith(heroes[0]); - })); + it(`should change the hero's name (via debugElement API)`, () => { + const ngModel = fixture.debugElement.query(By.directive(NgModel)); - it(`should change the hero's name when the input box is set`, fakeAsync(() => { - fixture.detectChanges(); - tick(); - fixture.detectChanges(); + ngModel.triggerEventHandler('ngModelChange', 'Mr. Nice'); + 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(getHeadingText(fixture)).toContain('Mr. Nice'); + }); + }); - expect(fixture.debugElement.query(By.css('h2')).nativeElement.textContent).toContain('Mr. Nice'); - })); + describe('clicking save', () => { + beforeEach(fakeAsync(() => { + fixture.detectChanges(); + tick(); + fixture.detectChanges(); + })); + xit(`should update the hero service`, fakeAsync(() => { + const button = getSaveButton(fixture); + button.triggerEventHandler('click', null); + expect(mockHeroService.update).toHaveBeenCalledWith(heroes[0]); + })); + }); }); -function newEvent(eventName: string, bubbles = false, cancelable = false) { +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]; +} From bbc58ca1f302b0c9a994e11d212b822f9272ac2e Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sat, 24 Sep 2016 16:17:34 +0100 Subject: [PATCH 27/31] heroes: organise into subfolders --- .../app/{ => app.component}/app.component.css | 0 .../app/{ => app.component}/app.component.html | 0 .../app.component.isolated.spec.ts | 0 .../app.component.shallow.spec.ts | 0 .../src/app/{ => app.component}/app.component.ts | 0 tour-of-heroes/src/app/app.module.ts | 16 ++++++++-------- tour-of-heroes/src/app/app.routing.ts | 6 +++--- .../dashboard.component.css | 0 .../dashboard.component.html | 0 .../dashboard.component.spec.ts | 4 ++-- .../dashboard.component.ts | 4 ++-- .../exponential-strength.pipe.isolated.spec.ts | 0 .../exponential-strength.pipe.shallow.spec.ts | 0 .../exponential-strength.pipe.ts | 0 .../hero-detail.component.css | 0 .../hero-detail.component.html | 0 .../hero-detail.component.isolated.spec.ts | 0 .../hero-detail.component.shallow.spec.ts | 4 ++-- .../hero-detail.component.ts | 4 ++-- .../hero-search.component.css | 0 .../hero-search.component.html | 0 .../hero-search.component.ts | 4 ++-- .../hero-search.service.isolated.spec.ts | 2 +- .../hero-search.service.ts | 2 +- .../hero.service.isolated.spec.ts | 0 .../hero.service.pure-isolated.spec.ts | 0 .../hero.service.shallow.spec.ts | 0 .../src/app/{ => hero.service}/hero.service.ts | 2 +- .../{ => heroes.component}/heroes.component.css | 0 .../{ => heroes.component}/heroes.component.html | 0 .../{ => heroes.component}/heroes.component.ts | 4 ++-- tour-of-heroes/src/app/index.ts | 2 +- tour-of-heroes/src/app/{ => shared}/hero.ts | 0 .../{ => shared}/in-memory-data.service.spec.ts | 0 .../app/{ => shared}/in-memory-data.service.ts | 0 tour-of-heroes/src/app/shared/index.ts | 0 36 files changed, 27 insertions(+), 27 deletions(-) rename tour-of-heroes/src/app/{ => app.component}/app.component.css (100%) rename tour-of-heroes/src/app/{ => app.component}/app.component.html (100%) rename tour-of-heroes/src/app/{ => app.component}/app.component.isolated.spec.ts (100%) rename tour-of-heroes/src/app/{ => app.component}/app.component.shallow.spec.ts (100%) rename tour-of-heroes/src/app/{ => app.component}/app.component.ts (100%) rename tour-of-heroes/src/app/{ => dashboard.component}/dashboard.component.css (100%) rename tour-of-heroes/src/app/{ => dashboard.component}/dashboard.component.html (100%) rename tour-of-heroes/src/app/{ => dashboard.component}/dashboard.component.spec.ts (92%) rename tour-of-heroes/src/app/{ => dashboard.component}/dashboard.component.ts (88%) rename tour-of-heroes/src/app/{ => exponential-strength.pipe}/exponential-strength.pipe.isolated.spec.ts (100%) rename tour-of-heroes/src/app/{ => exponential-strength.pipe}/exponential-strength.pipe.shallow.spec.ts (100%) rename tour-of-heroes/src/app/{ => exponential-strength.pipe}/exponential-strength.pipe.ts (100%) rename tour-of-heroes/src/app/{ => hero-detail.component}/hero-detail.component.css (100%) rename tour-of-heroes/src/app/{ => hero-detail.component}/hero-detail.component.html (100%) rename tour-of-heroes/src/app/{ => hero-detail.component}/hero-detail.component.isolated.spec.ts (100%) rename tour-of-heroes/src/app/{ => hero-detail.component}/hero-detail.component.shallow.spec.ts (97%) rename tour-of-heroes/src/app/{ => hero-detail.component}/hero-detail.component.ts (89%) rename tour-of-heroes/src/app/{ => hero-search.component}/hero-search.component.css (100%) rename tour-of-heroes/src/app/{ => hero-search.component}/hero-search.component.html (100%) rename tour-of-heroes/src/app/{ => hero-search.component}/hero-search.component.ts (93%) rename tour-of-heroes/src/app/{ => hero-search.service}/hero-search.service.isolated.spec.ts (95%) rename tour-of-heroes/src/app/{ => hero-search.service}/hero-search.service.ts (92%) rename tour-of-heroes/src/app/{ => hero.service}/hero.service.isolated.spec.ts (100%) rename tour-of-heroes/src/app/{ => hero.service}/hero.service.pure-isolated.spec.ts (100%) rename tour-of-heroes/src/app/{ => hero.service}/hero.service.shallow.spec.ts (100%) rename tour-of-heroes/src/app/{ => hero.service}/hero.service.ts (97%) rename tour-of-heroes/src/app/{ => heroes.component}/heroes.component.css (100%) rename tour-of-heroes/src/app/{ => heroes.component}/heroes.component.html (100%) rename tour-of-heroes/src/app/{ => heroes.component}/heroes.component.ts (91%) rename tour-of-heroes/src/app/{ => shared}/hero.ts (100%) rename tour-of-heroes/src/app/{ => shared}/in-memory-data.service.spec.ts (100%) rename tour-of-heroes/src/app/{ => shared}/in-memory-data.service.ts (100%) delete mode 100644 tour-of-heroes/src/app/shared/index.ts 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.shallow.spec.ts b/tour-of-heroes/src/app/app.component/app.component.shallow.spec.ts similarity index 100% rename from tour-of-heroes/src/app/app.component.shallow.spec.ts rename to tour-of-heroes/src/app/app.component/app.component.shallow.spec.ts 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.ts b/tour-of-heroes/src/app/app.module.ts index 4e4d8fa..0eb4859 100644 --- a/tour-of-heroes/src/app/app.module.ts +++ b/tour-of-heroes/src/app/app.module.ts @@ -7,16 +7,16 @@ import { HttpModule } from '@angular/http'; // 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: [ 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.spec.ts similarity index 92% rename from tour-of-heroes/src/app/dashboard.component.spec.ts rename to tour-of-heroes/src/app/dashboard.component/dashboard.component.spec.ts index 948714c..e21dae9 100644 --- a/tour-of-heroes/src/app/dashboard.component.spec.ts +++ b/tour-of-heroes/src/app/dashboard.component/dashboard.component.spec.ts @@ -3,8 +3,8 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { DashboardComponent } from './dashboard.component'; import { Router } from '@angular/router'; import { RouterTestingModule } 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']); 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 100% 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 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.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.isolated.spec.ts b/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.isolated.spec.ts similarity index 100% rename from tour-of-heroes/src/app/hero-detail.component.isolated.spec.ts rename to tour-of-heroes/src/app/hero-detail.component/hero-detail.component.isolated.spec.ts diff --git a/tour-of-heroes/src/app/hero-detail.component.shallow.spec.ts b/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.shallow.spec.ts similarity index 97% rename from tour-of-heroes/src/app/hero-detail.component.shallow.spec.ts rename to tour-of-heroes/src/app/hero-detail.component/hero-detail.component.shallow.spec.ts index e6c398e..ee21adb 100644 --- a/tour-of-heroes/src/app/hero-detail.component.shallow.spec.ts +++ b/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.shallow.spec.ts @@ -1,11 +1,11 @@ import { TestBed, fakeAsync, tick, async, ComponentFixture } from '@angular/core/testing'; import { HeroDetailComponent } from './hero-detail.component'; -import { HeroService } from './hero.service'; +import { HeroService } from '../hero.service/hero.service'; import { ActivatedRoute } from '@angular/router'; import { By } from '@angular/platform-browser'; import { FormsModule, NgModel } from '@angular/forms'; -fdescribe('HeroDetailComponent (shallow tests)', () => { +describe('HeroDetailComponent (shallow tests)', () => { let fixture: ComponentFixture; let component: HeroDetailComponent; let element, mockHeroService; 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 89% 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 df10555..98f1a43 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,8 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Params } 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-hero-detail', 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/hero-search.service.isolated.spec.ts similarity index 95% rename from tour-of-heroes/src/app/hero-search.service.isolated.spec.ts rename to tour-of-heroes/src/app/hero-search.service/hero-search.service.isolated.spec.ts index ca0e84f..854baa0 100644 --- a/tour-of-heroes/src/app/hero-search.service.isolated.spec.ts +++ b/tour-of-heroes/src/app/hero-search.service/hero-search.service.isolated.spec.ts @@ -1,7 +1,7 @@ import { Observable } from 'rxjs/Observable'; import { ResponseOptions, Response } from '@angular/http'; import { HeroSearchService } from './hero-search.service'; -import { Hero } from './hero'; +import { Hero } from '../shared/hero'; describe('HeroSearchService', () => { 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.spec.ts b/tour-of-heroes/src/app/hero.service/hero.service.isolated.spec.ts similarity index 100% rename from tour-of-heroes/src/app/hero.service.isolated.spec.ts rename to tour-of-heroes/src/app/hero.service/hero.service.isolated.spec.ts diff --git a/tour-of-heroes/src/app/hero.service.pure-isolated.spec.ts b/tour-of-heroes/src/app/hero.service/hero.service.pure-isolated.spec.ts similarity index 100% rename from tour-of-heroes/src/app/hero.service.pure-isolated.spec.ts rename to tour-of-heroes/src/app/hero.service/hero.service.pure-isolated.spec.ts diff --git a/tour-of-heroes/src/app/hero.service.shallow.spec.ts b/tour-of-heroes/src/app/hero.service/hero.service.shallow.spec.ts similarity index 100% rename from tour-of-heroes/src/app/hero.service.shallow.spec.ts rename to tour-of-heroes/src/app/hero.service/hero.service.shallow.spec.ts 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 From a815144f7f2f9e5f48f9f040e67c9e991c10593b Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 25 Sep 2016 09:40:10 +0100 Subject: [PATCH 28/31] heroes: rename dashboard spec to shallow --- ...d.component.spec.ts => dashboard.component.shallow.spec.ts} | 3 --- 1 file changed, 3 deletions(-) rename tour-of-heroes/src/app/dashboard.component/{dashboard.component.spec.ts => dashboard.component.shallow.spec.ts} (97%) diff --git a/tour-of-heroes/src/app/dashboard.component/dashboard.component.spec.ts b/tour-of-heroes/src/app/dashboard.component/dashboard.component.shallow.spec.ts similarity index 97% rename from tour-of-heroes/src/app/dashboard.component/dashboard.component.spec.ts rename to tour-of-heroes/src/app/dashboard.component/dashboard.component.shallow.spec.ts index e21dae9..2c197a3 100644 --- a/tour-of-heroes/src/app/dashboard.component/dashboard.component.spec.ts +++ b/tour-of-heroes/src/app/dashboard.component/dashboard.component.shallow.spec.ts @@ -12,9 +12,6 @@ let mockRouter = jasmine.createSpyObj('router', ['navigate']); describe('dashboard.component', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], declarations: [ DashboardComponent, ExponentialStrengthPipe From a8731c4c2b43356c39a00fc441093103595ce3fc Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 25 Sep 2016 11:27:36 +0100 Subject: [PATCH 29/31] heroes: refactor to use Location rather than window.history --- tour-of-heroes/src/app/app.module.ts | 2 + .../hero-detail.component.isolated.spec.ts | 11 ++++- .../hero-detail.component.shallow.spec.ts | 42 +++++++++++++------ .../hero-detail.component.ts | 6 ++- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/tour-of-heroes/src/app/app.module.ts b/tour-of-heroes/src/app/app.module.ts index 0eb4859..9f09dee 100644 --- a/tour-of-heroes/src/app/app.module.ts +++ b/tour-of-heroes/src/app/app.module.ts @@ -4,6 +4,7 @@ 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'; @@ -36,6 +37,7 @@ import { routing } from './app.routing'; ], providers: [ HeroService, + Location ], bootstrap: [ AppComponent ] }) 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 index 6ba143c..e7e1c66 100644 --- 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 @@ -5,12 +5,14 @@ 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, mockRoute); + component = new HeroDetailComponent(mockHeroService, mockLocation, mockRoute); }); describe('ngOnInit', () => { @@ -39,4 +41,11 @@ describe('HeroDetailComponent (isolated tests)', () => { 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 index ee21adb..043919c 100644 --- 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 @@ -1,25 +1,30 @@ -import { TestBed, fakeAsync, tick, async, ComponentFixture } from '@angular/core/testing'; +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, mockHeroService; + let element; let heroes = [ {id: 3, name: 'Magneta', strength: 4}, {id: 4, name: 'Dynama', strength: 2} ]; beforeEach(() => { - mockHeroService = { getHero: () => {}, update: () => {} }; - spyOn(mockHeroService, 'getHero').and.returnValue(Promise.resolve(heroes[0])); - spyOn(mockHeroService, 'update').and.returnValue(Promise.resolve()); - const mockActiveRoute = {params: [{id: '3'}]}; + const mockHeroService = { + getHero: () => Promise.resolve(heroes[0]), + update: () => Promise.resolve() + }; + const mockActivatedRoute = { + params: [ { id: '3' } ] + }; TestBed.configureTestingModule({ imports: [ @@ -30,9 +35,10 @@ describe('HeroDetailComponent (shallow tests)', () => { HeroDetailComponent ], providers: [ - // useValue creates a clone of our service object - { provide: HeroService, useFactory: () => mockHeroService }, - { provide: ActivatedRoute, useFactory: () => mockActiveRoute } + // 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 @@ -104,17 +110,27 @@ describe('HeroDetailComponent (shallow tests)', () => { }); describe('clicking save', () => { + let saveButton; beforeEach(fakeAsync(() => { fixture.detectChanges(); tick(); fixture.detectChanges(); + saveButton = getSaveButton(fixture); })); - xit(`should update the hero service`, fakeAsync(() => { - const button = getSaveButton(fixture); - button.triggerEventHandler('click', null); - expect(mockHeroService.update).toHaveBeenCalledWith(heroes[0]); + 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(); + }))); }); }); diff --git a/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.ts b/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.ts index 98f1a43..28503fc 100644 --- a/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.ts +++ b/tour-of-heroes/src/app/hero-detail.component/hero-detail.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Params } from '@angular/router'; +import { Location } from '@angular/common'; import { Hero } from '../shared/hero'; import { HeroService } from '../hero.service/hero.service'; @@ -14,6 +15,7 @@ export class HeroDetailComponent implements OnInit { constructor( private heroService: HeroService, + private location: Location, private route: ActivatedRoute) { } @@ -27,11 +29,11 @@ export class HeroDetailComponent implements OnInit { save(): void { this.heroService.update(this.hero) - .then(this.goBack); + .then(() => this.goBack()); } goBack(): void { - window.history.back(); + this.location.back(); } } From 2b1c161158ca3d913387f0e76a9efa738fea4552 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 25 Sep 2016 14:23:07 +0100 Subject: [PATCH 30/31] heroes: remove unused import --- .../app/dashboard.component/dashboard.component.shallow.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tour-of-heroes/src/app/dashboard.component/dashboard.component.shallow.spec.ts b/tour-of-heroes/src/app/dashboard.component/dashboard.component.shallow.spec.ts index 2c197a3..e9fde0d 100644 --- a/tour-of-heroes/src/app/dashboard.component/dashboard.component.shallow.spec.ts +++ b/tour-of-heroes/src/app/dashboard.component/dashboard.component.shallow.spec.ts @@ -2,7 +2,6 @@ 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 } from '@angular/router/testing'; import { HeroService } from '../hero.service/hero.service'; import { ExponentialStrengthPipe } from '../exponential-strength.pipe/exponential-strength.pipe'; From 6a2e21bbba1cc8c8e70209beeec9be4e27907655 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 25 Sep 2016 17:08:15 +0100 Subject: [PATCH 31/31] heroes: add deep test --- tour-of-heroes/src/app/app.module.spec.ts | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tour-of-heroes/src/app/app.module.spec.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'); + }))); + }); +});