├── .gitignore ├── .travis.yml ├── README.md ├── app ├── css │ ├── .gitkeep │ └── app.css ├── img │ └── .gitkeep ├── index-async.html ├── index.html ├── js │ ├── app.js │ ├── controllers.js │ ├── directives.js │ ├── filters.js │ └── services.js ├── lib │ └── angular │ │ ├── angular-cookies.js │ │ ├── angular-cookies.min.js │ │ ├── angular-loader.js │ │ ├── angular-loader.min.js │ │ ├── angular-resource.js │ │ ├── angular-resource.min.js │ │ ├── angular-sanitize.js │ │ ├── angular-sanitize.min.js │ │ ├── angular.js │ │ ├── angular.min.js │ │ └── version.txt └── partials │ ├── .gitkeep │ ├── partial1.html │ └── partial2.html ├── config ├── karma-e2e.conf.js └── karma.conf.js ├── logs └── .gitkeep ├── scripts ├── e2e-test.bat ├── e2e-test.sh ├── test.bat ├── test.sh ├── watchr.rb └── web-server.js └── test ├── e2e ├── runner.html └── scenarios.js ├── lib └── angular │ ├── angular-mocks.js │ ├── angular-scenario.js │ └── version.txt └── unit ├── controllersSpec.js ├── directivesSpec.js ├── filtersSpec.js └── servicesSpec.js /.gitignore: -------------------------------------------------------------------------------- 1 | logs/* 2 | !.gitkeep 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | 5 | before_script: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | - npm install --quiet -g karma 9 | - ./scripts/web-server.js > /dev/null & 10 | - sleep 1 # give server time to start 11 | 12 | script: 13 | - karma start config/karma.conf.js --no-auto-watch --single-run --reporters=dots --browsers=Firefox 14 | - karma start config/karma-e2e.conf.js --reporters=dots --browsers=Firefox 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is the codebase for the REST/AngularJS example published here: 2 | 3 | http://coder1.com/articles/consuming-rest-services-angularjs 4 | 5 | The modified files are: 6 | 7 | - index.html 8 | - js/services.js 9 | - js/controllers.js 10 | - partials/partial1.js 11 | -------------------------------------------------------------------------------- /app/css/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikemilano/angular-seed-rest/6fcad6c2d86bc688ed2f2821c55b282b2513cc45/app/css/.gitkeep -------------------------------------------------------------------------------- /app/css/app.css: -------------------------------------------------------------------------------- 1 | /* app css stylesheet */ 2 | 3 | .menu { 4 | list-style: none; 5 | border-bottom: 0.1em solid black; 6 | margin-bottom: 2em; 7 | padding: 0 0 0.5em; 8 | } 9 | 10 | .menu:before { 11 | content: "["; 12 | } 13 | 14 | .menu:after { 15 | content: "]"; 16 | } 17 | 18 | .menu > li { 19 | display: inline; 20 | } 21 | 22 | .menu > li:before { 23 | content: "|"; 24 | padding-right: 0.3em; 25 | } 26 | 27 | .menu > li:nth-child(1):before { 28 | content: ""; 29 | padding: 0; 30 | } 31 | 32 | a { 33 | cursor: pointer; 34 | } 35 | 36 | thead { 37 | cursor: pointer; 38 | } 39 | 40 | th.sortable { 41 | color: #0088cc; 42 | } 43 | 44 | td { 45 | padding: 0.2em 1em; 46 | } 47 | 48 | td.open { 49 | color: darkgreen; 50 | } 51 | 52 | td.closed { 53 | color: maroon; 54 | } 55 | 56 | .sort-desc { 57 | background:no-repeat right center url(data:image/gif;base64,R0lGODlhCgAKALMAAHFxcYKCgp2dnaampq+vr83NzeHh4f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAAAgAIf/8SUNDUkdCRzEwMTIAAAUwYXBwbAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkc2NtAAABCAAAAvJkZXNjAAAD/AAAAG9nWFlaAAAEbAAAABR3dHB0AAAEgAAAABRyWFlaAAAElAAAABRiWFlaAAAEqAAAABRyVFJDAAAEvAAAAA5jcHJ0AAAEzAAAADhjaGFkAAAFBAAAACxn/1RSQwAABLwAAAAOYlRSQwAABLwAAAAObWx1YwAAAAAAAAARAAAADGVuVVMAAAAmAAACfmVzRVMAAAAmAAABgmRhREsAAAAuAAAB6mRlREUAAAAsAAABqGZpRkkAAAAoAAAA3GZyRlUAAAAoAAABKml0SVQAAAAoAAACVm5sTkwAAAAoAAACGG5iTk8AAAAmAAABBHB0QlIAAAAmAAABgnN2U0UAAAAmAAABBGphSlAAAAAaAAABUmtvS1IAAAAWAAACQHpoVFcAAAAWAAABbHpoQ04AAAAWAAAB1HJ1UlUAAAAiAAACpHBsUEwAAAAsAAACxgBZAGwAZQBpAG4AZf8AbgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAHMAawAgAFIARwBCAC0AcAByAG8AZgBpAGwAUAByAG8AZgBpAGwAIABHAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCTgCCLAAgAFIARwBCACAw1zDtMNUwoTCkMOuQGnUoACAAUgBHAEIAIIJyX2ljz4/wAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8AQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkAbGZukBoAIABSAEcAQgAgY8+P8GX/h072AEcAZQBuAGUAcgBlAGwAIABSAEcAQgAtAGIAZQBzAGsAcgBpAHYAZQBsAHMAZQBBAGwAZwBlAG0AZQBlAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBlAGzHfLwYACAAUgBHAEIAINUEuFzTDMd8AFAAcgBvAGYAaQBsAG8AIABSAEcAQgAgAEcAZQBuAGUAcgBpAGMAbwBHAGUAbgBlAHIAaQBjACAAUgBHAEIAIABQAHIAbwBmAGkAbABlBB4EMQRJBDgEOQAgBD8EQAQ+BEQEOAQ7BEwAIABSAEcAQgBVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBm/wBpAGwAIABSAEcAQgAAZGVzYwAAAAAAAAAUR2VuZXJpYyBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAAAAAAdE0AAD3uAAAD0FhZWiAAAAAAAAAoGgAAFZ8AALg2Y3VydgAAAAAAAAABAc0AAHRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBwbGUgSW5jLkMsIGFsbCByaWdodHMgcmVzZXJ2ZWQuAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA/ZH///ui///9owAAA9wAAMBsACwAAAAACgAKAAAEJZAMIcakQZjNtyhFxwEIIRofAookUnapu26t+6KFLYe1TgQ5VwQAOw%3D%3D); 58 | } 59 | .sort-asc { 60 | background:no-repeat right center url(data:image/gif;base64,R0lGODlhCgAKALMAAHFxcYKCgp2dnaampq+vr83NzeHh4f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAAAgAIf/8SUNDUkdCRzEwMTIAAAUwYXBwbAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkc2NtAAABCAAAAvJkZXNjAAAD/AAAAG9nWFlaAAAEbAAAABR3dHB0AAAEgAAAABRyWFlaAAAElAAAABRiWFlaAAAEqAAAABRyVFJDAAAEvAAAAA5jcHJ0AAAEzAAAADhjaGFkAAAFBAAAACxn/1RSQwAABLwAAAAOYlRSQwAABLwAAAAObWx1YwAAAAAAAAARAAAADGVuVVMAAAAmAAACfmVzRVMAAAAmAAABgmRhREsAAAAuAAAB6mRlREUAAAAsAAABqGZpRkkAAAAoAAAA3GZyRlUAAAAoAAABKml0SVQAAAAoAAACVm5sTkwAAAAoAAACGG5iTk8AAAAmAAABBHB0QlIAAAAmAAABgnN2U0UAAAAmAAABBGphSlAAAAAaAAABUmtvS1IAAAAWAAACQHpoVFcAAAAWAAABbHpoQ04AAAAWAAAB1HJ1UlUAAAAiAAACpHBsUEwAAAAsAAACxgBZAGwAZQBpAG4AZf8AbgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAHMAawAgAFIARwBCAC0AcAByAG8AZgBpAGwAUAByAG8AZgBpAGwAIABHAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCTgCCLAAgAFIARwBCACAw1zDtMNUwoTCkMOuQGnUoACAAUgBHAEIAIIJyX2ljz4/wAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8AQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkAbGZukBoAIABSAEcAQgAgY8+P8GX/h072AEcAZQBuAGUAcgBlAGwAIABSAEcAQgAtAGIAZQBzAGsAcgBpAHYAZQBsAHMAZQBBAGwAZwBlAG0AZQBlAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBlAGzHfLwYACAAUgBHAEIAINUEuFzTDMd8AFAAcgBvAGYAaQBsAG8AIABSAEcAQgAgAEcAZQBuAGUAcgBpAGMAbwBHAGUAbgBlAHIAaQBjACAAUgBHAEIAIABQAHIAbwBmAGkAbABlBB4EMQRJBDgEOQAgBD8EQAQ+BEQEOAQ7BEwAIABSAEcAQgBVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBm/wBpAGwAIABSAEcAQgAAZGVzYwAAAAAAAAAUR2VuZXJpYyBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAAAAAAdE0AAD3uAAAD0FhZWiAAAAAAAAAoGgAAFZ8AALg2Y3VydgAAAAAAAAABAc0AAHRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBwbGUgSW5jLkMsIGFsbCByaWdodHMgcmVzZXJ2ZWQuAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA/ZH///ui///9owAAA9wAAMBsACwAAAAACgAKAAAEJRBJREKZsxQDsCSGIVzZFnYTGIqktp7fG46uzAn2TAyCMPC9QAQAOw%3D%3D); 61 | } -------------------------------------------------------------------------------- /app/img/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikemilano/angular-seed-rest/6fcad6c2d86bc688ed2f2821c55b282b2513cc45/app/img/.gitkeep -------------------------------------------------------------------------------- /app/index-async.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 44 | My AngularJS App 45 | 46 | 47 | 48 | 52 | 53 |
54 | 55 |
Angular seed app: v
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My AngularJS App 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 |
18 | 19 |
Angular seed app: v
20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/js/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | // Declare app level module which depends on filters, and services 5 | angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives', 'myApp.controllers']). 6 | config(['$routeProvider', function($routeProvider) { 7 | $routeProvider.when('/view1', {templateUrl: 'partials/partial1.html', controller: 'MyCtrl1'}); 8 | $routeProvider.when('/view2', {templateUrl: 'partials/partial2.html', controller: 'MyCtrl2'}); 9 | $routeProvider.otherwise({redirectTo: '/view1'}); 10 | }]); 11 | -------------------------------------------------------------------------------- /app/js/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp.controllers', []) 4 | .controller('MyCtrl1', ['$scope', 'AngularIssues', function($scope, AngularIssues) { 5 | // Instantiate an object to store your scope data in (Best Practices) 6 | $scope.myData = { 7 | currentIssue: null, 8 | issueList: [], 9 | issueListState: 'open', 10 | issueListSort: 'created', 11 | issueListDirection: 'desc', 12 | issueListPage: 1 13 | }; 14 | 15 | // Available default actions: 16 | // 'get': {method:'GET'}, 17 | // 'save': {method:'POST'}, 18 | // 'query': {method:'GET', isArray:true}, 19 | // 'remove': {method:'DELETE'}, 20 | // 'delete': {method:'DELETE'} 21 | /* 22 | AngularIssues.query(function(data) { 23 | // Assign the response INSIDE the callback 24 | // because this is asynchonous. 25 | $scope.myData.issues = data; 26 | console.log('line 16'); 27 | }); 28 | 29 | console.log('line 19'); 30 | */ 31 | 32 | /* 33 | // Accomplish the same thing with $http 34 | $http.get('https://api.github.com/repos/angular/angular.js/issues') 35 | .success(function(data) { 36 | console.log(data); 37 | $scope.myCtrl1Data.issues = data; 38 | }) 39 | .error(function(data) { 40 | console.log(data); 41 | }); 42 | */ 43 | 44 | $scope.setIssueList = function() { 45 | AngularIssues.query({ 46 | state: $scope.myData.issueListState, 47 | //labels: $scope.myData.labels, 48 | sort: $scope.myData.issueListSort, 49 | direction: $scope.myData.issueListDirection 50 | }, function(data) { 51 | $scope.myData.issueList = data; 52 | }); 53 | }; 54 | 55 | $scope.setSort = function(sort) { 56 | var oldSort = angular.copy($scope.myData.issueListSort); 57 | $scope.myData.issueListSort = sort; 58 | if (oldSort == sort) { 59 | $scope.setDirection($scope.myData.issueListDirection == 'desc' ? 'asc' : 'desc'); 60 | } else { 61 | $scope.setDirection('desc'); 62 | } 63 | }; 64 | 65 | $scope.setDirection = function(direction) { 66 | $scope.myData.issueListDirection = direction; 67 | $scope.setIssueList(); 68 | }; 69 | 70 | $scope.sortClass = function(column){ 71 | return column == $scope.myData.issueListSort && 'sort-' + $scope.myData.issueListDirection; 72 | }; 73 | 74 | $scope.setCurrentIssue = function(number) { 75 | AngularIssues.getIssue({number: number}, function(data){ 76 | console.log(data); 77 | $scope.myData.currentIssue = data; 78 | }); 79 | }; 80 | 81 | $scope.showAll = function() { 82 | $scope.myData.currentIssue = null; 83 | }; 84 | 85 | $scope.setIssueList(); 86 | }]) 87 | 88 | .controller('MyCtrl3', [function() { 89 | 90 | }]); -------------------------------------------------------------------------------- /app/js/directives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Directives */ 4 | 5 | 6 | angular.module('myApp.directives', []). 7 | directive('appVersion', ['version', function(version) { 8 | return function(scope, elm, attrs) { 9 | elm.text(version); 10 | }; 11 | }]); 12 | -------------------------------------------------------------------------------- /app/js/filters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Filters */ 4 | 5 | angular.module('myApp.filters', []). 6 | filter('interpolate', ['version', function(version) { 7 | return function(text) { 8 | return String(text).replace(/\%VERSION\%/mg, version); 9 | } 10 | }]); 11 | -------------------------------------------------------------------------------- /app/js/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp.services', ['ngResource']) 4 | .factory('AngularIssues', function($resource){ 5 | return $resource('https://api.github.com/repos/angular/angular.js/issues/:number', 6 | {number: '@number'}, 7 | {getIssue: {method: 'GET', params: {number: 0}}} 8 | ) 9 | }) 10 | .value('version', '0.1'); 11 | 12 | /* 13 | { 'get': {method:'GET'}, 14 | 'save': {method:'POST'}, 15 | 'query': {method:'GET', isArray:true}, 16 | 'remove': {method:'DELETE'}, 17 | 'delete': {method:'DELETE'} }; 18 | */ -------------------------------------------------------------------------------- /app/lib/angular/angular-cookies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.7 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngCookies 12 | */ 13 | 14 | 15 | angular.module('ngCookies', ['ng']). 16 | /** 17 | * @ngdoc object 18 | * @name ngCookies.$cookies 19 | * @requires $browser 20 | * 21 | * @description 22 | * Provides read/write access to browser's cookies. 23 | * 24 | * Only a simple Object is exposed and by adding or removing properties to/from 25 | * this object, new cookies are created/deleted at the end of current $eval. 26 | * 27 | * @example 28 | 29 | 30 | 38 | 39 | 40 | */ 41 | factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) { 42 | var cookies = {}, 43 | lastCookies = {}, 44 | lastBrowserCookies, 45 | runEval = false, 46 | copy = angular.copy, 47 | isUndefined = angular.isUndefined; 48 | 49 | //creates a poller fn that copies all cookies from the $browser to service & inits the service 50 | $browser.addPollFn(function() { 51 | var currentCookies = $browser.cookies(); 52 | if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl 53 | lastBrowserCookies = currentCookies; 54 | copy(currentCookies, lastCookies); 55 | copy(currentCookies, cookies); 56 | if (runEval) $rootScope.$apply(); 57 | } 58 | })(); 59 | 60 | runEval = true; 61 | 62 | //at the end of each eval, push cookies 63 | //TODO: this should happen before the "delayed" watches fire, because if some cookies are not 64 | // strings or browser refuses to store some cookies, we update the model in the push fn. 65 | $rootScope.$watch(push); 66 | 67 | return cookies; 68 | 69 | 70 | /** 71 | * Pushes all the cookies from the service to the browser and verifies if all cookies were stored. 72 | */ 73 | function push() { 74 | var name, 75 | value, 76 | browserCookies, 77 | updated; 78 | 79 | //delete any cookies deleted in $cookies 80 | for (name in lastCookies) { 81 | if (isUndefined(cookies[name])) { 82 | $browser.cookies(name, undefined); 83 | } 84 | } 85 | 86 | //update all cookies updated in $cookies 87 | for(name in cookies) { 88 | value = cookies[name]; 89 | if (!angular.isString(value)) { 90 | if (angular.isDefined(lastCookies[name])) { 91 | cookies[name] = lastCookies[name]; 92 | } else { 93 | delete cookies[name]; 94 | } 95 | } else if (value !== lastCookies[name]) { 96 | $browser.cookies(name, value); 97 | updated = true; 98 | } 99 | } 100 | 101 | //verify what was actually stored 102 | if (updated){ 103 | updated = false; 104 | browserCookies = $browser.cookies(); 105 | 106 | for (name in cookies) { 107 | if (cookies[name] !== browserCookies[name]) { 108 | //delete or reset all cookies that the browser dropped from $cookies 109 | if (isUndefined(browserCookies[name])) { 110 | delete cookies[name]; 111 | } else { 112 | cookies[name] = browserCookies[name]; 113 | } 114 | updated = true; 115 | } 116 | } 117 | } 118 | } 119 | }]). 120 | 121 | 122 | /** 123 | * @ngdoc object 124 | * @name ngCookies.$cookieStore 125 | * @requires $cookies 126 | * 127 | * @description 128 | * Provides a key-value (string-object) storage, that is backed by session cookies. 129 | * Objects put or retrieved from this storage are automatically serialized or 130 | * deserialized by angular's toJson/fromJson. 131 | * @example 132 | */ 133 | factory('$cookieStore', ['$cookies', function($cookies) { 134 | 135 | return { 136 | /** 137 | * @ngdoc method 138 | * @name ngCookies.$cookieStore#get 139 | * @methodOf ngCookies.$cookieStore 140 | * 141 | * @description 142 | * Returns the value of given cookie key 143 | * 144 | * @param {string} key Id to use for lookup. 145 | * @returns {Object} Deserialized cookie value. 146 | */ 147 | get: function(key) { 148 | var value = $cookies[key]; 149 | return value ? angular.fromJson(value) : value; 150 | }, 151 | 152 | /** 153 | * @ngdoc method 154 | * @name ngCookies.$cookieStore#put 155 | * @methodOf ngCookies.$cookieStore 156 | * 157 | * @description 158 | * Sets a value for given cookie key 159 | * 160 | * @param {string} key Id for the `value`. 161 | * @param {Object} value Value to be stored. 162 | */ 163 | put: function(key, value) { 164 | $cookies[key] = angular.toJson(value); 165 | }, 166 | 167 | /** 168 | * @ngdoc method 169 | * @name ngCookies.$cookieStore#remove 170 | * @methodOf ngCookies.$cookieStore 171 | * 172 | * @description 173 | * Remove given cookie 174 | * 175 | * @param {string} key Id of the key-value pair to delete. 176 | */ 177 | remove: function(key) { 178 | delete $cookies[key]; 179 | } 180 | }; 181 | 182 | }]); 183 | 184 | 185 | })(window, window.angular); 186 | -------------------------------------------------------------------------------- /app/lib/angular/angular-cookies.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.7 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(m,f,l){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,b){var c={},g={},h,i=!1,j=f.copy,k=f.isUndefined;b.addPollFn(function(){var a=b.cookies();h!=a&&(h=a,j(a,g),j(a,c),i&&d.$apply())})();i=!0;d.$watch(function(){var a,e,d;for(a in g)k(c[a])&&b.cookies(a,l);for(a in c)e=c[a],f.isString(e)?e!==g[a]&&(b.cookies(a,e),d=!0):f.isDefined(g[a])?c[a]=g[a]:delete c[a];if(d)for(a in e=b.cookies(),c)c[a]!==e[a]&&(k(e[a])?delete c[a]:c[a]=e[a])});return c}]).factory("$cookieStore", 7 | ["$cookies",function(d){return{get:function(b){return(b=d[b])?f.fromJson(b):b},put:function(b,c){d[b]=f.toJson(c)},remove:function(b){delete d[b]}}}])})(window,window.angular); 8 | -------------------------------------------------------------------------------- /app/lib/angular/angular-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.7 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | 7 | ( 8 | 9 | /** 10 | * @ngdoc interface 11 | * @name angular.Module 12 | * @description 13 | * 14 | * Interface for configuring angular {@link angular.module modules}. 15 | */ 16 | 17 | function setupModuleLoader(window) { 18 | 19 | function ensure(obj, name, factory) { 20 | return obj[name] || (obj[name] = factory()); 21 | } 22 | 23 | return ensure(ensure(window, 'angular', Object), 'module', function() { 24 | /** @type {Object.} */ 25 | var modules = {}; 26 | 27 | /** 28 | * @ngdoc function 29 | * @name angular.module 30 | * @description 31 | * 32 | * The `angular.module` is a global place for creating and registering Angular modules. All 33 | * modules (angular core or 3rd party) that should be available to an application must be 34 | * registered using this mechanism. 35 | * 36 | * 37 | * # Module 38 | * 39 | * A module is a collocation of services, directives, filters, and configuration information. Module 40 | * is used to configure the {@link AUTO.$injector $injector}. 41 | * 42 | *
 43 |      * // Create a new module
 44 |      * var myModule = angular.module('myModule', []);
 45 |      *
 46 |      * // register a new service
 47 |      * myModule.value('appName', 'MyCoolApp');
 48 |      *
 49 |      * // configure existing services inside initialization blocks.
 50 |      * myModule.config(function($locationProvider) {
 51 | 'use strict';
 52 |      *   // Configure existing providers
 53 |      *   $locationProvider.hashPrefix('!');
 54 |      * });
 55 |      * 
56 | * 57 | * Then you can create an injector and load your modules like this: 58 | * 59 | *
 60 |      * var injector = angular.injector(['ng', 'MyModule'])
 61 |      * 
62 | * 63 | * However it's more likely that you'll just use 64 | * {@link ng.directive:ngApp ngApp} or 65 | * {@link angular.bootstrap} to simplify this process for you. 66 | * 67 | * @param {!string} name The name of the module to create or retrieve. 68 | * @param {Array.=} requires If specified then new module is being created. If unspecified then the 69 | * the module is being retrieved for further configuration. 70 | * @param {Function} configFn Optional configuration function for the module. Same as 71 | * {@link angular.Module#config Module#config()}. 72 | * @returns {module} new module with the {@link angular.Module} api. 73 | */ 74 | return function module(name, requires, configFn) { 75 | if (requires && modules.hasOwnProperty(name)) { 76 | modules[name] = null; 77 | } 78 | return ensure(modules, name, function() { 79 | if (!requires) { 80 | throw Error('No module: ' + name); 81 | } 82 | 83 | /** @type {!Array.>} */ 84 | var invokeQueue = []; 85 | 86 | /** @type {!Array.} */ 87 | var runBlocks = []; 88 | 89 | var config = invokeLater('$injector', 'invoke'); 90 | 91 | /** @type {angular.Module} */ 92 | var moduleInstance = { 93 | // Private state 94 | _invokeQueue: invokeQueue, 95 | _runBlocks: runBlocks, 96 | 97 | /** 98 | * @ngdoc property 99 | * @name angular.Module#requires 100 | * @propertyOf angular.Module 101 | * @returns {Array.} List of module names which must be loaded before this module. 102 | * @description 103 | * Holds the list of modules which the injector will load before the current module is loaded. 104 | */ 105 | requires: requires, 106 | 107 | /** 108 | * @ngdoc property 109 | * @name angular.Module#name 110 | * @propertyOf angular.Module 111 | * @returns {string} Name of the module. 112 | * @description 113 | */ 114 | name: name, 115 | 116 | 117 | /** 118 | * @ngdoc method 119 | * @name angular.Module#provider 120 | * @methodOf angular.Module 121 | * @param {string} name service name 122 | * @param {Function} providerType Construction function for creating new instance of the service. 123 | * @description 124 | * See {@link AUTO.$provide#provider $provide.provider()}. 125 | */ 126 | provider: invokeLater('$provide', 'provider'), 127 | 128 | /** 129 | * @ngdoc method 130 | * @name angular.Module#factory 131 | * @methodOf angular.Module 132 | * @param {string} name service name 133 | * @param {Function} providerFunction Function for creating new instance of the service. 134 | * @description 135 | * See {@link AUTO.$provide#factory $provide.factory()}. 136 | */ 137 | factory: invokeLater('$provide', 'factory'), 138 | 139 | /** 140 | * @ngdoc method 141 | * @name angular.Module#service 142 | * @methodOf angular.Module 143 | * @param {string} name service name 144 | * @param {Function} constructor A constructor function that will be instantiated. 145 | * @description 146 | * See {@link AUTO.$provide#service $provide.service()}. 147 | */ 148 | service: invokeLater('$provide', 'service'), 149 | 150 | /** 151 | * @ngdoc method 152 | * @name angular.Module#value 153 | * @methodOf angular.Module 154 | * @param {string} name service name 155 | * @param {*} object Service instance object. 156 | * @description 157 | * See {@link AUTO.$provide#value $provide.value()}. 158 | */ 159 | value: invokeLater('$provide', 'value'), 160 | 161 | /** 162 | * @ngdoc method 163 | * @name angular.Module#constant 164 | * @methodOf angular.Module 165 | * @param {string} name constant name 166 | * @param {*} object Constant value. 167 | * @description 168 | * Because the constant are fixed, they get applied before other provide methods. 169 | * See {@link AUTO.$provide#constant $provide.constant()}. 170 | */ 171 | constant: invokeLater('$provide', 'constant', 'unshift'), 172 | 173 | /** 174 | * @ngdoc method 175 | * @name angular.Module#filter 176 | * @methodOf angular.Module 177 | * @param {string} name Filter name. 178 | * @param {Function} filterFactory Factory function for creating new instance of filter. 179 | * @description 180 | * See {@link ng.$filterProvider#register $filterProvider.register()}. 181 | */ 182 | filter: invokeLater('$filterProvider', 'register'), 183 | 184 | /** 185 | * @ngdoc method 186 | * @name angular.Module#controller 187 | * @methodOf angular.Module 188 | * @param {string} name Controller name. 189 | * @param {Function} constructor Controller constructor function. 190 | * @description 191 | * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 192 | */ 193 | controller: invokeLater('$controllerProvider', 'register'), 194 | 195 | /** 196 | * @ngdoc method 197 | * @name angular.Module#directive 198 | * @methodOf angular.Module 199 | * @param {string} name directive name 200 | * @param {Function} directiveFactory Factory function for creating new instance of 201 | * directives. 202 | * @description 203 | * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 204 | */ 205 | directive: invokeLater('$compileProvider', 'directive'), 206 | 207 | /** 208 | * @ngdoc method 209 | * @name angular.Module#config 210 | * @methodOf angular.Module 211 | * @param {Function} configFn Execute this function on module load. Useful for service 212 | * configuration. 213 | * @description 214 | * Use this method to register work which needs to be performed on module loading. 215 | */ 216 | config: config, 217 | 218 | /** 219 | * @ngdoc method 220 | * @name angular.Module#run 221 | * @methodOf angular.Module 222 | * @param {Function} initializationFn Execute this function after injector creation. 223 | * Useful for application initialization. 224 | * @description 225 | * Use this method to register work which should be performed when the injector is done 226 | * loading all modules. 227 | */ 228 | run: function(block) { 229 | runBlocks.push(block); 230 | return this; 231 | } 232 | }; 233 | 234 | if (configFn) { 235 | config(configFn); 236 | } 237 | 238 | return moduleInstance; 239 | 240 | /** 241 | * @param {string} provider 242 | * @param {string} method 243 | * @param {String=} insertMethod 244 | * @returns {angular.Module} 245 | */ 246 | function invokeLater(provider, method, insertMethod) { 247 | return function() { 248 | invokeQueue[insertMethod || 'push']([provider, method, arguments]); 249 | return moduleInstance; 250 | } 251 | } 252 | }); 253 | }; 254 | }); 255 | 256 | } 257 | 258 | )(window); 259 | 260 | /** 261 | * Closure compiler type information 262 | * 263 | * @typedef { { 264 | * requires: !Array., 265 | * invokeQueue: !Array.>, 266 | * 267 | * service: function(string, Function):angular.Module, 268 | * factory: function(string, Function):angular.Module, 269 | * value: function(string, *):angular.Module, 270 | * 271 | * filter: function(string, Function):angular.Module, 272 | * 273 | * init: function(Function):angular.Module 274 | * } } 275 | */ 276 | angular.Module; 277 | 278 | -------------------------------------------------------------------------------- /app/lib/angular/angular-loader.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.7 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(i){'use strict';function d(c,b,e){return c[b]||(c[b]=e())}return d(d(i,"angular",Object),"module",function(){var c={};return function(b,e,f){e&&c.hasOwnProperty(b)&&(c[b]=null);return d(c,b,function(){function a(a,b,d){return function(){c[d||"push"]([a,b,arguments]);return g}}if(!e)throw Error("No module: "+b);var c=[],d=[],h=a("$injector","invoke"),g={_invokeQueue:c,_runBlocks:d,requires:e,name:b,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"), 7 | value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:h,run:function(a){d.push(a);return this}};f&&h(f);return g})}})})(window); 8 | -------------------------------------------------------------------------------- /app/lib/angular/angular-resource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.7 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngResource 12 | * @description 13 | */ 14 | 15 | /** 16 | * @ngdoc object 17 | * @name ngResource.$resource 18 | * @requires $http 19 | * 20 | * @description 21 | * A factory which creates a resource object that lets you interact with 22 | * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. 23 | * 24 | * The returned resource object has action methods which provide high-level behaviors without 25 | * the need to interact with the low level {@link ng.$http $http} service. 26 | * 27 | * # Installation 28 | * To use $resource make sure you have included the `angular-resource.js` that comes in Angular 29 | * package. You can also find this file on Google CDN, bower as well as at 30 | * {@link http://code.angularjs.org/ code.angularjs.org}. 31 | * 32 | * Finally load the module in your application: 33 | * 34 | * angular.module('app', ['ngResource']); 35 | * 36 | * and you are ready to get started! 37 | * 38 | * @param {string} url A parameterized URL template with parameters prefixed by `:` as in 39 | * `/user/:username`. If you are using a URL with a port number (e.g. 40 | * `http://example.com:8080/api`), you'll need to escape the colon character before the port 41 | * number, like this: `$resource('http://example.com\\:8080/api')`. 42 | * 43 | * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 44 | * `actions` methods. 45 | * 46 | * Each key value in the parameter object is first bound to url template if present and then any 47 | * excess keys are appended to the url search query after the `?`. 48 | * 49 | * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 50 | * URL `/path/greet?salutation=Hello`. 51 | * 52 | * If the parameter value is prefixed with `@` then the value of that parameter is extracted from 53 | * the data object (useful for non-GET operations). 54 | * 55 | * @param {Object.=} actions Hash with declaration of custom action that should extend the 56 | * default set of resource actions. The declaration should be created in the following format: 57 | * 58 | * {action1: {method:?, params:?, isArray:?}, 59 | * action2: {method:?, params:?, isArray:?}, 60 | * ...} 61 | * 62 | * Where: 63 | * 64 | * - `action` – {string} – The name of action. This name becomes the name of the method on your 65 | * resource object. 66 | * - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, 67 | * and `JSONP` 68 | * - `params` – {object=} – Optional set of pre-bound parameters for this action. 69 | * - isArray – {boolean=} – If true then the returned object for this action is an array, see 70 | * `returns` section. 71 | * 72 | * @returns {Object} A resource "class" object with methods for the default set of resource actions 73 | * optionally extended with custom `actions`. The default set contains these actions: 74 | * 75 | * { 'get': {method:'GET'}, 76 | * 'save': {method:'POST'}, 77 | * 'query': {method:'GET', isArray:true}, 78 | * 'remove': {method:'DELETE'}, 79 | * 'delete': {method:'DELETE'} }; 80 | * 81 | * Calling these methods invoke an {@link ng.$http} with the specified http method, 82 | * destination and parameters. When the data is returned from the server then the object is an 83 | * instance of the resource class. The actions `save`, `remove` and `delete` are available on it 84 | * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, 85 | * read, update, delete) on server-side data like this: 86 | *
 87 |         var User = $resource('/user/:userId', {userId:'@id'});
 88 |         var user = User.get({userId:123}, function() {
 89 |           user.abc = true;
 90 |           user.$save();
 91 |         });
 92 |      
93 | * 94 | * It is important to realize that invoking a $resource object method immediately returns an 95 | * empty reference (object or array depending on `isArray`). Once the data is returned from the 96 | * server the existing reference is populated with the actual data. This is a useful trick since 97 | * usually the resource is assigned to a model which is then rendered by the view. Having an empty 98 | * object results in no rendering, once the data arrives from the server then the object is 99 | * populated with the data and the view automatically re-renders itself showing the new data. This 100 | * means that in most case one never has to write a callback function for the action methods. 101 | * 102 | * The action methods on the class object or instance object can be invoked with the following 103 | * parameters: 104 | * 105 | * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` 106 | * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` 107 | * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 108 | * 109 | * 110 | * @example 111 | * 112 | * # Credit card resource 113 | * 114 | *
115 |      // Define CreditCard class
116 |      var CreditCard = $resource('/user/:userId/card/:cardId',
117 |       {userId:123, cardId:'@id'}, {
118 |        charge: {method:'POST', params:{charge:true}}
119 |       });
120 | 
121 |      // We can retrieve a collection from the server
122 |      var cards = CreditCard.query(function() {
123 |        // GET: /user/123/card
124 |        // server returns: [ {id:456, number:'1234', name:'Smith'} ];
125 | 
126 |        var card = cards[0];
127 |        // each item is an instance of CreditCard
128 |        expect(card instanceof CreditCard).toEqual(true);
129 |        card.name = "J. Smith";
130 |        // non GET methods are mapped onto the instances
131 |        card.$save();
132 |        // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
133 |        // server returns: {id:456, number:'1234', name: 'J. Smith'};
134 | 
135 |        // our custom method is mapped as well.
136 |        card.$charge({amount:9.99});
137 |        // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
138 |      });
139 | 
140 |      // we can create an instance as well
141 |      var newCard = new CreditCard({number:'0123'});
142 |      newCard.name = "Mike Smith";
143 |      newCard.$save();
144 |      // POST: /user/123/card {number:'0123', name:'Mike Smith'}
145 |      // server returns: {id:789, number:'01234', name: 'Mike Smith'};
146 |      expect(newCard.id).toEqual(789);
147 |  * 
148 | * 149 | * The object returned from this function execution is a resource "class" which has "static" method 150 | * for each action in the definition. 151 | * 152 | * Calling these methods invoke `$http` on the `url` template with the given `method` and `params`. 153 | * When the data is returned from the server then the object is an instance of the resource type and 154 | * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 155 | * operations (create, read, update, delete) on server-side data. 156 | 157 |
158 |      var User = $resource('/user/:userId', {userId:'@id'});
159 |      var user = User.get({userId:123}, function() {
160 |        user.abc = true;
161 |        user.$save();
162 |      });
163 |    
164 | * 165 | * It's worth noting that the success callback for `get`, `query` and other method gets passed 166 | * in the response that came from the server as well as $http header getter function, so one 167 | * could rewrite the above example and get access to http headers as: 168 | * 169 |
170 |      var User = $resource('/user/:userId', {userId:'@id'});
171 |      User.get({userId:123}, function(u, getResponseHeaders){
172 |        u.abc = true;
173 |        u.$save(function(u, putResponseHeaders) {
174 |          //u => saved user object
175 |          //putResponseHeaders => $http header getter
176 |        });
177 |      });
178 |    
179 | 180 | * # Buzz client 181 | 182 | Let's look at what a buzz client created with the `$resource` service looks like: 183 | 184 | 185 | 205 | 206 |
207 | 208 | 209 |
210 |
211 |

212 | 213 | {{item.actor.name}} 214 | Expand replies: {{item.links.replies[0].count}} 215 |

216 | {{item.object.content | html}} 217 |
218 | 219 | {{reply.actor.name}}: {{reply.content | html}} 220 |
221 |
222 |
223 |
224 | 225 | 226 |
227 | */ 228 | angular.module('ngResource', ['ng']). 229 | factory('$resource', ['$http', '$parse', function($http, $parse) { 230 | var DEFAULT_ACTIONS = { 231 | 'get': {method:'GET'}, 232 | 'save': {method:'POST'}, 233 | 'query': {method:'GET', isArray:true}, 234 | 'remove': {method:'DELETE'}, 235 | 'delete': {method:'DELETE'} 236 | }; 237 | var noop = angular.noop, 238 | forEach = angular.forEach, 239 | extend = angular.extend, 240 | copy = angular.copy, 241 | isFunction = angular.isFunction, 242 | getter = function(obj, path) { 243 | return $parse(path)(obj); 244 | }; 245 | 246 | /** 247 | * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 248 | * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 249 | * segments: 250 | * segment = *pchar 251 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 252 | * pct-encoded = "%" HEXDIG HEXDIG 253 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 254 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 255 | * / "*" / "+" / "," / ";" / "=" 256 | */ 257 | function encodeUriSegment(val) { 258 | return encodeUriQuery(val, true). 259 | replace(/%26/gi, '&'). 260 | replace(/%3D/gi, '='). 261 | replace(/%2B/gi, '+'); 262 | } 263 | 264 | 265 | /** 266 | * This method is intended for encoding *key* or *value* parts of query component. We need a custom 267 | * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be 268 | * encoded per http://tools.ietf.org/html/rfc3986: 269 | * query = *( pchar / "/" / "?" ) 270 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 271 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 272 | * pct-encoded = "%" HEXDIG HEXDIG 273 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 274 | * / "*" / "+" / "," / ";" / "=" 275 | */ 276 | function encodeUriQuery(val, pctEncodeSpaces) { 277 | return encodeURIComponent(val). 278 | replace(/%40/gi, '@'). 279 | replace(/%3A/gi, ':'). 280 | replace(/%24/g, '$'). 281 | replace(/%2C/gi, ','). 282 | replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 283 | } 284 | 285 | function Route(template, defaults) { 286 | this.template = template = template + '#'; 287 | this.defaults = defaults || {}; 288 | var urlParams = this.urlParams = {}; 289 | forEach(template.split(/\W/), function(param){ 290 | if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(template))) { 291 | urlParams[param] = true; 292 | } 293 | }); 294 | this.template = template.replace(/\\:/g, ':'); 295 | } 296 | 297 | Route.prototype = { 298 | url: function(params) { 299 | var self = this, 300 | url = this.template, 301 | val, 302 | encodedVal; 303 | 304 | params = params || {}; 305 | forEach(this.urlParams, function(_, urlParam){ 306 | val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 307 | if (angular.isDefined(val) && val !== null) { 308 | encodedVal = encodeUriSegment(val); 309 | url = url.replace(new RegExp(":" + urlParam + "(\\W)", "g"), encodedVal + "$1"); 310 | } else { 311 | url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W)", "g"), function(match, 312 | leadingSlashes, tail) { 313 | if (tail.charAt(0) == '/') { 314 | return tail; 315 | } else { 316 | return leadingSlashes + tail; 317 | } 318 | }); 319 | } 320 | }); 321 | url = url.replace(/\/?#$/, ''); 322 | var query = []; 323 | forEach(params, function(value, key){ 324 | if (!self.urlParams[key]) { 325 | query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value)); 326 | } 327 | }); 328 | query.sort(); 329 | url = url.replace(/\/*$/, ''); 330 | return url + (query.length ? '?' + query.join('&') : ''); 331 | } 332 | }; 333 | 334 | 335 | function ResourceFactory(url, paramDefaults, actions) { 336 | var route = new Route(url); 337 | 338 | actions = extend({}, DEFAULT_ACTIONS, actions); 339 | 340 | function extractParams(data, actionParams){ 341 | var ids = {}; 342 | actionParams = extend({}, paramDefaults, actionParams); 343 | forEach(actionParams, function(value, key){ 344 | ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; 345 | }); 346 | return ids; 347 | } 348 | 349 | function Resource(value){ 350 | copy(value || {}, this); 351 | } 352 | 353 | forEach(actions, function(action, name) { 354 | action.method = angular.uppercase(action.method); 355 | var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH'; 356 | Resource[name] = function(a1, a2, a3, a4) { 357 | var params = {}; 358 | var data; 359 | var success = noop; 360 | var error = null; 361 | switch(arguments.length) { 362 | case 4: 363 | error = a4; 364 | success = a3; 365 | //fallthrough 366 | case 3: 367 | case 2: 368 | if (isFunction(a2)) { 369 | if (isFunction(a1)) { 370 | success = a1; 371 | error = a2; 372 | break; 373 | } 374 | 375 | success = a2; 376 | error = a3; 377 | //fallthrough 378 | } else { 379 | params = a1; 380 | data = a2; 381 | success = a3; 382 | break; 383 | } 384 | case 1: 385 | if (isFunction(a1)) success = a1; 386 | else if (hasBody) data = a1; 387 | else params = a1; 388 | break; 389 | case 0: break; 390 | default: 391 | throw "Expected between 0-4 arguments [params, data, success, error], got " + 392 | arguments.length + " arguments."; 393 | } 394 | 395 | var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); 396 | $http({ 397 | method: action.method, 398 | url: route.url(extend({}, extractParams(data, action.params || {}), params)), 399 | data: data 400 | }).then(function(response) { 401 | var data = response.data; 402 | 403 | if (data) { 404 | if (action.isArray) { 405 | value.length = 0; 406 | forEach(data, function(item) { 407 | value.push(new Resource(item)); 408 | }); 409 | } else { 410 | copy(data, value); 411 | } 412 | } 413 | (success||noop)(value, response.headers); 414 | }, error); 415 | 416 | return value; 417 | }; 418 | 419 | 420 | Resource.prototype['$' + name] = function(a1, a2, a3) { 421 | var params = extractParams(this), 422 | success = noop, 423 | error; 424 | 425 | switch(arguments.length) { 426 | case 3: params = a1; success = a2; error = a3; break; 427 | case 2: 428 | case 1: 429 | if (isFunction(a1)) { 430 | success = a1; 431 | error = a2; 432 | } else { 433 | params = a1; 434 | success = a2 || noop; 435 | } 436 | case 0: break; 437 | default: 438 | throw "Expected between 1-3 arguments [params, success, error], got " + 439 | arguments.length + " arguments."; 440 | } 441 | var data = hasBody ? this : undefined; 442 | Resource[name].call(this, params, data, success, error); 443 | }; 444 | }); 445 | 446 | Resource.bind = function(additionalParamDefaults){ 447 | return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); 448 | }; 449 | 450 | return Resource; 451 | } 452 | 453 | return ResourceFactory; 454 | }]); 455 | 456 | 457 | })(window, window.angular); 458 | -------------------------------------------------------------------------------- /app/lib/angular/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.7 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(C,d,w){'use strict';d.module("ngResource",["ng"]).factory("$resource",["$http","$parse",function(x,y){function s(b,e){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,e?"%20":"+")}function t(b,e){this.template=b+="#";this.defaults=e||{};var a=this.urlParams={};h(b.split(/\W/),function(f){f&&RegExp("(^|[^\\\\]):"+f+"\\W").test(b)&&(a[f]=!0)});this.template=b.replace(/\\:/g,":")}function u(b,e,a){function f(m,a){var b= 7 | {},a=o({},e,a);h(a,function(a,z){var c;a.charAt&&a.charAt(0)=="@"?(c=a.substr(1),c=y(c)(m)):c=a;b[z]=c});return b}function g(a){v(a||{},this)}var k=new t(b),a=o({},A,a);h(a,function(a,b){a.method=d.uppercase(a.method);var e=a.method=="POST"||a.method=="PUT"||a.method=="PATCH";g[b]=function(b,c,d,B){var j={},i,l=p,q=null;switch(arguments.length){case 4:q=B,l=d;case 3:case 2:if(r(c)){if(r(b)){l=b;q=c;break}l=c;q=d}else{j=b;i=c;l=d;break}case 1:r(b)?l=b:e?i=b:j=b;break;case 0:break;default:throw"Expected between 0-4 arguments [params, data, success, error], got "+ 8 | arguments.length+" arguments.";}var n=this instanceof g?this:a.isArray?[]:new g(i);x({method:a.method,url:k.url(o({},f(i,a.params||{}),j)),data:i}).then(function(b){var c=b.data;if(c)a.isArray?(n.length=0,h(c,function(a){n.push(new g(a))})):v(c,n);(l||p)(n,b.headers)},q);return n};g.prototype["$"+b]=function(a,d,h){var m=f(this),j=p,i;switch(arguments.length){case 3:m=a;j=d;i=h;break;case 2:case 1:r(a)?(j=a,i=d):(m=a,j=d||p);case 0:break;default:throw"Expected between 1-3 arguments [params, success, error], got "+ 9 | arguments.length+" arguments.";}g[b].call(this,m,e?this:w,j,i)}});g.bind=function(d){return u(b,o({},e,d),a)};return g}var A={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},p=d.noop,h=d.forEach,o=d.extend,v=d.copy,r=d.isFunction;t.prototype={url:function(b){var e=this,a=this.template,f,g,b=b||{};h(this.urlParams,function(h,c){f=b.hasOwnProperty(c)?b[c]:e.defaults[c];d.isDefined(f)&&f!==null?(g=s(f,!0).replace(/%26/gi,"&").replace(/%3D/gi, 10 | "=").replace(/%2B/gi,"+"),a=a.replace(RegExp(":"+c+"(\\W)","g"),g+"$1")):a=a.replace(RegExp("(/?):"+c+"(\\W)","g"),function(a,b,c){return c.charAt(0)=="/"?c:b+c})});var a=a.replace(/\/?#$/,""),k=[];h(b,function(a,b){e.urlParams[b]||k.push(s(b)+"="+s(a))});k.sort();a=a.replace(/\/*$/,"");return a+(k.length?"?"+k.join("&"):"")}};return u}])})(window,window.angular); 11 | -------------------------------------------------------------------------------- /app/lib/angular/angular-sanitize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.7 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngSanitize 12 | * @description 13 | */ 14 | 15 | /* 16 | * HTML Parser By Misko Hevery (misko@hevery.com) 17 | * based on: HTML Parser By John Resig (ejohn.org) 18 | * Original code by Erik Arvidsson, Mozilla Public License 19 | * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js 20 | * 21 | * // Use like so: 22 | * htmlParser(htmlString, { 23 | * start: function(tag, attrs, unary) {}, 24 | * end: function(tag) {}, 25 | * chars: function(text) {}, 26 | * comment: function(text) {} 27 | * }); 28 | * 29 | */ 30 | 31 | 32 | /** 33 | * @ngdoc service 34 | * @name ngSanitize.$sanitize 35 | * @function 36 | * 37 | * @description 38 | * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are 39 | * then serialized back to properly escaped html string. This means that no unsafe input can make 40 | * it into the returned string, however, since our parser is more strict than a typical browser 41 | * parser, it's possible that some obscure input, which would be recognized as valid HTML by a 42 | * browser, won't make it through the sanitizer. 43 | * 44 | * @param {string} html Html input. 45 | * @returns {string} Sanitized html. 46 | * 47 | * @example 48 | 49 | 50 | 58 |
59 | Snippet: 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
FilterSourceRendered
html filter 69 |
<div ng-bind-html="snippet">
</div>
70 |
72 |
73 |
no filter
<div ng-bind="snippet">
</div>
unsafe html filter
<div ng-bind-html-unsafe="snippet">
</div>
86 |
87 |
88 | 89 | it('should sanitize the html snippet ', function() { 90 | expect(using('#html-filter').element('div').html()). 91 | toBe('

an html\nclick here\nsnippet

'); 92 | }); 93 | 94 | it('should escape snippet without any filter', function() { 95 | expect(using('#escaped-html').element('div').html()). 96 | toBe("<p style=\"color:blue\">an html\n" + 97 | "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 98 | "snippet</p>"); 99 | }); 100 | 101 | it('should inline raw snippet if filtered as unsafe', function() { 102 | expect(using('#html-unsafe-filter').element("div").html()). 103 | toBe("

an html\n" + 104 | "click here\n" + 105 | "snippet

"); 106 | }); 107 | 108 | it('should update', function() { 109 | input('snippet').enter('new text'); 110 | expect(using('#html-filter').binding('snippet')).toBe('new text'); 111 | expect(using('#escaped-html').element('div').html()).toBe("new <b>text</b>"); 112 | expect(using('#html-unsafe-filter').binding("snippet")).toBe('new text'); 113 | }); 114 |
115 |
116 | */ 117 | var $sanitize = function(html) { 118 | var buf = []; 119 | htmlParser(html, htmlSanitizeWriter(buf)); 120 | return buf.join(''); 121 | }; 122 | 123 | 124 | // Regular Expressions for parsing tags and attributes 125 | var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, 126 | END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/, 127 | ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, 128 | BEGIN_TAG_REGEXP = /^/g, 131 | CDATA_REGEXP = //g, 132 | URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/, 133 | NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character) 134 | 135 | 136 | // Good source of info about elements and attributes 137 | // http://dev.w3.org/html5/spec/Overview.html#semantics 138 | // http://simon.html5.org/html-elements 139 | 140 | // Safe Void Elements - HTML5 141 | // http://dev.w3.org/html5/spec/Overview.html#void-elements 142 | var voidElements = makeMap("area,br,col,hr,img,wbr"); 143 | 144 | // Elements that you can, intentionally, leave open (and which close themselves) 145 | // http://dev.w3.org/html5/spec/Overview.html#optional-tags 146 | var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), 147 | optionalEndTagInlineElements = makeMap("rp,rt"), 148 | optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements); 149 | 150 | // Safe Block Elements - HTML5 151 | var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article,aside," + 152 | "blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6," + 153 | "header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); 154 | 155 | // Inline Elements - HTML5 156 | var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b,bdi,bdo," + 157 | "big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small," + 158 | "span,strike,strong,sub,sup,time,tt,u,var")); 159 | 160 | 161 | // Special Elements (can contain anything) 162 | var specialElements = makeMap("script,style"); 163 | 164 | var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements); 165 | 166 | //Attributes that have href and hence need to be sanitized 167 | var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap"); 168 | var validAttrs = angular.extend({}, uriAttrs, makeMap( 169 | 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ 170 | 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ 171 | 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ 172 | 'scope,scrolling,shape,span,start,summary,target,title,type,'+ 173 | 'valign,value,vspace,width')); 174 | 175 | function makeMap(str) { 176 | var obj = {}, items = str.split(','), i; 177 | for (i = 0; i < items.length; i++) obj[items[i]] = true; 178 | return obj; 179 | } 180 | 181 | 182 | /** 183 | * @example 184 | * htmlParser(htmlString, { 185 | * start: function(tag, attrs, unary) {}, 186 | * end: function(tag) {}, 187 | * chars: function(text) {}, 188 | * comment: function(text) {} 189 | * }); 190 | * 191 | * @param {string} html string 192 | * @param {object} handler 193 | */ 194 | function htmlParser( html, handler ) { 195 | var index, chars, match, stack = [], last = html; 196 | stack.last = function() { return stack[ stack.length - 1 ]; }; 197 | 198 | while ( html ) { 199 | chars = true; 200 | 201 | // Make sure we're not in a script or style element 202 | if ( !stack.last() || !specialElements[ stack.last() ] ) { 203 | 204 | // Comment 205 | if ( html.indexOf(""); 207 | 208 | if ( index >= 0 ) { 209 | if (handler.comment) handler.comment( html.substring( 4, index ) ); 210 | html = html.substring( index + 3 ); 211 | chars = false; 212 | } 213 | 214 | // end tag 215 | } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { 216 | match = html.match( END_TAG_REGEXP ); 217 | 218 | if ( match ) { 219 | html = html.substring( match[0].length ); 220 | match[0].replace( END_TAG_REGEXP, parseEndTag ); 221 | chars = false; 222 | } 223 | 224 | // start tag 225 | } else if ( BEGIN_TAG_REGEXP.test(html) ) { 226 | match = html.match( START_TAG_REGEXP ); 227 | 228 | if ( match ) { 229 | html = html.substring( match[0].length ); 230 | match[0].replace( START_TAG_REGEXP, parseStartTag ); 231 | chars = false; 232 | } 233 | } 234 | 235 | if ( chars ) { 236 | index = html.indexOf("<"); 237 | 238 | var text = index < 0 ? html : html.substring( 0, index ); 239 | html = index < 0 ? "" : html.substring( index ); 240 | 241 | if (handler.chars) handler.chars( decodeEntities(text) ); 242 | } 243 | 244 | } else { 245 | html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){ 246 | text = text. 247 | replace(COMMENT_REGEXP, "$1"). 248 | replace(CDATA_REGEXP, "$1"); 249 | 250 | if (handler.chars) handler.chars( decodeEntities(text) ); 251 | 252 | return ""; 253 | }); 254 | 255 | parseEndTag( "", stack.last() ); 256 | } 257 | 258 | if ( html == last ) { 259 | throw "Parse Error: " + html; 260 | } 261 | last = html; 262 | } 263 | 264 | // Clean up any remaining tags 265 | parseEndTag(); 266 | 267 | function parseStartTag( tag, tagName, rest, unary ) { 268 | tagName = angular.lowercase(tagName); 269 | if ( blockElements[ tagName ] ) { 270 | while ( stack.last() && inlineElements[ stack.last() ] ) { 271 | parseEndTag( "", stack.last() ); 272 | } 273 | } 274 | 275 | if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { 276 | parseEndTag( "", tagName ); 277 | } 278 | 279 | unary = voidElements[ tagName ] || !!unary; 280 | 281 | if ( !unary ) 282 | stack.push( tagName ); 283 | 284 | var attrs = {}; 285 | 286 | rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) { 287 | var value = doubleQuotedValue 288 | || singleQoutedValue 289 | || unqoutedValue 290 | || ''; 291 | 292 | attrs[name] = decodeEntities(value); 293 | }); 294 | if (handler.start) handler.start( tagName, attrs, unary ); 295 | } 296 | 297 | function parseEndTag( tag, tagName ) { 298 | var pos = 0, i; 299 | tagName = angular.lowercase(tagName); 300 | if ( tagName ) 301 | // Find the closest opened tag of the same type 302 | for ( pos = stack.length - 1; pos >= 0; pos-- ) 303 | if ( stack[ pos ] == tagName ) 304 | break; 305 | 306 | if ( pos >= 0 ) { 307 | // Close all the open elements, up the stack 308 | for ( i = stack.length - 1; i >= pos; i-- ) 309 | if (handler.end) handler.end( stack[ i ] ); 310 | 311 | // Remove the open elements from the stack 312 | stack.length = pos; 313 | } 314 | } 315 | } 316 | 317 | /** 318 | * decodes all entities into regular string 319 | * @param value 320 | * @returns {string} A string with decoded entities. 321 | */ 322 | var hiddenPre=document.createElement("pre"); 323 | function decodeEntities(value) { 324 | hiddenPre.innerHTML=value.replace(//g, '>'); 343 | } 344 | 345 | /** 346 | * create an HTML/XML writer which writes to buffer 347 | * @param {Array} buf use buf.jain('') to get out sanitized html string 348 | * @returns {object} in the form of { 349 | * start: function(tag, attrs, unary) {}, 350 | * end: function(tag) {}, 351 | * chars: function(text) {}, 352 | * comment: function(text) {} 353 | * } 354 | */ 355 | function htmlSanitizeWriter(buf){ 356 | var ignore = false; 357 | var out = angular.bind(buf, buf.push); 358 | return { 359 | start: function(tag, attrs, unary){ 360 | tag = angular.lowercase(tag); 361 | if (!ignore && specialElements[tag]) { 362 | ignore = tag; 363 | } 364 | if (!ignore && validElements[tag] == true) { 365 | out('<'); 366 | out(tag); 367 | angular.forEach(attrs, function(value, key){ 368 | var lkey=angular.lowercase(key); 369 | if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) { 370 | out(' '); 371 | out(key); 372 | out('="'); 373 | out(encodeEntities(value)); 374 | out('"'); 375 | } 376 | }); 377 | out(unary ? '/>' : '>'); 378 | } 379 | }, 380 | end: function(tag){ 381 | tag = angular.lowercase(tag); 382 | if (!ignore && validElements[tag] == true) { 383 | out(''); 386 | } 387 | if (tag == ignore) { 388 | ignore = false; 389 | } 390 | }, 391 | chars: function(chars){ 392 | if (!ignore) { 393 | out(encodeEntities(chars)); 394 | } 395 | } 396 | }; 397 | } 398 | 399 | 400 | // define ngSanitize module and register $sanitize service 401 | angular.module('ngSanitize', []).value('$sanitize', $sanitize); 402 | 403 | /** 404 | * @ngdoc directive 405 | * @name ngSanitize.directive:ngBindHtml 406 | * 407 | * @description 408 | * Creates a binding that will sanitize the result of evaluating the `expression` with the 409 | * {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element. 410 | * 411 | * See {@link ngSanitize.$sanitize $sanitize} docs for examples. 412 | * 413 | * @element ANY 414 | * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 415 | */ 416 | angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) { 417 | return function(scope, element, attr) { 418 | element.addClass('ng-binding').data('$binding', attr.ngBindHtml); 419 | scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) { 420 | value = $sanitize(value); 421 | element.html(value || ''); 422 | }); 423 | }; 424 | }]); 425 | 426 | /** 427 | * @ngdoc filter 428 | * @name ngSanitize.filter:linky 429 | * @function 430 | * 431 | * @description 432 | * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and 433 | * plain email address links. 434 | * 435 | * @param {string} text Input text. 436 | * @returns {string} Html-linkified text. 437 | * 438 | * @usage 439 | 440 | * 441 | * @example 442 | 443 | 444 | 454 |
455 | Snippet: 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 467 | 470 | 471 | 472 | 473 | 474 | 475 | 476 |
FilterSourceRendered
linky filter 465 |
<div ng-bind-html="snippet | linky">
</div>
466 |
468 |
469 |
no filter
<div ng-bind="snippet">
</div>
477 | 478 | 479 | it('should linkify the snippet with urls', function() { 480 | expect(using('#linky-filter').binding('snippet | linky')). 481 | toBe('Pretty text with some links: ' + 482 | 'http://angularjs.org/, ' + 483 | 'us@somewhere.org, ' + 484 | 'another@somewhere.org, ' + 485 | 'and one more: ftp://127.0.0.1/.'); 486 | }); 487 | 488 | it ('should not linkify snippet without the linky filter', function() { 489 | expect(using('#escaped-html').binding('snippet')). 490 | toBe("Pretty text with some links:\n" + 491 | "http://angularjs.org/,\n" + 492 | "mailto:us@somewhere.org,\n" + 493 | "another@somewhere.org,\n" + 494 | "and one more: ftp://127.0.0.1/."); 495 | }); 496 | 497 | it('should update', function() { 498 | input('snippet').enter('new http://link.'); 499 | expect(using('#linky-filter').binding('snippet | linky')). 500 | toBe('new http://link.'); 501 | expect(using('#escaped-html').binding('snippet')).toBe('new http://link.'); 502 | }); 503 | 504 | 505 | */ 506 | angular.module('ngSanitize').filter('linky', function() { 507 | var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/, 508 | MAILTO_REGEXP = /^mailto:/; 509 | 510 | return function(text) { 511 | if (!text) return text; 512 | var match; 513 | var raw = text; 514 | var html = []; 515 | // TODO(vojta): use $sanitize instead 516 | var writer = htmlSanitizeWriter(html); 517 | var url; 518 | var i; 519 | while ((match = raw.match(LINKY_URL_REGEXP))) { 520 | // We can not end in these as they are sometimes found at the end of the sentence 521 | url = match[0]; 522 | // if we did not match ftp/http/mailto then assume mailto 523 | if (match[2] == match[3]) url = 'mailto:' + url; 524 | i = match.index; 525 | writer.chars(raw.substr(0, i)); 526 | writer.start('a', {href:url}); 527 | writer.chars(match[0].replace(MAILTO_REGEXP, '')); 528 | writer.end('a'); 529 | raw = raw.substring(i + match[0].length); 530 | } 531 | writer.chars(raw); 532 | return html.join(''); 533 | }; 534 | }); 535 | 536 | 537 | })(window, window.angular); 538 | -------------------------------------------------------------------------------- /app/lib/angular/angular-sanitize.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.7 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(I,g){'use strict';function i(a){var d={},a=a.split(","),b;for(b=0;b=0;e--)if(f[e]==b)break;if(e>=0){for(c=f.length-1;c>=e;c--)d.end&&d.end(f[c]);f.length= 7 | e}}var c,h,f=[],j=a;for(f.last=function(){return f[f.length-1]};a;){h=!0;if(!f.last()||!q[f.last()]){if(a.indexOf("<\!--")===0)c=a.indexOf("--\>"),c>=0&&(d.comment&&d.comment(a.substring(4,c)),a=a.substring(c+3),h=!1);else if(B.test(a)){if(c=a.match(r))a=a.substring(c[0].length),c[0].replace(r,e),h=!1}else if(C.test(a)&&(c=a.match(s)))a=a.substring(c[0].length),c[0].replace(s,b),h=!1;h&&(c=a.indexOf("<"),h=c<0?a:a.substring(0,c),a=c<0?"":a.substring(c),d.chars&&d.chars(k(h)))}else a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+ 8 | f.last()+"[^>]*>","i"),function(b,a){a=a.replace(D,"$1").replace(E,"$1");d.chars&&d.chars(k(a));return""}),e("",f.last());if(a==j)throw"Parse Error: "+a;j=a}e()}function k(a){l.innerHTML=a.replace(//g,">")}function u(a){var d=!1,b=g.bind(a,a.push);return{start:function(a,c,h){a=g.lowercase(a);!d&&q[a]&&(d=a);!d&&v[a]== 9 | !0&&(b("<"),b(a),g.forEach(c,function(a,c){var e=g.lowercase(c);if(G[e]==!0&&(w[e]!==!0||a.match(H)))b(" "),b(c),b('="'),b(t(a)),b('"')}),b(h?"/>":">"))},end:function(a){a=g.lowercase(a);!d&&v[a]==!0&&(b(""));a==d&&(d=!1)},chars:function(a){d||b(t(a))}}}var s=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,r=/^<\s*\/\s*([\w:-]+)[^>]*>/,A=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,C=/^/g, 10 | E=//g,H=/^((ftp|https?):\/\/|mailto:|#)/,F=/([^\#-~| |!])/g,p=i("area,br,col,hr,img,wbr"),x=i("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=i("rp,rt"),o=g.extend({},y,x),m=g.extend({},x,i("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),n=g.extend({},y,i("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")), 11 | q=i("script,style"),v=g.extend({},p,m,n,o),w=i("background,cite,href,longdesc,src,usemap"),G=g.extend({},w,i("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),l=document.createElement("pre");g.module("ngSanitize",[]).value("$sanitize",function(a){var d=[]; 12 | z(a,u(d));return d.join("")});g.module("ngSanitize").directive("ngBindHtml",["$sanitize",function(a){return function(d,b,e){b.addClass("ng-binding").data("$binding",e.ngBindHtml);d.$watch(e.ngBindHtml,function(c){c=a(c);b.html(c||"")})}}]);g.module("ngSanitize").filter("linky",function(){var a=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,d=/^mailto:/;return function(b){if(!b)return b;for(var e=b,c=[],h=u(c),f,g;b=e.match(a);)f=b[0],b[2]==b[3]&&(f="mailto:"+f),g=b.index, 13 | h.chars(e.substr(0,g)),h.start("a",{href:f}),h.chars(b[0].replace(d,"")),h.end("a"),e=e.substring(g+b[0].length);h.chars(e);return c.join("")}})})(window,window.angular); 14 | -------------------------------------------------------------------------------- /app/lib/angular/version.txt: -------------------------------------------------------------------------------- 1 | 1.0.7 2 | -------------------------------------------------------------------------------- /app/partials/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikemilano/angular-seed-rest/6fcad6c2d86bc688ed2f2821c55b282b2513cc45/app/partials/.gitkeep -------------------------------------------------------------------------------- /app/partials/partial1.html: -------------------------------------------------------------------------------- 1 |

AngularJS GitHub Issues

2 | 3 |
4 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
NumberCreatedUpdatedCommentsStateReporterTitle
{{issue.number}}{{issue.created_at | date:'short'}}{{issue.updated_at | date:'short'}}{{issue.comments}}{{issue.state}}{{issue.user.login}}{{issue.title}}
41 |
42 | 43 |
44 | « Back 45 |

{{myData.issue.title}}

46 |
47 |
Number
48 |
{{myData.currentIssue.number}}
49 |
Date
50 |
{{myData.currentIssue.created_at | date:'short'}}
51 |
Last Update
52 |
{{myData.currentIssue.updated_at | date:'short'}}
53 |
State
54 |
{{myData.currentIssue.state}}
55 |
URL
56 |
{{myData.currentIssue.url}}
57 |
Asignee
58 |
{{myData.currentIssue.asignee}}
59 |
Body
60 |
{{myData.currentIssue.body}}
61 |
62 |
-------------------------------------------------------------------------------- /app/partials/partial2.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 2.

2 |

3 | Showing of 'interpolate' filter: 4 | {{ 'Current version is v%VERSION%.' | interpolate }} 5 |

6 | -------------------------------------------------------------------------------- /config/karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | basePath = '../'; 2 | 3 | files = [ 4 | ANGULAR_SCENARIO, 5 | ANGULAR_SCENARIO_ADAPTER, 6 | 'test/e2e/**/*.js' 7 | ]; 8 | 9 | autoWatch = false; 10 | 11 | browsers = ['Chrome']; 12 | 13 | singleRun = true; 14 | 15 | proxies = { 16 | '/': 'http://localhost:8000/' 17 | }; 18 | 19 | junitReporter = { 20 | outputFile: 'test_out/e2e.xml', 21 | suite: 'e2e' 22 | }; 23 | -------------------------------------------------------------------------------- /config/karma.conf.js: -------------------------------------------------------------------------------- 1 | basePath = '../'; 2 | 3 | files = [ 4 | JASMINE, 5 | JASMINE_ADAPTER, 6 | 'app/lib/angular/angular.js', 7 | 'app/lib/angular/angular-*.js', 8 | 'test/lib/angular/angular-mocks.js', 9 | 'app/js/**/*.js', 10 | 'test/unit/**/*.js' 11 | ]; 12 | 13 | autoWatch = true; 14 | 15 | browsers = ['Chrome']; 16 | 17 | junitReporter = { 18 | outputFile: 'test_out/unit.xml', 19 | suite: 'unit' 20 | }; 21 | -------------------------------------------------------------------------------- /logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikemilano/angular-seed-rest/6fcad6c2d86bc688ed2f2821c55b282b2513cc45/logs/.gitkeep -------------------------------------------------------------------------------- /scripts/e2e-test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM Windows script for running e2e tests 4 | REM You have to run server and capture some browser first 5 | REM 6 | REM Requirements: 7 | REM - NodeJS (http://nodejs.org/) 8 | REM - Karma (npm install -g karma) 9 | 10 | set BASE_DIR=%~dp0 11 | karma start "%BASE_DIR%\..\config\karma-e2e.conf.js" %* 12 | -------------------------------------------------------------------------------- /scripts/e2e-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE_DIR=`dirname $0` 4 | 5 | echo "" 6 | echo "Starting Karma Server (http://karma-runner.github.io)" 7 | echo "-------------------------------------------------------------------" 8 | 9 | karma start $BASE_DIR/../config/karma-e2e.conf.js $* 10 | -------------------------------------------------------------------------------- /scripts/test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM Windows script for running unit tests 4 | REM You have to run server and capture some browser first 5 | REM 6 | REM Requirements: 7 | REM - NodeJS (http://nodejs.org/) 8 | REM - Karma (npm install -g karma) 9 | 10 | set BASE_DIR=%~dp0 11 | karma start "%BASE_DIR%\..\config\karma.conf.js" %* 12 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE_DIR=`dirname $0` 4 | 5 | echo "" 6 | echo "Starting Karma Server (http://karma-runner.github.io)" 7 | echo "-------------------------------------------------------------------" 8 | 9 | karma start $BASE_DIR/../config/karma.conf.js $* 10 | -------------------------------------------------------------------------------- /scripts/watchr.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env watchr 2 | 3 | # config file for watchr http://github.com/mynyml/watchr 4 | # install: gem install watchr 5 | # run: watch watchr.rb 6 | # note: make sure that you have jstd server running (server.sh) and a browser captured 7 | 8 | log_file = File.expand_path(File.dirname(__FILE__) + '/../logs/jstd.log') 9 | 10 | `cd ..` 11 | `touch #{log_file}` 12 | 13 | puts "String watchr... log file: #{log_file}" 14 | 15 | watch( '(app/js|test/unit)' ) do 16 | `echo "\n\ntest run started @ \`date\`" > #{log_file}` 17 | `scripts/test.sh &> #{log_file}` 18 | end 19 | 20 | -------------------------------------------------------------------------------- /scripts/web-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var util = require('util'), 4 | http = require('http'), 5 | fs = require('fs'), 6 | url = require('url'), 7 | events = require('events'); 8 | 9 | var DEFAULT_PORT = 8000; 10 | 11 | function main(argv) { 12 | new HttpServer({ 13 | 'GET': createServlet(StaticServlet), 14 | 'HEAD': createServlet(StaticServlet) 15 | }).start(Number(argv[2]) || DEFAULT_PORT); 16 | } 17 | 18 | function escapeHtml(value) { 19 | return value.toString(). 20 | replace('<', '<'). 21 | replace('>', '>'). 22 | replace('"', '"'); 23 | } 24 | 25 | function createServlet(Class) { 26 | var servlet = new Class(); 27 | return servlet.handleRequest.bind(servlet); 28 | } 29 | 30 | /** 31 | * An Http server implementation that uses a map of methods to decide 32 | * action routing. 33 | * 34 | * @param {Object} Map of method => Handler function 35 | */ 36 | function HttpServer(handlers) { 37 | this.handlers = handlers; 38 | this.server = http.createServer(this.handleRequest_.bind(this)); 39 | } 40 | 41 | HttpServer.prototype.start = function(port) { 42 | this.port = port; 43 | this.server.listen(port); 44 | util.puts('Http Server running at http://localhost:' + port + '/'); 45 | }; 46 | 47 | HttpServer.prototype.parseUrl_ = function(urlString) { 48 | var parsed = url.parse(urlString); 49 | parsed.pathname = url.resolve('/', parsed.pathname); 50 | return url.parse(url.format(parsed), true); 51 | }; 52 | 53 | HttpServer.prototype.handleRequest_ = function(req, res) { 54 | var logEntry = req.method + ' ' + req.url; 55 | if (req.headers['user-agent']) { 56 | logEntry += ' ' + req.headers['user-agent']; 57 | } 58 | util.puts(logEntry); 59 | req.url = this.parseUrl_(req.url); 60 | var handler = this.handlers[req.method]; 61 | if (!handler) { 62 | res.writeHead(501); 63 | res.end(); 64 | } else { 65 | handler.call(this, req, res); 66 | } 67 | }; 68 | 69 | /** 70 | * Handles static content. 71 | */ 72 | function StaticServlet() {} 73 | 74 | StaticServlet.MimeMap = { 75 | 'txt': 'text/plain', 76 | 'html': 'text/html', 77 | 'css': 'text/css', 78 | 'xml': 'application/xml', 79 | 'json': 'application/json', 80 | 'js': 'application/javascript', 81 | 'jpg': 'image/jpeg', 82 | 'jpeg': 'image/jpeg', 83 | 'gif': 'image/gif', 84 | 'png': 'image/png', 85 |   'svg': 'image/svg+xml' 86 | }; 87 | 88 | StaticServlet.prototype.handleRequest = function(req, res) { 89 | var self = this; 90 | var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){ 91 | return String.fromCharCode(parseInt(hex, 16)); 92 | }); 93 | var parts = path.split('/'); 94 | if (parts[parts.length-1].charAt(0) === '.') 95 | return self.sendForbidden_(req, res, path); 96 | fs.stat(path, function(err, stat) { 97 | if (err) 98 | return self.sendMissing_(req, res, path); 99 | if (stat.isDirectory()) 100 | return self.sendDirectory_(req, res, path); 101 | return self.sendFile_(req, res, path); 102 | }); 103 | } 104 | 105 | StaticServlet.prototype.sendError_ = function(req, res, error) { 106 | res.writeHead(500, { 107 | 'Content-Type': 'text/html' 108 | }); 109 | res.write('\n'); 110 | res.write('Internal Server Error\n'); 111 | res.write('

Internal Server Error

'); 112 | res.write('
' + escapeHtml(util.inspect(error)) + '
'); 113 | util.puts('500 Internal Server Error'); 114 | util.puts(util.inspect(error)); 115 | }; 116 | 117 | StaticServlet.prototype.sendMissing_ = function(req, res, path) { 118 | path = path.substring(1); 119 | res.writeHead(404, { 120 | 'Content-Type': 'text/html' 121 | }); 122 | res.write('\n'); 123 | res.write('404 Not Found\n'); 124 | res.write('

Not Found

'); 125 | res.write( 126 | '

The requested URL ' + 127 | escapeHtml(path) + 128 | ' was not found on this server.

' 129 | ); 130 | res.end(); 131 | util.puts('404 Not Found: ' + path); 132 | }; 133 | 134 | StaticServlet.prototype.sendForbidden_ = function(req, res, path) { 135 | path = path.substring(1); 136 | res.writeHead(403, { 137 | 'Content-Type': 'text/html' 138 | }); 139 | res.write('\n'); 140 | res.write('403 Forbidden\n'); 141 | res.write('

Forbidden

'); 142 | res.write( 143 | '

You do not have permission to access ' + 144 | escapeHtml(path) + ' on this server.

' 145 | ); 146 | res.end(); 147 | util.puts('403 Forbidden: ' + path); 148 | }; 149 | 150 | StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) { 151 | res.writeHead(301, { 152 | 'Content-Type': 'text/html', 153 | 'Location': redirectUrl 154 | }); 155 | res.write('\n'); 156 | res.write('301 Moved Permanently\n'); 157 | res.write('

Moved Permanently

'); 158 | res.write( 159 | '

The document has moved here.

' 162 | ); 163 | res.end(); 164 | util.puts('301 Moved Permanently: ' + redirectUrl); 165 | }; 166 | 167 | StaticServlet.prototype.sendFile_ = function(req, res, path) { 168 | var self = this; 169 | var file = fs.createReadStream(path); 170 | res.writeHead(200, { 171 | 'Content-Type': StaticServlet. 172 | MimeMap[path.split('.').pop()] || 'text/plain' 173 | }); 174 | if (req.method === 'HEAD') { 175 | res.end(); 176 | } else { 177 | file.on('data', res.write.bind(res)); 178 | file.on('close', function() { 179 | res.end(); 180 | }); 181 | file.on('error', function(error) { 182 | self.sendError_(req, res, error); 183 | }); 184 | } 185 | }; 186 | 187 | StaticServlet.prototype.sendDirectory_ = function(req, res, path) { 188 | var self = this; 189 | if (path.match(/[^\/]$/)) { 190 | req.url.pathname += '/'; 191 | var redirectUrl = url.format(url.parse(url.format(req.url))); 192 | return self.sendRedirect_(req, res, redirectUrl); 193 | } 194 | fs.readdir(path, function(err, files) { 195 | if (err) 196 | return self.sendError_(req, res, error); 197 | 198 | if (!files.length) 199 | return self.writeDirectoryIndex_(req, res, path, []); 200 | 201 | var remaining = files.length; 202 | files.forEach(function(fileName, index) { 203 | fs.stat(path + '/' + fileName, function(err, stat) { 204 | if (err) 205 | return self.sendError_(req, res, err); 206 | if (stat.isDirectory()) { 207 | files[index] = fileName + '/'; 208 | } 209 | if (!(--remaining)) 210 | return self.writeDirectoryIndex_(req, res, path, files); 211 | }); 212 | }); 213 | }); 214 | }; 215 | 216 | StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) { 217 | path = path.substring(1); 218 | res.writeHead(200, { 219 | 'Content-Type': 'text/html' 220 | }); 221 | if (req.method === 'HEAD') { 222 | res.end(); 223 | return; 224 | } 225 | res.write('\n'); 226 | res.write('' + escapeHtml(path) + '\n'); 227 | res.write('\n'); 230 | res.write('

Directory: ' + escapeHtml(path) + '

'); 231 | res.write('
    '); 232 | files.forEach(function(fileName) { 233 | if (fileName.charAt(0) !== '.') { 234 | res.write('
  1. ' + 236 | escapeHtml(fileName) + '
  2. '); 237 | } 238 | }); 239 | res.write('
'); 240 | res.end(); 241 | }; 242 | 243 | // Must be last, 244 | main(process.argv); 245 | -------------------------------------------------------------------------------- /test/e2e/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | End2end Test Runner 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/e2e/scenarios.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ 4 | 5 | describe('my app', function() { 6 | 7 | beforeEach(function() { 8 | browser().navigateTo('../../app/index.html'); 9 | }); 10 | 11 | 12 | it('should automatically redirect to /view1 when location hash/fragment is empty', function() { 13 | expect(browser().location().url()).toBe("/view1"); 14 | }); 15 | 16 | 17 | describe('view1', function() { 18 | 19 | beforeEach(function() { 20 | browser().navigateTo('#/view1'); 21 | }); 22 | 23 | 24 | it('should render view1 when user navigates to /view1', function() { 25 | expect(element('[ng-view] p:first').text()). 26 | toMatch(/partial for view 1/); 27 | }); 28 | 29 | }); 30 | 31 | 32 | describe('view2', function() { 33 | 34 | beforeEach(function() { 35 | browser().navigateTo('#/view2'); 36 | }); 37 | 38 | 39 | it('should render view2 when user navigates to /view2', function() { 40 | expect(element('[ng-view] p:first').text()). 41 | toMatch(/partial for view 2/); 42 | }); 43 | 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/lib/angular/angular-mocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.7 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | * 6 | * TODO(vojta): wrap whole file into closure during build 7 | */ 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name angular.mock 12 | * @description 13 | * 14 | * Namespace from 'angular-mocks.js' which contains testing related code. 15 | */ 16 | angular.mock = {}; 17 | 18 | /** 19 | * ! This is a private undocumented service ! 20 | * 21 | * @name ngMock.$browser 22 | * 23 | * @description 24 | * This service is a mock implementation of {@link ng.$browser}. It provides fake 25 | * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, 26 | * cookies, etc... 27 | * 28 | * The api of this service is the same as that of the real {@link ng.$browser $browser}, except 29 | * that there are several helper methods available which can be used in tests. 30 | */ 31 | angular.mock.$BrowserProvider = function() { 32 | this.$get = function(){ 33 | return new angular.mock.$Browser(); 34 | }; 35 | }; 36 | 37 | angular.mock.$Browser = function() { 38 | var self = this; 39 | 40 | this.isMock = true; 41 | self.$$url = "http://server/"; 42 | self.$$lastUrl = self.$$url; // used by url polling fn 43 | self.pollFns = []; 44 | 45 | // TODO(vojta): remove this temporary api 46 | self.$$completeOutstandingRequest = angular.noop; 47 | self.$$incOutstandingRequestCount = angular.noop; 48 | 49 | 50 | // register url polling fn 51 | 52 | self.onUrlChange = function(listener) { 53 | self.pollFns.push( 54 | function() { 55 | if (self.$$lastUrl != self.$$url) { 56 | self.$$lastUrl = self.$$url; 57 | listener(self.$$url); 58 | } 59 | } 60 | ); 61 | 62 | return listener; 63 | }; 64 | 65 | self.cookieHash = {}; 66 | self.lastCookieHash = {}; 67 | self.deferredFns = []; 68 | self.deferredNextId = 0; 69 | 70 | self.defer = function(fn, delay) { 71 | delay = delay || 0; 72 | self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); 73 | self.deferredFns.sort(function(a,b){ return a.time - b.time;}); 74 | return self.deferredNextId++; 75 | }; 76 | 77 | 78 | self.defer.now = 0; 79 | 80 | 81 | self.defer.cancel = function(deferId) { 82 | var fnIndex; 83 | 84 | angular.forEach(self.deferredFns, function(fn, index) { 85 | if (fn.id === deferId) fnIndex = index; 86 | }); 87 | 88 | if (fnIndex !== undefined) { 89 | self.deferredFns.splice(fnIndex, 1); 90 | return true; 91 | } 92 | 93 | return false; 94 | }; 95 | 96 | 97 | /** 98 | * @name ngMock.$browser#defer.flush 99 | * @methodOf ngMock.$browser 100 | * 101 | * @description 102 | * Flushes all pending requests and executes the defer callbacks. 103 | * 104 | * @param {number=} number of milliseconds to flush. See {@link #defer.now} 105 | */ 106 | self.defer.flush = function(delay) { 107 | if (angular.isDefined(delay)) { 108 | self.defer.now += delay; 109 | } else { 110 | if (self.deferredFns.length) { 111 | self.defer.now = self.deferredFns[self.deferredFns.length-1].time; 112 | } else { 113 | throw Error('No deferred tasks to be flushed'); 114 | } 115 | } 116 | 117 | while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { 118 | self.deferredFns.shift().fn(); 119 | } 120 | }; 121 | /** 122 | * @name ngMock.$browser#defer.now 123 | * @propertyOf ngMock.$browser 124 | * 125 | * @description 126 | * Current milliseconds mock time. 127 | */ 128 | 129 | self.$$baseHref = ''; 130 | self.baseHref = function() { 131 | return this.$$baseHref; 132 | }; 133 | }; 134 | angular.mock.$Browser.prototype = { 135 | 136 | /** 137 | * @name ngMock.$browser#poll 138 | * @methodOf ngMock.$browser 139 | * 140 | * @description 141 | * run all fns in pollFns 142 | */ 143 | poll: function poll() { 144 | angular.forEach(this.pollFns, function(pollFn){ 145 | pollFn(); 146 | }); 147 | }, 148 | 149 | addPollFn: function(pollFn) { 150 | this.pollFns.push(pollFn); 151 | return pollFn; 152 | }, 153 | 154 | url: function(url, replace) { 155 | if (url) { 156 | this.$$url = url; 157 | return this; 158 | } 159 | 160 | return this.$$url; 161 | }, 162 | 163 | cookies: function(name, value) { 164 | if (name) { 165 | if (value == undefined) { 166 | delete this.cookieHash[name]; 167 | } else { 168 | if (angular.isString(value) && //strings only 169 | value.length <= 4096) { //strict cookie storage limits 170 | this.cookieHash[name] = value; 171 | } 172 | } 173 | } else { 174 | if (!angular.equals(this.cookieHash, this.lastCookieHash)) { 175 | this.lastCookieHash = angular.copy(this.cookieHash); 176 | this.cookieHash = angular.copy(this.cookieHash); 177 | } 178 | return this.cookieHash; 179 | } 180 | }, 181 | 182 | notifyWhenNoOutstandingRequests: function(fn) { 183 | fn(); 184 | } 185 | }; 186 | 187 | 188 | /** 189 | * @ngdoc object 190 | * @name ngMock.$exceptionHandlerProvider 191 | * 192 | * @description 193 | * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors passed 194 | * into the `$exceptionHandler`. 195 | */ 196 | 197 | /** 198 | * @ngdoc object 199 | * @name ngMock.$exceptionHandler 200 | * 201 | * @description 202 | * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed 203 | * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration 204 | * information. 205 | * 206 | * 207 | *
 208 |  *   describe('$exceptionHandlerProvider', function() {
 209 |  *
 210 |  *     it('should capture log messages and exceptions', function() {
 211 |  *
 212 |  *       module(function($exceptionHandlerProvider) {
 213 |  *         $exceptionHandlerProvider.mode('log');
 214 |  *       });
 215 |  *
 216 |  *       inject(function($log, $exceptionHandler, $timeout) {
 217 |  *         $timeout(function() { $log.log(1); });
 218 |  *         $timeout(function() { $log.log(2); throw 'banana peel'; });
 219 |  *         $timeout(function() { $log.log(3); });
 220 |  *         expect($exceptionHandler.errors).toEqual([]);
 221 |  *         expect($log.assertEmpty());
 222 |  *         $timeout.flush();
 223 |  *         expect($exceptionHandler.errors).toEqual(['banana peel']);
 224 |  *         expect($log.log.logs).toEqual([[1], [2], [3]]);
 225 |  *       });
 226 |  *     });
 227 |  *   });
 228 |  * 
229 | */ 230 | 231 | angular.mock.$ExceptionHandlerProvider = function() { 232 | var handler; 233 | 234 | /** 235 | * @ngdoc method 236 | * @name ngMock.$exceptionHandlerProvider#mode 237 | * @methodOf ngMock.$exceptionHandlerProvider 238 | * 239 | * @description 240 | * Sets the logging mode. 241 | * 242 | * @param {string} mode Mode of operation, defaults to `rethrow`. 243 | * 244 | * - `rethrow`: If any errors are passed into the handler in tests, it typically 245 | * means that there is a bug in the application or test, so this mock will 246 | * make these tests fail. 247 | * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an 248 | * array of errors in `$exceptionHandler.errors`, to allow later assertion of them. 249 | * See {@link ngMock.$log#assertEmpty assertEmpty()} and 250 | * {@link ngMock.$log#reset reset()} 251 | */ 252 | this.mode = function(mode) { 253 | switch(mode) { 254 | case 'rethrow': 255 | handler = function(e) { 256 | throw e; 257 | }; 258 | break; 259 | case 'log': 260 | var errors = []; 261 | 262 | handler = function(e) { 263 | if (arguments.length == 1) { 264 | errors.push(e); 265 | } else { 266 | errors.push([].slice.call(arguments, 0)); 267 | } 268 | }; 269 | 270 | handler.errors = errors; 271 | break; 272 | default: 273 | throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); 274 | } 275 | }; 276 | 277 | this.$get = function() { 278 | return handler; 279 | }; 280 | 281 | this.mode('rethrow'); 282 | }; 283 | 284 | 285 | /** 286 | * @ngdoc service 287 | * @name ngMock.$log 288 | * 289 | * @description 290 | * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays 291 | * (one array per logging level). These arrays are exposed as `logs` property of each of the 292 | * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. 293 | * 294 | */ 295 | angular.mock.$LogProvider = function() { 296 | 297 | function concat(array1, array2, index) { 298 | return array1.concat(Array.prototype.slice.call(array2, index)); 299 | } 300 | 301 | 302 | this.$get = function () { 303 | var $log = { 304 | log: function() { $log.log.logs.push(concat([], arguments, 0)); }, 305 | warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, 306 | info: function() { $log.info.logs.push(concat([], arguments, 0)); }, 307 | error: function() { $log.error.logs.push(concat([], arguments, 0)); } 308 | }; 309 | 310 | /** 311 | * @ngdoc method 312 | * @name ngMock.$log#reset 313 | * @methodOf ngMock.$log 314 | * 315 | * @description 316 | * Reset all of the logging arrays to empty. 317 | */ 318 | $log.reset = function () { 319 | /** 320 | * @ngdoc property 321 | * @name ngMock.$log#log.logs 322 | * @propertyOf ngMock.$log 323 | * 324 | * @description 325 | * Array of messages logged using {@link ngMock.$log#log}. 326 | * 327 | * @example 328 | *
 329 |        * $log.log('Some Log');
 330 |        * var first = $log.log.logs.unshift();
 331 |        * 
332 | */ 333 | $log.log.logs = []; 334 | /** 335 | * @ngdoc property 336 | * @name ngMock.$log#warn.logs 337 | * @propertyOf ngMock.$log 338 | * 339 | * @description 340 | * Array of messages logged using {@link ngMock.$log#warn}. 341 | * 342 | * @example 343 | *
 344 |        * $log.warn('Some Warning');
 345 |        * var first = $log.warn.logs.unshift();
 346 |        * 
347 | */ 348 | $log.warn.logs = []; 349 | /** 350 | * @ngdoc property 351 | * @name ngMock.$log#info.logs 352 | * @propertyOf ngMock.$log 353 | * 354 | * @description 355 | * Array of messages logged using {@link ngMock.$log#info}. 356 | * 357 | * @example 358 | *
 359 |        * $log.info('Some Info');
 360 |        * var first = $log.info.logs.unshift();
 361 |        * 
362 | */ 363 | $log.info.logs = []; 364 | /** 365 | * @ngdoc property 366 | * @name ngMock.$log#error.logs 367 | * @propertyOf ngMock.$log 368 | * 369 | * @description 370 | * Array of messages logged using {@link ngMock.$log#error}. 371 | * 372 | * @example 373 | *
 374 |        * $log.log('Some Error');
 375 |        * var first = $log.error.logs.unshift();
 376 |        * 
377 | */ 378 | $log.error.logs = []; 379 | }; 380 | 381 | /** 382 | * @ngdoc method 383 | * @name ngMock.$log#assertEmpty 384 | * @methodOf ngMock.$log 385 | * 386 | * @description 387 | * Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown. 388 | */ 389 | $log.assertEmpty = function() { 390 | var errors = []; 391 | angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) { 392 | angular.forEach($log[logLevel].logs, function(log) { 393 | angular.forEach(log, function (logItem) { 394 | errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); 395 | }); 396 | }); 397 | }); 398 | if (errors.length) { 399 | errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " + 400 | "log message was not checked and removed:"); 401 | errors.push(''); 402 | throw new Error(errors.join('\n---------\n')); 403 | } 404 | }; 405 | 406 | $log.reset(); 407 | return $log; 408 | }; 409 | }; 410 | 411 | 412 | (function() { 413 | var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; 414 | 415 | function jsonStringToDate(string){ 416 | var match; 417 | if (match = string.match(R_ISO8061_STR)) { 418 | var date = new Date(0), 419 | tzHour = 0, 420 | tzMin = 0; 421 | if (match[9]) { 422 | tzHour = int(match[9] + match[10]); 423 | tzMin = int(match[9] + match[11]); 424 | } 425 | date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); 426 | date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0)); 427 | return date; 428 | } 429 | return string; 430 | } 431 | 432 | function int(str) { 433 | return parseInt(str, 10); 434 | } 435 | 436 | function padNumber(num, digits, trim) { 437 | var neg = ''; 438 | if (num < 0) { 439 | neg = '-'; 440 | num = -num; 441 | } 442 | num = '' + num; 443 | while(num.length < digits) num = '0' + num; 444 | if (trim) 445 | num = num.substr(num.length - digits); 446 | return neg + num; 447 | } 448 | 449 | 450 | /** 451 | * @ngdoc object 452 | * @name angular.mock.TzDate 453 | * @description 454 | * 455 | * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. 456 | * 457 | * Mock of the Date type which has its timezone specified via constructor arg. 458 | * 459 | * The main purpose is to create Date-like instances with timezone fixed to the specified timezone 460 | * offset, so that we can test code that depends on local timezone settings without dependency on 461 | * the time zone settings of the machine where the code is running. 462 | * 463 | * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) 464 | * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* 465 | * 466 | * @example 467 | * !!!! WARNING !!!!! 468 | * This is not a complete Date object so only methods that were implemented can be called safely. 469 | * To make matters worse, TzDate instances inherit stuff from Date via a prototype. 470 | * 471 | * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is 472 | * incomplete we might be missing some non-standard methods. This can result in errors like: 473 | * "Date.prototype.foo called on incompatible Object". 474 | * 475 | *
 476 |    * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
 477 |    * newYearInBratislava.getTimezoneOffset() => -60;
 478 |    * newYearInBratislava.getFullYear() => 2010;
 479 |    * newYearInBratislava.getMonth() => 0;
 480 |    * newYearInBratislava.getDate() => 1;
 481 |    * newYearInBratislava.getHours() => 0;
 482 |    * newYearInBratislava.getMinutes() => 0;
 483 |    * 
484 | * 485 | */ 486 | angular.mock.TzDate = function (offset, timestamp) { 487 | var self = new Date(0); 488 | if (angular.isString(timestamp)) { 489 | var tsStr = timestamp; 490 | 491 | self.origDate = jsonStringToDate(timestamp); 492 | 493 | timestamp = self.origDate.getTime(); 494 | if (isNaN(timestamp)) 495 | throw { 496 | name: "Illegal Argument", 497 | message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" 498 | }; 499 | } else { 500 | self.origDate = new Date(timestamp); 501 | } 502 | 503 | var localOffset = new Date(timestamp).getTimezoneOffset(); 504 | self.offsetDiff = localOffset*60*1000 - offset*1000*60*60; 505 | self.date = new Date(timestamp + self.offsetDiff); 506 | 507 | self.getTime = function() { 508 | return self.date.getTime() - self.offsetDiff; 509 | }; 510 | 511 | self.toLocaleDateString = function() { 512 | return self.date.toLocaleDateString(); 513 | }; 514 | 515 | self.getFullYear = function() { 516 | return self.date.getFullYear(); 517 | }; 518 | 519 | self.getMonth = function() { 520 | return self.date.getMonth(); 521 | }; 522 | 523 | self.getDate = function() { 524 | return self.date.getDate(); 525 | }; 526 | 527 | self.getHours = function() { 528 | return self.date.getHours(); 529 | }; 530 | 531 | self.getMinutes = function() { 532 | return self.date.getMinutes(); 533 | }; 534 | 535 | self.getSeconds = function() { 536 | return self.date.getSeconds(); 537 | }; 538 | 539 | self.getTimezoneOffset = function() { 540 | return offset * 60; 541 | }; 542 | 543 | self.getUTCFullYear = function() { 544 | return self.origDate.getUTCFullYear(); 545 | }; 546 | 547 | self.getUTCMonth = function() { 548 | return self.origDate.getUTCMonth(); 549 | }; 550 | 551 | self.getUTCDate = function() { 552 | return self.origDate.getUTCDate(); 553 | }; 554 | 555 | self.getUTCHours = function() { 556 | return self.origDate.getUTCHours(); 557 | }; 558 | 559 | self.getUTCMinutes = function() { 560 | return self.origDate.getUTCMinutes(); 561 | }; 562 | 563 | self.getUTCSeconds = function() { 564 | return self.origDate.getUTCSeconds(); 565 | }; 566 | 567 | self.getUTCMilliseconds = function() { 568 | return self.origDate.getUTCMilliseconds(); 569 | }; 570 | 571 | self.getDay = function() { 572 | return self.date.getDay(); 573 | }; 574 | 575 | // provide this method only on browsers that already have it 576 | if (self.toISOString) { 577 | self.toISOString = function() { 578 | return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + 579 | padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + 580 | padNumber(self.origDate.getUTCDate(), 2) + 'T' + 581 | padNumber(self.origDate.getUTCHours(), 2) + ':' + 582 | padNumber(self.origDate.getUTCMinutes(), 2) + ':' + 583 | padNumber(self.origDate.getUTCSeconds(), 2) + '.' + 584 | padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z' 585 | } 586 | } 587 | 588 | //hide all methods not implemented in this mock that the Date prototype exposes 589 | var unimplementedMethods = ['getMilliseconds', 'getUTCDay', 590 | 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 591 | 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', 592 | 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', 593 | 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', 594 | 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; 595 | 596 | angular.forEach(unimplementedMethods, function(methodName) { 597 | self[methodName] = function() { 598 | throw Error("Method '" + methodName + "' is not implemented in the TzDate mock"); 599 | }; 600 | }); 601 | 602 | return self; 603 | }; 604 | 605 | //make "tzDateInstance instanceof Date" return true 606 | angular.mock.TzDate.prototype = Date.prototype; 607 | })(); 608 | 609 | 610 | /** 611 | * @ngdoc function 612 | * @name angular.mock.dump 613 | * @description 614 | * 615 | * *NOTE*: this is not an injectable instance, just a globally available function. 616 | * 617 | * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging. 618 | * 619 | * This method is also available on window, where it can be used to display objects on debug console. 620 | * 621 | * @param {*} object - any object to turn into string. 622 | * @return {string} a serialized string of the argument 623 | */ 624 | angular.mock.dump = function(object) { 625 | return serialize(object); 626 | 627 | function serialize(object) { 628 | var out; 629 | 630 | if (angular.isElement(object)) { 631 | object = angular.element(object); 632 | out = angular.element('
'); 633 | angular.forEach(object, function(element) { 634 | out.append(angular.element(element).clone()); 635 | }); 636 | out = out.html(); 637 | } else if (angular.isArray(object)) { 638 | out = []; 639 | angular.forEach(object, function(o) { 640 | out.push(serialize(o)); 641 | }); 642 | out = '[ ' + out.join(', ') + ' ]'; 643 | } else if (angular.isObject(object)) { 644 | if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { 645 | out = serializeScope(object); 646 | } else if (object instanceof Error) { 647 | out = object.stack || ('' + object.name + ': ' + object.message); 648 | } else { 649 | out = angular.toJson(object, true); 650 | } 651 | } else { 652 | out = String(object); 653 | } 654 | 655 | return out; 656 | } 657 | 658 | function serializeScope(scope, offset) { 659 | offset = offset || ' '; 660 | var log = [offset + 'Scope(' + scope.$id + '): {']; 661 | for ( var key in scope ) { 662 | if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) { 663 | log.push(' ' + key + ': ' + angular.toJson(scope[key])); 664 | } 665 | } 666 | var child = scope.$$childHead; 667 | while(child) { 668 | log.push(serializeScope(child, offset + ' ')); 669 | child = child.$$nextSibling; 670 | } 671 | log.push('}'); 672 | return log.join('\n' + offset); 673 | } 674 | }; 675 | 676 | /** 677 | * @ngdoc object 678 | * @name ngMock.$httpBackend 679 | * @description 680 | * Fake HTTP backend implementation suitable for unit testing applications that use the 681 | * {@link ng.$http $http service}. 682 | * 683 | * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less 684 | * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. 685 | * 686 | * During unit testing, we want our unit tests to run quickly and have no external dependencies so 687 | * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or 688 | * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is 689 | * to verify whether a certain request has been sent or not, or alternatively just let the 690 | * application make requests, respond with pre-trained responses and assert that the end result is 691 | * what we expect it to be. 692 | * 693 | * This mock implementation can be used to respond with static or dynamic responses via the 694 | * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). 695 | * 696 | * When an Angular application needs some data from a server, it calls the $http service, which 697 | * sends the request to a real server using $httpBackend service. With dependency injection, it is 698 | * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify 699 | * the requests and respond with some testing data without sending a request to real server. 700 | * 701 | * There are two ways to specify what test data should be returned as http responses by the mock 702 | * backend when the code under test makes http requests: 703 | * 704 | * - `$httpBackend.expect` - specifies a request expectation 705 | * - `$httpBackend.when` - specifies a backend definition 706 | * 707 | * 708 | * # Request Expectations vs Backend Definitions 709 | * 710 | * Request expectations provide a way to make assertions about requests made by the application and 711 | * to define responses for those requests. The test will fail if the expected requests are not made 712 | * or they are made in the wrong order. 713 | * 714 | * Backend definitions allow you to define a fake backend for your application which doesn't assert 715 | * if a particular request was made or not, it just returns a trained response if a request is made. 716 | * The test will pass whether or not the request gets made during testing. 717 | * 718 | * 719 | * 720 | * 721 | * 722 | * 723 | * 724 | * 725 | * 726 | * 727 | * 728 | * 729 | * 730 | * 731 | * 732 | * 733 | * 734 | * 735 | * 736 | * 737 | * 738 | * 739 | * 740 | * 741 | * 742 | * 743 | * 744 | * 745 | * 746 | * 747 | * 748 | * 749 | * 750 | * 751 | *
Request expectationsBackend definitions
Syntax.expect(...).respond(...).when(...).respond(...)
Typical usagestrict unit testsloose (black-box) unit testing
Fulfills multiple requestsNOYES
Order of requests mattersYESNO
Request requiredYESNO
Response requiredoptional (see below)YES
752 | * 753 | * In cases where both backend definitions and request expectations are specified during unit 754 | * testing, the request expectations are evaluated first. 755 | * 756 | * If a request expectation has no response specified, the algorithm will search your backend 757 | * definitions for an appropriate response. 758 | * 759 | * If a request didn't match any expectation or if the expectation doesn't have the response 760 | * defined, the backend definitions are evaluated in sequential order to see if any of them match 761 | * the request. The response from the first matched definition is returned. 762 | * 763 | * 764 | * # Flushing HTTP requests 765 | * 766 | * The $httpBackend used in production, always responds to requests with responses asynchronously. 767 | * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are 768 | * hard to write, follow and maintain. At the same time the testing mock, can't respond 769 | * synchronously because that would change the execution of the code under test. For this reason the 770 | * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending 771 | * requests and thus preserving the async api of the backend, while allowing the test to execute 772 | * synchronously. 773 | * 774 | * 775 | * # Unit testing with mock $httpBackend 776 | * 777 | *
 778 |    // controller
 779 |    function MyController($scope, $http) {
 780 |      $http.get('/auth.py').success(function(data) {
 781 |        $scope.user = data;
 782 |      });
 783 | 
 784 |      this.saveMessage = function(message) {
 785 |        $scope.status = 'Saving...';
 786 |        $http.post('/add-msg.py', message).success(function(response) {
 787 |          $scope.status = '';
 788 |        }).error(function() {
 789 |          $scope.status = 'ERROR!';
 790 |        });
 791 |      };
 792 |    }
 793 | 
 794 |    // testing controller
 795 |    var $httpBackend;
 796 | 
 797 |    beforeEach(inject(function($injector) {
 798 |      $httpBackend = $injector.get('$httpBackend');
 799 | 
 800 |      // backend definition common for all tests
 801 |      $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
 802 |    }));
 803 | 
 804 | 
 805 |    afterEach(function() {
 806 |      $httpBackend.verifyNoOutstandingExpectation();
 807 |      $httpBackend.verifyNoOutstandingRequest();
 808 |    });
 809 | 
 810 | 
 811 |    it('should fetch authentication token', function() {
 812 |      $httpBackend.expectGET('/auth.py');
 813 |      var controller = scope.$new(MyController);
 814 |      $httpBackend.flush();
 815 |    });
 816 | 
 817 | 
 818 |    it('should send msg to server', function() {
 819 |      // now you don’t care about the authentication, but
 820 |      // the controller will still send the request and
 821 |      // $httpBackend will respond without you having to
 822 |      // specify the expectation and response for this request
 823 |      $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
 824 | 
 825 |      var controller = scope.$new(MyController);
 826 |      $httpBackend.flush();
 827 |      controller.saveMessage('message content');
 828 |      expect(controller.status).toBe('Saving...');
 829 |      $httpBackend.flush();
 830 |      expect(controller.status).toBe('');
 831 |    });
 832 | 
 833 | 
 834 |    it('should send auth header', function() {
 835 |      $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
 836 |        // check if the header was send, if it wasn't the expectation won't
 837 |        // match the request and the test will fail
 838 |        return headers['Authorization'] == 'xxx';
 839 |      }).respond(201, '');
 840 | 
 841 |      var controller = scope.$new(MyController);
 842 |      controller.saveMessage('whatever');
 843 |      $httpBackend.flush();
 844 |    });
 845 |    
846 | */ 847 | angular.mock.$HttpBackendProvider = function() { 848 | this.$get = [createHttpBackendMock]; 849 | }; 850 | 851 | /** 852 | * General factory function for $httpBackend mock. 853 | * Returns instance for unit testing (when no arguments specified): 854 | * - passing through is disabled 855 | * - auto flushing is disabled 856 | * 857 | * Returns instance for e2e testing (when `$delegate` and `$browser` specified): 858 | * - passing through (delegating request to real backend) is enabled 859 | * - auto flushing is enabled 860 | * 861 | * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) 862 | * @param {Object=} $browser Auto-flushing enabled if specified 863 | * @return {Object} Instance of $httpBackend mock 864 | */ 865 | function createHttpBackendMock($delegate, $browser) { 866 | var definitions = [], 867 | expectations = [], 868 | responses = [], 869 | responsesPush = angular.bind(responses, responses.push); 870 | 871 | function createResponse(status, data, headers) { 872 | if (angular.isFunction(status)) return status; 873 | 874 | return function() { 875 | return angular.isNumber(status) 876 | ? [status, data, headers] 877 | : [200, status, data]; 878 | }; 879 | } 880 | 881 | // TODO(vojta): change params to: method, url, data, headers, callback 882 | function $httpBackend(method, url, data, callback, headers) { 883 | var xhr = new MockXhr(), 884 | expectation = expectations[0], 885 | wasExpected = false; 886 | 887 | function prettyPrint(data) { 888 | return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) 889 | ? data 890 | : angular.toJson(data); 891 | } 892 | 893 | if (expectation && expectation.match(method, url)) { 894 | if (!expectation.matchData(data)) 895 | throw Error('Expected ' + expectation + ' with different data\n' + 896 | 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); 897 | 898 | if (!expectation.matchHeaders(headers)) 899 | throw Error('Expected ' + expectation + ' with different headers\n' + 900 | 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + 901 | prettyPrint(headers)); 902 | 903 | expectations.shift(); 904 | 905 | if (expectation.response) { 906 | responses.push(function() { 907 | var response = expectation.response(method, url, data, headers); 908 | xhr.$$respHeaders = response[2]; 909 | callback(response[0], response[1], xhr.getAllResponseHeaders()); 910 | }); 911 | return; 912 | } 913 | wasExpected = true; 914 | } 915 | 916 | var i = -1, definition; 917 | while ((definition = definitions[++i])) { 918 | if (definition.match(method, url, data, headers || {})) { 919 | if (definition.response) { 920 | // if $browser specified, we do auto flush all requests 921 | ($browser ? $browser.defer : responsesPush)(function() { 922 | var response = definition.response(method, url, data, headers); 923 | xhr.$$respHeaders = response[2]; 924 | callback(response[0], response[1], xhr.getAllResponseHeaders()); 925 | }); 926 | } else if (definition.passThrough) { 927 | $delegate(method, url, data, callback, headers); 928 | } else throw Error('No response defined !'); 929 | return; 930 | } 931 | } 932 | throw wasExpected ? 933 | Error('No response defined !') : 934 | Error('Unexpected request: ' + method + ' ' + url + '\n' + 935 | (expectation ? 'Expected ' + expectation : 'No more request expected')); 936 | } 937 | 938 | /** 939 | * @ngdoc method 940 | * @name ngMock.$httpBackend#when 941 | * @methodOf ngMock.$httpBackend 942 | * @description 943 | * Creates a new backend definition. 944 | * 945 | * @param {string} method HTTP method. 946 | * @param {string|RegExp} url HTTP url. 947 | * @param {(string|RegExp)=} data HTTP request body. 948 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 949 | * object and returns true if the headers match the current definition. 950 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 951 | * request is handled. 952 | * 953 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 954 | * – The respond method takes a set of static data to be returned or a function that can return 955 | * an array containing response status (number), response data (string) and response headers 956 | * (Object). 957 | */ 958 | $httpBackend.when = function(method, url, data, headers) { 959 | var definition = new MockHttpExpectation(method, url, data, headers), 960 | chain = { 961 | respond: function(status, data, headers) { 962 | definition.response = createResponse(status, data, headers); 963 | } 964 | }; 965 | 966 | if ($browser) { 967 | chain.passThrough = function() { 968 | definition.passThrough = true; 969 | }; 970 | } 971 | 972 | definitions.push(definition); 973 | return chain; 974 | }; 975 | 976 | /** 977 | * @ngdoc method 978 | * @name ngMock.$httpBackend#whenGET 979 | * @methodOf ngMock.$httpBackend 980 | * @description 981 | * Creates a new backend definition for GET requests. For more info see `when()`. 982 | * 983 | * @param {string|RegExp} url HTTP url. 984 | * @param {(Object|function(Object))=} headers HTTP headers. 985 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 986 | * request is handled. 987 | */ 988 | 989 | /** 990 | * @ngdoc method 991 | * @name ngMock.$httpBackend#whenHEAD 992 | * @methodOf ngMock.$httpBackend 993 | * @description 994 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 995 | * 996 | * @param {string|RegExp} url HTTP url. 997 | * @param {(Object|function(Object))=} headers HTTP headers. 998 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 999 | * request is handled. 1000 | */ 1001 | 1002 | /** 1003 | * @ngdoc method 1004 | * @name ngMock.$httpBackend#whenDELETE 1005 | * @methodOf ngMock.$httpBackend 1006 | * @description 1007 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 1008 | * 1009 | * @param {string|RegExp} url HTTP url. 1010 | * @param {(Object|function(Object))=} headers HTTP headers. 1011 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1012 | * request is handled. 1013 | */ 1014 | 1015 | /** 1016 | * @ngdoc method 1017 | * @name ngMock.$httpBackend#whenPOST 1018 | * @methodOf ngMock.$httpBackend 1019 | * @description 1020 | * Creates a new backend definition for POST requests. For more info see `when()`. 1021 | * 1022 | * @param {string|RegExp} url HTTP url. 1023 | * @param {(string|RegExp)=} data HTTP request body. 1024 | * @param {(Object|function(Object))=} headers HTTP headers. 1025 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1026 | * request is handled. 1027 | */ 1028 | 1029 | /** 1030 | * @ngdoc method 1031 | * @name ngMock.$httpBackend#whenPUT 1032 | * @methodOf ngMock.$httpBackend 1033 | * @description 1034 | * Creates a new backend definition for PUT requests. For more info see `when()`. 1035 | * 1036 | * @param {string|RegExp} url HTTP url. 1037 | * @param {(string|RegExp)=} data HTTP request body. 1038 | * @param {(Object|function(Object))=} headers HTTP headers. 1039 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1040 | * request is handled. 1041 | */ 1042 | 1043 | /** 1044 | * @ngdoc method 1045 | * @name ngMock.$httpBackend#whenJSONP 1046 | * @methodOf ngMock.$httpBackend 1047 | * @description 1048 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 1049 | * 1050 | * @param {string|RegExp} url HTTP url. 1051 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1052 | * request is handled. 1053 | */ 1054 | createShortMethods('when'); 1055 | 1056 | 1057 | /** 1058 | * @ngdoc method 1059 | * @name ngMock.$httpBackend#expect 1060 | * @methodOf ngMock.$httpBackend 1061 | * @description 1062 | * Creates a new request expectation. 1063 | * 1064 | * @param {string} method HTTP method. 1065 | * @param {string|RegExp} url HTTP url. 1066 | * @param {(string|RegExp)=} data HTTP request body. 1067 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1068 | * object and returns true if the headers match the current expectation. 1069 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1070 | * request is handled. 1071 | * 1072 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 1073 | * – The respond method takes a set of static data to be returned or a function that can return 1074 | * an array containing response status (number), response data (string) and response headers 1075 | * (Object). 1076 | */ 1077 | $httpBackend.expect = function(method, url, data, headers) { 1078 | var expectation = new MockHttpExpectation(method, url, data, headers); 1079 | expectations.push(expectation); 1080 | return { 1081 | respond: function(status, data, headers) { 1082 | expectation.response = createResponse(status, data, headers); 1083 | } 1084 | }; 1085 | }; 1086 | 1087 | 1088 | /** 1089 | * @ngdoc method 1090 | * @name ngMock.$httpBackend#expectGET 1091 | * @methodOf ngMock.$httpBackend 1092 | * @description 1093 | * Creates a new request expectation for GET requests. For more info see `expect()`. 1094 | * 1095 | * @param {string|RegExp} url HTTP url. 1096 | * @param {Object=} headers HTTP headers. 1097 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1098 | * request is handled. See #expect for more info. 1099 | */ 1100 | 1101 | /** 1102 | * @ngdoc method 1103 | * @name ngMock.$httpBackend#expectHEAD 1104 | * @methodOf ngMock.$httpBackend 1105 | * @description 1106 | * Creates a new request expectation for HEAD requests. For more info see `expect()`. 1107 | * 1108 | * @param {string|RegExp} url HTTP url. 1109 | * @param {Object=} headers HTTP headers. 1110 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1111 | * request is handled. 1112 | */ 1113 | 1114 | /** 1115 | * @ngdoc method 1116 | * @name ngMock.$httpBackend#expectDELETE 1117 | * @methodOf ngMock.$httpBackend 1118 | * @description 1119 | * Creates a new request expectation for DELETE requests. For more info see `expect()`. 1120 | * 1121 | * @param {string|RegExp} url HTTP url. 1122 | * @param {Object=} headers HTTP headers. 1123 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1124 | * request is handled. 1125 | */ 1126 | 1127 | /** 1128 | * @ngdoc method 1129 | * @name ngMock.$httpBackend#expectPOST 1130 | * @methodOf ngMock.$httpBackend 1131 | * @description 1132 | * Creates a new request expectation for POST requests. For more info see `expect()`. 1133 | * 1134 | * @param {string|RegExp} url HTTP url. 1135 | * @param {(string|RegExp)=} data HTTP request body. 1136 | * @param {Object=} headers HTTP headers. 1137 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1138 | * request is handled. 1139 | */ 1140 | 1141 | /** 1142 | * @ngdoc method 1143 | * @name ngMock.$httpBackend#expectPUT 1144 | * @methodOf ngMock.$httpBackend 1145 | * @description 1146 | * Creates a new request expectation for PUT requests. For more info see `expect()`. 1147 | * 1148 | * @param {string|RegExp} url HTTP url. 1149 | * @param {(string|RegExp)=} data HTTP request body. 1150 | * @param {Object=} headers HTTP headers. 1151 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1152 | * request is handled. 1153 | */ 1154 | 1155 | /** 1156 | * @ngdoc method 1157 | * @name ngMock.$httpBackend#expectPATCH 1158 | * @methodOf ngMock.$httpBackend 1159 | * @description 1160 | * Creates a new request expectation for PATCH requests. For more info see `expect()`. 1161 | * 1162 | * @param {string|RegExp} url HTTP url. 1163 | * @param {(string|RegExp)=} data HTTP request body. 1164 | * @param {Object=} headers HTTP headers. 1165 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1166 | * request is handled. 1167 | */ 1168 | 1169 | /** 1170 | * @ngdoc method 1171 | * @name ngMock.$httpBackend#expectJSONP 1172 | * @methodOf ngMock.$httpBackend 1173 | * @description 1174 | * Creates a new request expectation for JSONP requests. For more info see `expect()`. 1175 | * 1176 | * @param {string|RegExp} url HTTP url. 1177 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1178 | * request is handled. 1179 | */ 1180 | createShortMethods('expect'); 1181 | 1182 | 1183 | /** 1184 | * @ngdoc method 1185 | * @name ngMock.$httpBackend#flush 1186 | * @methodOf ngMock.$httpBackend 1187 | * @description 1188 | * Flushes all pending requests using the trained responses. 1189 | * 1190 | * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, 1191 | * all pending requests will be flushed. If there are no pending requests when the flush method 1192 | * is called an exception is thrown (as this typically a sign of programming error). 1193 | */ 1194 | $httpBackend.flush = function(count) { 1195 | if (!responses.length) throw Error('No pending request to flush !'); 1196 | 1197 | if (angular.isDefined(count)) { 1198 | while (count--) { 1199 | if (!responses.length) throw Error('No more pending request to flush !'); 1200 | responses.shift()(); 1201 | } 1202 | } else { 1203 | while (responses.length) { 1204 | responses.shift()(); 1205 | } 1206 | } 1207 | $httpBackend.verifyNoOutstandingExpectation(); 1208 | }; 1209 | 1210 | 1211 | /** 1212 | * @ngdoc method 1213 | * @name ngMock.$httpBackend#verifyNoOutstandingExpectation 1214 | * @methodOf ngMock.$httpBackend 1215 | * @description 1216 | * Verifies that all of the requests defined via the `expect` api were made. If any of the 1217 | * requests were not made, verifyNoOutstandingExpectation throws an exception. 1218 | * 1219 | * Typically, you would call this method following each test case that asserts requests using an 1220 | * "afterEach" clause. 1221 | * 1222 | *
1223 |    *   afterEach($httpBackend.verifyExpectations);
1224 |    * 
1225 | */ 1226 | $httpBackend.verifyNoOutstandingExpectation = function() { 1227 | if (expectations.length) { 1228 | throw Error('Unsatisfied requests: ' + expectations.join(', ')); 1229 | } 1230 | }; 1231 | 1232 | 1233 | /** 1234 | * @ngdoc method 1235 | * @name ngMock.$httpBackend#verifyNoOutstandingRequest 1236 | * @methodOf ngMock.$httpBackend 1237 | * @description 1238 | * Verifies that there are no outstanding requests that need to be flushed. 1239 | * 1240 | * Typically, you would call this method following each test case that asserts requests using an 1241 | * "afterEach" clause. 1242 | * 1243 | *
1244 |    *   afterEach($httpBackend.verifyNoOutstandingRequest);
1245 |    * 
1246 | */ 1247 | $httpBackend.verifyNoOutstandingRequest = function() { 1248 | if (responses.length) { 1249 | throw Error('Unflushed requests: ' + responses.length); 1250 | } 1251 | }; 1252 | 1253 | 1254 | /** 1255 | * @ngdoc method 1256 | * @name ngMock.$httpBackend#resetExpectations 1257 | * @methodOf ngMock.$httpBackend 1258 | * @description 1259 | * Resets all request expectations, but preserves all backend definitions. Typically, you would 1260 | * call resetExpectations during a multiple-phase test when you want to reuse the same instance of 1261 | * $httpBackend mock. 1262 | */ 1263 | $httpBackend.resetExpectations = function() { 1264 | expectations.length = 0; 1265 | responses.length = 0; 1266 | }; 1267 | 1268 | return $httpBackend; 1269 | 1270 | 1271 | function createShortMethods(prefix) { 1272 | angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) { 1273 | $httpBackend[prefix + method] = function(url, headers) { 1274 | return $httpBackend[prefix](method, url, undefined, headers) 1275 | } 1276 | }); 1277 | 1278 | angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { 1279 | $httpBackend[prefix + method] = function(url, data, headers) { 1280 | return $httpBackend[prefix](method, url, data, headers) 1281 | } 1282 | }); 1283 | } 1284 | } 1285 | 1286 | function MockHttpExpectation(method, url, data, headers) { 1287 | 1288 | this.data = data; 1289 | this.headers = headers; 1290 | 1291 | this.match = function(m, u, d, h) { 1292 | if (method != m) return false; 1293 | if (!this.matchUrl(u)) return false; 1294 | if (angular.isDefined(d) && !this.matchData(d)) return false; 1295 | if (angular.isDefined(h) && !this.matchHeaders(h)) return false; 1296 | return true; 1297 | }; 1298 | 1299 | this.matchUrl = function(u) { 1300 | if (!url) return true; 1301 | if (angular.isFunction(url.test)) return url.test(u); 1302 | return url == u; 1303 | }; 1304 | 1305 | this.matchHeaders = function(h) { 1306 | if (angular.isUndefined(headers)) return true; 1307 | if (angular.isFunction(headers)) return headers(h); 1308 | return angular.equals(headers, h); 1309 | }; 1310 | 1311 | this.matchData = function(d) { 1312 | if (angular.isUndefined(data)) return true; 1313 | if (data && angular.isFunction(data.test)) return data.test(d); 1314 | if (data && !angular.isString(data)) return angular.toJson(data) == d; 1315 | return data == d; 1316 | }; 1317 | 1318 | this.toString = function() { 1319 | return method + ' ' + url; 1320 | }; 1321 | } 1322 | 1323 | function MockXhr() { 1324 | 1325 | // hack for testing $http, $httpBackend 1326 | MockXhr.$$lastInstance = this; 1327 | 1328 | this.open = function(method, url, async) { 1329 | this.$$method = method; 1330 | this.$$url = url; 1331 | this.$$async = async; 1332 | this.$$reqHeaders = {}; 1333 | this.$$respHeaders = {}; 1334 | }; 1335 | 1336 | this.send = function(data) { 1337 | this.$$data = data; 1338 | }; 1339 | 1340 | this.setRequestHeader = function(key, value) { 1341 | this.$$reqHeaders[key] = value; 1342 | }; 1343 | 1344 | this.getResponseHeader = function(name) { 1345 | // the lookup must be case insensitive, that's why we try two quick lookups and full scan at last 1346 | var header = this.$$respHeaders[name]; 1347 | if (header) return header; 1348 | 1349 | name = angular.lowercase(name); 1350 | header = this.$$respHeaders[name]; 1351 | if (header) return header; 1352 | 1353 | header = undefined; 1354 | angular.forEach(this.$$respHeaders, function(headerVal, headerName) { 1355 | if (!header && angular.lowercase(headerName) == name) header = headerVal; 1356 | }); 1357 | return header; 1358 | }; 1359 | 1360 | this.getAllResponseHeaders = function() { 1361 | var lines = []; 1362 | 1363 | angular.forEach(this.$$respHeaders, function(value, key) { 1364 | lines.push(key + ': ' + value); 1365 | }); 1366 | return lines.join('\n'); 1367 | }; 1368 | 1369 | this.abort = angular.noop; 1370 | } 1371 | 1372 | 1373 | /** 1374 | * @ngdoc function 1375 | * @name ngMock.$timeout 1376 | * @description 1377 | * 1378 | * This service is just a simple decorator for {@link ng.$timeout $timeout} service 1379 | * that adds a "flush" method. 1380 | */ 1381 | 1382 | /** 1383 | * @ngdoc method 1384 | * @name ngMock.$timeout#flush 1385 | * @methodOf ngMock.$timeout 1386 | * @description 1387 | * 1388 | * Flushes the queue of pending tasks. 1389 | */ 1390 | 1391 | /** 1392 | * 1393 | */ 1394 | angular.mock.$RootElementProvider = function() { 1395 | this.$get = function() { 1396 | return angular.element('
'); 1397 | } 1398 | }; 1399 | 1400 | /** 1401 | * @ngdoc overview 1402 | * @name ngMock 1403 | * @description 1404 | * 1405 | * The `ngMock` is an angular module which is used with `ng` module and adds unit-test configuration as well as useful 1406 | * mocks to the {@link AUTO.$injector $injector}. 1407 | */ 1408 | angular.module('ngMock', ['ng']).provider({ 1409 | $browser: angular.mock.$BrowserProvider, 1410 | $exceptionHandler: angular.mock.$ExceptionHandlerProvider, 1411 | $log: angular.mock.$LogProvider, 1412 | $httpBackend: angular.mock.$HttpBackendProvider, 1413 | $rootElement: angular.mock.$RootElementProvider 1414 | }).config(function($provide) { 1415 | $provide.decorator('$timeout', function($delegate, $browser) { 1416 | $delegate.flush = function() { 1417 | $browser.defer.flush(); 1418 | }; 1419 | return $delegate; 1420 | }); 1421 | }); 1422 | 1423 | 1424 | /** 1425 | * @ngdoc overview 1426 | * @name ngMockE2E 1427 | * @description 1428 | * 1429 | * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. 1430 | * Currently there is only one mock present in this module - 1431 | * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. 1432 | */ 1433 | angular.module('ngMockE2E', ['ng']).config(function($provide) { 1434 | $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); 1435 | }); 1436 | 1437 | /** 1438 | * @ngdoc object 1439 | * @name ngMockE2E.$httpBackend 1440 | * @description 1441 | * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of 1442 | * applications that use the {@link ng.$http $http service}. 1443 | * 1444 | * *Note*: For fake http backend implementation suitable for unit testing please see 1445 | * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. 1446 | * 1447 | * This implementation can be used to respond with static or dynamic responses via the `when` api 1448 | * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the 1449 | * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch 1450 | * templates from a webserver). 1451 | * 1452 | * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application 1453 | * is being developed with the real backend api replaced with a mock, it is often desirable for 1454 | * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch 1455 | * templates or static files from the webserver). To configure the backend with this behavior 1456 | * use the `passThrough` request handler of `when` instead of `respond`. 1457 | * 1458 | * Additionally, we don't want to manually have to flush mocked out requests like we do during unit 1459 | * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests 1460 | * automatically, closely simulating the behavior of the XMLHttpRequest object. 1461 | * 1462 | * To setup the application to run with this http backend, you have to create a module that depends 1463 | * on the `ngMockE2E` and your application modules and defines the fake backend: 1464 | * 1465 | *
1466 |  *   myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
1467 |  *   myAppDev.run(function($httpBackend) {
1468 |  *     phones = [{name: 'phone1'}, {name: 'phone2'}];
1469 |  *
1470 |  *     // returns the current list of phones
1471 |  *     $httpBackend.whenGET('/phones').respond(phones);
1472 |  *
1473 |  *     // adds a new phone to the phones array
1474 |  *     $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
1475 |  *       phones.push(angular.fromJSON(data));
1476 |  *     });
1477 |  *     $httpBackend.whenGET(/^\/templates\//).passThrough();
1478 |  *     //...
1479 |  *   });
1480 |  * 
1481 | * 1482 | * Afterwards, bootstrap your app with this new module. 1483 | */ 1484 | 1485 | /** 1486 | * @ngdoc method 1487 | * @name ngMockE2E.$httpBackend#when 1488 | * @methodOf ngMockE2E.$httpBackend 1489 | * @description 1490 | * Creates a new backend definition. 1491 | * 1492 | * @param {string} method HTTP method. 1493 | * @param {string|RegExp} url HTTP url. 1494 | * @param {(string|RegExp)=} data HTTP request body. 1495 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1496 | * object and returns true if the headers match the current definition. 1497 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1498 | * control how a matched request is handled. 1499 | * 1500 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 1501 | * – The respond method takes a set of static data to be returned or a function that can return 1502 | * an array containing response status (number), response data (string) and response headers 1503 | * (Object). 1504 | * - passThrough – `{function()}` – Any request matching a backend definition with `passThrough` 1505 | * handler, will be pass through to the real backend (an XHR request will be made to the 1506 | * server. 1507 | */ 1508 | 1509 | /** 1510 | * @ngdoc method 1511 | * @name ngMockE2E.$httpBackend#whenGET 1512 | * @methodOf ngMockE2E.$httpBackend 1513 | * @description 1514 | * Creates a new backend definition for GET requests. For more info see `when()`. 1515 | * 1516 | * @param {string|RegExp} url HTTP url. 1517 | * @param {(Object|function(Object))=} headers HTTP headers. 1518 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1519 | * control how a matched request is handled. 1520 | */ 1521 | 1522 | /** 1523 | * @ngdoc method 1524 | * @name ngMockE2E.$httpBackend#whenHEAD 1525 | * @methodOf ngMockE2E.$httpBackend 1526 | * @description 1527 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 1528 | * 1529 | * @param {string|RegExp} url HTTP url. 1530 | * @param {(Object|function(Object))=} headers HTTP headers. 1531 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1532 | * control how a matched request is handled. 1533 | */ 1534 | 1535 | /** 1536 | * @ngdoc method 1537 | * @name ngMockE2E.$httpBackend#whenDELETE 1538 | * @methodOf ngMockE2E.$httpBackend 1539 | * @description 1540 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 1541 | * 1542 | * @param {string|RegExp} url HTTP url. 1543 | * @param {(Object|function(Object))=} headers HTTP headers. 1544 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1545 | * control how a matched request is handled. 1546 | */ 1547 | 1548 | /** 1549 | * @ngdoc method 1550 | * @name ngMockE2E.$httpBackend#whenPOST 1551 | * @methodOf ngMockE2E.$httpBackend 1552 | * @description 1553 | * Creates a new backend definition for POST requests. For more info see `when()`. 1554 | * 1555 | * @param {string|RegExp} url HTTP url. 1556 | * @param {(string|RegExp)=} data HTTP request body. 1557 | * @param {(Object|function(Object))=} headers HTTP headers. 1558 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1559 | * control how a matched request is handled. 1560 | */ 1561 | 1562 | /** 1563 | * @ngdoc method 1564 | * @name ngMockE2E.$httpBackend#whenPUT 1565 | * @methodOf ngMockE2E.$httpBackend 1566 | * @description 1567 | * Creates a new backend definition for PUT requests. For more info see `when()`. 1568 | * 1569 | * @param {string|RegExp} url HTTP url. 1570 | * @param {(string|RegExp)=} data HTTP request body. 1571 | * @param {(Object|function(Object))=} headers HTTP headers. 1572 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1573 | * control how a matched request is handled. 1574 | */ 1575 | 1576 | /** 1577 | * @ngdoc method 1578 | * @name ngMockE2E.$httpBackend#whenPATCH 1579 | * @methodOf ngMockE2E.$httpBackend 1580 | * @description 1581 | * Creates a new backend definition for PATCH requests. For more info see `when()`. 1582 | * 1583 | * @param {string|RegExp} url HTTP url. 1584 | * @param {(string|RegExp)=} data HTTP request body. 1585 | * @param {(Object|function(Object))=} headers HTTP headers. 1586 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1587 | * control how a matched request is handled. 1588 | */ 1589 | 1590 | /** 1591 | * @ngdoc method 1592 | * @name ngMockE2E.$httpBackend#whenJSONP 1593 | * @methodOf ngMockE2E.$httpBackend 1594 | * @description 1595 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 1596 | * 1597 | * @param {string|RegExp} url HTTP url. 1598 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1599 | * control how a matched request is handled. 1600 | */ 1601 | angular.mock.e2e = {}; 1602 | angular.mock.e2e.$httpBackendDecorator = ['$delegate', '$browser', createHttpBackendMock]; 1603 | 1604 | 1605 | angular.mock.clearDataCache = function() { 1606 | var key, 1607 | cache = angular.element.cache; 1608 | 1609 | for(key in cache) { 1610 | if (cache.hasOwnProperty(key)) { 1611 | var handle = cache[key].handle; 1612 | 1613 | handle && angular.element(handle.elem).unbind(); 1614 | delete cache[key]; 1615 | } 1616 | } 1617 | }; 1618 | 1619 | 1620 | window.jstestdriver && (function(window) { 1621 | /** 1622 | * Global method to output any number of objects into JSTD console. Useful for debugging. 1623 | */ 1624 | window.dump = function() { 1625 | var args = []; 1626 | angular.forEach(arguments, function(arg) { 1627 | args.push(angular.mock.dump(arg)); 1628 | }); 1629 | jstestdriver.console.log.apply(jstestdriver.console, args); 1630 | if (window.console) { 1631 | window.console.log.apply(window.console, args); 1632 | } 1633 | }; 1634 | })(window); 1635 | 1636 | 1637 | window.jasmine && (function(window) { 1638 | 1639 | afterEach(function() { 1640 | var spec = getCurrentSpec(); 1641 | var injector = spec.$injector; 1642 | 1643 | spec.$injector = null; 1644 | spec.$modules = null; 1645 | 1646 | if (injector) { 1647 | injector.get('$rootElement').unbind(); 1648 | injector.get('$browser').pollFns.length = 0; 1649 | } 1650 | 1651 | angular.mock.clearDataCache(); 1652 | 1653 | // clean up jquery's fragment cache 1654 | angular.forEach(angular.element.fragments, function(val, key) { 1655 | delete angular.element.fragments[key]; 1656 | }); 1657 | 1658 | MockXhr.$$lastInstance = null; 1659 | 1660 | angular.forEach(angular.callbacks, function(val, key) { 1661 | delete angular.callbacks[key]; 1662 | }); 1663 | angular.callbacks.counter = 0; 1664 | }); 1665 | 1666 | function getCurrentSpec() { 1667 | return jasmine.getEnv().currentSpec; 1668 | } 1669 | 1670 | function isSpecRunning() { 1671 | var spec = getCurrentSpec(); 1672 | return spec && spec.queue.running; 1673 | } 1674 | 1675 | /** 1676 | * @ngdoc function 1677 | * @name angular.mock.module 1678 | * @description 1679 | * 1680 | * *NOTE*: This function is also published on window for easy access.
1681 | * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. 1682 | * 1683 | * This function registers a module configuration code. It collects the configuration information 1684 | * which will be used when the injector is created by {@link angular.mock.inject inject}. 1685 | * 1686 | * See {@link angular.mock.inject inject} for usage example 1687 | * 1688 | * @param {...(string|Function)} fns any number of modules which are represented as string 1689 | * aliases or as anonymous module initialization functions. The modules are used to 1690 | * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. 1691 | */ 1692 | window.module = angular.mock.module = function() { 1693 | var moduleFns = Array.prototype.slice.call(arguments, 0); 1694 | return isSpecRunning() ? workFn() : workFn; 1695 | ///////////////////// 1696 | function workFn() { 1697 | var spec = getCurrentSpec(); 1698 | if (spec.$injector) { 1699 | throw Error('Injector already created, can not register a module!'); 1700 | } else { 1701 | var modules = spec.$modules || (spec.$modules = []); 1702 | angular.forEach(moduleFns, function(module) { 1703 | modules.push(module); 1704 | }); 1705 | } 1706 | } 1707 | }; 1708 | 1709 | /** 1710 | * @ngdoc function 1711 | * @name angular.mock.inject 1712 | * @description 1713 | * 1714 | * *NOTE*: This function is also published on window for easy access.
1715 | * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. 1716 | * 1717 | * The inject function wraps a function into an injectable function. The inject() creates new 1718 | * instance of {@link AUTO.$injector $injector} per test, which is then used for 1719 | * resolving references. 1720 | * 1721 | * See also {@link angular.mock.module module} 1722 | * 1723 | * Example of what a typical jasmine tests looks like with the inject method. 1724 | *
1725 |    *
1726 |    *   angular.module('myApplicationModule', [])
1727 |    *       .value('mode', 'app')
1728 |    *       .value('version', 'v1.0.1');
1729 |    *
1730 |    *
1731 |    *   describe('MyApp', function() {
1732 |    *
1733 |    *     // You need to load modules that you want to test,
1734 |    *     // it loads only the "ng" module by default.
1735 |    *     beforeEach(module('myApplicationModule'));
1736 |    *
1737 |    *
1738 |    *     // inject() is used to inject arguments of all given functions
1739 |    *     it('should provide a version', inject(function(mode, version) {
1740 |    *       expect(version).toEqual('v1.0.1');
1741 |    *       expect(mode).toEqual('app');
1742 |    *     }));
1743 |    *
1744 |    *
1745 |    *     // The inject and module method can also be used inside of the it or beforeEach
1746 |    *     it('should override a version and test the new version is injected', function() {
1747 |    *       // module() takes functions or strings (module aliases)
1748 |    *       module(function($provide) {
1749 |    *         $provide.value('version', 'overridden'); // override version here
1750 |    *       });
1751 |    *
1752 |    *       inject(function(version) {
1753 |    *         expect(version).toEqual('overridden');
1754 |    *       });
1755 |    *     ));
1756 |    *   });
1757 |    *
1758 |    * 
1759 | * 1760 | * @param {...Function} fns any number of functions which will be injected using the injector. 1761 | */ 1762 | window.inject = angular.mock.inject = function() { 1763 | var blockFns = Array.prototype.slice.call(arguments, 0); 1764 | var errorForStack = new Error('Declaration Location'); 1765 | return isSpecRunning() ? workFn() : workFn; 1766 | ///////////////////// 1767 | function workFn() { 1768 | var spec = getCurrentSpec(); 1769 | var modules = spec.$modules || []; 1770 | modules.unshift('ngMock'); 1771 | modules.unshift('ng'); 1772 | var injector = spec.$injector; 1773 | if (!injector) { 1774 | injector = spec.$injector = angular.injector(modules); 1775 | } 1776 | for(var i = 0, ii = blockFns.length; i < ii; i++) { 1777 | try { 1778 | injector.invoke(blockFns[i] || angular.noop, this); 1779 | } catch (e) { 1780 | if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack; 1781 | throw e; 1782 | } finally { 1783 | errorForStack = null; 1784 | } 1785 | } 1786 | } 1787 | }; 1788 | })(window); 1789 | -------------------------------------------------------------------------------- /test/lib/angular/version.txt: -------------------------------------------------------------------------------- 1 | 1.0.7 2 | -------------------------------------------------------------------------------- /test/unit/controllersSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for controllers go here */ 4 | 5 | describe('controllers', function(){ 6 | beforeEach(module('myApp.controllers')); 7 | 8 | 9 | it('should ....', inject(function() { 10 | //spec body 11 | })); 12 | 13 | it('should ....', inject(function() { 14 | //spec body 15 | })); 16 | }); 17 | -------------------------------------------------------------------------------- /test/unit/directivesSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for directives go here */ 4 | 5 | describe('directives', function() { 6 | beforeEach(module('myApp.directives')); 7 | 8 | describe('app-version', function() { 9 | it('should print current version', function() { 10 | module(function($provide) { 11 | $provide.value('version', 'TEST_VER'); 12 | }); 13 | inject(function($compile, $rootScope) { 14 | var element = $compile('')($rootScope); 15 | expect(element.text()).toEqual('TEST_VER'); 16 | }); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/filtersSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for filters go here */ 4 | 5 | describe('filter', function() { 6 | beforeEach(module('myApp.filters')); 7 | 8 | 9 | describe('interpolate', function() { 10 | beforeEach(module(function($provide) { 11 | $provide.value('version', 'TEST_VER'); 12 | })); 13 | 14 | 15 | it('should replace VERSION', inject(function(interpolateFilter) { 16 | expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after'); 17 | })); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/servicesSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for services go here */ 4 | 5 | describe('service', function() { 6 | beforeEach(module('myApp.services')); 7 | 8 | 9 | describe('version', function() { 10 | it('should return current version', inject(function(version) { 11 | expect(version).toEqual('0.1'); 12 | })); 13 | }); 14 | }); 15 | --------------------------------------------------------------------------------