├── .gitignore ├── Google Maps.png ├── README.md ├── Untitled 2-1 (dragged).tiff ├── Untitled 2.png ├── Untitled 3-1 (dragged).tiff ├── Untitled 3.png ├── app ├── css │ ├── .gitignore │ └── app.css ├── img │ └── .gitignore ├── 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 │ ├── .gitignore │ ├── partial1.html │ └── partial2.html ├── config ├── testacular-e2e.conf.js └── testacular.conf.js ├── logs └── .gitignore ├── map.png ├── 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 ├── unnamed.jpg └── unnamed.png /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | nbproject 3 | manifest.mf 4 | build.xml 5 | 6 | .project 7 | .settings 8 | .idea/* 9 | -------------------------------------------------------------------------------- /Google Maps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glitchtank/angular-seed-master/cc5ccb5548c60f58468d821f19e723e61c001b79/Google Maps.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-seed — the seed for AngularJS apps 2 | 3 | This project is an application skeleton for a typical [AngularJS](http://angularjs.org/) web app. 4 | You can use it to quickly bootstrap your angular webapp projects and dev environment for these 5 | projects. 6 | 7 | The seed contains AngularJS libraries, test libraries and a bunch of scripts all preconfigured for 8 | instant web development gratification. Just clone the repo (or download the zip/tarball), start up 9 | our (or yours) webserver and you are ready to develop and test your application. 10 | 11 | The seed app doesn't do much, just shows how to wire two controllers and views together. You can 12 | check it out by opening app/index.html in your browser (might not work file `file://` scheme in 13 | certain browsers, see note below). 14 | 15 | _Note: While angular is client-side-only technology and it's possible to create angular webapps that 16 | don't require a backend server at all, we recommend hosting the project files using a local 17 | webserver during development to avoid issues with security restrictions (sandbox) in browsers. The 18 | sandbox implementation varies between browsers, but quite often prevents things like cookies, xhr, 19 | etc to function properly when an html page is opened via `file://` scheme instead of `http://`._ 20 | 21 | 22 | ## How to use angular-seed 23 | 24 | Clone the angular-seed repository and start hacking... 25 | 26 | 27 | ### Running the app during development 28 | 29 | You can pick one of these options: 30 | 31 | * serve this repository with your webserver 32 | * install node.js and run `scripts/web-server.js` 33 | 34 | Then navigate your browser to `http://localhost:/app/index.html` to see the app running in 35 | your browser. 36 | 37 | 38 | ### Running the app in production 39 | 40 | This really depends on how complex is your app and the overall infrastructure of your system, but 41 | the general rule is that all you need in production are all the files under the `app/` directory. 42 | Everything else should be omitted. 43 | 44 | Angular apps are really just a bunch of static html, css and js files that just need to be hosted 45 | somewhere, where they can be accessed by browsers. 46 | 47 | If your Angular app is talking to the backend server via xhr or other means, you need to figure 48 | out what is the best way to host the static files to comply with the same origin policy if 49 | applicable. Usually this is done by hosting the files by the backend server or through 50 | reverse-proxying the backend server(s) and a webserver(s). 51 | 52 | 53 | ### Running unit tests 54 | 55 | We recommend using [jasmine](http://pivotal.github.com/jasmine/) and 56 | [Testacular](http://vojtajina.github.com/testacular/) for your unit tests/specs, but you are free 57 | to use whatever works for you. 58 | 59 | Requires [node.js](http://nodejs.org/), Testacular (`sudo npm install -g testacular`) and a local 60 | or remote browser. 61 | 62 | * start `scripts/test.sh` (on windows: `scripts\test.bat`) 63 | * a browser will start and connect to the Testacular server (Chrome is default browser, others can be captured by loading the same url as the one in Chrome or by changing the `config/testacular.conf.js` file) 64 | * to run or re-run tests just change any of your source or test javascript files 65 | 66 | 67 | ### End to end testing 68 | 69 | Angular ships with a baked-in end-to-end test runner that understands angular, your app and allows 70 | you to write your tests with jasmine-like BDD syntax. 71 | 72 | Requires a webserver, node.js + `./scripts/web-server.js` or your backend server that hosts the angular static files. 73 | 74 | Check out the 75 | [end-to-end runner's documentation](http://docs.angularjs.org/guide/dev_guide.e2e-testing) for more 76 | info. 77 | 78 | * create your end-to-end tests in `test/e2e/scenarios.js` 79 | * serve your project directory with your http/backend server or node.js + `scripts/web-server.js` 80 | * to run do one of: 81 | * open `http://localhost:port/test/e2e/runner.html` in your browser 82 | * run the tests from console with [Testacular](vojtajina.github.com/testacular) via 83 | `scripts/e2e-test.sh` or `script/e2e-test.bat` 84 | 85 | 86 | ### Receiving updates from upstream 87 | 88 | When we upgrade angular-seed's repo with newer angular or testing library code, you can just 89 | fetch the changes and merge them into your project with git. 90 | 91 | 92 | ## Directory Layout 93 | 94 | app/ --> all of the files to be used in production 95 | css/ --> css files 96 | app.css --> default stylesheet 97 | img/ --> image files 98 | index.html --> app layout file (the main html template file of the app) 99 | index-async.html --> just like index.html, but loads js files asynchronously 100 | js/ --> javascript files 101 | app.js --> application 102 | controllers.js --> application controllers 103 | directives.js --> application directives 104 | filters.js --> custom angular filters 105 | services.js --> custom angular services 106 | lib/ --> angular and 3rd party javascript libraries 107 | angular/ 108 | angular.js --> the latest angular js 109 | angular.min.js --> the latest minified angular js 110 | angular-*.js --> angular add-on modules 111 | version.txt --> version number 112 | partials/ --> angular view partials (partial html templates) 113 | partial1.html 114 | partial2.html 115 | 116 | config/testacular.conf.js --> config file for running unit tests with Testacular 117 | config/testacular-e2e.conf.js --> config file for running e2e tests with Testacular 118 | 119 | scripts/ --> handy shell/js/ruby scripts 120 | e2e-test.sh --> runs end-to-end tests with Testacular (*nix) 121 | e2e-test.bat --> runs end-to-end tests with Testacular (windows) 122 | test.bat --> autotests unit tests with Testacular (windows) 123 | test.sh --> autotests unit tests with Testacular (*nix) 124 | web-server.js --> simple development webserver based on node.js 125 | 126 | test/ --> test source files and libraries 127 | e2e/ --> 128 | runner.html --> end-to-end test runner (open in your browser to run) 129 | scenarios.js --> end-to-end specs 130 | lib/ 131 | angular/ --> angular testing libraries 132 | angular-mocks.js --> mocks that replace certain angular services in tests 133 | angular-scenario.js --> angular's scenario (end-to-end) test runner library 134 | version.txt --> version file 135 | unit/ --> unit level specs/tests 136 | controllersSpec.js --> specs for controllers 137 | directivessSpec.js --> specs for directives 138 | filtersSpec.js --> specs for filters 139 | servicesSpec.js --> specs for services 140 | 141 | ## Contact 142 | 143 | For more information on AngularJS please check out http://angularjs.org/ 144 | -------------------------------------------------------------------------------- /Untitled 2-1 (dragged).tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glitchtank/angular-seed-master/cc5ccb5548c60f58468d821f19e723e61c001b79/Untitled 2-1 (dragged).tiff -------------------------------------------------------------------------------- /Untitled 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glitchtank/angular-seed-master/cc5ccb5548c60f58468d821f19e723e61c001b79/Untitled 2.png -------------------------------------------------------------------------------- /Untitled 3-1 (dragged).tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glitchtank/angular-seed-master/cc5ccb5548c60f58468d821f19e723e61c001b79/Untitled 3-1 (dragged).tiff -------------------------------------------------------------------------------- /Untitled 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glitchtank/angular-seed-master/cc5ccb5548c60f58468d821f19e723e61c001b79/Untitled 3.png -------------------------------------------------------------------------------- /app/css/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glitchtank/angular-seed-master/cc5ccb5548c60f58468d821f19e723e61c001b79/app/css/.gitignore -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/img/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glitchtank/angular-seed-master/cc5ccb5548c60f58468d821f19e723e61c001b79/app/img/.gitignore -------------------------------------------------------------------------------- /app/index-async.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 43 | My AngularJS App 44 | 45 | 46 | 47 | 51 | 52 |
53 | 54 |
Angular seed app: v
55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My AngularJS App 6 | 7 | 8 | 9 | 13 | 14 |
15 | 16 |
Angular seed app: v
17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /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']). 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 | /* Controllers */ 4 | 5 | 6 | function MyCtrl1() {} 7 | MyCtrl1.$inject = []; 8 | 9 | 10 | function MyCtrl2() { 11 | } 12 | MyCtrl2.$inject = []; 13 | -------------------------------------------------------------------------------- /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 | /* Services */ 4 | 5 | 6 | // Demonstrate how to register services 7 | // In this case it is a simple value service. 8 | angular.module('myApp.services', []). 9 | value('version', '0.1'); 10 | -------------------------------------------------------------------------------- /app/lib/angular/angular-cookies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.3 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 | factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) { 30 | var cookies = {}, 31 | lastCookies = {}, 32 | lastBrowserCookies, 33 | runEval = false, 34 | copy = angular.copy, 35 | isUndefined = angular.isUndefined; 36 | 37 | //creates a poller fn that copies all cookies from the $browser to service & inits the service 38 | $browser.addPollFn(function() { 39 | var currentCookies = $browser.cookies(); 40 | if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl 41 | lastBrowserCookies = currentCookies; 42 | copy(currentCookies, lastCookies); 43 | copy(currentCookies, cookies); 44 | if (runEval) $rootScope.$apply(); 45 | } 46 | })(); 47 | 48 | runEval = true; 49 | 50 | //at the end of each eval, push cookies 51 | //TODO: this should happen before the "delayed" watches fire, because if some cookies are not 52 | // strings or browser refuses to store some cookies, we update the model in the push fn. 53 | $rootScope.$watch(push); 54 | 55 | return cookies; 56 | 57 | 58 | /** 59 | * Pushes all the cookies from the service to the browser and verifies if all cookies were stored. 60 | */ 61 | function push() { 62 | var name, 63 | value, 64 | browserCookies, 65 | updated; 66 | 67 | //delete any cookies deleted in $cookies 68 | for (name in lastCookies) { 69 | if (isUndefined(cookies[name])) { 70 | $browser.cookies(name, undefined); 71 | } 72 | } 73 | 74 | //update all cookies updated in $cookies 75 | for(name in cookies) { 76 | value = cookies[name]; 77 | if (!angular.isString(value)) { 78 | if (angular.isDefined(lastCookies[name])) { 79 | cookies[name] = lastCookies[name]; 80 | } else { 81 | delete cookies[name]; 82 | } 83 | } else if (value !== lastCookies[name]) { 84 | $browser.cookies(name, value); 85 | updated = true; 86 | } 87 | } 88 | 89 | //verify what was actually stored 90 | if (updated){ 91 | updated = false; 92 | browserCookies = $browser.cookies(); 93 | 94 | for (name in cookies) { 95 | if (cookies[name] !== browserCookies[name]) { 96 | //delete or reset all cookies that the browser dropped from $cookies 97 | if (isUndefined(browserCookies[name])) { 98 | delete cookies[name]; 99 | } else { 100 | cookies[name] = browserCookies[name]; 101 | } 102 | updated = true; 103 | } 104 | } 105 | } 106 | } 107 | }]). 108 | 109 | 110 | /** 111 | * @ngdoc object 112 | * @name ngCookies.$cookieStore 113 | * @requires $cookies 114 | * 115 | * @description 116 | * Provides a key-value (string-object) storage, that is backed by session cookies. 117 | * Objects put or retrieved from this storage are automatically serialized or 118 | * deserialized by angular's toJson/fromJson. 119 | * @example 120 | */ 121 | factory('$cookieStore', ['$cookies', function($cookies) { 122 | 123 | return { 124 | /** 125 | * @ngdoc method 126 | * @name ngCookies.$cookieStore#get 127 | * @methodOf ngCookies.$cookieStore 128 | * 129 | * @description 130 | * Returns the value of given cookie key 131 | * 132 | * @param {string} key Id to use for lookup. 133 | * @returns {Object} Deserialized cookie value. 134 | */ 135 | get: function(key) { 136 | return angular.fromJson($cookies[key]); 137 | }, 138 | 139 | /** 140 | * @ngdoc method 141 | * @name ngCookies.$cookieStore#put 142 | * @methodOf ngCookies.$cookieStore 143 | * 144 | * @description 145 | * Sets a value for given cookie key 146 | * 147 | * @param {string} key Id for the `value`. 148 | * @param {Object} value Value to be stored. 149 | */ 150 | put: function(key, value) { 151 | $cookies[key] = angular.toJson(value); 152 | }, 153 | 154 | /** 155 | * @ngdoc method 156 | * @name ngCookies.$cookieStore#remove 157 | * @methodOf ngCookies.$cookieStore 158 | * 159 | * @description 160 | * Remove given cookie 161 | * 162 | * @param {string} key Id of the key-value pair to delete. 163 | */ 164 | remove: function(key) { 165 | delete $cookies[key]; 166 | } 167 | }; 168 | 169 | }]); 170 | 171 | })(window, window.angular); 172 | -------------------------------------------------------------------------------- /app/lib/angular/angular-cookies.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.3 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,c){var b={},g={},h,i=!1,j=f.copy,k=f.isUndefined;c.addPollFn(function(){var a=c.cookies();h!=a&&(h=a,j(a,g),j(a,b),i&&d.$apply())})();i=!0;d.$watch(function(){var a,e,d;for(a in g)k(b[a])&&c.cookies(a,l);for(a in b)e=b[a],f.isString(e)?e!==g[a]&&(c.cookies(a,e),d=!0):f.isDefined(g[a])?b[a]=g[a]:delete b[a];if(d)for(a in e=c.cookies(),b)b[a]!==e[a]&&(k(e[a])?delete b[a]:b[a]=e[a])});return b}]).factory("$cookieStore", 7 | ["$cookies",function(d){return{get:function(c){return f.fromJson(d[c])},put:function(c,b){d[c]=f.toJson(b)},remove:function(c){delete d[c]}}}])})(window,window.angular); 8 | -------------------------------------------------------------------------------- /app/lib/angular/angular-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.3 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 | )(window); 258 | 259 | /** 260 | * Closure compiler type information 261 | * 262 | * @typedef { { 263 | * requires: !Array., 264 | * invokeQueue: !Array.>, 265 | * 266 | * service: function(string, Function):angular.Module, 267 | * factory: function(string, Function):angular.Module, 268 | * value: function(string, *):angular.Module, 269 | * 270 | * filter: function(string, Function):angular.Module, 271 | * 272 | * init: function(Function):angular.Module 273 | * } } 274 | */ 275 | angular.Module; 276 | 277 | -------------------------------------------------------------------------------- /app/lib/angular/angular-loader.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.3 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.3 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 | * @param {string} url A parameterized URL template with parameters prefixed by `:` as in 28 | * `/user/:username`. 29 | * 30 | * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 31 | * `actions` methods. 32 | * 33 | * Each key value in the parameter object is first bound to url template if present and then any 34 | * excess keys are appended to the url search query after the `?`. 35 | * 36 | * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 37 | * URL `/path/greet?salutation=Hello`. 38 | * 39 | * If the parameter value is prefixed with `@` then the value of that parameter is extracted from 40 | * the data object (useful for non-GET operations). 41 | * 42 | * @param {Object.=} actions Hash with declaration of custom action that should extend the 43 | * default set of resource actions. The declaration should be created in the following format: 44 | * 45 | * {action1: {method:?, params:?, isArray:?}, 46 | * action2: {method:?, params:?, isArray:?}, 47 | * ...} 48 | * 49 | * Where: 50 | * 51 | * - `action` – {string} – The name of action. This name becomes the name of the method on your 52 | * resource object. 53 | * - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, 54 | * and `JSONP` 55 | * - `params` – {object=} – Optional set of pre-bound parameters for this action. 56 | * - isArray – {boolean=} – If true then the returned object for this action is an array, see 57 | * `returns` section. 58 | * 59 | * @returns {Object} A resource "class" object with methods for the default set of resource actions 60 | * optionally extended with custom `actions`. The default set contains these actions: 61 | * 62 | * { 'get': {method:'GET'}, 63 | * 'save': {method:'POST'}, 64 | * 'query': {method:'GET', isArray:true}, 65 | * 'remove': {method:'DELETE'}, 66 | * 'delete': {method:'DELETE'} }; 67 | * 68 | * Calling these methods invoke an {@link ng.$http} with the specified http method, 69 | * destination and parameters. When the data is returned from the server then the object is an 70 | * instance of the resource class `save`, `remove` and `delete` actions are available on it as 71 | * methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read, 72 | * update, delete) on server-side data like this: 73 | *
 74 |         var User = $resource('/user/:userId', {userId:'@id'});
 75 |         var user = User.get({userId:123}, function() {
 76 |           user.abc = true;
 77 |           user.$save();
 78 |         });
 79 |      
