angularjs-rails 1.5.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.5.8
3
- * (c) 2010-2016 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,9 +23,9 @@ 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
- * # Usage
28
+ * ## Usage
29
29
  * The `ngMessages` directive allows keys in a key/value collection to be associated with a child element
30
30
  * (or 'message') that will show or hide based on the truthiness of that key's value in the collection. A common use
31
31
  * case for `ngMessages` is to display error messages for inputs using the `$error` object exposed by the
@@ -69,7 +69,7 @@ var jqLite;
69
69
  * By default, `ngMessages` will only display one message for a particular key/value collection at any time. If more
70
70
  * than one message (or error) key is currently true, then which message is shown is determined by the order of messages
71
71
  * in the HTML template code (messages declared first are prioritised). This mechanism means the developer does not have
72
- * to prioritise messages using custom JavaScript code.
72
+ * to prioritize messages using custom JavaScript code.
73
73
  *
74
74
  * Given the following error object for our example (which informs us that the field `myField` currently has both the
75
75
  * `required` and `email` errors):
@@ -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,16 +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
  })
294
+ .info({ angularVersion: '1.8.0' })
275
295
 
276
296
  /**
277
297
  * @ngdoc directive
@@ -290,8 +310,11 @@ angular.module('ngMessages', [], function initAngularHelpers() {
290
310
  * at a time and this depends on the prioritization of the messages within the template. (This can
291
311
  * be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
292
312
  *
293
- * A remote template can also be used to promote message reusability and messages can also be
294
- * 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.
295
318
  *
296
319
  * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
297
320
  *
@@ -302,6 +325,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
302
325
  * <ANY ng-message="stringValue">...</ANY>
303
326
  * <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
304
327
  * <ANY ng-message-exp="expressionValue">...</ANY>
328
+ * <ANY ng-message-default>...</ANY>
305
329
  * </ANY>
306
330
  *
307
331
  * <!-- or by using element directives -->
@@ -309,10 +333,11 @@ angular.module('ngMessages', [], function initAngularHelpers() {
309
333
  * <ng-message when="stringValue">...</ng-message>
310
334
  * <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
311
335
  * <ng-message when-exp="expressionValue">...</ng-message>
336
+ * <ng-message-default>...</ng-message-default>
312
337
  * </ng-messages>
313
338
  * ```
314
339
  *
315
- * @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
316
341
  * (this is typically the $error object on an ngModel instance).
317
342
  * @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true
318
343
  *
@@ -337,6 +362,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
337
362
  * <div ng-message="required">You did not enter a field</div>
338
363
  * <div ng-message="minlength">Your field is too short</div>
339
364
  * <div ng-message="maxlength">Your field is too long</div>
365
+ * <div ng-message-default>This field has an input error</div>
340
366
  * </div>
341
367
  * </form>
342
368
  * </file>
@@ -352,7 +378,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
352
378
  return {
353
379
  require: 'ngMessages',
354
380
  restrict: 'AE',
355
- controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
381
+ controller: ['$element', '$scope', '$attrs', function NgMessagesCtrl($element, $scope, $attrs) {
356
382
  var ctrl = this;
357
383
  var latestKey = 0;
358
384
  var nextAttachId = 0;
@@ -374,6 +400,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
374
400
 
375
401
  var unmatchedMessages = [];
376
402
  var matchedKeys = {};
403
+ var truthyKeys = 0;
377
404
  var messageItem = ctrl.head;
378
405
  var messageFound = false;
379
406
  var totalMessages = 0;
@@ -386,13 +413,17 @@ angular.module('ngMessages', [], function initAngularHelpers() {
386
413
  var messageUsed = false;
387
414
  if (!messageFound) {
388
415
  forEach(collection, function(value, key) {
389
- if (!messageUsed && truthy(value) && messageCtrl.test(key)) {
390
- // this is to prevent the same error name from showing up twice
391
- if (matchedKeys[key]) return;
392
- matchedKeys[key] = true;
416
+ if (truthy(value) && !messageUsed) {
417
+ truthyKeys++;
418
+
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;
393
423
 
394
- messageUsed = true;
395
- messageCtrl.attach();
424
+ messageUsed = true;
425
+ messageCtrl.attach();
426
+ }
396
427
  }
397
428
  });
398
429
  }
@@ -412,48 +443,60 @@ angular.module('ngMessages', [], function initAngularHelpers() {
412
443
  messageCtrl.detach();
413
444
  });
414
445
 
415
- unmatchedMessages.length !== totalMessages
416
- ? $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS)
417
- : $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
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) {
456
+ $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS);
457
+ } else {
458
+ $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
459
+ }
418
460
  };
419
461
 
420
462
  $scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
421
463
 
422
- // If the element is destroyed, proactively destroy all the currently visible messages
423
- $element.on('$destroy', function() {
424
- forEach(messages, function(item) {
425
- item.message.detach();
426
- });
427
- });
428
-
429
464
  this.reRender = function() {
430
465
  if (!renderLater) {
431
466
  renderLater = true;
432
467
  $scope.$evalAsync(function() {
433
- if (renderLater) {
434
- cachedCollection && ctrl.render(cachedCollection);
468
+ if (renderLater && cachedCollection) {
469
+ ctrl.render(cachedCollection);
435
470
  }
436
471
  });
437
472
  }
438
473
  };
439
474
 
440
- this.register = function(comment, messageCtrl) {
441
- var nextKey = latestKey.toString();
442
- messages[nextKey] = {
443
- message: messageCtrl
444
- };
445
- insertMessageNode($element[0], comment, nextKey);
446
- comment.$$ngMessageNode = nextKey;
447
- 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
+ }
448
487
 
449
488
  ctrl.reRender();
450
489
  };
451
490
 
452
- this.deregister = function(comment) {
453
- var key = comment.$$ngMessageNode;
454
- delete comment.$$ngMessageNode;
455
- removeMessageNode($element[0], comment, key);
456
- 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
+ }
457
500
  ctrl.reRender();
458
501
  };
459
502
 
@@ -500,6 +543,9 @@ angular.module('ngMessages', [], function initAngularHelpers() {
500
543
  function removeMessageNode(parent, comment, key) {
501
544
  var messageNode = messages[key];
502
545
 
546
+ // This message node may have already been removed by a call to deregister()
547
+ if (!messageNode) return;
548
+
503
549
  var match = findPreviousMessage(parent, comment);
504
550
  if (match) {
505
551
  match.next = messageNode.next;
@@ -594,6 +640,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
594
640
  * @name ngMessage
595
641
  * @restrict AE
596
642
  * @scope
643
+ * @priority 1
597
644
  *
598
645
  * @description
599
646
  * `ngMessage` is a directive with the purpose to show and hide a particular message.
@@ -632,10 +679,8 @@ angular.module('ngMessages', [], function initAngularHelpers() {
632
679
  * @scope
633
680
  *
634
681
  * @description
635
- * `ngMessageExp` is a directive with the purpose to show and hide a particular message.
636
- * For `ngMessageExp` to operate, a parent `ngMessages` directive on a parent DOM element
637
- * must be situated since it determines which messages are visible based on the state
638
- * of the provided key/value map that `ngMessages` listens on.
682
+ * `ngMessageExp` is the same as {@link directive:ngMessage `ngMessage`}, but instead of a static
683
+ * value, it accepts an expression to be evaluated for the message key.
639
684
  *
640
685
  * @usage
641
686
  * ```html
@@ -654,9 +699,41 @@ angular.module('ngMessages', [], function initAngularHelpers() {
654
699
  *
655
700
  * @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key.
656
701
  */
657
- .directive('ngMessageExp', ngMessageDirectiveFactory());
702
+ .directive('ngMessageExp', ngMessageDirectiveFactory())
658
703
 
