Compare commits

...

85 Commits

Author SHA1 Message Date
Georgios Kalpakas 19ecdb54bf fix(ngJq): properly detect when ng-jq is empty
Previously, even when `ng-jq` was empty (which should force the use of
jqLite), Angular tried to find jQuery on `window['']`. If it didn't find
anything there, it would fall back to jqLite (as expected).

Nonetheless, trying to access `window['']` calls `getElementById('')`,
which issues a warning in Firefox (maybe others).

This fix properly detects when `ng-jq` is empty and avoids trying to
access `window['']`.

Fixes #12741
2015-09-14 22:43:55 +02:00
Peter Bacon Darwin 30aa3eff4c chore(scripts/publish): get dist-tag from package.json
Closes #12722
2015-09-14 21:29:16 +01:00
Peter Bacon Darwin 8d39bd8abf fix($browser): handle async updates to location
Both browser reloads and iOS 9 bugs cause the window.location to report
a different href that which we have just set. The change does not become
available until the next tick.

This change generalises previous work to deal with reloads to deal with
the iOS 9 bug in the UIWebView component.

Closes #12241
Closes #12819
2015-09-14 21:27:46 +01:00
Matias Niemelä 472d076cca fix(ngAnimateMock): $animate.flush should work for looping animations 2015-09-14 13:02:46 -07:00
Arliang 1ae0be13c2 docs(CHANGELOG): fix typo
Closes #12837
2015-09-14 11:11:25 +03:00
toastman 159efdd429 docs($httpBackend): fix typo/mismatch
There is an mismatch for status in controller and test.
In controller $scope.status = 'ERROR!' and in test we
expect($rootScope.status).toBe('Failed...') so the test will fail;

Closes #12834
2015-09-13 22:18:07 +02:00
Martin Staffa 4755a35b7d test(input): clarify a test's description and expectations
The test ensures that when the trueValue is a string, the required error
is removed, not that the model is set to the trueValue.
2015-09-13 19:23:31 +02:00
Lucas Galfaso b2f8b0b875 fix($parse): throw error when accessing a restricted property indirectly
When accessing an instance thru a computed member and the property is an array,
then also check the string value of the array.

Closes #12833
2015-09-13 16:30:58 +01:00
lucastetreault 24cd70058d refactor(*): use isDefined and isUndefined consistently
Fix any place that compares with `undefined` to use `isUndefined` and `isDefined` instead.

Closes #4365
Closes #12831
2015-09-12 17:17:11 +01:00
indrimuska e46ab43422 docs($interpolate): add missing bracket in first example
Closes #12824
2015-09-11 15:00:15 +03:00
Georgios Kalpakas 8a62a8c7f0 docs(ngInit): fix typo ("unnecesary" --> "unnecessary")
Closes #12810
2015-09-11 14:54:16 +03:00
Georgios Kalpakas b6c2f8b854 docs(CONTRIBUTING.md): mention gitter community
Closes #12782
2015-09-11 14:46:33 +03:00
Martin Staffa 00ee090f4f docs(ngMessageFormat): convert plnkr to internal example
Fixes #12801
2015-09-10 00:12:32 +02:00
Georgios Kalpakas 544001f5a3 fix(input): ignore min/max if they are empty on all input types
When the min/max attributes are empty (i.e. `attrs.min/max === ''`), there
should be no min/max validation applied (i.e. all values should be valid
wrt min/max). This works correctly for `input[number]`, but not for date
input family (`input[date/datetime-local/time/week/month]`).

In the case on `input[number]`, an empty string for `attrs.min/max` is
translated to `undefined` for `minVal/maxVal` and a check for
`isUndefined(minVal/maxVal)` ensures that no min/max validation takes
place.
For the data input family, an empty string for `attrs.min/max` is
translated to `NaN` (by `parseDate()`), so an additional check (for
`isNaN(minVal/maxVal)`) is required.

Fixes #12363

Closes #12785
2015-09-09 15:31:33 +03:00
Peter Bacon Darwin 7175d0d0e3 docs(ngRepeat): make ngInit note a bit clearer
Closes #5623
2015-09-09 13:27:05 +01:00
Peter Bacon Darwin 010d9b6853 docs(ngInit): relaxed the warning notice 2015-09-09 13:15:02 +01:00
Peter Bacon Darwin 122ab074ca fix(angular.copy): support copying XML nodes
Closes #5429
Closes #12786
2015-09-09 13:10:03 +01:00
Peter Bacon Darwin e22bf9ac78 docs(indexPage): update Case Studies link to new site 2015-09-09 11:52:01 +01:00
Akshay Agarwal 324cb6b358 docs(angular.version): fix summary description
Fix summary description of `angular.version`.
The property description is best kept for the detailed page.

Closes #12790
2015-09-08 23:26:53 +03:00
Martin Staffa 80a2176e20 docs($compile): clarify scope types and controllerAs
Closes #12537
Closes #12758
2015-09-08 11:52:01 +01:00
Peter Bacon Darwin 681affef59 style($rootScope): remove trailing whitespace 2015-09-07 22:23:44 +01:00
kwypchlo 0ca8b1df20 fix(toDebugString): change replacement string
As discussed in #10085, the original replacement string can be treated
as html when displayed by the browser so it replaces it with '...' string.

Closes #10103
2015-09-07 22:23:43 +01:00
HeberLZ 20fb626b78 fix(rootScope): add support for watchCollection to watch an object which does not inherit from Object
Closes #9964
2015-09-07 22:08:02 +01:00
HeberLZ 7ea2c7f36e fix(ngRepeat): add support to iterate an object's properties even if it does not inherit from Object
Closes #9964
2015-09-07 22:06:41 +01:00
Bernie Telles 912cbdd468 docs($rootScope): explain why watchExpression should be idempotent
This attempts to clarify the rationale. The previous sentence was a bit ambiguous.

Closes #9963
2015-09-07 21:37:50 +01:00
Jason Bedard 0202663e93 perf(Angular): only create new collection in getBlockNodes if the block has changed
Closes #9899
2015-09-07 21:34:22 +01:00
=Florian Bernstein 159bbf11ac docs($compile): improve documentation on directive $scope usage
Add information about the behavior of several directives, especially of
their scopes when applied on a single element.

Closes #5761
Closes #9727
2015-09-07 21:05:34 +01:00
Peter Bacon Darwin 9d2cc8341c test($http): remove use of deprecated success and error calls in tests 2015-09-07 14:43:33 +01:00
Pawel Kozlowski 38520a1a73 fix($http): propagate status -1 for timed out requests
Fixes #4491
Closes #8756
2015-09-07 14:30:26 +01:00
Izhaki 470eb37d29 docs(guide/directive): clarification on the 'matches' terminology
Closes #8120
2015-09-07 12:19:09 +01:00
Peter Bacon Darwin 4baf25b3ce test(ng-options): add tests for option element with no value attribute
See #6519
2015-09-07 12:01:34 +01:00
Peter Bacon Darwin eb193686a5 test(select): add tests for option element with no value attribute
See #6519
2015-09-07 12:01:33 +01:00
Peter Bacon Darwin 4bebe7830b test(ngOptions): fix typo 2015-09-07 12:01:33 +01:00
Georgios Kalpakas 146cbf7eaa style(ngAria): make JSCS happy again 2015-09-07 12:54:38 +03:00
Marcy Sutton b8e356191f doc(ngAria): update the accessibility guide
Closes #12262
2015-09-07 12:16:36 +03:00
marcysutton f48244ce5e fix(ngAria): clean up tabindex usage
* Do not put tabindex on native controls using ng-model or ng-click
* Uses a single nodeBlacklist to limit which elements receive support

Closes #11500
2015-09-07 12:13:46 +03:00
thorn0 ebba426c0c docs($compile): clarify 'sharing' controllers
The current wording may make the reader erroneously think that one controller
can belong to multiple directives.

Closes #12768
2015-09-06 11:36:52 +03:00
Martin Staffa aa11dfc162 style(formSpec): fix indentation 2015-09-04 11:42:52 +02:00
Martin Staffa c6110e8b08 fix(form, ngModel): correctly notify parent form when children are added
Test that re-added controls propagate validity changes to the parent form.

Ensure that when a form / control that was removed and then attached
to a different parent, is renamed / deleted, the new parent will
be notified of the change.

Document that dynamic adding / removing of controls may require manually
propagating the current state of the control to the parent form.
2015-09-04 11:42:51 +02:00
Martin Staffa 290b5049c2 fix(ngModel): remove reference to parentForm from removed control
This fixes cases where the control gets removed, but the control's
element stays in the DOM.
Previously, if the removed control's validity changed, a reference to
it would again be stored on its former parent form.

Fixes #12263
2015-09-04 11:42:51 +02:00
Martin Staffa f8a07dd9fe refactor(form, ngModel): streamline how controls are added to parent forms
This delegates setting the control's parentForm to the parentForm's
$addControl method. This way, the model controller saves one instance
of looking up the parentForm controller. The form controller keeps two
lookups (one for its own ctrl, one for the optional parent).

This also fixes adding the parentForm in the following case:
- a control is removed from a parent, but its corresponding DOM
element is not destroyed
- the control is then re-added to the form

Before the fix, the control's parentForm was only set once during
controller initialization, so the the parentForm would not be set on
the control in that specific case.
2015-09-04 11:42:51 +02:00
Georgios Kalpakas 6f39f10827 fix($httpBackend): send null when post-data is undefined
IE11 (and maybe others) converts an `undefined` argument to `xhr.send()` to
string (`'undefined'`) for certain request methods (e.g. DELETE). This
causes the request to appear having a body, when it shouldn't.

Fixes #12141
Fixes #12739

Closes
2015-09-03 14:08:16 +03:00
Peter Bacon Darwin c3a654b7c8 fix($animate): invalid CSS class names should not break subsequent elements
The postDigest handler was not being added if the first element in
to modify the CSS classes contained invalid CSS class names. This meant
that subsequent valid CSS class changes were not being handled since we
were not then adding the handler for those correct cases.