80 | * 81 | * It is important to realize that invoking a $resource object method immediately returns an 82 | * empty reference (object or array depending on `isArray`). Once the data is returned from the 83 | * server the existing reference is populated with the actual data. This is a useful trick since 84 | * usually the resource is assigned to a model which is then rendered by the view. Having an empty 85 | * object results in no rendering, once the data arrives from the server then the object is 86 | * populated with the data and the view automatically re-renders itself showing the new data. This 87 | * means that in most case one never has to write a callback function for the action methods. 88 | * 89 | * The action methods on the class object or instance object can be invoked with the following 90 | * parameters: 91 | * 92 | * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` 93 | * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` 94 | * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 95 | * 96 | * 97 | * @example 98 | * 99 | * # Credit card resource 100 | * 101 | *
102 |      // Define CreditCard class
103 |      var CreditCard = $resource('/user/:userId/card/:cardId',
104 |       {userId:123, cardId:'@id'}, {
105 |        charge: {method:'POST', params:{charge:true}}
106 |       });
107 | 
108 |      // We can retrieve a collection from the server
109 |      var cards = CreditCard.query(function() {
110 |        // GET: /user/123/card
111 |        // server returns: [ {id:456, number:'1234', name:'Smith'} ];
112 | 
113 |        var card = cards[0];
114 |        // each item is an instance of CreditCard
115 |        expect(card instanceof CreditCard).toEqual(true);
116 |        card.name = "J. Smith";
117 |        // non GET methods are mapped onto the instances
118 |        card.$save();
119 |        // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
120 |        // server returns: {id:456, number:'1234', name: 'J. Smith'};
121 | 
122 |        // our custom method is mapped as well.
123 |        card.$charge({amount:9.99});
124 |        // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
125 |      });
126 | 
127 |      // we can create an instance as well
128 |      var newCard = new CreditCard({number:'0123'});
129 |      newCard.name = "Mike Smith";
130 |      newCard.$save();
131 |      // POST: /user/123/card {number:'0123', name:'Mike Smith'}
132 |      // server returns: {id:789, number:'01234', name: 'Mike Smith'};
133 |      expect(newCard.id).toEqual(789);
134 |  * 
135 | * 136 | * The object returned from this function execution is a resource "class" which has "static" method 137 | * for each action in the definition. 138 | * 139 | * Calling these methods invoke `$http` on the `url` template with the given `method` and `params`. 140 | * When the data is returned from the server then the object is an instance of the resource type and 141 | * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 142 | * operations (create, read, update, delete) on server-side data. 143 | 144 |
145 |      var User = $resource('/user/:userId', {userId:'@id'});
146 |      var user = User.get({userId:123}, function() {
147 |        user.abc = true;
148 |        user.$save();
149 |      });
150 |    
151 | * 152 | * It's worth noting that the success callback for `get`, `query` and other method gets passed 153 | * in the response that came from the server as well as $http header getter function, so one 154 | * could rewrite the above example and get access to http headers as: 155 | * 156 |
157 |      var User = $resource('/user/:userId', {userId:'@id'});
158 |      User.get({userId:123}, function(u, getResponseHeaders){
159 |        u.abc = true;
160 |        u.$save(function(u, putResponseHeaders) {
161 |          //u => saved user object
162 |          //putResponseHeaders => $http header getter
163 |        });
164 |      });
165 |    
166 | 167 | * # Buzz client 168 | 169 | Let's look at what a buzz client created with the `$resource` service looks like: 170 | 171 | 172 | 192 | 193 |
194 | 195 | 196 |
197 |
198 |

