├── .gitignore ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── config └── karma.conf.js ├── dist ├── angular-d3.js └── angular-d3.min.js ├── example └── index.html ├── lib ├── angular-d3.js └── directives │ └── d3.js ├── package.json ├── test └── unit │ └── directives │ └── d3.spec.js └── vendor └── angular ├── angular-mocks.js └── angular.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function(grunt) { 3 | grunt.loadNpmTasks('grunt-karma'); 4 | grunt.loadNpmTasks('grunt-contrib-watch'); 5 | grunt.loadNpmTasks('grunt-contrib-jshint'); 6 | grunt.loadNpmTasks('grunt-contrib-concat'); 7 | grunt.loadNpmTasks('grunt-contrib-uglify'); 8 | // Project configuration. 9 | grunt.initConfig({ 10 | pkg: grunt.file.readJSON('package.json'), 11 | concat: { 12 | options: { 13 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 14 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 15 | '<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' + 16 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + 17 | ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %\n */\n' 18 | }, 19 | dist: { 20 | src: ['lib/**/*.js'], 21 | dest: 'dist/angular-d3.js' 22 | } 23 | }, 24 | uglify: { 25 | dist: { 26 | files: { 27 | 'dist/angular-d3.min.js': 'dist/angular-d3.js' 28 | } 29 | } 30 | }, 31 | watch: { 32 | all: { 33 | files: ['Gruntfile.js', 'lib/**/*.js', 'test/**/*.js'], 34 | tasks: ['default'] 35 | } 36 | }, 37 | jshint: { 38 | all: ['Gruntfile.js', 'lib/**/*.js', 'test/**/*.js'], 39 | options: { 40 | curly: true, 41 | eqeqeq: true, 42 | immed: true, 43 | latedef: true, 44 | newcap: true, 45 | noarg: true, 46 | sub: true, 47 | undef: true, 48 | boss: true, 49 | eqnull: true, 50 | browser: true, 51 | globals: { 52 | angular: true, 53 | d3: true 54 | } 55 | } 56 | }, 57 | karma: { 58 | unit: { 59 | configFile: 'config/karma.conf.js', 60 | background: true, 61 | options: { 62 | keepalive: true 63 | } 64 | }, 65 | continuous: { 66 | configFile: 'config/karma.conf.js', 67 | singleRun: true, 68 | browsers: ['PhantomJS'] 69 | } 70 | } 71 | }); 72 | 73 | // Default task. 74 | grunt.registerTask('default', ['jshint', 'karma:continuous', 'concat', 75 | 'uglify']); 76 | grunt.registerTask('test', 'karma:continuous'); 77 | grunt.registerTask('testWatch', ['karma:unit', 'watch']); 78 | }; 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Michael Alexander 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 20 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angular-d3 - D3.js directives for AngularJS 2 | =========================================== 3 | 4 | [![Build Status](https://travis-ci.org/beefsack/angular-d3.png)](https://travis-ci.org/beefsack/angular-d3) 5 | 6 | angular-d3 provides a number of directives to assist in integrating D3.js into 7 | an AngularJS application, providing live scope updates to D3.js automating re- 8 | rendering. 9 | 10 | Installing 11 | ---------- 12 | 13 | angular-d3 is available via Bower as ```d3-angular``` 14 | 15 | ``` 16 | bower install d3-angular 17 | ``` 18 | 19 | 20 | License 21 | ------- 22 | 23 | Please refer to the LICENSE file packaged with this source. 24 | -------------------------------------------------------------------------------- /config/karma.conf.js: -------------------------------------------------------------------------------- 1 | basePath = '../'; 2 | 3 | files = [ 4 | JASMINE, 5 | JASMINE_ADAPTER, 6 | 'vendor/angular/angular.js', 7 | 'vendor/angular/angular-*.js', 8 | 'lib/angular-d3.js', 9 | 'lib/**/*.js', 10 | 'test/unit/**/*.js' 11 | ]; 12 | 13 | autoWatch = true; 14 | 15 | browsers = ['PhantomJS']; 16 | -------------------------------------------------------------------------------- /dist/angular-d3.js: -------------------------------------------------------------------------------- 1 | /*! angular-d3 - v0.1.1 - 2013-04-19 2 | * https://github.com/beefsack/angular-d3 3 | * Copyright (c) 2013 Michael Alexander; Licensed <%= _.pluck(pkg.licenses, "type").join(", ") % 4 | */ 5 | (function() { 6 | 'use strict'; 7 | angular.module('d3.directives', []); 8 | angular.module('d3', ['d3.directives']); 9 | }()); 10 | 11 | (function() { 12 | 'use strict'; 13 | angular.module('d3.directives').directive('d3', [ 14 | function() { 15 | return { 16 | scope: { 17 | d3Data: '=', 18 | d3Renderer: '=' 19 | }, 20 | restrict: 'EAMC', 21 | link: function(scope, iElement, iAttrs) { 22 | var el = d3.select(iElement[0]); 23 | scope.render = function() { 24 | if (typeof(scope.d3Renderer) === 'function') { 25 | scope.d3Renderer(el, scope.d3Data); 26 | } 27 | }; 28 | scope.$watch('d3Renderer', scope.render); 29 | scope.$watch('d3Data', scope.render, true); 30 | } 31 | }; 32 | } 33 | ]); 34 | }()); 35 | -------------------------------------------------------------------------------- /dist/angular-d3.min.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";angular.module("d3.directives",[]),angular.module("d3",["d3.directives"])})(),function(){"use strict";angular.module("d3.directives").directive("d3",[function(){return{scope:{d3Data:"=",d3Renderer:"="},restrict:"EAMC",link:function(e,r){var d=d3.select(r[0]);e.render=function(){"function"==typeof e.d3Renderer&&e.d3Renderer(d,e.d3Data)},e.$watch("d3Renderer",e.render),e.$watch("d3Data",e.render,!0)}}}])}(); -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 105 | 110 | 111 | 112 |
113 | 114 | 115 | 116 | | 117 | 118 | 119 | 120 |
121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /lib/angular-d3.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | angular.module('d3.directives', []); 4 | angular.module('d3', ['d3.directives']); 5 | }()); 6 | -------------------------------------------------------------------------------- /lib/directives/d3.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | angular.module('d3.directives').directive('d3', [ 4 | function() { 5 | return { 6 | scope: { 7 | d3Data: '=', 8 | d3Renderer: '=' 9 | }, 10 | restrict: 'EAMC', 11 | link: function(scope, iElement, iAttrs) { 12 | var el = d3.select(iElement[0]); 13 | scope.render = function() { 14 | if (typeof(scope.d3Renderer) === 'function') { 15 | scope.d3Renderer(el, scope.d3Data); 16 | } 17 | }; 18 | scope.$watch('d3Renderer', scope.render); 19 | scope.$watch('d3Data', scope.render); 20 | } 21 | }; 22 | } 23 | ]); 24 | }()); 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-d3", 3 | "description": "AngularJS directive adding support for D3.js.", 4 | "version": "0.1.1", 5 | "homepage": "https://github.com/beefsack/angular-d3", 6 | "author": { 7 | "name": "Michael Alexander", 8 | "email": "beefsack@gmail.com", 9 | "url": "http://beefsack.com" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/beefsack/angular-d3.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/beefsack/angular-d3/issues" 17 | }, 18 | "licenses": [ 19 | { 20 | "type": "MIT", 21 | "url": "https://github.com/beefsack/angular-d3/blob/master/LICENSE-MIT" 22 | } 23 | ], 24 | "main": "lib/angular-d3", 25 | "engines": { 26 | "node": ">= 0.8.0" 27 | }, 28 | "scripts": { 29 | "test": "grunt test" 30 | }, 31 | "devDependencies": { 32 | "grunt-cli": "~0.1.7", 33 | "grunt-karma": "~0.4.3", 34 | "grunt-contrib-watch": "~0.3.1", 35 | "grunt-contrib-jshint": "~0.4.3", 36 | "grunt-contrib-concat": "~0.2.0", 37 | "grunt-contrib-uglify": "~0.2.0", 38 | "karma": "~0.8.5", 39 | "node-phantom": "~0.2.1" 40 | }, 41 | "keywords": [] 42 | } 43 | -------------------------------------------------------------------------------- /test/unit/directives/d3.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, expect */ 2 | 3 | describe('A d3 directive', function() { 4 | it('should be true', function() { 5 | expect(true).toBe(true); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /vendor/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 | --------------------------------------------------------------------------------