From 030dabe17221db7b760c3cea40740a506369f97d Mon Sep 17 00:00:00 2001 From: manchandavishal Date: Sat, 8 May 2021 00:17:46 +0530 Subject: Update XStatic-Angular to 1.8.2 Related-Bug: #1927261 Change-Id: I8238e020df05825f6731499d027c0fd12cc2c00d --- xstatic/pkg/angular/data/angular-mocks.js | 1130 +++++++++++++++++++---------- 1 file changed, 728 insertions(+), 402 deletions(-) (limited to 'xstatic/pkg/angular/data/angular-mocks.js') 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} pendingTasks - A list of task objects. + * @return {Array} 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). + * + *
+ * 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. + *
+ * + * @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). + * + *
+ * 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. + *
+ * + * @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) { @@ -1469,15 +1624,57 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { return chain; }; + /** + * @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}._ + * + *
+ * 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}. + *
+ * * @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)._ + * + *
+ *

+ * 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}. + *

+ *

+ * 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')`. + *

+ *
*/ $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 */ }, { 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 */ }, { 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. - *
+ * 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`. - *
+ * + * 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. * - * - *
- * * @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 * * * 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) { * }); * * - *
+ *
*
* * @@ -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); -- cgit v1.2.1