angularjs-rails 1.5.8 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @license AngularJS v1.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);