Closes #12674
Closes #12725
2015-09-02 12:45:11 +01:00
Peter Bacon Darwin e7293daf2a refactor($animate): move CSS class update functions out of closure 2015-09-02 12:34:08 +01:00
Mike c71d414a95 docs(doc_widgets.css): remove dead link
This removes a dead link (https://bitbucket.org/alexg/syntaxhighlighter/issues/177/superfluous-vertical-scrollbars-in-chrome)
which linked to an issue on Bitbucket that no longer exists due to
the project moving to GitHub.

Closes #12710
2015-09-01 23:45:46 +02:00
Martin Staffa 06d4e18cda docs(faq): clarify browser support
Closes #12728
2015-09-01 23:37:00 +02:00
Tim Whitbeck 966e01cf26 docs(input): mention ngMin and ngMax for all date input types
Closes #11636
Closes #12244
2015-09-01 23:29:40 +02:00
Lucas Galfaso 67afd9dc63 docs(CHANGELOG): Add breaking change notice for issue 12506
Closes #12705
2015-09-01 22:14:38 +02:00
Martin Staffa 4175860af1 docs(ngModel): improve the $setViewValue documentation
- reorder the paragraphs to highlight more important info
- clarify what can / should be passed to the method,
and what to (not) expect from it
- clarify when the method will trigger a digest

Closes #12713
Closes #11121
Closes #12498
2015-09-01 19:38:11 +02:00
Martin Staffa 6fb90bda9a docs(guide/Directives): clarify what compile means
Closes #11908
2015-09-01 18:06:23 +02:00
Nabil Kadimi 770dd2dcfd docs(misc/Downloading): use the latest stable
Closes #12534
2015-09-01 17:59:21 +02:00
Martin Staffa 0ff7bba2e3 test(select): clean up and improve the option directive tests
- add tests to ensure options with interpolated text are added / updated
- refactor tests for interpolated option values to use the
standard compile helper defined in the spec file.
- rephrase some test descriptions for clarity

Closes #12580
2015-08-31 23:54:13 +02:00
Martin Staffa 82b0929e4e fix(select): update option if interpolated value attribute changes
This is for options added without ngOptions.
Previously, an option with an interpolated value attribute would
not be updated if the binding changed, i.e. the select controller would
not recognize the changed option. Now the value attribute will
be observed if it contains an interpolation.

Closes #12005
Closes #12582
2015-08-31 23:18:19 +02:00
Lucas Galfaso 7d2c6eeef8 fix($parse): assign returns the new value
The `.assign` function returns the new value.
The version with csp enabled already has this behavior.

Closes #12675
Closes #12708
2015-08-31 22:40:23 +02:00
Ron Fenolio 6d8c1950a0 style(guide/Conceptual Overview): clean up conceptual table overview.
Clean up the conceptual overview table to make it cleaner and easier
to look at.

Closes #12620
2015-08-31 21:55:56 +02:00
Rex Salisbury 1a5ea22079 docs(guide/Forms): replace form with user
Referring to the `user` as `form` in the previews is confusing,
since it makes it seem as though the data being displayed is attached
to the `form` object, when the `form` object is separate.

Closes #12687
2015-08-31 21:15:01 +02:00
Joakim Blomskøld 4f9eb2c6e4 docs(form): add info about pending prop and class
Closes #12704
2015-08-31 21:11:03 +02:00
Martin Staffa 43769fb676 fix(ngModel): let aliased validator directives work on any element
`ng(Pattern|Minlength|Maxlength)` directives will now validate the
`ngModel` when on an element that is not an `input` or
a `textarea`. Previously, only their HTML5 counterparts worked on
every element.

This is because the three validators were extracted
into  separate directives (see 26d91b653a
and 1be9bb9d35), whereas the aliased
attribute handling assumes the validators will only exist on
`input|textarea` (see d9b90d7c10 and
25541c1f87).

Since `ngMin` and `ngMax` are also aliased attributes, this means
observers of `min` and `max` will be fired if `ngMin` and `ngMax`
change. This will happen on any element, even if it does not have
an `ngModel`. However, since min/max validators are only ever added
as part of the `input[number|textarea]` types, even if the element
has an `ngModel`, no validators will be added.

Finally the commit also tests that `ng-required` works on any element,
although that validator worked on all elements before this fix.

Fixes #12158
Closes #12658
2015-08-31 20:52:06 +02:00
Ziyu Wang 170cd96646 fix(docs): fix typo "Hasbang"
Closes #12712
2015-08-31 10:27:13 +02:00
Matias Niemelä 1d18e60ef7 docs(CHANGELOG): add changes for 1.4.5 2015-08-28 12:06:35 -07:00
Matias Niemelä ea8016c4c8 fix(ngAnimate): use requestAnimationFrame to space out child animations
This reverts the previous behaviour of using foreced reflows to deal
with preparation classes in favour of a system that uses
requestAnimationFrame (RAF).

Closes #12669
Closes #12594
Closes #12655
Closes #12631
Closes #12612
Closes #12187
2015-08-27 16:56:18 -07:00
Matias Niemelä c3d5e33e18 fix($animate): $animate.enabled(false) should disable animations on $animateCss as well
Closes #12696
Closes #12685
2015-08-27 16:29:33 -07:00
Matias Niemelä 2f6b6fb7a1 fix($animateCss): do not throw errors when a closing timeout is fired on a removed element
Closes #12650
2015-08-27 14:07:59 -07:00
Lucas Galfaso ea2518fcea test($parse): fix csp setup
Run the parse interpreter for csp enabled tests
2015-08-26 23:24:02 +02:00
grsmvg 7e67e525a5 docs(jqLite): document unsupported event object as parameter in off()/unbind()
Closes #12291
2015-08-25 15:34:19 +03:00
Martin Staffa 0e001084ff fix(ngModel): validate pattern against the viewValue
Since the HTML5 pattern validation constraint validates the input value,
we should also validate against the viewValue. While this worked in
core up to Angular 1.2, in 1.3, we changed not only validation,
but the way `input[date]` and `input[number]` are handled - they parse
their input values into `Date` and `Number` respectively, which cannot
be validated by a regex.

Fixes #12344

BREAKING CHANGE:

The `ngPattern` and `pattern` directives will validate the regex
against the `viewValue` of `ngModel`, i.e. the value of the model
before the $parsers are applied. Previously, the modelValue
(the result of the $parsers) was validated.

This fixes issues where `input[date]` and `input[number]` cannot
be validated because the viewValue string is parsed into
`Date` and `Number` respectively (starting with Angular 1.3).
It also brings the directives in line with HTML5 constraint
validation, which validates against the input value.

This change is unlikely to cause applications to fail, because even
in Angular 1.2, the value that was validated by pattern could have
been manipulated by the $parsers, as all validation was done
inside this pipeline.

If you rely on the pattern being validated against the modelValue,
you must create your own validator directive that overwrites
the built-in pattern validator:

```
.directive('patternModelOverwrite', function patternModelOverwriteDirective() {
  return {
    restrict: 'A',
    require: '?ngModel',
    priority: 1,
    compile: function() {
      var regexp, patternExp;

      return {
        pre: function(scope, elm, attr, ctrl) {
          if (!ctrl) return;

          attr.$observe('pattern', function(regex) {
            /**
             * The built-in directive will call our overwritten validator
             * (see below). We just need to update the regex.
             * The preLink fn guaranetees our observer is called first.
             */
            if (isString(regex) && regex.length > 0) {
              regex = new RegExp('^' + regex + '$');
            }

            if (regex && !regex.test) {
              //The built-in validator will throw at this point
              return;
            }

            regexp = regex || undefined;
          });

        },
        post: function(scope, elm, attr, ctrl) {
          if (!ctrl) return;

          regexp, patternExp = attr.ngPattern || attr.pattern;

          //The postLink fn guarantees we overwrite the built-in pattern validator
          ctrl.$validators.pattern = function(value) {
            return ctrl.$isEmpty(value) ||
              isUndefined(regexp) ||
              regexp.test(value);
          };
        }
      };
    }
  };
});
```
2015-08-24 17:01:20 +02:00
Peter Bacon Darwin 85e3203918 chore(npm-shrinkwrap): update to dgeni-packages 0.10.19
See https://github.com/angular/dgeni-packages/commit/313a7c3832
2015-08-23 21:40:11 -04:00
Martin Staffa f95bc42cee docs(ngAnimate): fix typo
Closes #12521
2015-08-21 12:30:02 +02:00
Martin Staffa 9080d2c53c doc(ngMock.$controller): correct controller name in bindToController example
Closes #12550
2015-08-21 11:35:10 +02:00
Matias Niemelä 728f7e2a85 docs(ngAnimate): staggering example should contain duration:0s property
As of 1.4.4 this property needs to always be in the CSS code

Related #12594
Closes #12637
2015-08-20 20:54:31 +02:00
Matias Niemelä 5f704065a7 docs(ngShow): simplify the CSS transition code
The animation example contains unnecessarily complex CSS animation
code in it and the conventions are off.

Related #12631
2015-08-20 20:47:56 +02:00
Matias Niemelä 64631bf2e6 docs(ngAnimate): remove -webkit-transition properties
This property is no longer mandatory by browsers.
2015-08-20 20:47:55 +02:00
Karl Svartholm aa35b243f8 docs(ngAnimate): remove extraneous "then"
To improve readability

Closes #12634
2015-08-20 20:44:42 +02:00
Gabriel Monteagudo 1cc9c9ca9d fix($animateCss): fix parse errors on older Android WebViews
Errors are caused by reserved keywords 'finally' and 'catch'

Closes #12610
2015-08-20 20:39:41 +02:00
Matias Niemelä dc48aadd26 fix(ngAnimate): only buffer rAF requests within the animation runners
Closes #12280
2015-08-19 10:40:37 -07:00
Matias Niemelä ebce2f7253 revert: fix(core): ensure that multiple requests to requestAnimationFrame are buffered 2015-08-19 10:39:47 -07:00
Matias Niemelä d0e50fdcd0 docs(CHANGELOG): add changes for 1.3.18 2015-08-19 01:10:06 -07:00
Matias Niemelä d88167318d fix($animateCss): properly handle cancellation timeouts for follow-up animations
Prior to this fix if `$animateCss` was called multiple on the same
element with new animation data then the preceeding fallback timout
would cause the animation to cancel midway. This fix ensures that
`$animateCss` can be triggered multiple times and only when the final
timeout has passed then all animations will be closed.

Closes #12490
Closes #12359
2015-08-17 21:02:39 -07:00
Sreenivasan K 0a75a3db6e fix($animateCss): ensure failed animations clear the internal cache
Closes #12214
Closes #12518
Closes #12381
2015-08-17 16:06:25 -07:00
Lucas Galfaso b643f0d322 fix(ngResources): support IPv6 URLs
Do not confuse IPv6 URLs domains and resource parameters.

Closes #12512
Closes #12532
2015-08-16 12:36:56 +02:00
Martin Staffa 01dd588a28 docs(select): tweak description and add examples
- Change some wordings to make them more understandable
- Reorder the paragraphs so they can be read more easily as a coherent text
- Add examples for static single / multiple selects, and non-selected option
- Add example for select with repeated options
- Remove form-related info from ngOptions select (doesn't apply)
2015-08-15 18:32:50 +02:00
Meli 3d6dc3fe31 docs(select): explain how to set default value
Setting the default value in a select is a real trap for beginners, questions about how to do this on StackExchange have been view more than 40000 times in the last year.  This changes updates the documentation to make it clearer.

Closes #12546
2015-08-15 18:32:49 +02:00
Matias Niemelä 0c81e9fd25 fix($animateCss): the transitions options delay value should be applied before class application
When `options.delay` is passed into `$animateCss`the delay style would be
applied after the add/remove CSS classes are evaluated (for transitions).
At this point it is too late for the delay to be picked up (this
functionality however does work with keyfarme animations).

This patch ensures that the provided `options.delay` value is
applied before the CSS classes are applied to the element.

Closes #12584
2015-08-14 13:58:38 -07:00
Elvio Cavalcante 5df80e1854 docs(tutorial): fix test issue
Unnecessary split. The url returns a string without the hash,
resulting in an undefined value and making the test fails.

Matches the phonecat app more closely, too.

Closes #12590
2015-08-14 15:12:39 -04:00
David Czech ba9fb82f18 docs($animate): remove redundant 'animate' in link
Closes #12568
2015-08-14 12:08:32 +02:00
103 changed files with 2823 additions and 1116 deletions
+167
View File
@@ -1,3 +1,138 @@
<a name="1.4.5"></a>
# 1.4.5 permanent-internship (2015-08-28)
## Bug Fixes
- **$animate:** `$animate.enabled(false)` should disable animations on $animateCss as well
([c3d5e33e](https://github.com/angular/angular.js/commit/c3d5e33e18bd9e423e2d0678e85564fad1dba99f),
[#12696](https://github.com/angular/angular.js/issues/12696), [#12685](https://github.com/angular/angular.js/issues/12685))
- **$animateCss:**
- do not throw errors when a closing timeout is fired on a removed element
([2f6b6fb7](https://github.com/angular/angular.js/commit/2f6b6fb7a1dee0ff97c5d2959b927347eeda6e8b),
[#12650](https://github.com/angular/angular.js/issues/12650))
- fix parse errors on older Android WebViews
([1cc9c9ca](https://github.com/angular/angular.js/commit/1cc9c9ca9d9698356ea541517b3d06ce6556c01d),
[#12610](https://github.com/angular/angular.js/issues/12610))
- properly handle cancellation timeouts for follow-up animations
([d8816731](https://github.com/angular/angular.js/commit/d88167318d1c69f0dbd2101c05955eb450c34fd5),
[#12490](https://github.com/angular/angular.js/issues/12490), [#12359](https://github.com/angular/angular.js/issues/12359))
- ensure failed animations clear the internal cache
([0a75a3db](https://github.com/angular/angular.js/commit/0a75a3db6ef265389c8c955981c2fe67bb4f7769),
[#12214](https://github.com/angular/angular.js/issues/12214), [#12518](https://github.com/angular/angular.js/issues/12518), [#12381](https://github.com/angular/angular.js/issues/12381))
- the transitions options delay value should be applied before class application
([0c81e9fd](https://github.com/angular/angular.js/commit/0c81e9fd25285dd757db98d458919776a1fb62fc),
[#12584](https://github.com/angular/angular.js/issues/12584))
- **ngAnimate:**
- use requestAnimationFrame to space out child animations
([ea8016c4](https://github.com/angular/angular.js/commit/ea8016c4c8f55bc021549f342618ed869998e335),
[#12669](https://github.com/angular/angular.js/issues/12669), [#12594](https://github.com/angular/angular.js/issues/12594), [#12655](https://github.com/angular/angular.js/issues/12655), [#12631](https://github.com/angular/angular.js/issues/12631), [#12612](https://github.com/angular/angular.js/issues/12612), [#12187](https://github.com/angular/angular.js/issues/12187))
- only buffer rAF requests within the animation runners
([dc48aadd](https://github.com/angular/angular.js/commit/dc48aadd26bbf1797c1c408f63ffde99d67414a9),
[#12280](https://github.com/angular/angular.js/issues/12280))
- **ngModel:** validate pattern against the viewValue
([0e001084](https://github.com/angular/angular.js/commit/0e001084ffff8674efad289d37cb16cc4e46b50a),
[#12344](https://github.com/angular/angular.js/issues/12344))
- **ngResources:** support IPv6 URLs
([b643f0d3](https://github.com/angular/angular.js/commit/b643f0d3223a627ef813f0777524e25d2dd95371),
[#12512](https://github.com/angular/angular.js/issues/12512), [#12532](https://github.com/angular/angular.js/issues/12532))
## Breaking Changes
- **ngModel:** due to [0e001084](https://github.com/angular/angular.js/commit/0e001084ffff8674efad289d37cb16cc4e46b50a),
The `ngPattern` and `pattern` directives will validate the regex
against the `viewValue` of `ngModel`, i.e. the value of the model
before the $parsers are applied. Previously, the modelValue
(the result of the $parsers) was validated.
This fixes issues where `input[date]` and `input[number]` cannot
be validated because the viewValue string is parsed into
`Date` and `Number` respectively (starting with Angular 1.3).
It also brings the directives in line with HTML5 constraint
validation, which validates against the input value.
This change is unlikely to cause applications to fail, because even
in Angular 1.2, the value that was validated by pattern could have
been manipulated by the $parsers, as all validation was done
inside this pipeline.
If you rely on the pattern being validated against the modelValue,
you must create your own validator directive that overwrites
the built-in pattern validator:
```js
.directive('patternModelOverwrite', function patternModelOverwriteDirective() {
return {
restrict: 'A',
require: '?ngModel',
priority: 1,
compile: function() {
var regexp, patternExp;
return {
pre: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
attr.$observe('pattern', function(regex) {
/**
* The built-in directive will call our overwritten validator
* (see below). We just need to update the regex.
* The preLink fn guaranetees our observer is called first.
*/
if (isString(regex) && regex.length > 0) {
regex = new RegExp('^' + regex + '$');
}
if (regex && !regex.test) {
//The built-in validator will throw at this point
return;
}
regexp = regex || undefined;
});
},
post: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
regexp, patternExp = attr.ngPattern || attr.pattern;
//The postLink fn guarantees we overwrite the built-in pattern validator
ctrl.$validators.pattern = function(value) {
return ctrl.$isEmpty(value) ||
isUndefined(regexp) ||
regexp.test(value);
};
}
};
}
};
});
```
<a name="1.3.18"></a>
# 1.3.18 collective-penmanship (2015-08-18)
## Bug Fixes
- **$animate:**
- clear class animations cache if animation is not started
([2c03a357](https://github.com/angular/angular.js/commit/2c03a3574336ed814d020cf7ba36cee5b87e65b5),
[#12604](https://github.com/angular/angular.js/issues/12604), [#12603](https://github.com/angular/angular.js/issues/12603))
- do not throw errors if element is removed before animation starts
([6b72598b](https://github.com/angular/angular.js/commit/6b72598b87022e1dd96bddc4451e007ef0601579),
[#10205](https://github.com/angular/angular.js/issues/10205))
- **ngModel:** correct minErr usage for correct doc creation
([64a142b5](https://github.com/angular/angular.js/commit/64a142b58ed0a0e3896d82f3f9ce35373548d0ff),
[#12386](https://github.com/angular/angular.js/issues/12386), [#12416](https://github.com/angular/angular.js/issues/12416))
<a name="1.4.4"></a>
# 1.4.4 pylon-requirement (2015-08-13)
@@ -122,6 +257,38 @@ and/or `beforeRemoveClass` then the CSS classes will not be applied
in time for the children (and the parent class-based animation will not
be cancelled by any child animations).
- **$q** due to [6838c979](https://github.com/angular/angular.js/commit/6838c979451c109d959a15035177ccee715ccf19),
When writing tests, there is no need to call `$timeout.flush()` to resolve a call to `$q.when` with a value.
The previous behavior involved creating an extra promise that needed to be resolved. This is no longer needed when
`$q.when` is called with a value. In the case that the test is not aware if `$q.when` is called with a value or
another promise, it is possible to replace `$timeout.flush();` with `$timeout.flush(0);`.
```js
describe('$q.when', function() {
it('should not need a call to $timeout.flush() to resolve already resolved promises',
inject(function($q, $timeout) {
$q.when('foo');
// In Angular 1.4.3 a call to `$timeout.flush();` was needed
$timeout.verifyNoPendingTasks();
}));
it('should accept $timeout.flush(0) when not sure if $q.when was called with a value or a promise',
inject(function($q, $timeout) {
$q.when('foo');
$timeout.flush(0);
$timeout.verifyNoPendingTasks();
}));
it('should need a call to $timeout.flush() to resolve $q.when when called with a promise',
inject(function($q, $timeout) {
$q.when($q.when('foo'));
$timeout.flush();
$timeout.verifyNoPendingTasks();
}));
});
```
<a name="1.4.3"></a>
# 1.4.3 foam-acceleration (2015-07-15)
+2 -1
View File
@@ -19,7 +19,7 @@ Help us keep Angular open and inclusive. Please read and follow our [Code of Con
## <a name="question"></a> Got a Question or Problem?
If you have questions about how to use AngularJS, please direct these to the [Google Group][groups]
discussion list or [StackOverflow][stackoverflow]. We are also available on [IRC][irc].
discussion list or [StackOverflow][stackoverflow]. We are also available on [IRC][irc] and [Gitter][gitter].
## <a name="issue"></a> Found an Issue?
If you find a bug in the source code or a mistake in the documentation, you can help us by
@@ -268,6 +268,7 @@ You can find out more detailed information about contributing in the
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
[dev-doc]: https://docs.angularjs.org/guide
[github]: https://github.com/angular/angular.js
[gitter]: https://gitter.im/angular/angular.js
[groups]: https://groups.google.com/forum/?fromgroups#!forum/angular
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
[irc]: http://webchat.freenode.net/?channels=angularjs&uio=d4
+1
View File
@@ -93,6 +93,7 @@ var angularFiles = {
'ngAnimate': [
'src/ngAnimate/shared.js',
'src/ngAnimate/body.js',
'src/ngAnimate/rafScheduler.js',
'src/ngAnimate/animateChildrenDirective.js',
'src/ngAnimate/animateCss.js',
'src/ngAnimate/animateCssDriver.js',
+1 -1
View File
@@ -56,7 +56,7 @@ li.doc-example-live {
}
div.syntaxhighlighter {
padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars http://is.gd/gSMgC */
padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars */
}
/* TABS - tutorial environment navigation */
@@ -90,7 +90,7 @@
<li class="disabled"><a href="http://angularjs.org/">Why AngularJS?</a></li>
<li><a href="http://www.youtube.com/user/angularjs">Watch</a></li>
<li><a href="tutorial">Tutorial</a></li>
<li><a href="http://builtwith.angularjs.org/">Case Studies</a></li>
<li><a href="https://www.madewithangular.com/">Case Studies</a></li>
<li><a href="https://github.com/angular/angular-seed">Seed App project template</a></li>
<li><a href="misc/faq">FAQ</a></li>
</ul>
+1 -1
View File
@@ -301,7 +301,7 @@ it('should show example', inject(
### Fallback for legacy browsers
For browsers that support the HTML5 history API, `$location` uses the HTML5 history API to write
path and search. If the history API is not supported by a browser, `$location` supplies a Hasbang
path and search. If the history API is not supported by a browser, `$location` supplies a Hashbang
URL. This frees you from having to worry about whether the browser viewing your app supports the
history API or not; the `$location` service makes this transparent to you.
+30 -67
View File
@@ -23,9 +23,9 @@ angular.module('myApp', ['ngAria'])...
###Using ngAria
Most of what ngAria does is only visible "under the hood". To see the module in action, once you've
added it as a dependency, you can test a few things:
* Using your favorite element inspector, look for ngAria attributes in your own code.
* Using your favorite element inspector, look for attributes added by ngAria in your own code.
* Test using your keyboard to ensure `tabindex` is used correctly.
* Fire up a screen reader such as VoiceOver to listen for ARIA support.
* Fire up a screen reader such as VoiceOver or NVDA to check for ARIA support.
[Helpful screen reader tips.](http://webaim.org/articles/screenreader_testing/)
##Supported directives
@@ -41,8 +41,8 @@ Currently, ngAria interfaces with the following directives:
<h2 id="ngmodel">ngModel</h2>
Most of ngAria's heavy lifting happens in the {@link ngModel ngModel}
directive. For elements using ngModel, special attention is paid by ngAria if that element also
Much of ngAria's heavy lifting happens in the {@link ngModel ngModel}
directive. For elements using ngModel, special attention is paid by ngAria if that element also
has a role or type of `checkbox`, `radio`, `range` or `textbox`.
For those elements using ngModel, ngAria will dynamically bind and update the following ARIA
@@ -134,10 +134,8 @@ attributes (if they have not been explicitly specified by the developer):
ngAria will also add `tabIndex`, ensuring custom elements with these roles will be reachable from
the keyboard. It is still up to **you** as a developer to **ensure custom controls will be
operable** from the keybard. Think of `ng-click` on a `<div>` or `<md-checkbox>`: you still need
to bind `ng-keypress` to make it fully operable from the keyboard. As a rule, any time you create
a widget involving user interaction, be sure to test it with your keyboard and at least one mobile
and desktop screen reader (preferably more).
accessible**. As a rule, any time you create a widget involving user interaction, be sure to test
it with your keyboard and at least one mobile and desktop screen reader.
<h2 id="ngdisabled">ngDisabled</h2>
@@ -160,7 +158,7 @@ Becomes:
```
>You can check whether a control is legitimately disabled for a screen reader by visiting
[chrome://accessibility](chrome://accessibility).
[chrome://accessibility](chrome://accessibility) and inspecting [the accessibility tree](http://www.paciellogroup.com/blog/2015/01/the-browser-accessibility-tree/).
<h2 id="ngshow">ngShow</h2>
@@ -210,16 +208,25 @@ The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redun
`display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.
<h2><span id="ngclick">ngClick</span> and <span id="ngdblclick">ngDblclick</span></h2>
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` if it isn't there
already.
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` to any element not in
a node blacklist:
To fix widespread accessibility problems with `ng-click` on div elements, ngAria will dynamically
bind keypress by default as long as the element isn't an anchor, button, input or textarea.
You can turn this functionality on or off with the `bindKeypress` configuration option. ngAria
will also add the `button` role to communicate to users of assistive technologies.
* Button
* Anchor
* Input
* Textarea
* Select
* Details/Summary
For `ng-dblclick`, you must still manually add `ng-keypress` and role to non-interactive elements such
as `div` or `taco-button` to enable keyboard access.
To fix widespread accessibility problems with `ng-click` on `div` elements, ngAria will
dynamically bind a keypress event by default as long as the element isn't in the node blacklist.
You can turn this functionality on or off with the `bindKeypress` configuration option.
ngAria will also add the `button` role to communicate to users of assistive technologies. This can
be disabled with the `bindRoleForClick` configuration option.
For `ng-dblclick`, you must still manually add `ng-keypress` and a role to non-interactive elements
such as `div` or `taco-button` to enable keyboard access.
<h3>Example</h3>
```html
@@ -260,62 +267,18 @@ The attribute magic of ngAria may not work for every scenario. To disable indivi
you can use the {@link ngAria.$ariaProvider#config config} method. Just keep in mind this will
tell ngAria to ignore the attribute globally.
<example module="ngAria_ngDisabledExample" deps="angular-aria.js">
<example module="ngAria_ngClickExample" deps="angular-aria.js">
<file name="index.html">
<style>
[role=checkbox] {
cursor: pointer;
display: inline-block;
}
[role=checkbox] .icon:before {
content: '\2610';
display: inline-block;
font-size: 2em;
line-height: 1;
vertical-align: middle;
speak: none;
}
[role=checkbox].active .icon:before {
content: '\2611';
}
</style>
<form ng-controller="formsController">
<div ng-model="someModel" show-attrs>
Div with ngModel and aria-invalid disabled
<div ng-click="someFunction" show-attrs>
&lt;div&gt; with ng-click and bindRoleForClick, tabindex set to false
</div>
<div role="checkbox" ng-model="checked" ng-class="{active: checked}"
aria-label="Custom Checkbox" ng-click="toggleCheckbox()" some-checkbox show-attrs>
<span class="icon" aria-hidden="true"></span>
Custom Checkbox for comparison
</div>
</form>
<script>
angular.module('ngAria_ngDisabledExample', ['ngAria'], function config($ariaProvider) {
angular.module('ngAria_ngClickExample', ['ngAria'], function config($ariaProvider) {
$ariaProvider.config({
ariaInvalid: false,
tabindex: true
bindRoleForClick: false,
tabindex: false
});
})
.controller('formsController', function($scope){
$scope.checked = false;
$scope.toggleCheckbox = function(){
$scope.checked = !$scope.checked;
}
})
.directive('someCheckbox', function(){
return {
restrict: 'A',
link: function($scope, $el, $attrs) {
$el.on('keypress', function(event){
event.preventDefault();
if(event.keyCode === 32 || event.keyCode === 13){
$scope.toggleCheckbox();
$scope.$apply();
}
});
}
}
})
.directive('showAttrs', function() {
return function(scope, el, attrs) {
var pre = document.createElement('pre');
+16 -16
View File
@@ -8,22 +8,22 @@
This section briefly touches on all of the important parts of AngularJS using a simple example.
For a more in-depth explanation, see the {@link tutorial/ tutorial}.
| Concept | Description |
|------------------|------------------------------------------|
|{@link concepts#template Template} | HTML with additional markup |
|{@link concepts#directive Directives} | extend HTML with custom attributes and elements |
|{@link concepts#model Model} | the data shown to the user in the view and with which the user interacts |
|{@link concepts#scope Scope} | context where the model is stored so that controllers, directives and expressions can access it |
|{@link concepts#expression Expressions} | access variables and functions from the scope |
|{@link concepts#compiler Compiler} | parses the template and instantiates directives and expressions |
|{@link concepts#filter Filter} | formats the value of an expression for display to the user |
|{@link concepts#view View} | what the user sees (the DOM) |
|{@link concepts#databinding Data Binding} | sync data between the model and the view |
|{@link concepts#controller Controller} | the business logic behind views |
|{@link concepts#di Dependency Injection} | Creates and wires objects and functions |
|{@link concepts#injector Injector} | dependency injection container |
|{@link concepts#module Module} | a container for the different parts of an app including controllers, services, filters, directives which configures the Injector |
|{@link concepts#service Service} | reusable business logic independent of views |
| Concept | Description |
|--------------------------------------------|--------------------------------------------------------------------------|
|{@link concepts#template Template} | HTML with additional markup |
|{@link concepts#directive Directives} | extend HTML with custom attributes and elements |
|{@link concepts#model Model} | the data shown to the user in the view and with which the user interacts |
|{@link concepts#scope Scope} | context where the model is stored so that controllers, directives and expressions can access it |
|{@link concepts#expression Expressions} | access variables and functions from the scope |
|{@link concepts#compiler Compiler} | parses the template and instantiates directives and expressions |
|{@link concepts#filter Filter} | formats the value of an expression for display to the user |
|{@link concepts#view View} | what the user sees (the DOM) |
|{@link concepts#databinding Data Binding} | sync data between the model and the view |
|{@link concepts#controller Controller} | the business logic behind views |
|{@link concepts#di Dependency Injection} | Creates and wires objects and functions |
|{@link concepts#injector Injector} | dependency injection container |
|{@link concepts#module Module} | a container for the different parts of an app including controllers, services, filters, directives which configures the Injector |
|{@link concepts#service Service} | reusable business logic independent of views |
## A first example: Data binding
+16 -5
View File
@@ -19,8 +19,9 @@ how to implement them.
## What are Directives?
At a high level, directives are markers on a DOM element (such as an attribute, element
name, comment or CSS class) that tell AngularJS's **HTML compiler** ({@link ng.$compile `$compile`}) to
attach a specified behavior to that DOM element or even transform the DOM element and its children.
name, comment or CSS class) that tell AngularJS's **HTML compiler** ({@link ng.$compile `$compile`})
to attach a specified behavior to that DOM element (e.g. via event listeners), or even to transform
the DOM element and its children.
Angular comes with a set of these directives built-in, like `ngBind`, `ngModel`, and `ngClass`.
Much like you create controllers and services, you can create your own directives for Angular to use.
@@ -30,7 +31,7 @@ When Angular {@link guide/bootstrap bootstraps} your application, the
<div class="alert alert-info">
**What does it mean to "compile" an HTML template?**
For AngularJS, "compilation" means attaching event listeners to the HTML to make it interactive.
For AngularJS, "compilation" means attaching directives to the HTML to make it interactive.
The reason we use the term "compile" is that the recursive process of attaching directives
mirrors the process of compiling source code in
[compiled programming languages](http://en.wikipedia.org/wiki/Compiled_languages).
@@ -42,18 +43,28 @@ mirrors the process of compiling source code in
Before we can write a directive, we need to know how Angular's {@link guide/compiler HTML compiler}
determines when to use a given directive.
In the following example, we say that the `<input>` element **matches** the `ngModel` directive.
Similar to the terminology used when an [element **matches** a selector]
(https://developer.mozilla.org/en-US/docs/Web/API/Element.matches), we say an element **matches** a
directive when the directive is part of its declaration.
In the following example, we say that the `<input>` element **matches** the `ngModel` directive
```html
<input ng-model="foo">
```
The following also **matches** `ngModel`:
The following `<input>` element also **matches** `ngModel`:
```html
<input data-ng-model="foo">
```
And the following <person> element **matches** the `person` directive:
```html
<person>{{name}}</person>
```
### Normalization
Angular **normalizes** an element's tag and attribute name to determine which elements match which
+3 -3
View File
@@ -33,7 +33,7 @@ for other directives to augment its behavior.
<input type="button" ng-click="reset()" value="Reset" />
<input type="submit" ng-click="update(user)" value="Save" />
</form>
<pre>form = {{user | json}}</pre>
<pre>user = {{user | json}}</pre>
<pre>master = {{master | json}}</pre>
</div>
@@ -95,7 +95,7 @@ and failing to satisfy its validity.
<input type="button" ng-click="reset()" value="Reset" />
<input type="submit" ng-click="update(user)" value="Save" />
</form>
<pre>form = {{user | json}}</pre>
<pre>user = {{user | json}}</pre>
<pre>master = {{master | json}}</pre>
</div>
@@ -185,7 +185,7 @@ didn't interact with a control
<input type="button" ng-click="reset(form)" value="Reset" />
<input type="submit" ng-click="update(user)" value="Save" />
</form>
<pre>form = {{user | json}}</pre>
<pre>user = {{user | json}}</pre>
<pre>master = {{master | json}}</pre>
</div>
</file>
+90 -4
View File
@@ -156,8 +156,94 @@ The syntax extension is based on a subset of the ICU MessageFormat syntax that c
gender selections. Please refer to the links in the “Further Reading” section at the bottom of this
section.
You may find it helpful to play with our [Plnkr Example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview)
as you read the examples below.
You may find it helpful to play with the following example as you read the explanations below:
<example name="message-format-example" module="messageFormatExample" deps="angular-message-format.js">
<file name="index.html">
<div ng-controller="ckCtrl">
<b>Set number of recipients</b>
<button ng-click="setNumRecipients(0)">None</button>
<button ng-click="setNumRecipients(1)">One</button>
<button ng-click="setNumRecipients(2)">Two</button>
<button ng-click="setNumRecipients(3)">Three</button>
<br><br>
<b>Sender's</b> name: <input ng-model="sender.name"> &nbsp;&nbsp;
<br><br><b>Recipients</b><br>
<div ng-repeat="recipient in recipients">
Name: <input ng-model="recipient.name"> &nbsp;&nbsp;
Gender: <button ng-click="setGender(recipient, 'male')">male</button>
<button ng-click="setGender(recipient, 'female')">female</button>
<button ng-click="setGender(recipient, 'other')">other</button>
</div>
<br><br><b>Message</b><br>
{{recipients.length, plural, offset:1
=0 {You ({{sender.name}}) gave no gifts}
=1 { {{ recipients[0].gender, select,
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.}
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.}
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.}
}}
}
one { {{ recipients[0].gender, select,
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.}
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.}
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.}
}}
}
other {You ({{sender.name}}) gave {{recipients.length}} people gifts. }
}}
<br><br><b>In an attribute</b><br>
<div attrib="{{recipients.length, plural, offset:1
=0 {You ({{sender.name}}) gave no gifts}
=1 { {{ recipients[0].gender, select,
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.}
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.}
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.}
}}
}
one { {{ recipients[0].gender, select,
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.}
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.}
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.}
}}
}
other {You ({{sender.name}}) gave {{recipients.length}} people gifts. }
}}">
This div has an attribute interpolated with messageformat. Use the DOM inspector to check it out.
</div>
</div>
</file>
<file name="app.js">
function Person(name, gender) {
this.name = name;
this.gender = gender;
}
angular.module('messageFormatExample', ['ngMessageFormat'])
.controller('ckCtrl', function ($scope, $injector, $parse) {
var people = [ new Person("Alice", "female"),
new Person("Bob", "male"),
new Person("Charlie", "male") ];
$scope.sender = new Person("Harry Potter", "male");
$scope.recipients = people.slice();
$scope.setNumRecipients = function(n) {
n = n > people.length ? people.length : n;
$scope.recipients = people.slice(0, n);
};
$scope.setGender = function(person, gender) {
person.gender = gender;
};
});
</file>
</example>
### Plural Syntax
@@ -333,9 +419,9 @@ allows you to nest plural and gender expressions in any order.
Please note that if these are intended to reach a translator and be translated, it is recommended
that the messages appear as a whole and not be split up.
### More complex example that demonstrates nesting
### Demonstration of nesting
This is taken from the [plunker example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview) linked to earlier.
This is taken from the above example.
```text
{{recipients.length, plural, offset:1
+2 -2
View File
@@ -15,14 +15,14 @@ development.
production.
To point your code to an angular script on the Google CDN server, use the following template. This
example points to the minified version 1.3.14:
example points to the minified version 1.4.5:
```
<!doctype html>
<html ng-app>
<head>
<title>My Angular App</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
</head>
<body>
</body>
+9 -3
View File
@@ -86,9 +86,15 @@ Yes. See instructions in {@link downloading}.
### What browsers does Angular work with?
We run our extensive test suite against the following browsers: Safari, Chrome, Firefox, Opera 15,
IE9 and mobile browsers (Android, Chrome Mobile, iOS Safari). See {@link guide/ie Internet
Explorer Compatibility} for more details on supporting legacy IE browsers.
We run our extensive test suite against the following browsers: the latest versions of Chrome,
Firefox, Safari, and Safari for iOs, as well as Internet Explorer versions 9-11. See {@link guide/ie
Internet Explorer Compatibility} for more details on supporting legacy IE browsers.
If a browser is untested, it doesn't mean it won't work; for example, older Android (2.3.x)
is supported in the sense that we avoid the dot notation for reserved words as property names,
but we don't actively test changes against it. You can also expect browsers to work that share
a large part of their codebase with a browser we test, such as Opera > version 12
(uses the Blink engine), or the various Firefox derivatives.
### What's Angular's performance like?
+1 -1
View File
@@ -75,7 +75,7 @@ __`test/e2e/scenarios.js`__:
query.sendKeys('nexus');
element.all(by.css('.phones li a')).first().click();
browser.getLocationAbsUrl().then(function(url) {
expect(url.split('#')[1]).toBe('/phones/nexus-s');
expect(url).toBe('/phones/nexus-s');
});
});
...
+7 -7
View File
@@ -2284,7 +2284,7 @@
"version": "0.6.1",
"dependencies": {
"wordwrap": {
"version": "0.0.2"
"version": "0.0.3"
},
"minimist": {
"version": "0.0.10"
@@ -2385,7 +2385,7 @@
}
},
"dgeni-packages": {
"version": "0.10.17",
"version": "0.10.19",
"dependencies": {
"catharsis": {
"version": "0.8.7",
@@ -2518,7 +2518,7 @@
"version": "0.3.0",
"dependencies": {
"lru-cache": {
"version": "2.6.4"
"version": "2.6.5"
},
"sigmund": {
"version": "1.0.1"
@@ -2552,7 +2552,7 @@
"version": "0.2.14",
"dependencies": {
"lru-cache": {
"version": "2.6.4"
"version": "2.6.5"
},
"sigmund": {
"version": "1.0.1"
@@ -2582,10 +2582,10 @@
"version": "0.1.6"
},
"fsevents": {
"version": "0.3.6",
"version": "0.3.8",
"dependencies": {
"nan": {
"version": "1.8.4"
"version": "2.0.5"
}
}
}
@@ -2625,7 +2625,7 @@
"version": "4.3.6"
},
"shelljs": {
"version": "0.5.1"
"version": "0.5.3"
},
"winston": {
"version": "0.7.3",
+48 -48
View File
@@ -3487,96 +3487,96 @@
"dependencies": {
"dependency-graph": {
"version": "0.1.0",
"from": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.1.0.tgz",
"from": "dependency-graph@>=0.1.0 <0.2.0",
"resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.1.0.tgz",
"dependencies": {
"underscore": {
"version": "1.4.4",
"from": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz",
"from": "underscore@1.4.4",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz"
}
}
},
"di": {
"version": "0.0.1",
"from": "https://registry.npmjs.org/di/-/di-0.0.1.tgz",
"from": "di@0.0.1",
"resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz"
},
"optimist": {
"version": "0.6.1",
"from": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
"from": "optimist@>=0.6.0 <0.7.0",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
"dependencies": {
"wordwrap": {
"version": "0.0.2",
"from": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz"
"version": "0.0.3",
"from": "wordwrap@>=0.0.2 <0.1.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
},
"minimist": {
"version": "0.0.10",
"from": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
"from": "minimist@>=0.0.1 <0.1.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
}
}
},
"q": {
"version": "0.9.7",
"from": "https://registry.npmjs.org/q/-/q-0.9.7.tgz",
"from": "q@>=0.9.7 <0.10.0",
"resolved": "https://registry.npmjs.org/q/-/q-0.9.7.tgz"
},
"validate.js": {
"version": "0.2.0",
"from": "https://registry.npmjs.org/validate.js/-/validate.js-0.2.0.tgz",
"from": "validate.js@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/validate.js/-/validate.js-0.2.0.tgz"
},
"winston": {
"version": "0.7.3",
"from": "https://registry.npmjs.org/winston/-/winston-0.7.3.tgz",
"from": "winston@>=0.7.2 <0.8.0",
"resolved": "https://registry.npmjs.org/winston/-/winston-0.7.3.tgz",
"dependencies": {
"async": {
"version": "0.2.10",
"from": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
"from": "async@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
},
"colors": {
"version": "0.6.2",
"from": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz",
"from": "colors@>=0.6.0 <0.7.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz"
},
"cycle": {
"version": "1.0.3",
"from": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz",
"from": "cycle@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz"
},
"eyes": {
"version": "0.1.8",
"from": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
"from": "eyes@>=0.1.0 <0.2.0",
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz"
},
"pkginfo": {
"version": "0.3.0",
"from": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.0.tgz",
"from": "pkginfo@>=0.3.0 <0.4.0",
"resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.0.tgz"
},
"request": {
"version": "2.16.6",
"from": "https://registry.npmjs.org/request/-/request-2.16.6.tgz",
"from": "request@>=2.16.0 <2.17.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.16.6.tgz",
"dependencies": {
"form-data": {
"version": "0.0.10",
"from": "https://registry.npmjs.org/form-data/-/form-data-0.0.10.tgz",
"from": "form-data@>=0.0.3 <0.1.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-0.0.10.tgz",
"dependencies": {
"combined-stream": {
"version": "0.0.7",
"from": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz",
"from": "combined-stream@>=0.0.4 <0.1.0",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz",
"dependencies": {
"delayed-stream": {
"version": "0.0.5",
"from": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz",
"from": "delayed-stream@0.0.5",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz"
}
}
@@ -3585,81 +3585,81 @@
},
"mime": {
"version": "1.2.11",
"from": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz",
"from": "mime@>=1.2.7 <1.3.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
},
"hawk": {
"version": "0.10.2",
"from": "https://registry.npmjs.org/hawk/-/hawk-0.10.2.tgz",
"from": "hawk@>=0.10.2 <0.11.0",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-0.10.2.tgz",
"dependencies": {
"hoek": {
"version": "0.7.6",
"from": "https://registry.npmjs.org/hoek/-/hoek-0.7.6.tgz",
"from": "hoek@>=0.7.0 <0.8.0",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-0.7.6.tgz"
},
"boom": {
"version": "0.3.8",
"from": "https://registry.npmjs.org/boom/-/boom-0.3.8.tgz",
"from": "boom@>=0.3.0 <0.4.0",
"resolved": "https://registry.npmjs.org/boom/-/boom-0.3.8.tgz"
},
"cryptiles": {
"version": "0.1.3",
"from": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.1.3.tgz",
"from": "cryptiles@>=0.1.0 <0.2.0",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.1.3.tgz"
},
"sntp": {
"version": "0.1.4",
"from": "https://registry.npmjs.org/sntp/-/sntp-0.1.4.tgz",
"from": "sntp@>=0.1.0 <0.2.0",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-0.1.4.tgz"
}
}
},
"node-uuid": {
"version": "1.4.3",
"from": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz",
"from": "node-uuid@>=1.4.0 <1.5.0",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
},
"cookie-jar": {
"version": "0.2.0",
"from": "https://registry.npmjs.org/cookie-jar/-/cookie-jar-0.2.0.tgz",
"from": "cookie-jar@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/cookie-jar/-/cookie-jar-0.2.0.tgz"
},
"aws-sign": {
"version": "0.2.0",
"from": "https://registry.npmjs.org/aws-sign/-/aws-sign-0.2.0.tgz",
"from": "aws-sign@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/aws-sign/-/aws-sign-0.2.0.tgz"
},
"oauth-sign": {
"version": "0.2.0",
"from": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.2.0.tgz",
"from": "oauth-sign@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.2.0.tgz"
},
"forever-agent": {
"version": "0.2.0",
"from": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.2.0.tgz",
"from": "forever-agent@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.2.0.tgz"
},
"tunnel-agent": {
"version": "0.2.0",
"from": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.2.0.tgz",
"from": "tunnel-agent@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.2.0.tgz"
},
"json-stringify-safe": {
"version": "3.0.0",
"from": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-3.0.0.tgz",
"from": "json-stringify-safe@>=3.0.0 <3.1.0",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-3.0.0.tgz"
},
"qs": {
"version": "0.5.6",
"from": "https://registry.npmjs.org/qs/-/qs-0.5.6.tgz",
"from": "qs@>=0.5.4 <0.6.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-0.5.6.tgz"
}
}
},
"stack-trace": {
"version": "0.0.9",
"from": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz",
"from": "stack-trace@>=0.0.0 <0.1.0",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz"
}
}
@@ -3667,8 +3667,8 @@
}
},
"dgeni-packages": {
"version": "0.10.17",
"from": "dgeni-packages@0.10.17",
"version": "0.10.19",
"from": "dgeni-packages@0.10.19",
"dependencies": {
"catharsis": {
"version": "0.8.7",
@@ -3877,9 +3877,9 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
"dependencies": {
"lru-cache": {
"version": "2.6.4",
"version": "2.6.5",
"from": "lru-cache@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.4.tgz"
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz"
},
"sigmund": {
"version": "1.0.1",
@@ -3931,9 +3931,9 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
"dependencies": {
"lru-cache": {
"version": "2.6.4",
"version": "2.6.5",
"from": "lru-cache@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.4.tgz"
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz"
},
"sigmund": {
"version": "1.0.1",
@@ -3977,14 +3977,14 @@
"resolved": "https://registry.npmjs.org/async-each/-/async-each-0.1.6.tgz"
},
"fsevents": {
"version": "0.3.6",
"version": "0.3.8",
"from": "fsevents@>=0.3.1 <0.4.0",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-0.3.6.tgz",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-0.3.8.tgz",
"dependencies": {
"nan": {
"version": "1.8.4",
"from": "nan@>=1.8.0 <2.0.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-1.8.4.tgz"
"version": "2.0.5",
"from": "nan@>=2.0.2 <3.0.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.0.5.tgz"
}
}
}
@@ -4042,9 +4042,9 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz"
},
"shelljs": {
"version": "0.5.1",
"version": "0.5.3",
"from": "shelljs@>=0.5.0 <0.6.0",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.1.tgz"
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz"
},
"winston": {
"version": "0.7.3",
+1
View File
@@ -3,6 +3,7 @@
"license": "MIT",
"branchVersion": "^1.4.0-beta.0",
"branchPattern": "1.4.*",
"distTag": "latest",
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
+4 -13
View File
@@ -95,19 +95,10 @@ function publish {
# don't publish every build to npm
if [ "${NEW_VERSION/+sha}" = "$NEW_VERSION" ] ; then
if [ "${NEW_VERSION/-}" = "$NEW_VERSION" ] ; then
if [[ $NEW_VERSION =~ ^1\.2\.[0-9]+$ ]] ; then
# publish 1.2.x releases with the appropriate tag
# this ensures that `npm install` by default will not grab `1.2.x` releases
npm publish --tag=old
else
# publish releases as "latest"
npm publish
fi
else
# publish prerelease builds with the beta tag
npm publish --tag=beta
fi
# get the npm dist-tag from a custom property (distTag) in package.json
DIST_TAG=$(readJsonProp "package.json" "distTag")
echo "-- Publishing to npm as $DIST_TAG"
npm publish --tag=$DIST_TAG
fi
cd $SCRIPT_DIR
+18 -15
View File
@@ -825,6 +825,8 @@ function copy(source, destination, stackSource, stackDest) {
} else if (isRegExp(source)) {
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
destination.lastIndex = source.lastIndex;
} else if (isFunction(source.cloneNode)) {
destination = source.cloneNode(true);
} else {
var emptyObject = Object.create(getPrototypeOf(source));
return copy(source, emptyObject, stackSource, stackDest);
@@ -975,7 +977,7 @@ function equals(o1, o2) {
for (key in o2) {
if (!(key in keySet) &&
key.charAt(0) !== '$' &&
o2[key] !== undefined &&
isDefined(o2[key]) &&
!isFunction(o2[key])) return false;
}
return true;
@@ -1671,10 +1673,9 @@ function bindJQuery() {
// bind to jQuery if present;
var jqName = jq();
jQuery = window.jQuery; // use default jQuery.
if (isDefined(jqName)) { // `ngJq` present
jQuery = jqName === null ? undefined : window[jqName]; // if empty; use jqLite. if not empty, use jQuery specified by `ngJq`.
}
jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
!jqName ? undefined : // use jqLite
window[jqName]; // use jQuery specified by `ngJq`
// Use jQuery if it exists with proper functionality, otherwise default to us.
// Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
@@ -1779,22 +1780,24 @@ function getter(obj, path, bindFnToScope) {
/**
* Return the DOM siblings between the first and last node in the given array.
* @param {Array} array like object
* @returns {jqLite} jqLite collection containing the nodes
* @returns {Array} the inputted object or a jqLite collection containing the nodes
*/
function getBlockNodes(nodes) {
// TODO(perf): just check if all items in `nodes` are siblings and if they are return the original
// collection, otherwise update the original collection.
// TODO(perf): update `nodes` instead of creating a new object?
var node = nodes[0];
var endNode = nodes[nodes.length - 1];
var blockNodes = [node];
var blockNodes;
do {
node = node.nextSibling;
if (!node) break;
blockNodes.push(node);
} while (node !== endNode);
for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
if (blockNodes || nodes[i] !== node) {
if (!blockNodes) {
blockNodes = jqLite(slice.call(nodes, 0, i));
}
blockNodes.push(node);
}
}
return jqLite(blockNodes);
return blockNodes || nodes;
}
+3 -2
View File
@@ -98,8 +98,9 @@
* @name angular.version
* @module ng
* @description
* An object that contains information about the current AngularJS version. This object has the
* following properties:
* An object that contains information about the current AngularJS version.
*
* This object has the following properties:
*
* - `full` `{string}` Full version string, such as "0.9.18".
* - `major` `{number}` Major version number, such as "0".
+7 -8
View File
@@ -65,7 +65,7 @@
* - [`html()`](http://api.jquery.com/html/)
* - [`next()`](http://api.jquery.com/next/) - Does not support selectors
* - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
* - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors
* - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
* - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
* - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
* - [`prepend()`](http://api.jquery.com/prepend/)
@@ -79,7 +79,7 @@
* - [`text()`](http://api.jquery.com/text/)
* - [`toggleClass()`](http://api.jquery.com/toggleClass/)
* - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
* - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces
* - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter
* - [`val()`](http://api.jquery.com/val/)
* - [`wrap()`](http://api.jquery.com/wrap/)
*
@@ -450,7 +450,7 @@ function jqLiteInheritedData(element, name, value) {
while (element) {
for (var i = 0, ii = names.length; i < ii; i++) {
if ((value = jqLite.data(element, names[i])) !== undefined) return value;
if (isDefined(value = jqLite.data(element, names[i]))) return value;
}
// If dealing with a document fragment node with a host element, and no parent, use the host
@@ -556,9 +556,8 @@ function getBooleanAttrName(element, name) {
return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
}
function getAliasedAttrName(element, name) {
var nodeName = element.nodeName;
return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name];
function getAliasedAttrName(name) {
return ALIASED_ATTR[name];
}
forEach({
@@ -695,7 +694,7 @@ forEach({
// in a way that survives minification.
// jqLiteEmpty takes no arguments but is a setter.
if (fn !== jqLiteEmpty &&
(((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) {
(isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
if (isObject(arg1)) {
// we are a write, but the object properties are the key/values
@@ -716,7 +715,7 @@ forEach({
// TODO: do we still need this?
var value = fn.$dv;
// Only if we have $dv do we iterate over all, otherwise it is just the first element.
var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount;
var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
for (var j = 0; j < jj; j++) {
var nodeValue = fn(this[j], arg1, arg2);
value = value ? value + nodeValue : nodeValue;
+55 -50
View File
@@ -105,61 +105,66 @@ var $$CoreAnimateQueueProvider = function() {
}
};
function addRemoveClassesPostDigest(element, add, remove) {
var classVal, data = postDigestQueue.get(element);
if (!data) {
postDigestQueue.put(element, data = {});
postDigestElements.push(element);
}
var updateData = function(classes, value) {
var changed = false;
if (classes) {
classes = isString(classes) ? classes.split(' ') :
isArray(classes) ? classes : [];
forEach(classes, function(className) {
if (className) {
changed = true;
data[className] = value;
}
});
}
return changed;
};
var classesAdded = updateData(add, true);
var classesRemoved = updateData(remove, false);
if ((!classesAdded && !classesRemoved) || postDigestElements.length > 1) return;
$rootScope.$$postDigest(function() {
forEach(postDigestElements, function(element) {
var data = postDigestQueue.get(element);
if (data) {
var existing = splitClasses(element.attr('class'));
var toAdd = '';
var toRemove = '';
forEach(data, function(status, className) {
var hasClass = !!existing[className];
if (status !== hasClass) {
if (status) {
toAdd += (toAdd.length ? ' ' : '') + className;
} else {
toRemove += (toRemove.length ? ' ' : '') + className;
}
}
});
forEach(element, function(elm) {
toAdd && jqLiteAddClass(elm, toAdd);
toRemove && jqLiteRemoveClass(elm, toRemove);
});
postDigestQueue.remove(element);
function updateData(data, classes, value) {
var changed = false;
if (classes) {
classes = isString(classes) ? classes.split(' ') :
isArray(classes) ? classes : [];
forEach(classes, function(className) {
if (className) {
changed = true;
data[className] = value;
}
});
}
return changed;
}
postDigestElements.length = 0;
function handleCSSClassChanges() {
forEach(postDigestElements, function(element) {
var data = postDigestQueue.get(element);
if (data) {
var existing = splitClasses(element.attr('class'));
var toAdd = '';
var toRemove = '';
forEach(data, function(status, className) {
var hasClass = !!existing[className];
if (status !== hasClass) {
if (status) {
toAdd += (toAdd.length ? ' ' : '') + className;
} else {
toRemove += (toRemove.length ? ' ' : '') + className;
}
}
});
forEach(element, function(elm) {
toAdd && jqLiteAddClass(elm, toAdd);
toRemove && jqLiteRemoveClass(elm, toRemove);
});
postDigestQueue.remove(element);
}
});
postDigestElements.length = 0;
}
function addRemoveClassesPostDigest(element, add, remove) {
var data = postDigestQueue.get(element) || {};
var classesAdded = updateData(data, add, true);
var classesRemoved = updateData(data, remove, false);
if (classesAdded || classesRemoved) {
postDigestQueue.put(element, data);
postDigestElements.push(element);
if (postDigestElements.length === 1) {
$rootScope.$$postDigest(handleCSSClassChanges);
}
}
}
}];
};
+2 -2
View File
@@ -35,10 +35,10 @@ var $CoreAnimateCssProvider = function() {
return this.getPromise().then(f1,f2);
},
'catch': function(f1) {
return this.getPromise().catch(f1);
return this.getPromise()['catch'](f1);
},
'finally': function(f1) {
return this.getPromise().finally(f1);
return this.getPromise()['finally'](f1);
}
};
+11 -6
View File
@@ -87,7 +87,7 @@ function Browser(window, document, $log, $sniffer) {
var cachedState, lastHistoryState,
lastBrowserUrl = location.href,
baseElement = document.find('base'),
reloadLocation = null;
pendingLocation = null;
cacheState();
lastHistoryState = cachedState;
@@ -147,8 +147,8 @@ function Browser(window, document, $log, $sniffer) {
// Do the assignment again so that those two variables are referentially identical.
lastHistoryState = cachedState;
} else {
if (!sameBase || reloadLocation) {
reloadLocation = url;
if (!sameBase || pendingLocation) {
pendingLocation = url;
}
if (replace) {
location.replace(url);
@@ -157,14 +157,18 @@ function Browser(window, document, $log, $sniffer) {
} else {
location.hash = getHash(url);
}
if (location.href !== url) {
pendingLocation = url;
}
}
return self;
// getter
} else {
// - reloadLocation is needed as browsers don't allow to read out
// the new location.href if a reload happened.
// - pendingLocation is needed as browsers don't allow to read out
// the new location.href if a reload happened or if there is a bug like in iOS 9 (see
// https://openradar.appspot.com/22186109).
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
return reloadLocation || location.href.replace(/%27/g,"'");
return pendingLocation || location.href.replace(/%27/g,"'");
}
};
@@ -186,6 +190,7 @@ function Browser(window, document, $log, $sniffer) {
urlChangeInit = false;
function cacheStateAndFireUrlChange() {
pendingLocation = null;
cacheState();
fireUrlChange();
}
+2 -2
View File
@@ -67,10 +67,10 @@
$scope.keys = [];
$scope.cache = $cacheFactory('cacheId');
$scope.put = function(key, value) {
if ($scope.cache.get(key) === undefined) {
if (isUndefined($scope.cache.get(key))) {
$scope.keys.push(key);
}
$scope.cache.put(key, value === undefined ? null : value);
$scope.cache.put(key, isUndefined(value) ? null : value);
};
}]);
</file>
+38 -17
View File
@@ -146,18 +146,24 @@
* and other directives used in the directive's template will also be excluded from execution.
*
* #### `scope`
* **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the
* same element request a new scope, only one new scope is created. The new scope rule does not
* apply for the root of the template since the root of the template always gets a new scope.
* The scope property can be `true`, an object or a falsy value:
*
* **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from
* normal scope in that it does not prototypically inherit from the parent scope. This is useful
* when creating reusable components, which should not accidentally read or modify data in the
* parent scope.
* * **falsy:** No scope will be created for the directive. The directive will use its parent's scope.
*
* The 'isolate' scope takes an object hash which defines a set of local scope properties
* derived from the parent scope. These local properties are useful for aliasing values for
* templates. Locals definition is a hash of local scope property to its source:
* * **`true`:** A new child scope that prototypically inherits from its parent will be created for
* the directive's element. If multiple directives on the same element request a new scope,
* only one new scope is created. The new scope rule does not apply for the root of the template
* since the root of the template always gets a new scope.
*
* * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The
* 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent
* scope. This is useful when creating reusable components, which should not accidentally read or modify
* data in the parent scope.
*
* The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
* directive's element. These local properties are useful for aliasing values for templates. The keys in
* the object hash map to the name of the property on the isolate scope; the values define how the property
* is bound to the parent scope, via matching attributes on the directive's element:
*
* * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
* always a string since DOM attributes are strings. If no `attr` name is specified then the
@@ -190,6 +196,20 @@
* For example, if the expression is `increment(amount)` then we can specify the amount value
* by calling the `localFn` as `localFn({amount: 22})`.
*
* In general it's possible to apply more than one directive to one element, but there might be limitations
* depending on the type of scope required by the directives. The following points will help explain these limitations.
* For simplicity only two directives are taken into account, but it is also applicable for several directives:
*
* * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
* * **child scope** + **no scope** => Both directives will share one single child scope
* * **child scope** + **child scope** => Both directives will share one single child scope
* * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
* its parent's scope
* * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
* be applied to the same element.
* * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
* cannot be applied to the same element.
*
*
* #### `bindToController`
* When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
@@ -198,7 +218,7 @@
*
* #### `controller`
* Controller constructor function. The controller is instantiated before the
* pre-linking phase and it is shared with other directives (see
* pre-linking phase and can be accessed by other directives (see
* `require` attribute). This allows the directives to communicate with each other and augment
* each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
*
@@ -238,9 +258,10 @@
*
* #### `controllerAs`
* Identifier name for a reference to the controller in the directive's scope.
* This allows the controller to be referenced from the directive template. The directive
* needs to define a scope for this configuration to be used. Useful in the case when
* directive is used as component.
* This allows the controller to be referenced from the directive template. This is especially
* useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
* to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
* `controllerAs` reference might overwrite a property that already exists on the parent scope.
*
*
* #### `restrict`
@@ -1077,7 +1098,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var node = this.$$element[0],
booleanKey = getBooleanAttrName(node, key),
aliasedKey = getAliasedAttrName(node, key),
aliasedKey = getAliasedAttrName(key),
observer = key,
nodeName;
@@ -1144,7 +1165,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
if (writeAttr !== false) {
if (value === null || value === undefined) {
if (value === null || isUndefined(value)) {
this.$$element.removeAttr(attrName);
} else {
this.$$element.attr(attrName, value);
@@ -2110,7 +2131,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
i = 0, ii = directives.length; i < ii; i++) {
try {
directive = directives[i];
if ((maxPriority === undefined || maxPriority > directive.priority) &&
if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
directive.restrict.indexOf(location) != -1) {
if (startAttrName) {
directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
+1 -1
View File
@@ -39,7 +39,7 @@ function $$CookieReader($document) {
// the first value that is seen for a cookie is the most
// specific one. values for the same cookie name that
// follow are for less specific paths.
if (lastCookies[name] === undefined) {
if (isUndefined(lastCookies[name])) {
lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
}
}
+38 -14
View File
@@ -25,6 +25,7 @@ function nullFormRenameControl(control, name) {
* @property {boolean} $dirty True if user has already interacted with the form.
* @property {boolean} $valid True if all of the containing forms and controls are valid.
* @property {boolean} $invalid True if at least one containing control or form is invalid.
* @property {boolean} $pending True if at least one containing control or form is pending.
* @property {boolean} $submitted True if user has submitted the form even if its invalid.
*
* @property {Object} $error Is an object hash, containing references to controls or
@@ -64,8 +65,6 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
var form = this,
controls = [];
var parentForm = form.$$parentForm = element.parent().controller('form') || nullFormCtrl;
// init state
form.$error = {};
form.$$success = {};
@@ -76,8 +75,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
form.$valid = true;
form.$invalid = false;
form.$submitted = false;
parentForm.$addControl(form);
form.$$parentForm = nullFormCtrl;
/**
* @ngdoc method
@@ -116,11 +114,23 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
/**
* @ngdoc method
* @name form.FormController#$addControl
* @param {object} control control object, either a {@link form.FormController} or an
* {@link ngModel.NgModelController}
*
* @description
* Register a control with the form.
* Register a control with the form. Input elements using ngModelController do this automatically
* when they are linked.
*
* Input elements using ngModelController do this automatically when they are linked.
* Note that the current state of the control will not be reflected on the new parent form. This
* is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
* state.
*
* However, if the method is used programmatically, for example by adding dynamically created controls,
* or controls that have been previously removed without destroying their corresponding DOM element,
* it's the developers responsiblity to make sure the current state propagates to the parent form.
*
* For example, if an input control is added that is already `$dirty` and has `$error` properties,
* calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
*/
form.$addControl = function(control) {
// Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
@@ -131,6 +141,8 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
if (control.$name) {
form[control.$name] = control;
}
control.$$parentForm = form;
};
// Private API: rename a form control
@@ -147,11 +159,18 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
/**
* @ngdoc method
* @name form.FormController#$removeControl
* @param {object} control control object, either a {@link form.FormController} or an
* {@link ngModel.NgModelController}
*
* @description
* Deregister a control from the form.
*
* Input elements using ngModelController do this automatically when they are destroyed.
*
* Note that only the removed control's validation state (`$errors`etc.) will be removed from the
* form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
* different from case to case. For example, removing the only `$dirty` control from a form may or
* may not mean that the form is still `$dirty`.
*/
form.$removeControl = function(control) {
if (control.$name && form[control.$name] === control) {
@@ -168,6 +187,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
});
arrayRemove(controls, control);
control.$$parentForm = nullFormCtrl;
};
@@ -204,7 +224,6 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
delete object[property];
}
},
parentForm: parentForm,
$animate: $animate
});
@@ -223,7 +242,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
$animate.addClass(element, DIRTY_CLASS);
form.$dirty = true;
form.$pristine = false;
parentForm.$setDirty();
form.$$parentForm.$setDirty();
};
/**
@@ -279,7 +298,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
form.$setSubmitted = function() {
$animate.addClass(element, SUBMITTED_CLASS);
form.$submitted = true;
parentForm.$setSubmitted();
form.$$parentForm.$setSubmitted();
};
}
@@ -329,6 +348,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
* # CSS classes
* - `ng-valid` is set if the form is valid.
* - `ng-invalid` is set if the form is invalid.
* - `ng-pending` is set if the form is pending.
* - `ng-pristine` is set if the form is pristine.
* - `ng-dirty` is set if the form is dirty.
* - `ng-submitted` is set if the form was submitted.
@@ -404,7 +424,6 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
</script>
<style>
.my-form {
-webkit-transition:all linear 0.5s;
transition:all linear 0.5s;
background: transparent;
}
@@ -453,6 +472,7 @@ var formDirectiveFactory = function(isNgForm) {
var formDirective = {
name: 'form',
restrict: isNgForm ? 'EAC' : 'E',
require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
controller: FormController,
compile: function ngFormCompile(formElement, attr) {
// Setup initial state of the control
@@ -461,7 +481,9 @@ var formDirectiveFactory = function(isNgForm) {
var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
return {
pre: function ngFormPreLink(scope, formElement, attr, controller) {
pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
var controller = ctrls[0];
// if `action` attr is not present on the form, prevent the default action (submission)
if (!('action' in attr)) {
// we can't use jq events because if a form is destroyed during submission the default
@@ -490,7 +512,9 @@ var formDirectiveFactory = function(isNgForm) {
});
}
var parentFormCtrl = controller.$$parentForm;
var parentFormCtrl = ctrls[1] || controller.$$parentForm;
parentFormCtrl.$addControl(controller);
var setter = nameAttr ? getSetter(controller.$name) : noop;
if (nameAttr) {
@@ -498,13 +522,13 @@ var formDirectiveFactory = function(isNgForm) {
attr.$observe(nameAttr, function(newValue) {
if (controller.$name === newValue) return;
setter(scope, undefined);
parentFormCtrl.$$renameControl(controller, newValue);
controller.$$parentForm.$$renameControl(controller, newValue);
setter = getSetter(controller.$name);
setter(scope, controller);
});
}
formElement.on('$destroy', function() {
parentFormCtrl.$removeControl(controller);
controller.$$parentForm.$removeControl(controller);
setter(scope, undefined);
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
});
+60 -19
View File
@@ -138,9 +138,17 @@ var inputType = {
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
* valid ISO date string (yyyy-MM-dd).
* valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
* (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
* constraint validation.
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
* a valid ISO date string (yyyy-MM-dd).
* a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
* (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
* constraint validation.
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
* @param {string=} required Sets `required` validation error key if the value is not entered.
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
@@ -232,10 +240,18 @@ var inputType = {
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
* valid ISO datetime format (yyyy-MM-ddTHH:mm:ss).
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
* a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss).
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
* This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
* inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
* Note that `min` will also add native HTML5 constraint validation.
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
* This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
* inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
* Note that `max` will also add native HTML5 constraint validation.
* @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
* @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
* @param {string=} required Sets `required` validation error key if the value is not entered.
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
@@ -328,10 +344,18 @@ var inputType = {
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
* valid ISO time format (HH:mm:ss).
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be a
* valid ISO time format (HH:mm:ss).
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
* This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
* attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
* native HTML5 constraint validation.
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
* This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
* attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
* native HTML5 constraint validation.
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
* `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
* `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
* @param {string=} required Sets `required` validation error key if the value is not entered.
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
@@ -423,10 +447,18 @@ var inputType = {
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
* valid ISO week format (yyyy-W##).
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
* a valid ISO week format (yyyy-W##).
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
* This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
* attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
* native HTML5 constraint validation.
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
* This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
* attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
* native HTML5 constraint validation.
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
* @param {string=} required Sets `required` validation error key if the value is not entered.
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
@@ -520,10 +552,19 @@ var inputType = {
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be
* a valid ISO month format (yyyy-MM).
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must
* be a valid ISO month format (yyyy-MM).
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
* This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
* attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
* native HTML5 constraint validation.
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
* This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
* attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
* native HTML5 constraint validation.
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
* @param {string=} required Sets `required` validation error key if the value is not entered.
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
@@ -1285,7 +1326,7 @@ function createDateInputType(type, regexp, parseDate, format) {
}
function parseObservedDateValue(val) {
return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;
return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
}
};
}
+2 -2
View File
@@ -60,7 +60,7 @@ var ngBindDirective = ['$compile', function($compile) {
$compile.$$addBindingInfo(element, attr.ngBind);
element = element[0];
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
element.textContent = value === undefined ? '' : value;
element.textContent = isUndefined(value) ? '' : value;
});
};
}
@@ -128,7 +128,7 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
$compile.$$addBindingInfo(element, interpolateFn.expressions);
element = element[0];
attr.$observe('ngBindTemplate', function(value) {
element.textContent = value === undefined ? '' : value;
element.textContent = isUndefined(value) ? '' : value;
});
};
}
-1
View File
@@ -262,7 +262,6 @@ function classDirective(name, selector) {
</file>
<file name="style.css">
.base-class {
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
}
-1
View File
@@ -61,7 +61,6 @@
}
.animate-if.ng-enter, .animate-if.ng-leave {
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
}
-1
View File
@@ -85,7 +85,6 @@
}
.slide-animate.ng-enter, .slide-animate.ng-leave {
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
position:absolute;
+9 -7
View File
@@ -10,16 +10,18 @@
* current scope.
*
* <div class="alert alert-danger">
* The only appropriate use of `ngInit` is for aliasing special properties of
* {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you
* should use {@link guide/controller controllers} rather than `ngInit`
* to initialize values on a scope.
* This directive can be abused to add unnecessary amounts of logic into your templates.
* There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of
* {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via
* server side scripting. Besides these few cases, you should use {@link guide/controller controllers}
* rather than `ngInit` to initialize values on a scope.
* </div>
*
* <div class="alert alert-warning">
* **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
* sure you have parenthesis for correct precedence:
* **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
* sure you have parentheses to ensure correct operator precedence:
* <pre class="prettyprint">
* `<div ng-init="test1 = (data | orderBy:'name')"></div>`
* `<div ng-init="test1 = ($index | toString)"></div>`
* </pre>
* </div>
*
+40 -34
View File
@@ -236,7 +236,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
this.$$success = {}; // keep valid keys here
this.$pending = undefined; // keep pending keys here
this.$name = $interpolate($attr.name || '', false)($scope);
this.$$parentForm = nullFormCtrl;
var parsedNgModel = $parse($attr.ngModel),
parsedNgModelAssign = parsedNgModel.assign,
@@ -316,8 +316,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
return isUndefined(value) || value === '' || value === null || value !== value;
};
var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
currentValidationRunId = 0;
var currentValidationRunId = 0;
/**
* @ngdoc method
@@ -350,7 +349,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
unset: function(object, property) {
delete object[property];
},
parentForm: parentForm,
$animate: $animate
});
@@ -388,7 +386,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
ctrl.$pristine = false;
$animate.removeClass($element, PRISTINE_CLASS);
$animate.addClass($element, DIRTY_CLASS);
parentForm.$setDirty();
ctrl.$$parentForm.$setDirty();
};
/**
@@ -558,7 +556,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
function processParseErrors() {
var errorKey = ctrl.$$parserName || 'parse';
if (parserValid === undefined) {
if (isUndefined(parserValid)) {
setValidity(errorKey, null);
} else {
if (!parserValid) {
@@ -728,37 +726,47 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* @description
* Update the view value.
*
* This method should be called when an input directive want to change the view value; typically,
* this is done from within a DOM event handler.
* This method should be called when a control wants to change the view value; typically,
* this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
* directive calls it when the value of the input changes and {@link ng.directive:select select}
* calls it when an option is selected.
*
* For example {@link ng.directive:input input} calls it when the value of the input changes and
* {@link ng.directive:select select} calls it when an option is selected.
*
* If the new `value` is an object (rather than a string or a number), we should make a copy of the
* object before passing it to `$setViewValue`. This is because `ngModel` does not perform a deep
* watch of objects, it only looks for a change of identity. If you only change the property of
* the object then ngModel will not realise that the object has changed and will not invoke the
* `$parsers` and `$validators` pipelines.
*
* For this reason, you should not change properties of the copy once it has been passed to
* `$setViewValue`. Otherwise you may cause the model value on the scope to change incorrectly.
*
* When this method is called, the new `value` will be staged for committing through the `$parsers`
* When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
* and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
* value sent directly for processing, finally to be applied to `$modelValue` and then the
* **expression** specified in the `ng-model` attribute.
*
* Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
* **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners,
* in the `$viewChangeListeners` list, are called.
*
* In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
* and the `default` trigger is not listed, all those actions will remain pending until one of the
* `updateOn` events is triggered on the DOM element.
* All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
* directive is used with a custom debounce for this particular event.
* Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
* is specified, once the timer runs out.
*
* Note that calling this function does not trigger a `$digest`.
* When used with standard inputs, the view value will always be a string (which is in some cases
* parsed into another type, such as a `Date` object for `input[date]`.)
* However, custom controls might also pass objects to this method. In this case, we should make
* a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
* perform a deep watch of objects, it only looks for a change of identity. If you only change
* the property of the object then ngModel will not realise that the object has changed and
* will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
* not change properties of the copy once it has been passed to `$setViewValue`.
* Otherwise you may cause the model value on the scope to change incorrectly.
*
* @param {string} value Value from the view.
* <div class="alert alert-info">
* In any case, the value passed to the method should always reflect the current value
* of the control. For example, if you are calling `$setViewValue` for an input element,
* you should pass the input DOM value. Otherwise, the control and the scope model become
* out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
* the control's DOM value in any way. If we want to change the control's DOM value
* programmatically, we should update the `ngModel` scope expression. Its new value will be
* picked up by the model controller, which will run it through the `$formatters`, `$render` it
* to update the DOM, and finally call `$validate` on it.
* </div>
*
* @param {*} value value from the view.
* @param {string} trigger Event that triggered the update.
*/
this.$setViewValue = function(value, trigger) {
@@ -935,7 +943,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
</script>
<style>
.my-input {
-webkit-transition:all linear 0.5s;
transition:all linear 0.5s;
background: transparent;
}
@@ -1022,7 +1029,7 @@ var ngModelDirective = ['$rootScope', function($rootScope) {
return {
pre: function ngModelPreLink(scope, element, attr, ctrls) {
var modelCtrl = ctrls[0],
formCtrl = ctrls[1] || nullFormCtrl;
formCtrl = ctrls[1] || modelCtrl.$$parentForm;
modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
@@ -1031,12 +1038,12 @@ var ngModelDirective = ['$rootScope', function($rootScope) {
attr.$observe('name', function(newValue) {
if (modelCtrl.$name !== newValue) {
formCtrl.$$renameControl(modelCtrl, newValue);
modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
}
});
scope.$on('$destroy', function() {
formCtrl.$removeControl(modelCtrl);
modelCtrl.$$parentForm.$removeControl(modelCtrl);
});
},
post: function ngModelPostLink(scope, element, attr, ctrls) {
@@ -1235,7 +1242,7 @@ var ngModelOptionsDirective = function() {
var that = this;
this.$options = copy($scope.$eval($attrs.ngModelOptions));
// Allow adding/overriding bound events
if (this.$options.updateOn !== undefined) {
if (isDefined(this.$options.updateOn)) {
this.$options.updateOnDefault = false;
// extract "default" pseudo-event from list of events that can trigger a model update
this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
@@ -1258,7 +1265,6 @@ function addSetValidityMethod(context) {
classCache = {},
set = context.set,
unset = context.unset,
parentForm = context.parentForm,
$animate = context.$animate;
classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
@@ -1266,7 +1272,7 @@ function addSetValidityMethod(context) {
ctrl.$setValidity = setValidity;
function setValidity(validationErrorKey, state, controller) {
if (state === undefined) {
if (isUndefined(state)) {
createAndSet('$pending', validationErrorKey, controller);
} else {
unsetAndCleanup('$pending', validationErrorKey, controller);
@@ -1310,7 +1316,7 @@ function addSetValidityMethod(context) {
}
toggleValidationCss(validationErrorKey, combinedState);
parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
}
function createAndSet(name, value, controller) {
+5 -4
View File
@@ -21,8 +21,10 @@
* | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
* | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
*
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
* This may be useful when, for instance, nesting ngRepeats.
* <div class="alert alert-info">
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
* This may be useful when, for instance, nesting ngRepeats.
* </div>
*
*
* # Iterating over object properties
@@ -256,7 +258,6 @@
.animate-repeat.ng-move,
.animate-repeat.ng-enter,
.animate-repeat.ng-leave {
-webkit-transition:all linear 0.5s;
transition:all linear 0.5s;
}
@@ -428,7 +429,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
// if object, extract keys, in enumeration order, unsorted
collectionKeys = [];
for (var itemKey in collection) {
if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
collectionKeys.push(itemKey);
}
}
+1 -4
View File
@@ -125,9 +125,7 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
background: white;
}
.animate-show.ng-hide-add.ng-hide-add-active,
.animate-show.ng-hide-remove.ng-hide-remove-active {
-webkit-transition: all linear 0.5s;
.animate-show.ng-hide-add, .animate-show.ng-hide-remove {
transition: all linear 0.5s;
}
@@ -284,7 +282,6 @@ var ngShowDirective = ['$animate', function($animate) {
</file>
<file name="animations.css">
.animate-hide {
-webkit-transition: all linear 0.5s;
transition: all linear 0.5s;
line-height: 20px;
opacity: 1;
-1
View File
@@ -91,7 +91,6 @@
}
.animate-switch.ng-animate {
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
position:absolute;
+173 -25
View File
@@ -108,31 +108,162 @@ var SelectController =
* @description
* HTML `SELECT` element with angular data-binding.
*
* In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
* ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits such as reducing
* memory and increasing speed by not creating a new scope for each repeated instance, as well as providing
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
* comprehension expression.
* The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
* between the scope and the `<select>` control (including setting default values).
* Ìt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
* {@link ngOptions `ngOptions`} directives.
*
* When an item in the `<select>` menu is selected, the array element or object property
* represented by the selected option will be bound to the model identified by the `ngModel`
* directive.
* When an item in the `<select>` menu is selected, the value of the selected option will be bound
* to the model identified by the `ngModel` directive. With static or repeated options, this is
* the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
* If you want dynamic value attributes, you can use interpolation inside the value attribute.
*
* If the viewValue contains a value that doesn't match any of the options then the control
* will automatically add an "unknown" option, which it then removes when this is resolved.
* <div class="alert alert-warning">
* Note that the value of a `select` directive used without `ngOptions` is always a string.
* When the model needs to be bound to a non-string value, you must either explictly convert it
* using a directive (see example below) or use `ngOptions` to specify the set of options.
* This is because an option element can only be bound to string values at present.
* </div>
*
* If the viewValue of `ngModel` does not match any of the options, then the control
* will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
*
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
* option. See example below for demonstration.
*
* <div class="alert alert-info">
* The value of a `select` directive used without `ngOptions` is always a string.
* When the model needs to be bound to a non-string value, you must either explictly convert it
* using a directive (see example below) or use `ngOptions` to specify the set of options.
* This is because an option element can only be bound to string values at present.
* In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
* ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
* comprehension expression, and additionally in reducing memory and increasing speed by not creating
* a new scope for each repeated instance.
* </div>
*
* ### Example (binding `select` to a non-string value)
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} required Sets `required` validation error key if the value is not entered.
* @param {string=} ngRequired Adds required attribute and required validation constraint to
* the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
* when you want to data-bind to the required attribute.
* @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user
* interaction with the select element.
* @param {string=} ngOptions sets the options that the select is populated with and defines what is
* set on the model on selection. See {@link ngOptions `ngOptions`}.
*
* @example
* ### Simple `select` elements with static options
*
* <example name="static-select" module="staticSelect">
* <file name="index.html">
* <div ng-controller="ExampleController">
* <form name="myForm">
* <label for="singleSelect"> Single select: </label><br>
* <select name="singleSelect" ng-model="data.singleSelect">
* <option value="option-1">Option 1</option>
* <option value="option-2">Option 2</option>
* </select><br>
*
* <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
* <select name="singleSelect" ng-model="data.singleSelect">
* <option value="">---Please select---</option> <!-- not selected / blank option -->
* <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
* <option value="option-2">Option 2</option>
* </select><br>
* <button ng-click="forceUnknownOption()">Force unknown option</button><br>
* <tt>singleSelect = {{data.singleSelect}}</tt>
*
* <hr>
* <label for="multipleSelect"> Multiple select: </label><br>
* <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
* <option value="option-1">Option 1</option>
* <option value="option-2">Option 2</option>
* <option value="option-3">Option 3</option>
* </select><br>
* <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
* </form>
* </div>
* </file>
* <file name="app.js">
* angular.module('staticSelect', [])
* .controller('ExampleController', ['$scope', function($scope) {
* $scope.data = {
* singleSelect: null,
* multipleSelect: [],
* option1: 'option-1',
* };
*
* $scope.forceUnknownOption = function() {
* $scope.data.singleSelect = 'nonsense';
* };
* }]);
* </file>
*</example>
*
* ### Using `ngRepeat` to generate `select` options
* <example name="ngrepeat-select" module="ngrepeatSelect">
* <file name="index.html">
* <div ng-controller="ExampleController">
* <form name="myForm">
* <label for="repeatSelect"> Repeat select: </label>
* <select name="repeatSelect" ng-model="data.repeatSelect">
* <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
* </select>
* </form>
* <hr>
* <tt>repeatSelect = {{data.repeatSelect}}</tt><br/>
* </div>
* </file>
* <file name="app.js">
* angular.module('ngrepeatSelect', [])
* .controller('ExampleController', ['$scope', function($scope) {
* $scope.data = {
* singleSelect: null,
* availableOptions: [
* {id: '1', name: 'Option A'},
* {id: '2', name: 'Option B'},
* {id: '3', name: 'Option C'}
* ],
* };
* }]);
* </file>
*</example>
*
*
* ### Using `select` with `ngOptions` and setting a default value
* See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
*
* <example name="select-with-default-values" module="defaultValueSelect">
* <file name="index.html">
* <div ng-controller="ExampleController">
* <form name="myForm">
* <label for="mySelect">Make a choice:</label>
* <select name="mySelect" id="mySelect"
* ng-options="option.name for option in data.availableOptions track by option.id"
* ng-model="data.selectedOption"></select>
* </form>
* <hr>
* <tt>option = {{data.selectedOption}}</tt><br/>
* </div>
* </file>
* <file name="app.js">
* angular.module('defaultValueSelect', [])
* .controller('ExampleController', ['$scope', function($scope) {
* $scope.data = {
* availableOptions: [
* {id: '1', name: 'Option A'},
* {id: '2', name: 'Option B'},
* {id: '3', name: 'Option C'}
* ],
* selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
* };
* }]);
* </file>
*</example>
*
*
* ### Binding `select` to a non-string value via `ngModel` parsing / formatting
*
* <example name="select-with-non-string-options" module="nonStringSelect">
* <file name="index.html">
@@ -270,9 +401,12 @@ var optionDirective = ['$interpolate', function($interpolate) {
priority: 100,
compile: function(element, attr) {
// If the value attribute is not defined then we fall back to the
// text content of the option element, which may be interpolated
if (isUndefined(attr.value)) {
if (isDefined(attr.value)) {
// If the value attribute is defined, check if it contains an interpolation
var valueInterpolated = $interpolate(attr.value, true);
} else {
// If the value attribute is not defined then we fall back to the
// text content of the option element, which may be interpolated
var interpolateFn = $interpolate(element.text(), true);
if (!interpolateFn) {
attr.$set('value', element.text());
@@ -288,24 +422,38 @@ var optionDirective = ['$interpolate', function($interpolate) {
selectCtrl = parent.data(selectCtrlName) ||
parent.parent().data(selectCtrlName); // in case we are in optgroup
function addOption(optionValue) {
selectCtrl.addOption(optionValue, element);
selectCtrl.ngModelCtrl.$render();
chromeHack(element);
}
// Only update trigger option updates if this is an option within a `select`
// that also has `ngModel` attached
if (selectCtrl && selectCtrl.ngModelCtrl) {
if (interpolateFn) {
if (valueInterpolated) {
// The value attribute is interpolated
var oldVal;
attr.$observe('value', function valueAttributeObserveAction(newVal) {
if (isDefined(oldVal)) {
selectCtrl.removeOption(oldVal);
}
oldVal = newVal;
addOption(newVal);
});
} else if (interpolateFn) {
// The text content is interpolated
scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
attr.$set('value', newVal);
if (oldVal !== newVal) {
selectCtrl.removeOption(oldVal);
}
selectCtrl.addOption(newVal, element);
selectCtrl.ngModelCtrl.$render();
chromeHack(element);
addOption(newVal);
});
} else {
selectCtrl.addOption(attr.value, element);
selectCtrl.ngModelCtrl.$render();
chromeHack(element);
// The value attribute is static
addOption(attr.value);
}
element.on('$destroy', function() {
+3 -2
View File
@@ -43,8 +43,9 @@ var patternDirective = function() {
ctrl.$validate();
});
ctrl.$validators.pattern = function(value) {
return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
ctrl.$validators.pattern = function(modelValue, viewValue) {
// HTML5 pattern constraint validates the input value, so we validate the viewValue
return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
};
}
};
+2 -2
View File
@@ -1289,8 +1289,8 @@ function $HttpProvider() {
* Resolves the raw $http promise.
*/
function resolvePromise(response, status, headers, statusText) {
// normalize internal statuses to 0
status = Math.max(status, 0);
//status: HTTP response status code, 0, -1 (aborted by timeout / promise)
status = status >= -1 ? status : 0;
(isSuccess(status) ? deferred.resolve : deferred.reject)({
data: response,
+2 -2
View File
@@ -109,7 +109,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
}
}
xhr.send(post);
xhr.send(isUndefined(post) ? null : post);
}
if (timeout > 0) {
@@ -126,7 +126,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
function completeRequest(callback, status, response, headersString, statusText) {
// cancel timeout and subsequent timeout promise resolution
if (timeoutId !== undefined) {
if (isDefined(timeoutId)) {
$browserDefer.cancel(timeoutId);
}
jsonpDone = xhr = null;
+1 -1
View File
@@ -139,7 +139,7 @@ function $InterpolateProvider() {
* ```js
* var $interpolate = ...; // injected
* var exp = $interpolate('Hello {{name | uppercase}}!');
* expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!');
* expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
* ```
*
* `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
+3 -3
View File
@@ -141,14 +141,14 @@ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
var appUrl, prevAppUrl;
var rewrittenUrl;
if ((appUrl = beginsWith(appBase, url)) !== undefined) {
if (isDefined(appUrl = beginsWith(appBase, url))) {
prevAppUrl = appUrl;
if ((appUrl = beginsWith(basePrefix, appUrl)) !== undefined) {
if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) {
rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
} else {
rewrittenUrl = appBase + prevAppUrl;
}
} else if ((appUrl = beginsWith(appBaseNoFile, url)) !== undefined) {
} else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) {
rewrittenUrl = appBaseNoFile + appUrl;
} else if (appBaseNoFile == url + '/') {
rewrittenUrl = appBaseNoFile;
+10
View File
@@ -38,6 +38,15 @@ var $parseMinErr = minErr('$parse');
function ensureSafeMemberName(name, fullExpression) {
// From the JavaScript docs:
// Property names must be strings. This means that non-string objects cannot be used
// as keys in an object. Any non-string object, including a number, is typecasted
// into a string via the toString method.
//
// So, to ensure that we are checking the same `name` that JavaScript would use,
// we cast it to a string, if possible
name = (isObject(name) && name.toString) ? name.toString() : name;
if (name === "__defineGetter__" || name === "__defineSetter__"
|| name === "__lookupGetter__" || name === "__lookupSetter__"
|| name === "__proto__") {
@@ -774,6 +783,7 @@ ASTCompiler.prototype = {
this.state.computing = 'assign';
var result = this.nextId();
this.recurse(assignable, result);
this.return_(result);
extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
}
var toWatch = getInputs(ast.body);
+3 -41
View File
@@ -10,7 +10,7 @@ function $$RAFProvider() { //rAF
$window.webkitCancelRequestAnimationFrame;
var rafSupported = !!requestAnimationFrame;
var rafFn = rafSupported
var raf = rafSupported
? function(fn) {
var id = requestAnimationFrame(fn);
return function() {
@@ -24,46 +24,8 @@ function $$RAFProvider() { //rAF
};
};
queueFn.supported = rafSupported;
raf.supported = rafSupported;
var cancelLastRAF;
var taskCount = 0;
var taskQueue = [];
return queueFn;
function flush() {
for (var i = 0; i < taskQueue.length; i++) {
var task = taskQueue[i];
if (task) {
taskQueue[i] = null;
task();
}
}
taskCount = taskQueue.length = 0;
}
function queueFn(asyncFn) {
var index = taskQueue.length;
taskCount++;
taskQueue.push(asyncFn);
if (index === 0) {
cancelLastRAF = rafFn(flush);
}
return function cancelQueueFn() {
if (index >= 0) {
taskQueue[index] = null;
index = null;
if (--taskCount === 0 && cancelLastRAF) {
cancelLastRAF();
cancelLastRAF = null;
taskQueue.length = 0;
}
}
};
}
return raf;
}];
}
+6 -6
View File
@@ -253,10 +253,10 @@ function $RootScopeProvider() {
* Registers a `listener` callback to be executed whenever the `watchExpression` changes.
*
* - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
* $digest()} and should return the value that will be watched. (Since
* {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the
* `watchExpression` can execute multiple times per
* {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.)
* $digest()} and should return the value that will be watched. (`watchExpression` should not change
* its value when executed multiple times with the same input because it may be executed multiple
* times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
* [idempotent](http://en.wikipedia.org/wiki/Idempotence).
* - The `listener` is called only when the value from the current `watchExpression` and the
* previous call to `watchExpression` are not equal (with the exception of the initial run,
* see below). Inequality is determined according to reference inequality,
@@ -605,7 +605,7 @@ function $RootScopeProvider() {
// copy the items to oldValue and look for changes.
newLength = 0;
for (key in newValue) {
if (newValue.hasOwnProperty(key)) {
if (hasOwnProperty.call(newValue, key)) {
newLength++;
newItem = newValue[key];
oldItem = oldValue[key];
@@ -627,7 +627,7 @@ function $RootScopeProvider() {
// we used to have more keys, need to find them and destroy them.
changeDetected++;
for (key in oldValue) {
if (!newValue.hasOwnProperty(key)) {
if (!hasOwnProperty.call(newValue, key)) {
oldLength--;
delete oldValue[key];
}
+2 -2
View File
@@ -294,7 +294,7 @@ function $SceDelegateProvider() {
'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
type, trustedValue);
}
if (trustedValue === null || trustedValue === undefined || trustedValue === '') {
if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
return trustedValue;
}
// All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
@@ -349,7 +349,7 @@ function $SceDelegateProvider() {
* `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception.
*/
function getTrusted(type, maybeTrusted) {
if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') {
if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
return maybeTrusted;
}
var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
+67 -35
View File
@@ -1,5 +1,7 @@
'use strict';
var ANIMATE_TIMER_KEY = '$$animateCss';
/**
* @ngdoc service
* @name $animateCss
@@ -326,8 +328,10 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
var gcsLookup = createLocalCacheLookup();
var gcsStaggerLookup = createLocalCacheLookup();
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', '$$forceReflow', '$sniffer', '$$rAF',
function($window, $$jqLite, $$AnimateRunner, $timeout, $$forceReflow, $sniffer, $$rAF) {
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
'$$forceReflow', '$sniffer', '$$rAFScheduler', '$animate',
function($window, $$jqLite, $$AnimateRunner, $timeout,
$$forceReflow, $sniffer, $$rAFScheduler, $animate) {
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
@@ -387,12 +391,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
var cancelLastRAFRequest;
var rafWaitQueue = [];
function waitUntilQuiet(callback) {
if (cancelLastRAFRequest) {
cancelLastRAFRequest(); //cancels the request
}
rafWaitQueue.push(callback);
cancelLastRAFRequest = $$rAF(function() {
cancelLastRAFRequest = null;
$$rAFScheduler.waitUntilQuiet(function() {
gcsLookup.flush();
gcsStaggerLookup.flush();
@@ -409,8 +409,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
});
}
return init;
function computeTimings(node, className, cacheKey) {
var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
var aD = timings.animationDelay;
@@ -425,9 +423,11 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
return timings;
}
function init(element, options) {
return function init(element, options) {
var node = getDomNode(element);
if (!node || !node.parentNode) {
if (!node
|| !node.parentNode
|| !$animate.enabled()) {
return closeAndReturnNoopAnimator();
}
@@ -483,7 +483,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
// there actually is a detected transition or keyframe animation
if (options.applyClassesEarly && addRemoveClassName.length) {
applyAnimationClasses(element, options);
addRemoveClassName = '';
}
var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
@@ -598,6 +597,18 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
return closeAndReturnNoopAnimator();
}
if (options.delay != null) {
var delayStyle = parseFloat(options.delay);
if (flags.applyTransitionDelay) {
temporaryStyles.push(getCssDelayStyle(delayStyle));
}
if (flags.applyAnimationDelay) {
temporaryStyles.push(getCssDelayStyle(delayStyle, true));
}
}
// we need to recalculate the delay value since we used a pre-emptive negative
// delay value and the delay value is required for the final event checking. This
// property will ensure that this will happen after the RAF phase has passed.
@@ -712,6 +723,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
cancel: cancelFn
});
// should flush the cache animation
waitUntilQuiet(noop);
close();
return {
@@ -809,27 +822,16 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
flags.hasAnimations = timings.animationDuration > 0;
}
if (flags.applyTransitionDelay || flags.applyAnimationDelay) {
if (flags.applyAnimationDelay) {
relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)
? parseFloat(options.delay)
: relativeDelay;
maxDelay = Math.max(relativeDelay, 0);
var delayStyle;
if (flags.applyTransitionDelay) {
timings.transitionDelay = relativeDelay;
delayStyle = getCssDelayStyle(relativeDelay);
temporaryStyles.push(delayStyle);
node.style[delayStyle[0]] = delayStyle[1];
}
if (flags.applyAnimationDelay) {
timings.animationDelay = relativeDelay;
delayStyle = getCssDelayStyle(relativeDelay, true);
temporaryStyles.push(delayStyle);
node.style[delayStyle[0]] = delayStyle[1];
}
timings.animationDelay = relativeDelay;
delayStyle = getCssDelayStyle(relativeDelay, true);
temporaryStyles.push(delayStyle);
node.style[delayStyle[0]] = delayStyle[1];
}
maxDelayTime = maxDelay * ONE_SECOND;
@@ -858,17 +860,47 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
}
startTime = Date.now();
element.on(events.join(' '), onAnimationProgress);
$timeout(onAnimationExpired, maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime, false);
var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime;
var endTime = startTime + timerTime;
var animationsData = element.data(ANIMATE_TIMER_KEY) || [];
var setupFallbackTimer = true;
if (animationsData.length) {
var currentTimerData = animationsData[0];
setupFallbackTimer = endTime > currentTimerData.expectedEndTime;
if (setupFallbackTimer) {
$timeout.cancel(currentTimerData.timer);
} else {
animationsData.push(close);
}
}
if (setupFallbackTimer) {
var timer = $timeout(onAnimationExpired, timerTime, false);
animationsData[0] = {
timer: timer,
expectedEndTime: endTime
};
animationsData.push(close);
element.data(ANIMATE_TIMER_KEY, animationsData);
}
element.on(events.join(' '), onAnimationProgress);
applyAnimationToStyles(element, options);
}
function onAnimationExpired() {
// although an expired animation is a failed animation, getting to
// this outcome is very easy if the CSS code screws up. Therefore we
// should still continue normally as if the animation completed correctly.
close();
var animationsData = element.data(ANIMATE_TIMER_KEY);
// this will be false in the event that the element was
// removed from the DOM (via a leave animation or something
// similar)
if (animationsData) {
for (var i = 1; i < animationsData.length; i++) {
animationsData[i]();
}
element.removeData(ANIMATE_TIMER_KEY);
}
}
function onAnimationProgress(event) {
@@ -895,6 +927,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
}
}
}
}
};
}];
}];
+5 -17
View File
@@ -22,13 +22,13 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
return function initDriverFn(animationDetails, onBeforeClassesAppliedCb) {
return function initDriverFn(animationDetails) {
return animationDetails.from && animationDetails.to
? prepareFromToAnchorAnimation(animationDetails.from,
animationDetails.to,
animationDetails.classes,
animationDetails.anchors)
: prepareRegularAnimation(animationDetails, onBeforeClassesAppliedCb);
: prepareRegularAnimation(animationDetails);
};
function filterCssClasses(classes) {
@@ -224,21 +224,14 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
};
}
function prepareRegularAnimation(animationDetails, onBeforeClassesAppliedCb) {
function prepareRegularAnimation(animationDetails) {
var element = animationDetails.element;
var options = animationDetails.options || {};
// since the ng-EVENT, class-ADD and class-REMOVE classes are applied inside
// of the animateQueue pre and postDigest stages then there is no need to add
// then them here as well.
options.$$skipPreparationClasses = true;
// during the pre/post digest stages inside of animateQueue we also performed
// the blocking (transition:-9999s) so there is no point in doing that again.
options.skipBlocking = true;
if (animationDetails.structural) {
options.event = animationDetails.event;
options.structural = true;
options.applyClassesEarly = true;
// we special case the leave animation since we want to ensure that
// the element is removed as soon as the animation is over. Otherwise
@@ -248,11 +241,6 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
}
}
// we apply the classes right away since the pre-digest took care of the
// preparation classes.
onBeforeClassesAppliedCb(element);
applyAnimationClasses(element, options);
// We assign the preparationClasses as the actual animation event since
// the internals of $animateCss will just suffix the event token values
// with `-active` to trigger the animation.
+2 -2
View File
@@ -5,8 +5,8 @@
// by the time...
var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
this.$get = ['$injector', '$$AnimateRunner', '$$rAFMutex', '$$jqLite',
function($injector, $$AnimateRunner, $$rAFMutex, $$jqLite) {
this.$get = ['$injector', '$$AnimateRunner', '$$jqLite',
function($injector, $$AnimateRunner, $$jqLite) {
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
// $animateJs(element, 'enter');
+1 -7
View File
@@ -381,9 +381,6 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
return runner;
}
applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
// the counter keeps track of cancelled animations
var counter = (existingAnimation.counter || 0) + 1;
newAnimation.counter = counter;
@@ -442,10 +439,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
: animationDetails.event;
markElementAnimationState(element, RUNNING_STATE);
var realRunner = $$animation(element, event, animationDetails.options, function(e) {
$$forceReflow();
blockTransitions(getDomNode(e), false);
});
var realRunner = $$animation(element, event, animationDetails.options);
realRunner.done(function(status) {
close(!status);
+21 -6
View File
@@ -1,18 +1,33 @@
'use strict';
var $$rAFMutexFactory = ['$$rAF', function($$rAF) {
var $$AnimateAsyncRunFactory = ['$$rAF', function($$rAF) {
var waitQueue = [];
function waitForTick(fn) {
waitQueue.push(fn);
if (waitQueue.length > 1) return;
$$rAF(function() {
for (var i = 0; i < waitQueue.length; i++) {
waitQueue[i]();
}
waitQueue = [];
});
}
return function() {
var passed = false;
$$rAF(function() {
waitForTick(function() {
passed = true;
});
return function(fn) {
passed ? fn() : $$rAF(fn);
return function(callback) {
passed ? callback() : waitForTick(callback);
};
};
}];
var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) {
var $$AnimateRunnerFactory = ['$q', '$sniffer', '$$animateAsyncRun',
function($q, $sniffer, $$animateAsyncRun) {
var INITIAL_STATE = 0;
var DONE_PENDING_STATE = 1;
var DONE_COMPLETE_STATE = 2;
@@ -57,7 +72,7 @@ var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) {
this.setHost(host);
this._doneCallbacks = [];
this._runInAnimationFrame = $$rAFMutex();
this._runInAnimationFrame = $$animateAsyncRun();
this._state = 0;
}
+15 -17
View File
@@ -19,8 +19,8 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
return element.data(RUNNER_STORAGE_KEY);
}
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap',
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap) {
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) {
var animationQueue = [];
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
@@ -88,11 +88,11 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
if (remainingLevelEntries <= 0) {
remainingLevelEntries = nextLevelEntries;
nextLevelEntries = 0;
result = result.concat(row);
result.push(row);
row = [];
}
row.push(entry.fn);
forEach(entry.children, function(childEntry) {
entry.children.forEach(function(childEntry) {
nextLevelEntries++;
queue.push(childEntry);
});
@@ -100,14 +100,15 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
}
if (row.length) {
result = result.concat(row);
result.push(row);
}
return result;
}
}
// TODO(matsko): document the signature in a better way
return function(element, event, options, onBeforeClassesAppliedCb) {
return function(element, event, options) {
options = prepareAnimationOptions(options);
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
@@ -159,8 +160,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
// the element was destroyed early on which removed the runner
// form its storage. This means we can't animate this element
// at all and it already has been closed due to destruction.
var elm = entry.element;
if (getRunner(elm) && getDomNode(elm).parentNode) {
if (getRunner(entry.element)) {
animations.push(entry);
} else {
entry.close();
@@ -191,7 +191,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
: animationEntry.element;
if (getRunner(targetElement)) {
var operation = invokeFirstDriver(animationEntry, onBeforeClassesAppliedCb);
var operation = invokeFirstDriver(animationEntry);
if (operation) {
startAnimationFn = operation.start;
}
@@ -211,11 +211,9 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
});
// we need to sort each of the animations in order of parent to child
// relationships. This ensures that the parent to child classes are
// applied at the right time.
forEach(sortAnimations(toBeSortedAnimations), function(triggerAnimation) {
triggerAnimation();
});
// relationships. This ensures that the child classes are applied at the
// right time.
$$rAFScheduler(sortAnimations(toBeSortedAnimations));
});
return runner;
@@ -285,7 +283,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
var lookupKey = from.animationID.toString();
if (!anchorGroups[lookupKey]) {
var group = anchorGroups[lookupKey] = {
// TODO(matsko): double-check this code
structural: true,
beforeStart: function() {
fromAnimation.beforeStart();
toAnimation.beforeStart();
@@ -339,7 +337,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
return matches.join(' ');
}
function invokeFirstDriver(animationDetails, onBeforeClassesAppliedCb) {
function invokeFirstDriver(animationDetails) {
// we loop in reverse order since the more general drivers (like CSS and JS)
// may attempt more elements, but custom drivers are more particular
for (var i = drivers.length - 1; i >= 0; i--) {
@@ -347,7 +345,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
var factory = $injector.get(driverName);
var driver = factory(animationDetails, onBeforeClassesAppliedCb);
var driver = factory(animationDetails);
if (driver) {
return driver;
}
+9 -8
View File
@@ -3,7 +3,8 @@
/* global angularAnimateModule: true,
$$BodyProvider,
$$rAFMutexFactory,
$$AnimateAsyncRunFactory,
$$rAFSchedulerFactory,
$$AnimateChildrenDirective,
$$AnimateRunnerFactory,
$$AnimateQueueProvider,
@@ -20,7 +21,7 @@
* @description
*
* The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via
* callback hooks. Animations are not enabled by default, however, by including `ngAnimate` then the animation hooks are enabled for an Angular app.
* callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app.
*
* <div doc-module-components="ngAnimate"></div>
*
@@ -53,7 +54,7 @@
* CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML
* and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation.
*
* The example below shows how an `enter` animation can be made possible on a element using `ng-if`:
* The example below shows how an `enter` animation can be made possible on an element using `ng-if`:
*
* ```html
* <div ng-if="bool" class="fade">
@@ -188,8 +189,8 @@
* /&#42; this will have a 100ms delay between each successive leave animation &#42;/
* transition-delay: 0.1s;
*
* /&#42; in case the stagger doesn't work then the duration value
* must be set to 0 to avoid an accidental CSS inheritance &#42;/
* /&#42; As of 1.4.4, this must always be set: it signals ngAnimate
* to not accidentally inherit a delay property from another CSS class &#42;/
* transition-duration: 0s;
* }
* .my-animation.ng-enter.ng-enter-active {
@@ -738,16 +739,16 @@
* @description
* The ngAnimate `$animate` service documentation is the same for the core `$animate` service.
*
* Click here {@link ng.$animate $animate to learn more about animations with `$animate`}.
* Click here {@link ng.$animate to learn more about animations with `$animate`}.
*/
angular.module('ngAnimate', [])
.provider('$$body', $$BodyProvider)
.directive('ngAnimateChildren', $$AnimateChildrenDirective)
.factory('$$rAFMutex', $$rAFMutexFactory)
.factory('$$rAFScheduler', $$rAFSchedulerFactory)
.factory('$$AnimateRunner', $$AnimateRunnerFactory)
.factory('$$animateAsyncRun', $$AnimateAsyncRunFactory)
.provider('$$animateQueue', $$AnimateQueueProvider)
.provider('$$animation', $$AnimationProvider)
+50
View File
@@ -0,0 +1,50 @@
'use strict';
var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
var queue, cancelFn;
function scheduler(tasks) {
// we make a copy since RAFScheduler mutates the state
// of the passed in array variable and this would be difficult
// to track down on the outside code
queue = queue.concat(tasks);
nextTick();
}
queue = scheduler.queue = [];
/* waitUntilQuiet does two things:
* 1. It will run the FINAL `fn` value only when an uncancelled RAF has passed through
* 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
*
* The motivation here is that animation code can request more time from the scheduler
* before the next wave runs. This allows for certain DOM properties such as classes to
* be resolved in time for the next animation to run.
*/
scheduler.waitUntilQuiet = function(fn) {
if (cancelFn) cancelFn();
cancelFn = $$rAF(function() {
cancelFn = null;
fn();
nextTick();
});
};
return scheduler;
function nextTick() {
if (!queue.length) return;
var items = queue.shift();
for (var i = 0; i < items.length; i++) {
items[i]();
}
if (!cancelFn) {
$$rAF(function() {
if (!cancelFn) nextTick();
});
}
}
}];
+2 -2
View File
@@ -36,7 +36,7 @@ var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMA
// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
// therefore there is no reason to test anymore for other vendor prefixes:
// http://caniuse.com/#search=transition
if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) {
CSS_PREFIX = '-webkit-';
TRANSITION_PROP = 'WebkitTransition';
TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
@@ -45,7 +45,7 @@ if (window.ontransitionend === undefined && window.onwebkittransitionend !== und
TRANSITIONEND_EVENT = 'transitionend';
}
if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) {
CSS_PREFIX = '-webkit-';
ANIMATION_PROP = 'WebkitAnimation';
ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
+39 -35
View File
@@ -52,6 +52,16 @@
var ngAriaModule = angular.module('ngAria', ['ng']).
provider('$aria', $AriaProvider);
/**
* Internal Utilities
*/
var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY'];
var isNodeOneOf = function(elem, nodeTypeArray) {
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
return true;
}
};
/**
* @ngdoc provider
* @name $ariaProvider
@@ -113,10 +123,10 @@ function $AriaProvider() {
config = angular.extend(config, newConfig);
};
function watchExpr(attrName, ariaAttr, negate) {
function watchExpr(attrName, ariaAttr, nodeBlackList, negate) {
return function(scope, elem, attr) {
var ariaCamelName = attr.$normalize(ariaAttr);
if (config[ariaCamelName] && !attr[ariaCamelName]) {
if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) {
scope.$watch(attr[attrName], function(boolVal) {
// ensure boolean value
boolVal = negate ? !boolVal : !!boolVal;
@@ -125,7 +135,6 @@ function $AriaProvider() {
}
};
}
/**
* @ngdoc service
* @name $aria
@@ -184,10 +193,10 @@ function $AriaProvider() {
ngAriaModule.directive('ngShow', ['$aria', function($aria) {
return $aria.$$watchExpr('ngShow', 'aria-hidden', true);
return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true);
}])
.directive('ngHide', ['$aria', function($aria) {
return $aria.$$watchExpr('ngHide', 'aria-hidden', false);
return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false);
}])
.directive('ngModel', ['$aria', function($aria) {
@@ -261,6 +270,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
getRadioReaction() : ngAriaCheckboxReaction);
}
if (needsTabIndex) {
elem.attr('tabindex', 0);
}
break;
case 'range':
if (shouldAttachRole(shape, elem)) {
@@ -289,6 +301,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
});
}
}
if (needsTabIndex) {
elem.attr('tabindex', 0);
}
break;
case 'multiline':
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
@@ -297,10 +312,6 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
break;
}
if (needsTabIndex) {
elem.attr('tabindex', 0);
}
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
scope.$watch(function ngAriaRequiredWatch() {
return ngModel.$error.required;
@@ -322,7 +333,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
};
}])
.directive('ngDisabled', ['$aria', function($aria) {
return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
return $aria.$$watchExpr('ngDisabled', 'aria-disabled', []);
}])
.directive('ngMessages', function() {
return {
@@ -342,35 +353,28 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
return function(scope, elem, attr) {
var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA'];
if (!isNodeOneOf(elem, nodeBlackList)) {
function isNodeOneOf(elem, nodeTypeArray) {
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
return true;
if ($aria.config('bindRoleForClick') && !elem.attr('role')) {
elem.attr('role', 'button');
}
}
if ($aria.config('bindRoleForClick')
&& !elem.attr('role')
&& !isNodeOneOf(elem, nodeBlackList)) {
elem.attr('role', 'button');
}
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
elem.attr('tabindex', 0);
}
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
elem.attr('tabindex', 0);
}
if ($aria.config('bindKeypress') && !attr.ngKeypress) {
elem.on('keypress', function(event) {
var keyCode = event.which || event.keyCode;
if (keyCode === 32 || keyCode === 13) {
scope.$apply(callback);
}
if ($aria.config('bindKeypress') && !attr.ngKeypress && !isNodeOneOf(elem, nodeBlackList)) {
elem.on('keypress', function(event) {
var keyCode = event.which || event.keyCode;
if (keyCode === 32 || keyCode === 13) {
scope.$apply(callback);
}
function callback() {
fn(scope, { $event: event });
}
});
function callback() {
fn(scope, { $event: event });
}
});
}
}
};
}
@@ -378,7 +382,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
}])
.directive('ngDblclick', ['$aria', function($aria) {
return function(scope, elem, attr) {
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) {
elem.attr('tabindex', 0);
}
};
+1 -1
View File
@@ -20,7 +20,7 @@ function $$CookieWriter($document, $log, $browser) {
options = options || {};
expires = options.expires;
path = angular.isDefined(options.path) ? options.path : cookiePath;
if (value === undefined) {
if (angular.isUndefined(value)) {
expires = 'Thu, 01 Jan 1970 00:00:00 GMT';
value = '';
}
+61 -24
View File
@@ -87,7 +87,7 @@ angular.mock.$Browser = function() {
if (fn.id === deferId) fnIndex = index;
});
if (fnIndex !== undefined) {
if (angular.isDefined(fnIndex)) {
self.deferredFns.splice(fnIndex, 1);
return true;
}
@@ -462,7 +462,7 @@ angular.mock.$IntervalProvider = function() {
if (fn.id === promise.$$intervalId) fnIndex = index;
});
if (fnIndex !== undefined) {
if (angular.isDefined(fnIndex)) {
repeatFns.splice(fnIndex, 1);
}
}
@@ -504,7 +504,7 @@ angular.mock.$IntervalProvider = function() {
if (fn.id === promise.$$intervalId) fnIndex = index;
});
if (fnIndex !== undefined) {
if (angular.isDefined(fnIndex)) {
repeatFns[fnIndex].deferred.reject('canceled');
repeatFns.splice(fnIndex, 1);
return true;
@@ -763,25 +763,62 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
return reflowFn;
});
$provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF', '$$forceReflow',
function($delegate, $timeout, $browser, $$rAF, $$forceReflow) {
$provide.factory('$$animateAsyncRun', function() {
var queue = [];
var queueFn = function() {
return function(fn) {
queue.push(fn);
};
};
queueFn.flush = function() {
if (queue.length === 0) return false;
for (var i = 0; i < queue.length; i++) {
queue[i]();
}
queue = [];
return true;
};
return queueFn;
});
$provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF',
'$$forceReflow', '$$animateAsyncRun', '$rootScope',
function($delegate, $timeout, $browser, $$rAF,
$$forceReflow, $$animateAsyncRun, $rootScope) {
var animate = {
queue: [],
cancel: $delegate.cancel,
on: $delegate.on,
off: $delegate.off,
pin: $delegate.pin,
get reflows() {
return $$forceReflow.totalReflows;
},
enabled: $delegate.enabled,
triggerCallbackEvents: function() {
$$rAF.flush();
},
triggerCallbackPromise: function() {
$timeout.flush(0);
},
triggerCallbacks: function() {
this.triggerCallbackEvents();
this.triggerCallbackPromise();
flush: function() {
$rootScope.$digest();
var doNextRun, somethingFlushed = false;
do {
doNextRun = false;
if ($$rAF.queue.length) {
$$rAF.flush();
doNextRun = somethingFlushed = true;
}
if ($$animateAsyncRun.flush()) {
doNextRun = somethingFlushed = true;
}
} while (doNextRun);
if (!somethingFlushed) {
throw new Error('No pending animations ready to be closed or flushed');
}
$rootScope.$digest();
}
};
@@ -998,7 +1035,7 @@ angular.mock.dump = function(object) {
$http.post('/add-msg.py', message, { headers: headers } ).success(function(response) {
$scope.status = '';
}).error(function() {
$scope.status = 'ERROR!';
$scope.status = 'Failed...';
});
};
}
@@ -1733,28 +1770,28 @@ angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $
}];
angular.mock.$RAFDecorator = ['$delegate', function($delegate) {
var queue = [];
var rafFn = function(fn) {
var index = queue.length;
queue.push(fn);
var index = rafFn.queue.length;
rafFn.queue.push(fn);
return function() {
queue.splice(index, 1);
rafFn.queue.splice(index, 1);
};
};
rafFn.queue = [];
rafFn.supported = $delegate.supported;
rafFn.flush = function() {
if (queue.length === 0) {
if (rafFn.queue.length === 0) {
throw new Error('No rAF callbacks present');
}
var length = queue.length;
var length = rafFn.queue.length;
for (var i = 0; i < length; i++) {
queue[i]();
rafFn.queue[i]();
}
queue = queue.slice(i);
rafFn.queue = rafFn.queue.slice(i);
};
return rafFn;
@@ -1802,7 +1839,7 @@ angular.mock.$RootElementProvider = function() {
*
* describe('myDirectiveController', function() {
* it('should write the bound name to the log', inject(function($controller, $log) {
* var ctrl = $controller('MyDirective', { /* no locals &#42;/ }, { name: 'Clark Kent' });
* var ctrl = $controller('MyDirectiveController', { /* no locals &#42;/ }, { name: 'Clark Kent' });
* expect(ctrl.name).toEqual('Clark Kent');
* expect($log.info.logs).toEqual(['Clark Kent']);
* });
+9 -3
View File
@@ -17,7 +17,7 @@ function lookupDottedPath(obj, path) {
throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
}
var keys = path.split('.');
for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) {
for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) {
var key = keys[i];
obj = (obj !== null) ? obj[key] : undefined;
}
@@ -348,6 +348,7 @@ function shallowClearAndCopy(src, dst) {
*/
angular.module('ngResource', ['ng']).
provider('$resource', function() {
var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/;
var provider = this;
this.defaults = {
@@ -422,7 +423,8 @@ angular.module('ngResource', ['ng']).
var self = this,
url = actionUrl || self.template,
val,
encodedVal;
encodedVal,
protocolAndDomain = '';
var urlParams = self.urlParams = {};
forEach(url.split(/\W/), function(param) {
@@ -435,6 +437,10 @@ angular.module('ngResource', ['ng']).
}
});
url = url.replace(/\\:/g, ':');
url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) {
protocolAndDomain = match;
return '';
});
params = params || {};
forEach(self.urlParams, function(_, urlParam) {
@@ -465,7 +471,7 @@ angular.module('ngResource', ['ng']).
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
url = url.replace(/\/\.(?=\w+($|\?))/, '.');
// replace escaped `/\.` with `/.`
config.url = url.replace(/\/\\\./, '/.');
config.url = protocolAndDomain + url.replace(/\/\\\./, '/.');
// set params - delegate param encoding to $http
-1
View File
@@ -90,7 +90,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
}
.view-animate.ng-enter, .view-animate.ng-leave {
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
display:block;
+1 -1
View File
@@ -293,7 +293,7 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
}
function push(value) {
if (value === undefined) {
if (angular.isUndefined(value)) {
value = '';
} else if (typeof value !== 'string') {
value = angular.toJson(value);
+2 -2
View File
@@ -9,7 +9,7 @@ function serializeObject(obj) {
val = toJsonReplacer(key, val);
if (isObject(val)) {
if (seen.indexOf(val) >= 0) return '<<already seen>>';
if (seen.indexOf(val) >= 0) return '...';
seen.push(val);
}
@@ -20,7 +20,7 @@ function serializeObject(obj) {
function toDebugString(obj) {
if (typeof obj === 'function') {
return obj.toString().replace(/ \{[\s\S]*$/, '');
} else if (typeof obj === 'undefined') {
} else if (isUndefined(obj)) {
return 'undefined';
} else if (typeof obj !== 'string') {
return serializeObject(obj);
+12
View File
@@ -386,6 +386,18 @@ describe('angular', function() {
expect(aCopy).toBe(aCopy.self);
});
it('should deeply copy XML nodes', function() {
var anElement = document.createElement('foo');
anElement.appendChild(document.createElement('bar'));
var theCopy = anElement.cloneNode(true);
expect(copy(anElement).outerHTML).toEqual(theCopy.outerHTML);
expect(copy(anElement)).not.toBe(anElement);
});
it('should not try to call a non-function called `cloneNode`', function() {
expect(copy.bind(null, { cloneNode: 100 })).not.toThrow();
});
it('should handle objects with multiple references', function() {
var b = {};
var a = [b, -1, b];
+5
View File
@@ -4,6 +4,11 @@
{{jqueryVersion}}
<script src="../../../../bower_components/jquery/dist/jquery.js"></script>
<script type="text/javascript">
// Verify that empty ng-jq is not accessing `window['']`.
// (See https://github.com/angular/angular.js/issues/12741 for more details)
window[''] = window.jQuery;
</script>
<script src="angular.js"></script>
<script type="text/javascript" src="script.js"></script>
</body>
+1
View File
@@ -47,6 +47,7 @@ afterEach(function() {
if (bod) {
bod.$$hashKey = null;
}
document.$$hashKey = null;
if (this.$injector) {
var $rootScope = this.$injector.get('$rootScope');
+1 -1
View File
@@ -65,7 +65,7 @@ describe('minErr', function() {
a.b.a = a;
var myError = testError('26', 'a is {0}', a);
expect(myError.message).toMatch(/a is {"b":{"a":"<<already seen>>"}}/);
expect(myError.message).toMatch(/a is {"b":{"a":"..."}}/);
});
it('should preserve interpolation markers when fewer arguments than needed are provided', function() {
+15
View File
@@ -341,6 +341,21 @@ describe("$animate", function() {
});
});
it('should not break postDigest for subsequent elements if addClass contains non-valid CSS class names', function() {
inject(function($animate, $rootScope, $rootElement) {
var element1 = jqLite('<div></div>');
var element2 = jqLite('<div></div>');
$animate.enter(element1, $rootElement, null, { addClass: ' ' });
$animate.enter(element2, $rootElement, null, { addClass: 'valid-name' });
$rootScope.$digest();
expect(element2.hasClass('valid-name')).toBeTruthy();
});
});
it('should not issue a call to removeClass if the provided class value is not a string or array', function() {
inject(function($animate, $rootScope, $rootElement) {
var spy = spyOn(window, 'jqLiteRemoveClass').andCallThrough();
+48 -6
View File
@@ -12,12 +12,20 @@ function MockWindow(options) {
var events = {};
var timeouts = this.timeouts = [];
var locationHref = 'http://server/';
var committedHref = 'http://server/';
var mockWindow = this;
var msie = options.msie;
var ieState;
historyEntriesLength = 1;
function replaceHash(href, hash) {
// replace the hash with the new one (stripping off a leading hash if there is one)
// See hash setter spec: https://url.spec.whatwg.org/#urlutils-and-urlutilsreadonly-members
return stripHash(href) + '#' + hash.replace(/^#/,'');
}
this.setTimeout = function(fn) {
return timeouts.push(fn) - 1;
};
@@ -46,24 +54,28 @@ function MockWindow(options) {
this.location = {
get href() {
return locationHref;
return committedHref;
},
set href(value) {
locationHref = value;
mockWindow.history.state = null;
historyEntriesLength++;
if (!options.updateAsync) this.flushHref();
},
get hash() {
return getHash(locationHref);
return getHash(committedHref);
},
set hash(value) {
// replace the hash with the new one (stripping off a leading hash if there is one)
// See hash setter spec: https://url.spec.whatwg.org/#urlutils-and-urlutilsreadonly-members
locationHref = stripHash(locationHref) + '#' + value.replace(/^#/,'');
locationHref = replaceHash(locationHref, value);
if (!options.updateAsync) this.flushHref();
},
replace: function(url) {
locationHref = url;
mockWindow.history.state = null;
if (!options.updateAsync) this.flushHref();
},
flushHref: function() {
committedHref = locationHref;
}
};
@@ -132,7 +144,7 @@ describe('browser', function() {
logs = {log:[], warn:[], info:[], error:[]};
var fakeLog = {log: function() { logs.log.push(slice.call(arguments)); },
fakeLog = {log: function() { logs.log.push(slice.call(arguments)); },
warn: function() { logs.warn.push(slice.call(arguments)); },
info: function() { logs.info.push(slice.call(arguments)); },
error: function() { logs.error.push(slice.call(arguments)); }};
@@ -703,7 +715,11 @@ describe('browser', function() {
describe('integration tests with $location', function() {
function setup(options) {
fakeWindow = new MockWindow(options);
browser = new Browser(fakeWindow, fakeDocument, fakeLog, sniffer);
module(function($provide, $locationProvider) {
spyOn(fakeWindow.history, 'pushState').andCallFake(function(stateObj, title, newUrl) {
fakeWindow.location.href = newUrl;
});
@@ -827,6 +843,32 @@ describe('browser', function() {
});
});
// issue #12241
it('should not infinite digest if the browser does not synchronously update the location properties', function() {
setup({
history: true,
html5Mode: true,
updateAsync: true // Simulate a browser that doesn't update the href synchronously
});
inject(function($location, $rootScope) {
// Change the hash within Angular and check that we don't infinitely digest
$location.hash('newHash');
expect(function() { $rootScope.$digest(); }).not.toThrow();
expect($location.absUrl()).toEqual('http://server/#newHash');
// Now change the hash from outside Angular and check that $location updates correctly
fakeWindow.location.hash = '#otherHash';
// simulate next tick - since this browser doesn't update synchronously
fakeWindow.location.flushHref();
fakeWindow.fire('hashchange');
expect($location.absUrl()).toEqual('http://server/#otherHash');
});
});
});
describe('integration test with $rootScope', function() {
+256 -25
View File
@@ -58,6 +58,119 @@ describe('form', function() {
expect(form.alias).toBeUndefined();
});
it('should ignore changes in manually removed controls', function() {
doc = $compile(
'<form name="myForm">' +
'<input name="control" ng-maxlength="1" ng-model="value" store-model-ctrl/>' +
'</form>')(scope);
var form = scope.myForm;
var input = doc.find('input').eq(0);
var inputController = input.controller('ngModel');
changeInputValue(input, 'ab');
scope.$apply();
expect(form.$error.maxlength).toBeTruthy();
expect(form.$dirty).toBe(true);
expect(form.$error.maxlength[0].$name).toBe('control');
// remove control
form.$removeControl(form.control);
expect(form.control).toBeUndefined();
expect(form.$error.maxlength).toBeFalsy();
inputController.$setPristine();
expect(form.$dirty).toBe(true);
form.$setPristine();
changeInputValue(input, 'abc');
scope.$apply();
expect(form.$error.maxlength).toBeFalsy();
expect(form.$dirty).toBe(false);
});
it('should react to validation changes in manually added controls', function() {
doc = $compile(
'<form name="myForm">' +
'<input name="control" ng-maxlength="1" ng-model="value" store-model-ctrl/>' +
'</form>')(scope);
scope.$digest();
var form = scope.myForm;
var input = doc.find('input').eq(0);
// remove control and invalidate it
form.$removeControl(control);
expect(form.control).toBeUndefined();
changeInputValue(input, 'abc');
expect(control.$error.maxlength).toBe(true);
expect(control.$dirty).toBe(true);
expect(form.$error.maxlength).toBeFalsy();
expect(form.$dirty).toBe(false);
// re-add the control; its current validation state is not propagated
form.$addControl(control);
expect(form.control).toBe(control);
expect(form.$error.maxlength).toBeFalsy();
expect(form.$dirty).toBe(false);
// Only when the input changes again its validation state is propagated
changeInputValue(input, 'abcd');
expect(form.$error.maxlength[0]).toBe(control);
expect(form.$dirty).toBe(false);
});
it('should use the correct parent when renaming and removing dynamically added controls', function() {
scope.controlName = 'childControl';
scope.hasChildControl = true;
doc = $compile(
'<form name="myForm">' +
'<div ng-if="hasChildControl">' +
'<input name="{{controlName}}" ng-maxlength="1" ng-model="value"/>' +
'</div>' +
'</form>' +
'<form name="otherForm"></form>')(scope);
scope.$digest();
var form = scope.myForm;
var otherForm = scope.otherForm;
var childControl = form.childControl;
// remove child form and add it to another form
form.$removeControl(childControl);
otherForm.$addControl(childControl);
expect(form.childControl).toBeUndefined();
expect(otherForm.childControl).toBe(childControl);
// rename the childControl
scope.controlName = 'childControlMoved';
scope.$digest();
expect(form.childControlMoved).toBeUndefined();
expect(otherForm.childControl).toBeUndefined();
expect(otherForm.childControlMoved).toBe(childControl);
scope.hasChildControl = false;
scope.$digest();
expect(form.childControlMoved).toBeUndefined();
expect(otherForm.childControlMoved).toBeUndefined();
});
it('should remove scope reference when form with no parent form is removed from the DOM', function() {
var formController;
scope.ctrl = {};
@@ -547,35 +660,153 @@ describe('form', function() {
expect(doc.find('div').hasClass('ng-pending')).toBe(false);
});
it('should leave the parent form invalid when deregister a removed input', function() {
doc = jqLite(
'<form name="parent">' +
'<div class="ng-form" name="child">' +
'<input ng-if="inputPresent" ng-model="modelA" name="inputA" required>' +
'<input ng-model="modelB" name="inputB" required>' +
'</div>' +
'</form>');
$compile(doc)(scope);
scope.inputPresent = true;
scope.$apply();
var parent = scope.parent,
child = scope.child,
inputA = child.inputA,
inputB = child.inputB;
it('should leave the parent form invalid when deregister a removed input', function() {
doc = jqLite(
'<form name="parent">' +
'<div class="ng-form" name="child">' +
'<input ng-if="inputPresent" ng-model="modelA" name="inputA" required>' +
'<input ng-model="modelB" name="inputB" required>' +
'</div>' +
'</form>');
$compile(doc)(scope);
scope.inputPresent = true;
scope.$apply();
expect(parent).toBeDefined();
expect(child).toBeDefined();
expect(parent.$error.required).toEqual([child]);
expect(child.$error.required).toEqual([inputB, inputA]);
var parent = scope.parent,
child = scope.child,
inputA = child.inputA,
inputB = child.inputB;
//remove child input
scope.inputPresent = false;
scope.$apply();
expect(parent).toBeDefined();
expect(child).toBeDefined();
expect(parent.$error.required).toEqual([child]);
expect(child.$error.required).toEqual([inputB, inputA]);
//remove child input
scope.inputPresent = false;
scope.$apply();
expect(parent.$error.required).toEqual([child]);
expect(child.$error.required).toEqual([inputB]);
});
it('should ignore changes in manually removed child forms', function() {
doc = $compile(
'<form name="myForm">' +
'<ng-form name="childform">' +
'<input name="childformcontrol" ng-maxlength="1" ng-model="value"/>' +
'</ng-form>' +
'</form>')(scope);
var form = scope.myForm;
var childformController = doc.find('ng-form').eq(0).controller('form');
var input = doc.find('input').eq(0);
var inputController = input.controller('ngModel');
changeInputValue(input, 'ab');
scope.$apply();
expect(form.$dirty).toBe(true);
expect(form.$error.maxlength).toBeTruthy();
expect(form.$error.maxlength[0].$name).toBe('childform');
inputController.$setPristine();
expect(form.$dirty).toBe(true);
form.$setPristine();
// remove child form
form.$removeControl(childformController);
expect(form.childform).toBeUndefined();
expect(form.$error.maxlength).toBeFalsy();
changeInputValue(input, 'abc');
scope.$apply();
expect(form.$error.maxlength).toBeFalsy();
expect(form.$dirty).toBe(false);
});
it('should react to changes in manually added child forms', function() {
doc = $compile(
'<form name="myForm">' +
'<ng-form name="childForm">' +
'<input name="childformcontrol" ng-maxlength="1" ng-model="value" />' +
'</ng-form>' +
'</form>')(scope);
var form = scope.myForm;
var childFormController = doc.find('ng-form').eq(0).controller('form');
var input = doc.find('input').eq(0);
// remove child form so we can add it manually
form.$removeControl(childFormController);
changeInputValue(input, 'ab');
expect(form.childForm).toBeUndefined();
expect(form.$dirty).toBe(false);
expect(form.$error.maxlength).toBeFalsy();
// re-add the child form; its current validation state is not propagated
form.$addControl(childFormController);
expect(form.childForm).toBe(childFormController);
expect(form.$error.maxlength).toBeFalsy();
expect(form.$dirty).toBe(false);
// Only when the input inside the child form changes, the validation state is propagated
changeInputValue(input, 'abc');
expect(form.$error.maxlength[0]).toBe(childFormController);
expect(form.$dirty).toBe(false);
});
it('should use the correct parent when renaming and removing dynamically added forms', function() {
scope.formName = 'childForm';
scope.hasChildForm = true;
doc = $compile(
'<form name="myForm">' +
'<div ng-if="hasChildForm">' +
'<ng-form name="{{formName}}">' +
'<input name="childformcontrol" ng-maxlength="1" ng-model="value"/>' +
'</ng-form>' +
'</div>' +
'</form>' +
'<form name="otherForm"></form>')(scope);
scope.$digest();
var form = scope.myForm;
var otherForm = scope.otherForm;
var childForm = form.childForm;
// remove child form and add it to another form
form.$removeControl(childForm);
otherForm.$addControl(childForm);
expect(form.childForm).toBeUndefined();
expect(otherForm.childForm).toBe(childForm);
// rename the childForm
scope.formName = 'childFormMoved';
scope.$digest();
expect(form.childFormMoved).toBeUndefined();
expect(otherForm.childForm).toBeUndefined();
expect(otherForm.childFormMoved).toBe(childForm);
scope.hasChildForm = false;
scope.$digest();
expect(form.childFormMoved).toBeUndefined();
expect(otherForm.childFormMoved).toBeUndefined();
});
expect(parent.$error.required).toEqual([child]);
expect(child.$error.required).toEqual([inputB]);
});
it('should chain nested forms in repeater', function() {
doc = jqLite(
+93 -5
View File
@@ -688,6 +688,14 @@ describe('input', function() {
expect(inputElm).toBeInvalid();
expect($rootScope.form.alias.$error.min).toBeTruthy();
});
it('should validate if min is empty', function() {
$rootScope.minVal = undefined;
$rootScope.value = new Date(-9999, 0, 1, 0, 0, 0);
$rootScope.$digest();
expect($rootScope.form.alias.$error.min).toBeFalsy();
});
});
describe('max', function() {
@@ -722,6 +730,14 @@ describe('input', function() {
expect(inputElm).toBeInvalid();
expect($rootScope.form.alias.$error.max).toBeTruthy();
});
it('should validate if max is empty', function() {
$rootScope.maxVal = undefined;
$rootScope.value = new Date(9999, 11, 31, 23, 59, 59);
$rootScope.$digest();
expect($rootScope.form.alias.$error.max).toBeFalsy();
});
});
});
@@ -886,6 +902,14 @@ describe('input', function() {
expect(inputElm).toBeInvalid();
expect($rootScope.form.alias.$error.min).toBeTruthy();
});
it('should validate if min is empty', function() {
$rootScope.minVal = undefined;
$rootScope.value = new Date(-9999, 0, 1, 0, 0, 0);
$rootScope.$digest();
expect($rootScope.form.alias.$error.min).toBeFalsy();
});
});
describe('max', function() {
@@ -921,6 +945,14 @@ describe('input', function() {
expect(inputElm).toBeInvalid();
expect($rootScope.form.alias.$error.max).toBeTruthy();
});
it('should validate if max is empty', function() {
$rootScope.maxVal = undefined;
$rootScope.value = new Date(9999, 11, 31, 23, 59, 59);
$rootScope.$digest();
expect($rootScope.form.alias.$error.max).toBeFalsy();
});
});
});
@@ -1119,6 +1151,14 @@ describe('input', function() {
expect(inputElm).toBeInvalid();
expect($rootScope.form.alias.$error.min).toBeTruthy();
});
it('should validate if min is empty', function() {
$rootScope.minVal = undefined;
$rootScope.value = new Date(-9999, 0, 1, 0, 0, 0);
$rootScope.$digest();
expect($rootScope.form.alias.$error.min).toBeFalsy();
});
});
describe('max', function() {
@@ -1153,6 +1193,14 @@ describe('input', function() {
expect(inputElm).toBeInvalid();
expect($rootScope.form.alias.$error.max).toBeTruthy();
});
it('should validate if max is empty', function() {
$rootScope.maxVal = undefined;
$rootScope.value = new Date(9999, 11, 31, 23, 59, 59);
$rootScope.$digest();
expect($rootScope.form.alias.$error.max).toBeFalsy();
});
});
@@ -1428,12 +1476,21 @@ describe('input', function() {
expect(inputElm).toBeInvalid();
expect($rootScope.form.alias.$error.min).toBeTruthy();
});
it('should validate if min is empty', function() {
$rootScope.minVal = undefined;
$rootScope.value = new Date(-9999, 0, 1, 0, 0, 0);
$rootScope.$digest();
expect($rootScope.form.alias.$error.min).toBeFalsy();
});
});
describe('max', function() {
var inputElm;
beforeEach(function() {
inputElm = helper.compileInput('<input type="time" ng-model="value" name="alias" max="22:30:00" />');
$rootScope.maxVal = '22:30:00';
inputElm = helper.compileInput('<input type="time" ng-model="value" name="alias" max="{{ maxVal }}" />');
});
it('should invalidate', function() {
@@ -1449,11 +1506,19 @@ describe('input', function() {
expect(+$rootScope.value).toBe(+new Date(1970, 0, 1, 5, 30, 0));
expect($rootScope.form.alias.$error.max).toBeFalsy();
});
it('should validate if max is empty', function() {
$rootScope.maxVal = undefined;
$rootScope.value = new Date(9999, 11, 31, 23, 59, 59);
$rootScope.$digest();
expect($rootScope.form.alias.$error.max).toBeFalsy();
});
});
it('should validate even if max value changes on-the-fly', function() {
$rootScope.max = '4:02:00';
$rootScope.max = '04:02:00';
var inputElm = helper.compileInput('<input type="time" ng-model="value" name="alias" max="{{max}}" />');
helper.changeInputValueTo('05:34:00');
@@ -1481,7 +1546,7 @@ describe('input', function() {
it('should validate even if ng-max value changes on-the-fly', function() {
$rootScope.max = '4:02:00';
$rootScope.max = '04:02:00';
var inputElm = helper.compileInput('<input type="time" ng-model="value" name="alias" ng-max="max" />');
helper.changeInputValueTo('05:34:00');
@@ -1706,6 +1771,16 @@ describe('input', function() {
expect($rootScope.form.myControl.$error.min).toBeTruthy();
});
it('should validate if min is empty', function() {
var inputElm = helper.compileInput(
'<input type="date" name="alias" ng-model="value" min />');
$rootScope.value = new Date(-9999, 0, 1, 0, 0, 0);
$rootScope.$digest();
expect($rootScope.form.alias.$error.min).toBeFalsy();
});
});
describe('max', function() {
@@ -1735,6 +1810,16 @@ describe('input', function() {
expect($rootScope.form.myControl.$error.max).toBeTruthy();
});
it('should validate if max is empty', function() {
var inputElm = helper.compileInput(
'<input type="date" name="alias" ng-model="value" max />');
$rootScope.value = new Date(9999, 11, 31, 23, 59, 59);
$rootScope.$digest();
expect($rootScope.form.alias.$error.max).toBeFalsy();
});
});
@@ -2598,14 +2683,17 @@ describe('input', function() {
});
it('should set the ngTrueValue when required directive is present', function() {
var inputElm = helper.compileInput('<input type="checkbox" ng-model="value" required ng-true-value="\'yes\'" />');
it('should pass validation for "required" when trueValue is a string', function() {
var inputElm = helper.compileInput('<input type="checkbox" required name="cb"' +
'ng-model="value" ng-true-value="\'yes\'" />');
expect(inputElm).toBeInvalid();
expect($rootScope.form.cb.$error.required).toBe(true);
browserTrigger(inputElm, 'click');
expect(inputElm[0].checked).toBe(true);
expect(inputElm).toBeValid();
expect($rootScope.form.cb.$error.required).toBeUndefined();
});
});
+2 -2
View File
@@ -468,7 +468,7 @@ describe('ngClass animations', function() {
};
});
});
inject(function($compile, $rootScope, $browser, $rootElement, $animate, $timeout, $$body, $$rAF) {
inject(function($compile, $rootScope, $browser, $rootElement, $animate, $timeout, $$body) {
$animate.enabled(true);
$rootScope.val = 'crazy';
@@ -488,7 +488,7 @@ describe('ngClass animations', function() {
expect(enterComplete).toBe(false);
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
$rootScope.$digest();
expect(element.hasClass('crazy')).toBe(true);
+10 -8
View File
@@ -428,9 +428,11 @@ describe('ngInclude', function() {
});
expect(autoScrollSpy).not.toHaveBeenCalled();
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
$rootScope.$digest();
expect($animate.queue.shift().event).toBe('enter');
expect(autoScrollSpy).toHaveBeenCalledOnce();
}));
@@ -446,7 +448,6 @@ describe('ngInclude', function() {
});
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$rootScope.$apply(function() {
$rootScope.tpl = 'another.html';
@@ -455,7 +456,6 @@ describe('ngInclude', function() {
expect($animate.queue.shift().event).toBe('leave');
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$rootScope.$apply(function() {
$rootScope.tpl = 'template.html';
@@ -464,7 +464,9 @@ describe('ngInclude', function() {
expect($animate.queue.shift().event).toBe('leave');
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
$rootScope.$digest();
expect(autoScrollSpy).toHaveBeenCalled();
expect(autoScrollSpy.callCount).toBe(3);
@@ -480,7 +482,6 @@ describe('ngInclude', function() {
});
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
expect(autoScrollSpy).not.toHaveBeenCalled();
}));
@@ -496,7 +497,6 @@ describe('ngInclude', function() {
});
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$rootScope.$apply(function() {
$rootScope.tpl = 'template.html';
@@ -518,7 +518,9 @@ describe('ngInclude', function() {
$rootScope.$apply("tpl = 'template.html'");
expect($animate.queue.shift().event).toBe('enter');
$animate.triggerCallbacks();
$animate.flush();
$rootScope.$digest();
expect(autoScrollSpy).toHaveBeenCalledOnce();
}
+3 -1
View File
@@ -19,7 +19,6 @@ describe('ngModel', function() {
};
element = jqLite('<form><input></form>');
element.data('$formController', parentFormCtrl);
scope = $rootScope;
ngModelAccessor = jasmine.createSpy('ngModel accessor');
@@ -28,6 +27,9 @@ describe('ngModel', function() {
$element: element.find('input'),
$attrs: attrs
});
//Assign the mocked parentFormCtrl to the model controller
ctrl.$$parentForm = parentFormCtrl;
}));
+15 -1
View File
@@ -2013,7 +2013,7 @@ describe('ngOptions', function() {
});
it('should support biding via ngBind attribute', function() {
it('should support binding via ngBind attribute', function() {
var option;
createSingleSelect('<option value="" ng-bind="blankVal"></option>');
@@ -2029,6 +2029,20 @@ describe('ngOptions', function() {
expect(option.text()).toBe('is blank');
});
it('should support option without a value attribute', function() {
createSingleSelect('<option>--select--</option>');
scope.$apply(function() {
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
});
var options = element.find('option');
expect(options.eq(0)).toEqualUnknownOption();
expect(options.eq(1)).toEqualOption(scope.values[0], 'A');
expect(options.eq(2)).toEqualOption(scope.values[1], 'B');
expect(options.eq(3)).toEqualOption(scope.values[2], 'C');
});
it('should be rendered with the attributes preserved', function() {
var option;
+22 -2
View File
@@ -166,6 +166,26 @@ describe('ngRepeat', function() {
expect(element.text()).toEqual('age:20|wealth:20|prodname:Bingo|dogname:Bingo|codename:20|');
});
it('should iterate over on object created using `Object.create(null)`', function() {
element = $compile(
'<ul>' +
'<li ng-repeat="(key, value) in items">{{key}}:{{value}}|</li>' +
'</ul>')(scope);
var items = Object.create(null);
items.misko = 'swe';
items.shyam = 'set';
scope.items = items;
scope.$digest();
expect(element.text()).toEqual('misko:swe|shyam:set|');
delete items.shyam;
scope.$digest();
expect(element.text()).toEqual('misko:swe|');
});
describe('track by', function() {
it('should track using expression function', function() {
element = $compile(
@@ -1462,7 +1482,7 @@ describe('ngRepeat animations', function() {
}));
it('should not change the position of the block that is being animated away via a leave animation',
inject(function($compile, $rootScope, $animate, $document, $window, $sniffer, $timeout, $$rAF) {
inject(function($compile, $rootScope, $animate, $document, $window, $sniffer, $timeout) {
if (!$sniffer.transitions) return;
var item;
@@ -1487,7 +1507,7 @@ describe('ngRepeat animations', function() {
$rootScope.$digest();
expect(element.text()).toBe('123'); // the original order should be preserved
$$rAF.flush();
$animate.flush();
$timeout.flush(1500); // 1s * 1.5 closing buffer
expect(element.text()).toBe('13');
} finally {
+123 -2
View File
@@ -318,6 +318,26 @@ describe('select', function() {
});
it('should support option without a value attribute', function() {
compile('<select ng-model="robot">' +
'<option>--select--</option>' +
'<option value="x">robot x</option>' +
'<option value="y">robot y</option>' +
'</select>');
expect(element).toEqualSelect(["? undefined:undefined ?"], "--select--", 'x', 'y');
});
it('should support option without a value with other HTML attributes', function() {
compile('<select ng-model="robot">' +
'<option data-foo="bar">--select--</option>' +
'<option value="x">robot x</option>' +
'<option value="y">robot y</option>' +
'</select>');
expect(element).toEqualSelect(["? undefined:undefined ?"], "--select--", 'x', 'y');
});
describe('interactions with repeated options', function() {
it('should select empty option when model is undefined', function() {
@@ -964,22 +984,123 @@ describe('select', function() {
describe('option', function() {
it('should populate value attribute on OPTION', function() {
it('should populate a missing value attribute with the option text', function() {
compile('<select ng-model="x"><option selected>abc</option></select>');
expect(element).toEqualSelect([unknownValue(undefined)], 'abc');
});
it('should ignore value if already exists', function() {
it('should ignore the option text if the value attribute exists', function() {
compile('<select ng-model="x"><option value="abc">xyz</option></select>');
expect(element).toEqualSelect([unknownValue(undefined)], 'abc');
});
it('should set value even if self closing HTML', function() {
scope.x = 'hello';
compile('<select ng-model="x"><option>hello</select>');
expect(element).toEqualSelect(['hello']);
});
it('should add options with interpolated value attributes', function() {
scope.option1 = 'option1';
scope.option2 = 'option2';
compile('<select ng-model="selected">' +
'<option value="{{option1}}">Option 1</option>' +
'<option value="{{option2}}">Option 2</option>' +
'</select>');
scope.$digest();
expect(scope.selected).toBeUndefined();
browserTrigger(element.find('option').eq(0));
expect(scope.selected).toBe('option1');
scope.selected = 'option2';
scope.$digest();
expect(element.find('option').eq(1).prop('selected')).toBe(true);
expect(element.find('option').eq(1).text()).toBe('Option 2');
});
it('should update the option when the interpolated value attribute changes', function() {
scope.option1 = 'option1';
scope.option2 = '';
compile('<select ng-model="selected">' +
'<option value="{{option1}}">Option 1</option>' +
'<option value="{{option2}}">Option 2</option>' +
'</select>');
var selectCtrl = element.controller('select');
spyOn(selectCtrl, 'removeOption').andCallThrough();
scope.$digest();
expect(scope.selected).toBeUndefined();
expect(selectCtrl.removeOption).not.toHaveBeenCalled();
//Change value of option2
scope.option2 = 'option2Changed';
scope.selected = 'option2Changed';
scope.$digest();
expect(selectCtrl.removeOption).toHaveBeenCalledWith('');
expect(element.find('option').eq(1).prop('selected')).toBe(true);
expect(element.find('option').eq(1).text()).toBe('Option 2');
});
it('should add options with interpolated text', function() {
scope.option1 = 'Option 1';
scope.option2 = 'Option 2';
compile('<select ng-model="selected">' +
'<option>{{option1}}</option>' +
'<option>{{option2}}</option>' +
'</select>');
scope.$digest();
expect(scope.selected).toBeUndefined();
browserTrigger(element.find('option').eq(0));
expect(scope.selected).toBe('Option 1');
scope.selected = 'Option 2';
scope.$digest();
expect(element.find('option').eq(1).prop('selected')).toBe(true);
expect(element.find('option').eq(1).text()).toBe('Option 2');
});
it('should update options when their interpolated text changes', function() {
scope.option1 = 'Option 1';
scope.option2 = '';
compile('<select ng-model="selected">' +
'<option>{{option1}}</option>' +
'<option>{{option2}}</option>' +
'</select>');
var selectCtrl = element.controller('select');
spyOn(selectCtrl, 'removeOption').andCallThrough();
scope.$digest();
expect(scope.selected).toBeUndefined();
expect(selectCtrl.removeOption).not.toHaveBeenCalled();
//Change value of option2
scope.option2 = 'Option 2 Changed';
scope.selected = 'Option 2 Changed';
scope.$digest();
expect(selectCtrl.removeOption).toHaveBeenCalledWith('');
expect(element.find('option').eq(1).prop('selected')).toBe(true);
expect(element.find('option').eq(1).text()).toBe('Option 2 Changed');
});
it('should not blow up when option directive is found inside of a datalist',
inject(function($compile, $rootScope) {
var element = $compile('<div>' +
+88
View File
@@ -204,6 +204,39 @@ describe('validators', function() {
expect($rootScope.form.test.$error.pattern).toBe(true);
expect(inputElm).not.toBeValid();
});
it('should validate the viewValue and not the modelValue', function() {
var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="\\d{4}">');
var ctrl = inputElm.controller('ngModel');
ctrl.$parsers.push(function(value) {
return (value * 10) + '';
});
helper.changeInputValueTo('1234');
expect($rootScope.form.test.$error.pattern).not.toBe(true);
expect($rootScope.form.test.$modelValue).toBe('12340');
expect(inputElm).toBeValid();
});
it('should validate on non-input elements', inject(function($compile) {
$rootScope.pattern = '\\d{4}';
var elm = $compile('<span ng-model="value" pattern="\\d{4}"></span>')($rootScope);
var elmNg = $compile('<span ng-model="value" ng-pattern="pattern"></span>')($rootScope);
var ctrl = elm.controller('ngModel');
var ctrlNg = elmNg.controller('ngModel');
expect(ctrl.$error.pattern).not.toBe(true);
expect(ctrlNg.$error.pattern).not.toBe(true);
ctrl.$setViewValue('12');
ctrlNg.$setViewValue('12');
expect(ctrl.$error.pattern).toBe(true);
expect(ctrlNg.$error.pattern).toBe(true);
}));
});
@@ -268,6 +301,24 @@ describe('validators', function() {
helper.changeInputValueTo('12345');
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
});
it('should validate on non-input elements', inject(function($compile) {
$rootScope.min = 3;
var elm = $compile('<span ng-model="value" minlength="{{min}}"></span>')($rootScope);
var elmNg = $compile('<span ng-model="value" ng-minlength="min"></span>')($rootScope);
var ctrl = elm.controller('ngModel');
var ctrlNg = elmNg.controller('ngModel');
expect(ctrl.$error.minlength).not.toBe(true);
expect(ctrlNg.$error.minlength).not.toBe(true);
ctrl.$setViewValue('12');
ctrlNg.$setViewValue('12');
expect(ctrl.$error.minlength).toBe(true);
expect(ctrlNg.$error.minlength).toBe(true);
}));
});
@@ -438,6 +489,24 @@ describe('validators', function() {
helper.changeInputValueTo('12345');
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
});
it('should validate on non-input elements', inject(function($compile) {
$rootScope.max = 3;
var elm = $compile('<span ng-model="value" maxlength="{{max}}"></span>')($rootScope);
var elmNg = $compile('<span ng-model="value" ng-maxlength="max"></span>')($rootScope);
var ctrl = elm.controller('ngModel');
var ctrlNg = elmNg.controller('ngModel');
expect(ctrl.$error.maxlength).not.toBe(true);
expect(ctrlNg.$error.maxlength).not.toBe(true);
ctrl.$setViewValue('1234');
ctrlNg.$setViewValue('1234');
expect(ctrl.$error.maxlength).toBe(true);
expect(ctrlNg.$error.maxlength).toBe(true);
}));
});
@@ -532,6 +601,7 @@ describe('validators', function() {
expect(inputElm).toBeValid();
});
it('should validate emptiness against the viewValue', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" required />');
@@ -545,5 +615,23 @@ describe('validators', function() {
helper.changeInputValueTo('12345');
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
});
it('should validate on non-input elements', inject(function($compile) {
$rootScope.value = '12';
var elm = $compile('<span ng-model="value" required></span>')($rootScope);
var elmNg = $compile('<span ng-model="value" ng-required="true"></span>')($rootScope);
var ctrl = elm.controller('ngModel');
var ctrlNg = elmNg.controller('ngModel');
expect(ctrl.$error.required).not.toBe(true);
expect(ctrlNg.$error.required).not.toBe(true);
ctrl.$setViewValue('');
ctrlNg.$setViewValue('');
expect(ctrl.$error.required).toBe(true);
expect(ctrlNg.$error.required).toBe(true);
}));
});
});
+11 -9
View File
@@ -44,21 +44,23 @@ describe('$httpBackend', function() {
});
it('should pass null to send if no body is set', function() {
$backend('GET', '/some-url', null, noop);
$backend('GET', '/some-url', undefined, noop);
xhr = MockXhr.$$lastInstance;
expect(xhr.$$data).toBe(null);
});
it('should pass the correct falsy value to send if falsy body is set (excluding NaN)', function() {
var values = [false, 0, "", null, undefined];
angular.forEach(values, function(value) {
$backend('GET', '/some-url', value, noop);
xhr = MockXhr.$$lastInstance;
it('should pass the correct falsy value to send if falsy body is set (excluding undefined, NaN)',
function() {
var values = [false, 0, "", null];
angular.forEach(values, function(value) {
$backend('GET', '/some-url', value, noop);
xhr = MockXhr.$$lastInstance;
expect(xhr.$$data).toBe(value);
});
});
expect(xhr.$$data).toBe(value);
});
}
);
it('should pass NaN to send if NaN body is set', function() {
$backend('GET', '/some-url', NaN, noop);
+14 -13
View File
@@ -764,8 +764,8 @@ describe('$http', function() {
$httpBackend.expect('POST', '/url', 'messageBody', function(headers) {
return headers['accept'] == 'Rewritten' &&
headers['content-type'] == 'Content-Type Rewritten' &&
headers['Accept'] === undefined &&
headers['Content-Type'] === undefined;
isUndefined(headers['Accept']) &&
isUndefined(headers['Content-Type']);
}).respond('');
$http({url: '/url', method: 'POST', data: 'messageBody', headers: {
@@ -779,7 +779,7 @@ describe('$http', function() {
mockedCookies['XSRF-TOKEN'] = 'secret';
$browser.url('http://host.com/base');
$httpBackend.expect('GET', 'http://www.test.com/url', undefined, function(headers) {
return headers['X-XSRF-TOKEN'] === undefined;
return isUndefined(headers['X-XSRF-TOKEN']);
}).respond('');
$http({url: 'http://www.test.com/url', method: 'GET', headers: {}});
@@ -933,12 +933,13 @@ describe('$http', function() {
it('should handle empty response header', function() {
$httpBackend.expect('GET', '/url', undefined)
.respond(200, '', { 'Custom-Empty-Response-Header': '', 'Constructor': '' });
$http.get('/url').success(callback);
$http.get('/url').then(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnce();
expect(callback.mostRecentCall.args[2]('custom-empty-response-Header')).toBe('');
expect(callback.mostRecentCall.args[2]('ToString')).toBe(null);
expect(callback.mostRecentCall.args[2]('Constructor')).toBe('');
var headers = callback.mostRecentCall.args[0].headers;
expect(headers('custom-empty-response-Header')).toEqual('');
expect(headers('ToString')).toBe(null);
expect(headers('Constructor')).toBe('');
});
it('should have delete()', function() {
@@ -1731,12 +1732,12 @@ describe('$http', function() {
$httpBackend.expect('GET', '/some').respond(200);
$http({method: 'GET', url: '/some', timeout: canceler.promise}).error(
function(data, status, headers, config) {
expect(data).toBeUndefined();
expect(status).toBe(0);
expect(headers()).toEqual({});
expect(config.url).toBe('/some');
$http({method: 'GET', url: '/some', timeout: canceler.promise}).catch(
function(response) {
expect(response.data).toBeUndefined();
expect(response.status).toBe(-1);
expect(response.headers()).toEqual({});
expect(response.config.url).toBe('/some');
callback();
});
+1 -1
View File
@@ -29,7 +29,7 @@ describe('$interval', function() {
if (fn.id === id) fnIndex = index;
});
if (fnIndex !== undefined) {
if (isDefined(fnIndex)) {
repeatFns.splice(fnIndex, 1);
return true;
}
+28 -8
View File
@@ -1679,11 +1679,10 @@ describe('parser', function() {
forEach([true, false], function(cspEnabled) {
describe('csp: ' + cspEnabled, function() {
beforeEach(module(function($provide) {
$provide.decorator('$sniffer', function($delegate) {
$delegate.csp = cspEnabled;
return $delegate;
});
beforeEach(module(function() {
expect(csp().noUnsafeEval === true ||
csp().noUnsafeEval === false).toEqual(true);
csp().noUnsafeEval = cspEnabled;
}, provideLog));
beforeEach(inject(function($rootScope) {
@@ -2120,9 +2119,8 @@ describe('parser', function() {
expect(scope.$eval('items[1] = "abc"')).toEqual("abc");
expect(scope.$eval('items[1]')).toEqual("abc");
// Dont know how to make this work....
// expect(scope.$eval('books[1] = "moby"')).toEqual("moby");
// expect(scope.$eval('books[1]')).toEqual("moby");
expect(scope.$eval('books[1] = "moby"')).toEqual("moby");
expect(scope.$eval('books[1]')).toEqual("moby");
});
it('should evaluate grouped filters', function() {
@@ -2669,6 +2667,20 @@ describe('parser', function() {
scope.$eval('{}["__proto__"].foo = 1');
}).toThrowMinErr('$parse', 'isecfld');
expect(function() {
scope.$eval('{}[["__proto__"]]');
}).toThrowMinErr('$parse', 'isecfld');
expect(function() {
scope.$eval('{}[["__proto__"]].foo = 1');
}).toThrowMinErr('$parse', 'isecfld');
expect(function() {
scope.$eval('0[["__proto__"]]');
}).toThrowMinErr('$parse', 'isecfld');
expect(function() {
scope.$eval('0[["__proto__"]].foo = 1');
}).toThrowMinErr('$parse', 'isecfld');
scope.a = "__pro";
scope.b = "to__";
expect(function() {
@@ -2797,6 +2809,14 @@ describe('parser', function() {
expect(scope).toEqual({a:123});
}));
it('should return the assigned value', inject(function($parse) {
var fn = $parse('a');
var scope = {};
expect(fn.assign(scope, 123)).toBe(123);
var someObject = {};
expect(fn.assign(scope, someObject)).toBe(someObject);
}));
it('should expose working assignment function for expressions ending with brackets', inject(function($parse) {
var fn = $parse('a.b["c"]');
expect(fn.assign).toBeTruthy();
+1 -1
View File
@@ -51,7 +51,7 @@ describe('q', function() {
log.push(logPrefix + '->throw(' + _argToString(returnVal) + ')');
throw returnVal;
} else {
if (returnVal === undefined) {
if (isUndefined(returnVal)) {
log.push(logPrefix);
} else {
log.push(logPrefix + '->' + _argToString(returnVal));
-40
View File
@@ -31,46 +31,6 @@ describe('$$rAF', function() {
expect(present).toBe(true);
}));
it('should only consume only one RAF if multiple async functions are registered before the first frame kicks in', inject(function($$rAF) {
if (!$$rAF.supported) return;
//we need to create our own injector to work around the ngMock overrides
var rafLog = [];
var injector = createInjector(['ng', function($provide) {
$provide.value('$window', {
location: window.location,
history: window.history,
webkitRequestAnimationFrame: function(fn) {
rafLog.push(fn);
}
});
}]);
$$rAF = injector.get('$$rAF');
var log = [];
function logFn() {
log.push(log.length);
}
$$rAF(logFn);
$$rAF(logFn);
$$rAF(logFn);
expect(log).toEqual([]);
expect(rafLog.length).toBe(1);
rafLog[0]();
expect(log).toEqual([0,1,2]);
expect(rafLog.length).toBe(1);
$$rAF(logFn);
expect(log).toEqual([0,1,2]);
expect(rafLog.length).toBe(2);
}));
describe('$timeout fallback', function() {
it("it should use a $timeout incase native rAF isn't suppored", function() {
var timeoutSpy = jasmine.createSpy('callback');
+13
View File
@@ -822,6 +822,7 @@ describe('Scope', function() {
expect(log.empty()).toEqual([{newVal: {b: {}, c: 'B'}, oldVal: {a: [], b: {}, c: 'B'}}]);
});
it('should not infinitely digest when current value is NaN', function() {
$rootScope.obj = {a: NaN};
expect(function() {
@@ -829,6 +830,18 @@ describe('Scope', function() {
}).not.toThrow();
});
it('should handle objects created using `Object.create(null)`', function() {
$rootScope.obj = Object.create(null);
$rootScope.obj.a = 'a';
$rootScope.obj.b = 'b';
$rootScope.$digest();
expect(log.empty()[0].newVal).toEqual({a: 'a', b: 'b'});
delete $rootScope.obj.b;
$rootScope.$digest();
expect(log.empty()[0].newVal).toEqual({a: 'a'});
});
});
});
+2 -2
View File
@@ -282,10 +282,10 @@ describe('SCE', function() {
function runTest(cfg, testFn) {
return function() {
module(function($sceDelegateProvider) {
if (cfg.whiteList !== undefined) {
if (isDefined(cfg.whiteList)) {
$sceDelegateProvider.resourceUrlWhitelist(cfg.whiteList);
}
if (cfg.blackList !== undefined) {
if (isDefined(cfg.blackList)) {
$sceDelegateProvider.resourceUrlBlacklist(cfg.blackList);
}
});
+20 -28
View File
@@ -3,6 +3,7 @@
describe("ngAnimate $$animateCssDriver", function() {
beforeEach(module('ngAnimate'));
beforeEach(module('ngAnimateMock'));
function int(x) {
return parseInt(x, 10);
@@ -104,7 +105,7 @@ describe("ngAnimate $$animateCssDriver", function() {
expect(capturedAnimation[1].applyClassesEarly).toBeFalsy();
driver({ element: element, structural: true });
expect(capturedAnimation[1].applyClassesEarly).toBeFalsy();
expect(capturedAnimation[1].applyClassesEarly).toBeTruthy();
}));
it("should only set the event value if the animation is structural", inject(function() {
@@ -414,7 +415,7 @@ describe("ngAnimate $$animateCssDriver", function() {
}));
it("should then do an addClass('ng-anchor-in') animation on the cloned anchor and remove the old class",
inject(function($rootElement, $$rAF) {
inject(function($rootElement) {
var fromAnchor = jqLite('<div></div>');
from.append(fromAnchor);
@@ -434,7 +435,6 @@ describe("ngAnimate $$animateCssDriver", function() {
}).start();
captureLog.pop().runner.end();
$$rAF.flush();
var anchorDetails = captureLog.pop().args[1];
expect(anchorDetails.removeClass.trim()).toBe('ng-anchor-out');
@@ -464,7 +464,7 @@ describe("ngAnimate $$animateCssDriver", function() {
});
});
inject(function($rootElement, $$rAF) {
inject(function($rootElement, $animate) {
var fromAnchor = jqLite('<div></div>');
from.append(fromAnchor);
var toAnchor = jqLite('<div></div>');
@@ -488,7 +488,7 @@ describe("ngAnimate $$animateCssDriver", function() {
expect(animationStarted).toBe(expectedClass);
runner.end();
$$rAF.flush();
$animate.flush();
expect(complete).toBe(true);
});
});
@@ -552,7 +552,7 @@ describe("ngAnimate $$animateCssDriver", function() {
expect(int(fromStyles.left)).toBeGreaterThan(149);
}));
it("should append a `px` value for all seeded animation styles", inject(function($rootElement, $$rAF) {
it("should append a `px` value for all seeded animation styles", inject(function($rootElement) {
ss.addRule('.starting-element', 'width:10px; height:20px; display:inline-block;');
var fromAnchor = jqLite('<div class="starting-element"' +
@@ -582,7 +582,6 @@ describe("ngAnimate $$animateCssDriver", function() {
// the out animation goes first
anchorAnimation.runner.end();
$$rAF.flush();
anchorAnimation = captureLog.pop();
anchorDetails = anchorAnimation.args[1];
@@ -593,7 +592,7 @@ describe("ngAnimate $$animateCssDriver", function() {
}));
it("should then do an removeClass('out') + addClass('in') animation on the cloned anchor",
inject(function($rootElement, $$rAF) {
inject(function($rootElement) {
var fromAnchor = jqLite('<div></div>');
from.append(fromAnchor);
@@ -614,7 +613,6 @@ describe("ngAnimate $$animateCssDriver", function() {
// the out animation goes first
captureLog.pop().runner.end();
$$rAF.flush();
var anchorDetails = captureLog.pop().args[1];
expect(anchorDetails.removeClass).toMatch(/\bout\b/);
@@ -623,7 +621,7 @@ describe("ngAnimate $$animateCssDriver", function() {
}));
it("should add the `ng-anchor` class to the cloned anchor element",
inject(function($rootElement, $$rAF) {
inject(function($rootElement) {
var fromAnchor = jqLite('<div></div>');
from.append(fromAnchor);
@@ -647,7 +645,7 @@ describe("ngAnimate $$animateCssDriver", function() {
}));
it("should add and remove the `ng-animate-shim` class on the in anchor element during the animation",
inject(function($rootElement, $$rAF) {
inject(function($rootElement) {
var fromAnchor = jqLite('<div></div>');
from.append(fromAnchor);
@@ -670,14 +668,13 @@ describe("ngAnimate $$animateCssDriver", function() {
// the out animation goes first
captureLog.pop().runner.end();
$$rAF.flush();
captureLog.pop().runner.end();
expect(fromAnchor).not.toHaveClass('ng-animate-shim');
}));
it("should add and remove the `ng-animate-shim` class on the out anchor element during the animation",
inject(function($rootElement, $$rAF) {
inject(function($rootElement) {
var fromAnchor = jqLite('<div></div>');
from.append(fromAnchor);
@@ -700,7 +697,6 @@ describe("ngAnimate $$animateCssDriver", function() {
// the out animation goes first
captureLog.pop().runner.end();
$$rAF.flush();
expect(toAnchor).toHaveClass('ng-animate-shim');
captureLog.pop().runner.end();
@@ -709,7 +705,7 @@ describe("ngAnimate $$animateCssDriver", function() {
}));
it("should create the cloned anchor with all of the classes from the from anchor element",
inject(function($rootElement, $$rAF) {
inject(function($rootElement) {
var fromAnchor = jqLite('<div class="yes no maybe"></div>');
from.append(fromAnchor);
@@ -733,7 +729,7 @@ describe("ngAnimate $$animateCssDriver", function() {
}));
it("should remove the classes of the starting anchor from the cloned anchor node during the in animation and also add the classes of the destination anchor within the same animation",
inject(function($rootElement, $$rAF) {
inject(function($rootElement) {
var fromAnchor = jqLite('<div class="yes no maybe"></div>');
from.append(fromAnchor);
@@ -754,7 +750,6 @@ describe("ngAnimate $$animateCssDriver", function() {
// the out animation goes first
captureLog.pop().runner.end();
$$rAF.flush();
var anchorDetails = captureLog.pop().args[1];
var removedClasses = anchorDetails.removeClass.split(' ');
@@ -765,7 +760,7 @@ describe("ngAnimate $$animateCssDriver", function() {
}));
it("should not attempt to add/remove any classes that contain a `ng-` prefix",
inject(function($rootElement, $$rAF) {
inject(function($rootElement) {
var fromAnchor = jqLite('<div class="ng-yes ng-no sure"></div>');
from.append(fromAnchor);
@@ -786,7 +781,6 @@ describe("ngAnimate $$animateCssDriver", function() {
// the out animation goes first
captureLog.pop().runner.end();
$$rAF.flush();
var inAnimation = captureLog.pop();
var details = inAnimation.args[1];
@@ -802,7 +796,7 @@ describe("ngAnimate $$animateCssDriver", function() {
}));
it("should not remove any shared CSS classes between the starting and destination anchor element during the in animation",
inject(function($rootElement, $$rAF) {
inject(function($rootElement) {
var fromAnchor = jqLite('<div class="blue green red"></div>');
from.append(fromAnchor);
@@ -823,7 +817,6 @@ describe("ngAnimate $$animateCssDriver", function() {
// the out animation goes first
captureLog.pop().runner.end();
$$rAF.flush();
var inAnimation = captureLog.pop();
var clonedAnchor = inAnimation.element;
@@ -851,7 +844,7 @@ describe("ngAnimate $$animateCssDriver", function() {
}));
it("should continue the anchor animation by seeding the to styles based on where the final anchor element will be positioned",
inject(function($rootElement, $$rAF) {
inject(function($rootElement) {
ss.addRule('.ending-element', 'width:9999px; height:6666px; display:inline-block;');
var fromAnchor = jqLite('<div></div>');
@@ -874,7 +867,6 @@ describe("ngAnimate $$animateCssDriver", function() {
}).start();
captureLog.pop().runner.end();
$$rAF.flush();
var anchorAnimation = captureLog.pop();
var anchorElement = anchorAnimation.element;
@@ -889,7 +881,7 @@ describe("ngAnimate $$animateCssDriver", function() {
}));
it("should remove the cloned anchor node from the DOM once the 'in' animation is complete",
inject(function($rootElement, $$rAF) {
inject(function($rootElement) {
var fromAnchor = jqLite('<div class="blue green red"></div>');
from.append(fromAnchor);
@@ -913,7 +905,6 @@ describe("ngAnimate $$animateCssDriver", function() {
var clonedAnchor = inAnimation.element;
expect(clonedAnchor.parent().length).toBe(1);
inAnimation.runner.end();
$$rAF.flush();
// now the in animation completes
expect(clonedAnchor.parent().length).toBe(1);
@@ -923,7 +914,7 @@ describe("ngAnimate $$animateCssDriver", function() {
}));
it("should pass the provided domOperation into $animateCss to be run right after the element is animated if a leave animation is present",
inject(function($rootElement, $$rAF) {
inject(function($rootElement) {
toAnimation.structural = true;
toAnimation.event = 'enter';
@@ -949,7 +940,7 @@ describe("ngAnimate $$animateCssDriver", function() {
}));
it("should fire the returned runner promise when the from, to and anchor animations are all complete",
inject(function($rootElement, $rootScope, $$rAF) {
inject(function($rootElement, $rootScope, $animate) {
ss.addRule('.ending-element', 'width:9999px; height:6666px; display:inline-block;');
@@ -978,7 +969,8 @@ describe("ngAnimate $$animateCssDriver", function() {
captureLog.pop().runner.end(); //to
captureLog.pop().runner.end(); //anchor(out)
captureLog.pop().runner.end(); //anchor(in)
$$rAF.flush();
$animate.flush();
$rootScope.$digest();
expect(completed).toBe(true);
+194 -41
View File
@@ -3,6 +3,7 @@
describe("ngAnimate $animateCss", function() {
beforeEach(module('ngAnimate'));
beforeEach(module('ngAnimateMock'));
function assertAnimationRunning(element, not) {
var className = element.attr('class');
@@ -17,9 +18,10 @@ describe("ngAnimate $animateCss", function() {
var ss, prefix, triggerAnimationStartFrame;
beforeEach(module(function() {
return function($document, $window, $sniffer, $$rAF) {
return function($document, $window, $sniffer, $$rAF, $animate) {
prefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
ss = createMockStyleSheet($document, $window);
$animate.enabled(true);
triggerAnimationStartFrame = function() {
$$rAF.flush();
};
@@ -51,8 +53,25 @@ describe("ngAnimate $animateCss", function() {
describe('when active', function() {
if (!browserSupportsCssAnimations()) return;
it("should not attempt an animation if animations are globally disabled",
inject(function($animateCss, $animate, $rootElement, $$body) {
$animate.enabled(false);
var animator, element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
animator = $animateCss(element, {
duration: 10,
to: { 'height': '100px' }
});
expect(animator.$$willAnimate).toBeFalsy();
}));
it("should silently quit the animation and not throw when an element has no parent during preparation",
inject(function($animateCss, $$rAF, $rootScope, $document, $rootElement) {
inject(function($animateCss, $rootScope, $document, $rootElement) {
var element = jqLite('<div></div>');
expect(function() {
@@ -385,7 +404,7 @@ describe("ngAnimate $animateCss", function() {
they("should close the animation, but still accept $prop callbacks if no animation is detected",
['done', 'then'], function(method) {
inject(function($animateCss, $$rAF, $rootScope) {
inject(function($animateCss, $animate, $rootScope) {
ss.addRule('.the-third-fake-animation', 'background:green;');
element.addClass('another-fake-animation');
@@ -400,7 +419,8 @@ describe("ngAnimate $animateCss", function() {
});
expect(done).toBe(false);
$$rAF.flush();
$animate.flush();
if (method === 'then') {
$rootScope.$digest();
}
@@ -411,7 +431,7 @@ describe("ngAnimate $animateCss", function() {
they("should close the animation, but still accept recognize runner.$prop if no animation is detected",
['done(cancel)', 'catch'], function(method) {
inject(function($animateCss, $$rAF, $rootScope) {
inject(function($animateCss, $rootScope) {
ss.addRule('.the-third-fake-animation', 'background:green;');
element.addClass('another-fake-animation');
@@ -1078,7 +1098,7 @@ describe("ngAnimate $animateCss", function() {
}));
it("should still resolve the animation once expired",
inject(function($animateCss, $$body, $rootElement, $timeout) {
inject(function($animateCss, $$body, $rootElement, $timeout, $animate, $rootScope) {
ss.addRule('.ng-enter', 'transition:10s linear all;');
@@ -1097,11 +1117,13 @@ describe("ngAnimate $animateCss", function() {
triggerAnimationStartFrame();
$timeout.flush(15000);
$animate.flush();
$rootScope.$digest();
expect(passed).toBe(true);
}));
it("should not resolve/reject after passing if the animation completed successfully",
inject(function($animateCss, $$body, $rootElement, $timeout, $rootScope) {
inject(function($animateCss, $$body, $rootElement, $timeout, $rootScope, $animate) {
ss.addRule('.ng-enter', 'transition:10s linear all;');
@@ -1125,6 +1147,7 @@ describe("ngAnimate $animateCss", function() {
browserTrigger(element, 'transitionend',
{ timeStamp: Date.now() + 1000, elapsedTime: 10 });
$animate.flush();
$rootScope.$digest();
expect(passed).toBe(true);
@@ -1135,6 +1158,82 @@ describe("ngAnimate $animateCss", function() {
expect(passed).toBe(true);
expect(failed).not.toBe(true);
}));
it("should close all stacked animations after the last timeout runs on the same element",
inject(function($animateCss, $$body, $rootElement, $timeout, $animate) {
var now = 0;
spyOn(Date, 'now').andCallFake(function() {
return now;
});
var cancelSpy = spyOn($timeout, 'cancel').andCallThrough();
var doneSpy = jasmine.createSpy();
ss.addRule('.elm', 'transition:1s linear all;');
ss.addRule('.elm.red', 'background:red;');
ss.addRule('.elm.blue', 'transition:2s linear all; background:blue;');
ss.addRule('.elm.green', 'background:green;');
var element = jqLite('<div class="elm"></div>');
$rootElement.append(element);
$$body.append($rootElement);
// timeout will be at 1500s
animate(element, 'red', doneSpy);
expect(doneSpy).not.toHaveBeenCalled();
fastForwardClock(500); //1000s left to go
// timeout will not be at 500 + 3000s = 3500s
animate(element, 'blue', doneSpy);
expect(doneSpy).not.toHaveBeenCalled();
expect(cancelSpy).toHaveBeenCalled();
cancelSpy.reset();
// timeout will not be set again since the former animation is longer
animate(element, 'green', doneSpy);
expect(doneSpy).not.toHaveBeenCalled();
expect(cancelSpy).not.toHaveBeenCalled();
// this will close the animations fully
fastForwardClock(3500);
$animate.flush();
expect(doneSpy).toHaveBeenCalled();
expect(doneSpy.callCount).toBe(3);
function fastForwardClock(time) {
now += time;
$timeout.flush(time);
}
function animate(element, klass, onDone) {
var animator = $animateCss(element, { addClass: klass }).start();
animator.done(onDone);
triggerAnimationStartFrame();
return animator;
}
}));
it("should not throw an error any pending timeout requests resolve after the element has already been removed",
inject(function($animateCss, $$body, $rootElement, $timeout, $animate) {
var element = jqLite('<div></div>');
$rootElement.append(element);
$$body.append($rootElement);
ss.addRule('.red', 'transition:1s linear all;');
$animateCss(element, { addClass: 'red' }).start();
triggerAnimationStartFrame();
element.remove();
expect(function() {
$timeout.flush();
}).not.toThrow();
}));
});
describe("getComputedStyle", function() {
@@ -1162,7 +1261,7 @@ describe("ngAnimate $animateCss", function() {
}));
it("should cache frequent calls to getComputedStyle before the next animation frame kicks in",
inject(function($animateCss, $document, $rootElement, $$rAF) {
inject(function($animateCss, $document, $rootElement) {
var i, elm, animator;
for (i = 0; i < 5; i++) {
@@ -1277,6 +1376,25 @@ describe("ngAnimate $animateCss", function() {
expect(element.attr('style')).not.toContain('transition');
}));
it("should clear cache if no animation so follow-up animation on the same element will not be from cache",
inject(function($animateCss, $rootElement, $$body, $$rAF) {
var element = jqLite('<div class="rclass"></div>');
var options = {
event: 'enter',
structural: true
};
$rootElement.append(element);
$$body.append($rootElement);
var animator = $animateCss(element, options);
expect(animator.$$willAnimate).toBeFalsy();
$$rAF.flush();
ss.addRule('.ng-enter', '-webkit-animation:3.5s keyframe_animation;' +
'animation:3.5s keyframe_animation;');
animator = $animateCss(element, options);
expect(animator.$$willAnimate).toBeTruthy();
}));
it('should apply a custom temporary class when a non-structural animation is used',
inject(function($animateCss, $rootElement, $$body) {
@@ -1671,11 +1789,13 @@ describe("ngAnimate $animateCss", function() {
describe("options", function() {
var element;
beforeEach(inject(function($rootElement, $$body) {
$$body.append($rootElement);
beforeEach(module(function() {
return function($rootElement, $$body) {
$$body.append($rootElement);
element = jqLite('<div></div>');
$rootElement.append(element);
element = jqLite('<div></div>');
$rootElement.append(element);
};
}));
describe("[$$skipPreparationClasses]", function() {
@@ -1862,7 +1982,6 @@ describe("ngAnimate $animateCss", function() {
animator.start();
triggerAnimationStartFrame();
var prop = element.css('transition-delay');
expect(prop).toEqual('500s');
}));
@@ -1978,6 +2097,53 @@ describe("ngAnimate $animateCss", function() {
expect(element.css('transition-delay')).toEqual('10s');
}));
it("should apply the keyframe and transition duration value before the CSS classes are applied", function() {
var classSpy = jasmine.createSpy();
module(function($provide) {
$provide.value('$$jqLite', {
addClass: function() {
classSpy();
},
removeClass: function() {
classSpy();
}
});
});
inject(function($animateCss, $rootElement) {
element.addClass('element');
ss.addRule('.element', '-webkit-animation:3s keyframe_animation;' +
'animation:3s keyframe_animation;' +
'transition:5s linear all;');
var options = {
delay: 2,
duration: 2,
addClass: 'superman',
$$skipPreparationClasses: true,
structural: true
};
var animator = $animateCss(element, options);
expect(element.attr('style') || '').not.toContain('animation-delay');
expect(element.attr('style') || '').not.toContain('transition-delay');
expect(classSpy).not.toHaveBeenCalled();
//redefine the classSpy to assert that the delay values have been
//applied before the classes are added
var assertionsRun = false;
classSpy = function() {
assertionsRun = true;
expect(element.css(prefix + 'animation-delay')).toEqual('2s');
expect(element.css('transition-delay')).toEqual('2s');
expect(element).not.toHaveClass('superman');
};
animator.start();
triggerAnimationStartFrame();
expect(assertionsRun).toBe(true);
});
});
it("should apply blocking before the animation starts, but then apply the detected delay when options.delay is true",
inject(function($animateCss, $rootElement) {
@@ -1995,41 +2161,28 @@ describe("ngAnimate $animateCss", function() {
animator.start();
triggerAnimationStartFrame();
expect(element.css('transition-delay')).toEqual('1s');
expect(element.attr('style') || '').not.toContain('transition-delay');
}));
they("should consider a negative value when delay:true is used with a $prop animation", {
'transition': function() {
return {
prop: 'transition-delay',
css: 'transition:2s linear all; transition-delay: -1s'
};
},
'keyframe': function(prefix) {
return {
prop: prefix + 'animation-delay',
css: prefix + 'animation:2s keyframe_animation; ' + prefix + 'animation-delay: -1s;'
};
}
}, function(testDetailsFactory) {
it("should consider a negative value when delay:true is used with a keyframe animation",
inject(function($animateCss, $rootElement) {
var testDetails = testDetailsFactory(prefix);
ss.addRule('.ng-enter', testDetails.css);
var options = {
delay: true,
event: 'enter',
structural: true
};
ss.addRule('.ng-enter', prefix + 'animation:2s keyframe_animation; ' +
prefix + 'animation-delay: -1s;');
var animator = $animateCss(element, options);
var options = {
delay: true,
event: 'enter',
structural: true
};
animator.start();
triggerAnimationStartFrame();
var animator = $animateCss(element, options);
expect(element.css(testDetails.prop)).toContain('-1s');
});
});
animator.start();
triggerAnimationStartFrame();
expect(element.css(prefix + 'animation-delay')).toContain('-1s');
}));
they("should consider a negative value when a negative option delay is provided for a $prop animation", {
'transition': function() {
+6 -5
View File
@@ -3,6 +3,7 @@
describe("ngAnimate $$animateJsDriver", function() {
beforeEach(module('ngAnimate'));
beforeEach(module('ngAnimateMock'));
it('should register the $$animateJsDriver into the list of drivers found in $animateProvider',
module(function($animateProvider) {
@@ -96,7 +97,7 @@ describe("ngAnimate $$animateJsDriver", function() {
}));
they('should $prop both animations when $prop() is called on the runner', ['end', 'cancel'], function(method) {
inject(function($rootScope, $$rAF) {
inject(function($rootScope, $animate) {
var child1 = jqLite('<div></div>');
element.append(child1);
var child2 = jqLite('<div></div>');
@@ -127,7 +128,7 @@ describe("ngAnimate $$animateJsDriver", function() {
$rootScope.$digest();
runner[method]();
$$rAF.flush();
$animate.flush();
expect(animationsClosed).toBe(true);
expect(status).toBe(method === 'end' ? true : false);
@@ -135,7 +136,7 @@ describe("ngAnimate $$animateJsDriver", function() {
});
they('should fully $prop when all inner animations are complete', ['end', 'cancel'], function(method) {
inject(function($rootScope, $$rAF) {
inject(function($rootScope, $animate) {
var child1 = jqLite('<div></div>');
element.append(child1);
var child2 = jqLite('<div></div>');
@@ -163,12 +164,12 @@ describe("ngAnimate $$animateJsDriver", function() {
status = s;
});
$$rAF.flush();
captureLog[0].runner[method]();
expect(animationsClosed).toBe(false);
captureLog[1].runner[method]();
$animate.flush();
expect(animationsClosed).toBe(true);
expect(status).toBe(method === 'end' ? true : false);
+35 -34
View File
@@ -3,6 +3,7 @@
describe("ngAnimate $$animateJs", function() {
beforeEach(module('ngAnimate'));
beforeEach(module('ngAnimateMock'));
function getDoneFunction(args) {
for (var i = 1; i < args.length; i++) {
@@ -86,7 +87,7 @@ describe("ngAnimate $$animateJs", function() {
$animateProvider.register('.two', makeAnimation('enter'));
$animateProvider.register('.three', makeAnimation('enter'));
});
inject(function($$animateJs, $$rAF) {
inject(function($$animateJs, $animate) {
var element = jqLite('<div class="one two three"></div>');
var animator = $$animateJs(element, 'enter');
var complete = false;
@@ -97,7 +98,7 @@ describe("ngAnimate $$animateJs", function() {
forEach(doneCallbacks, function(cb) {
cb();
});
$$rAF.flush();
$animate.flush();
expect(complete).toBe(true);
});
});
@@ -206,7 +207,7 @@ describe("ngAnimate $$animateJs", function() {
};
});
});
inject(function($$animateJs, $$rAF) {
inject(function($$animateJs, $animate) {
var element = jqLite('<div class="the-end"></div>');
var animator = $$animateJs(element, 'addClass', {
addClass: 'red'
@@ -221,12 +222,12 @@ describe("ngAnimate $$animateJs", function() {
before();
expect(after).toBeUndefined();
$$rAF.flush();
$animate.flush();
expect(after).toBeDefined();
after();
expect(endCalled).toBeUndefined();
$$rAF.flush();
$animate.flush();
expect(endCalled).toBe(true);
});
});
@@ -251,7 +252,7 @@ describe("ngAnimate $$animateJs", function() {
};
});
});
inject(function($$animateJs, $$rAF) {
inject(function($$animateJs, $animate) {
var element = jqLite('<div class="the-end"></div>');
var animator = $$animateJs(element, 'addClass', {
domOperation: function() {
@@ -264,7 +265,7 @@ describe("ngAnimate $$animateJs", function() {
});
runner[method]();
$$rAF.flush();
$animate.flush();
expect(log).toEqual(
['before addClass ' + method,
'dom addClass',
@@ -278,7 +279,7 @@ describe("ngAnimate $$animateJs", function() {
return { beforeAddClass: noop };
});
});
inject(function($$animateJs, $$rAF, $rootScope) {
inject(function($$animateJs, $animate, $rootScope) {
var element = jqLite('<div class="the-end"></div>');
var animator = $$animateJs(element, 'addClass');
var runner = animator.start();
@@ -291,7 +292,7 @@ describe("ngAnimate $$animateJs", function() {
});
runner.end();
$$rAF.flush();
$animate.flush();
$rootScope.$digest();
expect(done).toBe(true);
expect(cancelled).toBe(false);
@@ -304,7 +305,7 @@ describe("ngAnimate $$animateJs", function() {
return { beforeAddClass: noop };
});
});
inject(function($$animateJs, $$rAF, $rootScope) {
inject(function($$animateJs, $animate, $rootScope) {
var element = jqLite('<div class="the-end"></div>');
var animator = $$animateJs(element, 'addClass');
var runner = animator.start();
@@ -317,7 +318,7 @@ describe("ngAnimate $$animateJs", function() {
});
runner.cancel();
$$rAF.flush();
$animate.flush();
$rootScope.$digest();
expect(done).toBe(false);
expect(cancelled).toBe(true);
@@ -504,7 +505,7 @@ describe("ngAnimate $$animateJs", function() {
var allEvents = ['leave'].concat(otherEvents).concat(enterMoveEvents);
they("$prop should asynchronously render the before$prop animation", otherEvents, function(event) {
inject(function($$rAF) {
inject(function($animate) {
var beforeMethod = 'before' + event.charAt(0).toUpperCase() + event.substr(1);
animations[beforeMethod] = function(element, a, b, c) {
log.push('before ' + event);
@@ -514,14 +515,14 @@ describe("ngAnimate $$animateJs", function() {
runAnimation(event);
expect(log).toEqual(['before ' + event]);
$$rAF.flush();
$animate.flush();
expect(log).toEqual(['before ' + event, 'dom ' + event]);
});
});
they("$prop should asynchronously render the $prop animation", allEvents, function(event) {
inject(function($$rAF) {
inject(function($animate) {
animations[event] = function(element, a, b, c) {
log.push('after ' + event);
var done = getDoneFunction(arguments);
@@ -534,11 +535,11 @@ describe("ngAnimate $$animateJs", function() {
if (event === 'leave') {
expect(log).toEqual(['after leave']);
$$rAF.flush();
$animate.flush();
expect(log).toEqual(['after leave', 'dom leave', 'complete']);
} else {
expect(log).toEqual(['dom ' + event, 'after ' + event]);
$$rAF.flush();
$animate.flush();
expect(log).toEqual(['dom ' + event, 'after ' + event, 'complete']);
}
});
@@ -547,7 +548,7 @@ describe("ngAnimate $$animateJs", function() {
they("$prop should asynchronously render the $prop animation when a start/end animator object is returned",
allEvents, function(event) {
inject(function($$rAF, $$AnimateRunner) {
inject(function($animate, $$AnimateRunner) {
var runner;
animations[event] = function(element, a, b, c) {
return {
@@ -565,12 +566,12 @@ describe("ngAnimate $$animateJs", function() {
if (event === 'leave') {
expect(log).toEqual(['start leave']);
runner.end();
$$rAF.flush();
$animate.flush();
expect(log).toEqual(['start leave', 'dom leave', 'complete']);
} else {
expect(log).toEqual(['dom ' + event, 'start ' + event]);
runner.end();
$$rAF.flush();
$animate.flush();
expect(log).toEqual(['dom ' + event, 'start ' + event, 'complete']);
}
});
@@ -579,7 +580,7 @@ describe("ngAnimate $$animateJs", function() {
they("$prop should asynchronously render the $prop animation when an instance of $$AnimateRunner is returned",
allEvents, function(event) {
inject(function($$rAF, $$AnimateRunner) {
inject(function($animate, $$AnimateRunner) {
var runner;
animations[event] = function(element, a, b, c) {
log.push('start ' + event);
@@ -593,19 +594,19 @@ describe("ngAnimate $$animateJs", function() {
if (event === 'leave') {
expect(log).toEqual(['start leave']);
runner.end();
$$rAF.flush();
$animate.flush();
expect(log).toEqual(['start leave', 'dom leave', 'complete']);
} else {
expect(log).toEqual(['dom ' + event, 'start ' + event]);
runner.end();
$$rAF.flush();
$animate.flush();
expect(log).toEqual(['dom ' + event, 'start ' + event, 'complete']);
}
});
});
they("$prop should asynchronously reject the before animation if the callback function is called with false", otherEvents, function(event) {
inject(function($$rAF, $rootScope) {
inject(function($animate, $rootScope) {
var beforeMethod = 'before' + event.charAt(0).toUpperCase() + event.substr(1);
animations[beforeMethod] = function(element, a, b, c) {
log.push('before ' + event);
@@ -624,13 +625,13 @@ describe("ngAnimate $$animateJs", function() {
function() { log.push('fail'); });
expect(log).toEqual(['before ' + event]);
$$rAF.flush();
$animate.flush();
expect(log).toEqual(['before ' + event, 'dom ' + event, 'fail']);
});
});
they("$prop should asynchronously reject the after animation if the callback function is called with false", allEvents, function(event) {
inject(function($$rAF, $rootScope) {
inject(function($animate, $rootScope) {
animations[event] = function(element, a, b, c) {
log.push('after ' + event);
var done = getDoneFunction(arguments);
@@ -644,17 +645,17 @@ describe("ngAnimate $$animateJs", function() {
var expectations = [];
if (event === 'leave') {
expect(log).toEqual(['after leave']);
$$rAF.flush();
$animate.flush();
expect(log).toEqual(['after leave', 'dom leave', 'fail']);
} else {
expect(log).toEqual(['dom ' + event, 'after ' + event]);
$$rAF.flush();
$animate.flush();
expect(log).toEqual(['dom ' + event, 'after ' + event, 'fail']);
}
});
});
it('setClass should delegate down to addClass/removeClass if not defined', inject(function($$rAF) {
it('setClass should delegate down to addClass/removeClass if not defined', inject(function($animate) {
animations.addClass = function(element, done) {
log.push('addClass');
};
@@ -671,7 +672,7 @@ describe("ngAnimate $$animateJs", function() {
}));
it('beforeSetClass should delegate down to beforeAddClass/beforeRemoveClass if not defined',
inject(function($$rAF) {
inject(function($animate) {
animations.beforeAddClass = function(element, className, done) {
log.push('beforeAddClass');
@@ -686,13 +687,13 @@ describe("ngAnimate $$animateJs", function() {
expect(animations.setClass).toBeFalsy();
runAnimation('setClass');
$$rAF.flush();
$animate.flush();
expect(log).toEqual(['beforeRemoveClass', 'beforeAddClass', 'dom setClass']);
}));
it('leave should always ignore the `beforeLeave` animation',
inject(function($$rAF) {
inject(function($animate) {
animations.beforeLeave = function(element, done) {
log.push('beforeLeave');
@@ -705,13 +706,13 @@ describe("ngAnimate $$animateJs", function() {
};
runAnimation('leave');
$$rAF.flush();
$animate.flush();
expect(log).toEqual(['leave', 'dom leave']);
}));
it('should allow custom events to be triggered',
inject(function($$rAF) {
inject(function($animate) {
animations.beforeFlex = function(element, done) {
log.push('beforeFlex');
@@ -724,7 +725,7 @@ describe("ngAnimate $$animateJs", function() {
};
runAnimation('flex');
$$rAF.flush();
$animate.flush();
expect(log).toEqual(['beforeFlex', 'dom flex', 'flex']);
}));
+8 -8
View File
@@ -1,12 +1,12 @@
'use strict';
describe('$$rAFMutex', function() {
describe('$$animateAsyncRun', function() {
beforeEach(module('ngAnimate'));
it('should fire the callback only when one or more RAFs have passed',
inject(function($$rAF, $$rAFMutex) {
inject(function($$animateAsyncRun, $$rAF) {
var trigger = $$rAFMutex();
var trigger = $$animateAsyncRun();
var called = false;
trigger(function() {
called = true;
@@ -18,9 +18,9 @@ describe('$$rAFMutex', function() {
}));
it('should immediately fire the callback if a RAF has passed since construction',
inject(function($$rAF, $$rAFMutex) {
inject(function($$animateAsyncRun, $$rAF) {
var trigger = $$rAFMutex();
var trigger = $$animateAsyncRun();
$$rAF.flush();
var called = false;
@@ -89,7 +89,7 @@ describe("$$AnimateRunner", function() {
they("should immediately resolve each combined runner in a bottom-up order when $prop is called",
['end', 'cancel'], function(method) {
inject(function($$AnimateRunner, $$rAF) {
inject(function($$AnimateRunner) {
var runner1 = new $$AnimateRunner();
var runner2 = new $$AnimateRunner();
runner1.setHost(runner2);
@@ -206,7 +206,7 @@ describe("$$AnimateRunner", function() {
they("should immediately resolve if and when all runners have been $prop",
{ ended: 'end', cancelled: 'cancel' }, function(method) {
inject(function($$rAF, $$AnimateRunner) {
inject(function($$AnimateRunner) {
var runner1 = new $$AnimateRunner();
var runner2 = new $$AnimateRunner();
var runner3 = new $$AnimateRunner();
@@ -227,7 +227,7 @@ describe("$$AnimateRunner", function() {
});
it("should return a status of `false` if one or more runners was cancelled",
inject(function($$rAF, $$AnimateRunner) {
inject(function($$AnimateRunner) {
var runner1 = new $$AnimateRunner();
var runner2 = new $$AnimateRunner();
+48 -116
View File
@@ -3,6 +3,7 @@
describe("animations", function() {
beforeEach(module('ngAnimate'));
beforeEach(module('ngAnimateMock'));
var element, applyAnimationClasses;
afterEach(inject(function($$jqLite) {
@@ -410,7 +411,7 @@ describe("animations", function() {
}));
it('should remove all element and comment nodes during leave animation',
inject(function($compile, $rootScope, $$rAF, $$AnimateRunner) {
inject(function($compile, $rootScope, $animate, $$AnimateRunner) {
element = $compile(
'<div>' +
@@ -433,7 +434,7 @@ describe("animations", function() {
$rootScope.items.length = 0;
$rootScope.$digest();
runner.end();
$$rAF.flush();
$animate.flush();
// we're left with a text node and a comment node
expect(element[0].childNodes.length).toBeLessThan(3);
@@ -491,74 +492,6 @@ describe("animations", function() {
expect(element).not.toHaveClass('green');
}));
they('should apply the $prop CSS class to the element before digest for the given event and remove when complete',
{'ng-enter': 'enter', 'ng-leave': 'leave', 'ng-move': 'move'}, function(event) {
inject(function($animate, $rootScope, $document, $rootElement) {
$animate.enabled(true);
var element = jqLite('<div></div>');
var parent = jqLite('<div></div>');
$rootElement.append(parent);
jqLite($document[0].body).append($rootElement);
var runner;
if (event === 'leave') {
parent.append(element);
runner = $animate[event](element);
} else {
runner = $animate[event](element, parent);
}
var expectedClassName = 'ng-' + event;
expect(element).toHaveClass(expectedClassName);
$rootScope.$digest();
expect(element).toHaveClass(expectedClassName);
runner.end();
expect(element).not.toHaveClass(expectedClassName);
dealoc(parent);
});
});
they('should add CSS classes with the $prop suffix when depending on the event and remove when complete',
{'-add': 'add', '-remove': 'remove'}, function(event) {
inject(function($animate, $rootScope, $document, $rootElement) {
$animate.enabled(true);
var element = jqLite('<div></div>');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
var classes = 'one two';
var expectedClasses = ['one-',event,' ','two-', event].join('');
var runner;
if (event === 'add') {
runner = $animate.addClass(element, classes);
} else {
element.addClass(classes);
runner = $animate.removeClass(element, classes);
}
expect(element).toHaveClass(expectedClasses);
$rootScope.$digest();
expect(element).toHaveClass(expectedClasses);
runner.end();
expect(element).not.toHaveClass(expectedClasses);
dealoc(element);
});
});
they('$prop() should operate using a native DOM element',
['enter', 'move', 'leave', 'addClass', 'removeClass', 'setClass', 'animate'], function(event) {
@@ -671,7 +604,7 @@ describe("animations", function() {
they('should not cancel a pre-digest parent class-based animation if a child $prop animation is set to run',
['structural', 'class-based'], function(animationType) {
inject(function($rootScope, $animate, $$rAF) {
inject(function($rootScope, $animate) {
parent.append(element);
var child = jqLite('<div></div>');
@@ -692,7 +625,7 @@ describe("animations", function() {
they('should not cancel a post-digest parent class-based animation if a child $prop animation is set to run',
['structural', 'class-based'], function(animationType) {
inject(function($rootScope, $animate, $$rAF) {
inject(function($rootScope, $animate) {
parent.append(element);
var child = jqLite('<div></div>');
@@ -717,7 +650,7 @@ describe("animations", function() {
they('should not cancel a post-digest $prop child animation if a class-based parent animation is set to run',
['structural', 'class-based'], function(animationType) {
inject(function($rootScope, $animate, $$rAF) {
inject(function($rootScope, $animate) {
parent.append(element);
var child = jqLite('<div></div>');
@@ -815,8 +748,8 @@ describe("animations", function() {
expect(capturedAnimation).toBeFalsy();
}));
it("should disable all child animations for atleast one RAF when a structural animation is issued",
inject(function($animate, $rootScope, $compile, $$body, $rootElement, $$rAF, $$AnimateRunner) {
it("should disable all child animations for atleast one turn when a structural animation is issued",
inject(function($animate, $rootScope, $compile, $$body, $rootElement, $$AnimateRunner) {
element = $compile(
'<div><div class="if-animation" ng-if="items.length">' +
@@ -847,7 +780,7 @@ describe("animations", function() {
expect(element[0].querySelectorAll('.repeat-animation').length).toBe(2);
runner.end();
$$rAF.flush();
$animate.flush();
$rootScope.items = [1, 2, 3];
$rootScope.$digest();
@@ -946,7 +879,7 @@ describe("animations", function() {
}));
it('should allow follow-up class-based animations to run in parallel on the same element',
inject(function($rootScope, $animate, $$rAF) {
inject(function($rootScope, $animate) {
parent.append(element);
@@ -957,7 +890,7 @@ describe("animations", function() {
});
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(capturedAnimation).toBeTruthy();
expect(runner1done).toBeFalsy();
@@ -977,7 +910,7 @@ describe("animations", function() {
});
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(capturedAnimation).toBeTruthy();
expect(runner2done).toBeFalsy();
@@ -990,7 +923,7 @@ describe("animations", function() {
}));
it('should remove the animation block on child animations once the parent animation is complete',
inject(function($rootScope, $rootElement, $animate, $$AnimateRunner, $$rAF) {
inject(function($rootScope, $rootElement, $animate, $$AnimateRunner) {
var runner = new $$AnimateRunner();
overriddenAnimationRunner = runner;
@@ -1005,7 +938,7 @@ describe("animations", function() {
expect(capturedAnimationHistory.length).toBe(1);
runner.end();
$$rAF.flush();
$animate.flush();
$animate.addClass(element, 'stark');
$rootScope.$digest();
@@ -1036,19 +969,19 @@ describe("animations", function() {
}));
it('should cancel the previous structural animation if a follow-up structural animation takes over before the postDigest',
inject(function($animate, $$rAF) {
inject(function($animate) {
var enterDone = jasmine.createSpy('enter animation done');
$animate.enter(element, parent).done(enterDone);
expect(enterDone).not.toHaveBeenCalled();
$animate.leave(element);
$$rAF.flush();
$animate.flush();
expect(enterDone).toHaveBeenCalled();
}));
it('should cancel the previously running addClass animation if a follow-up removeClass animation is using the same class value',
inject(function($animate, $rootScope, $$rAF) {
inject(function($animate, $rootScope) {
parent.append(element);
var runner = $animate.addClass(element, 'active-class');
@@ -1057,7 +990,7 @@ describe("animations", function() {
var doneHandler = jasmine.createSpy('addClass done');
runner.done(doneHandler);
$$rAF.flush();
$animate.flush();
expect(doneHandler).not.toHaveBeenCalled();
@@ -1068,7 +1001,7 @@ describe("animations", function() {
}));
it('should cancel the previously running removeClass animation if a follow-up addClass animation is using the same class value',
inject(function($animate, $rootScope, $$rAF) {
inject(function($animate, $rootScope) {
element.addClass('active-class');
parent.append(element);
@@ -1078,7 +1011,7 @@ describe("animations", function() {
var doneHandler = jasmine.createSpy('addClass done');
runner.done(doneHandler);
$$rAF.flush();
$animate.flush();
expect(doneHandler).not.toHaveBeenCalled();
@@ -1294,7 +1227,7 @@ describe("animations", function() {
}));
it('should not skip or miss the animations when animations are executed sequential',
inject(function($animate, $rootScope, $$rAF, $rootElement) {
inject(function($animate, $rootScope, $rootElement) {
element = jqLite('<div></div>');
@@ -1306,8 +1239,6 @@ describe("animations", function() {
$animate.removeClass(element, 'rclass');
$rootScope.$digest();
$$rAF.flush();
expect(element).not.toHaveClass('rclass');
}));
});
@@ -1532,7 +1463,7 @@ describe("animations", function() {
}));
it('should trigger a callback for an enter animation',
inject(function($animate, $rootScope, $$rAF, $rootElement, $$body) {
inject(function($animate, $rootScope, $rootElement, $$body) {
var callbackTriggered = false;
$animate.on('enter', $$body, function() {
@@ -1543,13 +1474,13 @@ describe("animations", function() {
$animate.enter(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(callbackTriggered).toBe(true);
}));
it('should fire the callback with the signature of (element, phase, data)',
inject(function($animate, $rootScope, $$rAF, $rootElement, $$body) {
inject(function($animate, $rootScope, $rootElement, $$body) {
var capturedElement;
var capturedPhase;
@@ -1565,7 +1496,7 @@ describe("animations", function() {
element = jqLite('<div></div>');
$animate.enter(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(capturedElement).toBe(element);
expect(isString(capturedPhase)).toBe(true);
@@ -1573,7 +1504,7 @@ describe("animations", function() {
}));
it('should not fire a callback if the element is outside of the given container',
inject(function($animate, $rootScope, $$rAF, $rootElement) {
inject(function($animate, $rootScope, $rootElement) {
var callbackTriggered = false;
var innerContainer = jqLite('<div></div>');
@@ -1588,13 +1519,13 @@ describe("animations", function() {
element = jqLite('<div></div>');
$animate.enter(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(callbackTriggered).toBe(false);
}));
it('should fire a callback if the element is the given container',
inject(function($animate, $rootScope, $$rAF, $rootElement) {
inject(function($animate, $rootScope, $rootElement) {
element = jqLite('<div></div>');
@@ -1607,13 +1538,13 @@ describe("animations", function() {
$animate.enter(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(callbackTriggered).toBe(true);
}));
it('should remove all the event-based event listeners when $animate.off(event) is called',
inject(function($animate, $rootScope, $$rAF, $rootElement, $$body) {
inject(function($animate, $rootScope, $rootElement, $$body) {
element = jqLite('<div></div>');
@@ -1627,7 +1558,7 @@ describe("animations", function() {
$animate.enter(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(count).toBe(2);
@@ -1635,13 +1566,13 @@ describe("animations", function() {
$animate.enter(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(count).toBe(2);
}));
it('should remove the container-based event listeners when $animate.off(event, container) is called',
inject(function($animate, $rootScope, $$rAF, $rootElement, $$body) {
inject(function($animate, $rootScope, $rootElement, $$body) {
element = jqLite('<div></div>');
@@ -1657,7 +1588,7 @@ describe("animations", function() {
$animate.enter(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(count).toBe(2);
@@ -1665,13 +1596,13 @@ describe("animations", function() {
$animate.enter(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(count).toBe(3);
}));
it('should remove the callback-based event listener when $animate.off(event, container, callback) is called',
inject(function($animate, $rootScope, $$rAF, $rootElement) {
inject(function($animate, $rootScope, $rootElement) {
element = jqLite('<div></div>');
@@ -1693,7 +1624,7 @@ describe("animations", function() {
$animate.enter(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(count).toBe(2);
@@ -1701,13 +1632,13 @@ describe("animations", function() {
$animate.enter(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(count).toBe(3);
}));
it('should fire a `start` callback when the animation starts with the matching element',
inject(function($animate, $rootScope, $$rAF, $rootElement, $$body) {
inject(function($animate, $rootScope, $rootElement, $$body) {
element = jqLite('<div></div>');
@@ -1720,14 +1651,14 @@ describe("animations", function() {
$animate.enter(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(capturedState).toBe('start');
expect(capturedElement).toBe(element);
}));
it('should fire a `close` callback when the animation ends with the matching element',
inject(function($animate, $rootScope, $$rAF, $rootElement, $$body) {
inject(function($animate, $rootScope, $rootElement, $$body) {
element = jqLite('<div></div>');
@@ -1741,14 +1672,14 @@ describe("animations", function() {
var runner = $animate.enter(element, $rootElement);
$rootScope.$digest();
runner.end();
$$rAF.flush();
$animate.flush();
expect(capturedState).toBe('close');
expect(capturedElement).toBe(element);
}));
it('should remove the event listener if the element is removed',
inject(function($animate, $rootScope, $$rAF, $rootElement) {
inject(function($animate, $rootScope, $rootElement) {
element = jqLite('<div></div>');
@@ -1764,19 +1695,20 @@ describe("animations", function() {
$animate.enter(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(count).toBe(1);
element.remove();
$animate.addClass(element, 'viljami');
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
expect(count).toBe(1);
}));
it('leave: should remove the element even if another animation is called after',
inject(function($animate, $rootScope, $$rAF, $rootElement) {
inject(function($animate, $rootScope, $rootElement) {
var outerContainer = jqLite('<div></div>');
element = jqLite('<div></div>');
@@ -1787,7 +1719,7 @@ describe("animations", function() {
$animate.removeClass(element,'rclass');
$rootScope.$digest();
runner.end();
$$rAF.flush();
$animate.flush();
var isElementRemoved = !outerContainer[0].contains(element[0]);
expect(isElementRemoved).toBe(true);
+109 -22
View File
@@ -3,6 +3,7 @@
describe('$$animation', function() {
beforeEach(module('ngAnimate'));
beforeEach(module('ngAnimateMock'));
var element;
afterEach(function() {
@@ -14,14 +15,14 @@ describe('$$animation', function() {
}));
it("should not run an animation if there are no drivers",
inject(function($$animation, $$rAF, $rootScope) {
inject(function($$animation, $animate, $rootScope) {
element = jqLite('<div></div>');
var done = false;
$$animation(element, 'someEvent').then(function() {
done = true;
});
$$rAF.flush();
$animate.flush();
$rootScope.$digest();
expect(done).toBe(true);
}));
@@ -33,14 +34,14 @@ describe('$$animation', function() {
return false;
});
});
inject(function($$animation, $$rAF, $rootScope) {
inject(function($$animation, $animate, $rootScope) {
element = jqLite('<div></div>');
var done = false;
$$animation(element, 'someEvent').then(function() {
done = true;
});
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
$rootScope.$digest();
expect(done).toBe(true);
});
@@ -195,7 +196,7 @@ describe('$$animation', function() {
});
});
inject(function($$animation, $rootScope, $$rAF) {
inject(function($$animation, $rootScope, $animate) {
var status, element = jqLite('<div></div>');
var runner = $$animation(element, 'enter');
runner.then(function() {
@@ -210,7 +211,7 @@ describe('$$animation', function() {
event === 'resolve' ? runner.end() : runner.cancel();
// the resolve/rejection digest
$$rAF.flush();
$animate.flush();
$rootScope.$digest();
expect(status).toBe(event);
@@ -296,6 +297,90 @@ describe('$$animation', function() {
};
}));
it('should space out multiple ancestorial class-based animations with a RAF in between',
inject(function($rootScope, $$animation, $$rAF) {
var parent = element;
element = jqLite('<div></div>');
parent.append(element);
var child = jqLite('<div></div>');
element.append(child);
$$animation(parent, 'addClass', { addClass: 'blue' });
$$animation(element, 'addClass', { addClass: 'red' });
$$animation(child, 'addClass', { addClass: 'green' });
$rootScope.$digest();
expect(captureLog.length).toBe(1);
expect(capturedAnimation.options.addClass).toBe('blue');
$$rAF.flush();
expect(captureLog.length).toBe(2);
expect(capturedAnimation.options.addClass).toBe('red');
$$rAF.flush();
expect(captureLog.length).toBe(3);
expect(capturedAnimation.options.addClass).toBe('green');
}));
it('should properly cancel out pending animations that are spaced with a RAF request before the digest completes',
inject(function($rootScope, $$animation, $$rAF) {
var parent = element;
element = jqLite('<div></div>');
parent.append(element);
var child = jqLite('<div></div>');
element.append(child);
var r1 = $$animation(parent, 'addClass', { addClass: 'blue' });
var r2 = $$animation(element, 'addClass', { addClass: 'red' });
var r3 = $$animation(child, 'addClass', { addClass: 'green' });
r2.end();
$rootScope.$digest();
expect(captureLog.length).toBe(1);
expect(capturedAnimation.options.addClass).toBe('blue');
$$rAF.flush();
expect(captureLog.length).toBe(2);
expect(capturedAnimation.options.addClass).toBe('green');
}));
it('should properly cancel out pending animations that are spaced with a RAF request after the digest completes',
inject(function($rootScope, $$animation, $$rAF) {
var parent = element;
element = jqLite('<div></div>');
parent.append(element);
var child = jqLite('<div></div>');
element.append(child);
var r1 = $$animation(parent, 'addClass', { addClass: 'blue' });
var r2 = $$animation(element, 'addClass', { addClass: 'red' });
var r3 = $$animation(child, 'addClass', { addClass: 'green' });
$rootScope.$digest();
r2.end();
expect(captureLog.length).toBe(1);
expect(capturedAnimation.options.addClass).toBe('blue');
$$rAF.flush();
expect(captureLog.length).toBe(1);
$$rAF.flush();
expect(captureLog.length).toBe(2);
expect(capturedAnimation.options.addClass).toBe('green');
}));
they('should return a runner that object that contains a $prop() function',
['end', 'cancel', 'then'], function(method) {
inject(function($$animation) {
@@ -306,7 +391,7 @@ describe('$$animation', function() {
they('should close the animation if runner.$prop() is called before the $postDigest phase kicks in',
['end', 'cancel'], function(method) {
inject(function($$animation, $rootScope, $$rAF) {
inject(function($$animation, $rootScope, $animate) {
var status;
var runner = $$animation(element, 'someEvent');
runner.then(function() { status = 'end'; },
@@ -316,7 +401,7 @@ describe('$$animation', function() {
$rootScope.$digest();
expect(runnerLog).toEqual([]);
$$rAF.flush();
$animate.flush();
expect(status).toBe(method);
});
});
@@ -363,11 +448,10 @@ describe('$$animation', function() {
}));
it('should immediately end the animation if the element is removed from the DOM during the animation',
inject(function($$animation, $$rAF, $rootScope) {
inject(function($$animation, $animate, $rootScope) {
var runner = $$animation(element, 'someEvent');
$rootScope.$digest();
$$rAF.flush(); //the animation is "animating"
expect(capturedAnimation).toBeTruthy();
expect(runnerLog).toEqual([]);
@@ -376,14 +460,13 @@ describe('$$animation', function() {
}));
it('should not end the animation when the leave animation removes the element from the DOM',
inject(function($$animation, $$rAF, $rootScope) {
inject(function($$animation, $animate, $rootScope) {
var runner = $$animation(element, 'leave', {}, function() {
element.remove();
});
$rootScope.$digest();
$$rAF.flush(); //the animation is "animating"
expect(runnerLog).toEqual([]);
capturedAnimation.options.domOperation(); //this removes the element
@@ -392,7 +475,7 @@ describe('$$animation', function() {
}));
it('should remove the $destroy event listener when the animation is closed',
inject(function($$animation, $$rAF, $rootScope) {
inject(function($$animation, $rootScope) {
var addListen = spyOn(element, 'on').andCallThrough();
var removeListen = spyOn(element, 'off').andCallThrough();
@@ -408,7 +491,7 @@ describe('$$animation', function() {
}));
it('should always sort parent-element animations to run in order of parent-to-child DOM structure',
inject(function($$animation, $$rAF, $rootScope) {
inject(function($$animation, $rootScope, $animate) {
var child = jqLite('<div></div>');
var grandchild = jqLite('<div></div>');
@@ -424,6 +507,8 @@ describe('$$animation', function() {
$rootScope.$digest();
$animate.flush();
expect(captureLog[0].element).toBe(element);
expect(captureLog[1].element).toBe(child);
expect(captureLog[2].element).toBe(grandchild);
@@ -543,7 +628,7 @@ describe('$$animation', function() {
}));
it("should not group animations into an anchored animation if enter/leave events are NOT used",
inject(function($$animation, $rootScope) {
inject(function($$animation, $rootScope, $$rAF) {
fromElement.addClass('shared-class');
fromElement.attr('ng-animate-ref', '1');
@@ -558,6 +643,7 @@ describe('$$animation', function() {
});
$rootScope.$digest();
$$rAF.flush();
expect(captureLog.length).toBe(2);
}));
@@ -649,7 +735,7 @@ describe('$$animation', function() {
});
it("should not end the animation when the `from` animation calls its own leave dom operation",
inject(function($$animation, $rootScope, $$rAF) {
inject(function($$animation, $rootScope) {
fromElement.addClass('group-1');
var elementRemoved = false;
@@ -679,7 +765,7 @@ describe('$$animation', function() {
}));
it("should not end the animation if any of the anchor elements are removed from the DOM during the animation",
inject(function($$animation, $rootScope, $$rAF) {
inject(function($$animation, $rootScope) {
fromElement.addClass('group-1');
var elementRemoved = false;
@@ -702,7 +788,7 @@ describe('$$animation', function() {
}));
it('should prepare a parent-element animation to run first before the anchored animation',
inject(function($$animation, $$rAF, $rootScope, $rootElement) {
inject(function($$animation, $rootScope, $rootElement, $animate) {
fromAnchors[0].attr('ng-animate-ref', 'shared');
toAnchors[0].attr('ng-animate-ref', 'shared');
@@ -725,6 +811,7 @@ describe('$$animation', function() {
expect(captureLog.length).toBe(0);
$rootScope.$digest();
$animate.flush();
expect(captureLog[0].element).toBe(parent);
expect(captureLog[1].from.element).toBe(fromElement);
@@ -869,7 +956,7 @@ describe('$$animation', function() {
};
});
});
inject(function($$animation, $rootScope, $$rAF) {
inject(function($$animation, $rootScope, $animate) {
element.addClass('four');
var completed = false;
@@ -882,7 +969,7 @@ describe('$$animation', function() {
$rootScope.$digest(); //runs the animation
$rootScope.$digest(); //flushes the step code
$$rAF.flush(); //runs the $$animation promise
$animate.flush();
$rootScope.$digest(); //the runner promise
expect(completed).toBe(true);
@@ -921,7 +1008,7 @@ describe('$$animation', function() {
};
});
});
inject(function($$animation, $rootScope, $$rAF) {
inject(function($$animation, $rootScope, $animate) {
element.addClass('four');
var completed = false;
@@ -937,7 +1024,7 @@ describe('$$animation', function() {
$rootScope.$digest(); //flushes the step code
runner.end();
$$rAF.flush(); //runs the $$animation promise
$animate.flush();
$rootScope.$digest(); //the runner promise
expect(completed).toBe(true);
+77 -89
View File
@@ -3,6 +3,7 @@
describe('ngAnimate integration tests', function() {
beforeEach(module('ngAnimate'));
beforeEach(module('ngAnimateMock'));
var element, html, ss;
beforeEach(module(function() {
@@ -30,7 +31,7 @@ describe('ngAnimate integration tests', function() {
they('should render an $prop animation',
['enter', 'leave', 'move', 'addClass', 'removeClass', 'setClass'], function(event) {
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF) {
inject(function($animate, $compile, $rootScope, $rootElement) {
element = jqLite('<div class="animate-me"></div>');
$compile(element)($rootScope);
@@ -97,10 +98,11 @@ describe('ngAnimate integration tests', function() {
});
expect(element).toHaveClass(setupClass);
$$rAF.flush();
$animate.flush();
expect(element).toHaveClass(activeClass);
browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2 });
$animate.flush();
expect(element).not.toHaveClass(setupClass);
expect(element).not.toHaveClass(activeClass);
@@ -112,7 +114,7 @@ describe('ngAnimate integration tests', function() {
});
it('should not throw an error if the element is orphaned before the CSS animation starts',
inject(function($rootScope, $rootElement, $animate, $$rAF) {
inject(function($rootScope, $rootElement, $animate) {
ss.addRule('.animate-me', 'transition:2s linear all;');
@@ -127,51 +129,19 @@ describe('ngAnimate integration tests', function() {
$rootScope.$digest();
// this will run the first class-based animation
$$rAF.flush();
$animate.flush();
element.remove();
expect(function() {
$$rAF.flush();
$animate.flush();
}).not.toThrow();
dealoc(element);
}));
it('should always synchronously add css classes in order for child animations to animate properly',
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF, $document) {
ss.addRule('.animations-enabled .animate-me.ng-enter', 'transition:2s linear all;');
element = jqLite('<div ng-class="{\'animations-enabled\':exp}"></div>');
var child = jqLite('<div ng-if="exp" class="animate-me"></div>');
element.append(child);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$compile(element)($rootScope);
$rootScope.exp = true;
$rootScope.$digest();
child = element.find('div');
expect(element).toHaveClass('animations-enabled');
expect(child).toHaveClass('ng-enter');
$$rAF.flush();
expect(child).toHaveClass('ng-enter-active');
browserTrigger(child, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2 });
expect(child).not.toHaveClass('ng-enter-active');
expect(child).not.toHaveClass('ng-enter');
}));
it('should synchronously add/remove ng-class expressions in time for other animations to run on the same element',
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF, $document) {
it('should include the added/removed classes in lieu of the enter animation',
inject(function($animate, $compile, $rootScope, $rootElement, $document) {
ss.addRule('.animate-me.ng-enter.on', 'transition:2s linear all;');
@@ -184,7 +154,7 @@ describe('ngAnimate integration tests', function() {
$rootScope.exp = true;
$rootScope.$digest();
$$rAF.flush();
$animate.flush();
var child = element.find('div');
@@ -203,18 +173,19 @@ describe('ngAnimate integration tests', function() {
expect(child).toHaveClass('on');
expect(child).toHaveClass('ng-enter');
$$rAF.flush();
$animate.flush();
expect(child).toHaveClass('ng-enter-active');
browserTrigger(child, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2 });
$animate.flush();
expect(child).not.toHaveClass('ng-enter-active');
expect(child).not.toHaveClass('ng-enter');
}));
it('should animate ng-class and a structural animation in parallel on the same element',
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF, $document) {
inject(function($animate, $compile, $rootScope, $rootElement, $document) {
ss.addRule('.animate-me.ng-enter', 'transition:2s linear all;');
ss.addRule('.animate-me.expand', 'transition:5s linear all; font-size:200px;');
@@ -236,12 +207,13 @@ describe('ngAnimate integration tests', function() {
expect(child).toHaveClass('expand-add');
expect(child).toHaveClass('expand');
$$rAF.flush();
$animate.flush();
expect(child).toHaveClass('ng-enter-active');
expect(child).toHaveClass('expand-add-active');
browserTrigger(child, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2 });
$animate.flush();
expect(child).not.toHaveClass('ng-enter-active');
expect(child).not.toHaveClass('ng-enter');
@@ -249,9 +221,9 @@ describe('ngAnimate integration tests', function() {
expect(child).not.toHaveClass('expand-add');
}));
it('should issue a reflow for each element animation on all DOM levels', function() {
it('should issue a RAF for each element animation on all DOM levels', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF, $document) {
inject(function($animate, $compile, $rootScope, $rootElement, $document, $$rAF) {
element = jqLite(
'<div ng-class="{parent:exp}">' +
'<div ng-class="{parent2:exp}">' +
@@ -267,70 +239,84 @@ describe('ngAnimate integration tests', function() {
$compile(element)($rootScope);
$rootScope.$digest();
expect($animate.reflows).toBe(0);
var outer = element;
var inner = element.find('div');
$rootScope.exp = true;
$rootScope.items = [1,2,3,4,5,6,7,8,9,10];
$rootScope.$digest();
expect(outer).not.toHaveClass('parent');
expect(inner).not.toHaveClass('parent2');
// 2 parents + 10 items = 12
expect($animate.reflows).toBe(12);
assertTotalRepeats(0);
$$rAF.flush();
expect(outer).toHaveClass('parent');
assertTotalRepeats(0);
$$rAF.flush();
expect(inner).toHaveClass('parent2');
assertTotalRepeats(10);
function assertTotalRepeats(total) {
expect(inner[0].querySelectorAll('div.ng-enter').length).toBe(total);
}
});
});
it('should issue a reflow for each element and also its children', function() {
it('should pack level elements into their own RAF flush', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF, $document) {
inject(function($animate, $compile, $rootScope, $rootElement, $document) {
ss.addRule('.inner', 'transition:2s linear all;');
element = jqLite(
'<div ng-class="{one:exp}">' +
'<div ng-if="exp"></div>' +
'</div>' +
'<div ng-class="{two:exp}">' +
'<div ng-if="exp"></div>' +
'</div>' +
'<div ng-class="{three:exp}">' +
'<div ng-if="false"></div>' +
'</div>' +
'<div ng-class="{four:exp}"></div>'
);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$compile(element)($rootScope);
$rootScope.$digest();
expect($animate.reflows).toBe(0);
$rootScope.exp = true;
$rootScope.$digest();
// there is one element's expression in there that is false
expect($animate.reflows).toBe(6);
});
});
it('should always issue atleast one reflow incase there are no parent class-based animations', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF, $document) {
element = jqLite(
'<div ng-repeat="item in items" ng-class="{someAnimation:exp}">' +
'{{ item }}' +
'<div>' +
'<div class="outer" ng-class="{on:exp}">' +
'<div class="inner" ng-if="exp"></div>' +
'</div>' +
'<div class="outer" ng-class="{on:exp}">' +
'<div class="inner" ng-if="exp"></div>' +
'</div>' +
'<div class="outer" ng-class="{on:exp}">' +
'<div class="inner" ng-if="exp"></div>' +
'</div>' +
'<div class="outer" ng-class="{on:exp}"></div>' +
'</div>'
);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$compile(element)($rootScope);
$rootScope.$digest();
expect($animate.reflows).toBe(0);
assertGroupHasClass(query('outer'), 'on', true);
expect(query('inner').length).toBe(0);
$rootScope.exp = true;
$rootScope.items = [1,2,3,4,5,6,7,8,9,10];
$rootScope.$digest();
expect($animate.reflows).toBe(10);
assertGroupHasClass(query('outer'), 'on', true);
assertGroupHasClass(query('inner'), 'ng-enter', true);
$animate.flush();
assertGroupHasClass(query('outer'), 'on');
assertGroupHasClass(query('inner'), 'ng-enter');
function query(className) {
return element[0].querySelectorAll('.' + className);
}
function assertGroupHasClass(elms, className, not) {
for (var i = 0; i < elms.length; i++) {
var assert = expect(jqLite(elms[i]));
(not ? assert.not : assert).toHaveClass(className);
}
}
});
});
});
@@ -355,7 +341,7 @@ describe('ngAnimate integration tests', function() {
});
});
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF) {
inject(function($animate, $compile, $rootScope, $rootElement) {
element = jqLite('<div class="animate-me"></div>');
$compile(element)($rootScope);
@@ -407,7 +393,7 @@ describe('ngAnimate integration tests', function() {
expect(isFunction(endAnimation)).toBe(true);
endAnimation();
$$rAF.flush();
$animate.flush();
expect(animateCompleteCallbackFired).toBe(true);
$rootScope.$digest();
@@ -465,6 +451,7 @@ describe('ngAnimate integration tests', function() {
}
$rootScope.$digest();
$animate.flush();
expect(endParentAnimationFn).toBeTruthy();
@@ -529,6 +516,7 @@ describe('ngAnimate integration tests', function() {
}
$rootScope.$digest();
$animate.flush();
expect(endParentAnimationFn).toBeTruthy();
+143
View File
@@ -0,0 +1,143 @@
'use strict';
describe("$$rAFScheduler", function() {
beforeEach(module('ngAnimate'));
it('should accept an array of tasks and run the first task immediately',
inject(function($$rAFScheduler) {
var taskSpy = jasmine.createSpy();
var tasks = [taskSpy];
$$rAFScheduler([tasks]);
expect(taskSpy).toHaveBeenCalled();
}));
it('should run tasks based on how many RAFs have run in comparison to the task index',
inject(function($$rAFScheduler, $$rAF) {
var i, tasks = [];
for (i = 0; i < 5; i++) {
tasks.push([jasmine.createSpy()]);
}
$$rAFScheduler(tasks);
for (i = 1; i < 5; i++) {
var taskSpy = tasks[i][0];
expect(taskSpy).not.toHaveBeenCalled();
$$rAF.flush();
expect(taskSpy).toHaveBeenCalled();
}
}));
it('should space out subarrays by a RAF and run the internals in parallel',
inject(function($$rAFScheduler, $$rAF) {
var spies = {
a: jasmine.createSpy(),
b: jasmine.createSpy(),
c: jasmine.createSpy(),
x: jasmine.createSpy(),
y: jasmine.createSpy(),
z: jasmine.createSpy()
};
var items = [[spies.a, spies.x],
[spies.b, spies.y],
[spies.c, spies.z]];
expect(spies.a).not.toHaveBeenCalled();
expect(spies.x).not.toHaveBeenCalled();
$$rAFScheduler(items);
expect(spies.a).toHaveBeenCalled();
expect(spies.x).toHaveBeenCalled();
expect(spies.b).not.toHaveBeenCalled();
expect(spies.y).not.toHaveBeenCalled();
$$rAF.flush();
expect(spies.b).toHaveBeenCalled();
expect(spies.y).toHaveBeenCalled();
expect(spies.c).not.toHaveBeenCalled();
expect(spies.z).not.toHaveBeenCalled();
$$rAF.flush();
expect(spies.c).toHaveBeenCalled();
expect(spies.z).toHaveBeenCalled();
}));
describe('.waitUntilQuiet', function() {
it('should run the `last` provided function when a RAF fully passes',
inject(function($$rAFScheduler, $$rAF) {
var q1 = jasmine.createSpy();
$$rAFScheduler.waitUntilQuiet(q1);
expect(q1).not.toHaveBeenCalled();
var q2 = jasmine.createSpy();
$$rAFScheduler.waitUntilQuiet(q2);
expect(q1).not.toHaveBeenCalled();
expect(q2).not.toHaveBeenCalled();
var q3 = jasmine.createSpy();
$$rAFScheduler.waitUntilQuiet(q3);
expect(q1).not.toHaveBeenCalled();
expect(q2).not.toHaveBeenCalled();
expect(q3).not.toHaveBeenCalled();
$$rAF.flush();
expect(q1).not.toHaveBeenCalled();
expect(q2).not.toHaveBeenCalled();
expect(q3).toHaveBeenCalled();
}));
it('should always execute itself before the next RAF task tick occurs', function() {
module(provideLog);
inject(function($$rAFScheduler, $$rAF, log) {
var quietFn = log.fn('quiet');
var tasks = [
[log.fn('task1')],
[log.fn('task2')],
[log.fn('task3')],
[log.fn('task4')]
];
$$rAFScheduler(tasks);
expect(log).toEqual(['task1']);
$$rAFScheduler.waitUntilQuiet(quietFn);
expect(log).toEqual(['task1']);
$$rAF.flush();
expect(log).toEqual(['task1', 'quiet', 'task2']);
$$rAF.flush();
expect(log).toEqual(['task1', 'quiet', 'task2', 'task3']);
$$rAFScheduler.waitUntilQuiet(quietFn);
$$rAF.flush();
expect(log).toEqual(['task1', 'quiet', 'task2', 'task3', 'quiet', 'task4']);
});
});
});
});
+26 -21
View File
@@ -616,10 +616,35 @@ describe('$aria', function() {
describe('tabindex', function() {
beforeEach(injectScopeAndCompiler);
it('should attach tabindex to role="checkbox", ng-click, and ng-dblclick', function() {
it('should not attach to native controls', function() {
var element = [
$compile("<button ng-click='something'></button>")(scope),
$compile("<a ng-href='#/something'>")(scope),
$compile("<input ng-model='val'>")(scope),
$compile("<textarea ng-model='val'></textarea>")(scope),
$compile("<select ng-model='val'></select>")(scope),
$compile("<details ng-model='val'></details>")(scope)
];
expectAriaAttrOnEachElement(element, 'tabindex', undefined);
});
it('should not attach to random ng-model elements', function() {
compileElement('<div ng-model="val"></div>');
expect(element.attr('tabindex')).toBeUndefined();
});
it('should attach tabindex to custom inputs', function() {
compileElement('<div type="checkbox" ng-model="val"></div>');
expect(element.attr('tabindex')).toBe('0');
compileElement('<div role="checkbox" ng-model="val"></div>');
expect(element.attr('tabindex')).toBe('0');
compileElement('<div type="range" ng-model="val"></div>');
expect(element.attr('tabindex')).toBe('0');
});
it('should attach to ng-click and ng-dblclick', function() {
compileElement('<div ng-click="someAction()"></div>');
expect(element.attr('tabindex')).toBe('0');
@@ -640,26 +665,6 @@ describe('$aria', function() {
compileElement('<div ng-dblclick="someAction()" tabindex="userSetValue"></div>');
expect(element.attr('tabindex')).toBe('userSetValue');
});
it('should set proper tabindex values for radiogroup', function() {
compileElement('<div role="radiogroup">' +
'<div role="radio" ng-model="val" value="one">1</div>' +
'<div role="radio" ng-model="val" value="two">2</div>' +
'</div>');
var one = element.contents().eq(0);
var two = element.contents().eq(1);
scope.$apply("val = 'one'");
expect(one.attr('tabindex')).toBe('0');
expect(two.attr('tabindex')).toBe('-1');
scope.$apply("val = 'two'");
expect(one.attr('tabindex')).toBe('-1');
expect(two.attr('tabindex')).toBe('0');
dealoc(element);
});
});
describe('accessible actions', function() {
+2 -2
View File
@@ -138,8 +138,8 @@ describe('cookie options', function() {
.reduce(function(prev, value) {
var pair = value.split('=', 2);
if (pair[0] === key) {
if (prev === undefined) {
return pair[1] === undefined ? true : pair[1];
if (isUndefined(prev)) {
return isUndefined(pair[1]) ? true : pair[1];
} else {
throw 'duplicate key in cookie string';
}
+181
View File
@@ -1828,6 +1828,187 @@ describe('ngMockE2E', function() {
}));
});
});
describe('ngAnimateMock', function() {
beforeEach(module('ngAnimate'));
beforeEach(module('ngAnimateMock'));
var ss, element, trackedAnimations;
afterEach(function() {
if (element) {
element.remove();
}
if (ss) {
ss.destroy();
}
});
beforeEach(module(function($animateProvider) {
trackedAnimations = [];
$animateProvider.register('.animate', function() {
return {
leave: logFn('leave'),
addClass: logFn('addClass')
};
function logFn(method) {
return function(element) {
trackedAnimations.push(getDoneCallback(arguments));
};
}
function getDoneCallback(args) {
for (var i = args.length; i > 0; i--) {
if (angular.isFunction(args[i])) return args[i];
}
}
});
return function($animate, $rootElement, $document, $rootScope, $window) {
ss = createMockStyleSheet($document, $window);
element = angular.element('<div class="animate"></div>');
$rootElement.append(element);
angular.element($document[0].body).append($rootElement);
$animate.enabled(true);
$rootScope.$digest();
};
}));
describe('$animate.queue', function() {
it('should maintain a queue of the executed animations', inject(function($animate) {
element.removeClass('animate'); // we don't care to test any actual animations
var options = {};
$animate.addClass(element, 'on', options);
var first = $animate.queue[0];
expect(first.element).toBe(element);
expect(first.event).toBe('addClass');
expect(first.options).toBe(options);
$animate.removeClass(element, 'off', options);
var second = $animate.queue[1];
expect(second.element).toBe(element);
expect(second.event).toBe('removeClass');
expect(second.options).toBe(options);
$animate.leave(element, options);
var third = $animate.queue[2];
expect(third.element).toBe(element);
expect(third.event).toBe('leave');
expect(third.options).toBe(options);
}));
});
describe('$animate.flush()', function() {
it('should throw an error if there is nothing to animate', inject(function($animate) {
expect(function() {
$animate.flush();
}).toThrow('No pending animations ready to be closed or flushed');
}));
it('should trigger the animation to start',
inject(function($animate) {
expect(trackedAnimations.length).toBe(0);
$animate.leave(element);
$animate.flush();
expect(trackedAnimations.length).toBe(1);
}));
it('should trigger the animation to end once run and called',
inject(function($animate) {
$animate.leave(element);
$animate.flush();
expect(element.parent().length).toBe(1);
trackedAnimations[0]();
$animate.flush();
expect(element.parent().length).toBe(0);
}));
it('should trigger the animation promise callback to fire once run and closed',
inject(function($animate) {
var doneSpy = jasmine.createSpy();
$animate.leave(element).then(doneSpy);
$animate.flush();
trackedAnimations[0]();
expect(doneSpy).not.toHaveBeenCalled();
$animate.flush();
expect(doneSpy).toHaveBeenCalled();
}));
it('should trigger a series of CSS animations to trigger and start once run',
inject(function($animate, $rootScope) {
if (!browserSupportsCssAnimations()) return;
ss.addRule('.leave-me.ng-leave', 'transition:1s linear all;');
var i, elm, elms = [];
for (i = 0; i < 5; i++) {
elm = angular.element('<div class="leave-me"></div>');
element.append(elm);
elms.push(elm);
$animate.leave(elm);
}
$rootScope.$digest();
for (i = 0; i < 5; i++) {
elm = elms[i];
expect(elm.hasClass('ng-leave')).toBe(true);
expect(elm.hasClass('ng-leave-active')).toBe(false);
}
$animate.flush();
for (i = 0; i < 5; i++) {
elm = elms[i];
expect(elm.hasClass('ng-leave')).toBe(true);
expect(elm.hasClass('ng-leave-active')).toBe(true);
}
}));
it('should trigger parent and child animations to run within the same flush',
inject(function($animate, $rootScope) {
var child = angular.element('<div class="animate child"></div>');
element.append(child);
expect(trackedAnimations.length).toBe(0);
$animate.addClass(element, 'go');
$animate.addClass(child, 'start');
$animate.flush();
expect(trackedAnimations.length).toBe(2);
}));
it('should trigger animation callbacks when called',
inject(function($animate, $rootScope) {
var spy = jasmine.createSpy();
$animate.on('addClass', element, spy);
$animate.addClass(element, 'on');
expect(spy).not.toHaveBeenCalled();
$animate.flush();
expect(spy.callCount).toBe(1);
trackedAnimations[0]();
$animate.flush();
expect(spy.callCount).toBe(2);
}));
});
});
});
describe('make sure that we can create an injector outside of tests', function() {
+8
View File
@@ -297,6 +297,14 @@ describe("resource", function() {
R.get({a: 'foo'});
});
it('should support IPv6 URLs', function() {
var R = $resource('http://[2620:0:861:ed1a::1]/:ed1a/', {}, {}, {stripTrailingSlashes: false});
$httpBackend.expect('GET', 'http://[2620:0:861:ed1a::1]/foo/').respond({});
$httpBackend.expect('GET', 'http://[2620:0:861:ed1a::1]/').respond({});
R.get({ed1a: 'foo'});
R.get({});
});
it('should support overriding provider default trailing-slash stripping configuration', function() {
// Set the new behavior for all new resources created by overriding the
// provider configuration

Some files were not shown because too many files have changed in this diff Show More