From b21e25b18bcec998e26a93380137a8f158f35a5a Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Sat, 18 Oct 2014 14:45:31 +0200 Subject: [PATCH 1/7] step-0 bootstrap angular app - add ngApp directive to bootstrap the app - add simple template with an expression --- app/index.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/index.html b/app/index.html index f4c7d5c4d..910a5ef81 100644 --- a/app/index.html +++ b/app/index.html @@ -1,5 +1,5 @@ - + My HTML File @@ -8,5 +8,8 @@ + +

Nothing here {{'yet' + '!'}}

+ - \ No newline at end of file + From f1457431cbf6a4184e85080899b36594f7944dce Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sat, 18 Oct 2014 14:45:31 +0200 Subject: [PATCH 2/7] step-1 static phone list - Added static html list with two phones into index.html --- app/css/app.css | 5 +++++ app/index.html | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/css/app.css b/app/css/app.css index 8d3eae692..e8d83bcb5 100644 --- a/app/css/app.css +++ b/app/css/app.css @@ -1 +1,6 @@ /* app css stylesheet */ + +body { + padding-top: 20px; +} + diff --git a/app/index.html b/app/index.html index 910a5ef81..7ed5b5c82 100644 --- a/app/index.html +++ b/app/index.html @@ -2,14 +2,27 @@ - My HTML File + Google Phone Gallery -

Nothing here {{'yet' + '!'}}

+ From 7c1e0bbc9575848ec64634f00c2d31b16b879514 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sat, 18 Oct 2014 14:45:31 +0200 Subject: [PATCH 3/7] step-2 angular template with repeater - Converted the static html list into dynamic one by: - creating PhoneListCtrl controller for the application - extracting the data from HTML into a the controller as an in-memory dataset - converting the static document into a template with the use of `[ngRepeat]` [directive] which iterates over the dataset with phones, clones the ngRepeat template for each instance and renders it into the view - Added a simple unit test to show off how to write tests and run them with Karma (see README.md for instructions) --- app/index.html | 19 ++++++------------- app/js/controllers.js | 13 +++++++++++++ test/unit/controllersSpec.js | 13 ++++++++++--- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/app/index.html b/app/index.html index 7ed5b5c82..e8898b348 100644 --- a/app/index.html +++ b/app/index.html @@ -1,26 +1,19 @@ - + Google Phone Gallery + - +
    -
  • - Nexus S -

    - Fast just got faster with Nexus S. -

    -
  • -
  • - Motorola XOOM™ with Wi-Fi -

    - The Next, Next Generation tablet. -

    +
  • + {{phone.name}} +

    {{phone.snippet}}

