angularjs-rails 1.6.8 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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() {