659
- 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) {
660
737
  return ['$animate', function($animate) {
661
738
  return {
662
739
  restrict: 'AE',
@@ -665,25 +742,28 @@ function ngMessageDirectiveFactory() {
665
742
  terminal: true,
666
743
  require: '^^ngMessages',
667
744
  link: function(scope, element, attrs, ngMessagesCtrl, $transclude) {
668
- var commentNode = element[0];
669
-
670
- var records;
671
- var staticExp = attrs.ngMessage || attrs.when;
672
- var dynamicExp = attrs.ngMessageExp || attrs.whenExp;
673
- var assignRecords = function(items) {
674
- records = items
675
- ? (isArray(items)
676
- ? items
677
- : items.split(/[\s,]+/))
678
- : null;
679
- ngMessagesCtrl.reRender();
680
- };
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
+ };
681
760
 
682
- if (dynamicExp) {
683
- assignRecords(scope.$eval(dynamicExp));
684
- scope.$watchCollection(dynamicExp, assignRecords);
685
- } else {
686
- assignRecords(staticExp);
761
+ if (dynamicExp) {
762
+ assignRecords(scope.$eval(dynamicExp));
763
+ scope.$watchCollection(dynamicExp, assignRecords);
764
+ } else {
765
+ assignRecords(staticExp);
766
+ }
687
767
  }
688
768
 
689
769
  var currentElement, messageCtrl;
@@ -705,8 +785,10 @@ function ngMessageDirectiveFactory() {
705
785
  // by another structural directive then it's time
706
786
  // to deregister the message from the controller
707
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.
708
790
  if (currentElement && currentElement.$$attachId === $$attachId) {
709
- ngMessagesCtrl.deregister(commentNode);
791
+ ngMessagesCtrl.deregister(commentNode, isDefault);
710
792
  messageCtrl.detach();
711
793
  }
712
794
  newScope.$destroy();
@@ -721,6 +803,14 @@ function ngMessageDirectiveFactory() {
721
803
  $animate.leave(elm);
722
804
  }
723
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);
724
814
  });
725
815
  }
726
816
  };
@@ -1,12 +1,61 @@
1
1
  /**
2
- * @license AngularJS v1.5.8
3
- * (c) 2010-2016 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,23 +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
- self.$$url = "http://server/";
96
+ self.$$url = 'http://server/';
44
97
  self.$$lastUrl = self.$$url; // used by url polling fn
45
98
  self.pollFns = [];
46
99
 
47
- // TODO(vojta): remove this temporary api
48
- self.$$completeOutstandingRequest = angular.noop;
49
- self.$$incOutstandingRequestCount = angular.noop;
50
-
100
+ // Task-tracking API
101
+ self.$$completeOutstandingRequest = taskTracker.completeTask;
102
+ self.$$incOutstandingRequestCount = taskTracker.incTaskCount;
103
+ self.notifyWhenNoOutstandingRequests = taskTracker.notifyWhenNoPendingTasks;
51
104
 
52
105
  // register url polling fn
53
106
 
@@ -71,11 +124,22 @@ angular.mock.$Browser = function() {
71
124
  self.deferredFns = [];
72
125
  self.deferredNextId = 0;
73
126
 
74
- self.defer = function(fn, delay) {
127
+ self.defer = function(fn, delay, taskType) {
128
+ var timeoutId = self.deferredNextId++;
129
+
75
130
  delay = delay || 0;
76
- self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId});
77
- self.deferredFns.sort(function(a, b) { return a.time - b.time;});
78
- return self.deferredNextId++;
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;
79
143
  };
80
144
 
81
145
 
@@ -89,14 +153,15 @@ angular.mock.$Browser = function() {
89
153
 
90
154
 
91
155
  self.defer.cancel = function(deferId) {
92
- var fnIndex;
156
+ var taskIndex;
93
157
 
94
- angular.forEach(self.deferredFns, function(fn, index) {
95
- if (fn.id === deferId) fnIndex = index;
158
+ angular.forEach(self.deferredFns, function(task, index) {
159
+ if (task.id === deferId) taskIndex = index;
96
160
  });
97
161
 
98
- if (angular.isDefined(fnIndex)) {
99
- 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);
100
165
  return true;
101
166
  }
102
167
 
@@ -110,6 +175,8 @@ angular.mock.$Browser = function() {
110
175
  * @description
111
176
  * Flushes all pending requests and executes the defer callbacks.
112
177
  *
178
+ * See {@link ngMock.$flushPendingsTasks} for more info.
179
+ *
113
180
  * @param {number=} number of milliseconds to flush. See {@link #defer.now}
114
181
  */
115
182
  self.defer.flush = function(delay) {
@@ -118,26 +185,76 @@ angular.mock.$Browser = function() {
118
185
  if (angular.isDefined(delay)) {
119
186
  // A delay was passed so compute the next time
120
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;
121
191
  } else {
122
- if (self.deferredFns.length) {
123
- // No delay was passed so set the next time so that it clears the deferred queue
124
- nextTime = self.deferredFns[self.deferredFns.length - 1].time;
125
- } else {
126
- // No delay passed, but there are no deferred tasks so flush - indicates an error!
127
- throw new Error('No deferred tasks to be flushed');
128
- }
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');
129
194
  }
130
195
 
131
196
  while (self.deferredFns.length && self.deferredFns[0].time <= nextTime) {
132
197
  // Increment the time and call the next deferred function
133
198
  self.defer.now = self.deferredFns[0].time;
134
- self.deferredFns.shift().fn();
199
+ var task = self.deferredFns.shift();
200
+ taskTracker.completeTask(task.fn, task.type);
135
201
  }
136
202
 
137
203
  // Ensure that the current time is correct
138
204
  self.defer.now = nextTime;
139
205
  };
140
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
+
141
258
  self.$$baseHref = '/';
142
259
  self.baseHref = function() {
143
260
  return this.$$baseHref;
@@ -162,7 +279,8 @@ angular.mock.$Browser.prototype = {
162
279
  state = null;
163
280
  }
164
281
  if (url) {
165
- this.$$url = url;
282
+ // The `$browser` service trims empty hashes; simulate it.
283
+ this.$$url = url.replace(/#$/, '');
166
284
  // Native pushState serializes & copies the object; simulate it.
167
285
  this.$$state = angular.copy(state);
168
286
  return this;
@@ -173,13 +291,85 @@ angular.mock.$Browser.prototype = {
173
291
 
174
292
  state: function() {
175
293
  return this.$$state;
176
- },
177
-
178
- notifyWhenNoOutstandingRequests: function(fn) {
179
- fn();
180
294
  }
181
295
  };
182
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
+ };
183
373
 
184
374
  /**
185
375
  * @ngdoc provider
@@ -252,19 +442,19 @@ angular.mock.$ExceptionHandlerProvider = function() {
252
442
  case 'rethrow':
253
443
  var errors = [];
254
444
  handler = function(e) {
255
- if (arguments.length == 1) {
445
+ if (arguments.length === 1) {
256
446
  errors.push(e);
257
447
  } else {
258
448
  errors.push([].slice.call(arguments, 0));
259
449
  }
260
- if (mode === "rethrow") {
450
+ if (mode === 'rethrow') {
261
451
  throw e;
262
452
  }
263
453
  };
264
454
  handler.errors = errors;
265
455
  break;
266
456
  default:
267
- throw new Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!");
457
+ throw new Error('Unknown mode \'' + mode + '\', only \'log\'/\'rethrow\' modes are allowed!');
268
458
  }
269
459
  };
270
460
 
@@ -414,8 +604,8 @@ angular.mock.$LogProvider = function() {
414
604
  });
415
605
  });
416
606
  if (errors.length) {
417
- errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or " +
418
- "an expected log message was not checked and removed:");
607
+ errors.unshift('Expected $log to be empty! Either a message was logged unexpectedly, or ' +
608
+ 'an expected log message was not checked and removed:');
419
609
  errors.push('');
420
610
  throw new Error(errors.join('\n---------\n'));
421
611
  }
@@ -448,62 +638,40 @@ angular.mock.$LogProvider = function() {
448
638
  * @returns {promise} A promise which will be notified on each iteration.
449
639
  */
450
640
  angular.mock.$IntervalProvider = function() {
451
- this.$get = ['$browser', '$rootScope', '$q', '$$q',
452
- function($browser, $rootScope, $q, $$q) {
641
+ this.$get = ['$browser', '$$intervalFactory',
642
+ function($browser, $$intervalFactory) {
453
643
  var repeatFns = [],
454
644
  nextRepeatId = 0,
455
- now = 0;
456
-
457
- var $interval = function(fn, delay, count, invokeApply) {
458
- var hasParams = arguments.length > 4,
459
- args = hasParams ? Array.prototype.slice.call(arguments, 4) : [],
460
- iteration = 0,
461
- skipApply = (angular.isDefined(invokeApply) && !invokeApply),
462
- deferred = (skipApply ? $$q : $q).defer(),
463
- promise = deferred.promise;
464
-
465
- count = (angular.isDefined(count)) ? count : 0;
466
- promise.then(null, null, (!hasParams) ? fn : function() {
467
- fn.apply(null, args);
468
- });
469
-
470
- promise.$$intervalId = nextRepeatId;
471
-
472
- function tick() {
473
- deferred.notify(iteration++);
474
-
475
- if (count > 0 && iteration >= count) {
476
- var fnIndex;
477
- deferred.resolve(iteration);
478
-
479
- angular.forEach(repeatFns, function(fn, index) {
480
- 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
481
659
  });
660
+ repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime; });
482
661
 
483
- if (angular.isDefined(fnIndex)) {
484
- 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
+ }
485
670
  }
486
- }
487
-
488
- if (skipApply) {
489
- $browser.defer.flush();
490
- } else {
491
- $rootScope.$apply();
492
- }
493
- }
671
+ };
494
672
 
495
- repeatFns.push({
496
- nextTime:(now + delay),
497
- delay: delay,
498
- fn: tick,
499
- id: nextRepeatId,
500
- deferred: deferred
501
- });
502
- repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;});
673
+ var $interval = $$intervalFactory(setIntervalFn, clearIntervalFn);
503
674
 
504
- nextRepeatId++;
505
- return promise;
506
- };
507
675
  /**
508
676
  * @ngdoc method
509
677
  * @name $interval#cancel
@@ -516,16 +684,15 @@ angular.mock.$IntervalProvider = function() {
516
684
  */
517
685
  $interval.cancel = function(promise) {
518
686
  if (!promise) return false;
519
- var fnIndex;
520
-
521
- angular.forEach(repeatFns, function(fn, index) {
522
- if (fn.id === promise.$$intervalId) fnIndex = index;
523
- });
524
687
 
525
- if (angular.isDefined(fnIndex)) {
526
- repeatFns[fnIndex].deferred.reject('canceled');
527
- repeatFns.splice(fnIndex, 1);
528
- 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
+ }
529
696
  }
530
697
 
531
698
  return false;
@@ -538,15 +705,21 @@ angular.mock.$IntervalProvider = function() {
538
705
  *
539
706
  * Runs interval tasks scheduled to be run in the next `millis` milliseconds.
540
707
  *
541
- * @param {number=} millis maximum timeout amount to flush up until.
708
+ * @param {number} millis maximum timeout amount to flush up until.
542
709
  *
543
710
  * @return {number} The amount of time moved forward.
544
711
  */
545
712
  $interval.flush = function(millis) {
713
+ var before = now;
546
714
  now += millis;
547
715
  while (repeatFns.length && repeatFns[0].nextTime <= now) {
548
716
  var task = repeatFns[0];
549
717
  task.fn();
718
+ if (task.nextTime === before) {
719
+ // this can only happen the first time
720
+ // a zero-delay interval gets triggered
721
+ task.nextTime++;
722
+ }
550
723
  task.nextTime += task.delay;
551
724
  repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;});
552
725
  }
@@ -558,16 +731,13 @@ angular.mock.$IntervalProvider = function() {
558
731
  };
559
732
 
560
733
 
561
- /* jshint -W101 */
562
- /* The R_ISO8061_STR regex is never going to fit into the 100 char limit!
563
- * This directive should go inside the anonymous function but a bug in JSHint means that it would
564
- * not be enacted early enough to prevent the warning.
565
- */
566
- var R_ISO8061_STR = /^(-?\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
567
-
568
734
  function jsonStringToDate(string) {
735
+ // The R_ISO8061_STR regex is never going to fit into the 100 char limit!
736
+ // eslit-disable-next-line max-len
737
+ var R_ISO8061_STR = /^(-?\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
738
+
569
739
  var match;
570
- if (match = string.match(R_ISO8061_STR)) {
740
+ if ((match = string.match(R_ISO8061_STR))) {
571
741
  var date = new Date(0),
572
742
  tzHour = 0,
573
743
  tzMin = 0;
@@ -650,9 +820,10 @@ angular.mock.TzDate = function(offset, timestamp) {
650
820
 
651
821
  timestamp = self.origDate.getTime();
652
822
  if (isNaN(timestamp)) {
823
+ // eslint-disable-next-line no-throw-literal
653
824
  throw {
654
- name: "Illegal Argument",
655
- message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
825
+ name: 'Illegal Argument',
826
+ message: 'Arg \'' + tsStr + '\' passed into TzDate constructor is not a valid date string'
656
827
  };
657
828
  }
658
829
  } else {
@@ -758,7 +929,7 @@ angular.mock.TzDate = function(offset, timestamp) {
758
929
 
759
930
  angular.forEach(unimplementedMethods, function(methodName) {
760
931
  self[methodName] = function() {
761
- throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock");
932
+ throw new Error('Method \'' + methodName + '\' is not implemented in the TzDate mock');
762
933
  };
763
934
  });
764
935
 
@@ -767,7 +938,6 @@ angular.mock.TzDate = function(offset, timestamp) {
767
938
 
768
939
  //make "tzDateInstance instanceof Date" return true
769
940
  angular.mock.TzDate.prototype = Date.prototype;
770
- /* jshint +W101 */
771
941
 
772
942
 
773
943
  /**
@@ -781,6 +951,7 @@ angular.mock.TzDate.prototype = Date.prototype;
781
951
  * You need to require the `ngAnimateMock` module in your test suite for instance `beforeEach(module('ngAnimateMock'))`
782
952
  */
783
953
  angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
954
+ .info({ angularVersion: '1.8.0' })
784
955
 
785
956
  .config(['$provide', function($provide) {
786
957
 
@@ -946,7 +1117,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
946
1117
  *
947
1118
  * *NOTE*: This is not an injectable instance, just a globally available function.
948
1119
  *
949
- * Method for serializing common angular objects (scope, elements, etc..) into strings.
1120
+ * Method for serializing common AngularJS objects (scope, elements, etc..) into strings.
950
1121
  * It is useful for logging objects to the console when debugging.
951
1122
  *
952
1123
  * @param {*} object - any object to turn into string.
@@ -1028,7 +1199,7 @@ angular.mock.dump = function(object) {
1028
1199
  * This mock implementation can be used to respond with static or dynamic responses via the
1029
1200
  * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc).
1030
1201
  *
1031
- * 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
1032
1203
  * sends the request to a real server using $httpBackend service. With dependency injection, it is
1033
1204
  * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify
1034
1205
  * the requests and respond with some testing data without sending a request to a real server.
@@ -1123,6 +1294,8 @@ angular.mock.dump = function(object) {
1123
1294
  $http.get('/auth.py').then(function(response) {
1124
1295
  authToken = response.headers('A-Token');
1125
1296
  $scope.user = response.data;
1297
+ }).catch(function() {
1298
+ $scope.status = 'Failed...';
1126
1299
  });
1127
1300
 
1128
1301
  $scope.saveMessage = function(message) {
@@ -1215,7 +1388,7 @@ angular.mock.dump = function(object) {
1215
1388
  $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
1216
1389
  // check if the header was sent, if it wasn't the expectation won't
1217
1390
  // match the request and the test will fail
1218
- return headers['Authorization'] == 'xxx';
1391
+ return headers['Authorization'] === 'xxx';
1219
1392
  }).respond(201, '');
1220
1393
 
1221
1394
  $rootScope.saveMessage('whatever');
@@ -1264,7 +1437,7 @@ angular.mock.dump = function(object) {
1264
1437
  * ## Matching route requests
1265
1438
  *
1266
1439
  * For extra convenience, `whenRoute` and `expectRoute` shortcuts are available. These methods offer colon
1267
- * 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
1268
1441
  * similar to how application routes are configured with `$routeProvider`. Because these methods convert
1269
1442
  * the definition url to regex, declaration order is important. Combined with query parameter parsing,
1270
1443
  * the following is possible:
@@ -1304,9 +1477,8 @@ angular.mock.dump = function(object) {
1304
1477
  });
1305
1478
  ```
1306
1479
  */
1307
- angular.mock.$HttpBackendProvider = function() {
1308
- this.$get = ['$rootScope', '$timeout', createHttpBackendMock];
1309
- };
1480
+ angular.mock.$httpBackendDecorator =
1481
+ ['$rootScope', '$timeout', '$delegate', createHttpBackendMock];
1310
1482
 
1311
1483
  /**
1312
1484
  * General factory function for $httpBackend mock.
@@ -1325,17 +1497,21 @@ angular.mock.$HttpBackendProvider = function() {
1325
1497
  function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1326
1498
  var definitions = [],
1327
1499
  expectations = [],
1500
+ matchLatestDefinition = false,
1328
1501
  responses = [],
1329
1502
  responsesPush = angular.bind(responses, responses.push),
1330
- copy = angular.copy;
1503
+ copy = angular.copy,
1504
+ // We cache the original backend so that if both ngMock and ngMockE2E override the
1505
+ // service the ngMockE2E version can pass through to the real backend
1506
+ originalHttpBackend = $delegate.$$originalHttpBackend || $delegate;
1331
1507
 
1332
1508
  function createResponse(status, data, headers, statusText) {
1333
1509
  if (angular.isFunction(status)) return status;
1334
1510
 
1335
1511
  return function() {
1336
1512
  return angular.isNumber(status)
1337
- ? [status, data, headers, statusText]
1338
- : [200, status, data, headers];
1513
+ ? [status, data, headers, statusText, 'complete']
1514
+ : [200, status, data, headers, 'complete'];
1339
1515
  };
1340
1516
  }
1341
1517
 
@@ -1357,39 +1533,57 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1357
1533
 
1358
1534
  function wrapResponse(wrapped) {
1359
1535
  if (!$browser && timeout) {
1360
- timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout);
1536
+ if (timeout.then) {
1537
+ timeout.then(function() {
1538
+ handlePrematureEnd(angular.isDefined(timeout.$$timeoutId) ? 'timeout' : 'abort');
1539
+ });
1540
+ } else {
1541
+ $timeout(function() {
1542
+ handlePrematureEnd('timeout');
1543
+ }, timeout);
1544
+ }
1361
1545
  }
1362
1546
 
1547
+ handleResponse.description = method + ' ' + url;
1363
1548
  return handleResponse;
1364
1549
 
1365
1550
  function handleResponse() {
1366
1551
  var response = wrapped.response(method, url, data, headers, wrapped.params(url));
1367
1552
  xhr.$$respHeaders = response[2];
1368
1553
  callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(),
1369
- copy(response[3] || ''));
1554
+ copy(response[3] || ''), copy(response[4]));
1370
1555
  }
1371
1556
 
1372
- function handleTimeout() {
1557
+ function handlePrematureEnd(reason) {
1373
1558
  for (var i = 0, ii = responses.length; i < ii; i++) {
1374
1559
  if (responses[i] === handleResponse) {
1375
1560
  responses.splice(i, 1);
1376
- callback(-1, undefined, '');
1561
+ callback(-1, undefined, '', undefined, reason);
1377
1562
  break;
1378
1563
  }
1379
1564
  }
1380
1565
  }
1381
1566
  }
1382
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
+
1383
1576
  if (expectation && expectation.match(method, url)) {
1384
1577
  if (!expectation.matchData(data)) {
1385
- throw new Error('Expected ' + expectation + ' with different data\n' +
1386
- 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
1578
+ throw createFatalError('Expected ' + expectation + ' with different data\n' +
1579
+ 'EXPECTED: ' + prettyPrint(expectation.data) + '\n' +
1580
+ 'GOT: ' + data);
1387
1581
  }
1388
1582
 
1389
1583
  if (!expectation.matchHeaders(headers)) {
1390
- throw new Error('Expected ' + expectation + ' with different headers\n' +
1391
- 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' +
1392
- prettyPrint(headers));
1584
+ throw createFatalError('Expected ' + expectation + ' with different headers\n' +
1585
+ 'EXPECTED: ' + prettyPrint(expectation.headers) + '\n' +
1586
+ 'GOT: ' + prettyPrint(headers));
1393
1587
  }
1394
1588
 
1395
1589
  expectations.shift();
@@ -1401,22 +1595,26 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1401
1595
  wasExpected = true;
1402
1596
  }
1403
1597
 
1404
- var i = -1, definition;
1405
- while ((definition = definitions[++i])) {
1598
+ var i = matchLatestDefinition ? definitions.length : -1, definition;
1599
+
1600
+ while ((definition = definitions[matchLatestDefinition ? --i : ++i])) {
1406
1601
  if (definition.match(method, url, data, headers || {})) {
1407
1602
  if (definition.response) {
1408
1603
  // if $browser specified, we do auto flush all requests
1409
1604
  ($browser ? $browser.defer : responsesPush)(wrapResponse(definition));
1410
1605
  } else if (definition.passThrough) {
1411
- $delegate(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers);
1412
- } else throw new Error('No response defined !');
1606
+ originalHttpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers);
1607
+ } else throw createFatalError('No response defined !');
1413
1608
  return;
1414
1609
  }
1415
1610
  }
1416
- throw wasExpected ?
1417
- new Error('No response defined !') :
1418
- new Error('Unexpected request: ' + method + ' ' + url + '\n' +
1419
- (expectation ? 'Expected ' + expectation : 'No more request expected'));
1611
+
1612
+ if (wasExpected) {
1613
+ throw createFatalError('No response defined !');
1614
+ }
1615
+
1616
+ throw createFatalError('Unexpected request: ' + method + ' ' + url + '\n' +
1617
+ (expectation ? 'Expected ' + expectation : 'No more request expected'));
1420
1618
  }
1421
1619
 
1422
1620
  /**
@@ -1426,7 +1624,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1426
1624
  * Creates a new backend definition.
1427
1625
  *
1428
1626
  * @param {string} method HTTP method.
1429
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1627
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1430
1628
  * and returns true if the url matches the current definition.
1431
1629
  * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
1432
1630
  * data string and returns true if the data is as expected.
@@ -1444,10 +1642,14 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1444
1642
  * ```
1445
1643
  * – The respond method takes a set of static data to be returned or a function that can
1446
1644
  * return an array containing response status (number), response data (Array|Object|string),
1447
- * response headers (Object), and the text for the status (string). The respond method returns
1448
- * 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.
1449
1648
  */
1450
1649
  $httpBackend.when = function(method, url, data, headers, keys) {
1650
+
1651
+ assertArgDefined(arguments, 1, 'url');
1652
+
1451
1653
  var definition = new MockHttpExpectation(method, url, data, headers, keys),
1452
1654
  chain = {
1453
1655
  respond: function(status, data, headers, statusText) {
@@ -1469,15 +1671,57 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1469
1671
  return chain;
1470
1672
  };
1471
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
+
1472
1715
  /**
1473
1716
  * @ngdoc method
1474
1717
  * @name $httpBackend#whenGET
1475
1718
  * @description
1476
1719
  * Creates a new backend definition for GET requests. For more info see `when()`.
1477
1720
  *
1478
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1721
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1479
1722
  * and returns true if the url matches the current definition.
1480
- * @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.
1481
1725
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1482
1726
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1483
1727
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1490,9 +1734,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1490
1734
  * @description
1491
1735
  * Creates a new backend definition for HEAD requests. For more info see `when()`.
1492
1736
  *
1493
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1737
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1494
1738
  * and returns true if the url matches the current definition.
1495
- * @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.
1496
1741
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1497
1742
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1498
1743
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1505,9 +1750,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1505
1750
  * @description
1506
1751
  * Creates a new backend definition for DELETE requests. For more info see `when()`.
1507
1752
  *
1508
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1753
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1509
1754
  * and returns true if the url matches the current definition.
1510
- * @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.
1511
1757
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1512
1758
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1513
1759
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1520,11 +1766,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1520
1766
  * @description
1521
1767
  * Creates a new backend definition for POST requests. For more info see `when()`.
1522
1768
  *
1523
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1769
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1524
1770
  * and returns true if the url matches the current definition.
1525
1771
  * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
1526
1772
  * data string and returns true if the data is as expected.
1527
- * @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.
1528
1775
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1529
1776
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1530
1777
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1537,11 +1784,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1537
1784
  * @description
1538
1785
  * Creates a new backend definition for PUT requests. For more info see `when()`.
1539
1786
  *
1540
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1787
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1541
1788
  * and returns true if the url matches the current definition.
1542
1789
  * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
1543
1790
  * data string and returns true if the data is as expected.
1544
- * @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.
1545
1793
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1546
1794
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1547
1795
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1554,7 +1802,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1554
1802
  * @description
1555
1803
  * Creates a new backend definition for JSONP requests. For more info see `when()`.
1556
1804
  *
1557
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1805
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1558
1806
  * and returns true if the url matches the current definition.
1559
1807
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1560
1808
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
@@ -1573,42 +1821,13 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1573
1821
  * @param {string} url HTTP url string that supports colon param matching.
1574
1822
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1575
1823
  * request is handled. You can save this object for later use and invoke `respond` again in
1576
- * 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.
1577
1826
  */
1578
1827
  $httpBackend.whenRoute = function(method, url) {
1579
- var pathObj = parseRoute(url);
1580
- return $httpBackend.when(method, pathObj.regexp, undefined, undefined, pathObj.keys);
1581
- };
1582
-
1583
- function parseRoute(url) {
1584
- var ret = {
1585
- regexp: url
1586
- },
1587
- keys = ret.keys = [];
1588
-
1589
- if (!url || !angular.isString(url)) return ret;
1590
-
1591
- url = url
1592
- .replace(/([().])/g, '\\$1')
1593
- .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) {
1594
- var optional = option === '?' ? option : null;
1595
- var star = option === '*' ? option : null;
1596
- keys.push({ name: key, optional: !!optional });
1597
- slash = slash || '';
1598
- return ''
1599
- + (optional ? '' : slash)
1600
- + '(?:'
1601
- + (optional ? slash : '')
1602
- + (star && '(.+?)' || '([^/]+)')
1603
- + (optional || '')
1604
- + ')'
1605
- + (optional || '');
1606
- })
1607
- .replace(/([\/$\*])/g, '\\$1');
1608
-
1609
- ret.regexp = new RegExp('^' + url, 'i');
1610
- return ret;
1611
- }
1828
+ var parsed = parseRouteUrl(url);
1829
+ return $httpBackend.when(method, parsed.regexp, undefined, undefined, parsed.keys);
1830
+ };
1612
1831
 
1613
1832
  /**
1614
1833
  * @ngdoc method
@@ -1617,7 +1836,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1617
1836
  * Creates a new request expectation.
1618
1837
  *
1619
1838
  * @param {string} method HTTP method.
1620
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1839
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1621
1840
  * and returns true if the url matches the current definition.
1622
1841
  * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
1623
1842
  * receives data string and returns true if the data is as expected, or Object if request body
@@ -1630,16 +1849,20 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1630
1849
  * order to change how a matched request is handled.
1631
1850
  *
1632
1851
  * - respond –
1633
- * ```
1634
- * { function([status,] data[, headers, statusText])
1635
- * | function(function(method, url, data, headers, params)}
1636
- * ```
1852
+ * ```js
1853
+ * {function([status,] data[, headers, statusText])
1854
+ * | function(function(method, url, data, headers, params)}
1855
+ * ```
1637
1856
  * – The respond method takes a set of static data to be returned or a function that can
1638
1857
  * return an array containing response status (number), response data (Array|Object|string),
1639
- * response headers (Object), and the text for the status (string). The respond method returns
1640
- * 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.
1641
1861
  */
1642
1862
  $httpBackend.expect = function(method, url, data, headers, keys) {
1863
+
1864
+ assertArgDefined(arguments, 1, 'url');
1865
+
1643
1866
  var expectation = new MockHttpExpectation(method, url, data, headers, keys),
1644
1867
  chain = {
1645
1868
  respond: function(status, data, headers, statusText) {
@@ -1658,9 +1881,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1658
1881
  * @description
1659
1882
  * Creates a new request expectation for GET requests. For more info see `expect()`.
1660
1883
  *
1661
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1662
- * and returns true if the url matches the current definition.
1663
- * @param {Object=} headers HTTP headers.
1884
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
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.
1664
1888
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1665
1889
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1666
1890
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1673,9 +1897,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1673
1897
  * @description
1674
1898
  * Creates a new request expectation for HEAD requests. For more info see `expect()`.
1675
1899
  *
1676
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1677
- * and returns true if the url matches the current definition.
1678
- * @param {Object=} headers HTTP headers.
1900
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
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.
1679
1904
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1680
1905
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1681
1906
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1688,9 +1913,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1688
1913
  * @description
1689
1914
  * Creates a new request expectation for DELETE requests. For more info see `expect()`.
1690
1915
  *
1691
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1692
- * and returns true if the url matches the current definition.
1693
- * @param {Object=} headers HTTP headers.
1916
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
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.
1694
1920
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1695
1921
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1696
1922
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1703,12 +1929,13 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1703
1929
  * @description
1704
1930
  * Creates a new request expectation for POST requests. For more info see `expect()`.
1705
1931
  *
1706
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1707
- * and returns true if the url matches the current definition.
1932
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1933
+ * and returns true if the url matches the current expectation.
1708
1934
  * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
1709
1935
  * receives data string and returns true if the data is as expected, or Object if request body
1710
1936
  * is in JSON format.
1711
- * @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.
1712
1939
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1713
1940
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1714
1941
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1721,12 +1948,13 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1721
1948
  * @description
1722
1949
  * Creates a new request expectation for PUT requests. For more info see `expect()`.
1723
1950
  *
1724
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1725
- * and returns true if the url matches the current definition.
1951
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1952
+ * and returns true if the url matches the current expectation.
1726
1953
  * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
1727
1954
  * receives data string and returns true if the data is as expected, or Object if request body
1728
1955
  * is in JSON format.
1729
- * @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.
1730
1958
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1731
1959
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1732
1960
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1739,12 +1967,13 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1739
1967
  * @description
1740
1968
  * Creates a new request expectation for PATCH requests. For more info see `expect()`.
1741
1969
  *
1742
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
1743
- * and returns true if the url matches the current definition.
1970
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
1971
+ * and returns true if the url matches the current expectation.
1744
1972
  * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
1745
1973
  * receives data string and returns true if the data is as expected, or Object if request body
1746
1974
  * is in JSON format.
1747
- * @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.
1748
1977
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1749
1978
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1750
1979
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1757,8 +1986,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1757
1986
  * @description
1758
1987
  * Creates a new request expectation for JSONP requests. For more info see `expect()`.
1759
1988
  *
1760
- * @param {string|RegExp|function(string)} url HTTP url or function that receives an url
1761
- * and returns true if the url matches the current definition.
1989
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives an url
1990
+ * and returns true if the url matches the current expectation.
1762
1991
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above.
1763
1992
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1764
1993
  * request is handled. You can save this object for later use and invoke `respond` again in
@@ -1776,11 +2005,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1776
2005
  * @param {string} url HTTP url string that supports colon param matching.
1777
2006
  * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
1778
2007
  * request is handled. You can save this object for later use and invoke `respond` again in
1779
- * 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.
1780
2010
  */
1781
2011
  $httpBackend.expectRoute = function(method, url) {
1782
- var pathObj = parseRoute(url);
1783
- 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);
1784
2014
  };
1785
2015
 
1786
2016
 
@@ -1788,24 +2018,34 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1788
2018
  * @ngdoc method
1789
2019
  * @name $httpBackend#flush
1790
2020
  * @description
1791
- * Flushes all pending requests using the trained responses.
1792
- *
1793
- * @param {number=} count Number of responses to flush (in the order they arrived). If undefined,
1794
- * all pending requests will be flushed. If there are no pending requests when the flush method
1795
- * is called an exception is thrown (as this typically a sign of programming error).
2021
+ * Flushes pending requests using the trained responses. Requests are flushed in the order they
2022
+ * were made, but it is also possible to skip one or more requests (for example to have them
2023
+ * flushed later). This is useful for simulating scenarios where responses arrive from the server
2024
+ * in any order.
2025
+ *
2026
+ * If there are no pending requests to flush when the method is called, an exception is thrown (as
2027
+ * this is typically a sign of programming error).
2028
+ *
2029
+ * @param {number=} count - Number of responses to flush. If undefined/null, all pending requests
2030
+ * (starting after `skip`) will be flushed.
2031
+ * @param {number=} [skip=0] - Number of pending requests to skip. For example, a value of `5`
2032
+ * would skip the first 5 pending requests and start flushing from the 6th onwards.
1796
2033
  */
1797
- $httpBackend.flush = function(count, digest) {
2034
+ $httpBackend.flush = function(count, skip, digest) {
1798
2035
  if (digest !== false) $rootScope.$digest();
1799
- if (!responses.length) throw new Error('No pending request to flush !');
2036
+
2037
+ skip = skip || 0;
2038
+ if (skip >= responses.length) throw new Error('No pending request to flush !');
1800
2039
 
1801
2040
  if (angular.isDefined(count) && count !== null) {
1802
2041
  while (count--) {
1803
- if (!responses.length) throw new Error('No more pending request to flush !');
1804
- responses.shift()();
2042
+ var part = responses.splice(skip, 1);
2043
+ if (!part.length) throw new Error('No more pending request to flush !');
2044
+ part[0]();
1805
2045
  }
1806
2046
  } else {
1807
- while (responses.length) {
1808
- responses.shift()();
2047
+ while (responses.length > skip) {
2048
+ responses.splice(skip, 1)[0]();
1809
2049
  }
1810
2050
  }
1811
2051
  $httpBackend.verifyNoOutstandingExpectation(digest);
@@ -1847,9 +2087,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1847
2087
  * afterEach($httpBackend.verifyNoOutstandingRequest);
1848
2088
  * ```
1849
2089
  */
1850
- $httpBackend.verifyNoOutstandingRequest = function() {
2090
+ $httpBackend.verifyNoOutstandingRequest = function(digest) {
2091
+ if (digest !== false) $rootScope.$digest();
1851
2092
  if (responses.length) {
1852
- throw new Error('Unflushed requests: ' + responses.length);
2093
+ var unflushedDescriptions = responses.map(function(res) { return res.description; });
2094
+ throw new Error('Unflushed requests: ' + responses.length + '\n ' +
2095
+ unflushedDescriptions.join('\n '));
1853
2096
  }
1854
2097
  };
1855
2098
 
@@ -1867,125 +2110,166 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
1867
2110
  responses.length = 0;
1868
2111
  };
1869
2112
 
2113
+ $httpBackend.$$originalHttpBackend = originalHttpBackend;
2114
+
1870
2115
  return $httpBackend;
1871
2116
 
1872
2117
 
1873
2118
  function createShortMethods(prefix) {
1874
2119
  angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) {
1875
2120
  $httpBackend[prefix + method] = function(url, headers, keys) {
2121
+ assertArgDefined(arguments, 0, 'url');
2122
+
2123
+ // Change url to `null` if `undefined` to stop it throwing an exception further down
2124
+ if (angular.isUndefined(url)) url = null;
2125
+
1876
2126
  return $httpBackend[prefix](method, url, undefined, headers, keys);
1877
2127
  };
1878
2128
  });
1879
2129
 
1880
2130
  angular.forEach(['PUT', 'POST', 'PATCH'], function(method) {
1881
2131
  $httpBackend[prefix + method] = function(url, data, headers, keys) {
2132
+ assertArgDefined(arguments, 0, 'url');
2133
+
2134
+ // Change url to `null` if `undefined` to stop it throwing an exception further down
2135
+ if (angular.isUndefined(url)) url = null;
2136
+
1882
2137
  return $httpBackend[prefix](method, url, data, headers, keys);
1883
2138
  };
1884
2139
  });
1885
2140
  }
1886
- }
1887
-
1888
- function MockHttpExpectation(method, url, data, headers, keys) {
1889
2141
 
1890
- function getUrlParams(u) {
1891
- var params = u.slice(u.indexOf('?') + 1).split('&');
1892
- return params.sort();
2142
+ function parseRouteUrl(url) {
2143
+ var strippedUrl = stripQueryAndHash(url);
2144
+ var parseOptions = {caseInsensitiveMatch: true, ignoreTrailingSlashes: true};
2145
+ return routeToRegExp(strippedUrl, parseOptions);
1893
2146
  }
2147
+ }
1894
2148
 
1895
- function compareUrl(u) {
1896
- return (url.slice(0, url.indexOf('?')) == u.slice(0, u.indexOf('?')) && getUrlParams(url).join() == getUrlParams(u).join());
2149
+ function assertArgDefined(args, index, name) {
2150
+ if (args.length > index && angular.isUndefined(args[index])) {
2151
+ throw new Error('Undefined argument `' + name + '`; the argument is provided but not defined');
1897
2152
  }
2153
+ }
1898
2154
 
1899
- this.data = data;
1900
- this.headers = headers;
2155
+ function stripQueryAndHash(url) {
2156
+ return url.replace(/[?#].*$/, '');
2157
+ }
2158
+
2159
+ function MockHttpExpectation(expectedMethod, expectedUrl, expectedData, expectedHeaders,
2160
+ expectedKeys) {
2161
+
2162
+ this.data = expectedData;
2163
+ this.headers = expectedHeaders;
1901
2164
 
1902
- this.match = function(m, u, d, h) {
1903
- if (method != m) return false;
1904
- if (!this.matchUrl(u)) return false;
1905
- if (angular.isDefined(d) && !this.matchData(d)) return false;
1906
- 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;
1907
2170
  return true;
1908
2171
  };
1909
2172
 
1910
- this.matchUrl = function(u) {
1911
- if (!url) return true;
1912
- if (angular.isFunction(url.test)) return url.test(u);
1913
- if (angular.isFunction(url)) return url(u);
1914
- 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));
1915
2178
  };
1916
2179
 
1917
- this.matchHeaders = function(h) {
1918
- if (angular.isUndefined(headers)) return true;
1919
- if (angular.isFunction(headers)) return headers(h);
1920
- 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);
1921
2184
  };
1922
2185
 
1923
- this.matchData = function(d) {
1924
- if (angular.isUndefined(data)) return true;
1925
- if (data && angular.isFunction(data.test)) return data.test(d);
1926
- if (data && angular.isFunction(data)) return data(d);
1927
- if (data && !angular.isString(data)) {
1928
- 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));
1929
2192
  }
1930
- return data == d;
2193
+ // eslint-disable-next-line eqeqeq
2194
+ return expectedData == data;
1931
2195
  };
1932
2196
 
1933
2197
  this.toString = function() {
1934
- return method + ' ' + url;
2198
+ return expectedMethod + ' ' + expectedUrl;
1935
2199
  };
1936
2200
 
1937
- this.params = function(u) {
1938
- 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);
1939
2204
 
1940
- function pathParams() {
1941
- var keyObj = {};
1942
- if (!url || !angular.isFunction(url.test) || !keys || keys.length === 0) return keyObj;
2205
+ return angular.extend(extractParamsFromQuery(queryStr), extractParamsFromPath(strippedUrl));
2206
+ };
1943
2207
 
1944
- var m = url.exec(u);
1945
- if (!m) return keyObj;
1946
- for (var i = 1, len = m.length; i < len; ++i) {
1947
- var key = keys[i - 1];
1948
- var val = m[i];
1949
- if (key && val) {
1950
- keyObj[key.name || key] = val;
1951
- }
1952
- }
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
+ }
2218
+
2219
+ function normalizeQuery(queryStr) {
2220
+ return queryStr.split('&').sort().join('&');
2221
+ }
1953
2222
 
1954
- return keyObj;
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
+ }
1955
2238
  }
1956
2239
 
1957
- function parseQuery() {
1958
- var obj = {}, key_value, key,
1959
- queryStr = u.indexOf('?') > -1
1960
- ? u.substring(u.indexOf('?') + 1)
1961
- : "";
1962
-
1963
- angular.forEach(queryStr.split('&'), function(keyValue) {
1964
- if (keyValue) {
1965
- key_value = keyValue.replace(/\+/g,'%20').split('=');
1966
- key = tryDecodeURIComponent(key_value[0]);
1967
- if (angular.isDefined(key)) {
1968
- var val = angular.isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
1969
- if (!hasOwnProperty.call(obj, key)) {
1970
- obj[key] = val;
1971
- } else if (angular.isArray(obj[key])) {
1972
- obj[key].push(val);
1973
- } else {
1974
- obj[key] = [obj[key],val];
1975
- }
1976
- }
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];
1977
2259
  }
1978
- });
1979
- return obj;
1980
- }
1981
- function tryDecodeURIComponent(value) {
1982
- try {
1983
- return decodeURIComponent(value);
1984
- } catch (e) {
1985
- // Ignore any invalid uri component
1986
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
1987
2271
  }
1988
- };
2272
+ }
1989
2273
  }
1990
2274
 
1991
2275
  function createMockXhr() {
@@ -2019,13 +2303,13 @@ function MockXhr() {
2019
2303
  var header = this.$$respHeaders[name];
2020
2304
  if (header) return header;
2021
2305
 
2022
- name = angular.lowercase(name);
2306
+ name = angular.$$lowercase(name);
2023
2307
  header = this.$$respHeaders[name];
2024
2308
  if (header) return header;
2025
2309
 
2026
2310
  header = undefined;
2027
2311
  angular.forEach(this.$$respHeaders, function(headerVal, headerName) {
2028
- if (!header && angular.lowercase(headerName) == name) header = headerVal;
2312
+ if (!header && angular.$$lowercase(headerName) === name) header = headerVal;
2029
2313
  });
2030
2314
  return header;
2031
2315
  };
@@ -2039,10 +2323,14 @@ function MockXhr() {
2039
2323
  return lines.join('\n');
2040
2324
  };
2041
2325
 
2042
- this.abort = angular.noop;
2326
+ this.abort = function() {
2327
+ if (isFunction(this.onabort)) {
2328
+ this.onabort();
2329
+ }
2330
+ };
2043
2331
 
2044
2332
  // This section simulates the events on a real XHR object (and the upload object)
2045
- // 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
2046
2334
  // but store the events directly ourselves on `$$events`, instead of going through the `addEventListener`
2047
2335
  this.$$events = {};
2048
2336
  this.addEventListener = function(name, listener) {
@@ -2071,39 +2359,86 @@ angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $
2071
2359
  /**
2072
2360
  * @ngdoc method
2073
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
+ *
2074
2369
  * @description
2075
2370
  *
2076
2371
  * Flushes the queue of pending tasks.
2077
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
+ *
2078
2382
  * @param {number=} delay maximum timeout amount to flush up until
2079
2383
  */
2080
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).
2081
2388
  $browser.defer.flush(delay);
2082
2389
  };
2083
2390
 
2084
2391
  /**
2085
2392
  * @ngdoc method
2086
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
+ *
2087
2402
  * @description
2088
2403
  *
2089
- * 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>
2090
2424
  */
2091
2425
  $delegate.verifyNoPendingTasks = function() {
2092
- if ($browser.deferredFns.length) {
2093
- throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' +
2094
- 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);
2095
2439
  }
2096
2440
  };
2097
2441
 
2098
- function formatPendingTasksAsString(tasks) {
2099
- var result = [];
2100
- angular.forEach(tasks, function(task) {
2101
- result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}');
2102
- });
2103
-
2104
- return result.join(', ');
2105
- }
2106
-
2107
2442
  return $delegate;
2108
2443
  }];
2109
2444
 
@@ -2153,7 +2488,6 @@ angular.mock.$RootElementProvider = function() {
2153
2488
  * A decorator for {@link ng.$controller} with additional `bindings` parameter, useful when testing
2154
2489
  * controllers of directives that use {@link $compile#-bindtocontroller- `bindToController`}.
2155
2490
  *
2156
- *
2157
2491
  * ## Example
2158
2492
  *
2159
2493
  * ```js
@@ -2171,18 +2505,24 @@ angular.mock.$RootElementProvider = function() {
2171
2505
  * // Controller definition ...
2172
2506
  *
2173
2507
  * myMod.controller('MyDirectiveController', ['$log', function($log) {
2174
- * $log.info(this.name);
2508
+ * this.log = function() {
2509
+ * $log.info(this.name);
2510
+ * };
2175
2511
  * }]);
2176
2512
  *
2177
2513
  *
2178
2514
  * // In a test ...
2179
2515
  *
2180
2516
  * describe('myDirectiveController', function() {
2181
- * it('should write the bound name to the log', inject(function($controller, $log) {
2182
- * var ctrl = $controller('MyDirectiveController', { /* no locals &#42;/ }, { name: 'Clark Kent' });
2183
- * expect(ctrl.name).toEqual('Clark Kent');
2184
- * expect($log.info.logs).toEqual(['Clark Kent']);
2185
- * }));
2517
+ * describe('log()', function() {
2518
+ * it('should write the bound name to the log', inject(function($controller, $log) {
2519
+ * var ctrl = $controller('MyDirectiveController', { /* no locals &#42;/ }, { name: 'Clark Kent' });
2520
+ * ctrl.log();
2521
+ *
2522
+ * expect(ctrl.name).toEqual('Clark Kent');
2523
+ * expect($log.info.logs).toEqual(['Clark Kent']);
2524
+ * }));
2525
+ * });
2186
2526
  * });
2187
2527
  *
2188
2528
  * ```
@@ -2193,45 +2533,51 @@ angular.mock.$RootElementProvider = function() {
2193
2533
  *
2194
2534
  * * check if a controller with given name is registered via `$controllerProvider`
2195
2535
  * * check if evaluating the string on the current scope returns a constructor
2196
- * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
2197
- * `window` object (not recommended)
2198
2536
  *
2199
2537
  * The string can use the `controller as property` syntax, where the controller instance is published
2200
2538
  * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
2201
2539
  * to work correctly.
2202
2540
  *
2203
2541
  * @param {Object} locals Injection locals for Controller.
2204
- * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used
2205
- * to simulate the `bindToController` feature and simplify certain kinds of tests.
2542
+ * @param {Object=} bindings Properties to add to the controller instance. This is used to simulate
2543
+ * the `bindToController` feature and simplify certain kinds of tests.
2206
2544
  * @return {Object} Instance of given controller.
2207
2545
  */
2208
- angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
2209
- return function(expression, locals, later, ident) {
2210
- if (later && typeof later === 'object') {
2211
- var instantiate = $delegate(expression, locals, true, ident);
2212
- angular.extend(instantiate.instance, later);
2213
-
2214
- var instance = instantiate();
2215
- if (instance !== instantiate.instance) {
2546
+ function createControllerDecorator() {
2547
+ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
2548
+ return function(expression, locals, later, ident) {
2549
+ if (later && typeof later === 'object') {
2550
+ var instantiate = $delegate(expression, locals, true, ident);
2551
+ var instance = instantiate();
2216
2552
  angular.extend(instance, later);
2553
+ return instance;
2217
2554
  }
2555
+ return $delegate(expression, locals, later, ident);
2556
+ };
2557
+ }];
2218
2558
 
2219
- return instance;
2220
- }
2221
- return $delegate(expression, locals, later, ident);
2222
- };
2223
- }];
2559
+ return angular.mock.$ControllerDecorator;
2560
+ }
2224
2561
 
2225
2562
  /**
2226
2563
  * @ngdoc service
2227
2564
  * @name $componentController
2228
2565
  * @description
2229
- * A service that can be used to create instances of component controllers.
2230
- * <div class="alert alert-info">
2566
+ * A service that can be used to create instances of component controllers. Useful for unit-testing.
2567
+ *
2231
2568
  * Be aware that the controller will be instantiated and attached to the scope as specified in
2232
2569
  * the component definition object. If you do not provide a `$scope` object in the `locals` param
2233
2570
  * then the helper will create a new isolated scope as a child of `$rootScope`.
2234
- * </div>
2571
+ *
2572
+ * If you are using `$element` or `$attrs` in the controller, make sure to provide them as `locals`.
2573
+ * The `$element` must be a jqLite-wrapped DOM element, and `$attrs` should be an object that
2574
+ * has all properties / functions that you are using in the controller. If this is getting too complex,
2575
+ * you should compile the component instead and access the component's controller via the
2576
+ * {@link angular.element#methods `controller`} function.
2577
+ *
2578
+ * See also the section on {@link guide/component#unit-testing-component-controllers unit-testing component controllers}
2579
+ * in the guide.
2580
+ *
2235
2581
  * @param {string} componentName the name of the component whose controller we want to instantiate
2236
2582
  * @param {Object} locals Injection locals for Controller.
2237
2583
  * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used
@@ -2239,7 +2585,8 @@ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
2239
2585
  * @param {string=} ident Override the property name to use when attaching the controller to the scope.
2240
2586
  * @return {Object} Instance of requested controller.
2241
2587
  */
2242
- angular.mock.$ComponentControllerProvider = ['$compileProvider', function($compileProvider) {
2588
+ angular.mock.$ComponentControllerProvider = ['$compileProvider',
2589
+ function ComponentControllerProvider($compileProvider) {
2243
2590
  this.$get = ['$controller','$injector', '$rootScope', function($controller, $injector, $rootScope) {
2244
2591
  return function $componentController(componentName, locals, bindings, ident) {
2245
2592
  // get all directives associated to the component name
@@ -2273,21 +2620,17 @@ angular.mock.$ComponentControllerProvider = ['$compileProvider', function($compi
2273
2620
  * @packageName angular-mocks
2274
2621
  * @description
2275
2622
  *
2276
- * # ngMock
2277
- *
2278
- * The `ngMock` module provides support to inject and mock Angular services into unit tests.
2279
- * In addition, ngMock also extends various core ng services such that they can be
2623
+ * The `ngMock` module provides support to inject and mock AngularJS services into unit tests.
2624
+ * In addition, ngMock also extends various core AngularJS services such that they can be
2280
2625
  * inspected and controlled in a synchronous manner within test code.
2281
2626
  *
2282
- *
2283
- * <div doc-module-components="ngMock"></div>
2284
- *
2285
2627
  * @installation
2286
2628
  *
2287
2629
  * First, download the file:
2288
2630
  * * [Google CDN](https://developers.google.com/speed/libraries/devguide#angularjs) e.g.
2289
2631
  * `"//ajax.googleapis.com/ajax/libs/angularjs/X.Y.Z/angular-mocks.js"`
2290
2632
  * * [NPM](https://www.npmjs.com/) e.g. `npm install angular-mocks@X.Y.Z`
2633
+ * * [Yarn](https://yarnpkg.com) e.g. `yarn add angular-mocks@X.Y.Z`
2291
2634
  * * [Bower](http://bower.io) e.g. `bower install angular-mocks#X.Y.Z`
2292
2635
  * * [code.angularjs.org](https://code.angularjs.org/) (discouraged for production use) e.g.
2293
2636
  * `"//code.angularjs.org/X.Y.Z/angular-mocks.js"`
@@ -2316,15 +2659,17 @@ angular.module('ngMock', ['ng']).provider({
2316
2659
  $exceptionHandler: angular.mock.$ExceptionHandlerProvider,
2317
2660
  $log: angular.mock.$LogProvider,
2318
2661
  $interval: angular.mock.$IntervalProvider,
2319
- $httpBackend: angular.mock.$HttpBackendProvider,
2320
2662
  $rootElement: angular.mock.$RootElementProvider,
2321
- $componentController: angular.mock.$ComponentControllerProvider
2322
- }).config(['$provide', function($provide) {
2663
+ $componentController: angular.mock.$ComponentControllerProvider,
2664
+ $flushPendingTasks: angular.mock.$FlushPendingTasksProvider,
2665
+ $verifyNoPendingTasks: angular.mock.$VerifyNoPendingTasksProvider
2666
+ }).config(['$provide', '$compileProvider', function($provide, $compileProvider) {
2323
2667
  $provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
2324
2668
  $provide.decorator('$$rAF', angular.mock.$RAFDecorator);
2325
2669
  $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
2326
- $provide.decorator('$controller', angular.mock.$ControllerDecorator);
2327
- }]);
2670
+ $provide.decorator('$controller', createControllerDecorator($compileProvider));
2671
+ $provide.decorator('$httpBackend', angular.mock.$httpBackendDecorator);
2672
+ }]).info({ angularVersion: '1.8.0' });
2328
2673
 
2329
2674
  /**
2330
2675
  * @ngdoc module
@@ -2333,14 +2678,13 @@ angular.module('ngMock', ['ng']).provider({
2333
2678
  * @packageName angular-mocks
2334
2679
  * @description
2335
2680
  *
2336
- * 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.
2337
2682
  * Currently there is only one mock present in this module -
2338
2683
  * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock.
2339
2684
  */
2340
2685
  angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2341
- $provide.value('$httpBackend', angular.injector(['ng']).get('$httpBackend'));
2342
2686
  $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
2343
- }]);
2687
+ }]).info({ angularVersion: '1.8.0' });
2344
2688
 
2345
2689
  /**
2346
2690
  * @ngdoc service
@@ -2387,19 +2731,19 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2387
2731
  * phones.push(phone);
2388
2732
  * return [200, phone, {}];
2389
2733
  * });
2390
- * $httpBackend.whenGET(/^\/templates\//).passThrough(); // Requests for templare are handled by the real server
2734
+ * $httpBackend.whenGET(/^\/templates\//).passThrough(); // Requests for templates are handled by the real server
2391
2735
  * //...
2392
2736
  * });
2393
2737
  * ```
2394
2738
  *
2395
2739
  * Afterwards, bootstrap your app with this new module.
2396
2740
  *
2397
- * ## Example
2741
+ * @example
2398
2742
  * <example name="httpbackend-e2e-testing" module="myAppE2E" deps="angular-mocks.js">
2399
2743
  * <file name="app.js">
2400
2744
  * var myApp = angular.module('myApp', []);
2401
2745
  *
2402
- * myApp.controller('main', function($http) {
2746
+ * myApp.controller('MainCtrl', function MainCtrl($http) {
2403
2747
  * var ctrl = this;
2404
2748
  *
2405
2749
  * ctrl.phones = [];
@@ -2441,7 +2785,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2441
2785
  * });
2442
2786
  * </file>
2443
2787
  * <file name="index.html">
2444
- * <div ng-controller="main as $ctrl">
2788
+ * <div ng-controller="MainCtrl as $ctrl">
2445
2789
  * <form name="newPhoneForm" ng-submit="$ctrl.addPhone($ctrl.newPhone)">
2446
2790
  * <input type="text" ng-model="$ctrl.newPhone.name">
2447
2791
  * <input type="submit" value="Add Phone">
@@ -2465,9 +2809,10 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2465
2809
  * Creates a new backend definition.
2466
2810
  *
2467
2811
  * @param {string} method HTTP method.
2468
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
2812
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
2469
2813
  * and returns true if the url matches the current definition.
2470
- * @param {(string|RegExp)=} data HTTP request body.
2814
+ * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
2815
+ * data string and returns true if the data is as expected.
2471
2816
  * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
2472
2817
  * object and returns true if the headers match the current definition.
2473
2818
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
@@ -2497,7 +2842,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2497
2842
  * @description
2498
2843
  * Creates a new backend definition for GET requests. For more info see `when()`.
2499
2844
  *
2500
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
2845
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
2501
2846
  * and returns true if the url matches the current definition.
2502
2847
  * @param {(Object|function(Object))=} headers HTTP headers.
2503
2848
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
@@ -2514,7 +2859,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2514
2859
  * @description
2515
2860
  * Creates a new backend definition for HEAD requests. For more info see `when()`.
2516
2861
  *
2517
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
2862
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
2518
2863
  * and returns true if the url matches the current definition.
2519
2864
  * @param {(Object|function(Object))=} headers HTTP headers.
2520
2865
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
@@ -2531,7 +2876,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2531
2876
  * @description
2532
2877
  * Creates a new backend definition for DELETE requests. For more info see `when()`.
2533
2878
  *
2534
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
2879
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
2535
2880
  * and returns true if the url matches the current definition.
2536
2881
  * @param {(Object|function(Object))=} headers HTTP headers.
2537
2882
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
@@ -2548,9 +2893,10 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2548
2893
  * @description
2549
2894
  * Creates a new backend definition for POST requests. For more info see `when()`.
2550
2895
  *
2551
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
2896
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
2552
2897
  * and returns true if the url matches the current definition.
2553
- * @param {(string|RegExp)=} data HTTP request body.
2898
+ * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
2899
+ * data string and returns true if the data is as expected.
2554
2900
  * @param {(Object|function(Object))=} headers HTTP headers.
2555
2901
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
2556
2902
  * {@link ngMock.$httpBackend $httpBackend mock}.
@@ -2566,9 +2912,10 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2566
2912
  * @description
2567
2913
  * Creates a new backend definition for PUT requests. For more info see `when()`.
2568
2914
  *
2569
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
2915
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
2570
2916
  * and returns true if the url matches the current definition.
2571
- * @param {(string|RegExp)=} data HTTP request body.
2917
+ * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
2918
+ * data string and returns true if the data is as expected.
2572
2919
  * @param {(Object|function(Object))=} headers HTTP headers.
2573
2920
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
2574
2921
  * {@link ngMock.$httpBackend $httpBackend mock}.
@@ -2584,9 +2931,10 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2584
2931
  * @description
2585
2932
  * Creates a new backend definition for PATCH requests. For more info see `when()`.
2586
2933
  *
2587
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
2934
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
2588
2935
  * and returns true if the url matches the current definition.
2589
- * @param {(string|RegExp)=} data HTTP request body.
2936
+ * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
2937
+ * data string and returns true if the data is as expected.
2590
2938
  * @param {(Object|function(Object))=} headers HTTP headers.
2591
2939
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
2592
2940
  * {@link ngMock.$httpBackend $httpBackend mock}.
@@ -2602,7 +2950,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2602
2950
  * @description
2603
2951
  * Creates a new backend definition for JSONP requests. For more info see `when()`.
2604
2952
  *
2605
- * @param {string|RegExp|function(string)} url HTTP url or function that receives a url
2953
+ * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url
2606
2954
  * and returns true if the url matches the current definition.
2607
2955
  * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on
2608
2956
  * {@link ngMock.$httpBackend $httpBackend mock}.
@@ -2623,6 +2971,39 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
2623
2971
  * control how a matched request is handled. You can save this object for later use and invoke
2624
2972
  * `respond` or `passThrough` again in order to change how a matched request is handled.
2625
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
+ */
2626
3007
  angular.mock.e2e = {};
2627
3008
  angular.mock.e2e.$httpBackendDecorator =
2628
3009
  ['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock];
@@ -2654,6 +3035,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
2654
3035
  * @ngdoc method
2655
3036
  * @name $rootScope.Scope#$countChildScopes
2656
3037
  * @module ngMock
3038
+ * @this $rootScope.Scope
2657
3039
  * @description
2658
3040
  * Counts all the direct and indirect child scopes of the current scope.
2659
3041
  *
@@ -2662,7 +3044,6 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
2662
3044
  * @returns {number} Total number of child scopes.
2663
3045
  */
2664
3046
  function countChildScopes() {
2665
- // jshint validthis: true
2666
3047
  var count = 0; // exclude the current scope
2667
3048
  var pendingChildHeads = [this.$$childHead];
2668
3049
  var currentScope;
@@ -2684,6 +3065,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
2684
3065
  /**
2685
3066
  * @ngdoc method
2686
3067
  * @name $rootScope.Scope#$countWatchers
3068
+ * @this $rootScope.Scope
2687
3069
  * @module ngMock
2688
3070
  * @description
2689
3071
  * Counts all the watchers of direct and indirect child scopes of the current scope.
@@ -2694,7 +3076,6 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
2694
3076
  * @returns {number} Total number of watchers.
2695
3077
  */
2696
3078
  function countWatchers() {
2697
- // jshint validthis: true
2698
3079
  var count = this.$$watchers ? this.$$watchers.length : 0; // include the current scope
2699
3080
  var pendingChildHeads = [this.$$childHead];
2700
3081
  var currentScope;
@@ -2714,7 +3095,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
2714
3095
  }];
2715
3096
 
2716
3097
 
2717
- !(function(jasmineOrMocha) {
3098
+ (function(jasmineOrMocha) {
2718
3099
 
2719
3100
  if (!jasmineOrMocha) {
2720
3101
  return;
@@ -2809,7 +3190,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
2809
3190
  *
2810
3191
  * You cannot call `sharedInjector()` from within a context already using `sharedInjector()`.
2811
3192
  *
2812
- * ## Example
3193
+ * ## Example
2813
3194
  *
2814
3195
  * Typically beforeAll is used to make many assertions about a single operation. This can
2815
3196
  * cut down test run-time as the test setup doesn't need to be re-run, and enabling focussed
@@ -2847,14 +3228,14 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
2847
3228
  */
2848
3229
  module.sharedInjector = function() {
2849
3230
  if (!(module.$$beforeAllHook && module.$$afterAllHook)) {
2850
- throw Error("sharedInjector() cannot be used unless your test runner defines beforeAll/afterAll");
3231
+ throw Error('sharedInjector() cannot be used unless your test runner defines beforeAll/afterAll');
2851
3232
  }
2852
3233
 
2853
3234
  var initialized = false;
2854
3235
 
2855
- module.$$beforeAllHook(function() {
3236
+ module.$$beforeAllHook(/** @this */ function() {
2856
3237
  if (injectorState.shared) {
2857
- injectorState.sharedError = Error("sharedInjector() cannot be called inside a context that has already called sharedInjector()");
3238
+ injectorState.sharedError = Error('sharedInjector() cannot be called inside a context that has already called sharedInjector()');
2858
3239
  throw injectorState.sharedError;
2859
3240
  }
2860
3241
  initialized = true;
@@ -2873,10 +3254,10 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
2873
3254
  };
2874
3255
 
2875
3256
  module.$$beforeEach = function() {
2876
- if (injectorState.shared && currentSpec && currentSpec != this) {
3257
+ if (injectorState.shared && currentSpec && currentSpec !== this) {
2877
3258
  var state = currentSpec;
2878
3259
  currentSpec = this;
2879
- angular.forEach(["$injector","$modules","$providerInjector", "$injectorStrict"], function(k) {
3260
+ angular.forEach(['$injector','$modules','$providerInjector', '$injectorStrict'], function(k) {
2880
3261
  currentSpec[k] = state[k];
2881
3262
  state[k] = null;
2882
3263
  });
@@ -2900,12 +3281,6 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
2900
3281
  delete fn.$inject;
2901
3282
  });
2902
3283
 
2903
- angular.forEach(currentSpec.$modules, function(module) {
2904
- if (module && module.$$hashKey) {
2905
- module.$$hashKey = undefined;
2906
- }
2907
- });
2908
-
2909
3284
  currentSpec.$injector = null;
2910
3285
  currentSpec.$modules = null;
2911
3286
  currentSpec.$providerInjector = null;
@@ -2967,7 +3342,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
2967
3342
  * These are ignored by the injector when the reference name is resolved.
2968
3343
  *
2969
3344
  * For example, the parameter `_myService_` would be resolved as the reference `myService`.
2970
- * Since it is available in the function body as _myService_, we can then assign it to a variable
3345
+ * Since it is available in the function body as `_myService_`, we can then assign it to a variable
2971
3346
  * defined in an outer scope.
2972
3347
  *
2973
3348
  * ```
@@ -3031,7 +3406,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
3031
3406
 
3032
3407
 
3033
3408
 
3034
- var ErrorAddingDeclarationLocationStack = function(e, errorForStack) {
3409
+ var ErrorAddingDeclarationLocationStack = function ErrorAddingDeclarationLocationStack(e, errorForStack) {
3035
3410
  this.message = e.message;
3036
3411
  this.name = e.name;
3037
3412
  if (e.line) this.line = e.line;
@@ -3049,11 +3424,11 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
3049
3424
  if (!errorForStack.stack) {
3050
3425
  try {
3051
3426
  throw errorForStack;
3052
- } catch (e) {}
3427
+ } catch (e) { /* empty */ }
3053
3428
  }
3054
- return wasInjectorCreated() ? workFn.call(currentSpec) : workFn;
3429
+ return wasInjectorCreated() ? WorkFn.call(currentSpec) : WorkFn;
3055
3430
  /////////////////////
3056
- function workFn() {
3431
+ function WorkFn() {
3057
3432
  var modules = currentSpec.$modules || [];
3058
3433
  var strictDi = !!currentSpec.$injectorStrict;
3059
3434
  modules.unshift(['$injector', function($injector) {
@@ -3066,7 +3441,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
3066
3441
  if (strictDi) {
3067
3442
  // If strictDi is enabled, annotate the providerInjector blocks
3068
3443
  angular.forEach(modules, function(moduleFn) {
3069
- if (typeof moduleFn === "function") {
3444
+ if (typeof moduleFn === 'function') {
3070
3445
  angular.injector.$$annotate(moduleFn);
3071
3446
  }
3072
3447
  });
@@ -3081,9 +3456,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
3081
3456
  injector.annotate(blockFns[i]);
3082
3457
  }
3083
3458
  try {
3084
- /* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */
3085
3459
  injector.invoke(blockFns[i] || angular.noop, this);
3086
- /* jshint +W040 */
3087
3460
  } catch (e) {
3088
3461
  if (e.stack && errorForStack) {
3089
3462
  throw new ErrorAddingDeclarationLocationStack(e, errorForStack);
@@ -3122,5 +3495,263 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
3122
3495
  }
3123
3496
  })(window.jasmine || window.mocha);
3124
3497
 
3498
+ 'use strict';
3499
+
3500
+ (function() {
3501
+ /**
3502
+ * @ngdoc function
3503
+ * @name browserTrigger
3504
+ * @description
3505
+ *
3506
+ * This is a global (window) function that is only available when the {@link ngMock} module is
3507
+ * included.
3508
+ *
3509
+ * It can be used to trigger a native browser event on an element, which is useful for unit testing.
3510
+ *
3511
+ *
3512
+ * @param {Object} element Either a wrapped jQuery/jqLite node or a DOMElement
3513
+ * @param {string=} eventType Optional event type. If none is specified, the function tries
3514
+ * to determine the right event type for the element, e.g. `change` for
3515
+ * `input[text]`.
3516
+ * @param {Object=} eventData An optional object which contains additional event data that is used
3517
+ * when creating the event:
3518
+ *
3519
+ * - `bubbles`: [Event.bubbles](https://developer.mozilla.org/docs/Web/API/Event/bubbles).
3520
+ * Not applicable to all events.
3521
+ *
3522
+ * - `cancelable`: [Event.cancelable](https://developer.mozilla.org/docs/Web/API/Event/cancelable).
3523
+ * Not applicable to all events.
3524
+ *
3525
+ * - `charcode`: [charCode](https://developer.mozilla.org/docs/Web/API/KeyboardEvent/charcode)
3526
+ * for keyboard events (keydown, keypress, and keyup).
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
+ *
3531
+ * - `elapsedTime`: the elapsedTime for
3532
+ * [TransitionEvent](https://developer.mozilla.org/docs/Web/API/TransitionEvent)
3533
+ * and [AnimationEvent](https://developer.mozilla.org/docs/Web/API/AnimationEvent).
3534
+ *
3535
+ * - `keycode`: [keyCode](https://developer.mozilla.org/docs/Web/API/KeyboardEvent/keycode)
3536
+ * for keyboard events (keydown, keypress, and keyup).
3537
+ *
3538
+ * - `keys`: an array of possible modifier keys (ctrl, alt, shift, meta) for
3539
+ * [MouseEvent](https://developer.mozilla.org/docs/Web/API/MouseEvent) and
3540
+ * keyboard events (keydown, keypress, and keyup).
3541
+ *
3542
+ * - `relatedTarget`: the
3543
+ * [relatedTarget](https://developer.mozilla.org/docs/Web/API/MouseEvent/relatedTarget)
3544
+ * for [MouseEvent](https://developer.mozilla.org/docs/Web/API/MouseEvent).
3545
+ *
3546
+ * - `which`: [which](https://developer.mozilla.org/docs/Web/API/KeyboardEvent/which)
3547
+ * for keyboard events (keydown, keypress, and keyup).
3548
+ *
3549
+ * - `x`: x-coordinates for [MouseEvent](https://developer.mozilla.org/docs/Web/API/MouseEvent)
3550
+ * and [TouchEvent](https://developer.mozilla.org/docs/Web/API/TouchEvent).
3551
+ *
3552
+ * - `y`: y-coordinates for [MouseEvent](https://developer.mozilla.org/docs/Web/API/MouseEvent)
3553
+ * and [TouchEvent](https://developer.mozilla.org/docs/Web/API/TouchEvent).
3554
+ *
3555
+ */
3556
+ window.browserTrigger = function browserTrigger(element, eventType, eventData) {
3557
+ if (element && !element.nodeName) element = element[0];
3558
+ if (!element) return;
3559
+
3560
+ eventData = eventData || {};
3561
+ var relatedTarget = eventData.relatedTarget || element;
3562
+ var keys = eventData.keys;
3563
+ var x = eventData.x;
3564
+ var y = eventData.y;
3565
+
3566
+ var inputType = (element.type) ? element.type.toLowerCase() : null,
3567
+ nodeName = element.nodeName.toLowerCase();
3568
+ if (!eventType) {
3569
+ eventType = {
3570
+ 'text': 'change',
3571
+ 'textarea': 'change',
3572
+ 'hidden': 'change',
3573
+ 'password': 'change',
3574
+ 'button': 'click',
3575
+ 'submit': 'click',
3576
+ 'reset': 'click',
3577
+ 'image': 'click',
3578
+ 'checkbox': 'click',
3579
+ 'radio': 'click',
3580
+ 'select-one': 'change',
3581
+ 'select-multiple': 'change',
3582
+ '_default_': 'click'
3583
+ }[inputType || '_default_'];
3584
+ }
3585
+
3586
+ if (nodeName === 'option') {
3587
+ element.parentNode.value = element.value;
3588
+ element = element.parentNode;
3589
+ eventType = 'change';
3590
+ }
3591
+
3592
+ keys = keys || [];
3593
+ function pressed(key) {
3594
+ return keys.indexOf(key) !== -1;
3595
+ }
3596
+
3597
+ var evnt;
3598
+ if (/transitionend/.test(eventType)) {
3599
+ if (window.WebKitTransitionEvent) {
3600
+ evnt = new window.WebKitTransitionEvent(eventType, eventData);
3601
+ evnt.initEvent(eventType, eventData.bubbles, true);
3602
+ } else {
3603
+ try {
3604
+ evnt = new window.TransitionEvent(eventType, eventData);
3605
+ } catch (e) {
3606
+ evnt = window.document.createEvent('TransitionEvent');
3607
+ evnt.initTransitionEvent(eventType, eventData.bubbles, null, null, eventData.elapsedTime || 0);
3608
+ }
3609
+ }
3610
+ } else if (/animationend/.test(eventType)) {
3611
+ if (window.WebKitAnimationEvent) {
3612
+ evnt = new window.WebKitAnimationEvent(eventType, eventData);
3613
+ evnt.initEvent(eventType, eventData.bubbles, true);
3614
+ } else {
3615
+ try {
3616
+ evnt = new window.AnimationEvent(eventType, eventData);
3617
+ } catch (e) {
3618
+ evnt = window.document.createEvent('AnimationEvent');
3619
+ evnt.initAnimationEvent(eventType, eventData.bubbles, null, null, eventData.elapsedTime || 0);
3620
+ }
3621
+ }
3622
+ } else if (/touch/.test(eventType) && supportsTouchEvents()) {
3623
+ evnt = createTouchEvent(element, eventType, x, y);
3624
+ } else if (/key/.test(eventType)) {
3625
+ evnt = window.document.createEvent('Events');
3626
+ evnt.initEvent(eventType, eventData.bubbles, eventData.cancelable);
3627
+ evnt.view = window;
3628
+ evnt.ctrlKey = pressed('ctrl');
3629
+ evnt.altKey = pressed('alt');
3630
+ evnt.shiftKey = pressed('shift');
3631
+ evnt.metaKey = pressed('meta');
3632
+ evnt.keyCode = eventData.keyCode;
3633
+ evnt.charCode = eventData.charCode;
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
+
3653
+ } else {
3654
+ evnt = window.document.createEvent('MouseEvents');
3655
+ x = x || 0;
3656
+ y = y || 0;
3657
+ evnt.initMouseEvent(eventType, true, true, window, 0, x, y, x, y, pressed('ctrl'),
3658
+ pressed('alt'), pressed('shift'), pressed('meta'), 0, relatedTarget);
3659
+ }
3660
+
3661
+ /* we're unable to change the timeStamp value directly so this
3662
+ * is only here to allow for testing where the timeStamp value is
3663
+ * read */
3664
+ evnt.$manualTimeStamp = eventData.timeStamp;
3665
+
3666
+ if (!evnt) return;
3667
+
3668
+ if (!eventData.bubbles || supportsEventBubblingInDetachedTree() || isAttachedToDocument(element)) {
3669
+ return element.dispatchEvent(evnt);
3670
+ } else {
3671
+ triggerForPath(element, evnt);
3672
+ }
3673
+ };
3674
+
3675
+ function supportsTouchEvents() {
3676
+ if ('_cached' in supportsTouchEvents) {
3677
+ return supportsTouchEvents._cached;
3678
+ }
3679
+ if (!window.document.createTouch || !window.document.createTouchList) {
3680
+ supportsTouchEvents._cached = false;
3681
+ return false;
3682
+ }
3683
+ try {
3684
+ window.document.createEvent('TouchEvent');
3685
+ } catch (e) {
3686
+ supportsTouchEvents._cached = false;
3687
+ return false;
3688
+ }
3689
+ supportsTouchEvents._cached = true;
3690
+ return true;
3691
+ }
3692
+
3693
+ function createTouchEvent(element, eventType, x, y) {
3694
+ var evnt = new window.Event(eventType);
3695
+ x = x || 0;
3696
+ y = y || 0;
3697
+
3698
+ var touch = window.document.createTouch(window, element, Date.now(), x, y, x, y);
3699
+ var touches = window.document.createTouchList(touch);
3700
+
3701
+ evnt.touches = touches;
3702
+
3703
+ return evnt;
3704
+ }
3705
+
3706
+ function supportsEventBubblingInDetachedTree() {
3707
+ if ('_cached' in supportsEventBubblingInDetachedTree) {
3708
+ return supportsEventBubblingInDetachedTree._cached;
3709
+ }
3710
+ supportsEventBubblingInDetachedTree._cached = false;
3711
+ var doc = window.document;
3712
+ if (doc) {
3713
+ var parent = doc.createElement('div'),
3714
+ child = parent.cloneNode();
3715
+ parent.appendChild(child);
3716
+ parent.addEventListener('e', function() {
3717
+ supportsEventBubblingInDetachedTree._cached = true;
3718
+ });
3719
+ var evnt = window.document.createEvent('Events');
3720
+ evnt.initEvent('e', true, true);
3721
+ child.dispatchEvent(evnt);
3722
+ }
3723
+ return supportsEventBubblingInDetachedTree._cached;
3724
+ }
3725
+
3726
+ function triggerForPath(element, evnt) {
3727
+ var stop = false;
3728
+
3729
+ var _stopPropagation = evnt.stopPropagation;
3730
+ evnt.stopPropagation = function() {
3731
+ stop = true;
3732
+ _stopPropagation.apply(evnt, arguments);
3733
+ };
3734
+ patchEventTargetForBubbling(evnt, element);
3735
+ do {
3736
+ element.dispatchEvent(evnt);
3737
+ // eslint-disable-next-line no-unmodified-loop-condition
3738
+ } while (!stop && (element = element.parentNode));
3739
+ }
3740
+
3741
+ function patchEventTargetForBubbling(event, target) {
3742
+ event._target = target;
3743
+ Object.defineProperty(event, 'target', {get: function() { return this._target;}});
3744
+ }
3745
+
3746
+ function isAttachedToDocument(element) {
3747
+ while ((element = element.parentNode)) {
3748
+ if (element === window) {
3749
+ return true;
3750
+ }
3751
+ }
3752
+ return false;
3753
+ }
3754
+ })();
3755
+
3125
3756
 
3126
3757
  })(window, window.angular);