diff --git a/app/js/controllers.js b/app/js/controllers.js index d314a3331..aa7ffe086 100644 --- a/app/js/controllers.js +++ b/app/js/controllers.js @@ -1,3 +1,16 @@ 'use strict'; /* Controllers */ + +var phonecatApp = angular.module('phonecatApp', []); + +phonecatApp.controller('PhoneListCtrl', function($scope) { + $scope.phones = [ + {'name': 'Nexus S', + 'snippet': 'Fast just got faster with Nexus S.'}, + {'name': 'Motorola XOOM™ with Wi-Fi', + 'snippet': 'The Next, Next Generation tablet.'}, + {'name': 'MOTOROLA XOOM™', + 'snippet': 'The Next, Next Generation tablet.'} + ]; +}); diff --git a/test/unit/controllersSpec.js b/test/unit/controllersSpec.js index 63d80c3c3..9e1257c1b 100644 --- a/test/unit/controllersSpec.js +++ b/test/unit/controllersSpec.js @@ -1,11 +1,18 @@ 'use strict'; /* jasmine specs for controllers go here */ +describe('PhoneCat controllers', function() { -describe('controllers', function() { + describe('PhoneListCtrl', function(){ - it("should do something", function() { + beforeEach(module('phonecatApp')); - }); + it('should create "phones" model with 3 phones', inject(function($controller) { + var scope = {}, + ctrl = $controller('PhoneListCtrl', {$scope:scope}); + + expect(scope.phones.length).toBe(3); + })); + }); }); From cf24f6a274685d540b4eb7785486ce6290212d3a Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 19 Oct 2014 09:19:13 +0100 Subject: [PATCH 4/7] step-3 interactive search - Added a search box to demonstrate how: - the data-binding works on input fields - to use [filter] filter - [ngRepeat] automatically shrinks and grows the number of phones in the view - Added an end-to-end test to: - show how end-to-end tests are written and used - to prove that the search box and the repeater are correctly wired together --- app/index.html | 27 +++++++++++++++++++++------ test/e2e/scenarios.js | 26 ++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/app/index.html b/app/index.html index e8898b348..1e2d0b4f0 100644 --- a/app/index.html +++ b/app/index.html @@ -10,12 +10,27 @@ -
    -
  • - {{phone.name}} -

    {{phone.snippet}}

    -
  • -
+
+
+
+ + + Search: + +
+
+ + +
    +
  • + {{phone.name}} +

    {{phone.snippet}}

    +
  • +
+ +
+
+
diff --git a/test/e2e/scenarios.js b/test/e2e/scenarios.js index ed4b2c3e7..5d9013b60 100644 --- a/test/e2e/scenarios.js +++ b/test/e2e/scenarios.js @@ -2,10 +2,28 @@ /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ -describe('my app', function() { +describe('PhoneCat App', function() { - beforeEach(function() { - browser.get('app/index.html'); - }); + describe('Phone list view', function() { + + beforeEach(function() { + browser.get('app/index.html'); + }); + + + it('should filter the phone list as a user types into the search box', function() { + var phoneList = element.all(by.repeater('phone in phones')); + var query = element(by.model('query')); + + expect(phoneList.count()).toBe(3); + + query.sendKeys('nexus'); + expect(phoneList.count()).toBe(1); + + query.clear(); + query.sendKeys('motorola'); + expect(phoneList.count()).toBe(2); + }); + }); }); From b2ad58f1b0a50fe27a46f23a9d1a9c87ad150c9e Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 19 Oct 2014 09:19:49 +0100 Subject: [PATCH 5/7] step-4 phone ordering - Add "age" property to the phone model - Add select box to control phone list order - Override the default order value in controller - Add unit and e2e test for this feature --- app/index.html | 7 ++++++- app/js/controllers.js | 11 ++++++++--- test/e2e/scenarios.js | 27 +++++++++++++++++++++++++++ test/unit/controllersSpec.js | 14 +++++++++++--- 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/app/index.html b/app/index.html index 1e2d0b4f0..414443d2c 100644 --- a/app/index.html +++ b/app/index.html @@ -16,13 +16,18 @@ Search: + Sort by: +
    -
  • +
  • {{phone.name}}

    {{phone.snippet}}

  • diff --git a/app/js/controllers.js b/app/js/controllers.js index aa7ffe086..8af52cd09 100644 --- a/app/js/controllers.js +++ b/app/js/controllers.js @@ -7,10 +7,15 @@ var phonecatApp = angular.module('phonecatApp', []); phonecatApp.controller('PhoneListCtrl', function($scope) { $scope.phones = [ {'name': 'Nexus S', - 'snippet': 'Fast just got faster with Nexus S.'}, + 'snippet': 'Fast just got faster with Nexus S.', + 'age': 1}, {'name': 'Motorola XOOM™ with Wi-Fi', - 'snippet': 'The Next, Next Generation tablet.'}, + 'snippet': 'The Next, Next Generation tablet.', + 'age': 2}, {'name': 'MOTOROLA XOOM™', - 'snippet': 'The Next, Next Generation tablet.'} + 'snippet': 'The Next, Next Generation tablet.', + 'age': 3} ]; + + $scope.orderProp = 'age'; }); diff --git a/test/e2e/scenarios.js b/test/e2e/scenarios.js index 5d9013b60..ea6a02762 100644 --- a/test/e2e/scenarios.js +++ b/test/e2e/scenarios.js @@ -25,5 +25,32 @@ describe('PhoneCat App', function() { query.sendKeys('motorola'); expect(phoneList.count()).toBe(2); }); + + + it('should be possible to control phone order via the drop down select box', function() { + + var phoneNameColumn = element.all(by.repeater('phone in phones').column('{{phone.name}}')); + var query = element(by.model('query')); + + function getNames() { + return phoneNameColumn.map(function(elm) { + return elm.getText(); + }); + } + + query.sendKeys('tablet'); //let's narrow the dataset to make the test assertions shorter + + expect(getNames()).toEqual([ + "Motorola XOOM\u2122 with Wi-Fi", + "MOTOROLA XOOM\u2122" + ]); + + element(by.model('orderProp')).element(by.css('option[value="name"]')).click(); + + expect(getNames()).toEqual([ + "MOTOROLA XOOM\u2122", + "Motorola XOOM\u2122 with Wi-Fi" + ]); + }); }); }); diff --git a/test/unit/controllersSpec.js b/test/unit/controllersSpec.js index 9e1257c1b..7d2d5ecaf 100644 --- a/test/unit/controllersSpec.js +++ b/test/unit/controllersSpec.js @@ -4,15 +4,23 @@ describe('PhoneCat controllers', function() { describe('PhoneListCtrl', function(){ + var scope, ctrl; beforeEach(module('phonecatApp')); - it('should create "phones" model with 3 phones', inject(function($controller) { - var scope = {}, - ctrl = $controller('PhoneListCtrl', {$scope:scope}); + beforeEach(inject(function($controller) { + scope = {}; + ctrl = $controller('PhoneListCtrl', {$scope:scope}); + })); + + it('should create "phones" model with 3 phones', inject(function($controller) { expect(scope.phones.length).toBe(3); })); + + it('should set the default value of orderProp model', function() { + expect(scope.orderProp).toBe('age'); + }); }); }); From 16bf1def0c40a142a93c34ac67eadedfbc8e75ab Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 19 Oct 2014 09:19:49 +0100 Subject: [PATCH 6/7] step-5 XHR and dependency injection - Replaced the in-memory dataset with data loaded from the server (in the form of static phone.json file to make this tutorial backend agnostic) - The json file is loaded using the [$http] service - Demonstrate the use of [services][service] and [dependency injection][DI] - The [$http] is injected into the controller through [dependency injection][DI] --- app/js/controllers.js | 16 ++++------------ test/e2e/scenarios.js | 4 ++-- test/unit/controllersSpec.js | 21 ++++++++++++++------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/app/js/controllers.js b/app/js/controllers.js index 8af52cd09..69d5c7d2f 100644 --- a/app/js/controllers.js +++ b/app/js/controllers.js @@ -4,18 +4,10 @@ var phonecatApp = angular.module('phonecatApp', []); -phonecatApp.controller('PhoneListCtrl', function($scope) { - $scope.phones = [ - {'name': 'Nexus S', - 'snippet': 'Fast just got faster with Nexus S.', - 'age': 1}, - {'name': 'Motorola XOOM™ with Wi-Fi', - 'snippet': 'The Next, Next Generation tablet.', - 'age': 2}, - {'name': 'MOTOROLA XOOM™', - 'snippet': 'The Next, Next Generation tablet.', - 'age': 3} - ]; +phonecatApp.controller('PhoneListCtrl', function($scope, $http) { + $http.get('phones/phones.json').success(function(data) { + $scope.phones = data; + }); $scope.orderProp = 'age'; }); diff --git a/test/e2e/scenarios.js b/test/e2e/scenarios.js index ea6a02762..f85934853 100644 --- a/test/e2e/scenarios.js +++ b/test/e2e/scenarios.js @@ -16,14 +16,14 @@ describe('PhoneCat App', function() { var phoneList = element.all(by.repeater('phone in phones')); var query = element(by.model('query')); - expect(phoneList.count()).toBe(3); + expect(phoneList.count()).toBe(20); query.sendKeys('nexus'); expect(phoneList.count()).toBe(1); query.clear(); query.sendKeys('motorola'); - expect(phoneList.count()).toBe(2); + expect(phoneList.count()).toBe(8); }); diff --git a/test/unit/controllersSpec.js b/test/unit/controllersSpec.js index 7d2d5ecaf..2e4e78028 100644 --- a/test/unit/controllersSpec.js +++ b/test/unit/controllersSpec.js @@ -4,19 +4,26 @@ describe('PhoneCat controllers', function() { describe('PhoneListCtrl', function(){ - var scope, ctrl; + var scope, ctrl, $httpBackend; beforeEach(module('phonecatApp')); + beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) { + $httpBackend = _$httpBackend_; + $httpBackend.expectGET('phones/phones.json'). + respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]); - beforeEach(inject(function($controller) { - scope = {}; - ctrl = $controller('PhoneListCtrl', {$scope:scope}); + scope = $rootScope.$new(); + ctrl = $controller('PhoneListCtrl', {$scope: scope}); })); - it('should create "phones" model with 3 phones', inject(function($controller) { - expect(scope.phones.length).toBe(3); - })); + it('should create "phones" model with 2 phones fetched from xhr', function() { + expect(scope.phones).toBeUndefined(); + $httpBackend.flush(); + + expect(scope.phones).toEqual([{name: 'Nexus S'}, + {name: 'Motorola DROID'}]); + }); it('should set the default value of orderProp model', function() { From 1c855e6259fb6db1676bde3d03058e700c9eedcc Mon Sep 17 00:00:00 2001 From: guatebus Date: Mon, 20 Oct 2014 20:56:25 +0200 Subject: [PATCH 7/7] Added inline annotation to controller definition Added inline annotation so the code matches the tutorial at https://docs.angularjs.org/tutorial/step_05 --- app/js/controllers.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/js/controllers.js b/app/js/controllers.js index 69d5c7d2f..3ec6eaf0d 100644 --- a/app/js/controllers.js +++ b/app/js/controllers.js @@ -4,10 +4,11 @@ var phonecatApp = angular.module('phonecatApp', []); -phonecatApp.controller('PhoneListCtrl', function($scope, $http) { - $http.get('phones/phones.json').success(function(data) { - $scope.phones = data; - }); - - $scope.orderProp = 'age'; -}); +phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', + function($scope, $http) { + $http.get('phones/phones.json').success(function(data) { + $scope.phones = data; + }); + + $scope.orderProp = 'age'; + }]);