From e4fba97d01081d41db3561ba247531718e5cef77 Mon Sep 17 00:00:00 2001 From: Tero Parviainen Date: Tue, 19 Jan 2016 17:59:27 +0200 Subject: [PATCH 01/18] chore(bower): update to Angular 1.5 --- bower.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index ced73f6da..fe86410eb 100644 --- a/bower.json +++ b/bower.json @@ -6,8 +6,8 @@ "license": "MIT", "private": true, "dependencies": { - "angular": "1.4.x", - "angular-mocks": "1.4.x", + "angular": "1.5.x", + "angular-mocks": "1.5.x", "jquery": "~2.1.1", "bootstrap": "~3.1.1" } From c1ab610ca9bc59619767ea9af2017b715b223663 Mon Sep 17 00:00:00 2001 From: Tero Parviainen Date: Tue, 19 Jan 2016 18:10:33 +0200 Subject: [PATCH 02/18] chore(seed) replace controllers.js placeholder file with components.js --- app/js/components.js | 3 +++ app/js/controllers.js | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 app/js/components.js delete mode 100644 app/js/controllers.js diff --git a/app/js/components.js b/app/js/components.js new file mode 100644 index 000000000..ccf12c321 --- /dev/null +++ b/app/js/components.js @@ -0,0 +1,3 @@ +'use strict'; + +/* Components */ diff --git a/app/js/controllers.js b/app/js/controllers.js deleted file mode 100644 index d314a3331..000000000 --- a/app/js/controllers.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -/* Controllers */ From afe51d06959d00bf4d1ba324c01101d0ec89ade8 Mon Sep 17 00:00:00 2001 From: Tero Parviainen Date: Sun, 24 Jan 2016 13:18:45 +0200 Subject: [PATCH 03/18] chore(package) upgrade Karma to 0.13 and Jasmine to 2.4 --- package.json | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index d0339488f..4455b4542 100644 --- a/package.json +++ b/package.json @@ -6,32 +6,28 @@ "repository": "https://github.com/angular/angular-phonecat", "license": "MIT", "devDependencies": { - "karma": "^0.12.16", - "karma-chrome-launcher": "^0.1.4", - "karma-firefox-launcher": "^0.1.3", - "karma-jasmine": "~0.1.0", - "protractor": "^2.1.0", - "http-server": "^0.6.1", - "tmp": "0.0.23", "bower": "^1.3.1", - "shelljs": "^0.2.6" + "http-server": "^0.6.1", + "jasmine-core": "^2.4.1", + "karma": "^0.13.19", + "karma-chrome-launcher": "^0.2.2", + "karma-firefox-launcher": "^0.1.7", + "karma-jasmine": "^0.3.6", + "protractor": "^2.1.0", + "shelljs": "^0.2.6", + "tmp": "0.0.23" }, "scripts": { "postinstall": "bower install", - "prestart": "npm install", "start": "http-server -a 0.0.0.0 -p 8000", - "pretest": "npm install", "test": "node node_modules/karma/bin/karma start test/karma.conf.js", "test-single-run": "node node_modules/karma/bin/karma start test/karma.conf.js --single-run", - "preupdate-webdriver": "npm install", "update-webdriver": "webdriver-manager update", - "preprotractor": "npm run update-webdriver", "protractor": "protractor test/protractor-conf.js", - "update-index-async": "node -e \"require('shelljs/global'); sed('-i', /\\/\\/@@NG_LOADER_START@@[\\s\\S]*\\/\\/@@NG_LOADER_END@@/, '//@@NG_LOADER_START@@\\n' + cat('bower_components/angular-loader/angular-loader.min.js') + '\\n//@@NG_LOADER_END@@', 'app/index-async.html');\"" } } From 6caa0ce3020f0e9400e1dab355fff9a50bbbc094 Mon Sep 17 00:00:00 2001 From: Tero Parviainen Date: Sun, 24 Jan 2016 15:08:41 +0200 Subject: [PATCH 04/18] chore(scripts) increase step count to 13 --- README.md | 8 ++++++++ scripts/private/test-all.sh | 4 ++-- scripts/private/update-gh-pages.sh | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5d0d274dc..8ab0bf497 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,14 @@ large version of the thumbnail image. - Animate changes to the phone list, adding, removing and reordering phones. - Animate changes to the main phone image in the detail view. +### step-13 + +- Organize the code to comply with the [Angular Style Guide](https://github.com/johnpapa/angular-styleguide): + - Use modules by feature structure. + - Split files so that there is one application component per file. + - Use `feature.type.js` file naming scheme. + + ## Development with angular-phonecat diff --git a/scripts/private/test-all.sh b/scripts/private/test-all.sh index ea310df07..e330f4751 100755 --- a/scripts/private/test-all.sh +++ b/scripts/private/test-all.sh @@ -25,11 +25,11 @@ node_modules/.bin/http-server -p 8000 & WEBSERVER_PID=$! # Run the unit and e2e tests -for i in {0..12} +for i in {0..13} do git checkout -f step-$i node_modules/karma/bin/karma start test/karma.conf.js --single-run node_modules/.bin/protractor test/protractor-conf.js -done \ No newline at end of file +done diff --git a/scripts/private/update-gh-pages.sh b/scripts/private/update-gh-pages.sh index 2129f7440..b035e628b 100755 --- a/scripts/private/update-gh-pages.sh +++ b/scripts/private/update-gh-pages.sh @@ -39,7 +39,7 @@ rm -rf $SNAP_DIR mkdir $SNAP_DIR # Iterate through each step, checking it out and copying it into the temporary snapshot directory -for i in {0..12} +for i in {0..13} do mkdir $SNAP_DIR/step-$i git checkout -f step-$i From e8bc0ff03122bfb5136cf6224c8b3ff8d5856334 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Sat, 18 Oct 2014 14:45:31 +0200 Subject: [PATCH 05/18] 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 5d7de6ee333c48023075f5b8b72f0d2cb2691399 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sat, 18 Oct 2014 14:45:31 +0200 Subject: [PATCH 06/18] 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' + '!'}}

+
    +
  • + Nexus S +

    + Fast just got faster with Nexus S. +

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

    + The Next, Next Generation tablet. +

    +
  • +
From 23f9017186dacdddab43c7b0a7ec4622d81a7ebc Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 24 Jan 2016 15:36:38 +0200 Subject: [PATCH 07/18] step-2 angular template with repeater - Converted the static html list into dynamic one by: - creating phoneList component and 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 | 20 +++----------------- app/js/components.js | 16 ++++++++++++++++ app/partials/phone-list.html | 6 ++++++ test/unit/controllersSpec.js | 12 +++++++++--- 4 files changed, 34 insertions(+), 20 deletions(-) create mode 100644 app/partials/phone-list.html diff --git a/app/index.html b/app/index.html index 7ed5b5c82..c4e3b0301 100644 --- a/app/index.html +++ b/app/index.html @@ -1,28 +1,14 @@ - + Google Phone Gallery + - -
    -
  • - Nexus S -

    - Fast just got faster with Nexus S. -

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

    - The Next, Next Generation tablet. -

    -
  • -
