├── .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 [](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 | 
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 | 
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 | 
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 |
14 | -
15 | {{tag}}
16 |
17 |
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 | '' +
29 | '- ' +
30 | '
Title 1
' +
31 | 'Description 1
' +
32 | ' ' +
33 | '- ' +
34 | '
Title 2
' +
35 | 'Description 2
' +
36 | ' ' +
37 | '- ' +
38 | '
Title 3
' +
39 | 'Description 3
' +
40 | ' ' +
41 | '
';
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 | '' +
62 | '- ' +
63 | '
Title 1
' +
64 | 'Description 1
' +
65 | ' ' +
66 | '- ' +
67 | '
Title 2
' +
68 | 'Description 2
' +
69 | ' ' +
70 | '- ' +
71 | '
Title 3
' +
72 | 'Description 3
' +
73 | ' ' +
74 | '
';
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 | '' +
128 | '- ' +
129 | '
' +
130 | '- ' +
131 | '
' +
132 | '- ' +
133 | '
' +
134 | '
';
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 | * | Request expectations | Backend definitions |
925 | *
926 | * Syntax |
927 | * .expect(...).respond(...) |
928 | * .when(...).respond(...) |
929 | *
930 | *
931 | * Typical usage |
932 | * strict unit tests |
933 | * loose (black-box) unit testing |
934 | *
935 | *
936 | * Fulfills multiple requests |
937 | * NO |
938 | * YES |
939 | *
940 | *
941 | * Order of requests matters |
942 | * YES |
943 | * NO |
944 | *
945 | *
946 | * Request required |
947 | * YES |
948 | * NO |
949 | *
950 | *
951 | * Response required |
952 | * optional (see below) |
953 | * YES |
954 | *
955 | *
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 | * | Request expectations | Backend definitions |
950 | *
951 | * Syntax |
952 | * .expect(...).respond(...) |
953 | * .when(...).respond(...) |
954 | *
955 | *
956 | * Typical usage |
957 | * strict unit tests |
958 | * loose (black-box) unit testing |
959 | *
960 | *
961 | * Fulfills multiple requests |
962 | * NO |
963 | * YES |
964 | *
965 | *
966 | * Order of requests matters |
967 | * YES |
968 | * NO |
969 | *
970 | *
971 | * Request required |
972 | * YES |
973 | * NO |
974 | *
975 | *
976 | * Response required |
977 | * optional (see below) |
978 | * YES |
979 | *
980 | *
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 |
--------------------------------------------------------------------------------