angularjs-rails 1.6.8 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @license AngularJS v1.6.8
3
- * (c) 2010-2017 Google, Inc. http://angularjs.org
2
+ * @license AngularJS v1.8.0
3
+ * (c) 2010-2020 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
6
6
  (function(window, angular) {'use strict';
@@ -23,7 +23,7 @@ var jqLite;
23
23
  * sequencing based on the order of how the messages are defined in the template.
24
24
  *
25
25
  * Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude`
26
- * `ngMessage` and `ngMessageExp` directives.
26
+ * `ngMessage`, `ngMessageExp` and `ngMessageDefault` directives.
27
27
  *
28
28
  * ## Usage
29
29
  * The `ngMessages` directive allows keys in a key/value collection to be associated with a child element
@@ -200,7 +200,7 @@ var jqLite;
200
200
  *
201
201
  * Feel free to use other structural directives such as ng-if and ng-switch to further control
202
202
  * what messages are active and when. Be careful, if you place ng-message on the same element
203
- * as these structural directives, Angular may not be able to determine if a message is active
203
+ * as these structural directives, AngularJS may not be able to determine if a message is active
204
204
  * or not. Therefore it is best to place the ng-message on a child element of the structural
205
205
  * directive.
206
206
  *
@@ -262,17 +262,36 @@ var jqLite;
262
262
  * .some-message.ng-leave.ng-leave-active {}
263
263
  * ```
264
264
  *
265
- * {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate.
265
+ * {@link ngAnimate See the ngAnimate docs} to learn how to use JavaScript animations or to learn
266
+ * more about ngAnimate.
267
+ *
268
+ * ## Displaying a default message
269
+ * If the ngMessages renders no inner ngMessage directive (i.e. when none of the truthy
270
+ * keys are matched by a defined message), then it will render a default message
271
+ * using the {@link ngMessageDefault} directive.
272
+ * Note that matched messages will always take precedence over unmatched messages. That means
273
+ * the default message will not be displayed when another message is matched. This is also
274
+ * true for `ng-messages-multiple`.
275
+ *
276
+ * ```html
277
+ * <div ng-messages="myForm.myField.$error" role="alert">
278
+ * <div ng-message="required">This field is required</div>
279
+ * <div ng-message="minlength">This field is too short</div>
280
+ * <div ng-message-default>This field has an input error</div>
281
+ * </div>
282
+ * ```
283
+ *
284
+
266
285
  */
267
286
  angular.module('ngMessages', [], function initAngularHelpers() {
268
- // Access helpers from angular core.
287
+ // Access helpers from AngularJS core.
269
288
  // Do it inside a `config` block to ensure `window.angular` is available.
270
289
  forEach = angular.forEach;
271
290
  isArray = angular.isArray;
272
291
  isString = angular.isString;
273
292
  jqLite = angular.element;
274
293
  })
275
- .info({ angularVersion: '1.6.8' })
294
+ .info({ angularVersion: '1.8.0' })
276
295
 
277
296
  /**
278
297
  * @ngdoc directive
@@ -291,8 +310,11 @@ angular.module('ngMessages', [], function initAngularHelpers() {
291
310
  * at a time and this depends on the prioritization of the messages within the template. (This can
292
311
  * be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
293
312
  *
294
- * A remote template can also be used to promote message reusability and messages can also be
295
- * overridden.
313
+ * A remote template can also be used (With {@link ngMessagesInclude}) to promote message
314
+ * reusability and messages can also be overridden.
315
+ *
316
+ * A default message can also be displayed when no `ngMessage` directive is inserted, using the
317
+ * {@link ngMessageDefault} directive.
296
318
  *
297
319
  * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
298
320
  *
@@ -303,6 +325,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
303
325
  * <ANY ng-message="stringValue">...</ANY>
304
326
  * <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
305
327
  * <ANY ng-message-exp="expressionValue">...</ANY>
328
+ * <ANY ng-message-default>...</ANY>
306
329
  * </ANY>
307
330
  *
308
331
  * <!-- or by using element directives -->
@@ -310,10 +333,11 @@ angular.module('ngMessages', [], function initAngularHelpers() {
310
333
  * <ng-message when="stringValue">...</ng-message>
311
334
  * <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
312
335
  * <ng-message when-exp="expressionValue">...</ng-message>
336
+ * <ng-message-default>...</ng-message-default>
313
337
  * </ng-messages>
314
338
  * ```
315
339
  *
316
- * @param {string} ngMessages an angular expression evaluating to a key/value object
340
+ * @param {string} ngMessages an AngularJS expression evaluating to a key/value object
317
341
  * (this is typically the $error object on an ngModel instance).
318
342
  * @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true
319
343
  *
@@ -338,6 +362,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
338
362
  * <div ng-message="required">You did not enter a field</div>
339
363
  * <div ng-message="minlength">Your field is too short</div>
340
364
  * <div ng-message="maxlength">Your field is too long</div>
365
+ * <div ng-message-default>This field has an input error</div>
341
366
  * </div>
342
367
  * </form>
343
368
  * </file>
@@ -375,6 +400,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
375
400
 
376
401
  var unmatchedMessages = [];
377
402
  var matchedKeys = {};
403
+ var truthyKeys = 0;
378
404
  var messageItem = ctrl.head;
379
405
  var messageFound = false;
380
406
  var totalMessages = 0;
@@ -387,13 +413,17 @@ angular.module('ngMessages', [], function initAngularHelpers() {
387
413
  var messageUsed = false;
388
414
  if (!messageFound) {
389
415
  forEach(collection, function(value, key) {
390
- if (!messageUsed && truthy(value) && messageCtrl.test(key)) {
391
- // this is to prevent the same error name from showing up twice
392
- if (matchedKeys[key]) return;
393
- matchedKeys[key] = true;
416
+ if (truthy(value) && !messageUsed) {
417
+ truthyKeys++;
394
418
 
395
- messageUsed = true;
396
- messageCtrl.attach();
419
+ if (messageCtrl.test(key)) {
420
+ // this is to prevent the same error name from showing up twice
421
+ if (matchedKeys[key]) return;
422
+ matchedKeys[key] = true;
423
+
424
+ messageUsed = true;
425
+ messageCtrl.attach();
426
+ }
397
427
  }
398
428
  });
399
429
  }
@@ -413,7 +443,16 @@ angular.module('ngMessages', [], function initAngularHelpers() {
413
443
  messageCtrl.detach();
414
444
  });
415
445
 
416
- if (unmatchedMessages.length !== totalMessages) {
446
+ var messageMatched = unmatchedMessages.length !== totalMessages;
447
+ var attachDefault = ctrl.default && !messageMatched && truthyKeys > 0;
448
+
449
+ if (attachDefault) {
450
+ ctrl.default.attach();
451
+ } else if (ctrl.default) {
452
+ ctrl.default.detach();
453
+ }
454
+
455
+ if (messageMatched || attachDefault) {
417
456
  $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS);
418
457
  } else {
419
458
  $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
@@ -422,13 +461,6 @@ angular.module('ngMessages', [], function initAngularHelpers() {
422
461
 
423
462
  $scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
424
463
 
425
- // If the element is destroyed, proactively destroy all the currently visible messages
426
- $element.on('$destroy', function() {
427
- forEach(messages, function(item) {
428
- item.message.detach();
429
- });
430
- });
431
-
432
464
  this.reRender = function() {
433
465
  if (!renderLater) {
434
466
  renderLater = true;
@@ -440,23 +472,31 @@ angular.module('ngMessages', [], function initAngularHelpers() {
440
472
  }
441
473
  };
442
474
 
443
- this.register = function(comment, messageCtrl) {
444
- var nextKey = latestKey.toString();
445
- messages[nextKey] = {
446
- message: messageCtrl
447
- };
448
- insertMessageNode($element[0], comment, nextKey);
449
- comment.$$ngMessageNode = nextKey;
450
- latestKey++;
475
+ this.register = function(comment, messageCtrl, isDefault) {
476
+ if (isDefault) {
477
+ ctrl.default = messageCtrl;
478
+ } else {
479
+ var nextKey = latestKey.toString();
480
+ messages[nextKey] = {
481
+ message: messageCtrl
482
+ };
483
+ insertMessageNode($element[0], comment, nextKey);
484
+ comment.$$ngMessageNode = nextKey;
485
+ latestKey++;
486
+ }
451
487
 
452
488
  ctrl.reRender();
453
489
  };
454
490
 
455
- this.deregister = function(comment) {
456
- var key = comment.$$ngMessageNode;
457
- delete comment.$$ngMessageNode;
458
- removeMessageNode($element[0], comment, key);
459
- delete messages[key];
491
+ this.deregister = function(comment, isDefault) {
492
+ if (isDefault) {
493
+ delete ctrl.default;
494
+ } else {
495
+ var key = comment.$$ngMessageNode;
496
+ delete comment.$$ngMessageNode;
497
+ removeMessageNode($element[0], comment, key);
498
+ delete messages[key];
499
+ }
460
500
  ctrl.reRender();
461
501
  };
462
502
 
@@ -503,6 +543,9 @@ angular.module('ngMessages', [], function initAngularHelpers() {
503
543
  function removeMessageNode(parent, comment, key) {
504
544
  var messageNode = messages[key];
505
545
 
546
+ // This message node may have already been removed by a call to deregister()
547
+ if (!messageNode) return;
548
+
506
549
  var match = findPreviousMessage(parent, comment);
507
550
  if (match) {
508
551
  match.next = messageNode.next;
@@ -656,9 +699,41 @@ angular.module('ngMessages', [], function initAngularHelpers() {
656
699
  *
657
700
  * @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key.
658
701
  */
659
- .directive('ngMessageExp', ngMessageDirectiveFactory());
702
+ .directive('ngMessageExp', ngMessageDirectiveFactory())
660
703
 
661
- function ngMessageDirectiveFactory() {
704
+ /**
705
+ * @ngdoc directive
706
+ * @name ngMessageDefault
707
+ * @restrict AE
708
+ * @scope
709
+ *
710
+ * @description
711
+ * `ngMessageDefault` is a directive with the purpose to show and hide a default message for
712
+ * {@link directive:ngMessages}, when none of provided messages matches.
713
+ *
714
+ * More information about using `ngMessageDefault` can be found in the
715
+ * {@link module:ngMessages `ngMessages` module documentation}.
716
+ *
717
+ * @usage
718
+ * ```html
719
+ * <!-- using attribute directives -->
720
+ * <ANY ng-messages="expression" role="alert">
721
+ * <ANY ng-message="stringValue">...</ANY>
722
+ * <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
723
+ * <ANY ng-message-default>...</ANY>
724
+ * </ANY>
725
+ *
726
+ * <!-- or by using element directives -->
727
+ * <ng-messages for="expression" role="alert">
728
+ * <ng-message when="stringValue">...</ng-message>
729
+ * <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
730
+ * <ng-message-default>...</ng-message-default>
731
+ * </ng-messages>
732
+ *
733
+ */
734
+ .directive('ngMessageDefault', ngMessageDirectiveFactory(true));
735
+
736
+ function ngMessageDirectiveFactory(isDefault) {
662
737
  return ['$animate', function($animate) {
663
738
  return {
664
739
  restrict: 'AE',
@@ -667,25 +742,28 @@ function ngMessageDirectiveFactory() {
667
742
  terminal: true,
668
743
  require: '^^ngMessages',
669
744
  link: function(scope, element, attrs, ngMessagesCtrl, $transclude) {
670
- var commentNode = element[0];
671
-
672
- var records;
673
- var staticExp = attrs.ngMessage || attrs.when;
674
- var dynamicExp = attrs.ngMessageExp || attrs.whenExp;
675
- var assignRecords = function(items) {
676
- records = items
677
- ? (isArray(items)
678
- ? items
679
- : items.split(/[\s,]+/))
680
- : null;
681
- ngMessagesCtrl.reRender();
682
- };
745
+ var commentNode, records, staticExp, dynamicExp;
746
+
747
+ if (!isDefault) {
748
+ commentNode = element[0];
749
+ staticExp = attrs.ngMessage || attrs.when;
750
+ dynamicExp = attrs.ngMessageExp || attrs.whenExp;
751
+
752
+ var assignRecords = function(items) {
753
+ records = items
754
+ ? (isArray(items)
755
+ ? items
756
+ : items.split(/[\s,]+/))
757
+ : null;
758
+ ngMessagesCtrl.reRender();
759
+ };
683
760
 
684
- if (dynamicExp) {
685
- assignRecords(scope.$eval(dynamicExp));
686
- scope.$watchCollection(dynamicExp, assignRecords);
687
- } else {
688
- assignRecords(staticExp);
761
+ if (dynamicExp) {
762
+ assignRecords(scope.$eval(dynamicExp));
763
+ scope.$watchCollection(dynamicExp, assignRecords);
764
+ } else {
765
+ assignRecords(staticExp);
766
+ }
689
767
  }
690
768
 
691
769
  var currentElement, messageCtrl;
@@ -707,8 +785,10 @@ function ngMessageDirectiveFactory() {
707
785
  // by another structural directive then it's time
708
786
  // to deregister the message from the controller
709
787
  currentElement.on('$destroy', function() {
788
+ // If the message element was removed via a call to `detach` then `currentElement` will be null
789
+ // So this handler only handles cases where something else removed the message element.
710
790
  if (currentElement && currentElement.$$attachId === $$attachId) {
711
- ngMessagesCtrl.deregister(commentNode);
791
+ ngMessagesCtrl.deregister(commentNode, isDefault);
712
792
  messageCtrl.detach();
713
793
  }
714
794
  newScope.$destroy();
@@ -723,6 +803,14 @@ function ngMessageDirectiveFactory() {
723
803
  $animate.leave(elm);
724
804
  }
725
805
  }
806
+ }, isDefault);
807
+
808
+ // We need to ensure that this directive deregisters itself when it no longer exists
809
+ // Normally this is done when the attached element is destroyed; but if this directive
810
+ // gets removed before we attach the message to the DOM there is nothing to watch
811
+ // in which case we must deregister when the containing scope is destroyed.
812
+ scope.$on('$destroy', function() {
813
+ ngMessagesCtrl.deregister(commentNode, isDefault);
726
814
  });
727
815
  }
728
816
  };
@@ -1,12 +1,61 @@
1
1
  /**
2
- * @license AngularJS v1.6.8
3
- * (c) 2010-2017 Google, Inc. http://angularjs.org
2
+ * @license AngularJS v1.8.0
3
+ * (c) 2010-2020 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
6
6
  (function(window, angular) {
7
7
 
8
8
  'use strict';
9
9
 
10
+ /* global routeToRegExp: true */
11
+
12
+ /**
13
+ * @param {string} path - The path to parse. (It is assumed to have query and hash stripped off.)
14
+ * @param {Object} opts - Options.
15
+ * @return {Object} - An object containing an array of path parameter names (`keys`) and a regular
16
+ * expression (`regexp`) that can be used to identify a matching URL and extract the path
17
+ * parameter values.
18
+ *
19
+ * @description
20
+ * Parses the given path, extracting path parameter names and a regular expression to match URLs.
21
+ *
22
+ * Originally inspired by `pathRexp` in `visionmedia/express/lib/utils.js`.
23
+ */
24
+ function routeToRegExp(path, opts) {
25
+ var keys = [];
26
+
27
+ var pattern = path
28
+ .replace(/([().])/g, '\\$1')
29
+ .replace(/(\/)?:(\w+)(\*\?|[?*])?/g, function(_, slash, key, option) {
30
+ var optional = option === '?' || option === '*?';
31
+ var star = option === '*' || option === '*?';
32
+ keys.push({name: key, optional: optional});
33
+ slash = slash || '';
34
+ return (
35
+ (optional ? '(?:' + slash : slash + '(?:') +
36
+ (star ? '(.+?)' : '([^/]+)') +
37
+ (optional ? '?)?' : ')')
38
+ );
39
+ })
40
+ .replace(/([/$*])/g, '\\$1');
41
+
42
+ if (opts.ignoreTrailingSlashes) {
43
+ pattern = pattern.replace(/\/+$/, '') + '/*';
44
+ }
45
+
46
+ return {
47
+ keys: keys,
48
+ regexp: new RegExp(
49
+ '^' + pattern + '(?:[?#]|$)',
50
+ opts.caseInsensitiveMatch ? 'i' : ''
51
+ )
52
+ };
53
+ }
54
+
55
+ 'use strict';
56
+
57
+ /* global routeToRegExp: false */
58
+
10
59
  /**
11
60
  * @ngdoc object
12
61
  * @name angular.mock
@@ -31,43 +80,27 @@ angular.mock = {};
31
80
  * that there are several helper methods available which can be used in tests.
32
81
  */
33
82
  angular.mock.$BrowserProvider = function() {
34
- this.$get = function() {
35
- return new angular.mock.$Browser();
36
- };
83
+ this.$get = [
84
+ '$log', '$$taskTrackerFactory',
85
+ function($log, $$taskTrackerFactory) {
86
+ return new angular.mock.$Browser($log, $$taskTrackerFactory);
87
+ }
88
+ ];
37
89
  };
38
90
 
39
- angular.mock.$Browser = function() {
91
+ angular.mock.$Browser = function($log, $$taskTrackerFactory) {
40
92
  var self = this;
93
+ var taskTracker = $$taskTrackerFactory($log);
41
94
 
42
95
  this.isMock = true;
43
96
  self.$$url = 'http://server/';
44
97
  self.$$lastUrl = self.$$url; // used by url polling fn
45
98
  self.pollFns = [];
46
99
 
47
- // Testability API
48
-
49
- var outstandingRequestCount = 0;
50
- var outstandingRequestCallbacks = [];
51
- self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
52
- self.$$completeOutstandingRequest = function(fn) {
53
- try {
54
- fn();
55
- } finally {
56
- outstandingRequestCount--;
57
- if (!outstandingRequestCount) {
58
- while (outstandingRequestCallbacks.length) {
59
- outstandingRequestCallbacks.pop()();
60
- }
61
- }
62
- }
63
- };
64
- self.notifyWhenNoOutstandingRequests = function(callback) {
65
- if (outstandingRequestCount) {
66
- outstandingRequestCallbacks.push(callback);
67
- } else {
68
- callback();
69
- }
70
- };
100
+ // Task-tracking API
101
+ self.$$completeOutstandingRequest = taskTracker.completeTask;
102
+ self.$$incOutstandingRequestCount = taskTracker.incTaskCount;
103
+ self.notifyWhenNoOutstandingRequests = taskTracker.notifyWhenNoPendingTasks;
71
104
 
72
105
  // register url polling fn
73
106
 
@@ -91,13 +124,22 @@ angular.mock.$Browser = function() {
91
124
  self.deferredFns = [];
92
125
  self.deferredNextId = 0;
93
126
 
94
- self.defer = function(fn, delay) {
95
- // Note that we do not use `$$incOutstandingRequestCount` or `$$completeOutstandingRequest`
96
- // in this mock implementation.
127
+ self.defer = function(fn, delay, taskType) {
128
+ var timeoutId = self.deferredNextId++;
129
+
97
130
  delay = delay || 0;
98
- self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId});
99
- self.deferredFns.sort(function(a, b) { return a.time - b.time;});
100
- return self.deferredNextId++;
131
+ taskType = taskType || taskTracker.DEFAULT_TASK_TYPE;
132
+
133
+ taskTracker.incTaskCount(taskType);
134
+ self.deferredFns.push({
135
+ id: timeoutId,
136
+ type: taskType,
137
+ time: (self.defer.now + delay),
138
+ fn: fn
139
+ });
140
+ self.deferredFns.sort(function(a, b) { return a.time - b.time; });
141
+
142
+ return timeoutId;
101
143
  };
102
144
 
103
145
 
@@ -111,14 +153,15 @@ angular.mock.$Browser = function() {
111
153
 
112
154
 
113
155
  self.defer.cancel = function(deferId) {
114
- var fnIndex;
156
+ var taskIndex;
115
157
 
116
- angular.forEach(self.deferredFns, function(fn, index) {
117
- if (fn.id === deferId) fnIndex = index;
158
+ angular.forEach(self.deferredFns, function(task, index) {
159
+ if (task.id === deferId) taskIndex = index;
118
160
  });
119
161
 
120
- if (angular.isDefined(fnIndex)) {
121
- self.deferredFns.splice(fnIndex, 1);
162
+ if (angular.isDefined(taskIndex)) {
163
+ var task = self.deferredFns.splice(taskIndex, 1)[0];
164
+ taskTracker.completeTask(angular.noop, task.type);
122
165
  return true;
123
166
  }
124
167
 
@@ -132,6 +175,8 @@ angular.mock.$Browser = function() {
132
175
  * @description
133
176
  * Flushes all pending requests and executes the defer callbacks.
134
177
  *
178
+ * See {@link ngMock.$flushPendingsTasks} for more info.
179
+ *
135
180
  * @param {number=} number of milliseconds to flush. See {@link #defer.now}
136
181
  */
137
182
  self.defer.flush = function(delay) {
@@ -140,26 +185,76 @@ angular.mock.$Browser = function() {
140
185
  if (angular.isDefined(delay)) {
141
186
  // A delay was passed so compute the next time
142
187
  nextTime = self.defer.now + delay;
188
+ } else if (self.deferredFns.length) {
189
+ // No delay was passed so set the next time so that it clears the deferred queue
190
+ nextTime = self.deferredFns[self.deferredFns.length - 1].time;
143
191
  } else {
144
- if (self.deferredFns.length) {
145
- // No delay was passed so set the next time so that it clears the deferred queue
146
- nextTime = self.deferredFns[self.deferredFns.length - 1].time;
147
- } else {
148
- // No delay passed, but there are no deferred tasks so flush - indicates an error!
149
- throw new Error('No deferred tasks to be flushed');
150
- }
192
+ // No delay passed, but there are no deferred tasks so flush - indicates an error!
193
+ throw new Error('No deferred tasks to be flushed');
151
194
  }
152
195
 
153
196
  while (self.deferredFns.length && self.deferredFns[0].time <= nextTime) {
154
197
  // Increment the time and call the next deferred function
155
198
  self.defer.now = self.deferredFns[0].time;
156
- self.deferredFns.shift().fn();
199
+ var task = self.deferredFns.shift();
200
+ taskTracker.completeTask(task.fn, task.type);
157
201
  }
158
202
 
159
203
  // Ensure that the current time is correct
160
204
  self.defer.now = nextTime;
161
205
  };
162
206
 
207
+ /**
208
+ * @name $browser#defer.getPendingTasks
209
+ *
210
+ * @description
211
+ * Returns the currently pending tasks that need to be flushed.
212
+ * You can request a specific type of tasks only, by specifying a `taskType`.
213
+ *
214
+ * @param {string=} taskType - The type tasks to return.
215
+ */
216
+ self.defer.getPendingTasks = function(taskType) {
217
+ return !taskType
218
+ ? self.deferredFns
219
+ : self.deferredFns.filter(function(task) { return task.type === taskType; });
220
+ };
221
+
222
+ /**
223
+ * @name $browser#defer.formatPendingTasks
224
+ *
225
+ * @description
226
+ * Formats each task in a list of pending tasks as a string, suitable for use in error messages.
227
+ *
228
+ * @param {Array<Object>} pendingTasks - A list of task objects.
229
+ * @return {Array<string>} A list of stringified tasks.
230
+ */
231
+ self.defer.formatPendingTasks = function(pendingTasks) {
232
+ return pendingTasks.map(function(task) {
233
+ return '{id: ' + task.id + ', type: ' + task.type + ', time: ' + task.time + '}';
234
+ });
235
+ };
236
+
237
+ /**
238
+ * @name $browser#defer.verifyNoPendingTasks
239
+ *
240
+ * @description
241
+ * Verifies that there are no pending tasks that need to be flushed.
242
+ * You can check for a specific type of tasks only, by specifying a `taskType`.
243
+ *
244
+ * See {@link $verifyNoPendingTasks} for more info.
245
+ *
246
+ * @param {string=} taskType - The type tasks to check for.
247
+ */
248
+ self.defer.verifyNoPendingTasks = function(taskType) {
249
+ var pendingTasks = self.defer.getPendingTasks(taskType);
250
+
251
+ if (pendingTasks.length) {
252
+ var formattedTasks = self.defer.formatPendingTasks(pendingTasks).join('\n ');
253
+ throw new Error('Deferred tasks to flush (' + pendingTasks.length + '):\n ' +
254
+ formattedTasks);
255
+ }
256
+ };
257
+
163
258
  self.$$baseHref = '/';
164
259
  self.baseHref = function() {
165
260
  return this.$$baseHref;
@@ -184,7 +279,8 @@ angular.mock.$Browser.prototype = {
184
279
  state = null;
185
280
  }
186
281
  if (url) {
187
- this.$$url = url;
282
+ // The `$browser` service trims empty hashes; simulate it.
283
+ this.$$url = url.replace(/#$/, '');
188
284
  // Native pushState serializes & copies the object; simulate it.
189
285
  this.$$state = angular.copy(state);
190
286
  return this;
@@ -198,6 +294,82 @@ angular.mock.$Browser.prototype = {
198
294
  }
199
295
  };
200
296
 
297
+ /**
298
+ * @ngdoc service
299
+ * @name $flushPendingTasks
300
+ *
301
+ * @description
302
+ * Flushes all currently pending tasks and executes the corresponding callbacks.
303
+ *
304
+ * Optionally, you can also pass a `delay` argument to only flush tasks that are scheduled to be
305
+ * executed within `delay` milliseconds. Currently, `delay` only applies to timeouts, since all
306
+ * other tasks have a delay of 0 (i.e. they are scheduled to be executed as soon as possible, but
307
+ * still asynchronously).
308
+ *
309
+ * If no delay is specified, it uses a delay such that all currently pending tasks are flushed.
310
+ *
311
+ * The types of tasks that are flushed include:
312
+ *
313
+ * - Pending timeouts (via {@link $timeout}).
314
+ * - Pending tasks scheduled via {@link ng.$rootScope.Scope#$applyAsync $applyAsync}.
315
+ * - Pending tasks scheduled via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}.
316
+ * These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises).
317
+ *
318
+ * <div class="alert alert-info">
319
+ * Periodic tasks scheduled via {@link $interval} use a different queue and are not flushed by
320
+ * `$flushPendingTasks()`. Use {@link ngMock.$interval#flush $interval.flush(millis)} instead.
321
+ * </div>
322
+ *
323
+ * @param {number=} delay - The number of milliseconds to flush.
324
+ */
325
+ angular.mock.$FlushPendingTasksProvider = function() {
326
+ this.$get = [
327
+ '$browser',
328
+ function($browser) {
329
+ return function $flushPendingTasks(delay) {
330
+ return $browser.defer.flush(delay);
331
+ };
332
+ }
333
+ ];
334
+ };
335
+
336
+ /**
337
+ * @ngdoc service
338
+ * @name $verifyNoPendingTasks
339
+ *
340
+ * @description
341
+ * Verifies that there are no pending tasks that need to be flushed. It throws an error if there are
342
+ * still pending tasks.
343
+ *
344
+ * You can check for a specific type of tasks only, by specifying a `taskType`.
345
+ *
346
+ * Available task types:
347
+ *
348
+ * - `$timeout`: Pending timeouts (via {@link $timeout}).
349
+ * - `$http`: Pending HTTP requests (via {@link $http}).
350
+ * - `$route`: In-progress route transitions (via {@link $route}).
351
+ * - `$applyAsync`: Pending tasks scheduled via {@link ng.$rootScope.Scope#$applyAsync $applyAsync}.
352
+ * - `$evalAsync`: Pending tasks scheduled via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}.
353
+ * These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises).
354
+ *
355
+ * <div class="alert alert-info">
356
+ * Periodic tasks scheduled via {@link $interval} use a different queue and are not taken into
357
+ * account by `$verifyNoPendingTasks()`. There is currently no way to verify that there are no
358
+ * pending {@link $interval} tasks.
359
+ * </div>
360
+ *
361
+ * @param {string=} taskType - The type of tasks to check for.
362
+ */
363
+ angular.mock.$VerifyNoPendingTasksProvider = function() {
364
+ this.$get = [
365
+ '$browser',
366
+ function($browser) {
367
+ return function $verifyNoPendingTasks(taskType) {
368
+ return $browser.defer.verifyNoPendingTasks(taskType);
369
+ };
370
+ }
371
+ ];
372
+ };
201
373
 
202
374
  /**
203
375
  * @ngdoc provider
@@ -466,62 +638,40 @@ angular.mock.$LogProvider = function() {
466
638
  * @returns {promise} A promise which will be notified on each iteration.
467
639
  */
468
640
  angular.mock.$IntervalProvider = function() {
469
- this.$get = ['$browser', '$rootScope', '$q', '$$q',
470
- function($browser, $rootScope, $q, $$q) {
641
+ this.$get = ['$browser', '$$intervalFactory',
642
+ function($browser, $$intervalFactory) {
471
643
  var repeatFns = [],
472
644
  nextRepeatId = 0,
473
- now = 0;
474
-
475
- var $interval = function(fn, delay, count, invokeApply) {
476
- var hasParams = arguments.length > 4,
477
- args = hasParams ? Array.prototype.slice.call(arguments, 4) : [],
478
- iteration = 0,
479
- skipApply = (angular.isDefined(invokeApply) && !invokeApply),
480
- deferred = (skipApply ? $$q : $q).defer(),
481
- promise = deferred.promise;
482
-
483
- count = (angular.isDefined(count)) ? count : 0;
484
- promise.then(null, function() {}, (!hasParams) ? fn : function() {
485
- fn.apply(null, args);
486
- });
487
-
488
- promise.$$intervalId = nextRepeatId;
489
-
490
- function tick() {
491
- deferred.notify(iteration++);
492
-
493
- if (count > 0 && iteration >= count) {
494
- var fnIndex;
495
- deferred.resolve(iteration);
496
-
497
- angular.forEach(repeatFns, function(fn, index) {
498
- if (fn.id === promise.$$intervalId) fnIndex = index;
645
+ now = 0,
646
+ setIntervalFn = function(tick, delay, deferred, skipApply) {
647
+ var id = nextRepeatId++;
648
+ var fn = !skipApply ? tick : function() {
649
+ tick();
650
+ $browser.defer.flush();
651
+ };
652
+
653
+ repeatFns.push({
654
+ nextTime: (now + (delay || 0)),
655
+ delay: delay || 1,
656
+ fn: fn,
657
+ id: id,
658
+ deferred: deferred
499
659
  });
660
+ repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime; });
500
661
 
501
- if (angular.isDefined(fnIndex)) {
502
- repeatFns.splice(fnIndex, 1);
662
+ return id;
663
+ },
664
+ clearIntervalFn = function(id) {
665
+ for (var fnIndex = repeatFns.length - 1; fnIndex >= 0; fnIndex--) {
666
+ if (repeatFns[fnIndex].id === id) {
667
+ repeatFns.splice(fnIndex, 1);
668
+ break;
669
+ }
503
670
  }
504
- }
505
-
506
- if (skipApply) {
507
- $browser.defer.flush();
508
- } else {
509
- $rootScope.$apply();
510
- }
511
- }
671
+ };
512
672
 
513
- repeatFns.push({
514
- nextTime: (now + (delay || 0)),
515
- delay: delay || 1,
516
- fn: tick,
517
- id: nextRepeatId,
518
- deferred: deferred
519
- });
520
- repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;});
673
+ var $interval = $$intervalFactory(setIntervalFn, clearIntervalFn);
521
674
 
522
- nextRepeatId++;
523
- return promise;
524
- };
525
675
  /**
526
676
  * @ngdoc method
527
677
  * @name $interval#cancel
@@ -534,17 +684,15 @@ angular.mock.$IntervalProvider = function() {
534
684
  */
535
685
  $interval.cancel = function(promise) {
536
686
  if (!promise) return false;
537
- var fnIndex;
538
-
539
- angular.forEach(repeatFns, function(fn, index) {
540
- if (fn.id === promise.$$intervalId) fnIndex = index;
541
- });
542
687
 
543
- if (angular.isDefined(fnIndex)) {
544
- repeatFns[fnIndex].deferred.promise.then(undefined, function() {});
545
- repeatFns[fnIndex].deferred.reject('canceled');
546
- repeatFns.splice(fnIndex, 1);
547
- return true;
688
+ for (var fnIndex = repeatFns.length - 1; fnIndex >= 0; fnIndex--) {
689
+ if (repeatFns[fnIndex].id === promise.$$intervalId) {
690
+ var deferred = repeatFns[fnIndex].deferred;
691
+ deferred.promise.then(undefined, function() {});
692
+ deferred.reject('canceled');
693
+ repeatFns.splice(fnIndex, 1);
694
+ return true;
695
+ }
548
696
  }
549
697
 
550
698
  return false;
@@ -557,7 +705,7 @@ angular.mock.$IntervalProvider = function() {
557
705
  *
558
706
  * Runs interval tasks scheduled to be run in the next `millis` milliseconds.
559
707
  *
560
- * @param {number=} millis maximum timeout amount to flush up until.
708
+ * @param {number} millis maximum timeout amount to flush up until.
561
709
  *
562
710
  * @return {number} The amount of time moved forward.
563
711
  */
@@ -803,7 +951,7 @@ angular.mock.TzDate.prototype = Date.prototype;
803
951
  * You need to require the `ngAnimateMock` module in your test suite for instance `beforeEach(module('ngAnimateMock'))`
804
952
  */
805
953
  angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
806
- .info({ angularVersion: '1.6.8' })
954
+ .info({ angularVersion: '1.8.0' })
807
955
 
808
956
  .config(['$provide', function($provide) {
809
957
 
@@ -969,7 +1117,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
969
1117
  *
970
1118
  * *NOTE*: This is not an injectable instance, just a globally available function.
971
1119
  *
972
- * Method for serializing common angular objects (scope, elements, etc..) into strings.
1120
+ * Method for serializing common AngularJS objects (scope, elements, etc..) into strings.
973
1121
  * It is useful for logging objects to the console when debugging.
974
1122
  *
975
1123
  * @param {*} object - any object to turn into string.
@@ -1051,7 +1199,7 @@ angular.mock.dump = function(object) {
1051
1199
  * This mock implementation can be used to respond with static or dynamic responses via the
1052
1200
  * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc).
1053
1201
  *
1054
- * When an Angular application needs some data from a server, it calls the $http service, which
1202
+ * When an AngularJS application needs some data from a server, it calls the $http service, which
1055
1203
  * sends the request to a real server using $httpBackend service. With dependency injection, it is
1056
1204
  * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify
1057
1205
  * the requests and respond with some testing data without sending a request to a real server.
@@ -1289,7 +1437,7 @@ angular.mock.dump = function(object) {
1289
1437
  * ## Matching route requests
1290
1438
  *
1291
1439
  * For extra convenience, `whenRoute` and `expectRoute` shortcuts are available. These methods offer colon
1292
- * delimited matching of the url path, ignoring the query string. This allows declarations
1440
+ * delimited matching of the url path, ignoring the query string and trailing slashes. This allows declarations
1293
1441
  * similar to how application routes are configured with `$routeProvider`. Because these methods convert
1294
1442
  * the definition url to regex, declaration order is important. Combined with query parameter parsing,
1295
1443
  * the following is possible:
@@ -1349,6 +1497,7 @@ angular.mock.$httpBackendDecorator =
1349
1497
  function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1350
1498
  var definitions = [],
1351
1499
  expectations = [],
1500
+ matchLatestDefinition = false,
1352
1501
  responses = [],
1353
1502
  responsesPush = angular.bind(responses, responses.push),
1354
1503
  copy = angular.copy,
@@ -1385,9 +1534,13 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1385
1534
  function wrapResponse(wrapped) {
1386
1535
  if (!$browser && timeout) {
1387
1536
  if (timeout.then) {
1388
- timeout.then(handleTimeout);
1537
+ timeout.then(function() {
1538
+ handlePrematureEnd(angular.isDefined(timeout.$$timeoutId) ? 'timeout' : 'abort');
1539
+ });
1389
1540
  } else {
1390
- $timeout(handleTimeout, timeout);
1541
+ $timeout(function() {
1542
+ handlePrematureEnd('timeout');
1543
+ }, timeout);
1391
1544
  }
1392
1545
  }
1393
1546
 
@@ -1401,27 +1554,36 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1401
1554
  copy(response[3] || ''), copy(response[4]));
1402
1555
  }
1403
1556
 
1404
- function handleTimeout() {
1557
+ function handlePrematureEnd(reason) {
1405
1558
  for (var i = 0, ii = responses.length; i < ii; i++) {
1406
1559
  if (responses[i] === handleResponse) {
1407
1560
  responses.splice(i, 1);
1408
- callback(-1, undefined, '', undefined, 'timeout');
1561
+ callback(-1, undefined, '', undefined, reason);
1409
1562
  break;
1410
1563
  }
1411
1564
  }
1412
1565
  }
1413
1566
  }
1414
1567
 
1568
+ function createFatalError(message) {
1569
+ var error = new Error(message);
1570
+ // In addition to being converted to a rejection, these errors also need to be passed to
1571
+ // the $exceptionHandler and be rethrown (so that the test fails).
1572
+ error.$$passToExceptionHandler = true;
1573
+ return error;
1574
+ }
1575
+
1415
1576
  if (expectation && expectation.match(method, url)) {
1416
1577
  if (!expectation.matchData(data)) {
1417
- throw new Error('Expected ' + expectation + ' with different data\n' +
1418
- 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
1578
+ throw createFatalError('Expected ' + expectation + ' with different data\n' +
1579
+ 'EXPECTED: ' + prettyPrint(expectation.data) + '\n' +
1580
+ 'GOT: ' + data);
1419
1581
  }
1420
1582
 
1421
1583
  if (!expectation.matchHeaders(headers)) {
1422
- throw new Error('Expected ' + expectation + ' with different headers\n' +
1423
- 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' +
1424
- prettyPrint(headers));
1584
+ throw createFatalError('Expected ' + expectation + ' with different headers\n' +
1585
+ 'EXPECTED: ' + prettyPrint(expectation.headers) + '\n' +
1586
+ 'GOT: ' + prettyPrint(headers));
1425
1587
  }
1426
1588
 
1427
1589
  expectations.shift();
@@ -1433,28 +1595,26 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1433
1595
  wasExpected = true;
1434
1596
  }
1435
1597
 
1436
- var i = -1, definition;
1437
- while ((definition = definitions[++i])) {
1598
+ var i = matchLatestDefinition ? definitions.length : -1, definition;
1599
+
1600
+ while ((definition = definitions[matchLatestDefinition ? --i : ++i])) {
1438
1601
  if (definition.match(method, url, data, headers || {})) {
1439
1602
  if (definition.response) {
1440
1603
  // if $browser specified, we do auto flush all requests
1441
1604
  ($browser ? $browser.defer : responsesPush)(wrapResponse(definition));
1442
1605
  } else if (definition.passThrough) {
1443
1606
  originalHttpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers);
1444
- } else throw new Error('No response defined !');
1607
+ } else throw createFatalError('No response defined !');
1445
1608
  return;
1446
1609
  }
1447
1610
  }
1448
- var error = wasExpected ?
1449
- new Error('No response defined !') :
1450
- new Error('Unexpected request: ' + method + ' ' + url + '\n' +
1451
- (expectation ? 'Expected ' + expectation : 'No more request expected'));
1452
1611
 
1453
- // In addition to be being converted to a rejection, this error also needs to be passed to
1454
- // the $exceptionHandler and be rethrown (so that the test fails).
1455
- error.$$passToExceptionHandler = true;
1612
+ if (wasExpected) {
1613
+ throw createFatalError('No response defined !');
1614
+ }
1456
1615
 
1457
- throw error;
1616
+ throw createFatalError('Unexpected request: ' + method + ' ' + url + '\n' +
1617
+ (expectation ? 'Expected ' + expectation : 'No more request expected'));
1458
1618
  }
1459
1619
 
1460
1620
  /**
@@ -1482,8 +1642,9 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1482
1642
  * ```
1483
1643
  * – The respond method takes a set of static data to be returned or a function that can
1484
1644
  * return an array containing response status (number), response data (Array|Object|string),
1485
- * response headers (Object), and the text for the status (string). The respond method returns
1486
- * the `requestHandler` object for possible overrides.
1645
+ * response headers (Object), HTTP status text (string), and XMLHttpRequest status (string:
1646
+ * `complete`, `error`, `timeout` or `abort`). The respond method returns the `requestHandler`
1647
+ * object for possible overrides.
1487
1648
  */
1488
1649
  $httpBackend.when = function(method, url, data, headers, keys) {
1489
1650
 
@@ -1510,6 +1671,47 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1510
1671
  return chain;
1511
1672
  };
1512
1673
 
1674
+ /**
1675
+ * @ngdoc method
1676
+ * @name $httpBackend#matchLatestDefinitionEnabled
1677
+ * @description
1678
+ * This method can be used to change which mocked responses `$httpBackend` returns, when defining
1679
+ * them with {@link ngMock.$httpBackend#when $httpBackend.when()} (and shortcut methods).
1680
+ * By default, `$httpBackend` returns the first definition that matches. When setting
1681
+ * `$http.matchLatestDefinitionEnabled(true)`, it will use the last response that matches, i.e. the
1682
+ * one that was added last.
1683
+ *
1684
+ * ```js
1685
+ * hb.when('GET', '/url1').respond(200, 'content', {});
1686
+ * hb.when('GET', '/url1').respond(201, 'another', {});
1687
+ * hb('GET', '/url1'); // receives "content"
1688
+ *
1689
+ * $http.matchLatestDefinitionEnabled(true)
1690
+ * hb('GET', '/url1'); // receives "another"
1691
+ *
1692
+ * hb.when('GET', '/url1').respond(201, 'onemore', {});
1693
+ * hb('GET', '/url1'); // receives "onemore"
1694
+ * ```
1695
+ *
1696
+ * This is useful if a you have a default response that is overriden inside specific tests.
1697
+ *
1698
+ * Note that different from config methods on providers, `matchLatestDefinitionEnabled()` can be changed
1699
+ * even when the application is already running.
1700
+ *
1701
+ * @param {Boolean=} value value to set, either `true` or `false`. Default is `false`.
1702
+ * If omitted, it will return the current value.
1703
+ * @return {$httpBackend|Boolean} self when used as a setter, and the current value when used
1704
+ * as a getter
1705
+ */
1706
+ $httpBackend.matchLatestDefinitionEnabled = function(value) {
1707
+ if (angular.isDefined(value)) {
1708
+ matchLatestDefinition = value;
1709
+ return this;
1710
+ } else {
1711
+ return matchLatestDefinition;
1712
+ }
1713
+ };
1714
+
1513
1715
  /**
1514
1716
  * @ngdoc method
1515
1717
  * @name $httpBackend#whenGET
@@ -1518,7 +1720,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1518
1720
  *
1519
1721
  * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1520
1722
  * and returns true if the url matches the current definition.
1521
- * @param {(Object|function(Object))=} headers HTTP headers.
1723
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1724
+ * object and returns true if the headers match the current definition.
1522
1725
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1523
1726
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1524
1727
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1533,7 +1736,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1533
1736
  *
1534
1737
  * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1535
1738
  * and returns true if the url matches the current definition.
1536
- * @param {(Object|function(Object))=} headers HTTP headers.
1739
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1740
+ * object and returns true if the headers match the current definition.
1537
1741
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1538
1742
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1539
1743
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1548,7 +1752,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1548
1752
  *
1549
1753
  * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1550
1754
  * and returns true if the url matches the current definition.
1551
- * @param {(Object|function(Object))=} headers HTTP headers.
1755
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1756
+ * object and returns true if the headers match the current definition.
1552
1757
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1553
1758
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1554
1759
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1565,7 +1770,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1565
1770
  * and returns true if the url matches the current definition.
1566
1771
  * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
1567
1772
  * data string and returns true if the data is as expected.
1568
- * @param {(Object|function(Object))=} headers HTTP headers.
1773
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1774
+ * object and returns true if the headers match the current definition.
1569
1775
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1570
1776
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1571
1777
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1582,7 +1788,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1582
1788
  * and returns true if the url matches the current definition.
1583
1789
  * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
1584
1790
  * data string and returns true if the data is as expected.
1585
- * @param {(Object|function(Object))=} headers HTTP headers.
1791
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1792
+ * object and returns true if the headers match the current definition.
1586
1793
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1587
1794
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1588
1795
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1614,42 +1821,13 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1614
1821
  * @param {string} url HTTP url string that supports colon param matching.
1615
1822
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1616
1823
  * request is handled. You can save this object for later use and invoke `respond` again in
1617
- * order to change how a matched request is handled. See #when for more info.
1824
+ * order to change how a matched request is handled.
1825
+ * See {@link ngMock.$httpBackend#when `when`} for more info.
1618
1826
  */
1619
1827
  $httpBackend.whenRoute = function(method, url) {
1620
- var pathObj = parseRoute(url);
1621
- return $httpBackend.when(method, pathObj.regexp, undefined, undefined, pathObj.keys);
1622
- };
1623
-
1624
- function parseRoute(url) {
1625
- var ret = {
1626
- regexp: url
1627
- },
1628
- keys = ret.keys = [];
1629
-
1630
- if (!url || !angular.isString(url)) return ret;
1631
-
1632
- url = url
1633
- .replace(/([().])/g, '\\$1')
1634
- .replace(/(\/)?:(\w+)([?*])?/g, function(_, slash, key, option) {
1635
- var optional = option === '?' ? option : null;
1636
- var star = option === '*' ? option : null;
1637
- keys.push({ name: key, optional: !!optional });
1638
- slash = slash || '';
1639
- return ''
1640
- + (optional ? '' : slash)
1641
- + '(?:'
1642
- + (optional ? slash : '')
1643
- + (star && '(.+?)' || '([^/]+)')
1644
- + (optional || '')
1645
- + ')'
1646
- + (optional || '');
1647
- })
1648
- .replace(/([/$*])/g, '\\$1');
1649
-
1650
- ret.regexp = new RegExp('^' + url, 'i');
1651
- return ret;
1652
- }
1828
+ var parsed = parseRouteUrl(url);
1829
+ return $httpBackend.when(method, parsed.regexp, undefined, undefined, parsed.keys);
1830
+ };
1653
1831
 
1654
1832
  /**
1655
1833
  * @ngdoc method
@@ -1671,14 +1849,15 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1671
1849
  * order to change how a matched request is handled.
1672
1850
  *
1673
1851
  * - respond –
1674
- * ```
1675
- * { function([status,] data[, headers, statusText])
1676
- * | function(function(method, url, data, headers, params)}
1677
- * ```
1852
+ * ```js
1853
+ * {function([status,] data[, headers, statusText])
1854
+ * | function(function(method, url, data, headers, params)}
1855
+ * ```
1678
1856
  * – The respond method takes a set of static data to be returned or a function that can
1679
1857
  * return an array containing response status (number), response data (Array|Object|string),
1680
- * response headers (Object), and the text for the status (string). The respond method returns
1681
- * the `requestHandler` object for possible overrides.
1858
+ * response headers (Object), HTTP status text (string), and XMLHttpRequest status (string:
1859
+ * `complete`, `error`, `timeout` or `abort`). The respond method returns the `requestHandler`
1860
+ * object for possible overrides.
1682
1861
  */
1683
1862
  $httpBackend.expect = function(method, url, data, headers, keys) {
1684
1863
 
@@ -1703,8 +1882,9 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1703
1882
  * Creates a new request expectation for GET requests. For more info see `expect()`.
1704
1883
  *
1705
1884
  * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1706
- * and returns true if the url matches the current definition.
1707
- * @param {Object=} headers HTTP headers.
1885
+ * and returns true if the url matches the current expectation.
1886
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1887
+ * object and returns true if the headers match the current expectation.
1708
1888
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1709
1889
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1710
1890
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1718,8 +1898,9 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1718
1898
  * Creates a new request expectation for HEAD requests. For more info see `expect()`.
1719
1899
  *
1720
1900
  * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1721
- * and returns true if the url matches the current definition.
1722
- * @param {Object=} headers HTTP headers.
1901
+ * and returns true if the url matches the current expectation.
1902
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1903
+ * object and returns true if the headers match the current expectation.
1723
1904
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1724
1905
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1725
1906
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1733,8 +1914,9 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1733
1914
  * Creates a new request expectation for DELETE requests. For more info see `expect()`.
1734
1915
  *
1735
1916
  * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1736
- * and returns true if the url matches the current definition.
1737
- * @param {Object=} headers HTTP headers.
1917
+ * and returns true if the url matches the current expectation.
1918
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1919
+ * object and returns true if the headers match the current expectation.
1738
1920
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1739
1921
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1740
1922
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1748,11 +1930,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1748
1930
  * Creates a new request expectation for POST requests. For more info see `expect()`.
1749
1931
  *
1750
1932
  * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1751
- * and returns true if the url matches the current definition.
1933
+ * and returns true if the url matches the current expectation.
1752
1934
  * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
1753
1935
  * receives data string and returns true if the data is as expected, or Object if request body
1754
1936
  * is in JSON format.
1755
- * @param {Object=} headers HTTP headers.
1937
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1938
+ * object and returns true if the headers match the current expectation.
1756
1939
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1757
1940
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1758
1941
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1766,11 +1949,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1766
1949
  * Creates a new request expectation for PUT requests. For more info see `expect()`.
1767
1950
  *
1768
1951
  * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1769
- * and returns true if the url matches the current definition.
1952
+ * and returns true if the url matches the current expectation.
1770
1953
  * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
1771
1954
  * receives data string and returns true if the data is as expected, or Object if request body
1772
1955
  * is in JSON format.
1773
- * @param {Object=} headers HTTP headers.
1956
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1957
+ * object and returns true if the headers match the current expectation.
1774
1958
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1775
1959
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1776
1960
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1784,11 +1968,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1784
1968
  * Creates a new request expectation for PATCH requests. For more info see `expect()`.
1785
1969
  *
1786
1970
  * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1787
- * and returns true if the url matches the current definition.
1971
+ * and returns true if the url matches the current expectation.
1788
1972
  * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
1789
1973
  * receives data string and returns true if the data is as expected, or Object if request body
1790
1974
  * is in JSON format.
1791
- * @param {Object=} headers HTTP headers.
1975
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1976
+ * object and returns true if the headers match the current expectation.
1792
1977
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1793
1978
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1794
1979
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1802,7 +1987,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1802
1987
  * Creates a new request expectation for JSONP requests. For more info see `expect()`.
1803
1988
  *
1804
1989
  * @param {string|RegExp|function(string)=} url HTTP url or function that receives an url
1805
- * and returns true if the url matches the current definition.
1990
+ * and returns true if the url matches the current expectation.
1806
1991
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1807
1992
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1808
1993
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1820,11 +2005,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1820
2005
  * @param {string} url HTTP url string that supports colon param matching.
1821
2006
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1822
2007
  * request is handled. You can save this object for later use and invoke `respond` again in
1823
- * order to change how a matched request is handled. See #expect for more info.
2008
+ * order to change how a matched request is handled.
2009
+ * See {@link ngMock.$httpBackend#expect `expect`} for more info.
1824
2010
  */
1825
2011
  $httpBackend.expectRoute = function(method, url) {
1826
- var pathObj = parseRoute(url);
1827
- return $httpBackend.expect(method, pathObj.regexp, undefined, undefined, pathObj.keys);
2012
+ var parsed = parseRouteUrl(url);
2013
+ return $httpBackend.expect(method, parsed.regexp, undefined, undefined, parsed.keys);
1828
2014
  };
1829
2015
 
1830
2016
 
@@ -1952,6 +2138,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1952
2138
  };
1953
2139
  });
1954
2140
  }
2141
+
2142
+ function parseRouteUrl(url) {
2143
+ var strippedUrl = stripQueryAndHash(url);
2144
+ var parseOptions = {caseInsensitiveMatch: true, ignoreTrailingSlashes: true};
2145
+ return routeToRegExp(strippedUrl, parseOptions);
2146
+ }
1955
2147
  }
1956
2148
 
1957
2149
  function assertArgDefined(args, index, name) {
@@ -1960,110 +2152,124 @@ function assertArgDefined(args, index, name) {
1960
2152
  }
1961
2153
  }
1962
2154
 
2155
+ function stripQueryAndHash(url) {
2156
+ return url.replace(/[?#].*$/, '');
2157
+ }
1963
2158
 
1964
- function MockHttpExpectation(method, url, data, headers, keys) {
1965
-
1966
- function getUrlParams(u) {
1967
- var params = u.slice(u.indexOf('?') + 1).split('&');
1968
- return params.sort();
1969
- }
1970
-
1971
- function compareUrl(u) {
1972
- return (url.slice(0, url.indexOf('?')) === u.slice(0, u.indexOf('?')) &&
1973
- getUrlParams(url).join() === getUrlParams(u).join());
1974
- }
2159
+ function MockHttpExpectation(expectedMethod, expectedUrl, expectedData, expectedHeaders,
2160
+ expectedKeys) {
1975
2161
 
1976
- this.data = data;
1977
- this.headers = headers;
2162
+ this.data = expectedData;
2163
+ this.headers = expectedHeaders;
1978
2164
 
1979
- this.match = function(m, u, d, h) {
1980
- if (method !== m) return false;
1981
- if (!this.matchUrl(u)) return false;
1982
- if (angular.isDefined(d) && !this.matchData(d)) return false;
1983
- if (angular.isDefined(h) && !this.matchHeaders(h)) return false;
2165
+ this.match = function(method, url, data, headers) {
2166
+ if (expectedMethod !== method) return false;
2167
+ if (!this.matchUrl(url)) return false;
2168
+ if (angular.isDefined(data) && !this.matchData(data)) return false;
2169
+ if (angular.isDefined(headers) && !this.matchHeaders(headers)) return false;
1984
2170
  return true;
1985
2171
  };
1986
2172
 
1987
- this.matchUrl = function(u) {
1988
- if (!url) return true;
1989
- if (angular.isFunction(url.test)) return url.test(u);
1990
- if (angular.isFunction(url)) return url(u);
1991
- return (url === u || compareUrl(u));
2173
+ this.matchUrl = function(url) {
2174
+ if (!expectedUrl) return true;
2175
+ if (angular.isFunction(expectedUrl.test)) return expectedUrl.test(url);
2176
+ if (angular.isFunction(expectedUrl)) return expectedUrl(url);
2177
+ return (expectedUrl === url || compareUrlWithQuery(url));
1992
2178
  };
1993
2179
 
1994
- this.matchHeaders = function(h) {
1995
- if (angular.isUndefined(headers)) return true;
1996
- if (angular.isFunction(headers)) return headers(h);
1997
- return angular.equals(headers, h);
2180
+ this.matchHeaders = function(headers) {
2181
+ if (angular.isUndefined(expectedHeaders)) return true;
2182
+ if (angular.isFunction(expectedHeaders)) return expectedHeaders(headers);
2183
+ return angular.equals(expectedHeaders, headers);
1998
2184
  };
1999
2185
 
2000
- this.matchData = function(d) {
2001
- if (angular.isUndefined(data)) return true;
2002
- if (data && angular.isFunction(data.test)) return data.test(d);
2003
- if (data && angular.isFunction(data)) return data(d);
2004
- if (data && !angular.isString(data)) {
2005
- return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d));
2186
+ this.matchData = function(data) {
2187
+ if (angular.isUndefined(expectedData)) return true;
2188
+ if (expectedData && angular.isFunction(expectedData.test)) return expectedData.test(data);
2189
+ if (expectedData && angular.isFunction(expectedData)) return expectedData(data);
2190
+ if (expectedData && !angular.isString(expectedData)) {
2191
+ return angular.equals(angular.fromJson(angular.toJson(expectedData)), angular.fromJson(data));
2006
2192
  }
2007
2193
  // eslint-disable-next-line eqeqeq
2008
- return data == d;
2194
+ return expectedData == data;
2009
2195
  };
2010
2196
 
2011
2197
  this.toString = function() {
2012
- return method + ' ' + url;
2198
+ return expectedMethod + ' ' + expectedUrl;
2013
2199
  };
2014
2200
 
2015
- this.params = function(u) {
2016
- return angular.extend(parseQuery(), pathParams());
2201
+ this.params = function(url) {
2202
+ var queryStr = url.indexOf('?') === -1 ? '' : url.substring(url.indexOf('?') + 1);
2203
+ var strippedUrl = stripQueryAndHash(url);
2017
2204
 
2018
- function pathParams() {
2019
- var keyObj = {};
2020
- if (!url || !angular.isFunction(url.test) || !keys || keys.length === 0) return keyObj;
2205
+ return angular.extend(extractParamsFromQuery(queryStr), extractParamsFromPath(strippedUrl));
2206
+ };
2021
2207
 
2022
- var m = url.exec(u);
2023
- if (!m) return keyObj;
2024
- for (var i = 1, len = m.length; i < len; ++i) {
2025
- var key = keys[i - 1];
2026
- var val = m[i];
2027
- if (key && val) {
2028
- keyObj[key.name || key] = val;
2029
- }
2030
- }
2208
+ function compareUrlWithQuery(url) {
2209
+ var urlWithQueryRe = /^([^?]*)\?(.*)$/;
2210
+
2211
+ var expectedMatch = urlWithQueryRe.exec(expectedUrl);
2212
+ var actualMatch = urlWithQueryRe.exec(url);
2213
+
2214
+ return !!(expectedMatch && actualMatch) &&
2215
+ (expectedMatch[1] === actualMatch[1]) &&
2216
+ (normalizeQuery(expectedMatch[2]) === normalizeQuery(actualMatch[2]));
2217
+ }
2031
2218
 
2032
- return keyObj;
2219
+ function normalizeQuery(queryStr) {
2220
+ return queryStr.split('&').sort().join('&');
2221
+ }
2222
+
2223
+ function extractParamsFromPath(strippedUrl) {
2224
+ var keyObj = {};
2225
+
2226
+ if (!expectedUrl || !angular.isFunction(expectedUrl.test) ||
2227
+ !expectedKeys || !expectedKeys.length) return keyObj;
2228
+
2229
+ var match = expectedUrl.exec(strippedUrl);
2230
+ if (!match) return keyObj;
2231
+
2232
+ for (var i = 1, len = match.length; i < len; ++i) {
2233
+ var key = expectedKeys[i - 1];
2234
+ var val = match[i];
2235
+ if (key && val) {
2236
+ keyObj[key.name || key] = val;
2237
+ }
2033
2238
  }
2034
2239
 
2035
- function parseQuery() {
2036
- var obj = {}, key_value, key,
2037
- queryStr = u.indexOf('?') > -1
2038
- ? u.substring(u.indexOf('?') + 1)
2039
- : '';
2040
-
2041
- angular.forEach(queryStr.split('&'), function(keyValue) {
2042
- if (keyValue) {
2043
- key_value = keyValue.replace(/\+/g,'%20').split('=');
2044
- key = tryDecodeURIComponent(key_value[0]);
2045
- if (angular.isDefined(key)) {
2046
- var val = angular.isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
2047
- if (!hasOwnProperty.call(obj, key)) {
2048
- obj[key] = val;
2049
- } else if (angular.isArray(obj[key])) {
2050
- obj[key].push(val);
2051
- } else {
2052
- obj[key] = [obj[key],val];
2053
- }
2054
- }
2240
+ return keyObj;
2241
+ }
2242
+
2243
+ function extractParamsFromQuery(queryStr) {
2244
+ var obj = {},
2245
+ keyValuePairs = queryStr.split('&').
2246
+ filter(angular.identity). // Ignore empty segments.
2247
+ map(function(keyValue) { return keyValue.replace(/\+/g, '%20').split('='); });
2248
+
2249
+ angular.forEach(keyValuePairs, function(pair) {
2250
+ var key = tryDecodeURIComponent(pair[0]);
2251
+ if (angular.isDefined(key)) {
2252
+ var val = angular.isDefined(pair[1]) ? tryDecodeURIComponent(pair[1]) : true;
2253
+ if (!hasOwnProperty.call(obj, key)) {
2254
+ obj[key] = val;
2255
+ } else if (angular.isArray(obj[key])) {
2256
+ obj[key].push(val);
2257
+ } else {
2258
+ obj[key] = [obj[key], val];
2055
2259
  }
2056
- });
2057
- return obj;
2058
- }
2059
- function tryDecodeURIComponent(value) {
2060
- try {
2061
- return decodeURIComponent(value);
2062
- } catch (e) {
2063
- // Ignore any invalid uri component
2064
2260
  }
2261
+ });
2262
+
2263
+ return obj;
2264
+ }
2265
+
2266
+ function tryDecodeURIComponent(value) {
2267
+ try {
2268
+ return decodeURIComponent(value);
2269
+ } catch (e) {
2270
+ // Ignore any invalid uri component
2065
2271
  }
2066
- };
2272
+ }
2067
2273
  }
2068
2274
 
2069
2275
  function createMockXhr() {
@@ -2097,13 +2303,13 @@ function MockXhr() {
2097
2303
  var header = this.$$respHeaders[name];
2098
2304
  if (header) return header;
2099
2305
 
2100
- name = angular.lowercase(name);
2306
+ name = angular.$$lowercase(name);
2101
2307
  header = this.$$respHeaders[name];
2102
2308
  if (header) return header;
2103
2309
 
2104
2310
  header = undefined;
2105
2311
  angular.forEach(this.$$respHeaders, function(headerVal, headerName) {
2106
- if (!header && angular.lowercase(headerName) === name) header = headerVal;
2312
+ if (!header && angular.$$lowercase(headerName) === name) header = headerVal;
2107
2313
  });
2108
2314
  return header;
2109
2315
  };
@@ -2117,10 +2323,14 @@ function MockXhr() {
2117
2323
  return lines.join('\n');
2118
2324
  };
2119
2325
 
2120
- this.abort = angular.noop;
2326
+ this.abort = function() {
2327
+ if (isFunction(this.onabort)) {
2328
+ this.onabort();
2329
+ }
2330
+ };
2121
2331
 
2122
2332
  // This section simulates the events on a real XHR object (and the upload object)
2123
- // When we are testing $httpBackend (inside the angular project) we make partial use of this
2333
+ // When we are testing $httpBackend (inside the AngularJS project) we make partial use of this
2124
2334
  // but store the events directly ourselves on `$$events`, instead of going through the `addEventListener`
2125
2335
  this.$$events = {};
2126
2336
  this.addEventListener = function(name, listener) {
@@ -2149,39 +2359,86 @@ angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $
2149
2359
  /**
2150
2360
  * @ngdoc method
2151
2361
  * @name $timeout#flush
2362
+ *
2363
+ * @deprecated
2364
+ * sinceVersion="1.7.3"
2365
+ *
2366
+ * This method flushes all types of tasks (not only timeouts), which is unintuitive.
2367
+ * It is recommended to use {@link ngMock.$flushPendingTasks} instead.
2368
+ *
2152
2369
  * @description
2153
2370
  *
2154
2371
  * Flushes the queue of pending tasks.
2155
2372
  *
2373
+ * _This method is essentially an alias of {@link ngMock.$flushPendingTasks}._
2374
+ *
2375
+ * <div class="alert alert-warning">
2376
+ * For historical reasons, this method will also flush non-`$timeout` pending tasks, such as
2377
+ * {@link $q} promises and tasks scheduled via
2378
+ * {@link ng.$rootScope.Scope#$applyAsync $applyAsync} and
2379
+ * {@link ng.$rootScope.Scope#$evalAsync $evalAsync}.
2380
+ * </div>
2381
+ *
2156
2382
  * @param {number=} delay maximum timeout amount to flush up until
2157
2383
  */
2158
2384
  $delegate.flush = function(delay) {
2385
+ // For historical reasons, `$timeout.flush()` flushes all types of pending tasks.
2386
+ // Keep the same behavior for backwards compatibility (and because it doesn't make sense to
2387
+ // selectively flush scheduled events out of order).
2159
2388
  $browser.defer.flush(delay);
2160
2389
  };
2161
2390
 
2162
2391
  /**
2163
2392
  * @ngdoc method
2164
2393
  * @name $timeout#verifyNoPendingTasks
2394
+ *
2395
+ * @deprecated
2396
+ * sinceVersion="1.7.3"
2397
+ *
2398
+ * This method takes all types of tasks (not only timeouts) into account, which is unintuitive.
2399
+ * It is recommended to use {@link ngMock.$verifyNoPendingTasks} instead, which additionally
2400
+ * allows checking for timeouts only (with `$verifyNoPendingTasks('$timeout')`).
2401
+ *
2165
2402
  * @description
2166
2403
  *
2167
- * Verifies that there are no pending tasks that need to be flushed.
2404
+ * Verifies that there are no pending tasks that need to be flushed. It throws an error if there
2405
+ * are still pending tasks.
2406
+ *
2407
+ * _This method is essentially an alias of {@link ngMock.$verifyNoPendingTasks} (called with no
2408
+ * arguments)._
2409
+ *
2410
+ * <div class="alert alert-warning">
2411
+ * <p>
2412
+ * For historical reasons, this method will also verify non-`$timeout` pending tasks, such as
2413
+ * pending {@link $http} requests, in-progress {@link $route} transitions, unresolved
2414
+ * {@link $q} promises and tasks scheduled via
2415
+ * {@link ng.$rootScope.Scope#$applyAsync $applyAsync} and
2416
+ * {@link ng.$rootScope.Scope#$evalAsync $evalAsync}.
2417
+ * </p>
2418
+ * <p>
2419
+ * It is recommended to use {@link ngMock.$verifyNoPendingTasks} instead, which additionally
2420
+ * supports verifying a specific type of tasks. For example, you can verify there are no
2421
+ * pending timeouts with `$verifyNoPendingTasks('$timeout')`.
2422
+ * </p>
2423
+ * </div>
2168
2424
  */
2169
2425
  $delegate.verifyNoPendingTasks = function() {
2170
- if ($browser.deferredFns.length) {
2171
- throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' +
2172
- formatPendingTasksAsString($browser.deferredFns));
2426
+ // For historical reasons, `$timeout.verifyNoPendingTasks()` takes all types of pending tasks
2427
+ // into account. Keep the same behavior for backwards compatibility.
2428
+ var pendingTasks = $browser.defer.getPendingTasks();
2429
+
2430
+ if (pendingTasks.length) {
2431
+ var formattedTasks = $browser.defer.formatPendingTasks(pendingTasks).join('\n ');
2432
+ var hasPendingTimeout = pendingTasks.some(function(task) { return task.type === '$timeout'; });
2433
+ var extraMessage = hasPendingTimeout ? '' : '\n\nNone of the pending tasks are timeouts. ' +
2434
+ 'If you only want to verify pending timeouts, use ' +
2435
+ '`$verifyNoPendingTasks(\'$timeout\')` instead.';
2436
+
2437
+ throw new Error('Deferred tasks to flush (' + pendingTasks.length + '):\n ' +
2438
+ formattedTasks + extraMessage);
2173
2439
  }
2174
2440
  };
2175
2441
 
2176
- function formatPendingTasksAsString(tasks) {
2177
- var result = [];
2178
- angular.forEach(tasks, function(task) {
2179
- result.push('{id: ' + task.id + ', time: ' + task.time + '}');
2180
- });
2181
-
2182
- return result.join(', ');
2183
- }
2184
-
2185
2442
  return $delegate;
2186
2443
  }];
2187
2444
 
@@ -2231,11 +2488,6 @@ angular.mock.$RootElementProvider = function() {
2231
2488
  * A decorator for {@link ng.$controller} with additional `bindings` parameter, useful when testing
2232
2489
  * controllers of directives that use {@link $compile#-bindtocontroller- `bindToController`}.
2233
2490
  *
2234
- * Depending on the value of
2235
- * {@link ng.$compileProvider#preAssignBindingsEnabled `preAssignBindingsEnabled()`}, the properties
2236
- * will be bound before or after invoking the constructor.
2237
- *
2238
- *
2239
2491
  * ## Example
2240
2492
  *
2241
2493
  * ```js
@@ -2281,8 +2533,6 @@ angular.mock.$RootElementProvider = function() {
2281
2533
  *
2282
2534
  * * check if a controller with given name is registered via `$controllerProvider`
2283
2535
  * * check if evaluating the string on the current scope returns a constructor
2284
- * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
2285
- * `window` object (deprecated, not recommended)
2286
2536
  *
2287
2537
  * The string can use the `controller as property` syntax, where the controller instance is published
2288
2538
  * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
@@ -2293,22 +2543,13 @@ angular.mock.$RootElementProvider = function() {
2293
2543
  * the `bindToController` feature and simplify certain kinds of tests.
2294
2544
  * @return {Object} Instance of given controller.
2295
2545
  */
2296
- function createControllerDecorator(compileProvider) {
2546
+ function createControllerDecorator() {
2297
2547
  angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
2298
2548
  return function(expression, locals, later, ident) {
2299
2549
  if (later && typeof later === 'object') {
2300
- var preAssignBindingsEnabled = compileProvider.preAssignBindingsEnabled();
2301
-
2302
2550
  var instantiate = $delegate(expression, locals, true, ident);
2303
- if (preAssignBindingsEnabled) {
2304
- angular.extend(instantiate.instance, later);
2305
- }
2306
-
2307
2551
  var instance = instantiate();
2308
- if (!preAssignBindingsEnabled || instance !== instantiate.instance) {
2309
- angular.extend(instance, later);
2310
- }
2311
-
2552
+ angular.extend(instance, later);
2312
2553
  return instance;
2313
2554
  }
2314
2555
  return $delegate(expression, locals, later, ident);
@@ -2419,14 +2660,16 @@ angular.module('ngMock', ['ng']).provider({
2419
2660
  $log: angular.mock.$LogProvider,
2420
2661
  $interval: angular.mock.$IntervalProvider,
2421
2662
  $rootElement: angular.mock.$RootElementProvider,
2422
- $componentController: angular.mock.$ComponentControllerProvider
2663
+ $componentController: angular.mock.$ComponentControllerProvider,
2664
+ $flushPendingTasks: angular.mock.$FlushPendingTasksProvider,
2665
+ $verifyNoPendingTasks: angular.mock.$VerifyNoPendingTasksProvider
2423
2666
  }).config(['$provide', '$compileProvider', function($provide, $compileProvider) {
2424
2667
  $provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
2425
2668
  $provide.decorator('$$rAF', angular.mock.$RAFDecorator);
2426
2669
  $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
2427
2670
  $provide.decorator('$controller', createControllerDecorator($compileProvider));
2428
2671
  $provide.decorator('$httpBackend', angular.mock.$httpBackendDecorator);
2429
- }]).info({ angularVersion: '1.6.8' });
2672
+ }]).info({ angularVersion: '1.8.0' });
2430
2673
 
2431
2674
  /**
2432
2675
  * @ngdoc module
@@ -2435,13 +2678,13 @@ angular.module('ngMock', ['ng']).provider({
2435
2678
  * @packageName angular-mocks
2436
2679
  * @description
2437
2680
  *
2438
- * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing.
2681
+ * The `ngMockE2E` is an AngularJS module which contains mocks suitable for end-to-end testing.
2439
2682
  * Currently there is only one mock present in this module -
2440
2683
  * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock.
2441
2684
  */
2442
2685
  angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2443
2686
  $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
2444
- }]).info({ angularVersion: '1.6.8' });
2687
+ }]).info({ angularVersion: '1.8.0' });
2445
2688
 
2446
2689
  /**
2447
2690
  * @ngdoc service
@@ -2728,6 +2971,39 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2728
2971
  * control how a matched request is handled. You can save this object for later use and invoke
2729
2972
  * `respond` or `passThrough` again in order to change how a matched request is handled.
2730
2973
  */
2974
+ /**
2975
+ * @ngdoc method
2976
+ * @name $httpBackend#matchLatestDefinitionEnabled
2977
+ * @module ngMockE2E
2978
+ * @description
2979
+ * This method can be used to change which mocked responses `$httpBackend` returns, when defining
2980
+ * them with {@link ngMock.$httpBackend#when $httpBackend.when()} (and shortcut methods).
2981
+ * By default, `$httpBackend` returns the first definition that matches. When setting
2982
+ * `$http.matchLatestDefinitionEnabled(true)`, it will use the last response that matches, i.e. the
2983
+ * one that was added last.
2984
+ *
2985
+ * ```js
2986
+ * hb.when('GET', '/url1').respond(200, 'content', {});
2987
+ * hb.when('GET', '/url1').respond(201, 'another', {});
2988
+ * hb('GET', '/url1'); // receives "content"
2989
+ *
2990
+ * $http.matchLatestDefinitionEnabled(true)
2991
+ * hb('GET', '/url1'); // receives "another"
2992
+ *
2993
+ * hb.when('GET', '/url1').respond(201, 'onemore', {});
2994
+ * hb('GET', '/url1'); // receives "onemore"
2995
+ * ```
2996
+ *
2997
+ * This is useful if a you have a default response that is overriden inside specific tests.
2998
+ *
2999
+ * Note that different from config methods on providers, `matchLatestDefinitionEnabled()` can be changed
3000
+ * even when the application is already running.
3001
+ *
3002
+ * @param {Boolean=} value value to set, either `true` or `false`. Default is `false`.
3003
+ * If omitted, it will return the current value.
3004
+ * @return {$httpBackend|Boolean} self when used as a setter, and the current value when used
3005
+ * as a getter
3006
+ */
2731
3007
  angular.mock.e2e = {};
2732
3008
  angular.mock.e2e.$httpBackendDecorator =
2733
3009
  ['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock];
@@ -3249,6 +3525,9 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
3249
3525
  * - `charcode`: [charCode](https://developer.mozilla.org/docs/Web/API/KeyboardEvent/charcode)
3250
3526
  * for keyboard events (keydown, keypress, and keyup).
3251
3527
  *
3528
+ * - `data`: [data](https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent/data) for
3529
+ * [CompositionEvents](https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent).
3530
+ *
3252
3531
  * - `elapsedTime`: the elapsedTime for
3253
3532
  * [TransitionEvent](https://developer.mozilla.org/docs/Web/API/TransitionEvent)
3254
3533
  * and [AnimationEvent](https://developer.mozilla.org/docs/Web/API/AnimationEvent).
@@ -3353,6 +3632,24 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
3353
3632
  evnt.keyCode = eventData.keyCode;
3354
3633
  evnt.charCode = eventData.charCode;
3355
3634
  evnt.which = eventData.which;
3635
+ } else if (/composition/.test(eventType)) {
3636
+ try {
3637
+ evnt = new window.CompositionEvent(eventType, {
3638
+ data: eventData.data
3639
+ });
3640
+ } catch (e) {
3641
+ // Support: IE9+
3642
+ evnt = window.document.createEvent('CompositionEvent', {});
3643
+ evnt.initCompositionEvent(
3644
+ eventType,
3645
+ eventData.bubbles,
3646
+ eventData.cancelable,
3647
+ window,
3648
+ eventData.data,
3649
+ null
3650
+ );
3651
+ }
3652
+
3356
3653
  } else {
3357
3654
  evnt = window.document.createEvent('MouseEvents');
3358
3655
  x = x || 0;
@@ -3368,30 +3665,11 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
3368
3665
 
3369
3666
  if (!evnt) return;
3370
3667
 
3371
- var originalPreventDefault = evnt.preventDefault,
3372
- appWindow = element.ownerDocument.defaultView,
3373
- fakeProcessDefault = true,
3374
- finalProcessDefault,
3375
- angular = appWindow.angular || {};
3376
-
3377
- // igor: temporary fix for https://bugzilla.mozilla.org/show_bug.cgi?id=684208
3378
- angular['ff-684208-preventDefault'] = false;
3379
- evnt.preventDefault = function() {
3380
- fakeProcessDefault = false;
3381
- return originalPreventDefault.apply(evnt, arguments);
3382
- };
3383
-
3384
3668
  if (!eventData.bubbles || supportsEventBubblingInDetachedTree() || isAttachedToDocument(element)) {
3385
- element.dispatchEvent(evnt);
3669
+ return element.dispatchEvent(evnt);
3386
3670
  } else {
3387
3671
  triggerForPath(element, evnt);
3388
3672
  }
3389
-
3390
- finalProcessDefault = !(angular['ff-684208-preventDefault'] || !fakeProcessDefault);
3391
-
3392
- delete angular['ff-684208-preventDefault'];
3393
-
3394
- return finalProcessDefault;
3395
3673
  };
3396
3674
 
3397
3675
  function supportsTouchEvents() {