├── .gitignore ├── .travis.yml ├── conf ├── karma.conf.js └── karma-common.js ├── package.json ├── gulpFile.js ├── lrCloudinary.min.js ├── test └── lrCloudinary-spec.js ├── readme.md ├── lrCloudinary.js └── lib └── angular-mocks.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | bower_components 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | 5 | before_script: 6 | - npm install 7 | 8 | script: 9 | - npm test -------------------------------------------------------------------------------- /conf/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | var conf = require('./karma-common.js'); 3 | conf.basePath = '../'; 4 | config.set(conf); 5 | }; -------------------------------------------------------------------------------- /conf/karma-common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ 3 | 'lib/angular.js', 4 | 'lib/angular-mocks.js', 5 | 'lrCloudinary.js', 6 | 'test/*.js' 7 | ], 8 | frameworks: ['jasmine'], 9 | browsers: ['Chrome'], 10 | 11 | port: 9876 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lrCloudinary", 3 | "version": "0.1.0", 4 | "description": "Angular filter to perform image transformation to images hosted with cloudinary in a declarative way", 5 | "main": "lrCloudinary.js", 6 | "scripts": { 7 | "test": "node ./node_modules/.bin/gulp" 8 | }, 9 | "author": "Laurent RENARD", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "gulp": "^3.8.6", 13 | "gulp-rename": "^1.2.0", 14 | "gulp-uglify": "^0.3.1", 15 | "karma": "^0.12.17", 16 | "karma-chrome-launcher": "^0.1.4", 17 | "karma-jasmine": "^0.1.5", 18 | "karma-phantomjs-launcher": "^0.1.4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /gulpFile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var uglify = require('gulp-uglify'); 3 | var rename = require('gulp-rename'); 4 | var karma = require('karma').server; 5 | 6 | gulp.task('build', function () { 7 | gulp.src('lrCloudinary.js') 8 | .pipe(uglify({ 9 | mangle: false 10 | })) 11 | .pipe(rename('lrCloudinary.min.js')) 12 | .pipe(gulp.dest('./')); 13 | }); 14 | 15 | gulp.task('karma-CI', function (done) { 16 | var conf = require('./conf/karma-common.js'); 17 | conf.singleRun = true; 18 | conf.browsers = ['PhantomJS']; 19 | conf.basePath = './'; 20 | karma.start(conf, done); 21 | }); 22 | 23 | gulp.task('default', ['karma-CI', 'build']); -------------------------------------------------------------------------------- /lrCloudinary.min.js: -------------------------------------------------------------------------------- 1 | !function(ng){"use strict";ng.module("lrCloudinary",[]).constant("lrCloudinary.config",{baseUrl:"https://res.cloudinary.com/"}).provider("lrCloudinaryUrl",["lrCloudinary.config",function(defaultConfig){var config=ng.extend({},defaultConfig);this.setConfig=function(configObject){ng.extend(config,configObject)},this.$get=[function(){function generateTransformationString(options){var params=[],output="/";return options.width&¶ms.push("w_"+options.width),options.height&¶ms.push("h_"+options.height),options.crop&¶ms.push("c_"+options.crop),params.length>0&&(output+=params.join(","),output+="/"),output}return function(input,options){var output="",optionsOrDefault=ng.extend({format:"jpg"},options),transformString=generateTransformationString(optionsOrDefault);return input&&config.cloudName&&(output=config.baseUrl+config.cloudName+"/image/upload"+transformString+input+"."+optionsOrDefault.format),output}}]}]).filter("lrCloudinaryFilter",["lrCloudinaryUrl",function(cloudinary){return cloudinary}])}(angular); -------------------------------------------------------------------------------- /test/lrCloudinary-spec.js: -------------------------------------------------------------------------------- 1 | describe('lrCloudinary module', function () { 2 | 3 | var filter; 4 | 5 | beforeEach(module('lrCloudinary', function (lrCloudinaryUrlProvider) { 6 | lrCloudinaryUrlProvider.setConfig({cloudName: 'test', baseUrl: 'http://example.com/'}); 7 | })); 8 | 9 | beforeEach(inject(function ($filter) { 10 | filter = $filter('lrCloudinaryFilter'); 11 | })); 12 | 13 | it('should return url based on cloud name configuration and cloudinary public_id (default to jpg extension)', function () { 14 | expect(filter('public')).toEqual('http://example.com/test/image/upload/public.jpg'); 15 | }); 16 | 17 | it('should support partial transformations', function () { 18 | expect(filter('public', {height: 150})).toEqual('http://example.com/test/image/upload/h_150/public.jpg') 19 | }); 20 | 21 | it('should support multiple transformation', function () { 22 | expect(filter('public', {width: 100, height: 150, crop: 'fill'})).toEqual('http://example.com/test/image/upload/w_100,h_150,c_fill/public.jpg'); 23 | }); 24 | 25 | it('should support other image format', function () { 26 | expect(filter('public', {format: 'gif'})).toEqual('http://example.com/test/image/upload/public.gif'); 27 | }); 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/lorenzofox3/lrCloudinary.svg?branch=master)](https://travis-ci.org/lorenzofox3/lrCloudinary) 2 | 3 | # lrCloudinary 4 | 5 | An Angular filter to perform image transformations on [cloudinary](http://cloudinary.com/) hosted images in a declarative way. There is no dependency other than Angular framework itself. 6 | 7 | ## Getting started 8 | 9 | 1. Add the script `lrCloudinary.js` or the minified version (lrCloudinary.min.js) in your application. 10 | 2. Register the module `angular.module('myApp',['lrCloudinary']) 11 | 3. Configure the module to use at least your cloud name 12 | ```javascript 13 | angular.module('myApp',['lrCloudinary']).config(function(lrCloudinaryUrlProvider){ 14 | lrCloudinaryUrlProvider.setConfig({cloudName: 'YOUR CLOUD NAME'}); 15 | }; 16 | ``` 17 | 18 | ### syntax 19 | 20 | you can bind an image src to a cloudinary public_id and then use the filter options if you want to perform transformations (or pass no arguments if you simply want to display the image as it has been uploaded 21 | ```html 22 | alternative 23 | ``` 24 | the supported options are 25 | 26 | 1. size 27 | 2. height 28 | 3. crop mode 29 | 4. format 30 | 31 | ## developers 32 | 33 | simply run `npm install` to download the developement dependencies then run `npm test`. 34 | You cun alternatively run `gulp karma-CI` 35 | 36 | ## Licence 37 | 38 | > Copyright (C) 2014 Laurent Renard. 39 | > 40 | > Permission is hereby granted, free of charge, to any person 41 | > obtaining a copy of this software and associated documentation files 42 | > (the "Software"), to deal in the Software without restriction, 43 | > including without limitation the rights to use, copy, modify, merge, 44 | > publish, distribute, sublicense, and/or sell copies of the Software, 45 | > and to permit persons to whom the Software is furnished to do so, 46 | > subject to the following conditions: 47 | > 48 | > The above copyright notice and this permission notice shall be 49 | > included in all copies or substantial portions of the Software. 50 | > 51 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 52 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 53 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 54 | > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 55 | > BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 56 | > ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 57 | > CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 58 | > SOFTWARE. 59 | -------------------------------------------------------------------------------- /lrCloudinary.js: -------------------------------------------------------------------------------- 1 | (function (ng, undefined) { 2 | 'use strict'; 3 | ng.module('lrCloudinary', []) 4 | .constant('lrCloudinary.config', { 5 | // cloudName: 'you cloud name', 6 | baseUrl: 'https://res.cloudinary.com/' 7 | }) 8 | .provider('lrCloudinaryUrl', ['lrCloudinary.config', function CloudinaryProvider(defaultConfig) { 9 | 10 | var config = ng.extend({}, defaultConfig); 11 | 12 | this.setConfig = function setConfig(configObject) { 13 | ng.extend(config, configObject); 14 | }; 15 | 16 | this.$get = [function () { 17 | 18 | function generateTransformationString(options) { 19 | var params = []; 20 | var output = '/'; 21 | if (options.width) { 22 | params.push('w_' + options.width); 23 | } 24 | if (options.height) { 25 | params.push('h_' + options.height); 26 | } 27 | if (options.crop) { 28 | params.push('c_' + options.crop); 29 | } 30 | 31 | if (params.length > 0) { 32 | output += params.join(','); 33 | output += '/'; 34 | } 35 | 36 | return output; 37 | 38 | } 39 | 40 | /** 41 | * create a cloudinary url from a cloudinary public_id passing all transformation parameters 42 | * @input the cloudinary public_id 43 | * @options transformation options 44 | *
45 |                  *     
46 |                  *          {
47 |                  *              format:'gif',//the requested format (default jpeg)
48 |                  *              width:150,//the width resize prop
49 |                  *              height:100,//the height resize prop
50 |                  *              crop:'fill'//the crop mode
51 |                  *          }
52 |                  *     
53 |                  * 
54 | */ 55 | return function generateUrl(input, options) { 56 | var output = ''; 57 | var optionsOrDefault = ng.extend({format: 'jpg'}, options); 58 | var transformString = generateTransformationString(optionsOrDefault); 59 | 60 | if (input && config.cloudName) { 61 | output = config.baseUrl + config.cloudName + '/image/upload' + transformString + input + '.' + optionsOrDefault.format; 62 | } 63 | return output; 64 | }; 65 | }]; 66 | }]) 67 | .filter('lrCloudinaryFilter', ['lrCloudinaryUrl', function (cloudinary) { 68 | return cloudinary; 69 | }]); 70 | })(angular); 71 | -------------------------------------------------------------------------------- /lib/angular-mocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.2.20 3 | * (c) 2010-2014 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * @ngdoc object 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 $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 | /** 80 | * @name $browser#defer.now 81 | * 82 | * @description 83 | * Current milliseconds mock time. 84 | */ 85 | self.defer.now = 0; 86 | 87 | 88 | self.defer.cancel = function(deferId) { 89 | var fnIndex; 90 | 91 | angular.forEach(self.deferredFns, function(fn, index) { 92 | if (fn.id === deferId) fnIndex = index; 93 | }); 94 | 95 | if (fnIndex !== undefined) { 96 | self.deferredFns.splice(fnIndex, 1); 97 | return true; 98 | } 99 | 100 | return false; 101 | }; 102 | 103 | 104 | /** 105 | * @name $browser#defer.flush 106 | * 107 | * @description 108 | * Flushes all pending requests and executes the defer callbacks. 109 | * 110 | * @param {number=} number of milliseconds to flush. See {@link #defer.now} 111 | */ 112 | self.defer.flush = function(delay) { 113 | if (angular.isDefined(delay)) { 114 | self.defer.now += delay; 115 | } else { 116 | if (self.deferredFns.length) { 117 | self.defer.now = self.deferredFns[self.deferredFns.length-1].time; 118 | } else { 119 | throw new Error('No deferred tasks to be flushed'); 120 | } 121 | } 122 | 123 | while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { 124 | self.deferredFns.shift().fn(); 125 | } 126 | }; 127 | 128 | self.$$baseHref = ''; 129 | self.baseHref = function() { 130 | return this.$$baseHref; 131 | }; 132 | }; 133 | angular.mock.$Browser.prototype = { 134 | 135 | /** 136 | * @name $browser#poll 137 | * 138 | * @description 139 | * run all fns in pollFns 140 | */ 141 | poll: function poll() { 142 | angular.forEach(this.pollFns, function(pollFn){ 143 | pollFn(); 144 | }); 145 | }, 146 | 147 | addPollFn: function(pollFn) { 148 | this.pollFns.push(pollFn); 149 | return pollFn; 150 | }, 151 | 152 | url: function(url, replace) { 153 | if (url) { 154 | this.$$url = url; 155 | return this; 156 | } 157 | 158 | return this.$$url; 159 | }, 160 | 161 | cookies: function(name, value) { 162 | if (name) { 163 | if (angular.isUndefined(value)) { 164 | delete this.cookieHash[name]; 165 | } else { 166 | if (angular.isString(value) && //strings only 167 | value.length <= 4096) { //strict cookie storage limits 168 | this.cookieHash[name] = value; 169 | } 170 | } 171 | } else { 172 | if (!angular.equals(this.cookieHash, this.lastCookieHash)) { 173 | this.lastCookieHash = angular.copy(this.cookieHash); 174 | this.cookieHash = angular.copy(this.cookieHash); 175 | } 176 | return this.cookieHash; 177 | } 178 | }, 179 | 180 | notifyWhenNoOutstandingRequests: function(fn) { 181 | fn(); 182 | } 183 | }; 184 | 185 | 186 | /** 187 | * @ngdoc provider 188 | * @name $exceptionHandlerProvider 189 | * 190 | * @description 191 | * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors 192 | * passed into the `$exceptionHandler`. 193 | */ 194 | 195 | /** 196 | * @ngdoc service 197 | * @name $exceptionHandler 198 | * 199 | * @description 200 | * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed 201 | * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration 202 | * information. 203 | * 204 | * 205 | * ```js 206 | * describe('$exceptionHandlerProvider', function() { 207 | * 208 | * it('should capture log messages and exceptions', function() { 209 | * 210 | * module(function($exceptionHandlerProvider) { 211 | * $exceptionHandlerProvider.mode('log'); 212 | * }); 213 | * 214 | * inject(function($log, $exceptionHandler, $timeout) { 215 | * $timeout(function() { $log.log(1); }); 216 | * $timeout(function() { $log.log(2); throw 'banana peel'; }); 217 | * $timeout(function() { $log.log(3); }); 218 | * expect($exceptionHandler.errors).toEqual([]); 219 | * expect($log.assertEmpty()); 220 | * $timeout.flush(); 221 | * expect($exceptionHandler.errors).toEqual(['banana peel']); 222 | * expect($log.log.logs).toEqual([[1], [2], [3]]); 223 | * }); 224 | * }); 225 | * }); 226 | * ``` 227 | */ 228 | 229 | angular.mock.$ExceptionHandlerProvider = function() { 230 | var handler; 231 | 232 | /** 233 | * @ngdoc method 234 | * @name $exceptionHandlerProvider#mode 235 | * 236 | * @description 237 | * Sets the logging mode. 238 | * 239 | * @param {string} mode Mode of operation, defaults to `rethrow`. 240 | * 241 | * - `rethrow`: If any errors are passed into the handler in tests, it typically 242 | * means that there is a bug in the application or test, so this mock will 243 | * make these tests fail. 244 | * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` 245 | * mode stores an array of errors in `$exceptionHandler.errors`, to allow later 246 | * assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and 247 | * {@link ngMock.$log#reset reset()} 248 | */ 249 | this.mode = function(mode) { 250 | switch(mode) { 251 | case 'rethrow': 252 | handler = function(e) { 253 | throw e; 254 | }; 255 | break; 256 | case 'log': 257 | var errors = []; 258 | 259 | handler = function(e) { 260 | if (arguments.length == 1) { 261 | errors.push(e); 262 | } else { 263 | errors.push([].slice.call(arguments, 0)); 264 | } 265 | }; 266 | 267 | handler.errors = errors; 268 | break; 269 | default: 270 | throw new Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); 271 | } 272 | }; 273 | 274 | this.$get = function() { 275 | return handler; 276 | }; 277 | 278 | this.mode('rethrow'); 279 | }; 280 | 281 | 282 | /** 283 | * @ngdoc service 284 | * @name $log 285 | * 286 | * @description 287 | * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays 288 | * (one array per logging level). These arrays are exposed as `logs` property of each of the 289 | * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. 290 | * 291 | */ 292 | angular.mock.$LogProvider = function() { 293 | var debug = true; 294 | 295 | function concat(array1, array2, index) { 296 | return array1.concat(Array.prototype.slice.call(array2, index)); 297 | } 298 | 299 | this.debugEnabled = function(flag) { 300 | if (angular.isDefined(flag)) { 301 | debug = flag; 302 | return this; 303 | } else { 304 | return debug; 305 | } 306 | }; 307 | 308 | this.$get = function () { 309 | var $log = { 310 | log: function() { $log.log.logs.push(concat([], arguments, 0)); }, 311 | warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, 312 | info: function() { $log.info.logs.push(concat([], arguments, 0)); }, 313 | error: function() { $log.error.logs.push(concat([], arguments, 0)); }, 314 | debug: function() { 315 | if (debug) { 316 | $log.debug.logs.push(concat([], arguments, 0)); 317 | } 318 | } 319 | }; 320 | 321 | /** 322 | * @ngdoc method 323 | * @name $log#reset 324 | * 325 | * @description 326 | * Reset all of the logging arrays to empty. 327 | */ 328 | $log.reset = function () { 329 | /** 330 | * @ngdoc property 331 | * @name $log#log.logs 332 | * 333 | * @description 334 | * Array of messages logged using {@link ngMock.$log#log}. 335 | * 336 | * @example 337 | * ```js 338 | * $log.log('Some Log'); 339 | * var first = $log.log.logs.unshift(); 340 | * ``` 341 | */ 342 | $log.log.logs = []; 343 | /** 344 | * @ngdoc property 345 | * @name $log#info.logs 346 | * 347 | * @description 348 | * Array of messages logged using {@link ngMock.$log#info}. 349 | * 350 | * @example 351 | * ```js 352 | * $log.info('Some Info'); 353 | * var first = $log.info.logs.unshift(); 354 | * ``` 355 | */ 356 | $log.info.logs = []; 357 | /** 358 | * @ngdoc property 359 | * @name $log#warn.logs 360 | * 361 | * @description 362 | * Array of messages logged using {@link ngMock.$log#warn}. 363 | * 364 | * @example 365 | * ```js 366 | * $log.warn('Some Warning'); 367 | * var first = $log.warn.logs.unshift(); 368 | * ``` 369 | */ 370 | $log.warn.logs = []; 371 | /** 372 | * @ngdoc property 373 | * @name $log#error.logs 374 | * 375 | * @description 376 | * Array of messages logged using {@link ngMock.$log#error}. 377 | * 378 | * @example 379 | * ```js 380 | * $log.error('Some Error'); 381 | * var first = $log.error.logs.unshift(); 382 | * ``` 383 | */ 384 | $log.error.logs = []; 385 | /** 386 | * @ngdoc property 387 | * @name $log#debug.logs 388 | * 389 | * @description 390 | * Array of messages logged using {@link ngMock.$log#debug}. 391 | * 392 | * @example 393 | * ```js 394 | * $log.debug('Some Error'); 395 | * var first = $log.debug.logs.unshift(); 396 | * ``` 397 | */ 398 | $log.debug.logs = []; 399 | }; 400 | 401 | /** 402 | * @ngdoc method 403 | * @name $log#assertEmpty 404 | * 405 | * @description 406 | * Assert that the all of the logging methods have no logged messages. If messages present, an 407 | * exception is thrown. 408 | */ 409 | $log.assertEmpty = function() { 410 | var errors = []; 411 | angular.forEach(['error', 'warn', 'info', 'log', 'debug'], function(logLevel) { 412 | angular.forEach($log[logLevel].logs, function(log) { 413 | angular.forEach(log, function (logItem) { 414 | errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + 415 | (logItem.stack || '')); 416 | }); 417 | }); 418 | }); 419 | if (errors.length) { 420 | errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or "+ 421 | "an expected log message was not checked and removed:"); 422 | errors.push(''); 423 | throw new Error(errors.join('\n---------\n')); 424 | } 425 | }; 426 | 427 | $log.reset(); 428 | return $log; 429 | }; 430 | }; 431 | 432 | 433 | /** 434 | * @ngdoc service 435 | * @name $interval 436 | * 437 | * @description 438 | * Mock implementation of the $interval service. 439 | * 440 | * Use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 441 | * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 442 | * time. 443 | * 444 | * @param {function()} fn A function that should be called repeatedly. 445 | * @param {number} delay Number of milliseconds between each function call. 446 | * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 447 | * indefinitely. 448 | * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 449 | * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 450 | * @returns {promise} A promise which will be notified on each iteration. 451 | */ 452 | angular.mock.$IntervalProvider = function() { 453 | this.$get = ['$rootScope', '$q', 454 | function($rootScope, $q) { 455 | var repeatFns = [], 456 | nextRepeatId = 0, 457 | now = 0; 458 | 459 | var $interval = function(fn, delay, count, invokeApply) { 460 | var deferred = $q.defer(), 461 | promise = deferred.promise, 462 | iteration = 0, 463 | skipApply = (angular.isDefined(invokeApply) && !invokeApply); 464 | 465 | count = (angular.isDefined(count)) ? count : 0; 466 | promise.then(null, null, fn); 467 | 468 | promise.$$intervalId = nextRepeatId; 469 | 470 | function tick() { 471 | deferred.notify(iteration++); 472 | 473 | if (count > 0 && iteration >= count) { 474 | var fnIndex; 475 | deferred.resolve(iteration); 476 | 477 | angular.forEach(repeatFns, function(fn, index) { 478 | if (fn.id === promise.$$intervalId) fnIndex = index; 479 | }); 480 | 481 | if (fnIndex !== undefined) { 482 | repeatFns.splice(fnIndex, 1); 483 | } 484 | } 485 | 486 | if (!skipApply) $rootScope.$apply(); 487 | } 488 | 489 | repeatFns.push({ 490 | nextTime:(now + delay), 491 | delay: delay, 492 | fn: tick, 493 | id: nextRepeatId, 494 | deferred: deferred 495 | }); 496 | repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;}); 497 | 498 | nextRepeatId++; 499 | return promise; 500 | }; 501 | /** 502 | * @ngdoc method 503 | * @name $interval#cancel 504 | * 505 | * @description 506 | * Cancels a task associated with the `promise`. 507 | * 508 | * @param {promise} promise A promise from calling the `$interval` function. 509 | * @returns {boolean} Returns `true` if the task was successfully cancelled. 510 | */ 511 | $interval.cancel = function(promise) { 512 | if(!promise) return false; 513 | var fnIndex; 514 | 515 | angular.forEach(repeatFns, function(fn, index) { 516 | if (fn.id === promise.$$intervalId) fnIndex = index; 517 | }); 518 | 519 | if (fnIndex !== undefined) { 520 | repeatFns[fnIndex].deferred.reject('canceled'); 521 | repeatFns.splice(fnIndex, 1); 522 | return true; 523 | } 524 | 525 | return false; 526 | }; 527 | 528 | /** 529 | * @ngdoc method 530 | * @name $interval#flush 531 | * @description 532 | * 533 | * Runs interval tasks scheduled to be run in the next `millis` milliseconds. 534 | * 535 | * @param {number=} millis maximum timeout amount to flush up until. 536 | * 537 | * @return {number} The amount of time moved forward. 538 | */ 539 | $interval.flush = function(millis) { 540 | now += millis; 541 | while (repeatFns.length && repeatFns[0].nextTime <= now) { 542 | var task = repeatFns[0]; 543 | task.fn(); 544 | task.nextTime += task.delay; 545 | repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;}); 546 | } 547 | return millis; 548 | }; 549 | 550 | return $interval; 551 | }]; 552 | }; 553 | 554 | 555 | /* jshint -W101 */ 556 | /* The R_ISO8061_STR regex is never going to fit into the 100 char limit! 557 | * This directive should go inside the anonymous function but a bug in JSHint means that it would 558 | * not be enacted early enough to prevent the warning. 559 | */ 560 | var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; 561 | 562 | function jsonStringToDate(string) { 563 | var match; 564 | if (match = string.match(R_ISO8061_STR)) { 565 | var date = new Date(0), 566 | tzHour = 0, 567 | tzMin = 0; 568 | if (match[9]) { 569 | tzHour = int(match[9] + match[10]); 570 | tzMin = int(match[9] + match[11]); 571 | } 572 | date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); 573 | date.setUTCHours(int(match[4]||0) - tzHour, 574 | int(match[5]||0) - tzMin, 575 | int(match[6]||0), 576 | int(match[7]||0)); 577 | return date; 578 | } 579 | return string; 580 | } 581 | 582 | function int(str) { 583 | return parseInt(str, 10); 584 | } 585 | 586 | function padNumber(num, digits, trim) { 587 | var neg = ''; 588 | if (num < 0) { 589 | neg = '-'; 590 | num = -num; 591 | } 592 | num = '' + num; 593 | while(num.length < digits) num = '0' + num; 594 | if (trim) 595 | num = num.substr(num.length - digits); 596 | return neg + num; 597 | } 598 | 599 | 600 | /** 601 | * @ngdoc type 602 | * @name angular.mock.TzDate 603 | * @description 604 | * 605 | * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. 606 | * 607 | * Mock of the Date type which has its timezone specified via constructor arg. 608 | * 609 | * The main purpose is to create Date-like instances with timezone fixed to the specified timezone 610 | * offset, so that we can test code that depends on local timezone settings without dependency on 611 | * the time zone settings of the machine where the code is running. 612 | * 613 | * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) 614 | * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* 615 | * 616 | * @example 617 | * !!!! WARNING !!!!! 618 | * This is not a complete Date object so only methods that were implemented can be called safely. 619 | * To make matters worse, TzDate instances inherit stuff from Date via a prototype. 620 | * 621 | * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is 622 | * incomplete we might be missing some non-standard methods. This can result in errors like: 623 | * "Date.prototype.foo called on incompatible Object". 624 | * 625 | * ```js 626 | * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z'); 627 | * newYearInBratislava.getTimezoneOffset() => -60; 628 | * newYearInBratislava.getFullYear() => 2010; 629 | * newYearInBratislava.getMonth() => 0; 630 | * newYearInBratislava.getDate() => 1; 631 | * newYearInBratislava.getHours() => 0; 632 | * newYearInBratislava.getMinutes() => 0; 633 | * newYearInBratislava.getSeconds() => 0; 634 | * ``` 635 | * 636 | */ 637 | angular.mock.TzDate = function (offset, timestamp) { 638 | var self = new Date(0); 639 | if (angular.isString(timestamp)) { 640 | var tsStr = timestamp; 641 | 642 | self.origDate = jsonStringToDate(timestamp); 643 | 644 | timestamp = self.origDate.getTime(); 645 | if (isNaN(timestamp)) 646 | throw { 647 | name: "Illegal Argument", 648 | message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" 649 | }; 650 | } else { 651 | self.origDate = new Date(timestamp); 652 | } 653 | 654 | var localOffset = new Date(timestamp).getTimezoneOffset(); 655 | self.offsetDiff = localOffset*60*1000 - offset*1000*60*60; 656 | self.date = new Date(timestamp + self.offsetDiff); 657 | 658 | self.getTime = function() { 659 | return self.date.getTime() - self.offsetDiff; 660 | }; 661 | 662 | self.toLocaleDateString = function() { 663 | return self.date.toLocaleDateString(); 664 | }; 665 | 666 | self.getFullYear = function() { 667 | return self.date.getFullYear(); 668 | }; 669 | 670 | self.getMonth = function() { 671 | return self.date.getMonth(); 672 | }; 673 | 674 | self.getDate = function() { 675 | return self.date.getDate(); 676 | }; 677 | 678 | self.getHours = function() { 679 | return self.date.getHours(); 680 | }; 681 | 682 | self.getMinutes = function() { 683 | return self.date.getMinutes(); 684 | }; 685 | 686 | self.getSeconds = function() { 687 | return self.date.getSeconds(); 688 | }; 689 | 690 | self.getMilliseconds = function() { 691 | return self.date.getMilliseconds(); 692 | }; 693 | 694 | self.getTimezoneOffset = function() { 695 | return offset * 60; 696 | }; 697 | 698 | self.getUTCFullYear = function() { 699 | return self.origDate.getUTCFullYear(); 700 | }; 701 | 702 | self.getUTCMonth = function() { 703 | return self.origDate.getUTCMonth(); 704 | }; 705 | 706 | self.getUTCDate = function() { 707 | return self.origDate.getUTCDate(); 708 | }; 709 | 710 | self.getUTCHours = function() { 711 | return self.origDate.getUTCHours(); 712 | }; 713 | 714 | self.getUTCMinutes = function() { 715 | return self.origDate.getUTCMinutes(); 716 | }; 717 | 718 | self.getUTCSeconds = function() { 719 | return self.origDate.getUTCSeconds(); 720 | }; 721 | 722 | self.getUTCMilliseconds = function() { 723 | return self.origDate.getUTCMilliseconds(); 724 | }; 725 | 726 | self.getDay = function() { 727 | return self.date.getDay(); 728 | }; 729 | 730 | // provide this method only on browsers that already have it 731 | if (self.toISOString) { 732 | self.toISOString = function() { 733 | return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + 734 | padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + 735 | padNumber(self.origDate.getUTCDate(), 2) + 'T' + 736 | padNumber(self.origDate.getUTCHours(), 2) + ':' + 737 | padNumber(self.origDate.getUTCMinutes(), 2) + ':' + 738 | padNumber(self.origDate.getUTCSeconds(), 2) + '.' + 739 | padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z'; 740 | }; 741 | } 742 | 743 | //hide all methods not implemented in this mock that the Date prototype exposes 744 | var unimplementedMethods = ['getUTCDay', 745 | 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 746 | 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', 747 | 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', 748 | 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', 749 | 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; 750 | 751 | angular.forEach(unimplementedMethods, function(methodName) { 752 | self[methodName] = function() { 753 | throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock"); 754 | }; 755 | }); 756 | 757 | return self; 758 | }; 759 | 760 | //make "tzDateInstance instanceof Date" return true 761 | angular.mock.TzDate.prototype = Date.prototype; 762 | /* jshint +W101 */ 763 | 764 | angular.mock.animate = angular.module('ngAnimateMock', ['ng']) 765 | 766 | .config(['$provide', function($provide) { 767 | 768 | var reflowQueue = []; 769 | $provide.value('$$animateReflow', function(fn) { 770 | var index = reflowQueue.length; 771 | reflowQueue.push(fn); 772 | return function cancel() { 773 | reflowQueue.splice(index, 1); 774 | }; 775 | }); 776 | 777 | $provide.decorator('$animate', function($delegate, $$asyncCallback) { 778 | var animate = { 779 | queue : [], 780 | enabled : $delegate.enabled, 781 | triggerCallbacks : function() { 782 | $$asyncCallback.flush(); 783 | }, 784 | triggerReflow : function() { 785 | angular.forEach(reflowQueue, function(fn) { 786 | fn(); 787 | }); 788 | reflowQueue = []; 789 | } 790 | }; 791 | 792 | angular.forEach( 793 | ['enter','leave','move','addClass','removeClass','setClass'], function(method) { 794 | animate[method] = function() { 795 | animate.queue.push({ 796 | event : method, 797 | element : arguments[0], 798 | args : arguments 799 | }); 800 | $delegate[method].apply($delegate, arguments); 801 | }; 802 | }); 803 | 804 | return animate; 805 | }); 806 | 807 | }]); 808 | 809 | 810 | /** 811 | * @ngdoc function 812 | * @name angular.mock.dump 813 | * @description 814 | * 815 | * *NOTE*: this is not an injectable instance, just a globally available function. 816 | * 817 | * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for 818 | * debugging. 819 | * 820 | * This method is also available on window, where it can be used to display objects on debug 821 | * console. 822 | * 823 | * @param {*} object - any object to turn into string. 824 | * @return {string} a serialized string of the argument 825 | */ 826 | angular.mock.dump = function(object) { 827 | return serialize(object); 828 | 829 | function serialize(object) { 830 | var out; 831 | 832 | if (angular.isElement(object)) { 833 | object = angular.element(object); 834 | out = angular.element('
'); 835 | angular.forEach(object, function(element) { 836 | out.append(angular.element(element).clone()); 837 | }); 838 | out = out.html(); 839 | } else if (angular.isArray(object)) { 840 | out = []; 841 | angular.forEach(object, function(o) { 842 | out.push(serialize(o)); 843 | }); 844 | out = '[ ' + out.join(', ') + ' ]'; 845 | } else if (angular.isObject(object)) { 846 | if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { 847 | out = serializeScope(object); 848 | } else if (object instanceof Error) { 849 | out = object.stack || ('' + object.name + ': ' + object.message); 850 | } else { 851 | // TODO(i): this prevents methods being logged, 852 | // we should have a better way to serialize objects 853 | out = angular.toJson(object, true); 854 | } 855 | } else { 856 | out = String(object); 857 | } 858 | 859 | return out; 860 | } 861 | 862 | function serializeScope(scope, offset) { 863 | offset = offset || ' '; 864 | var log = [offset + 'Scope(' + scope.$id + '): {']; 865 | for ( var key in scope ) { 866 | if (Object.prototype.hasOwnProperty.call(scope, key) && !key.match(/^(\$|this)/)) { 867 | log.push(' ' + key + ': ' + angular.toJson(scope[key])); 868 | } 869 | } 870 | var child = scope.$$childHead; 871 | while(child) { 872 | log.push(serializeScope(child, offset + ' ')); 873 | child = child.$$nextSibling; 874 | } 875 | log.push('}'); 876 | return log.join('\n' + offset); 877 | } 878 | }; 879 | 880 | /** 881 | * @ngdoc service 882 | * @name $httpBackend 883 | * @description 884 | * Fake HTTP backend implementation suitable for unit testing applications that use the 885 | * {@link ng.$http $http service}. 886 | * 887 | * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less 888 | * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. 889 | * 890 | * During unit testing, we want our unit tests to run quickly and have no external dependencies so 891 | * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or 892 | * [JSONP](http://en.wikipedia.org/wiki/JSONP) requests to a real server. All we really need is 893 | * to verify whether a certain request has been sent or not, or alternatively just let the 894 | * application make requests, respond with pre-trained responses and assert that the end result is 895 | * what we expect it to be. 896 | * 897 | * This mock implementation can be used to respond with static or dynamic responses via the 898 | * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). 899 | * 900 | * When an Angular application needs some data from a server, it calls the $http service, which 901 | * sends the request to a real server using $httpBackend service. With dependency injection, it is 902 | * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify 903 | * the requests and respond with some testing data without sending a request to a real server. 904 | * 905 | * There are two ways to specify what test data should be returned as http responses by the mock 906 | * backend when the code under test makes http requests: 907 | * 908 | * - `$httpBackend.expect` - specifies a request expectation 909 | * - `$httpBackend.when` - specifies a backend definition 910 | * 911 | * 912 | * # Request Expectations vs Backend Definitions 913 | * 914 | * Request expectations provide a way to make assertions about requests made by the application and 915 | * to define responses for those requests. The test will fail if the expected requests are not made 916 | * or they are made in the wrong order. 917 | * 918 | * Backend definitions allow you to define a fake backend for your application which doesn't assert 919 | * if a particular request was made or not, it just returns a trained response if a request is made. 920 | * The test will pass whether or not the request gets made during testing. 921 | * 922 | * 923 | * 924 | * 925 | * 926 | * 927 | * 928 | * 929 | * 930 | * 931 | * 932 | * 933 | * 934 | * 935 | * 936 | * 937 | * 938 | * 939 | * 940 | * 941 | * 942 | * 943 | * 944 | * 945 | * 946 | * 947 | * 948 | * 949 | * 950 | * 951 | * 952 | * 953 | * 954 | * 955 | *
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
956 | * 957 | * In cases where both backend definitions and request expectations are specified during unit 958 | * testing, the request expectations are evaluated first. 959 | * 960 | * If a request expectation has no response specified, the algorithm will search your backend 961 | * definitions for an appropriate response. 962 | * 963 | * If a request didn't match any expectation or if the expectation doesn't have the response 964 | * defined, the backend definitions are evaluated in sequential order to see if any of them match 965 | * the request. The response from the first matched definition is returned. 966 | * 967 | * 968 | * # Flushing HTTP requests 969 | * 970 | * The $httpBackend used in production always responds to requests asynchronously. If we preserved 971 | * this behavior in unit testing, we'd have to create async unit tests, which are hard to write, 972 | * to follow and to maintain. But neither can the testing mock respond synchronously; that would 973 | * change the execution of the code under test. For this reason, the mock $httpBackend has a 974 | * `flush()` method, which allows the test to explicitly flush pending requests. This preserves 975 | * the async api of the backend, while allowing the test to execute synchronously. 976 | * 977 | * 978 | * # Unit testing with mock $httpBackend 979 | * The following code shows how to setup and use the mock backend when unit testing a controller. 980 | * First we create the controller under test: 981 | * 982 | ```js 983 | // The controller code 984 | function MyController($scope, $http) { 985 | var authToken; 986 | 987 | $http.get('/auth.py').success(function(data, status, headers) { 988 | authToken = headers('A-Token'); 989 | $scope.user = data; 990 | }); 991 | 992 | $scope.saveMessage = function(message) { 993 | var headers = { 'Authorization': authToken }; 994 | $scope.status = 'Saving...'; 995 | 996 | $http.post('/add-msg.py', message, { headers: headers } ).success(function(response) { 997 | $scope.status = ''; 998 | }).error(function() { 999 | $scope.status = 'ERROR!'; 1000 | }); 1001 | }; 1002 | } 1003 | ``` 1004 | * 1005 | * Now we setup the mock backend and create the test specs: 1006 | * 1007 | ```js 1008 | // testing controller 1009 | describe('MyController', function() { 1010 | var $httpBackend, $rootScope, createController; 1011 | 1012 | beforeEach(inject(function($injector) { 1013 | // Set up the mock http service responses 1014 | $httpBackend = $injector.get('$httpBackend'); 1015 | // backend definition common for all tests 1016 | $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'}); 1017 | 1018 | // Get hold of a scope (i.e. the root scope) 1019 | $rootScope = $injector.get('$rootScope'); 1020 | // The $controller service is used to create instances of controllers 1021 | var $controller = $injector.get('$controller'); 1022 | 1023 | createController = function() { 1024 | return $controller('MyController', {'$scope' : $rootScope }); 1025 | }; 1026 | })); 1027 | 1028 | 1029 | afterEach(function() { 1030 | $httpBackend.verifyNoOutstandingExpectation(); 1031 | $httpBackend.verifyNoOutstandingRequest(); 1032 | }); 1033 | 1034 | 1035 | it('should fetch authentication token', function() { 1036 | $httpBackend.expectGET('/auth.py'); 1037 | var controller = createController(); 1038 | $httpBackend.flush(); 1039 | }); 1040 | 1041 | 1042 | it('should send msg to server', function() { 1043 | var controller = createController(); 1044 | $httpBackend.flush(); 1045 | 1046 | // now you don’t care about the authentication, but 1047 | // the controller will still send the request and 1048 | // $httpBackend will respond without you having to 1049 | // specify the expectation and response for this request 1050 | 1051 | $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, ''); 1052 | $rootScope.saveMessage('message content'); 1053 | expect($rootScope.status).toBe('Saving...'); 1054 | $httpBackend.flush(); 1055 | expect($rootScope.status).toBe(''); 1056 | }); 1057 | 1058 | 1059 | it('should send auth header', function() { 1060 | var controller = createController(); 1061 | $httpBackend.flush(); 1062 | 1063 | $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) { 1064 | // check if the header was send, if it wasn't the expectation won't 1065 | // match the request and the test will fail 1066 | return headers['Authorization'] == 'xxx'; 1067 | }).respond(201, ''); 1068 | 1069 | $rootScope.saveMessage('whatever'); 1070 | $httpBackend.flush(); 1071 | }); 1072 | }); 1073 | ``` 1074 | */ 1075 | angular.mock.$HttpBackendProvider = function() { 1076 | this.$get = ['$rootScope', createHttpBackendMock]; 1077 | }; 1078 | 1079 | /** 1080 | * General factory function for $httpBackend mock. 1081 | * Returns instance for unit testing (when no arguments specified): 1082 | * - passing through is disabled 1083 | * - auto flushing is disabled 1084 | * 1085 | * Returns instance for e2e testing (when `$delegate` and `$browser` specified): 1086 | * - passing through (delegating request to real backend) is enabled 1087 | * - auto flushing is enabled 1088 | * 1089 | * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) 1090 | * @param {Object=} $browser Auto-flushing enabled if specified 1091 | * @return {Object} Instance of $httpBackend mock 1092 | */ 1093 | function createHttpBackendMock($rootScope, $delegate, $browser) { 1094 | var definitions = [], 1095 | expectations = [], 1096 | responses = [], 1097 | responsesPush = angular.bind(responses, responses.push), 1098 | copy = angular.copy; 1099 | 1100 | function createResponse(status, data, headers, statusText) { 1101 | if (angular.isFunction(status)) return status; 1102 | 1103 | return function() { 1104 | return angular.isNumber(status) 1105 | ? [status, data, headers, statusText] 1106 | : [200, status, data]; 1107 | }; 1108 | } 1109 | 1110 | // TODO(vojta): change params to: method, url, data, headers, callback 1111 | function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) { 1112 | var xhr = new MockXhr(), 1113 | expectation = expectations[0], 1114 | wasExpected = false; 1115 | 1116 | function prettyPrint(data) { 1117 | return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) 1118 | ? data 1119 | : angular.toJson(data); 1120 | } 1121 | 1122 | function wrapResponse(wrapped) { 1123 | if (!$browser && timeout && timeout.then) timeout.then(handleTimeout); 1124 | 1125 | return handleResponse; 1126 | 1127 | function handleResponse() { 1128 | var response = wrapped.response(method, url, data, headers); 1129 | xhr.$$respHeaders = response[2]; 1130 | callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(), 1131 | copy(response[3] || '')); 1132 | } 1133 | 1134 | function handleTimeout() { 1135 | for (var i = 0, ii = responses.length; i < ii; i++) { 1136 | if (responses[i] === handleResponse) { 1137 | responses.splice(i, 1); 1138 | callback(-1, undefined, ''); 1139 | break; 1140 | } 1141 | } 1142 | } 1143 | } 1144 | 1145 | if (expectation && expectation.match(method, url)) { 1146 | if (!expectation.matchData(data)) 1147 | throw new Error('Expected ' + expectation + ' with different data\n' + 1148 | 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); 1149 | 1150 | if (!expectation.matchHeaders(headers)) 1151 | throw new Error('Expected ' + expectation + ' with different headers\n' + 1152 | 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + 1153 | prettyPrint(headers)); 1154 | 1155 | expectations.shift(); 1156 | 1157 | if (expectation.response) { 1158 | responses.push(wrapResponse(expectation)); 1159 | return; 1160 | } 1161 | wasExpected = true; 1162 | } 1163 | 1164 | var i = -1, definition; 1165 | while ((definition = definitions[++i])) { 1166 | if (definition.match(method, url, data, headers || {})) { 1167 | if (definition.response) { 1168 | // if $browser specified, we do auto flush all requests 1169 | ($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); 1170 | } else if (definition.passThrough) { 1171 | $delegate(method, url, data, callback, headers, timeout, withCredentials); 1172 | } else throw new Error('No response defined !'); 1173 | return; 1174 | } 1175 | } 1176 | throw wasExpected ? 1177 | new Error('No response defined !') : 1178 | new Error('Unexpected request: ' + method + ' ' + url + '\n' + 1179 | (expectation ? 'Expected ' + expectation : 'No more request expected')); 1180 | } 1181 | 1182 | /** 1183 | * @ngdoc method 1184 | * @name $httpBackend#when 1185 | * @description 1186 | * Creates a new backend definition. 1187 | * 1188 | * @param {string} method HTTP method. 1189 | * @param {string|RegExp} url HTTP url. 1190 | * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives 1191 | * data string and returns true if the data is as expected. 1192 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1193 | * object and returns true if the headers match the current definition. 1194 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1195 | * request is handled. 1196 | * 1197 | * - respond – 1198 | * `{function([status,] data[, headers, statusText]) 1199 | * | function(function(method, url, data, headers)}` 1200 | * – The respond method takes a set of static data to be returned or a function that can 1201 | * return an array containing response status (number), response data (string), response 1202 | * headers (Object), and the text for the status (string). 1203 | */ 1204 | $httpBackend.when = function(method, url, data, headers) { 1205 | var definition = new MockHttpExpectation(method, url, data, headers), 1206 | chain = { 1207 | respond: function(status, data, headers, statusText) { 1208 | definition.response = createResponse(status, data, headers, statusText); 1209 | } 1210 | }; 1211 | 1212 | if ($browser) { 1213 | chain.passThrough = function() { 1214 | definition.passThrough = true; 1215 | }; 1216 | } 1217 | 1218 | definitions.push(definition); 1219 | return chain; 1220 | }; 1221 | 1222 | /** 1223 | * @ngdoc method 1224 | * @name $httpBackend#whenGET 1225 | * @description 1226 | * Creates a new backend definition for GET requests. For more info see `when()`. 1227 | * 1228 | * @param {string|RegExp} url HTTP url. 1229 | * @param {(Object|function(Object))=} headers HTTP headers. 1230 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1231 | * request is handled. 1232 | */ 1233 | 1234 | /** 1235 | * @ngdoc method 1236 | * @name $httpBackend#whenHEAD 1237 | * @description 1238 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 1239 | * 1240 | * @param {string|RegExp} url HTTP url. 1241 | * @param {(Object|function(Object))=} headers HTTP headers. 1242 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1243 | * request is handled. 1244 | */ 1245 | 1246 | /** 1247 | * @ngdoc method 1248 | * @name $httpBackend#whenDELETE 1249 | * @description 1250 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 1251 | * 1252 | * @param {string|RegExp} url HTTP url. 1253 | * @param {(Object|function(Object))=} headers HTTP headers. 1254 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1255 | * request is handled. 1256 | */ 1257 | 1258 | /** 1259 | * @ngdoc method 1260 | * @name $httpBackend#whenPOST 1261 | * @description 1262 | * Creates a new backend definition for POST requests. For more info see `when()`. 1263 | * 1264 | * @param {string|RegExp} url HTTP url. 1265 | * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives 1266 | * data string and returns true if the data is as expected. 1267 | * @param {(Object|function(Object))=} headers HTTP headers. 1268 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1269 | * request is handled. 1270 | */ 1271 | 1272 | /** 1273 | * @ngdoc method 1274 | * @name $httpBackend#whenPUT 1275 | * @description 1276 | * Creates a new backend definition for PUT requests. For more info see `when()`. 1277 | * 1278 | * @param {string|RegExp} url HTTP url. 1279 | * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives 1280 | * data string and returns true if the data is as expected. 1281 | * @param {(Object|function(Object))=} headers HTTP headers. 1282 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1283 | * request is handled. 1284 | */ 1285 | 1286 | /** 1287 | * @ngdoc method 1288 | * @name $httpBackend#whenJSONP 1289 | * @description 1290 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 1291 | * 1292 | * @param {string|RegExp} url HTTP url. 1293 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1294 | * request is handled. 1295 | */ 1296 | createShortMethods('when'); 1297 | 1298 | 1299 | /** 1300 | * @ngdoc method 1301 | * @name $httpBackend#expect 1302 | * @description 1303 | * Creates a new request expectation. 1304 | * 1305 | * @param {string} method HTTP method. 1306 | * @param {string|RegExp} url HTTP url. 1307 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1308 | * receives data string and returns true if the data is as expected, or Object if request body 1309 | * is in JSON format. 1310 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1311 | * object and returns true if the headers match the current expectation. 1312 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1313 | * request is handled. 1314 | * 1315 | * - respond – 1316 | * `{function([status,] data[, headers, statusText]) 1317 | * | function(function(method, url, data, headers)}` 1318 | * – The respond method takes a set of static data to be returned or a function that can 1319 | * return an array containing response status (number), response data (string), response 1320 | * headers (Object), and the text for the status (string). 1321 | */ 1322 | $httpBackend.expect = function(method, url, data, headers) { 1323 | var expectation = new MockHttpExpectation(method, url, data, headers); 1324 | expectations.push(expectation); 1325 | return { 1326 | respond: function (status, data, headers, statusText) { 1327 | expectation.response = createResponse(status, data, headers, statusText); 1328 | } 1329 | }; 1330 | }; 1331 | 1332 | 1333 | /** 1334 | * @ngdoc method 1335 | * @name $httpBackend#expectGET 1336 | * @description 1337 | * Creates a new request expectation for GET requests. For more info see `expect()`. 1338 | * 1339 | * @param {string|RegExp} url HTTP url. 1340 | * @param {Object=} headers HTTP headers. 1341 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1342 | * request is handled. See #expect for more info. 1343 | */ 1344 | 1345 | /** 1346 | * @ngdoc method 1347 | * @name $httpBackend#expectHEAD 1348 | * @description 1349 | * Creates a new request expectation for HEAD requests. For more info see `expect()`. 1350 | * 1351 | * @param {string|RegExp} url HTTP url. 1352 | * @param {Object=} headers HTTP headers. 1353 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1354 | * request is handled. 1355 | */ 1356 | 1357 | /** 1358 | * @ngdoc method 1359 | * @name $httpBackend#expectDELETE 1360 | * @description 1361 | * Creates a new request expectation for DELETE requests. For more info see `expect()`. 1362 | * 1363 | * @param {string|RegExp} url HTTP url. 1364 | * @param {Object=} headers HTTP headers. 1365 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1366 | * request is handled. 1367 | */ 1368 | 1369 | /** 1370 | * @ngdoc method 1371 | * @name $httpBackend#expectPOST 1372 | * @description 1373 | * Creates a new request expectation for POST requests. For more info see `expect()`. 1374 | * 1375 | * @param {string|RegExp} url HTTP url. 1376 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1377 | * receives data string and returns true if the data is as expected, or Object if request body 1378 | * is in JSON format. 1379 | * @param {Object=} headers HTTP headers. 1380 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1381 | * request is handled. 1382 | */ 1383 | 1384 | /** 1385 | * @ngdoc method 1386 | * @name $httpBackend#expectPUT 1387 | * @description 1388 | * Creates a new request expectation for PUT requests. For more info see `expect()`. 1389 | * 1390 | * @param {string|RegExp} url HTTP url. 1391 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1392 | * receives data string and returns true if the data is as expected, or Object if request body 1393 | * is in JSON format. 1394 | * @param {Object=} headers HTTP headers. 1395 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1396 | * request is handled. 1397 | */ 1398 | 1399 | /** 1400 | * @ngdoc method 1401 | * @name $httpBackend#expectPATCH 1402 | * @description 1403 | * Creates a new request expectation for PATCH requests. For more info see `expect()`. 1404 | * 1405 | * @param {string|RegExp} url HTTP url. 1406 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1407 | * receives data string and returns true if the data is as expected, or Object if request body 1408 | * is in JSON format. 1409 | * @param {Object=} headers HTTP headers. 1410 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1411 | * request is handled. 1412 | */ 1413 | 1414 | /** 1415 | * @ngdoc method 1416 | * @name $httpBackend#expectJSONP 1417 | * @description 1418 | * Creates a new request expectation for JSONP requests. For more info see `expect()`. 1419 | * 1420 | * @param {string|RegExp} url HTTP url. 1421 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1422 | * request is handled. 1423 | */ 1424 | createShortMethods('expect'); 1425 | 1426 | 1427 | /** 1428 | * @ngdoc method 1429 | * @name $httpBackend#flush 1430 | * @description 1431 | * Flushes all pending requests using the trained responses. 1432 | * 1433 | * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, 1434 | * all pending requests will be flushed. If there are no pending requests when the flush method 1435 | * is called an exception is thrown (as this typically a sign of programming error). 1436 | */ 1437 | $httpBackend.flush = function(count) { 1438 | $rootScope.$digest(); 1439 | if (!responses.length) throw new Error('No pending request to flush !'); 1440 | 1441 | if (angular.isDefined(count)) { 1442 | while (count--) { 1443 | if (!responses.length) throw new Error('No more pending request to flush !'); 1444 | responses.shift()(); 1445 | } 1446 | } else { 1447 | while (responses.length) { 1448 | responses.shift()(); 1449 | } 1450 | } 1451 | $httpBackend.verifyNoOutstandingExpectation(); 1452 | }; 1453 | 1454 | 1455 | /** 1456 | * @ngdoc method 1457 | * @name $httpBackend#verifyNoOutstandingExpectation 1458 | * @description 1459 | * Verifies that all of the requests defined via the `expect` api were made. If any of the 1460 | * requests were not made, verifyNoOutstandingExpectation throws an exception. 1461 | * 1462 | * Typically, you would call this method following each test case that asserts requests using an 1463 | * "afterEach" clause. 1464 | * 1465 | * ```js 1466 | * afterEach($httpBackend.verifyNoOutstandingExpectation); 1467 | * ``` 1468 | */ 1469 | $httpBackend.verifyNoOutstandingExpectation = function() { 1470 | $rootScope.$digest(); 1471 | if (expectations.length) { 1472 | throw new Error('Unsatisfied requests: ' + expectations.join(', ')); 1473 | } 1474 | }; 1475 | 1476 | 1477 | /** 1478 | * @ngdoc method 1479 | * @name $httpBackend#verifyNoOutstandingRequest 1480 | * @description 1481 | * Verifies that there are no outstanding requests that need to be flushed. 1482 | * 1483 | * Typically, you would call this method following each test case that asserts requests using an 1484 | * "afterEach" clause. 1485 | * 1486 | * ```js 1487 | * afterEach($httpBackend.verifyNoOutstandingRequest); 1488 | * ``` 1489 | */ 1490 | $httpBackend.verifyNoOutstandingRequest = function() { 1491 | if (responses.length) { 1492 | throw new Error('Unflushed requests: ' + responses.length); 1493 | } 1494 | }; 1495 | 1496 | 1497 | /** 1498 | * @ngdoc method 1499 | * @name $httpBackend#resetExpectations 1500 | * @description 1501 | * Resets all request expectations, but preserves all backend definitions. Typically, you would 1502 | * call resetExpectations during a multiple-phase test when you want to reuse the same instance of 1503 | * $httpBackend mock. 1504 | */ 1505 | $httpBackend.resetExpectations = function() { 1506 | expectations.length = 0; 1507 | responses.length = 0; 1508 | }; 1509 | 1510 | return $httpBackend; 1511 | 1512 | 1513 | function createShortMethods(prefix) { 1514 | angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) { 1515 | $httpBackend[prefix + method] = function(url, headers) { 1516 | return $httpBackend[prefix](method, url, undefined, headers); 1517 | }; 1518 | }); 1519 | 1520 | angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { 1521 | $httpBackend[prefix + method] = function(url, data, headers) { 1522 | return $httpBackend[prefix](method, url, data, headers); 1523 | }; 1524 | }); 1525 | } 1526 | } 1527 | 1528 | function MockHttpExpectation(method, url, data, headers) { 1529 | 1530 | this.data = data; 1531 | this.headers = headers; 1532 | 1533 | this.match = function(m, u, d, h) { 1534 | if (method != m) return false; 1535 | if (!this.matchUrl(u)) return false; 1536 | if (angular.isDefined(d) && !this.matchData(d)) return false; 1537 | if (angular.isDefined(h) && !this.matchHeaders(h)) return false; 1538 | return true; 1539 | }; 1540 | 1541 | this.matchUrl = function(u) { 1542 | if (!url) return true; 1543 | if (angular.isFunction(url.test)) return url.test(u); 1544 | return url == u; 1545 | }; 1546 | 1547 | this.matchHeaders = function(h) { 1548 | if (angular.isUndefined(headers)) return true; 1549 | if (angular.isFunction(headers)) return headers(h); 1550 | return angular.equals(headers, h); 1551 | }; 1552 | 1553 | this.matchData = function(d) { 1554 | if (angular.isUndefined(data)) return true; 1555 | if (data && angular.isFunction(data.test)) return data.test(d); 1556 | if (data && angular.isFunction(data)) return data(d); 1557 | if (data && !angular.isString(data)) return angular.equals(data, angular.fromJson(d)); 1558 | return data == d; 1559 | }; 1560 | 1561 | this.toString = function() { 1562 | return method + ' ' + url; 1563 | }; 1564 | } 1565 | 1566 | function createMockXhr() { 1567 | return new MockXhr(); 1568 | } 1569 | 1570 | function MockXhr() { 1571 | 1572 | // hack for testing $http, $httpBackend 1573 | MockXhr.$$lastInstance = this; 1574 | 1575 | this.open = function(method, url, async) { 1576 | this.$$method = method; 1577 | this.$$url = url; 1578 | this.$$async = async; 1579 | this.$$reqHeaders = {}; 1580 | this.$$respHeaders = {}; 1581 | }; 1582 | 1583 | this.send = function(data) { 1584 | this.$$data = data; 1585 | }; 1586 | 1587 | this.setRequestHeader = function(key, value) { 1588 | this.$$reqHeaders[key] = value; 1589 | }; 1590 | 1591 | this.getResponseHeader = function(name) { 1592 | // the lookup must be case insensitive, 1593 | // that's why we try two quick lookups first and full scan last 1594 | var header = this.$$respHeaders[name]; 1595 | if (header) return header; 1596 | 1597 | name = angular.lowercase(name); 1598 | header = this.$$respHeaders[name]; 1599 | if (header) return header; 1600 | 1601 | header = undefined; 1602 | angular.forEach(this.$$respHeaders, function(headerVal, headerName) { 1603 | if (!header && angular.lowercase(headerName) == name) header = headerVal; 1604 | }); 1605 | return header; 1606 | }; 1607 | 1608 | this.getAllResponseHeaders = function() { 1609 | var lines = []; 1610 | 1611 | angular.forEach(this.$$respHeaders, function(value, key) { 1612 | lines.push(key + ': ' + value); 1613 | }); 1614 | return lines.join('\n'); 1615 | }; 1616 | 1617 | this.abort = angular.noop; 1618 | } 1619 | 1620 | 1621 | /** 1622 | * @ngdoc service 1623 | * @name $timeout 1624 | * @description 1625 | * 1626 | * This service is just a simple decorator for {@link ng.$timeout $timeout} service 1627 | * that adds a "flush" and "verifyNoPendingTasks" methods. 1628 | */ 1629 | 1630 | angular.mock.$TimeoutDecorator = function($delegate, $browser) { 1631 | 1632 | /** 1633 | * @ngdoc method 1634 | * @name $timeout#flush 1635 | * @description 1636 | * 1637 | * Flushes the queue of pending tasks. 1638 | * 1639 | * @param {number=} delay maximum timeout amount to flush up until 1640 | */ 1641 | $delegate.flush = function(delay) { 1642 | $browser.defer.flush(delay); 1643 | }; 1644 | 1645 | /** 1646 | * @ngdoc method 1647 | * @name $timeout#verifyNoPendingTasks 1648 | * @description 1649 | * 1650 | * Verifies that there are no pending tasks that need to be flushed. 1651 | */ 1652 | $delegate.verifyNoPendingTasks = function() { 1653 | if ($browser.deferredFns.length) { 1654 | throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' + 1655 | formatPendingTasksAsString($browser.deferredFns)); 1656 | } 1657 | }; 1658 | 1659 | function formatPendingTasksAsString(tasks) { 1660 | var result = []; 1661 | angular.forEach(tasks, function(task) { 1662 | result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}'); 1663 | }); 1664 | 1665 | return result.join(', '); 1666 | } 1667 | 1668 | return $delegate; 1669 | }; 1670 | 1671 | angular.mock.$RAFDecorator = function($delegate) { 1672 | var queue = []; 1673 | var rafFn = function(fn) { 1674 | var index = queue.length; 1675 | queue.push(fn); 1676 | return function() { 1677 | queue.splice(index, 1); 1678 | }; 1679 | }; 1680 | 1681 | rafFn.supported = $delegate.supported; 1682 | 1683 | rafFn.flush = function() { 1684 | if(queue.length === 0) { 1685 | throw new Error('No rAF callbacks present'); 1686 | } 1687 | 1688 | var length = queue.length; 1689 | for(var i=0;i'); 1719 | }; 1720 | }; 1721 | 1722 | /** 1723 | * @ngdoc module 1724 | * @name ngMock 1725 | * @description 1726 | * 1727 | * # ngMock 1728 | * 1729 | * The `ngMock` module provides support to inject and mock Angular services into unit tests. 1730 | * In addition, ngMock also extends various core ng services such that they can be 1731 | * inspected and controlled in a synchronous manner within test code. 1732 | * 1733 | * 1734 | *
1735 | * 1736 | */ 1737 | angular.module('ngMock', ['ng']).provider({ 1738 | $browser: angular.mock.$BrowserProvider, 1739 | $exceptionHandler: angular.mock.$ExceptionHandlerProvider, 1740 | $log: angular.mock.$LogProvider, 1741 | $interval: angular.mock.$IntervalProvider, 1742 | $httpBackend: angular.mock.$HttpBackendProvider, 1743 | $rootElement: angular.mock.$RootElementProvider 1744 | }).config(['$provide', function($provide) { 1745 | $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); 1746 | $provide.decorator('$$rAF', angular.mock.$RAFDecorator); 1747 | $provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator); 1748 | }]); 1749 | 1750 | /** 1751 | * @ngdoc module 1752 | * @name ngMockE2E 1753 | * @module ngMockE2E 1754 | * @description 1755 | * 1756 | * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. 1757 | * Currently there is only one mock present in this module - 1758 | * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. 1759 | */ 1760 | angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { 1761 | $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); 1762 | }]); 1763 | 1764 | /** 1765 | * @ngdoc service 1766 | * @name $httpBackend 1767 | * @module ngMockE2E 1768 | * @description 1769 | * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of 1770 | * applications that use the {@link ng.$http $http service}. 1771 | * 1772 | * *Note*: For fake http backend implementation suitable for unit testing please see 1773 | * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. 1774 | * 1775 | * This implementation can be used to respond with static or dynamic responses via the `when` api 1776 | * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the 1777 | * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch 1778 | * templates from a webserver). 1779 | * 1780 | * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application 1781 | * is being developed with the real backend api replaced with a mock, it is often desirable for 1782 | * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch 1783 | * templates or static files from the webserver). To configure the backend with this behavior 1784 | * use the `passThrough` request handler of `when` instead of `respond`. 1785 | * 1786 | * Additionally, we don't want to manually have to flush mocked out requests like we do during unit 1787 | * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests 1788 | * automatically, closely simulating the behavior of the XMLHttpRequest object. 1789 | * 1790 | * To setup the application to run with this http backend, you have to create a module that depends 1791 | * on the `ngMockE2E` and your application modules and defines the fake backend: 1792 | * 1793 | * ```js 1794 | * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); 1795 | * myAppDev.run(function($httpBackend) { 1796 | * phones = [{name: 'phone1'}, {name: 'phone2'}]; 1797 | * 1798 | * // returns the current list of phones 1799 | * $httpBackend.whenGET('/phones').respond(phones); 1800 | * 1801 | * // adds a new phone to the phones array 1802 | * $httpBackend.whenPOST('/phones').respond(function(method, url, data) { 1803 | * var phone = angular.fromJson(data); 1804 | * phones.push(phone); 1805 | * return [200, phone, {}]; 1806 | * }); 1807 | * $httpBackend.whenGET(/^\/templates\//).passThrough(); 1808 | * //... 1809 | * }); 1810 | * ``` 1811 | * 1812 | * Afterwards, bootstrap your app with this new module. 1813 | */ 1814 | 1815 | /** 1816 | * @ngdoc method 1817 | * @name $httpBackend#when 1818 | * @module ngMockE2E 1819 | * @description 1820 | * Creates a new backend definition. 1821 | * 1822 | * @param {string} method HTTP method. 1823 | * @param {string|RegExp} url HTTP url. 1824 | * @param {(string|RegExp)=} data HTTP request body. 1825 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1826 | * object and returns true if the headers match the current definition. 1827 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1828 | * control how a matched request is handled. 1829 | * 1830 | * - respond – 1831 | * `{function([status,] data[, headers, statusText]) 1832 | * | function(function(method, url, data, headers)}` 1833 | * – The respond method takes a set of static data to be returned or a function that can return 1834 | * an array containing response status (number), response data (string), response headers 1835 | * (Object), and the text for the status (string). 1836 | * - passThrough – `{function()}` – Any request matching a backend definition with 1837 | * `passThrough` handler will be passed through to the real backend (an XHR request will be made 1838 | * to the server.) 1839 | */ 1840 | 1841 | /** 1842 | * @ngdoc method 1843 | * @name $httpBackend#whenGET 1844 | * @module ngMockE2E 1845 | * @description 1846 | * Creates a new backend definition for GET requests. For more info see `when()`. 1847 | * 1848 | * @param {string|RegExp} url HTTP url. 1849 | * @param {(Object|function(Object))=} headers HTTP headers. 1850 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1851 | * control how a matched request is handled. 1852 | */ 1853 | 1854 | /** 1855 | * @ngdoc method 1856 | * @name $httpBackend#whenHEAD 1857 | * @module ngMockE2E 1858 | * @description 1859 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 1860 | * 1861 | * @param {string|RegExp} url HTTP url. 1862 | * @param {(Object|function(Object))=} headers HTTP headers. 1863 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1864 | * control how a matched request is handled. 1865 | */ 1866 | 1867 | /** 1868 | * @ngdoc method 1869 | * @name $httpBackend#whenDELETE 1870 | * @module ngMockE2E 1871 | * @description 1872 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 1873 | * 1874 | * @param {string|RegExp} url HTTP url. 1875 | * @param {(Object|function(Object))=} headers HTTP headers. 1876 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1877 | * control how a matched request is handled. 1878 | */ 1879 | 1880 | /** 1881 | * @ngdoc method 1882 | * @name $httpBackend#whenPOST 1883 | * @module ngMockE2E 1884 | * @description 1885 | * Creates a new backend definition for POST requests. For more info see `when()`. 1886 | * 1887 | * @param {string|RegExp} url HTTP url. 1888 | * @param {(string|RegExp)=} data HTTP request body. 1889 | * @param {(Object|function(Object))=} headers HTTP headers. 1890 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1891 | * control how a matched request is handled. 1892 | */ 1893 | 1894 | /** 1895 | * @ngdoc method 1896 | * @name $httpBackend#whenPUT 1897 | * @module ngMockE2E 1898 | * @description 1899 | * Creates a new backend definition for PUT requests. For more info see `when()`. 1900 | * 1901 | * @param {string|RegExp} url HTTP url. 1902 | * @param {(string|RegExp)=} data HTTP request body. 1903 | * @param {(Object|function(Object))=} headers HTTP headers. 1904 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1905 | * control how a matched request is handled. 1906 | */ 1907 | 1908 | /** 1909 | * @ngdoc method 1910 | * @name $httpBackend#whenPATCH 1911 | * @module ngMockE2E 1912 | * @description 1913 | * Creates a new backend definition for PATCH requests. For more info see `when()`. 1914 | * 1915 | * @param {string|RegExp} url HTTP url. 1916 | * @param {(string|RegExp)=} data HTTP request body. 1917 | * @param {(Object|function(Object))=} headers HTTP headers. 1918 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1919 | * control how a matched request is handled. 1920 | */ 1921 | 1922 | /** 1923 | * @ngdoc method 1924 | * @name $httpBackend#whenJSONP 1925 | * @module ngMockE2E 1926 | * @description 1927 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 1928 | * 1929 | * @param {string|RegExp} url HTTP url. 1930 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1931 | * control how a matched request is handled. 1932 | */ 1933 | angular.mock.e2e = {}; 1934 | angular.mock.e2e.$httpBackendDecorator = 1935 | ['$rootScope', '$delegate', '$browser', createHttpBackendMock]; 1936 | 1937 | 1938 | angular.mock.clearDataCache = function() { 1939 | var key, 1940 | cache = angular.element.cache; 1941 | 1942 | for(key in cache) { 1943 | if (Object.prototype.hasOwnProperty.call(cache,key)) { 1944 | var handle = cache[key].handle; 1945 | 1946 | handle && angular.element(handle.elem).off(); 1947 | delete cache[key]; 1948 | } 1949 | } 1950 | }; 1951 | 1952 | 1953 | if(window.jasmine || window.mocha) { 1954 | 1955 | var currentSpec = null, 1956 | isSpecRunning = function() { 1957 | return !!currentSpec; 1958 | }; 1959 | 1960 | 1961 | (window.beforeEach || window.setup)(function() { 1962 | currentSpec = this; 1963 | }); 1964 | 1965 | (window.afterEach || window.teardown)(function() { 1966 | var injector = currentSpec.$injector; 1967 | 1968 | angular.forEach(currentSpec.$modules, function(module) { 1969 | if (module && module.$$hashKey) { 1970 | module.$$hashKey = undefined; 1971 | } 1972 | }); 1973 | 1974 | currentSpec.$injector = null; 1975 | currentSpec.$modules = null; 1976 | currentSpec = null; 1977 | 1978 | if (injector) { 1979 | injector.get('$rootElement').off(); 1980 | injector.get('$browser').pollFns.length = 0; 1981 | } 1982 | 1983 | angular.mock.clearDataCache(); 1984 | 1985 | // clean up jquery's fragment cache 1986 | angular.forEach(angular.element.fragments, function(val, key) { 1987 | delete angular.element.fragments[key]; 1988 | }); 1989 | 1990 | MockXhr.$$lastInstance = null; 1991 | 1992 | angular.forEach(angular.callbacks, function(val, key) { 1993 | delete angular.callbacks[key]; 1994 | }); 1995 | angular.callbacks.counter = 0; 1996 | }); 1997 | 1998 | /** 1999 | * @ngdoc function 2000 | * @name angular.mock.module 2001 | * @description 2002 | * 2003 | * *NOTE*: This function is also published on window for easy access.
2004 | * 2005 | * This function registers a module configuration code. It collects the configuration information 2006 | * which will be used when the injector is created by {@link angular.mock.inject inject}. 2007 | * 2008 | * See {@link angular.mock.inject inject} for usage example 2009 | * 2010 | * @param {...(string|Function|Object)} fns any number of modules which are represented as string 2011 | * aliases or as anonymous module initialization functions. The modules are used to 2012 | * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. If an 2013 | * object literal is passed they will be registered as values in the module, the key being 2014 | * the module name and the value being what is returned. 2015 | */ 2016 | window.module = angular.mock.module = function() { 2017 | var moduleFns = Array.prototype.slice.call(arguments, 0); 2018 | return isSpecRunning() ? workFn() : workFn; 2019 | ///////////////////// 2020 | function workFn() { 2021 | if (currentSpec.$injector) { 2022 | throw new Error('Injector already created, can not register a module!'); 2023 | } else { 2024 | var modules = currentSpec.$modules || (currentSpec.$modules = []); 2025 | angular.forEach(moduleFns, function(module) { 2026 | if (angular.isObject(module) && !angular.isArray(module)) { 2027 | modules.push(function($provide) { 2028 | angular.forEach(module, function(value, key) { 2029 | $provide.value(key, value); 2030 | }); 2031 | }); 2032 | } else { 2033 | modules.push(module); 2034 | } 2035 | }); 2036 | } 2037 | } 2038 | }; 2039 | 2040 | /** 2041 | * @ngdoc function 2042 | * @name angular.mock.inject 2043 | * @description 2044 | * 2045 | * *NOTE*: This function is also published on window for easy access.
2046 | * 2047 | * The inject function wraps a function into an injectable function. The inject() creates new 2048 | * instance of {@link auto.$injector $injector} per test, which is then used for 2049 | * resolving references. 2050 | * 2051 | * 2052 | * ## Resolving References (Underscore Wrapping) 2053 | * Often, we would like to inject a reference once, in a `beforeEach()` block and reuse this 2054 | * in multiple `it()` clauses. To be able to do this we must assign the reference to a variable 2055 | * that is declared in the scope of the `describe()` block. Since we would, most likely, want 2056 | * the variable to have the same name of the reference we have a problem, since the parameter 2057 | * to the `inject()` function would hide the outer variable. 2058 | * 2059 | * To help with this, the injected parameters can, optionally, be enclosed with underscores. 2060 | * These are ignored by the injector when the reference name is resolved. 2061 | * 2062 | * For example, the parameter `_myService_` would be resolved as the reference `myService`. 2063 | * Since it is available in the function body as _myService_, we can then assign it to a variable 2064 | * defined in an outer scope. 2065 | * 2066 | * ``` 2067 | * // Defined out reference variable outside 2068 | * var myService; 2069 | * 2070 | * // Wrap the parameter in underscores 2071 | * beforeEach( inject( function(_myService_){ 2072 | * myService = _myService_; 2073 | * })); 2074 | * 2075 | * // Use myService in a series of tests. 2076 | * it('makes use of myService', function() { 2077 | * myService.doStuff(); 2078 | * }); 2079 | * 2080 | * ``` 2081 | * 2082 | * See also {@link angular.mock.module angular.mock.module} 2083 | * 2084 | * ## Example 2085 | * Example of what a typical jasmine tests looks like with the inject method. 2086 | * ```js 2087 | * 2088 | * angular.module('myApplicationModule', []) 2089 | * .value('mode', 'app') 2090 | * .value('version', 'v1.0.1'); 2091 | * 2092 | * 2093 | * describe('MyApp', function() { 2094 | * 2095 | * // You need to load modules that you want to test, 2096 | * // it loads only the "ng" module by default. 2097 | * beforeEach(module('myApplicationModule')); 2098 | * 2099 | * 2100 | * // inject() is used to inject arguments of all given functions 2101 | * it('should provide a version', inject(function(mode, version) { 2102 | * expect(version).toEqual('v1.0.1'); 2103 | * expect(mode).toEqual('app'); 2104 | * })); 2105 | * 2106 | * 2107 | * // The inject and module method can also be used inside of the it or beforeEach 2108 | * it('should override a version and test the new version is injected', function() { 2109 | * // module() takes functions or strings (module aliases) 2110 | * module(function($provide) { 2111 | * $provide.value('version', 'overridden'); // override version here 2112 | * }); 2113 | * 2114 | * inject(function(version) { 2115 | * expect(version).toEqual('overridden'); 2116 | * }); 2117 | * }); 2118 | * }); 2119 | * 2120 | * ``` 2121 | * 2122 | * @param {...Function} fns any number of functions which will be injected using the injector. 2123 | */ 2124 | 2125 | 2126 | 2127 | var ErrorAddingDeclarationLocationStack = function(e, errorForStack) { 2128 | this.message = e.message; 2129 | this.name = e.name; 2130 | if (e.line) this.line = e.line; 2131 | if (e.sourceId) this.sourceId = e.sourceId; 2132 | if (e.stack && errorForStack) 2133 | this.stack = e.stack + '\n' + errorForStack.stack; 2134 | if (e.stackArray) this.stackArray = e.stackArray; 2135 | }; 2136 | ErrorAddingDeclarationLocationStack.prototype.toString = Error.prototype.toString; 2137 | 2138 | window.inject = angular.mock.inject = function() { 2139 | var blockFns = Array.prototype.slice.call(arguments, 0); 2140 | var errorForStack = new Error('Declaration Location'); 2141 | return isSpecRunning() ? workFn.call(currentSpec) : workFn; 2142 | ///////////////////// 2143 | function workFn() { 2144 | var modules = currentSpec.$modules || []; 2145 | 2146 | modules.unshift('ngMock'); 2147 | modules.unshift('ng'); 2148 | var injector = currentSpec.$injector; 2149 | if (!injector) { 2150 | injector = currentSpec.$injector = angular.injector(modules); 2151 | } 2152 | for(var i = 0, ii = blockFns.length; i < ii; i++) { 2153 | try { 2154 | /* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */ 2155 | injector.invoke(blockFns[i] || angular.noop, this); 2156 | /* jshint +W040 */ 2157 | } catch (e) { 2158 | if (e.stack && errorForStack) { 2159 | throw new ErrorAddingDeclarationLocationStack(e, errorForStack); 2160 | } 2161 | throw e; 2162 | } finally { 2163 | errorForStack = null; 2164 | } 2165 | } 2166 | } 2167 | }; 2168 | } 2169 | 2170 | 2171 | })(window, window.angular); 2172 | --------------------------------------------------------------------------------