├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── examples ├── server_bind.html └── simple.html ├── gulpfile.js ├── karma.conf.js ├── package.json ├── src └── angular-server-repeat.js └── test ├── angular-server-repeat.spec.js └── lib ├── angular-1.2.21.js ├── angular-1.3.6.js ├── angular-mocks-1.2.21.js └── angular-mocks-1.3.6.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | 5 | script: 6 | - npm test 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Restorando 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-server-repeat [![Build Status](https://travis-ci.org/restorando/angular-server-repeat.svg?branch=master)](https://travis-ci.org/restorando/angular-server-repeat) 2 | 3 | Convert server-side repeated content into `ngRepeat` compatible [with some restrictions](#caveats-and-restrictions). 4 | 5 | ## Installation 6 | 7 | Include the javascript file and add the `ServerRepeat` module as a dependency of your app. 8 | 9 | ```js 10 | angular.module('YourApp', ['ServerRepeat']); 11 | ``` 12 | 13 | ## Usage 14 | 15 | Use the `serverRepeat` directive in your server-side content using `ngRepeat`'s short syntax. 16 | 17 | ```html 18 |
19 |
20 |

My awesome first post

21 | John Williams 22 |
My awesome first post summary
23 |
24 |
25 |

My awesome second post

26 | Peter Morello 27 |
My awesome second post summary
28 |
29 |
30 |

My awesome last post

31 | Mark Lopez 32 |
My awesome last post summary
33 |
34 |
35 | ``` 36 | 37 | This will generate a `posts` array in `PostsController` scope, and every post will have a child scope with a reference to the current post in the `post` property, as if rendered client side using `ngRepeat`. 38 | 39 | ![image](https://cloud.githubusercontent.com/assets/591992/5893438/dd360918-a4c2-11e4-88a9-80caeb6f5f2a.png) 40 | 41 | Having a child scope for each member of the collection allows you to "angularize" each item independently. As an example let's hide each post summary and add a link in each post to show it. 42 | 43 | ```html 44 |
45 |
46 |

My awesome first post

47 | John Williams 48 |
My awesome first post summary
49 | show summary 50 |
51 |
52 |

My awesome second post

53 | Peter Morello 54 |
My awesome second post summary
55 | show summary 56 |
57 |
58 |

My awesome last post

59 | Mark Lopez 60 |
My awesome last post summary
61 | show summary 62 |
63 |
64 | ``` 65 | 66 | Without a child scope, the snippet above would hide all post summaries as expected, but clicking on any link would display all the summaries instead of the selected one. 67 | 68 | ### Data Binding 69 | 70 | In the previous example, each child scope has a `post` empty object. If you need to use a post's data you can use the `server-bind` directive. 71 | 72 | ```html 73 |
74 |
75 |

My awesome first post

76 | John Williams 77 |
My awesome first post summary
78 |
79 |
80 |

My awesome second post

81 | Peter Morello 82 |
My awesome second post summary
83 |
84 |
85 |

My awesome last post

86 | Mark Lopez 87 |
My awesome last post summary
88 |
89 |
90 | ``` 91 | 92 | Now we have the html rendered content parsed and populated into the `post` object in each child scope. 93 | 94 | ![image](https://cloud.githubusercontent.com/assets/591992/5893513/23a0aedc-a4c6-11e4-9013-5191d4d09feb.png) 95 | 96 | ##### Note: 97 | `server-bind` works as `ng-bind`, so changing the value of a binded property like the example below will reflect the property update in the DOM. 98 | 99 | ```javascript 100 | $scope.posts[0].title = "My new title"; 101 | ``` 102 | 103 | #### Adding aditional data that is not rendered in the DOM 104 | 105 | The `server-bind` directive can be used in the same element that uses the `server-repeat` directive. In this case, it will expect a JSON representation with properties that will be extended to the `post` object. 106 | 107 | Example: 108 | 109 | ```html 110 |
111 |
112 |

My awesome first post

113 | John Williams 114 |
My awesome first post summary
115 |
116 |
117 |

My awesome second post

118 | Peter Morello 119 |
My awesome second post summary
120 |
121 |
122 |

My awesome last post

123 | Mark Lopez 124 |
My awesome last post summary
125 |
126 |
127 | 128 | ``` 129 | 130 | Will produce: 131 | 132 | ![image](https://cloud.githubusercontent.com/assets/591992/5893560/e78cbf42-a4c7-11e4-88a1-1bb2afb6422c.png) 133 | 134 | #### Iteration properties 135 | 136 | Each child scope has `$first`, `$last`, `$middle`, `$even` and `$odd` variables as in `ngRepeat`. 137 | 138 | ## Motivation 139 | 140 | In Restorando we have full client side apps that use AngularJS intensively, and server rendered apps with custom javascript for some pages. Since we had a great experience with AngularJS in the client side apps, we started to slowly remove the legacy javascript files in our "server-side apps" and replace them with reusable angular directives. 141 | 142 | During our work we discovered that we wanted to add functionality to our "repeated" html snippets, but we didn't want to immerse ourselves in a big refactor to render this data client-side using `ngRepeat`. Doing this would also prevent the search engines to index our content. 143 | 144 | Searching on the web, we found lots of people trying to accomplish the same thing, such as [this question](http://stackoverflow.com/questions/11838639/html-template-filled-in-server-side-and-updated-client-side), [this one](http://stackoverflow.com/questions/25463409/angularjs-server-side-rendering-of-ngrepeat-directive) and [this other one](http://stackoverflow.com/questions/20764100/build-html-in-server-and-bind-to-ng-repeat), none of them being successful. 145 | 146 | Using this directive in our own applications made us more agile, and it allowed us to replace our old javascript code into AngularJS faster and easily. 147 | 148 | ## Caveats and restrictions 149 | 150 | * Changes that modify the collection's length (adding or removing items) won't be reflected in the DOM. 151 | * For the moment you can only bind "flat level" properties using `server-bind`. Deep level properties will be considered for a future version. 152 | 153 | ## License 154 | 155 | Copyright (c) 2015 Restorando 156 | 157 | MIT License 158 | 159 | Permission is hereby granted, free of charge, to any person obtaining 160 | a copy of this software and associated documentation files (the 161 | "Software"), to deal in the Software without restriction, including 162 | without limitation the rights to use, copy, modify, merge, publish, 163 | distribute, sublicense, and/or sell copies of the Software, and to 164 | permit persons to whom the Software is furnished to do so, subject to 165 | the following conditions: 166 | 167 | The above copyright notice and this permission notice shall be 168 | included in all copies or substantial portions of the Software. 169 | 170 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 171 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 172 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 173 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 174 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 175 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 176 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 177 | 178 | -------------------------------------------------------------------------------- /examples/server_bind.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Posts count: {{posts.length}}

6 | 7 |
8 | Latest article: {{posts[0].title}} from {{posts[0].author}}. 9 | 10 |

11 | Tags: 12 |

13 | 18 |
19 | 20 |

Posts

21 | 22 |
23 |

My awesome first post

24 | John Williams 25 |
My awesome first post summary
26 |
27 |
28 |

My awesome second post

29 | Peter Morello 30 |
My awesome second post summary
31 |
32 |
33 |

My awesome last post

34 | Mark Lopez 35 |
My awesome last post summary
36 |
37 | 38 | 39 | 40 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Posts count: {{posts.length}}

6 |
7 |

My awesome first post

8 | John Williams 9 |
My awesome first post summary
10 |
11 |
12 |

My awesome second post

13 | Peter Morello 14 |
My awesome second post summary
15 |
16 |
17 |

My awesome last post

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