199 | 200 | {{item.actor.name}} 201 | Expand replies: {{item.links.replies[0].count}} 202 |

203 | {{item.object.content | html}} 204 |
205 | 206 | {{reply.actor.name}}: {{reply.content | html}} 207 |
208 |
209 |
210 |
211 | 212 | 213 |
214 | */ 215 | angular.module('ngResource', ['ng']). 216 | factory('$resource', ['$http', '$parse', function($http, $parse) { 217 | var DEFAULT_ACTIONS = { 218 | 'get': {method:'GET'}, 219 | 'save': {method:'POST'}, 220 | 'query': {method:'GET', isArray:true}, 221 | 'remove': {method:'DELETE'}, 222 | 'delete': {method:'DELETE'} 223 | }; 224 | var noop = angular.noop, 225 | forEach = angular.forEach, 226 | extend = angular.extend, 227 | copy = angular.copy, 228 | isFunction = angular.isFunction, 229 | getter = function(obj, path) { 230 | return $parse(path)(obj); 231 | }; 232 | 233 | /** 234 | * We need our custom mehtod because encodeURIComponent is too aggressive and doesn't follow 235 | * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 236 | * segments: 237 | * segment = *pchar 238 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 239 | * pct-encoded = "%" HEXDIG HEXDIG 240 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 241 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 242 | * / "*" / "+" / "," / ";" / "=" 243 | */ 244 | function encodeUriSegment(val) { 245 | return encodeUriQuery(val, true). 246 | replace(/%26/gi, '&'). 247 | replace(/%3D/gi, '='). 248 | replace(/%2B/gi, '+'); 249 | } 250 | 251 | 252 | /** 253 | * This method is intended for encoding *key* or *value* parts of query component. We need a custom 254 | * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be 255 | * encoded per http://tools.ietf.org/html/rfc3986: 256 | * query = *( pchar / "/" / "?" ) 257 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 258 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 259 | * pct-encoded = "%" HEXDIG HEXDIG 260 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 261 | * / "*" / "+" / "," / ";" / "=" 262 | */ 263 | function encodeUriQuery(val, pctEncodeSpaces) { 264 | return encodeURIComponent(val). 265 | replace(/%40/gi, '@'). 266 | replace(/%3A/gi, ':'). 267 | replace(/%24/g, '$'). 268 | replace(/%2C/gi, ','). 269 | replace((pctEncodeSpaces ? null : /%20/g), '+'); 270 | } 271 | 272 | function Route(template, defaults) { 273 | this.template = template = template + '#'; 274 | this.defaults = defaults || {}; 275 | var urlParams = this.urlParams = {}; 276 | forEach(template.split(/\W/), function(param){ 277 | if (param && template.match(new RegExp("[^\\\\]:" + param + "\\W"))) { 278 | urlParams[param] = true; 279 | } 280 | }); 281 | this.template = template.replace(/\\:/g, ':'); 282 | } 283 | 284 | Route.prototype = { 285 | url: function(params) { 286 | var self = this, 287 | url = this.template, 288 | val, 289 | encodedVal; 290 | 291 | params = params || {}; 292 | forEach(this.urlParams, function(_, urlParam){ 293 | val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 294 | if (angular.isDefined(val) && val !== null) { 295 | encodedVal = encodeUriSegment(val); 296 | url = url.replace(new RegExp(":" + urlParam + "(\\W)", "g"), encodedVal + "$1"); 297 | } else { 298 | url = url.replace(new RegExp("/?:" + urlParam + "(\\W)", "g"), '$1'); 299 | } 300 | }); 301 | url = url.replace(/\/?#$/, ''); 302 | var query = []; 303 | forEach(params, function(value, key){ 304 | if (!self.urlParams[key]) { 305 | query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value)); 306 | } 307 | }); 308 | query.sort(); 309 | url = url.replace(/\/*$/, ''); 310 | return url + (query.length ? '?' + query.join('&') : ''); 311 | } 312 | }; 313 | 314 | 315 | function ResourceFactory(url, paramDefaults, actions) { 316 | var route = new Route(url); 317 | 318 | actions = extend({}, DEFAULT_ACTIONS, actions); 319 | 320 | function extractParams(data, actionParams){ 321 | var ids = {}; 322 | actionParams = extend({}, paramDefaults, actionParams); 323 | forEach(actionParams, function(value, key){ 324 | ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; 325 | }); 326 | return ids; 327 | } 328 | 329 | function Resource(value){ 330 | copy(value || {}, this); 331 | } 332 | 333 | forEach(actions, function(action, name) { 334 | var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH'; 335 | Resource[name] = function(a1, a2, a3, a4) { 336 | var params = {}; 337 | var data; 338 | var success = noop; 339 | var error = null; 340 | switch(arguments.length) { 341 | case 4: 342 | error = a4; 343 | success = a3; 344 | //fallthrough 345 | case 3: 346 | case 2: 347 | if (isFunction(a2)) { 348 | if (isFunction(a1)) { 349 | success = a1; 350 | error = a2; 351 | break; 352 | } 353 | 354 | success = a2; 355 | error = a3; 356 | //fallthrough 357 | } else { 358 | params = a1; 359 | data = a2; 360 | success = a3; 361 | break; 362 | } 363 | case 1: 364 | if (isFunction(a1)) success = a1; 365 | else if (hasBody) data = a1; 366 | else params = a1; 367 | break; 368 | case 0: break; 369 | default: 370 | throw "Expected between 0-4 arguments [params, data, success, error], got " + 371 | arguments.length + " arguments."; 372 | } 373 | 374 | var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); 375 | $http({ 376 | method: action.method, 377 | url: route.url(extend({}, extractParams(data, action.params || {}), params)), 378 | data: data 379 | }).then(function(response) { 380 | var data = response.data; 381 | 382 | if (data) { 383 | if (action.isArray) { 384 | value.length = 0; 385 | forEach(data, function(item) { 386 | value.push(new Resource(item)); 387 | }); 388 | } else { 389 | copy(data, value); 390 | } 391 | } 392 | (success||noop)(value, response.headers); 393 | }, error); 394 | 395 | return value; 396 | }; 397 | 398 | 399 | Resource.bind = function(additionalParamDefaults){ 400 | return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); 401 | }; 402 | 403 | 404 | Resource.prototype['$' + name] = function(a1, a2, a3) { 405 | var params = extractParams(this), 406 | success = noop, 407 | error; 408 | 409 | switch(arguments.length) { 410 | case 3: params = a1; success = a2; error = a3; break; 411 | case 2: 412 | case 1: 413 | if (isFunction(a1)) { 414 | success = a1; 415 | error = a2; 416 | } else { 417 | params = a1; 418 | success = a2 || noop; 419 | } 420 | case 0: break; 421 | default: 422 | throw "Expected between 1-3 arguments [params, success, error], got " + 423 | arguments.length + " arguments."; 424 | } 425 | var data = hasBody ? this : undefined; 426 | Resource[name].call(this, params, data, success, error); 427 | }; 428 | }); 429 | return Resource; 430 | } 431 | 432 | return ResourceFactory; 433 | }]); 434 | 435 | })(window, window.angular); 436 | -------------------------------------------------------------------------------- /app/lib/angular/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.3 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(A,e,w){'use strict';e.module("ngResource",["ng"]).factory("$resource",["$http","$parse",function(x,y){function k(a,f){return encodeURIComponent(a).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(f?null:/%20/g,"+")}function t(a,f){this.template=a+="#";this.defaults=f||{};var b=this.urlParams={};l(a.split(/\W/),function(d){d&&a.match(RegExp("[^\\\\]:"+d+"\\W"))&&(b[d]=!0)});this.template=a.replace(/\\:/g,":")}function u(a,f,b){function d(b,c){var a= 7 | {},c=i({},f,c);l(c,function(o,c){var d;o.charAt&&o.charAt(0)=="@"?(d=o.substr(1),d=y(d)(b)):d=o;a[c]=d});return a}function h(a){v(a||{},this)}var e=new t(a),b=i({},z,b);l(b,function(g,c){var k=g.method=="POST"||g.method=="PUT"||g.method=="PATCH";h[c]=function(a,b,c,f){var s={},j,m=p,q=null;switch(arguments.length){case 4:q=f,m=c;case 3:case 2:if(r(b)){if(r(a)){m=a;q=b;break}m=b;q=c}else{s=a;j=b;m=c;break}case 1:r(a)?m=a:k?j=a:s=a;break;case 0:break;default:throw"Expected between 0-4 arguments [params, data, success, error], got "+ 8 | arguments.length+" arguments.";}var n=this instanceof h?this:g.isArray?[]:new h(j);x({method:g.method,url:e.url(i({},d(j,g.params||{}),s)),data:j}).then(function(a){var b=a.data;if(b)g.isArray?(n.length=0,l(b,function(a){n.push(new h(a))})):v(b,n);(m||p)(n,a.headers)},q);return n};h.bind=function(c){return u(a,i({},f,c),b)};h.prototype["$"+c]=function(a,b,f){var g=d(this),e=p,j;switch(arguments.length){case 3:g=a;e=b;j=f;break;case 2:case 1:r(a)?(e=a,j=b):(g=a,e=b||p);case 0:break;default:throw"Expected between 1-3 arguments [params, success, error], got "+ 9 | arguments.length+" arguments.";}h[c].call(this,g,k?this:w,e,j)}});return h}var z={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},p=e.noop,l=e.forEach,i=e.extend,v=e.copy,r=e.isFunction;t.prototype={url:function(a){var f=this,b=this.template,d,h,a=a||{};l(this.urlParams,function(g,c){d=a.hasOwnProperty(c)?a[c]:f.defaults[c];e.isDefined(d)&&d!==null?(h=k(d,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+"), 10 | b=b.replace(RegExp(":"+c+"(\\W)","g"),h+"$1")):b=b.replace(RegExp("/?:"+c+"(\\W)","g"),"$1")});var b=b.replace(/\/?#$/,""),i=[];l(a,function(a,b){f.urlParams[b]||i.push(k(b)+"="+k(a))});i.sort();b=b.replace(/\/*$/,"");return b+(i.length?"?"+i.join("&"):"")}};return u}])})(window,window.angular); 11 | -------------------------------------------------------------------------------- /app/lib/angular/angular-sanitize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.3 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 | * @ngdoc filter 427 | * @name ngSanitize.filter:linky 428 | * @function 429 | * 430 | * @description 431 | * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and 432 | * plain email address links. 433 | * 434 | * @param {string} text Input text. 435 | * @returns {string} Html-linkified text. 436 | * 437 | * @usage 438 | 439 | * 440 | * @example 441 | 442 | 443 | 453 |
454 | Snippet: 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 466 | 469 | 470 | 471 | 472 | 473 | 474 | 475 |
FilterSourceRendered
linky filter 464 |
<div ng-bind-html="snippet | linky">
</div>
465 |
467 |
468 |
no filter
<div ng-bind="snippet">
</div>
476 | 477 | 478 | it('should linkify the snippet with urls', function() { 479 | expect(using('#linky-filter').binding('snippet | linky')). 480 | toBe('Pretty text with some links: ' + 481 | 'http://angularjs.org/, ' + 482 | 'us@somewhere.org, ' + 483 | 'another@somewhere.org, ' + 484 | 'and one more: ftp://127.0.0.1/.'); 485 | }); 486 | 487 | it ('should not linkify snippet without the linky filter', function() { 488 | expect(using('#escaped-html').binding('snippet')). 489 | toBe("Pretty text with some links:\n" + 490 | "http://angularjs.org/,\n" + 491 | "mailto:us@somewhere.org,\n" + 492 | "another@somewhere.org,\n" + 493 | "and one more: ftp://127.0.0.1/."); 494 | }); 495 | 496 | it('should update', function() { 497 | input('snippet').enter('new http://link.'); 498 | expect(using('#linky-filter').binding('snippet | linky')). 499 | toBe('new http://link.'); 500 | expect(using('#escaped-html').binding('snippet')).toBe('new http://link.'); 501 | }); 502 | 503 | 504 | */ 505 | angular.module('ngSanitize').filter('linky', function() { 506 | var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/, 507 | MAILTO_REGEXP = /^mailto:/; 508 | 509 | return function(text) { 510 | if (!text) return text; 511 | var match; 512 | var raw = text; 513 | var html = []; 514 | // TODO(vojta): use $sanitize instead 515 | var writer = htmlSanitizeWriter(html); 516 | var url; 517 | var i; 518 | while ((match = raw.match(LINKY_URL_REGEXP))) { 519 | // We can not end in these as they are sometimes found at the end of the sentence 520 | url = match[0]; 521 | // if we did not match ftp/http/mailto then assume mailto 522 | if (match[2] == match[3]) url = 'mailto:' + url; 523 | i = match.index; 524 | writer.chars(raw.substr(0, i)); 525 | writer.start('a', {href:url}); 526 | writer.chars(match[0].replace(MAILTO_REGEXP, '')); 527 | writer.end('a'); 528 | raw = raw.substring(i + match[0].length); 529 | } 530 | writer.chars(raw); 531 | return html.join(''); 532 | }; 533 | }); 534 | 535 | })(window, window.angular); 536 | -------------------------------------------------------------------------------- /app/lib/angular/angular-sanitize.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.3 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 | 2 | -------------------------------------------------------------------------------- /app/partials/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glitchtank/angular-seed-master/cc5ccb5548c60f58468d821f19e723e61c001b79/app/partials/.gitignore -------------------------------------------------------------------------------- /app/partials/partial1.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 1.

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