- + diff --git a/app/js/components.js b/app/js/components.js index ccf12c321..3bdacea3b 100644 --- a/app/js/components.js +++ b/app/js/components.js @@ -1,3 +1,19 @@ 'use strict'; /* Components */ + +var phonecatApp = angular.module('phonecatApp', []); + +phonecatApp.component('phoneList', { + controller: 'PhoneListCtrl', + templateUrl: 'partials/phone-list.html' +}).controller('PhoneListCtrl', function() { + this.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/app/partials/phone-list.html b/app/partials/phone-list.html new file mode 100644 index 000000000..c22e755c6 --- /dev/null +++ b/app/partials/phone-list.html @@ -0,0 +1,6 @@ +
    +
  • + {{phone.name}} +

    {{phone.snippet}}

    +
  • +
diff --git a/test/unit/controllersSpec.js b/test/unit/controllersSpec.js index 63d80c3c3..ec67fefcb 100644 --- a/test/unit/controllersSpec.js +++ b/test/unit/controllersSpec.js @@ -1,11 +1,17 @@ '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 ctrl = $controller('PhoneListCtrl'); + + expect(ctrl.phones.length).toBe(3); + })); + }); }); From be21b632070454612f10ac2b14586d10eaae540c Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 24 Jan 2016 15:37:13 +0200 Subject: [PATCH 08/18] 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/partials/phone-list.html | 27 +++++++++++++++++++++------ test/e2e/scenarios.js | 26 ++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/app/partials/phone-list.html b/app/partials/phone-list.html index c22e755c6..b7775508b 100644 --- a/app/partials/phone-list.html +++ b/app/partials/phone-list.html @@ -1,6 +1,21 @@ -
    -
  • - {{phone.name}} -

    {{phone.snippet}}

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

    {{phone.snippet}}

    +
  • +
+ +
+
+
diff --git a/test/e2e/scenarios.js b/test/e2e/scenarios.js index ed4b2c3e7..a2beb1bd0 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 $ctrl.phones')); + var query = element(by.model('$ctrl.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 950ea0e6b1ee35a972c34888ba2b945755a6acb4 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 24 Jan 2016 15:37:35 +0200 Subject: [PATCH 09/18] 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 Closes #213 --- app/js/components.js | 11 ++++++++--- app/partials/phone-list.html | 9 +++++++-- test/e2e/scenarios.js | 27 +++++++++++++++++++++++++++ test/unit/controllersSpec.js | 14 +++++++++++--- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/app/js/components.js b/app/js/components.js index 3bdacea3b..eb570f975 100644 --- a/app/js/components.js +++ b/app/js/components.js @@ -10,10 +10,15 @@ phonecatApp.component('phoneList', { }).controller('PhoneListCtrl', function() { this.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} ]; + + this.orderProp = 'age'; }); diff --git a/app/partials/phone-list.html b/app/partials/phone-list.html index b7775508b..bd9240aa2 100644 --- a/app/partials/phone-list.html +++ b/app/partials/phone-list.html @@ -4,14 +4,19 @@ Search: + Sort by: +
    -
  • - {{phone.name}} +
  • + {{phone.name}}

    {{phone.snippet}}

diff --git a/test/e2e/scenarios.js b/test/e2e/scenarios.js index a2beb1bd0..45da54684 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 $ctrl.phones').column('phone.name')); + var query = element(by.model('$ctrl.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('$ctrl.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 ec67fefcb..5549990ed 100644 --- a/test/unit/controllersSpec.js +++ b/test/unit/controllersSpec.js @@ -4,14 +4,22 @@ describe('PhoneCat controllers', function() { describe('PhoneListCtrl', function(){ + var ctrl; beforeEach(module('phonecatApp')); - it('should create "phones" model with 3 phones', inject(function($controller) { - var ctrl = $controller('PhoneListCtrl'); + beforeEach(inject(function($controller) { + ctrl = $controller('PhoneListCtrl'); + })); + + it('should create "phones" model with 3 phones', function() { expect(ctrl.phones.length).toBe(3); - })); + }); + + it('should set the default value of orderProp model', function() { + expect(ctrl.orderProp).toBe('age'); + }); }); }); From 73caeea89ce446200c1b8e94306254b42bb53ba2 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 24 Jan 2016 15:37:58 +0200 Subject: [PATCH 10/18] 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] (Added inline annotation - thanks to @guatebus - closes #207) --- app/js/components.js | 24 +++++++++--------------- test/e2e/scenarios.js | 4 ++-- test/unit/controllersSpec.js | 15 +++++++++++---- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/app/js/components.js b/app/js/components.js index eb570f975..793a26233 100644 --- a/app/js/components.js +++ b/app/js/components.js @@ -7,18 +7,12 @@ var phonecatApp = angular.module('phonecatApp', []); phonecatApp.component('phoneList', { controller: 'PhoneListCtrl', templateUrl: 'partials/phone-list.html' -}).controller('PhoneListCtrl', function() { - this.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} - ]; - - this.orderProp = 'age'; -}); +}).controller('PhoneListCtrl', ['$http', function ($http) { + var ctrl = this; + + $http.get('phones/phones.json').success(function(data) { + ctrl.phones = data; + }); + + ctrl.orderProp = 'age'; +}]); diff --git a/test/e2e/scenarios.js b/test/e2e/scenarios.js index 45da54684..bf0b63ff6 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 $ctrl.phones')); var query = element(by.model('$ctrl.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 5549990ed..a9f69984c 100644 --- a/test/unit/controllersSpec.js +++ b/test/unit/controllersSpec.js @@ -4,17 +4,24 @@ describe('PhoneCat controllers', function() { describe('PhoneListCtrl', function(){ - var ctrl; + var ctrl, $httpBackend; beforeEach(module('phonecatApp')); + beforeEach(inject(function(_$httpBackend_, $controller) { + $httpBackend = _$httpBackend_; + $httpBackend.expectGET('phones/phones.json'). + respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]); - beforeEach(inject(function($controller) { ctrl = $controller('PhoneListCtrl'); })); - it('should create "phones" model with 3 phones', function() { - expect(ctrl.phones.length).toBe(3); + it('should create "phones" model with 2 phones fetched from xhr', function() { + expect(ctrl.phones).toBeUndefined(); + $httpBackend.flush(); + + expect(ctrl.phones).toEqual([{name: 'Nexus S'}, + {name: 'Motorola DROID'}]); }); From fdcbecd32d3a2202cb9c3380214f469b853567c6 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 24 Jan 2016 15:38:22 +0200 Subject: [PATCH 11/18] step-6 phone images and links - adding phone image and links to phone pages - add end2end test that verifies our phone links - css to style the page just a notch --- app/css/app.css | 17 +++++++++++++++++ app/partials/phone-list.html | 8 ++++++-- test/e2e/scenarios.js | 10 ++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/app/css/app.css b/app/css/app.css index e8d83bcb5..9e156c0d1 100644 --- a/app/css/app.css +++ b/app/css/app.css @@ -4,3 +4,20 @@ body { padding-top: 20px; } +.phones { + list-style: none; +} + +.thumb { + float: left; + margin: -0.5em 1em 1.5em 0; + padding-bottom: 1em; + height: 100px; + width: 100px; +} + +.phones li { + clear: both; + height: 115px; + padding-top: 15px; +} diff --git a/app/partials/phone-list.html b/app/partials/phone-list.html index bd9240aa2..1cc13f4ee 100644 --- a/app/partials/phone-list.html +++ b/app/partials/phone-list.html @@ -15,8 +15,12 @@ diff --git a/test/e2e/scenarios.js b/test/e2e/scenarios.js index bf0b63ff6..2cd777f1a 100644 --- a/test/e2e/scenarios.js +++ b/test/e2e/scenarios.js @@ -52,5 +52,15 @@ describe('PhoneCat App', function() { "Motorola XOOM\u2122 with Wi-Fi" ]); }); + + + it('should render phone specific links', function() { + var query = element(by.model('$ctrl.query')); + query.sendKeys('nexus'); + element(by.css('.phones li a')).click(); + browser.getLocationAbsUrl().then(function(url) { + expect(url).toEqual('/phones/nexus-s'); + }); + }); }); }); From cded1b107924fea711dc13ebcfa9b9ff5563bec8 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 24 Jan 2016 15:38:42 +0200 Subject: [PATCH 12/18] step-7 $route and app partitioning - Introduce the [$route] service which allows binding URLs for deep-linking with views - Load the ngRoute module - Map `/phones' to the phoneList component - Map `/phones/' to the phoneDetail component - Replace content of index.html with [ngView] directive - Create phone list route - Preserve existing phoneList component - Create phone details route - Empty placeholder phoneDetails component --- app/index.html | 6 +++++- app/js/app.js | 19 +++++++++++++++++++ app/js/components.js | 13 +++++++++++-- app/partials/phone-detail.html | 1 + bower.json | 3 ++- test/e2e/scenarios.js | 23 ++++++++++++++++++++++- test/unit/controllersSpec.js | 4 ++++ 7 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 app/partials/phone-detail.html diff --git a/app/index.html b/app/index.html index c4e3b0301..5e14ef684 100644 --- a/app/index.html +++ b/app/index.html @@ -6,9 +6,13 @@ + + - + +
+ diff --git a/app/js/app.js b/app/js/app.js index 7a8f274a0..ed814476f 100644 --- a/app/js/app.js +++ b/app/js/app.js @@ -1,3 +1,22 @@ 'use strict'; /* App Module */ + +var phonecatApp = angular.module('phonecatApp', [ + 'ngRoute', + 'phonecatComponents' +]); + +phonecatApp.config(['$routeProvider', + function($routeProvider) { + $routeProvider. + when('/phones', { + template: '' + }). + when('/phones/:phoneId', { + template: '' + }). + otherwise({ + redirectTo: '/phones' + }); + }]); diff --git a/app/js/components.js b/app/js/components.js index 793a26233..922adea96 100644 --- a/app/js/components.js +++ b/app/js/components.js @@ -2,9 +2,9 @@ /* Components */ -var phonecatApp = angular.module('phonecatApp', []); +var phonecatComponents = angular.module('phonecatComponents', []); -phonecatApp.component('phoneList', { +phonecatComponents.component('phoneList', { controller: 'PhoneListCtrl', templateUrl: 'partials/phone-list.html' }).controller('PhoneListCtrl', ['$http', function ($http) { @@ -16,3 +16,12 @@ phonecatApp.component('phoneList', { ctrl.orderProp = 'age'; }]); + + +phonecatComponents.component('phoneDetail', { + controller: 'PhoneDetailCtrl', + templateUrl: 'partials/phone-detail.html' +}).controller('PhoneDetailCtrl', ['$routeParams', + function($routeParams) { + this.phoneId = $routeParams.phoneId; + }]); diff --git a/app/partials/phone-detail.html b/app/partials/phone-detail.html new file mode 100644 index 000000000..2bfb26ea7 --- /dev/null +++ b/app/partials/phone-detail.html @@ -0,0 +1 @@ +TBD: detail view for {{$ctrl.phoneId}} diff --git a/bower.json b/bower.json index fe86410eb..b11a93fa0 100644 --- a/bower.json +++ b/bower.json @@ -9,6 +9,7 @@ "angular": "1.5.x", "angular-mocks": "1.5.x", "jquery": "~2.1.1", - "bootstrap": "~3.1.1" + "bootstrap": "~3.1.1", + "angular-route": "1.5.x" } } diff --git a/test/e2e/scenarios.js b/test/e2e/scenarios.js index 2cd777f1a..b6c8f92e9 100644 --- a/test/e2e/scenarios.js +++ b/test/e2e/scenarios.js @@ -4,10 +4,18 @@ describe('PhoneCat App', function() { + it('should redirect index.html to index.html#/phones', function() { + browser.get('app/index.html'); + browser.getLocationAbsUrl().then(function(url) { + expect(url).toEqual('/phones'); + }); + }); + + describe('Phone list view', function() { beforeEach(function() { - browser.get('app/index.html'); + browser.get('app/index.html#/phones'); }); @@ -63,4 +71,17 @@ describe('PhoneCat App', function() { }); }); }); + + + describe('Phone detail view', function() { + + beforeEach(function() { + browser.get('app/index.html#/phones/nexus-s'); + }); + + + it('should display placeholder page with phoneId', function() { + expect(element(by.binding('$ctrl.phoneId')).getText()).toBe('nexus-s'); + }); + }); }); diff --git a/test/unit/controllersSpec.js b/test/unit/controllersSpec.js index a9f69984c..2d6ca4ad7 100644 --- a/test/unit/controllersSpec.js +++ b/test/unit/controllersSpec.js @@ -29,4 +29,8 @@ describe('PhoneCat controllers', function() { expect(ctrl.orderProp).toBe('age'); }); }); + + + describe('PhoneDetailCtrl', function(){ + }); }); From 2878d8dc61322787f2a41e2347f48cd0a2b4768e Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 24 Jan 2016 15:38:59 +0200 Subject: [PATCH 13/18] step-8 phone details view - Fetch data for and render phone detail view - PhoneDetailCtrl controller to fetch details json with [$http] for a specific phone - template for the phone detailed view - CSS to make the phone details page look "pretty" --- app/css/app.css | 56 ++++++++++++++++ app/js/components.js | 9 ++- app/partials/phone-detail.html | 114 ++++++++++++++++++++++++++++++++- test/e2e/scenarios.js | 6 +- test/unit/controllersSpec.js | 20 +++++- 5 files changed, 197 insertions(+), 8 deletions(-) diff --git a/app/css/app.css b/app/css/app.css index 9e156c0d1..aff07e85a 100644 --- a/app/css/app.css +++ b/app/css/app.css @@ -21,3 +21,59 @@ body { height: 115px; padding-top: 15px; } + +/** Detail View **/ +img.phone { + float: left; + border: 1px solid black; + margin-right: 3em; + margin-bottom: 2em; + background-color: white; + padding: 2em; + height: 400px; + width: 400px; +} + +ul.phone-thumbs { + margin: 0; + list-style: none; +} + +ul.phone-thumbs li { + border: 1px solid black; + display: inline-block; + margin: 1em; + background-color: white; +} + +ul.phone-thumbs img { + height: 100px; + width: 100px; + padding: 1em; +} + +ul.specs { + clear: both; + margin: 0; + padding: 0; + list-style: none; +} + +ul.specs > li{ + display: inline-block; + width: 200px; + vertical-align: top; +} + +ul.specs > li > span{ + font-weight: bold; + font-size: 1.2em; +} + +ul.specs dt { + font-weight: bold; +} + +h1 { + border-bottom: 1px solid gray; +} diff --git a/app/js/components.js b/app/js/components.js index 922adea96..9d4578db2 100644 --- a/app/js/components.js +++ b/app/js/components.js @@ -21,7 +21,10 @@ phonecatComponents.component('phoneList', { phonecatComponents.component('phoneDetail', { controller: 'PhoneDetailCtrl', templateUrl: 'partials/phone-detail.html' -}).controller('PhoneDetailCtrl', ['$routeParams', - function($routeParams) { - this.phoneId = $routeParams.phoneId; +}).controller('PhoneDetailCtrl', ['$routeParams', '$http', + function($routeParams, $http) { + var ctrl = this; + $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) { + ctrl.phone = data; + }); }]); diff --git a/app/partials/phone-detail.html b/app/partials/phone-detail.html index 2bfb26ea7..b33ab7bb2 100644 --- a/app/partials/phone-detail.html +++ b/app/partials/phone-detail.html @@ -1 +1,113 @@ -TBD: detail view for {{$ctrl.phoneId}} + + +

{{$ctrl.phone.name}}

+ +

{{$ctrl.phone.description}}

+ +
    +
  • + +
  • +
+ +
    +
  • + Availability and Networks +
    +
    Availability
    +
    {{availability}}
    +
    +
  • +
  • + Battery +
    +
    Type
    +
    {{$ctrl.phone.battery.type}}
    +
    Talk Time
    +
    {{$ctrl.phone.battery.talkTime}}
    +
    Standby time (max)
    +
    {{$ctrl.phone.battery.standbyTime}}
    +
    +
  • +
  • + Storage and Memory +
    +
    RAM
    +
    {{$ctrl.phone.storage.ram}}
    +
    Internal Storage
    +
    {{$ctrl.phone.storage.flash}}
    +
    +
  • +
  • + Connectivity +
    +
    Network Support
    +
    {{$ctrl.phone.connectivity.cell}}
    +
    WiFi
    +
    {{$ctrl.phone.connectivity.wifi}}
    +
    Bluetooth
    +
    {{$ctrl.phone.connectivity.bluetooth}}
    +
    Infrared
    +
    {{$ctrl.phone.connectivity.infrared}}
    +
    GPS
    +
    {{$ctrl.phone.connectivity.gps}}
    +
    +
  • +
  • + Android +
    +
    OS Version
    +
    {{$ctrl.phone.android.os}}
    +
    UI
    +
    {{$ctrl.phone.android.ui}}
    +
    +
  • +
  • + Size and Weight +
    +
    Dimensions
    +
    {{dim}}
    +
    Weight
    +
    {{$ctrl.phone.sizeAndWeight.weight}}
    +
    +
  • +
  • + Display +
    +
    Screen size
    +
    {{$ctrl.phone.display.screenSize}}
    +
    Screen resolution
    +
    {{$ctrl.phone.display.screenResolution}}
    +
    Touch screen
    +
    {{$ctrl.phone.display.touchScreen}}
    +
    +
  • +
  • + Hardware +
    +
    CPU
    +
    {{$ctrl.phone.hardware.cpu}}
    +
    USB
    +
    {{$ctrl.phone.hardware.usb}}
    +
    Audio / headphone jack
    +
    {{$ctrl.phone.hardware.audioJack}}
    +
    FM Radio
    +
    {{$ctrl.phone.hardware.fmRadio}}
    +
    Accelerometer
    +
    {{$ctrl.phone.hardware.accelerometer}}
    +
    +
  • +
  • + Camera +
    +
    Primary
    +
    {{$ctrl.phone.camera.primary}}
    +
    Features
    +
    {{$ctrl.phone.camera.features.join(', ')}}
    +
    +
  • +
  • + Additional Features +
    {{$ctrl.phone.additionalFeatures}}
    +
  • +
diff --git a/test/e2e/scenarios.js b/test/e2e/scenarios.js index b6c8f92e9..a39339c39 100644 --- a/test/e2e/scenarios.js +++ b/test/e2e/scenarios.js @@ -65,7 +65,7 @@ describe('PhoneCat App', function() { it('should render phone specific links', function() { var query = element(by.model('$ctrl.query')); query.sendKeys('nexus'); - element(by.css('.phones li a')).click(); + element.all(by.css('.phones li a')).first().click(); browser.getLocationAbsUrl().then(function(url) { expect(url).toEqual('/phones/nexus-s'); }); @@ -80,8 +80,8 @@ describe('PhoneCat App', function() { }); - it('should display placeholder page with phoneId', function() { - expect(element(by.binding('$ctrl.phoneId')).getText()).toBe('nexus-s'); + it('should display nexus-s page', function() { + expect(element(by.binding('$ctrl.phone.name')).getText()).toBe('Nexus S'); }); }); }); diff --git a/test/unit/controllersSpec.js b/test/unit/controllersSpec.js index 2d6ca4ad7..384b8a982 100644 --- a/test/unit/controllersSpec.js +++ b/test/unit/controllersSpec.js @@ -3,10 +3,11 @@ /* jasmine specs for controllers go here */ describe('PhoneCat controllers', function() { + beforeEach(module('phonecatApp')); + describe('PhoneListCtrl', function(){ var ctrl, $httpBackend; - beforeEach(module('phonecatApp')); beforeEach(inject(function(_$httpBackend_, $controller) { $httpBackend = _$httpBackend_; $httpBackend.expectGET('phones/phones.json'). @@ -32,5 +33,22 @@ describe('PhoneCat controllers', function() { describe('PhoneDetailCtrl', function(){ + var $httpBackend, ctrl; + + beforeEach(inject(function(_$httpBackend_, $routeParams, $controller) { + $httpBackend = _$httpBackend_; + $httpBackend.expectGET('phones/xyz.json').respond({name:'phone xyz'}); + + $routeParams.phoneId = 'xyz'; + ctrl = $controller('PhoneDetailCtrl'); + })); + + + it('should fetch phone detail', function() { + expect(ctrl.phone).toBeUndefined(); + $httpBackend.flush(); + + expect(ctrl.phone).toEqual({name:'phone xyz'}); + }); }); }); From a8adbf13a7fa5dad53ebe756e6df3bffc8e326e8 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Sun, 24 Jan 2016 15:39:17 +0200 Subject: [PATCH 14/18] step-9 checkmark filter - Added custom checkmark filter - Update phone detail template to use checkmark filter - Added spec for the filter --- app/index.html | 1 + app/js/app.js | 3 ++- app/js/filters.js | 6 ++++++ app/partials/phone-detail.html | 10 +++++----- test/unit/filtersSpec.js | 11 +++++++++++ 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/app/index.html b/app/index.html index 5e14ef684..cefca7b0b 100644 --- a/app/index.html +++ b/app/index.html @@ -9,6 +9,7 @@ + diff --git a/app/js/app.js b/app/js/app.js index ed814476f..bcafbe578 100644 --- a/app/js/app.js +++ b/app/js/app.js @@ -4,7 +4,8 @@ var phonecatApp = angular.module('phonecatApp', [ 'ngRoute', - 'phonecatComponents' + 'phonecatComponents', + 'phonecatFilters' ]); phonecatApp.config(['$routeProvider', diff --git a/app/js/filters.js b/app/js/filters.js index 85e8440f8..4f62309ba 100644 --- a/app/js/filters.js +++ b/app/js/filters.js @@ -1,3 +1,9 @@ 'use strict'; /* Filters */ + +angular.module('phonecatFilters', []).filter('checkmark', function() { + return function(input) { + return input ? '\u2713' : '\u2718'; + }; +}); diff --git a/app/partials/phone-detail.html b/app/partials/phone-detail.html index b33ab7bb2..41eca2f8d 100644 --- a/app/partials/phone-detail.html +++ b/app/partials/phone-detail.html @@ -48,9 +48,9 @@

{{$ctrl.phone.name}}

Bluetooth
{{$ctrl.phone.connectivity.bluetooth}}
Infrared
-
{{$ctrl.phone.connectivity.infrared}}
+
{{$ctrl.phone.connectivity.infrared | checkmark}}
GPS
-
{{$ctrl.phone.connectivity.gps}}
+
{{$ctrl.phone.connectivity.gps | checkmark}}
  • @@ -79,7 +79,7 @@

    {{$ctrl.phone.name}}

    Screen resolution
    {{$ctrl.phone.display.screenResolution}}
    Touch screen
    -
    {{$ctrl.phone.display.touchScreen}}
    +
    {{$ctrl.phone.display.touchScreen | checkmark}}
  • @@ -92,9 +92,9 @@

    {{$ctrl.phone.name}}

    Audio / headphone jack
    {{$ctrl.phone.hardware.audioJack}}
    FM Radio
    -
    {{$ctrl.phone.hardware.fmRadio}}
    +
    {{$ctrl.phone.hardware.fmRadio | checkmark}}
    Accelerometer
    -
    {{$ctrl.phone.hardware.accelerometer}}
    +
    {{$ctrl.phone.hardware.accelerometer | checkmark}}
  • diff --git a/test/unit/filtersSpec.js b/test/unit/filtersSpec.js index 5fdc76a26..e5cbb7262 100644 --- a/test/unit/filtersSpec.js +++ b/test/unit/filtersSpec.js @@ -4,4 +4,15 @@ describe('filter', function() { + beforeEach(module('phonecatFilters')); + + + describe('checkmark', function() { + + it('should convert boolean values to unicode checkmark or cross', + inject(function(checkmarkFilter) { + expect(checkmarkFilter(true)).toBe('\u2713'); + expect(checkmarkFilter(false)).toBe('\u2718'); + })); + }); }); From d01fad8a7ed4cd77de09f8c5453ed673395230ca Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Sun, 24 Jan 2016 15:39:39 +0200 Subject: [PATCH 15/18] step-10 image swapping with ng:click In the phone detail view, clicking on a thumbnail image, changes the main phone image to be the large version of the thumbnail image. - Define mainImageUrl model variable in the PhoneDetailCtrl and set its default value - Create setImage controller method to change mainImageUrl - Register ng:click handler for thumb images to use setImage controller method - Add e2e tests for this feature - Add css to change the mouse cursor when user points at thumnail images --- app/css/app.css | 5 +++++ app/js/components.js | 5 +++++ app/partials/phone-detail.html | 4 ++-- test/e2e/scenarios.js | 14 ++++++++++++++ test/unit/controllersSpec.js | 12 +++++++++--- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/app/css/app.css b/app/css/app.css index aff07e85a..78bab744d 100644 --- a/app/css/app.css +++ b/app/css/app.css @@ -52,6 +52,11 @@ ul.phone-thumbs img { padding: 1em; } +ul.phone-thumbs img:hover { + cursor: pointer; +} + + ul.specs { clear: both; margin: 0; diff --git a/app/js/components.js b/app/js/components.js index 9d4578db2..62305ef39 100644 --- a/app/js/components.js +++ b/app/js/components.js @@ -26,5 +26,10 @@ phonecatComponents.component('phoneDetail', { var ctrl = this; $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) { ctrl.phone = data; + ctrl.mainImageUrl = data.images[0]; }); + + ctrl.setImage = function(imageUrl) { + ctrl.mainImageUrl = imageUrl; + }; }]); diff --git a/app/partials/phone-detail.html b/app/partials/phone-detail.html index 41eca2f8d..bf8d0665e 100644 --- a/app/partials/phone-detail.html +++ b/app/partials/phone-detail.html @@ -1,4 +1,4 @@ - +

    {{$ctrl.phone.name}}

    @@ -6,7 +6,7 @@

    {{$ctrl.phone.name}}

    • - +
    diff --git a/test/e2e/scenarios.js b/test/e2e/scenarios.js index a39339c39..0f4afde36 100644 --- a/test/e2e/scenarios.js +++ b/test/e2e/scenarios.js @@ -83,5 +83,19 @@ describe('PhoneCat App', function() { it('should display nexus-s page', function() { expect(element(by.binding('$ctrl.phone.name')).getText()).toBe('Nexus S'); }); + + + it('should display the first phone image as the main phone image', function() { + expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); + }); + + + it('should swap main image if a thumbnail image is clicked on', function() { + element(by.css('.phone-thumbs li:nth-child(3) img')).click(); + expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/); + + element(by.css('.phone-thumbs li:nth-child(1) img')).click(); + expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); + }); }); }); diff --git a/test/unit/controllersSpec.js b/test/unit/controllersSpec.js index 384b8a982..b940deec5 100644 --- a/test/unit/controllersSpec.js +++ b/test/unit/controllersSpec.js @@ -33,11 +33,17 @@ describe('PhoneCat controllers', function() { describe('PhoneDetailCtrl', function(){ - var $httpBackend, ctrl; + var $httpBackend, ctrl, + xyzPhoneData = function() { + return { + name: 'phone xyz', + images: ['image/url1.png', 'image/url2.png'] + } + }; beforeEach(inject(function(_$httpBackend_, $routeParams, $controller) { $httpBackend = _$httpBackend_; - $httpBackend.expectGET('phones/xyz.json').respond({name:'phone xyz'}); + $httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData()); $routeParams.phoneId = 'xyz'; ctrl = $controller('PhoneDetailCtrl'); @@ -48,7 +54,7 @@ describe('PhoneCat controllers', function() { expect(ctrl.phone).toBeUndefined(); $httpBackend.flush(); - expect(ctrl.phone).toEqual({name:'phone xyz'}); + expect(ctrl.phone).toEqual(xyzPhoneData()); }); }); }); From 8b3912131c9881e68470eb168492fc6f7298a0fb Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 24 Jan 2016 15:39:58 +0200 Subject: [PATCH 16/18] step-11 custom service and $resource - Replaced [$http] with [$resource] - Created a custom Phone service that represents the $resource client --- app/index.html | 2 ++ app/js/app.js | 3 ++- app/js/components.js | 20 +++++++------------- app/js/services.js | 8 ++++++++ bower.json | 3 ++- test/karma.conf.js | 1 + test/unit/controllersSpec.js | 12 ++++++++---- test/unit/servicesSpec.js | 11 ++++++++--- 8 files changed, 38 insertions(+), 22 deletions(-) diff --git a/app/index.html b/app/index.html index cefca7b0b..7103d9537 100644 --- a/app/index.html +++ b/app/index.html @@ -7,9 +7,11 @@ + + diff --git a/app/js/app.js b/app/js/app.js index bcafbe578..424eb23b4 100644 --- a/app/js/app.js +++ b/app/js/app.js @@ -5,7 +5,8 @@ var phonecatApp = angular.module('phonecatApp', [ 'ngRoute', 'phonecatComponents', - 'phonecatFilters' + 'phonecatFilters', + 'phonecatServices' ]); phonecatApp.config(['$routeProvider', diff --git a/app/js/components.js b/app/js/components.js index 62305ef39..fdc8142df 100644 --- a/app/js/components.js +++ b/app/js/components.js @@ -7,26 +7,20 @@ var phonecatComponents = angular.module('phonecatComponents', []); phonecatComponents.component('phoneList', { controller: 'PhoneListCtrl', templateUrl: 'partials/phone-list.html' -}).controller('PhoneListCtrl', ['$http', function ($http) { - var ctrl = this; - - $http.get('phones/phones.json').success(function(data) { - ctrl.phones = data; - }); - - ctrl.orderProp = 'age'; +}).controller('PhoneListCtrl', ['Phone', function (Phone) { + this.phones = Phone.query(); + this.orderProp = 'age'; }]); phonecatComponents.component('phoneDetail', { controller: 'PhoneDetailCtrl', templateUrl: 'partials/phone-detail.html' -}).controller('PhoneDetailCtrl', ['$routeParams', '$http', - function($routeParams, $http) { +}).controller('PhoneDetailCtrl', ['$routeParams', 'Phone', + function($routeParams, Phone) { var ctrl = this; - $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) { - ctrl.phone = data; - ctrl.mainImageUrl = data.images[0]; + ctrl.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) { + ctrl.mainImageUrl = phone.images[0]; }); ctrl.setImage = function(imageUrl) { diff --git a/app/js/services.js b/app/js/services.js index 8207480df..e0b81a8ac 100644 --- a/app/js/services.js +++ b/app/js/services.js @@ -2,3 +2,11 @@ /* Services */ +var phonecatServices = angular.module('phonecatServices', ['ngResource']); + +phonecatServices.factory('Phone', ['$resource', + function($resource){ + return $resource('phones/:phoneId.json', {}, { + query: {method:'GET', params:{phoneId:'phones'}, isArray:true} + }); + }]); diff --git a/bower.json b/bower.json index b11a93fa0..7057afe77 100644 --- a/bower.json +++ b/bower.json @@ -10,6 +10,7 @@ "angular-mocks": "1.5.x", "jquery": "~2.1.1", "bootstrap": "~3.1.1", - "angular-route": "1.5.x" + "angular-route": "1.5.x", + "angular-resource": "1.5.x" } } diff --git a/test/karma.conf.js b/test/karma.conf.js index ab92fa9fb..b6939eeea 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -6,6 +6,7 @@ module.exports = function(config){ files : [ 'app/bower_components/angular/angular.js', 'app/bower_components/angular-route/angular-route.js', + 'app/bower_components/angular-resource/angular-resource.js', 'app/bower_components/angular-mocks/angular-mocks.js', 'app/js/**/*.js', 'test/unit/**/*.js' diff --git a/test/unit/controllersSpec.js b/test/unit/controllersSpec.js index b940deec5..23d1f608e 100644 --- a/test/unit/controllersSpec.js +++ b/test/unit/controllersSpec.js @@ -3,6 +3,10 @@ /* jasmine specs for controllers go here */ describe('PhoneCat controllers', function() { + beforeEach(function(){ + jasmine.addCustomEqualityTester(angular.equals); + }); + beforeEach(module('phonecatApp')); describe('PhoneListCtrl', function(){ @@ -18,11 +22,11 @@ describe('PhoneCat controllers', function() { it('should create "phones" model with 2 phones fetched from xhr', function() { - expect(ctrl.phones).toBeUndefined(); + expect(ctrl.phones).toEqual([]); $httpBackend.flush(); - expect(ctrl.phones).toEqual([{name: 'Nexus S'}, - {name: 'Motorola DROID'}]); + expect(ctrl.phones).toEqual( + [{name: 'Nexus S'}, {name: 'Motorola DROID'}]); }); @@ -51,7 +55,7 @@ describe('PhoneCat controllers', function() { it('should fetch phone detail', function() { - expect(ctrl.phone).toBeUndefined(); + expect(ctrl.phone).toEqual({}); $httpBackend.flush(); expect(ctrl.phone).toEqual(xyzPhoneData()); diff --git a/test/unit/servicesSpec.js b/test/unit/servicesSpec.js index db8a232de..b495870cb 100644 --- a/test/unit/servicesSpec.js +++ b/test/unit/servicesSpec.js @@ -1,7 +1,12 @@ 'use strict'; -/* jasmine specs for services go here */ - describe('service', function() { -}); + // load modules + beforeEach(module('phonecatApp')); + + // Test service availability + it('check the existence of Phone factory', inject(function(Phone) { + expect(Phone).toBeDefined(); + })); +}); \ No newline at end of file From fef6553ace63591ac71796996e3039fac375c7f4 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 24 Jan 2016 15:40:14 +0200 Subject: [PATCH 17/18] step-12 animations --- app/css/animations.css | 97 ++++++++++++++++++++++++++++++++++ app/css/app.css | 17 +++++- app/index.html | 9 +++- app/js/animations.js | 52 ++++++++++++++++++ app/js/app.js | 1 + app/partials/phone-detail.html | 7 ++- app/partials/phone-list.html | 2 +- bower.json | 3 +- test/e2e/scenarios.js | 6 +-- test/karma.conf.js | 1 + 10 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 app/css/animations.css create mode 100644 app/js/animations.js diff --git a/app/css/animations.css b/app/css/animations.css new file mode 100644 index 000000000..46f3da6ec --- /dev/null +++ b/app/css/animations.css @@ -0,0 +1,97 @@ +/* + * animations css stylesheet + */ + +/* animate ngRepeat in phone listing */ + +.phone-listing.ng-enter, +.phone-listing.ng-leave, +.phone-listing.ng-move { + -webkit-transition: 0.5s linear all; + -moz-transition: 0.5s linear all; + -o-transition: 0.5s linear all; + transition: 0.5s linear all; +} + +.phone-listing.ng-enter, +.phone-listing.ng-move { + opacity: 0; + height: 0; + overflow: hidden; +} + +.phone-listing.ng-move.ng-move-active, +.phone-listing.ng-enter.ng-enter-active { + opacity: 1; + height: 120px; +} + +.phone-listing.ng-leave { + opacity: 1; + overflow: hidden; +} + +.phone-listing.ng-leave.ng-leave-active { + opacity: 0; + height: 0; + padding-top: 0; + padding-bottom: 0; +} + +/* cross fading between routes with ngView */ + +.view-container { + position: relative; +} + +.view-frame.ng-enter, +.view-frame.ng-leave { + background: white; + position: absolute; + top: 0; + left: 0; + right: 0; +} + +.view-frame.ng-enter { + -webkit-animation: 0.5s fade-in; + -moz-animation: 0.5s fade-in; + -o-animation: 0.5s fade-in; + animation: 0.5s fade-in; + z-index: 100; +} + +.view-frame.ng-leave { + -webkit-animation: 0.5s fade-out; + -moz-animation: 0.5s fade-out; + -o-animation: 0.5s fade-out; + animation: 0.5s fade-out; + z-index: 99; +} + +@keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } +} +@-moz-keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } +} +@-webkit-keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } +} +@-moz-keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } +} +@-webkit-keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } +} + diff --git a/app/css/app.css b/app/css/app.css index 78bab744d..951ea087c 100644 --- a/app/css/app.css +++ b/app/css/app.css @@ -4,6 +4,16 @@ body { padding-top: 20px; } + +.phone-images { + background-color: white; + width: 450px; + height: 450px; + overflow: hidden; + position: relative; + float: left; +} + .phones { list-style: none; } @@ -25,15 +35,20 @@ body { /** Detail View **/ img.phone { float: left; - border: 1px solid black; margin-right: 3em; margin-bottom: 2em; background-color: white; padding: 2em; height: 400px; width: 400px; + display: none; +} + +img.phone:first-child { + display: block; } + ul.phone-thumbs { margin: 0; list-style: none; diff --git a/app/index.html b/app/index.html index 7103d9537..dca80fcbc 100644 --- a/app/index.html +++ b/app/index.html @@ -5,17 +5,24 @@ Google Phone Gallery + + + + + -
    +
    +
    +
    diff --git a/app/js/animations.js b/app/js/animations.js new file mode 100644 index 000000000..8f3404265 --- /dev/null +++ b/app/js/animations.js @@ -0,0 +1,52 @@ +var phonecatAnimations = angular.module('phonecatAnimations', ['ngAnimate']); + +phonecatAnimations.animation('.phone', function() { + + var animateUp = function(element, className, done) { + if(className != 'active') { + return; + } + element.css({ + position: 'absolute', + top: 500, + left: 0, + display: 'block' + }); + + jQuery(element).animate({ + top: 0 + }, done); + + return function(cancel) { + if(cancel) { + element.stop(); + } + }; + } + + var animateDown = function(element, className, done) { + if(className != 'active') { + return; + } + element.css({ + position: 'absolute', + left: 0, + top: 0 + }); + + jQuery(element).animate({ + top: -500 + }, done); + + return function(cancel) { + if(cancel) { + element.stop(); + } + }; + } + + return { + addClass: animateUp, + removeClass: animateDown + }; +}); diff --git a/app/js/app.js b/app/js/app.js index 424eb23b4..1cf790f53 100644 --- a/app/js/app.js +++ b/app/js/app.js @@ -4,6 +4,7 @@ var phonecatApp = angular.module('phonecatApp', [ 'ngRoute', + 'phonecatAnimations', 'phonecatComponents', 'phonecatFilters', 'phonecatServices' diff --git a/app/partials/phone-detail.html b/app/partials/phone-detail.html index bf8d0665e..019ccf45c 100644 --- a/app/partials/phone-detail.html +++ b/app/partials/phone-detail.html @@ -1,4 +1,9 @@ - +
    + +

    {{$ctrl.phone.name}}

    diff --git a/app/partials/phone-list.html b/app/partials/phone-list.html index 1cc13f4ee..6721af7e8 100644 --- a/app/partials/phone-list.html +++ b/app/partials/phone-list.html @@ -16,7 +16,7 @@
    • + class="thumbnail phone-listing"> {{phone.name}} diff --git a/bower.json b/bower.json index 7057afe77..ecc9afe46 100644 --- a/bower.json +++ b/bower.json @@ -11,6 +11,7 @@ "jquery": "~2.1.1", "bootstrap": "~3.1.1", "angular-route": "1.5.x", - "angular-resource": "1.5.x" + "angular-resource": "1.5.x", + "angular-animate": "1.5.x" } } diff --git a/test/e2e/scenarios.js b/test/e2e/scenarios.js index 0f4afde36..3bcd5db1e 100644 --- a/test/e2e/scenarios.js +++ b/test/e2e/scenarios.js @@ -86,16 +86,16 @@ describe('PhoneCat App', function() { it('should display the first phone image as the main phone image', function() { - expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); + expect(element(by.css('img.phone.active')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); }); it('should swap main image if a thumbnail image is clicked on', function() { element(by.css('.phone-thumbs li:nth-child(3) img')).click(); - expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/); + expect(element(by.css('img.phone.active')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/); element(by.css('.phone-thumbs li:nth-child(1) img')).click(); - expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); + expect(element(by.css('img.phone.active')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); }); }); }); diff --git a/test/karma.conf.js b/test/karma.conf.js index b6939eeea..16c43543a 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -7,6 +7,7 @@ module.exports = function(config){ 'app/bower_components/angular/angular.js', 'app/bower_components/angular-route/angular-route.js', 'app/bower_components/angular-resource/angular-resource.js', + 'app/bower_components/angular-animate/angular-animate.js', 'app/bower_components/angular-mocks/angular-mocks.js', 'app/js/**/*.js', 'test/unit/**/*.js' From e285fbbe14d96c80196b4504f35cdad7b737aa0b Mon Sep 17 00:00:00 2001 From: Tero Parviainen Date: Sun, 24 Jan 2016 14:50:44 +0200 Subject: [PATCH 18/18] step-13 style guide and feature folders - Organized code into modules by feature structure - Split code so that one component is introduced in each file - Switched to feature.type.js file naming scheme --- app/index.html | 16 +++-- app/js/{app.js => app.module.js} | 7 +- app/js/components.js | 29 --------- .../{filters.js => core/checkmark.filter.js} | 4 +- app/js/core/core.module.js | 3 + app/js/core/phone.factory.js | 9 +++ app/js/directives.js | 3 - .../phone.animation.js} | 4 +- app/js/phone_detail/phone_detail.component.js | 6 ++ .../phone_detail/phone_detail.controller.js | 13 ++++ .../phone_detail/phone_detail.html} | 0 app/js/phone_detail/phone_detail.module.js | 7 ++ app/js/phone_list/phone_list.component.js | 7 ++ app/js/phone_list/phone_list.controller.js | 7 ++ .../phone_list/phone_list.html} | 0 app/js/phone_list/phone_list.module.js | 3 + app/js/services.js | 12 ---- app/partials/.gitkeep | 0 test/karma.conf.js | 5 +- test/unit/checkmark.filter.spec.js | 12 ++++ test/unit/controllersSpec.js | 64 ------------------- test/unit/directivesSpec.js | 7 -- test/unit/filtersSpec.js | 18 ------ ...{servicesSpec.js => phone.factory.spec.js} | 6 +- test/unit/phone_detail.controller.spec.js | 34 ++++++++++ test/unit/phone_list.controller.spec.js | 33 ++++++++++ 26 files changed, 157 insertions(+), 152 deletions(-) rename app/js/{app.js => app.module.js} (82%) delete mode 100644 app/js/components.js rename app/js/{filters.js => core/checkmark.filter.js} (51%) create mode 100644 app/js/core/core.module.js create mode 100644 app/js/core/phone.factory.js delete mode 100644 app/js/directives.js rename app/js/{animations.js => phone_detail/phone.animation.js} (85%) create mode 100644 app/js/phone_detail/phone_detail.component.js create mode 100644 app/js/phone_detail/phone_detail.controller.js rename app/{partials/phone-detail.html => js/phone_detail/phone_detail.html} (100%) create mode 100644 app/js/phone_detail/phone_detail.module.js create mode 100644 app/js/phone_list/phone_list.component.js create mode 100644 app/js/phone_list/phone_list.controller.js rename app/{partials/phone-list.html => js/phone_list/phone_list.html} (100%) create mode 100644 app/js/phone_list/phone_list.module.js delete mode 100644 app/js/services.js delete mode 100644 app/partials/.gitkeep create mode 100644 test/unit/checkmark.filter.spec.js delete mode 100644 test/unit/controllersSpec.js delete mode 100644 test/unit/directivesSpec.js delete mode 100644 test/unit/filtersSpec.js rename test/unit/{servicesSpec.js => phone.factory.spec.js} (70%) create mode 100644 test/unit/phone_detail.controller.spec.js create mode 100644 test/unit/phone_list.controller.spec.js diff --git a/app/index.html b/app/index.html index dca80fcbc..288b4660c 100644 --- a/app/index.html +++ b/app/index.html @@ -12,11 +12,17 @@ - - - - - + + + + + + + + + + + diff --git a/app/js/app.js b/app/js/app.module.js similarity index 82% rename from app/js/app.js rename to app/js/app.module.js index 1cf790f53..32d0e0c25 100644 --- a/app/js/app.js +++ b/app/js/app.module.js @@ -4,10 +4,9 @@ var phonecatApp = angular.module('phonecatApp', [ 'ngRoute', - 'phonecatAnimations', - 'phonecatComponents', - 'phonecatFilters', - 'phonecatServices' + 'phonecat.core', + 'phonecat.phoneList', + 'phonecat.phoneDetail' ]); phonecatApp.config(['$routeProvider', diff --git a/app/js/components.js b/app/js/components.js deleted file mode 100644 index fdc8142df..000000000 --- a/app/js/components.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -/* Components */ - -var phonecatComponents = angular.module('phonecatComponents', []); - -phonecatComponents.component('phoneList', { - controller: 'PhoneListCtrl', - templateUrl: 'partials/phone-list.html' -}).controller('PhoneListCtrl', ['Phone', function (Phone) { - this.phones = Phone.query(); - this.orderProp = 'age'; -}]); - - -phonecatComponents.component('phoneDetail', { - controller: 'PhoneDetailCtrl', - templateUrl: 'partials/phone-detail.html' -}).controller('PhoneDetailCtrl', ['$routeParams', 'Phone', - function($routeParams, Phone) { - var ctrl = this; - ctrl.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) { - ctrl.mainImageUrl = phone.images[0]; - }); - - ctrl.setImage = function(imageUrl) { - ctrl.mainImageUrl = imageUrl; - }; - }]); diff --git a/app/js/filters.js b/app/js/core/checkmark.filter.js similarity index 51% rename from app/js/filters.js rename to app/js/core/checkmark.filter.js index 4f62309ba..f3a0bed9c 100644 --- a/app/js/filters.js +++ b/app/js/core/checkmark.filter.js @@ -1,8 +1,6 @@ 'use strict'; -/* Filters */ - -angular.module('phonecatFilters', []).filter('checkmark', function() { +angular.module('phonecat.core').filter('checkmark', function() { return function(input) { return input ? '\u2713' : '\u2718'; }; diff --git a/app/js/core/core.module.js b/app/js/core/core.module.js new file mode 100644 index 000000000..ed112a1cd --- /dev/null +++ b/app/js/core/core.module.js @@ -0,0 +1,3 @@ +'use strict'; + +angular.module('phonecat.core', ['ngResource']); diff --git a/app/js/core/phone.factory.js b/app/js/core/phone.factory.js new file mode 100644 index 000000000..5c9f79f21 --- /dev/null +++ b/app/js/core/phone.factory.js @@ -0,0 +1,9 @@ +'use strict'; + +angular.module('phonecat.core') + .factory('Phone', ['$resource', + function($resource) { + return $resource('phones/:phoneId.json', {}, { + query: {method:'GET', params:{phoneId:'phones'}, isArray:true} + }); + }]); diff --git a/app/js/directives.js b/app/js/directives.js deleted file mode 100644 index ebc5dd144..000000000 --- a/app/js/directives.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -/* Directives */ diff --git a/app/js/animations.js b/app/js/phone_detail/phone.animation.js similarity index 85% rename from app/js/animations.js rename to app/js/phone_detail/phone.animation.js index 8f3404265..871ce3684 100644 --- a/app/js/animations.js +++ b/app/js/phone_detail/phone.animation.js @@ -1,6 +1,6 @@ -var phonecatAnimations = angular.module('phonecatAnimations', ['ngAnimate']); +'use strict'; -phonecatAnimations.animation('.phone', function() { +angular.module('phonecat.phoneDetail').animation('.phone', function() { var animateUp = function(element, className, done) { if(className != 'active') { diff --git a/app/js/phone_detail/phone_detail.component.js b/app/js/phone_detail/phone_detail.component.js new file mode 100644 index 000000000..79155a230 --- /dev/null +++ b/app/js/phone_detail/phone_detail.component.js @@ -0,0 +1,6 @@ +'use strict'; + +angular.module('phonecat.phoneDetail').component('phoneDetail', { + controller: 'PhoneDetailCtrl', + templateUrl: 'js/phone_detail/phone_detail.html' + }); diff --git a/app/js/phone_detail/phone_detail.controller.js b/app/js/phone_detail/phone_detail.controller.js new file mode 100644 index 000000000..353e308f9 --- /dev/null +++ b/app/js/phone_detail/phone_detail.controller.js @@ -0,0 +1,13 @@ +'use strict'; + +angular.module('phonecat.phoneDetail').controller('PhoneDetailCtrl', + ['$routeParams', 'Phone', function($routeParams, Phone) { + var ctrl = this; + ctrl.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) { + ctrl.mainImageUrl = phone.images[0]; + }); + + ctrl.setImage = function(imageUrl) { + ctrl.mainImageUrl = imageUrl; + }; + }]); diff --git a/app/partials/phone-detail.html b/app/js/phone_detail/phone_detail.html similarity index 100% rename from app/partials/phone-detail.html rename to app/js/phone_detail/phone_detail.html diff --git a/app/js/phone_detail/phone_detail.module.js b/app/js/phone_detail/phone_detail.module.js new file mode 100644 index 000000000..dc9c45b94 --- /dev/null +++ b/app/js/phone_detail/phone_detail.module.js @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('phonecat.phoneDetail', [ + 'ngAnimate', + 'ngRoute', + 'phonecat.core' +]); diff --git a/app/js/phone_list/phone_list.component.js b/app/js/phone_list/phone_list.component.js new file mode 100644 index 000000000..e15b8f9da --- /dev/null +++ b/app/js/phone_list/phone_list.component.js @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('phonecat.phoneList') + .component('phoneList', { + controller: 'PhoneListCtrl', + templateUrl: 'js/phone_list/phone_list.html' + }); diff --git a/app/js/phone_list/phone_list.controller.js b/app/js/phone_list/phone_list.controller.js new file mode 100644 index 000000000..3ca819ef2 --- /dev/null +++ b/app/js/phone_list/phone_list.controller.js @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('phonecat.phoneList').controller('PhoneListCtrl', + ['Phone', function (Phone) { + this.phones = Phone.query(); + this.orderProp = 'age'; + }]); diff --git a/app/partials/phone-list.html b/app/js/phone_list/phone_list.html similarity index 100% rename from app/partials/phone-list.html rename to app/js/phone_list/phone_list.html diff --git a/app/js/phone_list/phone_list.module.js b/app/js/phone_list/phone_list.module.js new file mode 100644 index 000000000..23c4f4968 --- /dev/null +++ b/app/js/phone_list/phone_list.module.js @@ -0,0 +1,3 @@ +'use strict'; + +angular.module('phonecat.phoneList', ['phonecat.core']); diff --git a/app/js/services.js b/app/js/services.js deleted file mode 100644 index e0b81a8ac..000000000 --- a/app/js/services.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -/* Services */ - -var phonecatServices = angular.module('phonecatServices', ['ngResource']); - -phonecatServices.factory('Phone', ['$resource', - function($resource){ - return $resource('phones/:phoneId.json', {}, { - query: {method:'GET', params:{phoneId:'phones'}, isArray:true} - }); - }]); diff --git a/app/partials/.gitkeep b/app/partials/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/karma.conf.js b/test/karma.conf.js index 16c43543a..54fe059c0 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -9,7 +9,8 @@ module.exports = function(config){ 'app/bower_components/angular-resource/angular-resource.js', 'app/bower_components/angular-animate/angular-animate.js', 'app/bower_components/angular-mocks/angular-mocks.js', - 'app/js/**/*.js', + 'app/js/**/*.module.js', + 'app/js/**/*.!(module).js', 'test/unit/**/*.js' ], @@ -31,4 +32,4 @@ module.exports = function(config){ } }); -}; \ No newline at end of file +}; diff --git a/test/unit/checkmark.filter.spec.js b/test/unit/checkmark.filter.spec.js new file mode 100644 index 000000000..a9b416d49 --- /dev/null +++ b/test/unit/checkmark.filter.spec.js @@ -0,0 +1,12 @@ +'use strict'; + +describe('checkmarkFilter', function() { + + beforeEach(module('phonecat.core')); + + it('should convert boolean values to unicode checkmark or cross', + inject(function(checkmarkFilter) { + expect(checkmarkFilter(true)).toBe('\u2713'); + expect(checkmarkFilter(false)).toBe('\u2718'); + })); +}); diff --git a/test/unit/controllersSpec.js b/test/unit/controllersSpec.js deleted file mode 100644 index 23d1f608e..000000000 --- a/test/unit/controllersSpec.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -/* jasmine specs for controllers go here */ -describe('PhoneCat controllers', function() { - - beforeEach(function(){ - jasmine.addCustomEqualityTester(angular.equals); - }); - - beforeEach(module('phonecatApp')); - - describe('PhoneListCtrl', function(){ - var ctrl, $httpBackend; - - beforeEach(inject(function(_$httpBackend_, $controller) { - $httpBackend = _$httpBackend_; - $httpBackend.expectGET('phones/phones.json'). - respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]); - - ctrl = $controller('PhoneListCtrl'); - })); - - - it('should create "phones" model with 2 phones fetched from xhr', function() { - expect(ctrl.phones).toEqual([]); - $httpBackend.flush(); - - expect(ctrl.phones).toEqual( - [{name: 'Nexus S'}, {name: 'Motorola DROID'}]); - }); - - - it('should set the default value of orderProp model', function() { - expect(ctrl.orderProp).toBe('age'); - }); - }); - - - describe('PhoneDetailCtrl', function(){ - var $httpBackend, ctrl, - xyzPhoneData = function() { - return { - name: 'phone xyz', - images: ['image/url1.png', 'image/url2.png'] - } - }; - - beforeEach(inject(function(_$httpBackend_, $routeParams, $controller) { - $httpBackend = _$httpBackend_; - $httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData()); - - $routeParams.phoneId = 'xyz'; - ctrl = $controller('PhoneDetailCtrl'); - })); - - - it('should fetch phone detail', function() { - expect(ctrl.phone).toEqual({}); - $httpBackend.flush(); - - expect(ctrl.phone).toEqual(xyzPhoneData()); - }); - }); -}); diff --git a/test/unit/directivesSpec.js b/test/unit/directivesSpec.js deleted file mode 100644 index 2e5469af4..000000000 --- a/test/unit/directivesSpec.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -/* jasmine specs for directives go here */ - -describe('directives', function() { - -}); diff --git a/test/unit/filtersSpec.js b/test/unit/filtersSpec.js deleted file mode 100644 index e5cbb7262..000000000 --- a/test/unit/filtersSpec.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -/* jasmine specs for filters go here */ - -describe('filter', function() { - - beforeEach(module('phonecatFilters')); - - - describe('checkmark', function() { - - it('should convert boolean values to unicode checkmark or cross', - inject(function(checkmarkFilter) { - expect(checkmarkFilter(true)).toBe('\u2713'); - expect(checkmarkFilter(false)).toBe('\u2718'); - })); - }); -}); diff --git a/test/unit/servicesSpec.js b/test/unit/phone.factory.spec.js similarity index 70% rename from test/unit/servicesSpec.js rename to test/unit/phone.factory.spec.js index b495870cb..8f0144245 100644 --- a/test/unit/servicesSpec.js +++ b/test/unit/phone.factory.spec.js @@ -1,12 +1,12 @@ 'use strict'; -describe('service', function() { +describe('Phone', function() { // load modules - beforeEach(module('phonecatApp')); + beforeEach(module('phonecat.core')); // Test service availability it('check the existence of Phone factory', inject(function(Phone) { expect(Phone).toBeDefined(); })); -}); \ No newline at end of file +}); diff --git a/test/unit/phone_detail.controller.spec.js b/test/unit/phone_detail.controller.spec.js new file mode 100644 index 000000000..5d494a114 --- /dev/null +++ b/test/unit/phone_detail.controller.spec.js @@ -0,0 +1,34 @@ +'use strict'; + +describe('PhoneDetailCtrl', function() { + + var $httpBackend, ctrl, + xyzPhoneData = function() { + return { + name: 'phone xyz', + images: ['image/url1.png', 'image/url2.png'] + } + }; + + beforeEach(function() { + jasmine.addCustomEqualityTester(angular.equals); + }); + + beforeEach(module('phonecat.phoneDetail')); + + beforeEach(inject(function(_$httpBackend_, $routeParams, $controller) { + $httpBackend = _$httpBackend_; + $httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData()); + + $routeParams.phoneId = 'xyz'; + ctrl = $controller('PhoneDetailCtrl'); + })); + + + it('should fetch phone detail', function() { + expect(ctrl.phone).toEqual({}); + $httpBackend.flush(); + + expect(ctrl.phone).toEqual(xyzPhoneData()); + }); +}); diff --git a/test/unit/phone_list.controller.spec.js b/test/unit/phone_list.controller.spec.js new file mode 100644 index 000000000..3ad52c863 --- /dev/null +++ b/test/unit/phone_list.controller.spec.js @@ -0,0 +1,33 @@ +'use strict'; + +describe('PhoneListCtrl', function() { + + var ctrl, $httpBackend; + + beforeEach(function(){ + jasmine.addCustomEqualityTester(angular.equals); + }); + + beforeEach(module('phonecat.phoneList')); + + beforeEach(inject(function(_$httpBackend_, $controller) { + $httpBackend = _$httpBackend_; + $httpBackend.expectGET('phones/phones.json'). + respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]); + + ctrl = $controller('PhoneListCtrl'); + })); + + it('should create "phones" model with 2 phones fetched from xhr', function() { + expect(ctrl.phones).toEqual([]); + $httpBackend.flush(); + + expect(ctrl.phones).toEqual( + [{name: 'Nexus S'}, {name: 'Motorola DROID'}]); + }); + + it('should set the default value of orderProp model', function() { + expect(ctrl.orderProp).toBe('age'); + }); + +});