summaryrefslogtreecommitdiff
path: root/xstatic/pkg/angular/data/angular-mocks.js
diff options
context:
space:
mode:
Diffstat (limited to 'xstatic/pkg/angular/data/angular-mocks.js')
-rw-r--r--xstatic/pkg/angular/data/angular-mocks.js1130
1 files changed, 728 insertions, 402 deletions
diff --git a/xstatic/pkg/angular/data/angular-mocks.js b/xstatic/pkg/angular/data/angular-mocks.js
index 42f19b7..c4c4868 100644
--- a/xstatic/pkg/angular/data/angular-mocks.js
+++ b/xstatic/pkg/angular/data/angular-mocks.js
@@ -1,12 +1,14 @@
/**
- * @license AngularJS v1.5.8
- * (c) 2010-2016 Google, Inc. http://angularjs.org
+ * @license AngularJS v1.8.2
+ * (c) 2010-2020 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular) {
'use strict';
+/* global routeToRegExp: false */
+
/**
* @ngdoc object
* @name angular.mock
@@ -31,23 +33,27 @@ angular.mock = {};
* that there are several helper methods available which can be used in tests.
*/
angular.mock.$BrowserProvider = function() {
- this.$get = function() {
- return new angular.mock.$Browser();
- };
+ this.$get = [
+ '$log', '$$taskTrackerFactory',
+ function($log, $$taskTrackerFactory) {
+ return new angular.mock.$Browser($log, $$taskTrackerFactory);
+ }
+ ];
};
-angular.mock.$Browser = function() {
+angular.mock.$Browser = function($log, $$taskTrackerFactory) {
var self = this;
+ var taskTracker = $$taskTrackerFactory($log);
this.isMock = true;
- self.$$url = "http://server/";
+ self.$$url = 'http://server/';
self.$$lastUrl = self.$$url; // used by url polling fn
self.pollFns = [];
- // TODO(vojta): remove this temporary api
- self.$$completeOutstandingRequest = angular.noop;
- self.$$incOutstandingRequestCount = angular.noop;
-
+ // Task-tracking API
+ self.$$completeOutstandingRequest = taskTracker.completeTask;
+ self.$$incOutstandingRequestCount = taskTracker.incTaskCount;
+ self.notifyWhenNoOutstandingRequests = taskTracker.notifyWhenNoPendingTasks;
// register url polling fn
@@ -71,11 +77,22 @@ angular.mock.$Browser = function() {
self.deferredFns = [];
self.deferredNextId = 0;
- self.defer = function(fn, delay) {
+ self.defer = function(fn, delay, taskType) {
+ var timeoutId = self.deferredNextId++;
+
delay = delay || 0;
- self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId});
- self.deferredFns.sort(function(a, b) { return a.time - b.time;});
- return self.deferredNextId++;
+ taskType = taskType || taskTracker.DEFAULT_TASK_TYPE;
+
+ taskTracker.incTaskCount(taskType);
+ self.deferredFns.push({
+ id: timeoutId,
+ type: taskType,
+ time: (self.defer.now + delay),
+ fn: fn
+ });
+ self.deferredFns.sort(function(a, b) { return a.time - b.time; });
+
+ return timeoutId;
};
@@ -89,14 +106,15 @@ angular.mock.$Browser = function() {
self.defer.cancel = function(deferId) {
- var fnIndex;
+ var taskIndex;
- angular.forEach(self.deferredFns, function(fn, index) {
- if (fn.id === deferId) fnIndex = index;
+ angular.forEach(self.deferredFns, function(task, index) {
+ if (task.id === deferId) taskIndex = index;
});
- if (angular.isDefined(fnIndex)) {
- self.deferredFns.splice(fnIndex, 1);
+ if (angular.isDefined(taskIndex)) {
+ var task = self.deferredFns.splice(taskIndex, 1)[0];
+ taskTracker.completeTask(angular.noop, task.type);
return true;
}
@@ -110,6 +128,8 @@ angular.mock.$Browser = function() {
* @description
* Flushes all pending requests and executes the defer callbacks.
*
+ * See {@link ngMock.$flushPendingsTasks} for more info.
+ *
* @param {number=} number of milliseconds to flush. See {@link #defer.now}
*/
self.defer.flush = function(delay) {
@@ -118,26 +138,76 @@ angular.mock.$Browser = function() {
if (angular.isDefined(delay)) {
// A delay was passed so compute the next time
nextTime = self.defer.now + delay;
+ } else if (self.deferredFns.length) {
+ // No delay was passed so set the next time so that it clears the deferred queue
+ nextTime = self.deferredFns[self.deferredFns.length - 1].time;
} else {
- if (self.deferredFns.length) {
- // No delay was passed so set the next time so that it clears the deferred queue
- nextTime = self.deferredFns[self.deferredFns.length - 1].time;
- } else {
- // No delay passed, but there are no deferred tasks so flush - indicates an error!
- throw new Error('No deferred tasks to be flushed');
- }
+ // No delay passed, but there are no deferred tasks so flush - indicates an error!
+ throw new Error('No deferred tasks to be flushed');
}
while (self.deferredFns.length && self.deferredFns[0].time <= nextTime) {
// Increment the time and call the next deferred function
self.defer.now = self.deferredFns[0].time;
- self.deferredFns.shift().fn();
+ var task = self.deferredFns.shift();
+ taskTracker.completeTask(task.fn, task.type);
}
// Ensure that the current time is correct
self.defer.now = nextTime;
};
+ /**
+ * @name $browser#defer.getPendingTasks
+ *
+ * @description
+ * Returns the currently pending tasks that need to be flushed.
+ * You can request a specific type of tasks only, by specifying a `taskType`.
+ *
+ * @param {string=} taskType - The type tasks to return.
+ */
+ self.defer.getPendingTasks = function(taskType) {
+ return !taskType
+ ? self.deferredFns
+ : self.deferredFns.filter(function(task) { return task.type === taskType; });
+ };
+
+ /**
+ * @name $browser#defer.formatPendingTasks
+ *
+ * @description
+ * Formats each task in a list of pending tasks as a string, suitable for use in error messages.
+ *
+ * @param {Array<Object>} pendingTasks - A list of task objects.
+ * @return {Array<string>} A list of stringified tasks.
+ */
+ self.defer.formatPendingTasks = function(pendingTasks) {
+ return pendingTasks.map(function(task) {
+ return '{id: ' + task.id + ', type: ' + task.type + ', time: ' + task.time + '}';
+ });
+ };
+
+ /**
+ * @name $browser#defer.verifyNoPendingTasks
+ *
+ * @description
+ * Verifies that there are no pending tasks that need to be flushed.
+ * You can check for a specific type of tasks only, by specifying a `taskType`.
+ *
+ * See {@link $verifyNoPendingTasks} for more info.
+ *
+ * @param {string=} taskType - The type tasks to check for.
+ */
+ self.defer.verifyNoPendingTasks = function(taskType) {
+ var pendingTasks = self.defer.getPendingTasks(taskType);
+
+ if (pendingTasks.length) {
+ var formattedTasks = self.defer.formatPendingTasks(pendingTasks).join('\n ');
+ throw new Error('Deferred tasks to flush (' + pendingTasks.length + '):\n ' +
+ formattedTasks);
+ }
+ };
+
self.$$baseHref = '/';
self.baseHref = function() {
return this.$$baseHref;
@@ -162,7 +232,8 @@ angular.mock.$Browser.prototype = {
state = null;
}
if (url) {
- this.$$url = url;
+ // The `$browser` service trims empty hashes; simulate it.
+ this.$$url = url.replace(/#$/, '');
// Native pushState serializes & copies the object; simulate it.
this.$$state = angular.copy(state);
return this;
@@ -173,13 +244,85 @@ angular.mock.$Browser.prototype = {
state: function() {
return this.$$state;
- },
-
- notifyWhenNoOutstandingRequests: function(fn) {
- fn();
}
};
+/**
+ * @ngdoc service
+ * @name $flushPendingTasks
+ *
+ * @description
+ * Flushes all currently pending tasks and executes the corresponding callbacks.
+ *
+ * Optionally, you can also pass a `delay` argument to only flush tasks that are scheduled to be
+ * executed within `delay` milliseconds. Currently, `delay` only applies to timeouts, since all
+ * other tasks have a delay of 0 (i.e. they are scheduled to be executed as soon as possible, but
+ * still asynchronously).
+ *
+ * If no delay is specified, it uses a delay such that all currently pending tasks are flushed.
+ *
+ * The types of tasks that are flushed include:
+ *
+ * - Pending timeouts (via {@link $timeout}).
+ * - Pending tasks scheduled via {@link ng.$rootScope.Scope#$applyAsync $applyAsync}.
+ * - Pending tasks scheduled via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}.
+ * These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises).
+ *
+ * <div class="alert alert-info">
+ * Periodic tasks scheduled via {@link $interval} use a different queue and are not flushed by
+ * `$flushPendingTasks()`. Use {@link ngMock.$interval#flush $interval.flush(millis)} instead.
+ * </div>
+ *
+ * @param {number=} delay - The number of milliseconds to flush.
+ */
+angular.mock.$FlushPendingTasksProvider = function() {
+ this.$get = [
+ '$browser',
+ function($browser) {
+ return function $flushPendingTasks(delay) {
+ return $browser.defer.flush(delay);
+ };
+ }
+ ];
+};
+
+/**
+ * @ngdoc service
+ * @name $verifyNoPendingTasks
+ *
+ * @description
+ * Verifies that there are no pending tasks that need to be flushed. It throws an error if there are
+ * still pending tasks.
+ *
+ * You can check for a specific type of tasks only, by specifying a `taskType`.
+ *
+ * Available task types:
+ *
+ * - `$timeout`: Pending timeouts (via {@link $timeout}).
+ * - `$http`: Pending HTTP requests (via {@link $http}).
+ * - `$route`: In-progress route transitions (via {@link $route}).
+ * - `$applyAsync`: Pending tasks scheduled via {@link ng.$rootScope.Scope#$applyAsync $applyAsync}.
+ * - `$evalAsync`: Pending tasks scheduled via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}.
+ * These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises).
+ *
+ * <div class="alert alert-info">
+ * Periodic tasks scheduled via {@link $interval} use a different queue and are not taken into
+ * account by `$verifyNoPendingTasks()`. There is currently no way to verify that there are no
+ * pending {@link $interval} tasks.
+ * </div>
+ *
+ * @param {string=} taskType - The type of tasks to check for.
+ */
+angular.mock.$VerifyNoPendingTasksProvider = function() {
+ this.$get = [
+ '$browser',
+ function($browser) {
+ return function $verifyNoPendingTasks(taskType) {
+ return $browser.defer.verifyNoPendingTasks(taskType);
+ };
+ }
+ ];
+};
/**
* @ngdoc provider
@@ -252,19 +395,19 @@ angular.mock.$ExceptionHandlerProvider = function() {
case 'rethrow':
var errors = [];
handler = function(e) {
- if (arguments.length == 1) {
+ if (arguments.length === 1) {
errors.push(e);
} else {
errors.push([].slice.call(arguments, 0));
}
- if (mode === "rethrow") {
+ if (mode === 'rethrow') {
throw e;
}
};
handler.errors = errors;
break;
default:
- throw new Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!");
+ throw new Error('Unknown mode \'' + mode + '\', only \'log\'/\'rethrow\' modes are allowed!');
}
};
@@ -414,8 +557,8 @@ angular.mock.$LogProvider = function() {
});
});
if (errors.length) {
- errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or " +
- "an expected log message was not checked and removed:");
+ errors.unshift('Expected $log to be empty! Either a message was logged unexpectedly, or ' +
+ 'an expected log message was not checked and removed:');
errors.push('');
throw new Error(errors.join('\n---------\n'));
}
@@ -448,62 +591,40 @@ angular.mock.$LogProvider = function() {
* @returns {promise} A promise which will be notified on each iteration.
*/
angular.mock.$IntervalProvider = function() {
- this.$get = ['$browser', '$rootScope', '$q', '$$q',
- function($browser, $rootScope, $q, $$q) {
+ this.$get = ['$browser', '$$intervalFactory',
+ function($browser, $$intervalFactory) {
var repeatFns = [],
nextRepeatId = 0,
- now = 0;
-
- var $interval = function(fn, delay, count, invokeApply) {
- var hasParams = arguments.length > 4,
- args = hasParams ? Array.prototype.slice.call(arguments, 4) : [],
- iteration = 0,
- skipApply = (angular.isDefined(invokeApply) && !invokeApply),
- deferred = (skipApply ? $$q : $q).defer(),
- promise = deferred.promise;
-
- count = (angular.isDefined(count)) ? count : 0;
- promise.then(null, null, (!hasParams) ? fn : function() {
- fn.apply(null, args);
- });
-
- promise.$$intervalId = nextRepeatId;
-
- function tick() {
- deferred.notify(iteration++);
-
- if (count > 0 && iteration >= count) {
- var fnIndex;
- deferred.resolve(iteration);
-
- angular.forEach(repeatFns, function(fn, index) {
- if (fn.id === promise.$$intervalId) fnIndex = index;
+ now = 0,
+ setIntervalFn = function(tick, delay, deferred, skipApply) {
+ var id = nextRepeatId++;
+ var fn = !skipApply ? tick : function() {
+ tick();
+ $browser.defer.flush();
+ };
+
+ repeatFns.push({
+ nextTime: (now + (delay || 0)),
+ delay: delay || 1,
+ fn: fn,
+ id: id,
+ deferred: deferred
});
+ repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime; });
- if (angular.isDefined(fnIndex)) {
- repeatFns.splice(fnIndex, 1);
+ return id;
+ },
+ clearIntervalFn = function(id) {
+ for (var fnIndex = repeatFns.length - 1; fnIndex >= 0; fnIndex--) {
+ if (repeatFns[fnIndex].id === id) {
+ repeatFns.splice(fnIndex, 1);
+ break;
+ }
}
- }
-
- if (skipApply) {
- $browser.defer.flush();
- } else {
- $rootScope.$apply();
- }
- }
+ };
- repeatFns.push({
- nextTime:(now + delay),
- delay: delay,
- fn: tick,
- id: nextRepeatId,
- deferred: deferred
- });
- repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;});
+ var $interval = $$intervalFactory(setIntervalFn, clearIntervalFn);
- nextRepeatId++;
- return promise;
- };
/**
* @ngdoc method
* @name $interval#cancel
@@ -516,16 +637,15 @@ angular.mock.$IntervalProvider = function() {
*/
$interval.cancel = function(promise) {
if (!promise) return false;
- var fnIndex;
-
- angular.forEach(repeatFns, function(fn, index) {
- if (fn.id === promise.$$intervalId) fnIndex = index;
- });
- if (angular.isDefined(fnIndex)) {
- repeatFns[fnIndex].deferred.reject('canceled');
- repeatFns.splice(fnIndex, 1);
- return true;
+ for (var fnIndex = repeatFns.length - 1; fnIndex >= 0; fnIndex--) {
+ if (repeatFns[fnIndex].id === promise.$$intervalId) {
+ var deferred = repeatFns[fnIndex].deferred;
+ deferred.promise.then(undefined, function() {});
+ deferred.reject('canceled');
+ repeatFns.splice(fnIndex, 1);
+ return true;
+ }
}
return false;
@@ -538,15 +658,21 @@ angular.mock.$IntervalProvider = function() {
*
* Runs interval tasks scheduled to be run in the next `millis` milliseconds.
*
- * @param {number=} millis maximum timeout amount to flush up until.
+ * @param {number} millis maximum timeout amount to flush up until.
*
* @return {number} The amount of time moved forward.
*/
$interval.flush = function(millis) {
+ var before = now;
now += millis;
while (repeatFns.length && repeatFns[0].nextTime <= now) {
var task = repeatFns[0];
task.fn();
+ if (task.nextTime === before) {
+ // this can only happen the first time
+ // a zero-delay interval gets triggered
+ task.nextTime++;
+ }
task.nextTime += task.delay;
repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;});
}
@@ -558,16 +684,13 @@ angular.mock.$IntervalProvider = function() {
};
-/* jshint -W101 */
-/* The R_ISO8061_STR regex is never going to fit into the 100 char limit!
- * This directive should go inside the anonymous function but a bug in JSHint means that it would
- * not be enacted early enough to prevent the warning.
- */
-var R_ISO8061_STR = /^(-?\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
-
function jsonStringToDate(string) {
+ // The R_ISO8061_STR regex is never going to fit into the 100 char limit!
+ // eslit-disable-next-line max-len
+ var R_ISO8061_STR = /^(-?\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
+
var match;
- if (match = string.match(R_ISO8061_STR)) {
+ if ((match = string.match(R_ISO8061_STR))) {
var date = new Date(0),
tzHour = 0,
tzMin = 0;
@@ -650,9 +773,10 @@ angular.mock.TzDate = function(offset, timestamp) {
timestamp = self.origDate.getTime();
if (isNaN(timestamp)) {
+ // eslint-disable-next-line no-throw-literal
throw {
- name: "Illegal Argument",
- message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
+ name: 'Illegal Argument',
+ message: 'Arg \'' + tsStr + '\' passed into TzDate constructor is not a valid date string'
};
}
} else {
@@ -758,7 +882,7 @@ angular.mock.TzDate = function(offset, timestamp) {
angular.forEach(unimplementedMethods, function(methodName) {
self[methodName] = function() {
- throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock");
+ throw new Error('Method \'' + methodName + '\' is not implemented in the TzDate mock');
};
});
@@ -767,7 +891,6 @@ angular.mock.TzDate = function(offset, timestamp) {
//make "tzDateInstance instanceof Date" return true
angular.mock.TzDate.prototype = Date.prototype;
-/* jshint +W101 */
/**
@@ -781,6 +904,7 @@ angular.mock.TzDate.prototype = Date.prototype;
* You need to require the `ngAnimateMock` module in your test suite for instance `beforeEach(module('ngAnimateMock'))`
*/
angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
+ .info({ angularVersion: '"1.8.2"' })
.config(['$provide', function($provide) {
@@ -946,7 +1070,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
*
* *NOTE*: This is not an injectable instance, just a globally available function.
*
- * Method for serializing common angular objects (scope, elements, etc..) into strings.
+ * Method for serializing common AngularJS objects (scope, elements, etc..) into strings.
* It is useful for logging objects to the console when debugging.
*
* @param {*} object - any object to turn into string.
@@ -1028,7 +1152,7 @@ angular.mock.dump = function(object) {
* This mock implementation can be used to respond with static or dynamic responses via the
* `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc).
*
- * When an Angular application needs some data from a server, it calls the $http service, which
+ * When an AngularJS application needs some data from a server, it calls the $http service, which
* sends the request to a real server using $httpBackend service. With dependency injection, it is
* easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify
* the requests and respond with some testing data without sending a request to a real server.
@@ -1123,6 +1247,8 @@ angular.mock.dump = function(object) {
$http.get('/auth.py').then(function(response) {
authToken = response.headers('A-Token');
$scope.user = response.data;
+ }).catch(function() {
+ $scope.status = 'Failed...';
});
$scope.saveMessage = function(message) {
@@ -1215,7 +1341,7 @@ angular.mock.dump = function(object) {
$httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
// check if the header was sent, if it wasn't the expectation won't
// match the request and the test will fail
- return headers['Authorization'] == 'xxx';
+ return headers['Authorization'] === 'xxx';
}).respond(201, '');
$rootScope.saveMessage('whatever');
@@ -1264,7 +1390,7 @@ angular.mock.dump = function(object) {
* ## Matching route requests
*
* For extra convenience, `whenRoute` and `expectRoute` shortcuts are available. These methods offer colon
- * delimited matching of the url path, ignoring the query string. This allows declarations
+ * delimited matching of the url path, ignoring the query string and trailing slashes. This allows declarations
* similar to how application routes are configured with `$routeProvider`. Because these methods convert
* the definition url to regex, declaration order is important. Combined with query parameter parsing,
* the following is possible:
@@ -1304,9 +1430,8 @@ angular.mock.dump = function(object) {
});
```
*/
-angular.mock.$HttpBackendProvider = function() {
- this.$get = ['$rootScope', '$timeout', createHttpBackendMock];
-};
+angular.mock.$httpBackendDecorator =
+ ['$rootScope', '$timeout', '$delegate', createHttpBackendMock];
/**
* General factory function for $httpBackend mock.
@@ -1325,17 +1450,21 @@ angular.mock.$HttpBackendProvider = function() {
function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
var definitions = [],
expectations = [],
+ matchLatestDefinition = false,
responses = [],
responsesPush = angular.bind(responses, responses.push),
- copy = angular.copy;
+ copy = angular.copy,
+ // We cache the original backend so that if both ngMock and ngMockE2E override the
+ // service the ngMockE2E version can pass through to the real backend
+ originalHttpBackend = $delegate.$$originalHttpBackend || $delegate;
function createResponse(status, data, headers, statusText) {
if (angular.isFunction(status)) return status;
return function() {
return angular.isNumber(status)
- ? [status, data, headers, statusText]
- : [200, status, data, headers];
+ ? [status, data, headers, statusText, 'complete']
+ : [200, status, data, headers, 'complete'];
};
}
@@ -1357,39 +1486,57 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
function wrapResponse(wrapped) {
if (!$browser && timeout) {
- timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout);
+ if (timeout.then) {
+ timeout.then(function() {
+ handlePrematureEnd(angular.isDefined(timeout.$$timeoutId) ? 'timeout' : 'abort');
+ });
+ } else {
+ $timeout(function() {
+ handlePrematureEnd('timeout');
+ }, timeout);
+ }
}
+ handleResponse.description = method + ' ' + url;
return handleResponse;
function handleResponse() {
var response = wrapped.response(method, url, data, headers, wrapped.params(url));
xhr.$$respHeaders = response[2];
callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(),
- copy(response[3] || ''));
+ copy(response[3] || ''), copy(response[4]));
}
- function handleTimeout() {
+ function handlePrematureEnd(reason) {
for (var i = 0, ii = responses.length; i < ii; i++) {
if (responses[i] === handleResponse) {
responses.splice(i, 1);
- callback(-1, undefined, '');
+ callback(-1, undefined, '', undefined, reason);
break;
}
}
}
}
+ function createFatalError(message) {
+ var error = new Error(message);
+ // In addition to being converted to a rejection, these errors also need to be passed to
+ // the $exceptionHandler and be rethrown (so that the test fails).
+ error.$$passToExceptionHandler = true;
+ return error;
+ }
+
if (expectation && expectation.match(method, url)) {
if (!expectation.matchData(data)) {
- throw new Error('Expected ' + expectation + ' with different data\n' +
- 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
+ throw createFatalError('Expected ' + expectation + ' with different data\n' +
+ 'EXPECTED: ' + prettyPrint(expectation.data) + '\n' +
+ 'GOT: ' + data);
}
if (!expectation.matchHeaders(headers)) {
- throw new Error('Expected ' + expectation + ' with different headers\n' +
- 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' +
- prettyPrint(headers));
+ throw createFatalError('Expected ' + expectation + ' with different headers\n' +
+ 'EXPECTED: ' + prettyPrint(expectation.headers) + '\n' +
+ 'GOT: ' + prettyPrint(headers));
}
expectations.shift();
@@ -1401,22 +1548,26 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
wasExpected = true;
}
- var i = -1, definition;
- while ((definition = definitions[++i])) {
+ var i = matchLatestDefinition ? definitions.length : -1, definition;
+
+ while ((definition = definitions[matchLatestDefinition ? --i : ++i])) {
if (definition.match(method, url, data, headers || {})) {
if (definition.response) {
// if $browser specified, we do auto flush all requests
($browser ? $browser.defer : responsesPush)(wrapResponse(definition));
} else if (definition.passThrough) {
- $delegate(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers);
- } else throw new Error('No response defined !');
+ originalHttpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers);
+ } else throw createFatalError('No response defined !');
return;
}
}
- throw wasExpected ?
- new Error('No response defined !') :
- new Error('Unexpected request: ' + method + ' ' + url + '\n' +
- (expectation ? 'Expected ' + expectation : 'No more request expected'));
+
+ if (wasExpected) {
+ throw createFatalError('No response defined !');
+ }
+
+ throw createFatalError('Unexpected request: ' + method + ' ' + url + '\n' +
+ (expectation ? 'Expected ' + expectation : 'No more request expected'));
}
/**
@@ -1426,7 +1577,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* Creates a new backend definition.
*
* @param {string} method HTTP method.
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
@@ -1444,10 +1595,14 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* ```
* – The respond method takes a set of static data to be returned or a function that can
* return an array containing response status (number), response data (Array|Object|string),
- * response headers (Object), and the text for the status (string). The respond method returns
- * the `requestHandler` object for possible overrides.
+ * response headers (Object), HTTP status text (string), and XMLHttpRequest status (string:
+ * `complete`, `error`, `timeout` or `abort`). The respond method returns the `requestHandler`
+ * object for possible overrides.
*/
$httpBackend.when = function(method, url, data, headers, keys) {
+
+ assertArgDefined(arguments, 1, 'url');
+
var definition = new MockHttpExpectation(method, url, data, headers, keys),
chain = {
respond: function(status, data, headers, statusText) {
@@ -1471,13 +1626,55 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
/**
* @ngdoc method
+ * @name $httpBackend#matchLatestDefinitionEnabled
+ * @description
+ * This method can be used to change which mocked responses `$httpBackend` returns, when defining
+ * them with {@link ngMock.$httpBackend#when $httpBackend.when()} (and shortcut methods).
+ * By default, `$httpBackend` returns the first definition that matches. When setting
+ * `$http.matchLatestDefinitionEnabled(true)`, it will use the last response that matches, i.e. the
+ * one that was added last.
+ *
+ * ```js
+ * hb.when('GET', '/url1').respond(200, 'content', {});
+ * hb.when('GET', '/url1').respond(201, 'another', {});
+ * hb('GET', '/url1'); // receives "content"
+ *
+ * $http.matchLatestDefinitionEnabled(true)
+ * hb('GET', '/url1'); // receives "another"
+ *
+ * hb.when('GET', '/url1').respond(201, 'onemore', {});
+ * hb('GET', '/url1'); // receives "onemore"
+ * ```
+ *
+ * This is useful if a you have a default response that is overriden inside specific tests.
+ *
+ * Note that different from config methods on providers, `matchLatestDefinitionEnabled()` can be changed
+ * even when the application is already running.
+ *
+ * @param {Boolean=} value value to set, either `true` or `false`. Default is `false`.
+ * If omitted, it will return the current value.
+ * @return {$httpBackend|Boolean} self when used as a setter, and the current value when used
+ * as a getter
+ */
+ $httpBackend.matchLatestDefinitionEnabled = function(value) {
+ if (angular.isDefined(value)) {
+ matchLatestDefinition = value;
+ return this;
+ } else {
+ return matchLatestDefinition;
+ }
+ };
+
+ /**
+ * @ngdoc method
* @name $httpBackend#whenGET
* @description
* Creates a new backend definition for GET requests. For more info see `when()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
- * @param {(Object|function(Object))=} headers HTTP headers.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current definition.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1490,9 +1687,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for HEAD requests. For more info see `when()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
- * @param {(Object|function(Object))=} headers HTTP headers.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current definition.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1505,9 +1703,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for DELETE requests. For more info see `when()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
- * @param {(Object|function(Object))=} headers HTTP headers.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current definition.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1520,11 +1719,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for POST requests. For more info see `when()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
- * @param {(Object|function(Object))=} headers HTTP headers.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current definition.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1537,11 +1737,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for PUT requests. For more info see `when()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
- * @param {(Object|function(Object))=} headers HTTP headers.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current definition.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1554,7 +1755,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for JSONP requests. For more info see `when()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
@@ -1573,42 +1774,13 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @param {string} url HTTP url string that supports colon param matching.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
- * order to change how a matched request is handled. See #when for more info.
+ * order to change how a matched request is handled.
+ * See {@link ngMock.$httpBackend#when `when`} for more info.
*/
$httpBackend.whenRoute = function(method, url) {
- var pathObj = parseRoute(url);
- return $httpBackend.when(method, pathObj.regexp, undefined, undefined, pathObj.keys);
- };
-
- function parseRoute(url) {
- var ret = {
- regexp: url
- },
- keys = ret.keys = [];
-
- if (!url || !angular.isString(url)) return ret;
-
- url = url
- .replace(/([().])/g, '\\$1')
- .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) {
- var optional = option === '?' ? option : null;
- var star = option === '*' ? option : null;
- keys.push({ name: key, optional: !!optional });
- slash = slash || '';
- return ''
- + (optional ? '' : slash)
- + '(?:'
- + (optional ? slash : '')
- + (star && '(.+?)' || '([^/]+)')
- + (optional || '')
- + ')'
- + (optional || '');
- })
- .replace(/([\/$\*])/g, '\\$1');
-
- ret.regexp = new RegExp('^' + url, 'i');
- return ret;
- }
+ var parsed = parseRouteUrl(url);
+ return $httpBackend.when(method, parsed.regexp, undefined, undefined, parsed.keys);
+ };
/**
* @ngdoc method
@@ -1617,7 +1789,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* Creates a new request expectation.
*
* @param {string} method HTTP method.
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
@@ -1630,16 +1802,20 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* order to change how a matched request is handled.
*
* - respond –
- * ```
- * { function([status,] data[, headers, statusText])
- * | function(function(method, url, data, headers, params)}
- * ```
+ * ```js
+ * {function([status,] data[, headers, statusText])
+ * | function(function(method, url, data, headers, params)}
+ * ```
* – The respond method takes a set of static data to be returned or a function that can
* return an array containing response status (number), response data (Array|Object|string),
- * response headers (Object), and the text for the status (string). The respond method returns
- * the `requestHandler` object for possible overrides.
+ * response headers (Object), HTTP status text (string), and XMLHttpRequest status (string:
+ * `complete`, `error`, `timeout` or `abort`). The respond method returns the `requestHandler`
+ * object for possible overrides.
*/
$httpBackend.expect = function(method, url, data, headers, keys) {
+
+ assertArgDefined(arguments, 1, 'url');
+
var expectation = new MockHttpExpectation(method, url, data, headers, keys),
chain = {
respond: function(status, data, headers, statusText) {
@@ -1658,9 +1834,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for GET requests. For more info see `expect()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
- * and returns true if the url matches the current definition.
- * @param {Object=} headers HTTP headers.
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
+ * and returns true if the url matches the current expectation.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current expectation.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1673,9 +1850,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for HEAD requests. For more info see `expect()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
- * and returns true if the url matches the current definition.
- * @param {Object=} headers HTTP headers.
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
+ * and returns true if the url matches the current expectation.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current expectation.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1688,9 +1866,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for DELETE requests. For more info see `expect()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
- * and returns true if the url matches the current definition.
- * @param {Object=} headers HTTP headers.
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
+ * and returns true if the url matches the current expectation.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current expectation.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1703,12 +1882,13 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for POST requests. For more info see `expect()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
- * and returns true if the url matches the current definition.
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
+ * and returns true if the url matches the current expectation.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
- * @param {Object=} headers HTTP headers.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current expectation.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1721,12 +1901,13 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for PUT requests. For more info see `expect()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
- * and returns true if the url matches the current definition.
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
+ * and returns true if the url matches the current expectation.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
- * @param {Object=} headers HTTP headers.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current expectation.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1739,12 +1920,13 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for PATCH requests. For more info see `expect()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
- * and returns true if the url matches the current definition.
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
+ * and returns true if the url matches the current expectation.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
- * @param {Object=} headers HTTP headers.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current expectation.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1757,8 +1939,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for JSONP requests. For more info see `expect()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives an url
- * and returns true if the url matches the current definition.
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives an url
+ * and returns true if the url matches the current expectation.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1776,11 +1958,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @param {string} url HTTP url string that supports colon param matching.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
- * order to change how a matched request is handled. See #expect for more info.
+ * order to change how a matched request is handled.
+ * See {@link ngMock.$httpBackend#expect `expect`} for more info.
*/
$httpBackend.expectRoute = function(method, url) {
- var pathObj = parseRoute(url);
- return $httpBackend.expect(method, pathObj.regexp, undefined, undefined, pathObj.keys);
+ var parsed = parseRouteUrl(url);
+ return $httpBackend.expect(method, parsed.regexp, undefined, undefined, parsed.keys);
};
@@ -1788,24 +1971,34 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @ngdoc method
* @name $httpBackend#flush
* @description
- * Flushes all pending requests using the trained responses.
- *
- * @param {number=} count Number of responses to flush (in the order they arrived). If undefined,
- * all pending requests will be flushed. If there are no pending requests when the flush method
- * is called an exception is thrown (as this typically a sign of programming error).
+ * Flushes pending requests using the trained responses. Requests are flushed in the order they
+ * were made, but it is also possible to skip one or more requests (for example to have them
+ * flushed later). This is useful for simulating scenarios where responses arrive from the server
+ * in any order.
+ *
+ * If there are no pending requests to flush when the method is called, an exception is thrown (as
+ * this is typically a sign of programming error).
+ *
+ * @param {number=} count - Number of responses to flush. If undefined/null, all pending requests
+ * (starting after `skip`) will be flushed.
+ * @param {number=} [skip=0] - Number of pending requests to skip. For example, a value of `5`
+ * would skip the first 5 pending requests and start flushing from the 6th onwards.
*/
- $httpBackend.flush = function(count, digest) {
+ $httpBackend.flush = function(count, skip, digest) {
if (digest !== false) $rootScope.$digest();
- if (!responses.length) throw new Error('No pending request to flush !');
+
+ skip = skip || 0;
+ if (skip >= responses.length) throw new Error('No pending request to flush !');
if (angular.isDefined(count) && count !== null) {
while (count--) {
- if (!responses.length) throw new Error('No more pending request to flush !');
- responses.shift()();
+ var part = responses.splice(skip, 1);
+ if (!part.length) throw new Error('No more pending request to flush !');
+ part[0]();
}
} else {
- while (responses.length) {
- responses.shift()();
+ while (responses.length > skip) {
+ responses.splice(skip, 1)[0]();
}
}
$httpBackend.verifyNoOutstandingExpectation(digest);
@@ -1847,9 +2040,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* afterEach($httpBackend.verifyNoOutstandingRequest);
* ```
*/
- $httpBackend.verifyNoOutstandingRequest = function() {
+ $httpBackend.verifyNoOutstandingRequest = function(digest) {
+ if (digest !== false) $rootScope.$digest();
if (responses.length) {
- throw new Error('Unflushed requests: ' + responses.length);
+ var unflushedDescriptions = responses.map(function(res) { return res.description; });
+ throw new Error('Unflushed requests: ' + responses.length + '\n ' +
+ unflushedDescriptions.join('\n '));
}
};
@@ -1867,125 +2063,166 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
responses.length = 0;
};
+ $httpBackend.$$originalHttpBackend = originalHttpBackend;
+
return $httpBackend;
function createShortMethods(prefix) {
angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) {
$httpBackend[prefix + method] = function(url, headers, keys) {
+ assertArgDefined(arguments, 0, 'url');
+
+ // Change url to `null` if `undefined` to stop it throwing an exception further down
+ if (angular.isUndefined(url)) url = null;
+
return $httpBackend[prefix](method, url, undefined, headers, keys);
};
});
angular.forEach(['PUT', 'POST', 'PATCH'], function(method) {
$httpBackend[prefix + method] = function(url, data, headers, keys) {
+ assertArgDefined(arguments, 0, 'url');
+
+ // Change url to `null` if `undefined` to stop it throwing an exception further down
+ if (angular.isUndefined(url)) url = null;
+
return $httpBackend[prefix](method, url, data, headers, keys);
};
});
}
-}
-function MockHttpExpectation(method, url, data, headers, keys) {
-
- function getUrlParams(u) {
- var params = u.slice(u.indexOf('?') + 1).split('&');
- return params.sort();
+ function parseRouteUrl(url) {
+ var strippedUrl = stripQueryAndHash(url);
+ var parseOptions = {caseInsensitiveMatch: true, ignoreTrailingSlashes: true};
+ return routeToRegExp(strippedUrl, parseOptions);
}
+}
- function compareUrl(u) {
- return (url.slice(0, url.indexOf('?')) == u.slice(0, u.indexOf('?')) && getUrlParams(url).join() == getUrlParams(u).join());
+function assertArgDefined(args, index, name) {
+ if (args.length > index && angular.isUndefined(args[index])) {
+ throw new Error('Undefined argument `' + name + '`; the argument is provided but not defined');
}
+}
+
+function stripQueryAndHash(url) {
+ return url.replace(/[?#].*$/, '');
+}
+
+function MockHttpExpectation(expectedMethod, expectedUrl, expectedData, expectedHeaders,
+ expectedKeys) {
- this.data = data;
- this.headers = headers;
+ this.data = expectedData;
+ this.headers = expectedHeaders;
- this.match = function(m, u, d, h) {
- if (method != m) return false;
- if (!this.matchUrl(u)) return false;
- if (angular.isDefined(d) && !this.matchData(d)) return false;
- if (angular.isDefined(h) && !this.matchHeaders(h)) return false;
+ this.match = function(method, url, data, headers) {
+ if (expectedMethod !== method) return false;
+ if (!this.matchUrl(url)) return false;
+ if (angular.isDefined(data) && !this.matchData(data)) return false;
+ if (angular.isDefined(headers) && !this.matchHeaders(headers)) return false;
return true;
};
- this.matchUrl = function(u) {
- if (!url) return true;
- if (angular.isFunction(url.test)) return url.test(u);
- if (angular.isFunction(url)) return url(u);
- return (url == u || compareUrl(u));
+ this.matchUrl = function(url) {
+ if (!expectedUrl) return true;
+ if (angular.isFunction(expectedUrl.test)) return expectedUrl.test(url);
+ if (angular.isFunction(expectedUrl)) return expectedUrl(url);
+ return (expectedUrl === url || compareUrlWithQuery(url));
};
- this.matchHeaders = function(h) {
- if (angular.isUndefined(headers)) return true;
- if (angular.isFunction(headers)) return headers(h);
- return angular.equals(headers, h);
+ this.matchHeaders = function(headers) {
+ if (angular.isUndefined(expectedHeaders)) return true;
+ if (angular.isFunction(expectedHeaders)) return expectedHeaders(headers);
+ return angular.equals(expectedHeaders, headers);
};
- this.matchData = function(d) {
- if (angular.isUndefined(data)) return true;
- if (data && angular.isFunction(data.test)) return data.test(d);
- if (data && angular.isFunction(data)) return data(d);
- if (data && !angular.isString(data)) {
- return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d));
+ this.matchData = function(data) {
+ if (angular.isUndefined(expectedData)) return true;
+ if (expectedData && angular.isFunction(expectedData.test)) return expectedData.test(data);
+ if (expectedData && angular.isFunction(expectedData)) return expectedData(data);
+ if (expectedData && !angular.isString(expectedData)) {
+ return angular.equals(angular.fromJson(angular.toJson(expectedData)), angular.fromJson(data));
}
- return data == d;
+ // eslint-disable-next-line eqeqeq
+ return expectedData == data;
};
this.toString = function() {
- return method + ' ' + url;
+ return expectedMethod + ' ' + expectedUrl;
};
- this.params = function(u) {
- return angular.extend(parseQuery(), pathParams());
+ this.params = function(url) {
+ var queryStr = url.indexOf('?') === -1 ? '' : url.substring(url.indexOf('?') + 1);
+ var strippedUrl = stripQueryAndHash(url);
- function pathParams() {
- var keyObj = {};
- if (!url || !angular.isFunction(url.test) || !keys || keys.length === 0) return keyObj;
+ return angular.extend(extractParamsFromQuery(queryStr), extractParamsFromPath(strippedUrl));
+ };
- var m = url.exec(u);
- if (!m) return keyObj;
- for (var i = 1, len = m.length; i < len; ++i) {
- var key = keys[i - 1];
- var val = m[i];
- if (key && val) {
- keyObj[key.name || key] = val;
- }
- }
+ function compareUrlWithQuery(url) {
+ var urlWithQueryRe = /^([^?]*)\?(.*)$/;
+
+ var expectedMatch = urlWithQueryRe.exec(expectedUrl);
+ var actualMatch = urlWithQueryRe.exec(url);
+
+ return !!(expectedMatch && actualMatch) &&
+ (expectedMatch[1] === actualMatch[1]) &&
+ (normalizeQuery(expectedMatch[2]) === normalizeQuery(actualMatch[2]));
+ }
+
+ function normalizeQuery(queryStr) {
+ return queryStr.split('&').sort().join('&');
+ }
- return keyObj;
+ function extractParamsFromPath(strippedUrl) {
+ var keyObj = {};
+
+ if (!expectedUrl || !angular.isFunction(expectedUrl.test) ||
+ !expectedKeys || !expectedKeys.length) return keyObj;
+
+ var match = expectedUrl.exec(strippedUrl);
+ if (!match) return keyObj;
+
+ for (var i = 1, len = match.length; i < len; ++i) {
+ var key = expectedKeys[i - 1];
+ var val = match[i];
+ if (key && val) {
+ keyObj[key.name || key] = val;
+ }
}
- function parseQuery() {
- var obj = {}, key_value, key,
- queryStr = u.indexOf('?') > -1
- ? u.substring(u.indexOf('?') + 1)
- : "";
-
- angular.forEach(queryStr.split('&'), function(keyValue) {
- if (keyValue) {
- key_value = keyValue.replace(/\+/g,'%20').split('=');
- key = tryDecodeURIComponent(key_value[0]);
- if (angular.isDefined(key)) {
- var val = angular.isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
- if (!hasOwnProperty.call(obj, key)) {
- obj[key] = val;
- } else if (angular.isArray(obj[key])) {
- obj[key].push(val);
- } else {
- obj[key] = [obj[key],val];
- }
- }
+ return keyObj;
+ }
+
+ function extractParamsFromQuery(queryStr) {
+ var obj = {},
+ keyValuePairs = queryStr.split('&').
+ filter(angular.identity). // Ignore empty segments.
+ map(function(keyValue) { return keyValue.replace(/\+/g, '%20').split('='); });
+
+ angular.forEach(keyValuePairs, function(pair) {
+ var key = tryDecodeURIComponent(pair[0]);
+ if (angular.isDefined(key)) {
+ var val = angular.isDefined(pair[1]) ? tryDecodeURIComponent(pair[1]) : true;
+ if (!hasOwnProperty.call(obj, key)) {
+ obj[key] = val;
+ } else if (angular.isArray(obj[key])) {
+ obj[key].push(val);
+ } else {
+ obj[key] = [obj[key], val];
}
- });
- return obj;
- }
- function tryDecodeURIComponent(value) {
- try {
- return decodeURIComponent(value);
- } catch (e) {
- // Ignore any invalid uri component
}
+ });
+
+ return obj;
+ }
+
+ function tryDecodeURIComponent(value) {
+ try {
+ return decodeURIComponent(value);
+ } catch (e) {
+ // Ignore any invalid uri component
}
- };
+ }
}
function createMockXhr() {
@@ -2019,13 +2256,13 @@ function MockXhr() {
var header = this.$$respHeaders[name];
if (header) return header;
- name = angular.lowercase(name);
+ name = angular.$$lowercase(name);
header = this.$$respHeaders[name];
if (header) return header;
header = undefined;
angular.forEach(this.$$respHeaders, function(headerVal, headerName) {
- if (!header && angular.lowercase(headerName) == name) header = headerVal;
+ if (!header && angular.$$lowercase(headerName) === name) header = headerVal;
});
return header;
};
@@ -2039,10 +2276,14 @@ function MockXhr() {
return lines.join('\n');
};
- this.abort = angular.noop;
+ this.abort = function() {
+ if (isFunction(this.onabort)) {
+ this.onabort();
+ }
+ };
// This section simulates the events on a real XHR object (and the upload object)
- // When we are testing $httpBackend (inside the angular project) we make partial use of this
+ // When we are testing $httpBackend (inside the AngularJS project) we make partial use of this
// but store the events directly ourselves on `$$events`, instead of going through the `addEventListener`
this.$$events = {};
this.addEventListener = function(name, listener) {
@@ -2071,39 +2312,86 @@ angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $
/**
* @ngdoc method
* @name $timeout#flush
+ *
+ * @deprecated
+ * sinceVersion="1.7.3"
+ *
+ * This method flushes all types of tasks (not only timeouts), which is unintuitive.
+ * It is recommended to use {@link ngMock.$flushPendingTasks} instead.
+ *
* @description
*
* Flushes the queue of pending tasks.
*
+ * _This method is essentially an alias of {@link ngMock.$flushPendingTasks}._
+ *
+ * <div class="alert alert-warning">
+ * For historical reasons, this method will also flush non-`$timeout` pending tasks, such as
+ * {@link $q} promises and tasks scheduled via
+ * {@link ng.$rootScope.Scope#$applyAsync $applyAsync} and
+ * {@link ng.$rootScope.Scope#$evalAsync $evalAsync}.
+ * </div>
+ *
* @param {number=} delay maximum timeout amount to flush up until
*/
$delegate.flush = function(delay) {
+ // For historical reasons, `$timeout.flush()` flushes all types of pending tasks.
+ // Keep the same behavior for backwards compatibility (and because it doesn't make sense to
+ // selectively flush scheduled events out of order).
$browser.defer.flush(delay);
};
/**
* @ngdoc method
* @name $timeout#verifyNoPendingTasks
+ *
+ * @deprecated
+ * sinceVersion="1.7.3"
+ *
+ * This method takes all types of tasks (not only timeouts) into account, which is unintuitive.
+ * It is recommended to use {@link ngMock.$verifyNoPendingTasks} instead, which additionally
+ * allows checking for timeouts only (with `$verifyNoPendingTasks('$timeout')`).
+ *
* @description
*
- * Verifies that there are no pending tasks that need to be flushed.
+ * Verifies that there are no pending tasks that need to be flushed. It throws an error if there
+ * are still pending tasks.
+ *
+ * _This method is essentially an alias of {@link ngMock.$verifyNoPendingTasks} (called with no
+ * arguments)._
+ *
+ * <div class="alert alert-warning">
+ * <p>
+ * For historical reasons, this method will also verify non-`$timeout` pending tasks, such as
+ * pending {@link $http} requests, in-progress {@link $route} transitions, unresolved
+ * {@link $q} promises and tasks scheduled via
+ * {@link ng.$rootScope.Scope#$applyAsync $applyAsync} and
+ * {@link ng.$rootScope.Scope#$evalAsync $evalAsync}.
+ * </p>
+ * <p>
+ * It is recommended to use {@link ngMock.$verifyNoPendingTasks} instead, which additionally
+ * supports verifying a specific type of tasks. For example, you can verify there are no
+ * pending timeouts with `$verifyNoPendingTasks('$timeout')`.
+ * </p>
+ * </div>
*/
$delegate.verifyNoPendingTasks = function() {
- if ($browser.deferredFns.length) {
- throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' +
- formatPendingTasksAsString($browser.deferredFns));
+ // For historical reasons, `$timeout.verifyNoPendingTasks()` takes all types of pending tasks
+ // into account. Keep the same behavior for backwards compatibility.
+ var pendingTasks = $browser.defer.getPendingTasks();
+
+ if (pendingTasks.length) {
+ var formattedTasks = $browser.defer.formatPendingTasks(pendingTasks).join('\n ');
+ var hasPendingTimeout = pendingTasks.some(function(task) { return task.type === '$timeout'; });
+ var extraMessage = hasPendingTimeout ? '' : '\n\nNone of the pending tasks are timeouts. ' +
+ 'If you only want to verify pending timeouts, use ' +
+ '`$verifyNoPendingTasks(\'$timeout\')` instead.';
+
+ throw new Error('Deferred tasks to flush (' + pendingTasks.length + '):\n ' +
+ formattedTasks + extraMessage);
}
};
- function formatPendingTasksAsString(tasks) {
- var result = [];
- angular.forEach(tasks, function(task) {
- result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}');
- });
-
- return result.join(', ');
- }
-
return $delegate;
}];
@@ -2153,7 +2441,6 @@ angular.mock.$RootElementProvider = function() {
* A decorator for {@link ng.$controller} with additional `bindings` parameter, useful when testing
* controllers of directives that use {@link $compile#-bindtocontroller- `bindToController`}.
*
- *
* ## Example
*
* ```js
@@ -2171,18 +2458,24 @@ angular.mock.$RootElementProvider = function() {
* // Controller definition ...
*
* myMod.controller('MyDirectiveController', ['$log', function($log) {
- * $log.info(this.name);
+ * this.log = function() {
+ * $log.info(this.name);
+ * };
* }]);
*
*
* // In a test ...
*
* describe('myDirectiveController', function() {
- * it('should write the bound name to the log', inject(function($controller, $log) {
- * var ctrl = $controller('MyDirectiveController', { /* no locals &#42;/ }, { name: 'Clark Kent' });
- * expect(ctrl.name).toEqual('Clark Kent');
- * expect($log.info.logs).toEqual(['Clark Kent']);
- * }));
+ * describe('log()', function() {
+ * it('should write the bound name to the log', inject(function($controller, $log) {
+ * var ctrl = $controller('MyDirectiveController', { /* no locals &#42;/ }, { name: 'Clark Kent' });
+ * ctrl.log();
+ *
+ * expect(ctrl.name).toEqual('Clark Kent');
+ * expect($log.info.logs).toEqual(['Clark Kent']);
+ * }));
+ * });
* });
*
* ```
@@ -2193,45 +2486,51 @@ angular.mock.$RootElementProvider = function() {
*
* * check if a controller with given name is registered via `$controllerProvider`
* * check if evaluating the string on the current scope returns a constructor
- * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
- * `window` object (not recommended)
*
* The string can use the `controller as property` syntax, where the controller instance is published
* as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
* to work correctly.
*
* @param {Object} locals Injection locals for Controller.
- * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used
- * to simulate the `bindToController` feature and simplify certain kinds of tests.
+ * @param {Object=} bindings Properties to add to the controller instance. This is used to simulate
+ * the `bindToController` feature and simplify certain kinds of tests.
* @return {Object} Instance of given controller.
*/
-angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
- return function(expression, locals, later, ident) {
- if (later && typeof later === 'object') {
- var instantiate = $delegate(expression, locals, true, ident);
- angular.extend(instantiate.instance, later);
-
- var instance = instantiate();
- if (instance !== instantiate.instance) {
+function createControllerDecorator() {
+ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
+ return function(expression, locals, later, ident) {
+ if (later && typeof later === 'object') {
+ var instantiate = $delegate(expression, locals, true, ident);
+ var instance = instantiate();
angular.extend(instance, later);
+ return instance;
}
+ return $delegate(expression, locals, later, ident);
+ };
+ }];
- return instance;
- }
- return $delegate(expression, locals, later, ident);
- };
-}];
+ return angular.mock.$ControllerDecorator;
+}
/**
* @ngdoc service
* @name $componentController
* @description
- * A service that can be used to create instances of component controllers.
- * <div class="alert alert-info">
+ * A service that can be used to create instances of component controllers. Useful for unit-testing.
+ *
* Be aware that the controller will be instantiated and attached to the scope as specified in
* the component definition object. If you do not provide a `$scope` object in the `locals` param
* then the helper will create a new isolated scope as a child of `$rootScope`.
- * </div>
+ *
+ * If you are using `$element` or `$attrs` in the controller, make sure to provide them as `locals`.
+ * The `$element` must be a jqLite-wrapped DOM element, and `$attrs` should be an object that
+ * has all properties / functions that you are using in the controller. If this is getting too complex,
+ * you should compile the component instead and access the component's controller via the
+ * {@link angular.element#methods `controller`} function.
+ *
+ * See also the section on {@link guide/component#unit-testing-component-controllers unit-testing component controllers}
+ * in the guide.
+ *
* @param {string} componentName the name of the component whose controller we want to instantiate
* @param {Object} locals Injection locals for Controller.
* @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used
@@ -2239,7 +2538,8 @@ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
* @param {string=} ident Override the property name to use when attaching the controller to the scope.
* @return {Object} Instance of requested controller.
*/
-angular.mock.$ComponentControllerProvider = ['$compileProvider', function($compileProvider) {
+angular.mock.$ComponentControllerProvider = ['$compileProvider',
+ function ComponentControllerProvider($compileProvider) {
this.$get = ['$controller','$injector', '$rootScope', function($controller, $injector, $rootScope) {
return function $componentController(componentName, locals, bindings, ident) {
// get all directives associated to the component name
@@ -2273,21 +2573,17 @@ angular.mock.$ComponentControllerProvider = ['$compileProvider', function($compi
* @packageName angular-mocks
* @description
*
- * # ngMock
- *
- * The `ngMock` module provides support to inject and mock Angular services into unit tests.
- * In addition, ngMock also extends various core ng services such that they can be
+ * The `ngMock` module provides support to inject and mock AngularJS services into unit tests.
+ * In addition, ngMock also extends various core AngularJS services such that they can be
* inspected and controlled in a synchronous manner within test code.
*
- *
- * <div doc-module-components="ngMock"></div>
- *
* @installation
*
* First, download the file:
* * [Google CDN](https://developers.google.com/speed/libraries/devguide#angularjs) e.g.
* `"//ajax.googleapis.com/ajax/libs/angularjs/X.Y.Z/angular-mocks.js"`
* * [NPM](https://www.npmjs.com/) e.g. `npm install angular-mocks@X.Y.Z`
+ * * [Yarn](https://yarnpkg.com) e.g. `yarn add angular-mocks@X.Y.Z`
* * [Bower](http://bower.io) e.g. `bower install angular-mocks#X.Y.Z`
* * [code.angularjs.org](https://code.angularjs.org/) (discouraged for production use) e.g.
* `"//code.angularjs.org/X.Y.Z/angular-mocks.js"`
@@ -2316,15 +2612,17 @@ angular.module('ngMock', ['ng']).provider({
$exceptionHandler: angular.mock.$ExceptionHandlerProvider,
$log: angular.mock.$LogProvider,
$interval: angular.mock.$IntervalProvider,
- $httpBackend: angular.mock.$HttpBackendProvider,
$rootElement: angular.mock.$RootElementProvider,
- $componentController: angular.mock.$ComponentControllerProvider
-}).config(['$provide', function($provide) {
+ $componentController: angular.mock.$ComponentControllerProvider,
+ $flushPendingTasks: angular.mock.$FlushPendingTasksProvider,
+ $verifyNoPendingTasks: angular.mock.$VerifyNoPendingTasksProvider
+}).config(['$provide', '$compileProvider', function($provide, $compileProvider) {
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
- $provide.decorator('$controller', angular.mock.$ControllerDecorator);
-}]);
+ $provide.decorator('$controller', createControllerDecorator($compileProvider));
+ $provide.decorator('$httpBackend', angular.mock.$httpBackendDecorator);
+}]).info({ angularVersion: '"1.8.2"' });
/**
* @ngdoc module
@@ -2333,14 +2631,13 @@ angular.module('ngMock', ['ng']).provider({
* @packageName angular-mocks
* @description
*
- * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing.
+ * The `ngMockE2E` is an AngularJS module which contains mocks suitable for end-to-end testing.
* Currently there is only one mock present in this module -
* the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock.
*/
angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
- $provide.value('$httpBackend', angular.injector(['ng']).get('$httpBackend'));
$provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
-}]);
+}]).info({ angularVersion: '"1.8.2"' });
/**
* @ngdoc service
@@ -2387,19 +2684,19 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* phones.push(phone);
* return [200, phone, {}];
* });
- * $httpBackend.whenGET(/^\/templates\//).passThrough(); // Requests for templare are handled by the real server
+ * $httpBackend.whenGET(/^\/templates\//).passThrough(); // Requests for templates are handled by the real server
* //...
* });
* ```
*
* Afterwards, bootstrap your app with this new module.
*
- * ## Example
+ * @example
* <example name="httpbackend-e2e-testing" module="myAppE2E" deps="angular-mocks.js">
* <file name="app.js">
* var myApp = angular.module('myApp', []);
*
- * myApp.controller('main', function($http) {
+ * myApp.controller('MainCtrl', function MainCtrl($http) {
* var ctrl = this;
*
* ctrl.phones = [];
@@ -2441,7 +2738,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* });
* </file>
* <file name="index.html">
- * <div ng-controller="main as $ctrl">
+ * <div ng-controller="MainCtrl as $ctrl">
* <form name="newPhoneForm" ng-submit="$ctrl.addPhone($ctrl.newPhone)">
* <input type="text" ng-model="$ctrl.newPhone.name">
* <input type="submit" value="Add Phone">
@@ -2465,9 +2762,10 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* Creates a new backend definition.
*
* @param {string} method HTTP method.
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
- * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
+ * data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
* object and returns true if the headers match the current definition.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
@@ -2497,7 +2795,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for GET requests. For more info see `when()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
@@ -2514,7 +2812,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for HEAD requests. For more info see `when()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
@@ -2531,7 +2829,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for DELETE requests. For more info see `when()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
@@ -2548,9 +2846,10 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for POST requests. For more info see `when()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
- * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
+ * data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
* {@link ngMock.$httpBackend $httpBackend mock}.
@@ -2566,9 +2865,10 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for PUT requests. For more info see `when()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
- * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
+ * data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
* {@link ngMock.$httpBackend $httpBackend mock}.
@@ -2584,9 +2884,10 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for PATCH requests. For more info see `when()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
- * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
+ * data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
* {@link ngMock.$httpBackend $httpBackend mock}.
@@ -2602,7 +2903,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for JSONP requests. For more info see `when()`.
*
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
* {@link ngMock.$httpBackend $httpBackend mock}.
@@ -2623,6 +2924,39 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* control how a matched request is handled. You can save this object for later use and invoke
* `respond` or `passThrough` again in order to change how a matched request is handled.
*/
+/**
+ * @ngdoc method
+ * @name $httpBackend#matchLatestDefinitionEnabled
+ * @module ngMockE2E
+ * @description
+ * This method can be used to change which mocked responses `$httpBackend` returns, when defining
+ * them with {@link ngMock.$httpBackend#when $httpBackend.when()} (and shortcut methods).
+ * By default, `$httpBackend` returns the first definition that matches. When setting
+ * `$http.matchLatestDefinitionEnabled(true)`, it will use the last response that matches, i.e. the
+ * one that was added last.
+ *
+ * ```js
+ * hb.when('GET', '/url1').respond(200, 'content', {});
+ * hb.when('GET', '/url1').respond(201, 'another', {});
+ * hb('GET', '/url1'); // receives "content"
+ *
+ * $http.matchLatestDefinitionEnabled(true)
+ * hb('GET', '/url1'); // receives "another"
+ *
+ * hb.when('GET', '/url1').respond(201, 'onemore', {});
+ * hb('GET', '/url1'); // receives "onemore"
+ * ```
+ *
+ * This is useful if a you have a default response that is overriden inside specific tests.
+ *
+ * Note that different from config methods on providers, `matchLatestDefinitionEnabled()` can be changed
+ * even when the application is already running.
+ *
+ * @param {Boolean=} value value to set, either `true` or `false`. Default is `false`.
+ * If omitted, it will return the current value.
+ * @return {$httpBackend|Boolean} self when used as a setter, and the current value when used
+ * as a getter
+ */
angular.mock.e2e = {};
angular.mock.e2e.$httpBackendDecorator =
['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock];
@@ -2654,6 +2988,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
* @ngdoc method
* @name $rootScope.Scope#$countChildScopes
* @module ngMock
+ * @this $rootScope.Scope
* @description
* Counts all the direct and indirect child scopes of the current scope.
*
@@ -2662,7 +2997,6 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
* @returns {number} Total number of child scopes.
*/
function countChildScopes() {
- // jshint validthis: true
var count = 0; // exclude the current scope
var pendingChildHeads = [this.$$childHead];
var currentScope;
@@ -2684,6 +3018,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
/**
* @ngdoc method
* @name $rootScope.Scope#$countWatchers
+ * @this $rootScope.Scope
* @module ngMock
* @description
* Counts all the watchers of direct and indirect child scopes of the current scope.
@@ -2694,7 +3029,6 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
* @returns {number} Total number of watchers.
*/
function countWatchers() {
- // jshint validthis: true
var count = this.$$watchers ? this.$$watchers.length : 0; // include the current scope
var pendingChildHeads = [this.$$childHead];
var currentScope;
@@ -2714,7 +3048,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
}];
-!(function(jasmineOrMocha) {
+(function(jasmineOrMocha) {
if (!jasmineOrMocha) {
return;
@@ -2809,7 +3143,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
*
* You cannot call `sharedInjector()` from within a context already using `sharedInjector()`.
*
- * ## Example
+ * ## Example
*
* Typically beforeAll is used to make many assertions about a single operation. This can
* cut down test run-time as the test setup doesn't need to be re-run, and enabling focussed
@@ -2847,14 +3181,14 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
*/
module.sharedInjector = function() {
if (!(module.$$beforeAllHook && module.$$afterAllHook)) {
- throw Error("sharedInjector() cannot be used unless your test runner defines beforeAll/afterAll");
+ throw Error('sharedInjector() cannot be used unless your test runner defines beforeAll/afterAll');
}
var initialized = false;
- module.$$beforeAllHook(function() {
+ module.$$beforeAllHook(/** @this */ function() {
if (injectorState.shared) {
- injectorState.sharedError = Error("sharedInjector() cannot be called inside a context that has already called sharedInjector()");
+ injectorState.sharedError = Error('sharedInjector() cannot be called inside a context that has already called sharedInjector()');
throw injectorState.sharedError;
}
initialized = true;
@@ -2873,10 +3207,10 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
};
module.$$beforeEach = function() {
- if (injectorState.shared && currentSpec && currentSpec != this) {
+ if (injectorState.shared && currentSpec && currentSpec !== this) {
var state = currentSpec;
currentSpec = this;
- angular.forEach(["$injector","$modules","$providerInjector", "$injectorStrict"], function(k) {
+ angular.forEach(['$injector','$modules','$providerInjector', '$injectorStrict'], function(k) {
currentSpec[k] = state[k];
state[k] = null;
});
@@ -2900,12 +3234,6 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
delete fn.$inject;
});
- angular.forEach(currentSpec.$modules, function(module) {
- if (module && module.$$hashKey) {
- module.$$hashKey = undefined;
- }
- });
-
currentSpec.$injector = null;
currentSpec.$modules = null;
currentSpec.$providerInjector = null;
@@ -2967,7 +3295,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
* These are ignored by the injector when the reference name is resolved.
*
* For example, the parameter `_myService_` would be resolved as the reference `myService`.
- * Since it is available in the function body as _myService_, we can then assign it to a variable
+ * Since it is available in the function body as `_myService_`, we can then assign it to a variable
* defined in an outer scope.
*
* ```
@@ -3031,7 +3359,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
- var ErrorAddingDeclarationLocationStack = function(e, errorForStack) {
+ var ErrorAddingDeclarationLocationStack = function ErrorAddingDeclarationLocationStack(e, errorForStack) {
this.message = e.message;
this.name = e.name;
if (e.line) this.line = e.line;
@@ -3049,11 +3377,11 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
if (!errorForStack.stack) {
try {
throw errorForStack;
- } catch (e) {}
+ } catch (e) { /* empty */ }
}
- return wasInjectorCreated() ? workFn.call(currentSpec) : workFn;
+ return wasInjectorCreated() ? WorkFn.call(currentSpec) : WorkFn;
/////////////////////
- function workFn() {
+ function WorkFn() {
var modules = currentSpec.$modules || [];
var strictDi = !!currentSpec.$injectorStrict;
modules.unshift(['$injector', function($injector) {
@@ -3066,7 +3394,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
if (strictDi) {
// If strictDi is enabled, annotate the providerInjector blocks
angular.forEach(modules, function(moduleFn) {
- if (typeof moduleFn === "function") {
+ if (typeof moduleFn === 'function') {
angular.injector.$$annotate(moduleFn);
}
});
@@ -3081,9 +3409,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
injector.annotate(blockFns[i]);
}
try {
- /* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */
injector.invoke(blockFns[i] || angular.noop, this);
- /* jshint +W040 */
} catch (e) {
if (e.stack && errorForStack) {
throw new ErrorAddingDeclarationLocationStack(e, errorForStack);