Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d7422da7d7 | |||
| c7cbc978c6 | |||
| 27146e8a7f | |||
| 5e418b1145 | |||
| f4bb973eb7 | |||
| 848857aa5b | |||
| ee8a05d3f1 | |||
| 275ebbf0ec | |||
| 0f23df4c06 | |||
| 11f700f7bd | |||
| 5785f2a991 | |||
| 2546c29f81 | |||
| 19ea708c9d | |||
| 5cf05d67f2 | |||
| 0377c6f0e8 | |||
| 9c13866824 | |||
| 419a4813e3 | |||
| 131af8272d | |||
| c219a46f59 | |||
| 25f008f541 | |||
| 4a593db79b | |||
| ad4fef0431 | |||
| 8a15fcc1f5 | |||
| f01212ab52 | |||
| 28693a1a67 | |||
| 29fd499552 | |||
| 2f97d9d647 | |||
| 4146b38459 | |||
| 05aab660ce | |||
| 5ecb64849e | |||
| 59dfe1b5a0 | |||
| 50ebfb735c | |||
| 0bdbfe5069 | |||
| 3fc4d6028c | |||
| 2eb12a052b | |||
| bd63b2235c | |||
| f418ffd083 | |||
| 6ab5f8ce4b | |||
| becfeb5aa3 | |||
| eb968c4a68 | |||
| 7f2af3f923 | |||
| cce98ff53a | |||
| b607618342 | |||
| fa50fbaf57 | |||
| f135e2dc05 | |||
| 780351db5e | |||
| a50bb0bfec | |||
| 4d86df6f48 | |||
| bb464d16b4 | |||
| 85b2eb1472 | |||
| 7608f92c6a | |||
| c0bf8db63c | |||
| c95a6737fb | |||
| cd43d24402 | |||
| 086c5d0354 |
+143
-32
@@ -1,34 +1,57 @@
|
||||
<a name="1.5.11"></a>
|
||||
# 1.5.11 princely-quest (2017-01-13)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
- **$compile:** allow the usage of "$" in isolate scope property alias
|
||||
([e75fbc](https://github.com/angular/angular.js/commit/e75fbc494e6a0da6a9231b40bb0382431b62be07),
|
||||
[#15586](https://github.com/angular/angular.js/issues/15586),
|
||||
[#15594](https://github.com/angular/angular.js/issues/15594))
|
||||
- **angularInit:** allow auto-bootstraping from inline script
|
||||
([41aa91](https://github.com/angular/angular.js/commit/41aa9125b9aaf771addb250642f524a4e6f9d8d3),
|
||||
[#15567](https://github.com/angular/angular.js/issues/15567),
|
||||
[#15571](https://github.com/angular/angular.js/issues/15571))
|
||||
- **$resource:** delete `$cancelRequest()` in `toJSON()`
|
||||
([4f3858](https://github.com/angular/angular.js/commit/4f3858e7c371f87534397f45b9d002add33b00cc),
|
||||
[#15244](https://github.com/angular/angular.js/issues/15244))
|
||||
- **$$cookieReader:** correctly handle forbidden access to `document.cookie`
|
||||
([6933cf](https://github.com/angular/angular.js/commit/6933cf64fe51f54b10d1639f2b95bab3c1178df9),
|
||||
[#15523](https://github.com/angular/angular.js/issues/15523),
|
||||
[#15532](https://github.com/angular/angular.js/issues/15532))
|
||||
|
||||
|
||||
|
||||
<a name="1.6.1"></a>
|
||||
# 1.6.1 promise-rectification (2016-12-23)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
- **$q:** Add traceback to unhandled promise rejections
|
||||
([174cb4](https://github.com/angular/angular.js/commit/174cb4a8c81e25581da5b452c2bb43b0fa377a9b)
|
||||
([174cb4](https://github.com/angular/angular.js/commit/174cb4a8c81e25581da5b452c2bb43b0fa377a9b),
|
||||
[#14631](https://github.com/angular/angular.js/issues/14631))
|
||||
- **$$cookieReader:** correctly handle forbidden access to `document.cookie`
|
||||
([33f769](https://github.com/angular/angular.js/commit/33f769b0a1214055c16fb59adad4897bf53d62bf)
|
||||
([33f769](https://github.com/angular/angular.js/commit/33f769b0a1214055c16fb59adad4897bf53d62bf),
|
||||
[#15523](https://github.com/angular/angular.js/issues/15523))
|
||||
- **ngOptions:** do not unset the `selected` property unless necessary
|
||||
([bc4844](https://github.com/angular/angular.js/commit/bc4844d3b297d80aecef89aa1b32615024decedc)
|
||||
([bc4844](https://github.com/angular/angular.js/commit/bc4844d3b297d80aecef89aa1b32615024decedc),
|
||||
[#15477](https://github.com/angular/angular.js/issues/15477))
|
||||
- **ngModelOptions:** work correctly when on the template of `replace` directives
|
||||
([5f8ed6](https://github.com/angular/angular.js/commit/5f8ed63f2ab02ffb9c21bf9c29d27c851d162e26)
|
||||
([5f8ed6](https://github.com/angular/angular.js/commit/5f8ed63f2ab02ffb9c21bf9c29d27c851d162e26),
|
||||
[#15492](https://github.com/angular/angular.js/issues/15492))
|
||||
- **ngClassOdd/Even:** add/remove the correct classes when expression/`$index` change simultaneously
|
||||
([d52864](https://github.com/angular/angular.js/commit/d528644fe3e9ffd43999e7fc67806059f9e1083e))
|
||||
- **jqLite:** silently ignore `after()` if element has no parent
|
||||
([3d68b9](https://github.com/angular/angular.js/commit/3d68b9502848ff6714ef89bfb95b8e70ae34eff6)
|
||||
([3d68b9](https://github.com/angular/angular.js/commit/3d68b9502848ff6714ef89bfb95b8e70ae34eff6),
|
||||
[#15331](https://github.com/angular/angular.js/issues/15331),
|
||||
[#15475](https://github.com/angular/angular.js/issues/15475))
|
||||
- **$rootScope:** when adding/removing watchers during $digest
|
||||
([163aca](https://github.com/angular/angular.js/commit/163aca336d7586a45255787af41b14b2a12361dd)
|
||||
([163aca](https://github.com/angular/angular.js/commit/163aca336d7586a45255787af41b14b2a12361dd),
|
||||
[#15422](https://github.com/angular/angular.js/issues/15422))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
- **ngClass:** avoid unnecessary `.data()` accesses, deep-watching and copies
|
||||
([1d3b65](https://github.com/angular/angular.js/commit/1d3b65adc2c22ff662159ef910089cf10d1edb7b)
|
||||
([1d3b65](https://github.com/angular/angular.js/commit/1d3b65adc2c22ff662159ef910089cf10d1edb7b),
|
||||
[#14404](https://github.com/angular/angular.js/issues/14404))
|
||||
|
||||
|
||||
@@ -40,46 +63,46 @@
|
||||
## Bug Fixes
|
||||
- **$compile:**
|
||||
- don't throw tplrt error when there is whitespace around a top-level comment
|
||||
([12752f](https://github.com/angular/angular.js/commit/12752f66ac425ab38a5ee574a4bfbf3516adc42c)
|
||||
([12752f](https://github.com/angular/angular.js/commit/12752f66ac425ab38a5ee574a4bfbf3516adc42c),
|
||||
[#15108](https://github.com/angular/angular.js/issues/15108))
|
||||
- clean up `@`-binding observers when re-assigning bindings
|
||||
([f3cb6e](https://github.com/angular/angular.js/commit/f3cb6e309aa1f676e5951ac745fa886d3581c2f4)
|
||||
([f3cb6e](https://github.com/angular/angular.js/commit/f3cb6e309aa1f676e5951ac745fa886d3581c2f4),
|
||||
[#15268](https://github.com/angular/angular.js/issues/15268))
|
||||
- set attribute value even if `ngAttr*` contains no interpolation
|
||||
([229799](https://github.com/angular/angular.js/commit/22979904fb754c59e9f6ee5d8763e3b8de0e18c2)
|
||||
([229799](https://github.com/angular/angular.js/commit/22979904fb754c59e9f6ee5d8763e3b8de0e18c2),
|
||||
[#15133](https://github.com/angular/angular.js/issues/15133))
|
||||
- `bindToController` should work without `controllerAs`
|
||||
([944989](https://github.com/angular/angular.js/commit/9449893763a4fd95ee8ff78b53c6966a874ec9ae)
|
||||
([944989](https://github.com/angular/angular.js/commit/9449893763a4fd95ee8ff78b53c6966a874ec9ae),
|
||||
[#15088](https://github.com/angular/angular.js/issues/15088))
|
||||
- do not overwrite values set in `$onInit()` for `<`-bound literals
|
||||
([07e1ba](https://github.com/angular/angular.js/commit/07e1ba365fb5e8a049be732bd7b62f71e0aa1672)
|
||||
([07e1ba](https://github.com/angular/angular.js/commit/07e1ba365fb5e8a049be732bd7b62f71e0aa1672),
|
||||
[#15118](https://github.com/angular/angular.js/issues/15118))
|
||||
- avoid calling `$onChanges()` twice for `NaN` initial values
|
||||
([0cf5be](https://github.com/angular/angular.js/commit/0cf5be52642f7e9d81a708b3005042eac6492572))
|
||||
- **$location:** prevent infinite digest with IDN urls in Edge
|
||||
([4bf892](https://github.com/angular/angular.js/commit/4bf89218130d434771089fdfe643490b8d2ee259)
|
||||
([4bf892](https://github.com/angular/angular.js/commit/4bf89218130d434771089fdfe643490b8d2ee259),
|
||||
[#15217](https://github.com/angular/angular.js/issues/15217))
|
||||
- **$rootScope:** correctly handle adding/removing watchers during `$digest`
|
||||
([a9708d](https://github.com/angular/angular.js/commit/a9708de84b50f06eacda33834d5bbdfc97c97f37)
|
||||
([a9708d](https://github.com/angular/angular.js/commit/a9708de84b50f06eacda33834d5bbdfc97c97f37),
|
||||
[#15422](https://github.com/angular/angular.js/issues/15422))
|
||||
- **$sce:** fix `adjustMatcher` to replace multiple `*` and `**`
|
||||
([78eecb](https://github.com/angular/angular.js/commit/78eecb43dbb0500358d333aea8955bd0646a7790))
|
||||
- **jqLite:** silently ignore `after()` if element has no parent
|
||||
([77ed85](https://github.com/angular/angular.js/commit/77ed85bcd3be057a5a79231565ac7accc6d644c6)
|
||||
([77ed85](https://github.com/angular/angular.js/commit/77ed85bcd3be057a5a79231565ac7accc6d644c6),
|
||||
[#15331](https://github.com/angular/angular.js/issues/15331))
|
||||
- **input[radio]:** use non-strict comparison for checkedness
|
||||
([593a50](https://github.com/angular/angular.js/commit/593a5034841b3b7661d3bcbdd06b7a9d0876fd34))
|
||||
- **select, ngOptions:**
|
||||
- let `ngValue` take precedence over option text with multiple interpolations
|
||||
([5b7ec8](https://github.com/angular/angular.js/commit/5b7ec8c84e88ee08aacaf9404853eda0016093f5)
|
||||
([5b7ec8](https://github.com/angular/angular.js/commit/5b7ec8c84e88ee08aacaf9404853eda0016093f5),
|
||||
[#15413](https://github.com/angular/angular.js/issues/15413))
|
||||
- don't add comment nodes as empty options
|
||||
([1d29c9](https://github.com/angular/angular.js/commit/1d29c91c3429de96e4103533752700d1266741be)
|
||||
([1d29c9](https://github.com/angular/angular.js/commit/1d29c91c3429de96e4103533752700d1266741be),
|
||||
[#15454](https://github.com/angular/angular.js/issues/15454))
|
||||
- **ngClassOdd/Even:** add/remove the correct classes when expression/`$index` change simultaneously
|
||||
([e3d020](https://github.com/angular/angular.js/commit/e3d02070ab8a02c818dcc5114db6fba9d3f385d6))
|
||||
- **$sanitize:** reduce stack height in IE <= 11
|
||||
([862dc2](https://github.com/angular/angular.js/commit/862dc2532f8126a4a71fd3d957884ba6f11f591c)
|
||||
([862dc2](https://github.com/angular/angular.js/commit/862dc2532f8126a4a71fd3d957884ba6f11f591c),
|
||||
[#14928](https://github.com/angular/angular.js/issues/14928))
|
||||
- **ngMock/$controller:** respect `$compileProvider.preAssignBindingsEnabled()`
|
||||
([75c83f](https://github.com/angular/angular.js/commit/75c83ff3195931859a099f7a95bf81d32abf2eb3))
|
||||
@@ -87,35 +110,35 @@
|
||||
|
||||
## New Features
|
||||
- **bootstrap:** do not bootstrap from unknown schemes with a different origin
|
||||
([bdeb33](https://github.com/angular/angular.js/commit/bdeb3392a8719131ab2b993f2a881c43a2860f92)
|
||||
([bdeb33](https://github.com/angular/angular.js/commit/bdeb3392a8719131ab2b993f2a881c43a2860f92),
|
||||
[#15428](https://github.com/angular/angular.js/issues/15428))
|
||||
- **$anchorScroll:** convert numeric hash targets to string
|
||||
([a52640](https://github.com/angular/angular.js/commit/a5264090b66ad0cf9a93de84bb7b307868c0edef)
|
||||
([a52640](https://github.com/angular/angular.js/commit/a5264090b66ad0cf9a93de84bb7b307868c0edef),
|
||||
[#14680](https://github.com/angular/angular.js/issues/14680))
|
||||
- **$compile:**
|
||||
- add `preAssignBindingsEnabled` option
|
||||
([f86576](https://github.com/angular/angular.js/commit/f86576def44005f180a66e3aa12d6cc73c1ac72c))
|
||||
- throw error when directive name or factory function is invalid
|
||||
([5c9399](https://github.com/angular/angular.js/commit/5c9399d18ae5cd79e6cf6fc4377d66df00f6fcc7)
|
||||
([5c9399](https://github.com/angular/angular.js/commit/5c9399d18ae5cd79e6cf6fc4377d66df00f6fcc7),
|
||||
[#15056](https://github.com/angular/angular.js/issues/15056))
|
||||
- **$controller:** throw when requested controller is not registered
|
||||
([9ae793](https://github.com/angular/angular.js/commit/9ae793d8a69afe84370b601e07fc375fc18a576a)
|
||||
([9ae793](https://github.com/angular/angular.js/commit/9ae793d8a69afe84370b601e07fc375fc18a576a),
|
||||
[#14980](https://github.com/angular/angular.js/issues/14980))
|
||||
- **$location:** add support for selectively rewriting links based on attribute
|
||||
([a4a222](https://github.com/angular/angular.js/commit/a4a22266f127d3b9a6818e6f4754f048e253f693))
|
||||
- **$resource:** pass `status`/`statusText` to success callbacks
|
||||
([a8da25](https://github.com/angular/angular.js/commit/a8da25c74d2c1f6265f0fafd95bf72c981d9d678)
|
||||
[#8341](https://github.com/angular/angular.js/issues/8841)
|
||||
([a8da25](https://github.com/angular/angular.js/commit/a8da25c74d2c1f6265f0fafd95bf72c981d9d678),
|
||||
[#8341](https://github.com/angular/angular.js/issues/8841),
|
||||
[#8841](https://github.com/angular/angular.js/issues/8841))
|
||||
- **ngSwitch:** allow multiple case matches via optional attribute `ngSwitchWhenSeparator`
|
||||
([0e1651](https://github.com/angular/angular.js/commit/0e1651bfd28ba73ebd0e4943d85af48c4506e02c)
|
||||
[#3410](https://github.com/angular/angular.js/issues/3410)
|
||||
([0e1651](https://github.com/angular/angular.js/commit/0e1651bfd28ba73ebd0e4943d85af48c4506e02c),
|
||||
[#3410](https://github.com/angular/angular.js/issues/3410),
|
||||
[#3516](https://github.com/angular/angular.js/issues/3516))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
- **all:** don't trigger digests after enter/leave of structural directives
|
||||
([c57779](https://github.com/angular/angular.js/commit/c57779d8725493c5853dceda0105dafd5c0e3a7c)
|
||||
([c57779](https://github.com/angular/angular.js/commit/c57779d8725493c5853dceda0105dafd5c0e3a7c),
|
||||
[#15322](https://github.com/angular/angular.js/issues/15322))
|
||||
- **$compile:** validate `directive.restrict` property on directive init
|
||||
([31d464](https://github.com/angular/angular.js/commit/31d464feef38b1cc950da6c8dccd0f194ebfc68b))
|
||||
@@ -149,6 +172,9 @@ consolidating all the changes shown in the previous 1.6.0 release candidates.**
|
||||
([369fb7](https://github.com/angular/angular.js/commit/369fb7f4f73664bcdab0350701552d8bef6f605e))
|
||||
- don't throw for elements with missing `getAttribute`
|
||||
([4e6c14](https://github.com/angular/angular.js/commit/4e6c14dcae4a9a30b3610a288ef8d20db47c4417))
|
||||
- don't get/set properties when getting/setting boolean attributes
|
||||
([7ceb5f](https://github.com/angular/angular.js/commit/7ceb5f6fcc43d35d1b66c3151ce6a71c60309304),
|
||||
[#14126](https://github.com/angular/angular.js/issues/14126))
|
||||
- don't remove a boolean attribute for `.attr(attrName, '')`
|
||||
([3faf45](https://github.com/angular/angular.js/commit/3faf4505732758165083c9d21de71fa9b6983f4a))
|
||||
- remove the attribute for `.attr(attribute, null)`
|
||||
@@ -628,6 +654,48 @@ var bgColor = elem.css('background-color');
|
||||
var bgColor = elem.css('backgroundColor');
|
||||
```
|
||||
|
||||
- **[7ceb5f](https://github.com/angular/angular.js/commit/7ceb5f6fcc43d35d1b66c3151ce6a71c60309304)**: don't get/set properties when getting/setting boolean attributes
|
||||
|
||||
Previously, all boolean attributes were reflected into the corresponding property when calling a
|
||||
setter and from the corresponding property when calling a getter, even on elements that don't treat
|
||||
those attributes in a special way. Now Angular doesn't do it by itself, but relies on browsers to
|
||||
know when to reflect the property. Note that this browser-level conversion differs between browsers;
|
||||
if you need to dynamically change the state of an element, you should modify the property, not the
|
||||
attribute. See https://jquery.com/upgrade-guide/1.9/#attr-versus-prop- for a more detailed
|
||||
description about a related change in jQuery 1.9.
|
||||
|
||||
This change aligns jqLite with jQuery 3. To migrate the code follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
CSS:
|
||||
|
||||
```css
|
||||
input[checked="checked"] { ... }
|
||||
```
|
||||
|
||||
JS:
|
||||
|
||||
```js
|
||||
elem1.attr('checked', 'checked');
|
||||
elem2.attr('checked', false);
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
CSS:
|
||||
|
||||
```css
|
||||
input:checked { ... }
|
||||
```
|
||||
|
||||
JS:
|
||||
|
||||
```js
|
||||
elem1.prop('checked', true);
|
||||
elem2.prop('checked', false);
|
||||
```
|
||||
|
||||
- **[3faf45](https://github.com/angular/angular.js/commit/3faf4505732758165083c9d21de71fa9b6983f4a)**:
|
||||
don't remove a boolean attribute for `.attr(attrName, '')`
|
||||
|
||||
@@ -1051,7 +1119,7 @@ previous behaviour simply add a comment:
|
||||
**Note:** Everything described below affects **IE11 only**.
|
||||
|
||||
Previously, consecutive text nodes would not get merged if they had no parent. They will now, which
|
||||
might have unexpectd side effects in the following cases:
|
||||
might have unexpected side effects in the following cases:
|
||||
|
||||
1. Passing an array or jqLite/jQuery collection of parent-less text nodes to `$compile` directly:
|
||||
|
||||
@@ -1205,7 +1273,7 @@ In cases where `ngView` was loaded asynchronously, `$route` (and its dependencie
|
||||
might also have been instantiated asynchronously. After this change, `$route` (and its dependencies)
|
||||
will - by default - be instantiated early on.
|
||||
|
||||
Although this is not expected to have unwanted side-effects in normal application bebavior, it may
|
||||
Although this is not expected to have unwanted side-effects in normal application behavior, it may
|
||||
affect your unit tests: When testing a module that (directly or indirectly) depends on `ngRoute`, a
|
||||
request will be made for the default route's template. If not properly "trained", `$httpBackend`
|
||||
will complain about this unexpected request.
|
||||
@@ -1828,6 +1896,7 @@ Please read the [Sandbox Removal Blog Post](http://angularjs.blogspot.com/2016/0
|
||||
- **jqLite:**
|
||||
- implement `jqLite(f)` as an alias to `jqLite(document).ready(f)` ([369fb7](https://github.com/angular/angular.js/commit/369fb7f4f73664bcdab0350701552d8bef6f605e))
|
||||
- don't throw for elements with missing `getAttribute` ([4e6c14](https://github.com/angular/angular.js/commit/4e6c14dcae4a9a30b3610a288ef8d20db47c4417))
|
||||
- don't get/set properties when getting/setting boolean attributes ([7ceb5f](https://github.com/angular/angular.js/commit/7ceb5f6fcc43d35d1b66c3151ce6a71c60309304), [#14126](https://github.com/angular/angular.js/issues/14126))
|
||||
- don't remove a boolean attribute for `.attr(attrName, '')` ([3faf45](https://github.com/angular/angular.js/commit/3faf4505732758165083c9d21de71fa9b6983f4a))
|
||||
- remove the attribute for `.attr(attribute, null)` ([4e3624](https://github.com/angular/angular.js/commit/4e3624552284d0e725bf6262b2e468cd2c7682fa))
|
||||
- return `[]` for `.val()` on `<select multiple>` with no selection ([d882fd](https://github.com/angular/angular.js/commit/d882fde2e532216e7cf424495db1ccb5be1789f8))
|
||||
@@ -2005,6 +2074,48 @@ var bgColor = elem.css('background-color');
|
||||
var bgColor = elem.css('backgroundColor');
|
||||
```
|
||||
|
||||
- **[7ceb5f](https://github.com/angular/angular.js/commit/7ceb5f6fcc43d35d1b66c3151ce6a71c60309304)**: don't get/set properties when getting/setting boolean attributes
|
||||
|
||||
Previously, all boolean attributes were reflected into the corresponding property when calling a
|
||||
setter and from the corresponding property when calling a getter, even on elements that don't treat
|
||||
those attributes in a special way. Now Angular doesn't do it by itself, but relies on browsers to
|
||||
know when to reflect the property. Note that this browser-level conversion differs between browsers;
|
||||
if you need to dynamically change the state of an element, you should modify the property, not the
|
||||
attribute. See https://jquery.com/upgrade-guide/1.9/#attr-versus-prop- for a more detailed
|
||||
description about a related change in jQuery 1.9.
|
||||
|
||||
This change aligns jqLite with jQuery 3. To migrate the code follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
CSS:
|
||||
|
||||
```css
|
||||
input[checked="checked"] { ... }
|
||||
```
|
||||
|
||||
JS:
|
||||
|
||||
```js
|
||||
elem1.attr('checked', 'checked');
|
||||
elem2.attr('checked', false);
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
CSS:
|
||||
|
||||
```css
|
||||
input:checked { ... }
|
||||
```
|
||||
|
||||
JS:
|
||||
|
||||
```js
|
||||
elem1.prop('checked', true);
|
||||
elem2.prop('checked', false);
|
||||
```
|
||||
|
||||
- **[3faf45](https://github.com/angular/angular.js/commit/3faf4505732758165083c9d21de71fa9b6983f4a)**: don't remove a boolean attribute for `.attr(attrName, '')`
|
||||
|
||||
Before, using the `attr` method with an empty string as a value
|
||||
@@ -2438,7 +2549,7 @@ affects custom directives that might have been reading options for their own pur
|
||||
**Note:** Everything described below affects **IE11 only**.
|
||||
|
||||
Previously, consecutive text nodes would not get merged if they had no parent. They will now, which
|
||||
might have unexpectd side effects in the following cases:
|
||||
might have unexpected side effects in the following cases:
|
||||
|
||||
1. Passing an array or jqLite/jQuery collection of parent-less text nodes to `$compile` directly:
|
||||
|
||||
@@ -2588,7 +2699,7 @@ In cases where `ngView` was loaded asynchronously, `$route` (and its dependencie
|
||||
might also have been instantiated asynchronously. After this change, `$route` (and its dependencies)
|
||||
will - by default - be instantiated early on.
|
||||
|
||||
Although this is not expected to have unwanted side-effects in normal application bebavior, it may
|
||||
Although this is not expected to have unwanted side-effects in normal application behavior, it may
|
||||
affect your unit tests: When testing a module that (directly or indirectly) depends on `ngRoute`, a
|
||||
request will be made for the default route's template. If not properly "trained", `$httpBackend`
|
||||
will complain about this unexpected request.
|
||||
@@ -12303,7 +12414,7 @@ Contains only these fixes cherry-picked from [v1.2.0rc1](#1.2.0rc1).
|
||||
- due to [39841f2e](https://github.com/angular/angular.js/commit/39841f2ec9b17b3b2920fd1eb548d444251f4f56),
|
||||
Interpolations inside DOM event handlers are disallowed.
|
||||
|
||||
DOM event handlers execute arbitrary Javascript code. Using an interpolation for such handlers means that the interpolated value is a JS string that is evaluated. Storing or generating such strings is error prone and leads to XSS vulnerabilities. On the other hand, `ngClick` and other Angular specific event handlers evaluate Angular expressions in non-window (Scope) context which makes them much safer.
|
||||
DOM event handlers execute arbitrary JavaScript code. Using an interpolation for such handlers means that the interpolated value is a JS string that is evaluated. Storing or generating such strings is error prone and leads to XSS vulnerabilities. On the other hand, `ngClick` and other Angular specific event handlers evaluate Angular expressions in non-window (Scope) context which makes them much safer.
|
||||
|
||||
To migrate the code follow the example below:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
Copyright (c) 2010-2017 Google, Inc. http://angularjs.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('animationBenchmark', ['ngAnimate'], config)
|
||||
.controller('BenchmarkController', BenchmarkController);
|
||||
|
||||
// Functions - Definitions
|
||||
function config($compileProvider) {
|
||||
$compileProvider
|
||||
.commentDirectivesEnabled(false)
|
||||
.cssClassDirectivesEnabled(false)
|
||||
.debugInfoEnabled(false);
|
||||
}
|
||||
|
||||
function BenchmarkController($scope) {
|
||||
var self = this;
|
||||
var itemCount = 1000;
|
||||
var items = (new Array(itemCount + 1)).join('.').split('');
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'create',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
self.items = items;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: '$digest',
|
||||
fn: function() {
|
||||
$scope.$root.$digest();
|
||||
}
|
||||
});
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: 'destroy',
|
||||
fn: function() {
|
||||
$scope.$apply(function() {
|
||||
self.items = [];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* eslint-env node */
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
scripts: [
|
||||
{
|
||||
id: 'jquery',
|
||||
src: 'jquery-noop.js'
|
||||
}, {
|
||||
id: 'angular',
|
||||
src: '/build/angular.js'
|
||||
}, {
|
||||
id: 'angular-animate',
|
||||
src: '/build/angular-animate.js'
|
||||
}, {
|
||||
src: 'app.js'
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
// Override me with ?jquery=/bower_components/jquery/dist/jquery.js
|
||||
@@ -0,0 +1,28 @@
|
||||
<style>
|
||||
[ng-cloak] { display: none !important; }
|
||||
.animation-container .ng-enter,
|
||||
.animation-container .ng-leave {
|
||||
transition: all 0.1s;
|
||||
}
|
||||
|
||||
.animation-container .ng-enter,
|
||||
.animation-container .ng-leave.ng-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.animation-container .ng-enter.ng-enter-active,
|
||||
.animation-container .ng-leave {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
<div ng-app="animationBenchmark" ng-cloak ng-controller="BenchmarkController as bm">
|
||||
<div class="container-fluid">
|
||||
<h2>Large collection of elements animated in and out with ngAnimate</h2>
|
||||
|
||||
<div class="animation-container">
|
||||
<div ng-repeat="i in bm.items track by $index">
|
||||
Just a plain ol' element
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -4,7 +4,7 @@
|
||||
/* global importScripts, lunr */
|
||||
|
||||
// Load up the lunr library
|
||||
importScripts('../components/lunr.js-0.5.12/lunr.min.js');
|
||||
importScripts('../components/lunr-0.7.2/lunr.min.js');
|
||||
|
||||
// Create the lunr index - the docs should be an array of object, each object containing
|
||||
// the path and search terms for a page
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "AngularJS-docs-app",
|
||||
"dependencies": {
|
||||
"jquery": "2.2.3",
|
||||
"lunr.js": "0.5.12",
|
||||
"open-sans-fontface": "1.0.4",
|
||||
"google-code-prettify": "1.0.1",
|
||||
"bootstrap": "3.1.1"
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,9 @@ module.exports = function debugDeployment(getVersion) {
|
||||
'../angular-sanitize.js',
|
||||
'../angular-touch.js',
|
||||
'../angular-animate.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'components/marked-' + getVersion('marked') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.js',
|
||||
'components/lunr-' + getVersion('lunr') + '/lunr.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
|
||||
'js/current-version-data.js',
|
||||
|
||||
@@ -17,9 +17,9 @@ module.exports = function defaultDeployment(getVersion) {
|
||||
'../angular-sanitize.min.js',
|
||||
'../angular-touch.min.js',
|
||||
'../angular-animate.min.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'components/marked-' + getVersion('marked') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.min.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
|
||||
'components/lunr-' + getVersion('lunr') + '/lunr.min.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
|
||||
'js/current-version-data.js',
|
||||
|
||||
+2
-2
@@ -21,9 +21,9 @@ module.exports = function jqueryDeployment(getVersion) {
|
||||
'../angular-sanitize.min.js',
|
||||
'../angular-touch.min.js',
|
||||
'../angular-animate.min.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'components/marked-' + getVersion('marked') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.min.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
|
||||
'components/lunr-' + getVersion('lunr') + '/lunr.min.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
|
||||
'js/current-version-data.js',
|
||||
|
||||
@@ -34,9 +34,9 @@ module.exports = function productionDeployment(getVersion) {
|
||||
cdnUrl + '/angular-sanitize.min.js',
|
||||
cdnUrl + '/angular-touch.min.js',
|
||||
cdnUrl + '/angular-animate.min.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'components/marked-' + getVersion('marked') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.min.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
|
||||
'components/lunr-' + getVersion('lunr') + '/lunr.min.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
|
||||
'js/current-version-data.js',
|
||||
|
||||
@@ -10,8 +10,8 @@ module.exports = function getVersion(readFilesProcessor) {
|
||||
var basePath = readFilesProcessor.basePath;
|
||||
|
||||
return function(component, sourceFolder, packageFile) {
|
||||
sourceFolder = path.resolve(basePath, sourceFolder || 'docs/bower_components');
|
||||
packageFile = packageFile || 'bower.json';
|
||||
sourceFolder = path.resolve(basePath, sourceFolder || 'node_modules');
|
||||
packageFile = packageFile || 'package.json';
|
||||
return require(path.join(sourceFolder,component,packageFile)).version;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -214,7 +214,7 @@
|
||||
<p class="pull-right"><a back-to-top>Back to top</a></p>
|
||||
|
||||
<p>
|
||||
Super-powered by Google ©2010-2016
|
||||
Super-powered by Google ©2010-2017
|
||||
(<a id="version"
|
||||
ng-href="https://github.com/angular/angular.js/blob/master/CHANGELOG.md#{{versionNumber}}"
|
||||
ng-bind-template="v{{version}}" title="Changelog of this version of Angular JS">
|
||||
|
||||
@@ -61,12 +61,12 @@
|
||||
</div>
|
||||
{% endblock -%}
|
||||
|
||||
{% include "lib/params.template.html" %}
|
||||
{% include "lib/events.template.html" %}
|
||||
|
||||
{%- if doc.animations %}
|
||||
<h2 id="animations">Animations</h2>
|
||||
{$ doc.animations | marked $}
|
||||
{$ 'module:ngAnimate.$animate' | link('Click here', doc) $} to learn more about the steps involved in the animation.
|
||||
{%- endif -%}
|
||||
|
||||
{% include "lib/params.template.html" %}
|
||||
{% include "lib/events.template.html" %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -232,27 +232,27 @@ than the hash fragment was changed.
|
||||
### Example
|
||||
|
||||
```js
|
||||
it('should show example', inject(
|
||||
function($locationProvider) {
|
||||
it('should show example', function() {
|
||||
module(function($locationProvider) {
|
||||
$locationProvider.html5Mode(false);
|
||||
$locationProvider.hashPrefix('!');
|
||||
},
|
||||
function($location) {
|
||||
});
|
||||
inject(function($location) {
|
||||
// open http://example.com/base/index.html#!/a
|
||||
$location.absUrl() === 'http://example.com/base/index.html#!/a'
|
||||
$location.path() === '/a'
|
||||
expect($location.absUrl()).toBe('http://example.com/base/index.html#!/a');
|
||||
expect($location.path()).toBe('/a');
|
||||
|
||||
$location.path('/foo')
|
||||
$location.absUrl() === 'http://example.com/base/index.html#!/foo'
|
||||
$location.path('/foo');
|
||||
expect($location.absUrl()).toBe('http://example.com/base/index.html#!/foo');
|
||||
|
||||
$location.search() === {}
|
||||
expect($location.search()).toEqual({});
|
||||
$location.search({a: 'b', c: true});
|
||||
$location.absUrl() === 'http://example.com/base/index.html#!/foo?a=b&c'
|
||||
expect($location.absUrl()).toBe('http://example.com/base/index.html#!/foo?a=b&c');
|
||||
|
||||
$location.path('/new').search('x=y');
|
||||
$location.absUrl() === 'http://example.com/base/index.html#!/new?x=y'
|
||||
}
|
||||
));
|
||||
expect($location.absUrl()).toBe('http://example.com/base/index.html#!/new?x=y');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## HTML5 mode
|
||||
@@ -274,40 +274,50 @@ and updates the url in a way that never performs a full page reload.
|
||||
### Example
|
||||
|
||||
```js
|
||||
it('should show example', inject(
|
||||
function($locationProvider) {
|
||||
it('should show example', function() {
|
||||
module(function($locationProvider) {
|
||||
$locationProvider.html5Mode(true);
|
||||
$locationProvider.hashPrefix('!');
|
||||
},
|
||||
function($location) {
|
||||
});
|
||||
inject(function($location) {
|
||||
// in browser with HTML5 history support:
|
||||
// open http://example.com/#!/a -> rewrite to http://example.com/a
|
||||
// (replacing the http://example.com/#!/a history record)
|
||||
$location.path() === '/a'
|
||||
expect($location.path()).toBe('/a');
|
||||
|
||||
$location.path('/foo');
|
||||
$location.absUrl() === 'http://example.com/foo'
|
||||
expect($location.absUrl()).toBe('http://example.com/foo');
|
||||
|
||||
$location.search() === {}
|
||||
expect($location.search()).toEqual({});
|
||||
$location.search({a: 'b', c: true});
|
||||
$location.absUrl() === 'http://example.com/foo?a=b&c'
|
||||
expect($location.absUrl()).toBe('http://example.com/foo?a=b&c');
|
||||
|
||||
$location.path('/new').search('x=y');
|
||||
$location.url() === 'new?x=y'
|
||||
$location.absUrl() === 'http://example.com/new?x=y'
|
||||
expect($location.url()).toBe('/new?x=y');
|
||||
expect($location.absUrl()).toBe('http://example.com/new?x=y');
|
||||
});
|
||||
});
|
||||
|
||||
it('should show example (when browser doesn\'t support HTML5 mode', function() {
|
||||
module(function($provide, $locationProvider) {
|
||||
$locationProvider.html5Mode(true);
|
||||
$locationProvider.hashPrefix('!');
|
||||
$provide.value('$sniffer', {history: false});
|
||||
});
|
||||
inject(initBrowser({ url: 'http://example.com/new?x=y', basePath: '/' }),
|
||||
function($location) {
|
||||
// in browser without html5 history support:
|
||||
// open http://example.com/new?x=y -> redirect to http://example.com/#!/new?x=y
|
||||
// (again replacing the http://example.com/new?x=y history item)
|
||||
$location.path() === '/new'
|
||||
$location.search() === {x: 'y'}
|
||||
expect($location.path()).toBe('/new');
|
||||
expect($location.search()).toEqual({x: 'y'});
|
||||
|
||||
$location.path('/foo/bar');
|
||||
$location.path() === '/foo/bar'
|
||||
$location.url() === '/foo/bar?x=y'
|
||||
$location.absUrl() === 'http://example.com/#!/foo/bar?x=y'
|
||||
}
|
||||
));
|
||||
expect($location.path()).toBe('/foo/bar');
|
||||
expect($location.url()).toBe('/foo/bar?x=y');
|
||||
expect($location.absUrl()).toBe('http://example.com/#!/foo/bar?x=y');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Fallback for legacy browsers
|
||||
@@ -555,7 +565,7 @@ In these examples we use `<base href="/base/index.html" />`. The inputs represen
|
||||
|
||||
</example>
|
||||
|
||||
####Browser in HTML5 Fallback mode (Hashbang mode)
|
||||
#### Browser in HTML5 Fallback mode (Hashbang mode)
|
||||
<example module="hashbang-mode" name="location-hashbang-mode">
|
||||
<file name="index.html">
|
||||
<div ng-controller="LocationController">
|
||||
|
||||
@@ -122,7 +122,7 @@ The same approach to animation can be used using JavaScript code (**jQuery is us
|
||||
```js
|
||||
myModule.animation('.repeated-item', function() {
|
||||
return {
|
||||
enter : function(element, done) {
|
||||
enter: function(element, done) {
|
||||
element.css('opacity',0);
|
||||
jQuery(element).animate({
|
||||
opacity: 1
|
||||
@@ -137,7 +137,7 @@ myModule.animation('.repeated-item', function() {
|
||||
}
|
||||
}
|
||||
},
|
||||
leave : function(element, done) {
|
||||
leave: function(element, done) {
|
||||
element.css('opacity', 1);
|
||||
jQuery(element).animate({
|
||||
opacity: 0
|
||||
@@ -152,7 +152,7 @@ myModule.animation('.repeated-item', function() {
|
||||
}
|
||||
}
|
||||
},
|
||||
move : function(element, done) {
|
||||
move: function(element, done) {
|
||||
element.css('opacity', 0);
|
||||
jQuery(element).animate({
|
||||
opacity: 1
|
||||
@@ -169,8 +169,8 @@ myModule.animation('.repeated-item', function() {
|
||||
},
|
||||
|
||||
// you can also capture these animation events
|
||||
addClass : function(element, className, done) {},
|
||||
removeClass : function(element, className, done) {}
|
||||
addClass: function(element, className, done) {},
|
||||
removeClass: function(element, className, done) {}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@@ -78,6 +78,7 @@ It's also possible to add components via {@link $compileProvider#component} in a
|
||||
| link functions | Yes | No |
|
||||
| multiElement | Yes | No |
|
||||
| priority | Yes | No |
|
||||
| replace | Yes (deprecated) | No |
|
||||
| require | Yes | Yes |
|
||||
| restrict | Yes | No (restricted to elements only) |
|
||||
| scope | Yes (default: false) | No (scope is always isolate) |
|
||||
|
||||
@@ -241,7 +241,7 @@ An expression that starts with `::` is considered a one-time expression. One-tim
|
||||
will stop recalculating once they are stable, which happens after the first digest if the expression
|
||||
result is a non-undefined value (see value stabilization algorithm below).
|
||||
|
||||
<example module="oneTimeBidingExampleApp" name="expression-one-time">
|
||||
<example module="oneTimeBindingExampleApp" name="expression-one-time">
|
||||
<file name="index.html">
|
||||
<div ng-controller="EventController">
|
||||
<button ng-click="clickMe($event)">Click Me</button>
|
||||
@@ -250,7 +250,7 @@ result is a non-undefined value (see value stabilization algorithm below).
|
||||
</div>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
angular.module('oneTimeBidingExampleApp', []).
|
||||
angular.module('oneTimeBindingExampleApp', []).
|
||||
controller('EventController', ['$scope', function($scope) {
|
||||
var counter = 0;
|
||||
var names = ['Igor', 'Misko', 'Chirayu', 'Lucas'];
|
||||
@@ -265,24 +265,24 @@ result is a non-undefined value (see value stabilization algorithm below).
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should freeze binding after its value has stabilized', function() {
|
||||
var oneTimeBiding = element(by.id('one-time-binding-example'));
|
||||
var oneTimeBinding = element(by.id('one-time-binding-example'));
|
||||
var normalBinding = element(by.id('normal-binding-example'));
|
||||
|
||||
expect(oneTimeBiding.getText()).toEqual('One time binding:');
|
||||
expect(oneTimeBinding.getText()).toEqual('One time binding:');
|
||||
expect(normalBinding.getText()).toEqual('Normal binding:');
|
||||
element(by.buttonText('Click Me')).click();
|
||||
|
||||
expect(oneTimeBiding.getText()).toEqual('One time binding: Igor');
|
||||
expect(oneTimeBinding.getText()).toEqual('One time binding: Igor');
|
||||
expect(normalBinding.getText()).toEqual('Normal binding: Igor');
|
||||
element(by.buttonText('Click Me')).click();
|
||||
|
||||
expect(oneTimeBiding.getText()).toEqual('One time binding: Igor');
|
||||
expect(oneTimeBinding.getText()).toEqual('One time binding: Igor');
|
||||
expect(normalBinding.getText()).toEqual('Normal binding: Misko');
|
||||
|
||||
element(by.buttonText('Click Me')).click();
|
||||
element(by.buttonText('Click Me')).click();
|
||||
|
||||
expect(oneTimeBiding.getText()).toEqual('One time binding: Igor');
|
||||
expect(oneTimeBinding.getText()).toEqual('One time binding: Igor');
|
||||
expect(normalBinding.getText()).toEqual('Normal binding: Lucas');
|
||||
});
|
||||
</file>
|
||||
|
||||
@@ -26,7 +26,7 @@ directive}. Additionally, you can use {@link guide/i18n#messageformat-extension
|
||||
All localizable Angular components depend on locale-specific rule sets managed by the {@link
|
||||
ng.$locale `$locale` service}.
|
||||
|
||||
There a few examples that showcase how to use Angular filters with various locale rule sets in the
|
||||
There are a few examples that showcase how to use Angular filters with various locale rule sets in the
|
||||
[`i18n/e2e` directory](https://github.com/angular/angular.js/tree/master/i18n/e2e) of the Angular
|
||||
source code.
|
||||
|
||||
@@ -85,7 +85,7 @@ requires German locale, you would serve index_de-de.html which will look somethi
|
||||
|
||||
Both approaches described above require you to prepare different `index.html` pages or JavaScript
|
||||
files for each locale that your app may use. You also need to configure your server to serve
|
||||
the correct file that correspond to the desired locale.
|
||||
the correct file that corresponds to the desired locale.
|
||||
|
||||
The second approach (including the locale JavaScript file in `index.html`) may be slower because
|
||||
an extra script needs to be loaded.
|
||||
|
||||
@@ -90,6 +90,7 @@ commits for more info.
|
||||
(see [details](guide/migration#migrate1.5to1.6-ng-misc-jqLite) below):
|
||||
- Keys passed to `.data()` and `.css()` are now camelCased in the same way as the jQuery methods
|
||||
do.
|
||||
- Getting/setting boolean attributes no longer takes the corresponding properties into account.
|
||||
- Setting boolean attributes to empty string no longer removes the attribute.
|
||||
- Calling `.val()` on a multiple select will always return an array, even if no option is
|
||||
selected.
|
||||
@@ -417,7 +418,7 @@ will be removed in a future version, so we strongly recommend migrating your app
|
||||
rely on it as soon as possible.
|
||||
|
||||
Initialization logic that relies on bindings being present should be put in the controller's
|
||||
`$onInit()` method, which is guarranteed to always be called _after_ the bindings have been
|
||||
`$onInit()` method, which is guaranteed to always be called _after_ the bindings have been
|
||||
assigned.
|
||||
|
||||
Before:
|
||||
@@ -502,7 +503,7 @@ running at `https://docs.angularjs.org` then the following will fail:
|
||||
<link href="{{ 'http://mydomain.org/unsafe.css' }}" rel="stylesheet" />
|
||||
```
|
||||
|
||||
By default, only URLs with the same domain and prototocl as the application document are considered
|
||||
By default, only URLs with the same domain and protocol as the application document are considered
|
||||
safe in the `RESOURCE_URL` context. To use URLs from other domains and/or protocols, you may either
|
||||
whitelist them or wrap them into a trusted value by calling `$sce.trustAsResourceUrl(url)`.
|
||||
|
||||
@@ -881,6 +882,48 @@ var bgColor = elem.css('background-color');
|
||||
var bgColor = elem.css('backgroundColor');
|
||||
```
|
||||
|
||||
<hr />
|
||||
<major />
|
||||
**Due to [7ceb5f](https://github.com/angular/angular.js/commit/7ceb5f6fcc43d35d1b66c3151ce6a71c60309304)**,
|
||||
getting/setting boolean attributes will no longer take the corresponding properties into account.
|
||||
Previously, all boolean attributes were reflected into the corresponding property when calling a
|
||||
setter and from the corresponding property when calling a getter, even on elements that don't treat
|
||||
those attributes in a special way. Now Angular doesn't do it by itself, but relies on browsers to
|
||||
know when to reflect the property. Note that this browser-level conversion differs between browsers;
|
||||
if you need to dynamically change the state of an element, you should modify the property, not the
|
||||
attribute. See https://jquery.com/upgrade-guide/1.9/#attr-versus-prop- for a more detailed
|
||||
description about a related change in jQuery 1.9.
|
||||
|
||||
This change aligns jqLite with jQuery 3. To migrate the code follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
```css
|
||||
/* CSS */
|
||||
|
||||
input[checked="checked"] { ... }
|
||||
```
|
||||
```js
|
||||
// JS
|
||||
|
||||
elem1.attr('checked', 'checked');
|
||||
elem2.attr('checked', false);
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```css
|
||||
/* CSS */
|
||||
|
||||
input:checked { ... }
|
||||
```
|
||||
```js
|
||||
// JS
|
||||
|
||||
elem1.prop('checked', true);
|
||||
elem2.prop('checked', false);
|
||||
```
|
||||
|
||||
<hr />
|
||||
<major />
|
||||
**Due to [3faf45](https://github.com/angular/angular.js/commit/3faf4505732758165083c9d21de71fa9b6983f4a)**,
|
||||
@@ -1170,7 +1213,7 @@ with an unencoded `;` character.
|
||||
Previously, in cases where `ngView` was loaded asynchronously, `$route` (and its dependencies) might
|
||||
also have been instantiated asynchronously.
|
||||
|
||||
Although this is not expected to have unwanted side-effects in normal application bebavior, it may
|
||||
Although this is not expected to have unwanted side-effects in normal application behavior, it may
|
||||
affect your unit tests: When testing a module that (directly or indirectly) depends on `ngRoute`, a
|
||||
request will be made for the default route's template. If not properly "trained", `$httpBackend`
|
||||
will complain about this unexpected request. You can restore the previous behavior (and avoid
|
||||
@@ -2606,7 +2649,7 @@ See [38deedd6](https://github.com/angular/angular.js/commit/38deedd6e3d806eb8262
|
||||
|
||||
### Interpolations inside DOM event handlers are now disallowed
|
||||
|
||||
DOM event handlers execute arbitrary Javascript code. Using an interpolation for such handlers
|
||||
DOM event handlers execute arbitrary JavaScript code. Using an interpolation for such handlers
|
||||
means that the interpolated value is a JS string that is evaluated. Storing or generating such
|
||||
strings is error prone and leads to XSS vulnerabilities. On the other hand, `ngClick` and other
|
||||
Angular specific event handlers evaluate Angular expressions in non-window (Scope) context which
|
||||
|
||||
@@ -221,8 +221,8 @@ it('should clear messages after alert', function() {
|
||||
notify('two');
|
||||
notify('third');
|
||||
|
||||
expect(mock.alert.callCount).toEqual(2);
|
||||
expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]);
|
||||
expect(mock.alert.calls.count()).toEqual(2);
|
||||
expect(mock.alert.calls.mostRecent().args).toEqual(["more\ntwo\nthird"]);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ tutorials.
|
||||
## Subscribe
|
||||
|
||||
* Subscribe to the [mailing list](http://groups.google.com/forum/?fromgroups#!forum/angular). Ask questions here!
|
||||
* Follow us on [Twitter](https://twitter.com/intent/follow?original_referer=http%3A%2F%2Fangularjs.org%2F®ion=follow_link&screen_name=angularjs&source=followbutton&variant=2.0)
|
||||
* Follow us on [Twitter](https://twitter.com/intent/follow?original_referer=http%3A%2F%2Fangularjs.org%2F®ion=follow_link&screen_name=angular&source=followbutton&variant=2.0)
|
||||
* Add us to your circles on [Google+](https://plus.google.com/110323587230527980117/posts)
|
||||
|
||||
## Read more
|
||||
|
||||
@@ -54,7 +54,7 @@ We will keep this in mind though, as we add more features.
|
||||
So, now that we learned we should put everything in its own file, our `app/` directory will soon be
|
||||
full with dozens of files and specs (remember we keep our unit test files next to the corresponding
|
||||
source code files). What's more important, logically related files will not be grouped together; it
|
||||
will be really difficult of locate all files related to a specific section of the application and
|
||||
will be really difficult to locate all files related to a specific section of the application and
|
||||
make a change or fix a bug.
|
||||
|
||||
So, what shall we do?
|
||||
|
||||
+6
-21
@@ -1,10 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
var gulp = require('gulp');
|
||||
var log = require('gulp-util').log;
|
||||
var concat = require('gulp-concat');
|
||||
var eslint = require('gulp-eslint');
|
||||
var bower = require('bower');
|
||||
var Dgeni = require('dgeni');
|
||||
var merge = require('event-stream').merge;
|
||||
var path = require('canonical-path');
|
||||
@@ -18,7 +16,6 @@ var rename = require('gulp-rename');
|
||||
// See clean and bower for async tasks, and see assets and doc-gen for dependent tasks below
|
||||
|
||||
var outputFolder = '../build/docs';
|
||||
var bowerFolder = 'bower_components';
|
||||
|
||||
var src = 'app/src/**/*.js';
|
||||
var ignoredFiles = '!src/angular.bind.js';
|
||||
@@ -57,8 +54,8 @@ var getMergedEslintConfig = function(filepath) {
|
||||
|
||||
var copyComponent = function(component, pattern, sourceFolder, packageFile) {
|
||||
pattern = pattern || '/**/*';
|
||||
sourceFolder = sourceFolder || bowerFolder;
|
||||
packageFile = packageFile || 'bower.json';
|
||||
sourceFolder = sourceFolder || '../node_modules';
|
||||
packageFile = packageFile || 'package.json';
|
||||
var version = require(path.resolve(sourceFolder, component, packageFile)).version;
|
||||
return gulp
|
||||
.src(sourceFolder + '/' + component + pattern)
|
||||
@@ -66,18 +63,6 @@ var copyComponent = function(component, pattern, sourceFolder, packageFile) {
|
||||
};
|
||||
|
||||
|
||||
gulp.task('bower', function() {
|
||||
var bowerTask = bower.commands.install();
|
||||
bowerTask.on('log', function(result) {
|
||||
log('bower:', result.id, result.data.endpoint.name);
|
||||
});
|
||||
bowerTask.on('error', function(error) {
|
||||
log(error);
|
||||
});
|
||||
return bowerTask;
|
||||
});
|
||||
|
||||
|
||||
gulp.task('build-app', function() {
|
||||
var file = 'docs.js';
|
||||
var minFile = 'docs.min.js';
|
||||
@@ -94,7 +79,7 @@ gulp.task('build-app', function() {
|
||||
});
|
||||
|
||||
|
||||
gulp.task('assets', ['bower'], function() {
|
||||
gulp.task('assets', function() {
|
||||
var JS_EXT = /\.js$/;
|
||||
return merge(
|
||||
gulp.src(['img/**/*']).pipe(gulp.dest(outputFolder + '/img')),
|
||||
@@ -113,15 +98,15 @@ gulp.task('assets', ['bower'], function() {
|
||||
})),
|
||||
copyComponent('bootstrap', '/dist/**/*'),
|
||||
copyComponent('open-sans-fontface'),
|
||||
copyComponent('lunr.js', '/*.js'),
|
||||
copyComponent('lunr', '/*.js'),
|
||||
copyComponent('google-code-prettify'),
|
||||
copyComponent('jquery', '/dist/*.js'),
|
||||
copyComponent('marked', '/**/*.js', '../node_modules', 'package.json')
|
||||
copyComponent('marked', '/**/*.js')
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
gulp.task('doc-gen', ['bower'], function() {
|
||||
gulp.task('doc-gen', function() {
|
||||
var dgeni = new Dgeni([require('./config')]);
|
||||
return dgeni.generate().catch(function() {
|
||||
process.exit(1);
|
||||
|
||||
+7
-2
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": "^6.9.1",
|
||||
"yarn": "^0.17.9",
|
||||
"yarn": ">=0.17.9",
|
||||
"grunt": "^1.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -22,6 +22,7 @@
|
||||
"devDependencies": {
|
||||
"angular-benchpress": "0.x.x",
|
||||
"benchmark": "1.x.x",
|
||||
"bootstrap": "3.1.1",
|
||||
"bower": "~1.3.9",
|
||||
"browserstacktunnel-wrapper": "^1.4.2",
|
||||
"canonical-path": "0.0.2",
|
||||
@@ -32,9 +33,10 @@
|
||||
"cross-spawn": "^4.0.0",
|
||||
"cz-conventional-changelog": "1.1.4",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.16.0",
|
||||
"dgeni-packages": "^0.16.4",
|
||||
"event-stream": "~3.1.0",
|
||||
"glob": "^6.0.1",
|
||||
"google-code-prettify": "1.0.1",
|
||||
"grunt": "^1.0.1",
|
||||
"grunt-bump": "^0.8.0",
|
||||
"grunt-cli": "^1.2.0",
|
||||
@@ -57,6 +59,7 @@
|
||||
"jasmine-core": "^2.4.0",
|
||||
"jasmine-node": "^2.0.0",
|
||||
"jasmine-reporters": "^2.2.0",
|
||||
"jquery": "^3.1.1",
|
||||
"karma": "^1.1.2",
|
||||
"karma-browserstack-launcher": "^1.0.1",
|
||||
"karma-chrome-launcher": "^1.0.1",
|
||||
@@ -69,9 +72,11 @@
|
||||
"load-grunt-tasks": "^3.5.0",
|
||||
"lodash": "~2.4.1",
|
||||
"log4js": "^0.6.27",
|
||||
"lunr": "^0.7.2",
|
||||
"marked": "~0.3.0",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"npm-run": "^4.1.0",
|
||||
"open-sans-fontface": "^1.4.0",
|
||||
"promises-aplus-tests": "~2.1.0",
|
||||
"protractor": "^4.0.10",
|
||||
"q": "~1.0.0",
|
||||
|
||||
+1
-1
@@ -150,7 +150,7 @@
|
||||
|
||||
/* apis.js */
|
||||
"hashKey": false,
|
||||
"HashMap": false,
|
||||
"NgMap": false,
|
||||
|
||||
/* urlUtils.js */
|
||||
"urlResolve": false,
|
||||
|
||||
+7
-3
@@ -1479,12 +1479,16 @@ function getNgAttribute(element, ngAttr) {
|
||||
}
|
||||
|
||||
function allowAutoBootstrap(document) {
|
||||
if (!document.currentScript) {
|
||||
var script = document.currentScript;
|
||||
var src = script && script.getAttribute('src');
|
||||
|
||||
if (!src) {
|
||||
return true;
|
||||
}
|
||||
var src = document.currentScript.getAttribute('src');
|
||||
|
||||
var link = document.createElement('a');
|
||||
link.href = src;
|
||||
|
||||
if (document.location.origin === link.origin) {
|
||||
// Same-origin resources are always allowed, even for non-whitelisted schemes.
|
||||
return true;
|
||||
@@ -1866,7 +1870,7 @@ function bindJQuery() {
|
||||
extend(jQuery.fn, {
|
||||
scope: JQLitePrototype.scope,
|
||||
isolateScope: JQLitePrototype.isolateScope,
|
||||
controller: JQLitePrototype.controller,
|
||||
controller: /** @type {?} */ (JQLitePrototype).controller,
|
||||
injector: JQLitePrototype.injector,
|
||||
inheritedData: JQLitePrototype.inheritedData
|
||||
});
|
||||
|
||||
@@ -70,7 +70,6 @@
|
||||
$$ForceReflowProvider,
|
||||
$InterpolateProvider,
|
||||
$IntervalProvider,
|
||||
$$HashMapProvider,
|
||||
$HttpProvider,
|
||||
$HttpParamSerializerProvider,
|
||||
$HttpParamSerializerJQLikeProvider,
|
||||
@@ -79,6 +78,7 @@
|
||||
$jsonpCallbacksProvider,
|
||||
$LocationProvider,
|
||||
$LogProvider,
|
||||
$$MapProvider,
|
||||
$ParseProvider,
|
||||
$RootScopeProvider,
|
||||
$QProvider,
|
||||
@@ -260,7 +260,7 @@ function publishExternalAPI(angular) {
|
||||
$window: $WindowProvider,
|
||||
$$rAF: $$RAFProvider,
|
||||
$$jqLite: $$jqLiteProvider,
|
||||
$$HashMap: $$HashMapProvider,
|
||||
$$Map: $$MapProvider,
|
||||
$$cookieReader: $$CookieReaderProvider
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2017 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window) {
|
||||
|
||||
+55
-36
@@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* Computes a hash of an 'obj'.
|
||||
* Hash of a:
|
||||
@@ -33,49 +32,69 @@ function hashKey(obj, nextUidFn) {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* HashMap which can use objects as keys
|
||||
*/
|
||||
function HashMap(array, isolatedUid) {
|
||||
if (isolatedUid) {
|
||||
var uid = 0;
|
||||
this.nextUid = function() {
|
||||
return ++uid;
|
||||
};
|
||||
}
|
||||
forEach(array, this.put, this);
|
||||
// A minimal ES2015 Map implementation.
|
||||
// Should be bug/feature equivalent to the native implementations of supported browsers
|
||||
// (for the features required in Angular).
|
||||
// See https://kangax.github.io/compat-table/es6/#test-Map
|
||||
var nanKey = Object.create(null);
|
||||
function NgMapShim() {
|
||||
this._keys = [];
|
||||
this._values = [];
|
||||
this._lastKey = NaN;
|
||||
this._lastIndex = -1;
|
||||
}
|
||||
HashMap.prototype = {
|
||||
/**
|
||||
* Store key value pair
|
||||
* @param key key to store can be any type
|
||||
* @param value value to store can be any type
|
||||
*/
|
||||
put: function(key, value) {
|
||||
this[hashKey(key, this.nextUid)] = value;
|
||||
NgMapShim.prototype = {
|
||||
_idx: function(key) {
|
||||
if (key === this._lastKey) {
|
||||
return this._lastIndex;
|
||||
}
|
||||
this._lastKey = key;
|
||||
this._lastIndex = this._keys.indexOf(key);
|
||||
return this._lastIndex;
|
||||
},
|
||||
_transformKey: function(key) {
|
||||
return isNumberNaN(key) ? nanKey : key;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @returns {Object} the value for the key
|
||||
*/
|
||||
get: function(key) {
|
||||
return this[hashKey(key, this.nextUid)];
|
||||
key = this._transformKey(key);
|
||||
var idx = this._idx(key);
|
||||
if (idx !== -1) {
|
||||
return this._values[idx];
|
||||
}
|
||||
},
|
||||
set: function(key, value) {
|
||||
key = this._transformKey(key);
|
||||
var idx = this._idx(key);
|
||||
if (idx === -1) {
|
||||
idx = this._lastIndex = this._keys.length;
|
||||
}
|
||||
this._keys[idx] = key;
|
||||
this._values[idx] = value;
|
||||
|
||||
/**
|
||||
* Remove the key/value pair
|
||||
* @param key
|
||||
*/
|
||||
remove: function(key) {
|
||||
var value = this[key = hashKey(key, this.nextUid)];
|
||||
delete this[key];
|
||||
return value;
|
||||
// Support: IE11
|
||||
// Do not `return this` to simulate the partial IE11 implementation
|
||||
},
|
||||
delete: function(key) {
|
||||
key = this._transformKey(key);
|
||||
var idx = this._idx(key);
|
||||
if (idx === -1) {
|
||||
return false;
|
||||
}
|
||||
this._keys.splice(idx, 1);
|
||||
this._values.splice(idx, 1);
|
||||
this._lastKey = NaN;
|
||||
this._lastIndex = -1;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
var $$HashMapProvider = [/** @this */function() {
|
||||
// For now, always use `NgMapShim`, even if `window.Map` is available. Some native implementations
|
||||
// are still buggy (often in subtle ways) and can cause hard-to-debug failures. When native `Map`
|
||||
// implementations get more stable, we can reconsider switching to `window.Map` (when available).
|
||||
var NgMap = NgMapShim;
|
||||
|
||||
var $$MapProvider = [/** @this */function() {
|
||||
this.$get = [function() {
|
||||
return HashMap;
|
||||
return NgMap;
|
||||
}];
|
||||
}];
|
||||
|
||||
@@ -71,11 +71,7 @@ var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
|
||||
var $injectorMinErr = minErr('$injector');
|
||||
|
||||
function stringifyFn(fn) {
|
||||
// Support: Chrome 50-51 only
|
||||
// Creating a new string by adding `' '` at the end, to hack around some bug in Chrome v50/51
|
||||
// (See https://github.com/angular/angular.js/issues/14487.)
|
||||
// TODO (gkalpak): Remove workaround when Chrome v52 is released
|
||||
return Function.prototype.toString.call(fn) + ' ';
|
||||
return Function.prototype.toString.call(fn);
|
||||
}
|
||||
|
||||
function extractArgs(fn) {
|
||||
@@ -649,7 +645,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
var INSTANTIATING = {},
|
||||
providerSuffix = 'Provider',
|
||||
path = [],
|
||||
loadedModules = new HashMap([], true),
|
||||
loadedModules = new NgMap(),
|
||||
providerCache = {
|
||||
$provide: {
|
||||
provider: supportObject(provider),
|
||||
@@ -757,7 +753,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
var runBlocks = [], moduleFn;
|
||||
forEach(modulesToLoad, function(module) {
|
||||
if (loadedModules.get(module)) return;
|
||||
loadedModules.put(module, true);
|
||||
loadedModules.set(module, true);
|
||||
|
||||
function runInvokeQueue(queue) {
|
||||
var i, ii;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2017 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2017 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular) {
|
||||
|
||||
+3
-3
@@ -60,7 +60,7 @@ var $$CoreAnimateJsProvider = /** @this */ function() {
|
||||
// this is prefixed with Core since it conflicts with
|
||||
// the animateQueueProvider defined in ngAnimate/animateQueue.js
|
||||
var $$CoreAnimateQueueProvider = /** @this */ function() {
|
||||
var postDigestQueue = new HashMap();
|
||||
var postDigestQueue = new NgMap();
|
||||
var postDigestElements = [];
|
||||
|
||||
this.$get = ['$$AnimateRunner', '$rootScope',
|
||||
@@ -139,7 +139,7 @@ var $$CoreAnimateQueueProvider = /** @this */ function() {
|
||||
jqLiteRemoveClass(elm, toRemove);
|
||||
}
|
||||
});
|
||||
postDigestQueue.remove(element);
|
||||
postDigestQueue.delete(element);
|
||||
}
|
||||
});
|
||||
postDigestElements.length = 0;
|
||||
@@ -154,7 +154,7 @@ var $$CoreAnimateQueueProvider = /** @this */ function() {
|
||||
|
||||
if (classesAdded || classesRemoved) {
|
||||
|
||||
postDigestQueue.put(element, data);
|
||||
postDigestQueue.set(element, data);
|
||||
postDigestElements.push(element);
|
||||
|
||||
if (postDigestElements.length === 1) {
|
||||
|
||||
+9
-8
@@ -96,7 +96,6 @@ function Browser(window, document, $log, $sniffer) {
|
||||
};
|
||||
|
||||
cacheState();
|
||||
lastHistoryState = cachedState;
|
||||
|
||||
/**
|
||||
* @name $browser#url
|
||||
@@ -150,8 +149,6 @@ function Browser(window, document, $log, $sniffer) {
|
||||
if ($sniffer.history && (!sameBase || !sameState)) {
|
||||
history[replace ? 'replaceState' : 'pushState'](state, '', url);
|
||||
cacheState();
|
||||
// Do the assignment again so that those two variables are referentially identical.
|
||||
lastHistoryState = cachedState;
|
||||
} else {
|
||||
if (!sameBase) {
|
||||
pendingLocation = url;
|
||||
@@ -200,8 +197,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
|
||||
function cacheStateAndFireUrlChange() {
|
||||
pendingLocation = null;
|
||||
cacheState();
|
||||
fireUrlChange();
|
||||
fireStateOrUrlChange();
|
||||
}
|
||||
|
||||
// This variable should be used *only* inside the cacheState function.
|
||||
@@ -215,11 +211,16 @@ function Browser(window, document, $log, $sniffer) {
|
||||
if (equals(cachedState, lastCachedState)) {
|
||||
cachedState = lastCachedState;
|
||||
}
|
||||
|
||||
lastCachedState = cachedState;
|
||||
lastHistoryState = cachedState;
|
||||
}
|
||||
|
||||
function fireUrlChange() {
|
||||
if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
|
||||
function fireStateOrUrlChange() {
|
||||
var prevLastHistoryState = lastHistoryState;
|
||||
cacheState();
|
||||
|
||||
if (lastBrowserUrl === self.url() && prevLastHistoryState === cachedState) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -285,7 +286,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
* Needs to be exported to be able to check for changes that have been done in sync,
|
||||
* as hashchange/popstate events fire in async.
|
||||
*/
|
||||
self.$$checkUrlChange = fireUrlChange;
|
||||
self.$$checkUrlChange = fireStateOrUrlChange;
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Misc API
|
||||
|
||||
+4
-3
@@ -128,7 +128,8 @@
|
||||
* * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
|
||||
* `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
|
||||
* object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
|
||||
* component such as cloning the bound value to prevent accidental mutation of the outer value.
|
||||
* component such as cloning the bound value to prevent accidental mutation of the outer value. Note that this will
|
||||
* also be called when your bindings are initialized.
|
||||
* * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on
|
||||
* changes. Any actions that you wish to take in response to the changes that you detect must be
|
||||
* invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook
|
||||
@@ -983,7 +984,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var bindingCache = createMap();
|
||||
|
||||
function parseIsolateBindings(scope, directiveName, isController) {
|
||||
var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/;
|
||||
var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*([\w$]*)\s*$/;
|
||||
|
||||
var bindings = createMap();
|
||||
|
||||
@@ -3155,7 +3156,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (error instanceof Error) {
|
||||
$exceptionHandler(error);
|
||||
}
|
||||
}).catch(noop);
|
||||
});
|
||||
|
||||
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
|
||||
var childBoundTranscludeFn = boundTranscludeFn;
|
||||
|
||||
@@ -159,7 +159,8 @@
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* This directive sets the `disabled` attribute on the element if the
|
||||
* This directive sets the `disabled` attribute on the element (typically a form control,
|
||||
* e.g. `input`, `button`, `select` etc.) if the
|
||||
* {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
|
||||
*
|
||||
* A special directive is necessary because we cannot use interpolation inside the `disabled`
|
||||
|
||||
@@ -1565,15 +1565,27 @@ function isValidForStep(viewValue, stepBase, step) {
|
||||
// and `viewValue` is expected to be a valid stringified number.
|
||||
var value = Number(viewValue);
|
||||
|
||||
var isNonIntegerValue = !isNumberInteger(value);
|
||||
var isNonIntegerStepBase = !isNumberInteger(stepBase);
|
||||
var isNonIntegerStep = !isNumberInteger(step);
|
||||
|
||||
// Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or
|
||||
// `0.5 % 0.1 !== 0`), we need to convert all numbers to integers.
|
||||
if (!isNumberInteger(value) || !isNumberInteger(stepBase) || !isNumberInteger(step)) {
|
||||
var decimalCount = Math.max(countDecimals(value), countDecimals(stepBase), countDecimals(step));
|
||||
if (isNonIntegerValue || isNonIntegerStepBase || isNonIntegerStep) {
|
||||
var valueDecimals = isNonIntegerValue ? countDecimals(value) : 0;
|
||||
var stepBaseDecimals = isNonIntegerStepBase ? countDecimals(stepBase) : 0;
|
||||
var stepDecimals = isNonIntegerStep ? countDecimals(step) : 0;
|
||||
|
||||
var decimalCount = Math.max(valueDecimals, stepBaseDecimals, stepDecimals);
|
||||
var multiplier = Math.pow(10, decimalCount);
|
||||
|
||||
value = value * multiplier;
|
||||
stepBase = stepBase * multiplier;
|
||||
step = step * multiplier;
|
||||
|
||||
if (isNonIntegerValue) value = Math.round(value);
|
||||
if (isNonIntegerStepBase) stepBase = Math.round(stepBase);
|
||||
if (isNonIntegerStep) step = Math.round(step);
|
||||
}
|
||||
|
||||
return (value - stepBase) % step === 0;
|
||||
@@ -2130,7 +2142,10 @@ var ngValueDirective = function() {
|
||||
* makes it possible to use ngValue as a sort of one-way bind.
|
||||
*/
|
||||
function updateElementValue(element, attr, value) {
|
||||
element.prop('value', value);
|
||||
// Support: IE9 only
|
||||
// In IE9 values are converted to string (e.g. `input.value = null` results in `input.value === 'null'`).
|
||||
var propValue = isDefined(value) ? value : (msie === 9) ? '' : null;
|
||||
element.prop('value', propValue);
|
||||
attr.$set('value', value);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,15 +53,15 @@ forEach(
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function($element, attr) {
|
||||
// We expose the powerful $event object on the scope that provides access to the Window,
|
||||
// etc. that isn't protected by the fast paths in $parse. We explicitly request better
|
||||
// checks at the cost of speed since event handler expressions are not executed as
|
||||
// frequently as regular change detection.
|
||||
var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
|
||||
// NOTE:
|
||||
// We expose the powerful `$event` object on the scope that provides access to the Window,
|
||||
// etc. This is OK, because expressions are not sandboxed any more (and the expression
|
||||
// sandbox was never meant to be a security feature anyway).
|
||||
var fn = $parse(attr[directiveName]);
|
||||
return function ngEventHandler(scope, element) {
|
||||
element.on(eventName, function(event) {
|
||||
var callback = function() {
|
||||
fn(scope, {$event:event});
|
||||
fn(scope, {$event: event});
|
||||
};
|
||||
if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
|
||||
scope.$evalAsync(callback);
|
||||
|
||||
@@ -829,6 +829,29 @@ NgModelController.prototype = {
|
||||
that.$commitViewValue();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
*
|
||||
* @name ngModel.NgModelController#$overrideModelOptions
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* Override the current model options settings programmatically.
|
||||
*
|
||||
* The previous `ModelOptions` value will not be modified. Instead, a
|
||||
* new `ModelOptions` object will inherit from the previous one overriding
|
||||
* or inheriting settings that are defined in the given parameter.
|
||||
*
|
||||
* See {@link ngModelOptions} for information about what options can be specified
|
||||
* and how model option inheritance works.
|
||||
*
|
||||
* @param {Object} options a hash of settings to override the previous options
|
||||
*
|
||||
*/
|
||||
$overrideModelOptions: function(options) {
|
||||
this.$options = this.$options.createChild(options);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+224
-159
@@ -8,11 +8,13 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
* @multiElement
|
||||
*
|
||||
* @description
|
||||
* The `ngShow` directive shows or hides the given HTML element based on the expression
|
||||
* provided to the `ngShow` attribute. The element is shown or hidden by removing or adding
|
||||
* the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
|
||||
* in AngularJS and sets the display style to none (using an !important flag).
|
||||
* For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
|
||||
* The `ngShow` directive shows or hides the given HTML element based on the expression provided to
|
||||
* the `ngShow` attribute.
|
||||
*
|
||||
* The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
|
||||
* The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an
|
||||
* `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see
|
||||
* {@link ng.directive:ngCsp ngCsp}).
|
||||
*
|
||||
* ```html
|
||||
* <!-- when $scope.myValue is truthy (element is visible) -->
|
||||
@@ -22,31 +24,32 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
* <div ng-show="myValue" class="ng-hide"></div>
|
||||
* ```
|
||||
*
|
||||
* When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class
|
||||
* attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed
|
||||
* from the element causing the element not to appear hidden.
|
||||
* When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added
|
||||
* to the class attribute on the element causing it to become hidden. When truthy, the `.ng-hide`
|
||||
* CSS class is removed from the element causing the element not to appear hidden.
|
||||
*
|
||||
* ## Why is !important used?
|
||||
* ## Why is `!important` used?
|
||||
*
|
||||
* You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
|
||||
* can be easily overridden by heavier selectors. For example, something as simple
|
||||
* as changing the display style on a HTML list item would make hidden elements appear visible.
|
||||
* This also becomes a bigger issue when dealing with CSS frameworks.
|
||||
* You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the
|
||||
* `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as
|
||||
* simple as changing the display style on a HTML list item would make hidden elements appear
|
||||
* visible. This also becomes a bigger issue when dealing with CSS frameworks.
|
||||
*
|
||||
* By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
|
||||
* specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
|
||||
* styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
|
||||
* By using `!important`, the show and hide behavior will work as expected despite any clash between
|
||||
* CSS selector specificity (when `!important` isn't used with any conflicting styles). If a
|
||||
* developer chooses to override the styling to change how to hide an element then it is just a
|
||||
* matter of using `!important` in their own CSS code.
|
||||
*
|
||||
* ### Overriding `.ng-hide`
|
||||
*
|
||||
* By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
|
||||
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
|
||||
* class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
|
||||
* with extra animation classes that can be added.
|
||||
* By default, the `.ng-hide` class will style the element with `display: none !important`. If you
|
||||
* wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for
|
||||
* the `.ng-hide` CSS class. Note that the selector that needs to be used is actually
|
||||
* `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added.
|
||||
*
|
||||
* ```css
|
||||
* .ng-hide:not(.ng-hide-animate) {
|
||||
* /* this is just another form of hiding an element */
|
||||
* /* These are just alternative ways of hiding an element */
|
||||
* display: block!important;
|
||||
* position: absolute;
|
||||
* top: -9999px;
|
||||
@@ -54,29 +57,20 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* By default you don't need to override in CSS anything and the animations will work around the display style.
|
||||
* By default you don't need to override anything in CSS and the animations will work around the
|
||||
* display style.
|
||||
*
|
||||
* ## A note about animations with `ngShow`
|
||||
*
|
||||
* Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
|
||||
* is true and false. This system works like the animation system present with ngClass except that
|
||||
* you must also include the !important flag to override the display property
|
||||
* so that you can perform an animation when the element is hidden during the time of the animation.
|
||||
* Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the
|
||||
* directive expression is true and false. This system works like the animation system present with
|
||||
* `ngClass` except that you must also include the `!important` flag to override the display
|
||||
* property so that the elements are not actually hidden during the animation.
|
||||
*
|
||||
* ```css
|
||||
* //
|
||||
* //a working example can be found at the bottom of this page
|
||||
* //
|
||||
* /* A working example can be found at the bottom of this page. */
|
||||
* .my-element.ng-hide-add, .my-element.ng-hide-remove {
|
||||
* /* this is required as of 1.3x to properly
|
||||
* apply all styling in a show/hide animation */
|
||||
* transition: 0s linear all;
|
||||
* }
|
||||
*
|
||||
* .my-element.ng-hide-add-active,
|
||||
* .my-element.ng-hide-remove-active {
|
||||
* /* the transition is defined in the active class */
|
||||
* transition: 1s linear all;
|
||||
* transition: all 0.5s linear;
|
||||
* }
|
||||
*
|
||||
* .my-element.ng-hide-add { ... }
|
||||
@@ -85,76 +79,108 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
* .my-element.ng-hide-remove.ng-hide-remove-active { ... }
|
||||
* ```
|
||||
*
|
||||
* Keep in mind that, as of AngularJS version 1.3, there is no need to change the display
|
||||
* property to block during animation states--ngAnimate will handle the style toggling automatically for you.
|
||||
* Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property
|
||||
* to block during animation states - ngAnimate will automatically handle the style toggling for you.
|
||||
*
|
||||
* @animations
|
||||
* | Animation | Occurs |
|
||||
* |----------------------------------|-------------------------------------|
|
||||
* | {@link $animate#addClass addClass} `.ng-hide` | after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden |
|
||||
* | {@link $animate#removeClass removeClass} `.ng-hide` | after the `ngShow` expression evaluates to a truthy value and just before contents are set to visible |
|
||||
* | Animation | Occurs |
|
||||
* |-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
|
||||
* | {@link $animate#addClass addClass} `.ng-hide` | After the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden. |
|
||||
* | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngShow` expression evaluates to a truthy value and just before contents are set to visible. |
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngShow If the {@link guide/expression expression} is truthy
|
||||
* then the element is shown or hidden respectively.
|
||||
* @param {expression} ngShow If the {@link guide/expression expression} is truthy/falsy then the
|
||||
* element is shown/hidden respectively.
|
||||
*
|
||||
* @example
|
||||
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show">
|
||||
* A simple example, animating the element's opacity:
|
||||
*
|
||||
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-simple">
|
||||
<file name="index.html">
|
||||
Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/>
|
||||
<div>
|
||||
Show:
|
||||
<div class="check-element animate-show" ng-show="checked">
|
||||
<span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
|
||||
</div>
|
||||
Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br />
|
||||
<div class="check-element animate-show-hide" ng-show="checked">
|
||||
I show up when your checkbox is checked.
|
||||
</div>
|
||||
<div>
|
||||
Hide:
|
||||
<div class="check-element animate-show" ng-hide="checked">
|
||||
<span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
|
||||
</div>
|
||||
</div>
|
||||
</file>
|
||||
<file name="glyphicons.css">
|
||||
@import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
|
||||
</file>
|
||||
<file name="animations.css">
|
||||
.animate-show {
|
||||
line-height: 20px;
|
||||
opacity: 1;
|
||||
padding: 10px;
|
||||
border: 1px solid black;
|
||||
background: white;
|
||||
.animate-show-hide.ng-hide {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.animate-show.ng-hide-add, .animate-show.ng-hide-remove {
|
||||
.animate-show-hide.ng-hide-add,
|
||||
.animate-show-hide.ng-hide-remove {
|
||||
transition: all linear 0.5s;
|
||||
}
|
||||
|
||||
.animate-show.ng-hide {
|
||||
line-height: 0;
|
||||
opacity: 0;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.check-element {
|
||||
padding: 10px;
|
||||
border: 1px solid black;
|
||||
background: white;
|
||||
opacity: 1;
|
||||
padding: 10px;
|
||||
}
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
|
||||
var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
|
||||
it('should check ngShow', function() {
|
||||
var checkbox = element(by.model('checked'));
|
||||
var checkElem = element(by.css('.check-element'));
|
||||
|
||||
it('should check ng-show / ng-hide', function() {
|
||||
expect(thumbsUp.isDisplayed()).toBeFalsy();
|
||||
expect(thumbsDown.isDisplayed()).toBeTruthy();
|
||||
expect(checkElem.isDisplayed()).toBe(false);
|
||||
checkbox.click();
|
||||
expect(checkElem.isDisplayed()).toBe(true);
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
*
|
||||
* <hr />
|
||||
* @example
|
||||
* A more complex example, featuring different show/hide animations:
|
||||
*
|
||||
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-complex">
|
||||
<file name="index.html">
|
||||
Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br />
|
||||
<div class="check-element funky-show-hide" ng-show="checked">
|
||||
I show up when your checkbox is checked.
|
||||
</div>
|
||||
</file>
|
||||
<file name="animations.css">
|
||||
body {
|
||||
overflow: hidden;
|
||||
perspective: 1000px;
|
||||
}
|
||||
|
||||
element(by.model('checked')).click();
|
||||
.funky-show-hide.ng-hide-add {
|
||||
transform: rotateZ(0);
|
||||
transform-origin: right;
|
||||
transition: all 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
expect(thumbsUp.isDisplayed()).toBeTruthy();
|
||||
expect(thumbsDown.isDisplayed()).toBeFalsy();
|
||||
.funky-show-hide.ng-hide-add.ng-hide-add-active {
|
||||
transform: rotateZ(-135deg);
|
||||
}
|
||||
|
||||
.funky-show-hide.ng-hide-remove {
|
||||
transform: rotateY(90deg);
|
||||
transform-origin: left;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.funky-show-hide.ng-hide-remove.ng-hide-remove-active {
|
||||
transform: rotateY(0);
|
||||
}
|
||||
|
||||
.check-element {
|
||||
border: 1px solid black;
|
||||
opacity: 1;
|
||||
padding: 10px;
|
||||
}
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should check ngShow', function() {
|
||||
var checkbox = element(by.model('checked'));
|
||||
var checkElem = element(by.css('.check-element'));
|
||||
|
||||
expect(checkElem.isDisplayed()).toBe(false);
|
||||
checkbox.click();
|
||||
expect(checkElem.isDisplayed()).toBe(true);
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
@@ -184,11 +210,13 @@ var ngShowDirective = ['$animate', function($animate) {
|
||||
* @multiElement
|
||||
*
|
||||
* @description
|
||||
* The `ngHide` directive shows or hides the given HTML element based on the expression
|
||||
* provided to the `ngHide` attribute. The element is shown or hidden by removing or adding
|
||||
* the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined
|
||||
* in AngularJS and sets the display style to none (using an !important flag).
|
||||
* For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
|
||||
* The `ngHide` directive shows or hides the given HTML element based on the expression provided to
|
||||
* the `ngHide` attribute.
|
||||
*
|
||||
* The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
|
||||
* The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an
|
||||
* `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see
|
||||
* {@link ng.directive:ngCsp ngCsp}).
|
||||
*
|
||||
* ```html
|
||||
* <!-- when $scope.myValue is truthy (element is hidden) -->
|
||||
@@ -198,30 +226,32 @@ var ngShowDirective = ['$animate', function($animate) {
|
||||
* <div ng-hide="myValue"></div>
|
||||
* ```
|
||||
*
|
||||
* When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class
|
||||
* attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed
|
||||
* from the element causing the element not to appear hidden.
|
||||
* When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added
|
||||
* to the class attribute on the element causing it to become hidden. When falsy, the `.ng-hide`
|
||||
* CSS class is removed from the element causing the element not to appear hidden.
|
||||
*
|
||||
* ## Why is !important used?
|
||||
* ## Why is `!important` used?
|
||||
*
|
||||
* You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
|
||||
* can be easily overridden by heavier selectors. For example, something as simple
|
||||
* as changing the display style on a HTML list item would make hidden elements appear visible.
|
||||
* This also becomes a bigger issue when dealing with CSS frameworks.
|
||||
* You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the
|
||||
* `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as
|
||||
* simple as changing the display style on a HTML list item would make hidden elements appear
|
||||
* visible. This also becomes a bigger issue when dealing with CSS frameworks.
|
||||
*
|
||||
* By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
|
||||
* specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
|
||||
* styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
|
||||
* By using `!important`, the show and hide behavior will work as expected despite any clash between
|
||||
* CSS selector specificity (when `!important` isn't used with any conflicting styles). If a
|
||||
* developer chooses to override the styling to change how to hide an element then it is just a
|
||||
* matter of using `!important` in their own CSS code.
|
||||
*
|
||||
* ### Overriding `.ng-hide`
|
||||
*
|
||||
* By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
|
||||
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
|
||||
* class in CSS:
|
||||
* By default, the `.ng-hide` class will style the element with `display: none !important`. If you
|
||||
* wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for
|
||||
* the `.ng-hide` CSS class. Note that the selector that needs to be used is actually
|
||||
* `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added.
|
||||
*
|
||||
* ```css
|
||||
* .ng-hide {
|
||||
* /* this is just another form of hiding an element */
|
||||
* .ng-hide:not(.ng-hide-animate) {
|
||||
* /* These are just alternative ways of hiding an element */
|
||||
* display: block!important;
|
||||
* position: absolute;
|
||||
* top: -9999px;
|
||||
@@ -229,20 +259,20 @@ var ngShowDirective = ['$animate', function($animate) {
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* By default you don't need to override in CSS anything and the animations will work around the display style.
|
||||
* By default you don't need to override in CSS anything and the animations will work around the
|
||||
* display style.
|
||||
*
|
||||
* ## A note about animations with `ngHide`
|
||||
*
|
||||
* Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
|
||||
* is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide`
|
||||
* CSS class is added and removed for you instead of your own CSS class.
|
||||
* Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the
|
||||
* directive expression is true and false. This system works like the animation system present with
|
||||
* `ngClass` except that you must also include the `!important` flag to override the display
|
||||
* property so that the elements are not actually hidden during the animation.
|
||||
*
|
||||
* ```css
|
||||
* //
|
||||
* //a working example can be found at the bottom of this page
|
||||
* //
|
||||
* /* A working example can be found at the bottom of this page. */
|
||||
* .my-element.ng-hide-add, .my-element.ng-hide-remove {
|
||||
* transition: 0.5s linear all;
|
||||
* transition: all 0.5s linear;
|
||||
* }
|
||||
*
|
||||
* .my-element.ng-hide-add { ... }
|
||||
@@ -251,74 +281,109 @@ var ngShowDirective = ['$animate', function($animate) {
|
||||
* .my-element.ng-hide-remove.ng-hide-remove-active { ... }
|
||||
* ```
|
||||
*
|
||||
* Keep in mind that, as of AngularJS version 1.3, there is no need to change the display
|
||||
* property to block during animation states--ngAnimate will handle the style toggling automatically for you.
|
||||
* Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property
|
||||
* to block during animation states - ngAnimate will automatically handle the style toggling for you.
|
||||
*
|
||||
* @animations
|
||||
* | Animation | Occurs |
|
||||
* |----------------------------------|-------------------------------------|
|
||||
* | {@link $animate#addClass addClass} `.ng-hide` | after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden |
|
||||
* | {@link $animate#removeClass removeClass} `.ng-hide` | after the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible |
|
||||
* | Animation | Occurs |
|
||||
* |-----------------------------------------------------|------------------------------------------------------------------------------------------------------------|
|
||||
* | {@link $animate#addClass addClass} `.ng-hide` | After the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden. |
|
||||
* | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible. |
|
||||
*
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngHide If the {@link guide/expression expression} is truthy then
|
||||
* the element is shown or hidden respectively.
|
||||
* @param {expression} ngHide If the {@link guide/expression expression} is truthy/falsy then the
|
||||
* element is hidden/shown respectively.
|
||||
*
|
||||
* @example
|
||||
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide">
|
||||
* A simple example, animating the element's opacity:
|
||||
*
|
||||
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-simple">
|
||||
<file name="index.html">
|
||||
Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/>
|
||||
<div>
|
||||
Show:
|
||||
<div class="check-element animate-hide" ng-show="checked">
|
||||
<span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
|
||||
</div>
|
||||
Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br />
|
||||
<div class="check-element animate-show-hide" ng-hide="checked">
|
||||
I hide when your checkbox is checked.
|
||||
</div>
|
||||
<div>
|
||||
Hide:
|
||||
<div class="check-element animate-hide" ng-hide="checked">
|
||||
<span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
|
||||
</div>
|
||||
</div>
|
||||
</file>
|
||||
<file name="glyphicons.css">
|
||||
@import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
|
||||
</file>
|
||||
<file name="animations.css">
|
||||
.animate-hide {
|
||||
transition: all linear 0.5s;
|
||||
line-height: 20px;
|
||||
opacity: 1;
|
||||
padding: 10px;
|
||||
border: 1px solid black;
|
||||
background: white;
|
||||
.animate-show-hide.ng-hide {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.animate-hide.ng-hide {
|
||||
line-height: 0;
|
||||
opacity: 0;
|
||||
padding: 0 10px;
|
||||
.animate-show-hide.ng-hide-add,
|
||||
.animate-show-hide.ng-hide-remove {
|
||||
transition: all linear 0.5s;
|
||||
}
|
||||
|
||||
.check-element {
|
||||
padding: 10px;
|
||||
border: 1px solid black;
|
||||
background: white;
|
||||
opacity: 1;
|
||||
padding: 10px;
|
||||
}
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
|
||||
var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
|
||||
it('should check ngHide', function() {
|
||||
var checkbox = element(by.model('checked'));
|
||||
var checkElem = element(by.css('.check-element'));
|
||||
|
||||
it('should check ng-show / ng-hide', function() {
|
||||
expect(thumbsUp.isDisplayed()).toBeFalsy();
|
||||
expect(thumbsDown.isDisplayed()).toBeTruthy();
|
||||
expect(checkElem.isDisplayed()).toBe(true);
|
||||
checkbox.click();
|
||||
expect(checkElem.isDisplayed()).toBe(false);
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
*
|
||||
* <hr />
|
||||
* @example
|
||||
* A more complex example, featuring different show/hide animations:
|
||||
*
|
||||
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-complex">
|
||||
<file name="index.html">
|
||||
Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br />
|
||||
<div class="check-element funky-show-hide" ng-hide="checked">
|
||||
I hide when your checkbox is checked.
|
||||
</div>
|
||||
</file>
|
||||
<file name="animations.css">
|
||||
body {
|
||||
overflow: hidden;
|
||||
perspective: 1000px;
|
||||
}
|
||||
|
||||
element(by.model('checked')).click();
|
||||
.funky-show-hide.ng-hide-add {
|
||||
transform: rotateZ(0);
|
||||
transform-origin: right;
|
||||
transition: all 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
expect(thumbsUp.isDisplayed()).toBeTruthy();
|
||||
expect(thumbsDown.isDisplayed()).toBeFalsy();
|
||||
.funky-show-hide.ng-hide-add.ng-hide-add-active {
|
||||
transform: rotateZ(-135deg);
|
||||
}
|
||||
|
||||
.funky-show-hide.ng-hide-remove {
|
||||
transform: rotateY(90deg);
|
||||
transform-origin: left;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.funky-show-hide.ng-hide-remove.ng-hide-remove-active {
|
||||
transform: rotateY(0);
|
||||
}
|
||||
|
||||
.check-element {
|
||||
border: 1px solid black;
|
||||
opacity: 1;
|
||||
padding: 10px;
|
||||
}
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should check ngHide', function() {
|
||||
var checkbox = element(by.model('checked'));
|
||||
var checkElem = element(by.css('.check-element'));
|
||||
|
||||
expect(checkElem.isDisplayed()).toBe(true);
|
||||
checkbox.click();
|
||||
expect(checkElem.isDisplayed()).toBe(false);
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
|
||||
@@ -16,7 +16,7 @@ var SelectController =
|
||||
['$element', '$scope', /** @this */ function($element, $scope) {
|
||||
|
||||
var self = this,
|
||||
optionsMap = new HashMap();
|
||||
optionsMap = new NgMap();
|
||||
|
||||
self.selectValueMap = {}; // Keys are the hashed values, values the original values
|
||||
|
||||
@@ -137,7 +137,7 @@ var SelectController =
|
||||
self.emptyOption = element;
|
||||
}
|
||||
var count = optionsMap.get(value) || 0;
|
||||
optionsMap.put(value, count + 1);
|
||||
optionsMap.set(value, count + 1);
|
||||
// Only render at the end of a digest. This improves render performance when many options
|
||||
// are added during a digest and ensures all relevant options are correctly marked as selected
|
||||
scheduleRender();
|
||||
@@ -148,13 +148,13 @@ var SelectController =
|
||||
var count = optionsMap.get(value);
|
||||
if (count) {
|
||||
if (count === 1) {
|
||||
optionsMap.remove(value);
|
||||
optionsMap.delete(value);
|
||||
if (value === '') {
|
||||
self.hasEmptyOption = false;
|
||||
self.emptyOption = undefined;
|
||||
}
|
||||
} else {
|
||||
optionsMap.put(value, count - 1);
|
||||
optionsMap.set(value, count - 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -281,7 +281,7 @@ var SelectController =
|
||||
var removeValue = optionAttrs.value;
|
||||
|
||||
self.removeOption(removeValue);
|
||||
self.ngModelCtrl.$render();
|
||||
scheduleRender();
|
||||
|
||||
if (self.multiple && currentValue && currentValue.indexOf(removeValue) !== -1 ||
|
||||
currentValue === removeValue
|
||||
@@ -606,9 +606,9 @@ var selectDirective = function() {
|
||||
|
||||
// Write value now needs to set the selected property of each matching option
|
||||
selectCtrl.writeValue = function writeMultipleValue(value) {
|
||||
var items = new HashMap(value);
|
||||
forEach(element.find('option'), function(option) {
|
||||
option.selected = isDefined(items.get(option.value)) || isDefined(items.get(selectCtrl.selectValueMap[option.value]));
|
||||
option.selected = !!value && (includes(value, option.value) ||
|
||||
includes(value, selectCtrl.selectValueMap[option.value]));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+2
-1
@@ -1255,7 +1255,8 @@ function $HttpProvider() {
|
||||
if ((config.cache || defaults.cache) && config.cache !== false &&
|
||||
(config.method === 'GET' || config.method === 'JSONP')) {
|
||||
cache = isObject(config.cache) ? config.cache
|
||||
: isObject(defaults.cache) ? defaults.cache
|
||||
: isObject(/** @type {?} */ (defaults).cache)
|
||||
? /** @type {?} */ (defaults).cache
|
||||
: defaultCache;
|
||||
}
|
||||
|
||||
|
||||
+37
-26
@@ -137,6 +137,8 @@ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
|
||||
|
||||
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
|
||||
this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
|
||||
|
||||
this.$$urlUpdatedByLocation = true;
|
||||
};
|
||||
|
||||
this.$$parseLinkUrl = function(url, relHref) {
|
||||
@@ -214,7 +216,7 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
|
||||
withoutHashUrl = '';
|
||||
if (isUndefined(withoutBaseUrl)) {
|
||||
appBase = url;
|
||||
this.replace();
|
||||
/** @type {?} */ (this).replace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,6 +272,8 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
|
||||
|
||||
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
|
||||
this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
|
||||
|
||||
this.$$urlUpdatedByLocation = true;
|
||||
};
|
||||
|
||||
this.$$parseLinkUrl = function(url, relHref) {
|
||||
@@ -327,6 +331,8 @@ function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
|
||||
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
|
||||
// include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
|
||||
this.$$absUrl = appBase + hashPrefix + this.$$url;
|
||||
|
||||
this.$$urlUpdatedByLocation = true;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -656,6 +662,7 @@ forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], fun
|
||||
// but we're changing the $$state reference to $browser.state() during the $digest
|
||||
// so the modification window is narrow.
|
||||
this.$$state = isUndefined(state) ? null : state;
|
||||
this.$$urlUpdatedByLocation = true;
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -968,36 +975,40 @@ function $LocationProvider() {
|
||||
|
||||
// update browser
|
||||
$rootScope.$watch(function $locationWatch() {
|
||||
var oldUrl = trimEmptyHash($browser.url());
|
||||
var newUrl = trimEmptyHash($location.absUrl());
|
||||
var oldState = $browser.state();
|
||||
var currentReplace = $location.$$replace;
|
||||
var urlOrStateChanged = oldUrl !== newUrl ||
|
||||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
|
||||
if (initializing || $location.$$urlUpdatedByLocation) {
|
||||
$location.$$urlUpdatedByLocation = false;
|
||||
|
||||
if (initializing || urlOrStateChanged) {
|
||||
initializing = false;
|
||||
var oldUrl = trimEmptyHash($browser.url());
|
||||
var newUrl = trimEmptyHash($location.absUrl());
|
||||
var oldState = $browser.state();
|
||||
var currentReplace = $location.$$replace;
|
||||
var urlOrStateChanged = oldUrl !== newUrl ||
|
||||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
|
||||
|
||||
$rootScope.$evalAsync(function() {
|
||||
var newUrl = $location.absUrl();
|
||||
var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
|
||||
$location.$$state, oldState).defaultPrevented;
|
||||
if (initializing || urlOrStateChanged) {
|
||||
initializing = false;
|
||||
|
||||
// if the location was changed by a `$locationChangeStart` handler then stop
|
||||
// processing this location change
|
||||
if ($location.absUrl() !== newUrl) return;
|
||||
$rootScope.$evalAsync(function() {
|
||||
var newUrl = $location.absUrl();
|
||||
var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
|
||||
$location.$$state, oldState).defaultPrevented;
|
||||
|
||||
if (defaultPrevented) {
|
||||
$location.$$parse(oldUrl);
|
||||
$location.$$state = oldState;
|
||||
} else {
|
||||
if (urlOrStateChanged) {
|
||||
setBrowserUrlWithFallback(newUrl, currentReplace,
|
||||
oldState === $location.$$state ? null : $location.$$state);
|
||||
// if the location was changed by a `$locationChangeStart` handler then stop
|
||||
// processing this location change
|
||||
if ($location.absUrl() !== newUrl) return;
|
||||
|
||||
if (defaultPrevented) {
|
||||
$location.$$parse(oldUrl);
|
||||
$location.$$state = oldState;
|
||||
} else {
|
||||
if (urlOrStateChanged) {
|
||||
setBrowserUrlWithFallback(newUrl, currentReplace,
|
||||
oldState === $location.$$state ? null : $location.$$state);
|
||||
}
|
||||
afterLocationChange(oldUrl, oldState);
|
||||
}
|
||||
afterLocationChange(oldUrl, oldState);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$location.$$replace = false;
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ function $LogProvider() {
|
||||
this.debugEnabled = function(flag) {
|
||||
if (isDefined(flag)) {
|
||||
debug = flag;
|
||||
return this;
|
||||
return this;
|
||||
} else {
|
||||
return debug;
|
||||
}
|
||||
|
||||
+11
-4
@@ -717,6 +717,13 @@ function findConstantAndWatchExpressions(ast, $filter) {
|
||||
if (!property.value.constant) {
|
||||
argsToWatch.push.apply(argsToWatch, property.value.toWatch);
|
||||
}
|
||||
if (property.computed) {
|
||||
findConstantAndWatchExpressions(property.key, $filter);
|
||||
if (!property.key.constant) {
|
||||
argsToWatch.push.apply(argsToWatch, property.key.toWatch);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
ast.constant = allConstants;
|
||||
ast.toWatch = argsToWatch;
|
||||
@@ -1782,13 +1789,13 @@ function $ParseProvider() {
|
||||
}
|
||||
}
|
||||
|
||||
function expressionInputDirtyCheck(newValue, oldValueOfValue) {
|
||||
function expressionInputDirtyCheck(newValue, oldValueOfValue, compareObjectIdentity) {
|
||||
|
||||
if (newValue == null || oldValueOfValue == null) { // null/undefined
|
||||
return newValue === oldValueOfValue;
|
||||
}
|
||||
|
||||
if (typeof newValue === 'object') {
|
||||
if (typeof newValue === 'object' && !compareObjectIdentity) {
|
||||
|
||||
// attempt to convert the value to a primitive type
|
||||
// TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
|
||||
@@ -1817,7 +1824,7 @@ function $ParseProvider() {
|
||||
inputExpressions = inputExpressions[0];
|
||||
return scope.$watch(function expressionInputWatch(scope) {
|
||||
var newInputValue = inputExpressions(scope);
|
||||
if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) {
|
||||
if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, parsedExpression.literal)) {
|
||||
lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
|
||||
oldInputValueOf = newInputValue && getValueOf(newInputValue);
|
||||
}
|
||||
@@ -1837,7 +1844,7 @@ function $ParseProvider() {
|
||||
|
||||
for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
|
||||
var newInputValue = inputExpressions[i](scope);
|
||||
if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) {
|
||||
if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], parsedExpression.literal))) {
|
||||
oldInputValues[i] = newInputValue;
|
||||
oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
|
||||
}
|
||||
|
||||
@@ -876,6 +876,10 @@ function $RootScopeProvider() {
|
||||
}
|
||||
}
|
||||
postDigestQueue.length = postDigestQueuePosition = 0;
|
||||
|
||||
// Check for changes to browser url that happened during the $digest
|
||||
// (for which no event is fired; e.g. via `history.pushState()`)
|
||||
$browser.$$checkUrlChange();
|
||||
},
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,10 @@ function $SnifferProvider() {
|
||||
// (see https://developer.chrome.com/apps/api_index). If sandboxed, they can be detected by
|
||||
// the presence of an extension runtime ID and the absence of other Chrome runtime APIs
|
||||
// (see https://developer.chrome.com/apps/manifest/sandbox).
|
||||
// (NW.js apps have access to Chrome APIs, but do support `history`.)
|
||||
isNw = $window.nw && $window.nw.process,
|
||||
isChromePackagedApp =
|
||||
!isNw &&
|
||||
$window.chrome &&
|
||||
($window.chrome.app && $window.chrome.app.runtime ||
|
||||
!$window.chrome.app && $window.chrome.runtime && $window.chrome.runtime.id),
|
||||
|
||||
@@ -36,9 +36,9 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
}
|
||||
}
|
||||
|
||||
function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
|
||||
function isAllowed(ruleType, currentAnimation, previousAnimation) {
|
||||
return rules[ruleType].some(function(fn) {
|
||||
return fn(element, currentAnimation, previousAnimation);
|
||||
return fn(currentAnimation, previousAnimation);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,40 +48,40 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
return and ? a && b : a || b;
|
||||
}
|
||||
|
||||
rules.join.push(function(element, newAnimation, currentAnimation) {
|
||||
rules.join.push(function(newAnimation, currentAnimation) {
|
||||
// if the new animation is class-based then we can just tack that on
|
||||
return !newAnimation.structural && hasAnimationClasses(newAnimation);
|
||||
});
|
||||
|
||||
rules.skip.push(function(element, newAnimation, currentAnimation) {
|
||||
rules.skip.push(function(newAnimation, currentAnimation) {
|
||||
// there is no need to animate anything if no classes are being added and
|
||||
// there is no structural animation that will be triggered
|
||||
return !newAnimation.structural && !hasAnimationClasses(newAnimation);
|
||||
});
|
||||
|
||||
rules.skip.push(function(element, newAnimation, currentAnimation) {
|
||||
rules.skip.push(function(newAnimation, currentAnimation) {
|
||||
// why should we trigger a new structural animation if the element will
|
||||
// be removed from the DOM anyway?
|
||||
return currentAnimation.event === 'leave' && newAnimation.structural;
|
||||
});
|
||||
|
||||
rules.skip.push(function(element, newAnimation, currentAnimation) {
|
||||
rules.skip.push(function(newAnimation, currentAnimation) {
|
||||
// if there is an ongoing current animation then don't even bother running the class-based animation
|
||||
return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
|
||||
});
|
||||
|
||||
rules.cancel.push(function(element, newAnimation, currentAnimation) {
|
||||
rules.cancel.push(function(newAnimation, currentAnimation) {
|
||||
// there can never be two structural animations running at the same time
|
||||
return currentAnimation.structural && newAnimation.structural;
|
||||
});
|
||||
|
||||
rules.cancel.push(function(element, newAnimation, currentAnimation) {
|
||||
rules.cancel.push(function(newAnimation, currentAnimation) {
|
||||
// if the previous animation is already running, but the new animation will
|
||||
// be triggered, but the new animation is structural
|
||||
return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
|
||||
});
|
||||
|
||||
rules.cancel.push(function(element, newAnimation, currentAnimation) {
|
||||
rules.cancel.push(function(newAnimation, currentAnimation) {
|
||||
// cancel the animation if classes added / removed in both animation cancel each other out,
|
||||
// but only if the current animation isn't structural
|
||||
|
||||
@@ -100,15 +100,15 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA);
|
||||
});
|
||||
|
||||
this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
|
||||
this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$Map',
|
||||
'$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
|
||||
'$$isDocumentHidden',
|
||||
function($$rAF, $rootScope, $rootElement, $document, $$HashMap,
|
||||
function($$rAF, $rootScope, $rootElement, $document, $$Map,
|
||||
$$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow,
|
||||
$$isDocumentHidden) {
|
||||
|
||||
var activeAnimationsLookup = new $$HashMap();
|
||||
var disabledElementsLookup = new $$HashMap();
|
||||
var activeAnimationsLookup = new $$Map();
|
||||
var disabledElementsLookup = new $$Map();
|
||||
var animationsEnabled = null;
|
||||
|
||||
function postDigestTaskFactory() {
|
||||
@@ -181,10 +181,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
return this === arg || !!(this.compareDocumentPosition(arg) & 16);
|
||||
};
|
||||
|
||||
function findCallbacks(parent, element, event) {
|
||||
var targetNode = getDomNode(element);
|
||||
var targetParentNode = getDomNode(parent);
|
||||
|
||||
function findCallbacks(targetParentNode, targetNode, event) {
|
||||
var matches = [];
|
||||
var entries = callbackRegistry[event];
|
||||
if (entries) {
|
||||
@@ -209,11 +206,11 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
});
|
||||
}
|
||||
|
||||
function cleanupEventListeners(phase, element) {
|
||||
if (phase === 'close' && !element[0].parentNode) {
|
||||
function cleanupEventListeners(phase, node) {
|
||||
if (phase === 'close' && !node.parentNode) {
|
||||
// If the element is not attached to a parentNode, it has been removed by
|
||||
// the domOperation, and we can safely remove the event callbacks
|
||||
$animate.off(element);
|
||||
$animate.off(node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +291,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
bool = !disabledElementsLookup.get(node);
|
||||
} else {
|
||||
// (element, bool) - Element setter
|
||||
disabledElementsLookup.put(node, !bool);
|
||||
disabledElementsLookup.set(node, !bool);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,18 +302,15 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
|
||||
return $animate;
|
||||
|
||||
function queueAnimation(element, event, initialOptions) {
|
||||
function queueAnimation(originalElement, event, initialOptions) {
|
||||
// we always make a copy of the options since
|
||||
// there should never be any side effects on
|
||||
// the input data when running `$animateCss`.
|
||||
var options = copy(initialOptions);
|
||||
|
||||
var node, parent;
|
||||
element = stripCommentsFromElement(element);
|
||||
if (element) {
|
||||
node = getDomNode(element);
|
||||
parent = element.parent();
|
||||
}
|
||||
var element = stripCommentsFromElement(originalElement);
|
||||
var node = getDomNode(element);
|
||||
var parentNode = node && node.parentNode;
|
||||
|
||||
options = prepareAnimationOptions(options);
|
||||
|
||||
@@ -381,7 +375,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
// there is no point in traversing the same collection of parent ancestors if a followup
|
||||
// animation will be run on the same element that already did all that checking work
|
||||
if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state !== PRE_DIGEST_STATE)) {
|
||||
skipAnimations = !areAnimationsAllowed(element, parent, event);
|
||||
skipAnimations = !areAnimationsAllowed(node, parentNode, event);
|
||||
}
|
||||
|
||||
if (skipAnimations) {
|
||||
@@ -393,7 +387,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
}
|
||||
|
||||
if (isStructural) {
|
||||
closeChildAnimations(element);
|
||||
closeChildAnimations(node);
|
||||
}
|
||||
|
||||
var newAnimation = {
|
||||
@@ -408,7 +402,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
};
|
||||
|
||||
if (hasExistingAnimation) {
|
||||
var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation);
|
||||
var skipAnimationFlag = isAllowed('skip', newAnimation, existingAnimation);
|
||||
if (skipAnimationFlag) {
|
||||
if (existingAnimation.state === RUNNING_STATE) {
|
||||
close();
|
||||
@@ -418,7 +412,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
return existingAnimation.runner;
|
||||
}
|
||||
}
|
||||
var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
|
||||
var cancelAnimationFlag = isAllowed('cancel', newAnimation, existingAnimation);
|
||||
if (cancelAnimationFlag) {
|
||||
if (existingAnimation.state === RUNNING_STATE) {
|
||||
// this will end the animation right away and it is safe
|
||||
@@ -440,7 +434,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
// a joined animation means that this animation will take over the existing one
|
||||
// so an example would involve a leave animation taking over an enter. Then when
|
||||
// the postDigest kicks in the enter will be ignored.
|
||||
var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
|
||||
var joinAnimationFlag = isAllowed('join', newAnimation, existingAnimation);
|
||||
if (joinAnimationFlag) {
|
||||
if (existingAnimation.state === RUNNING_STATE) {
|
||||
normalizeAnimationDetails(element, newAnimation);
|
||||
@@ -474,7 +468,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
|
||||
if (!isValidAnimation) {
|
||||
close();
|
||||
clearElementAnimationState(element);
|
||||
clearElementAnimationState(node);
|
||||
return runner;
|
||||
}
|
||||
|
||||
@@ -482,9 +476,18 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
var counter = (existingAnimation.counter || 0) + 1;
|
||||
newAnimation.counter = counter;
|
||||
|
||||
markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation);
|
||||
markElementAnimationState(node, PRE_DIGEST_STATE, newAnimation);
|
||||
|
||||
$rootScope.$$postDigest(function() {
|
||||
// It is possible that the DOM nodes inside `originalElement` have been replaced. This can
|
||||
// happen if the animated element is a transcluded clone and also has a `templateUrl`
|
||||
// directive on it. Therefore, we must recreate `element` in order to interact with the
|
||||
// actual DOM nodes.
|
||||
// Note: We still need to use the old `node` for certain things, such as looking up in
|
||||
// HashMaps where it was used as the key.
|
||||
|
||||
element = stripCommentsFromElement(originalElement);
|
||||
|
||||
var animationDetails = activeAnimationsLookup.get(node);
|
||||
var animationCancelled = !animationDetails;
|
||||
animationDetails = animationDetails || {};
|
||||
@@ -523,7 +526,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
// isn't allowed to animate from here then we need to clear the state of the element
|
||||
// so that any future animations won't read the expired animation data.
|
||||
if (!isValidAnimation) {
|
||||
clearElementAnimationState(element);
|
||||
clearElementAnimationState(node);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -535,7 +538,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
? 'setClass'
|
||||
: animationDetails.event;
|
||||
|
||||
markElementAnimationState(element, RUNNING_STATE);
|
||||
markElementAnimationState(node, RUNNING_STATE);
|
||||
var realRunner = $$animation(element, event, animationDetails.options);
|
||||
|
||||
// this will update the runner's flow-control events based on
|
||||
@@ -547,7 +550,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
close(!status);
|
||||
var animationDetails = activeAnimationsLookup.get(node);
|
||||
if (animationDetails && animationDetails.counter === counter) {
|
||||
clearElementAnimationState(getDomNode(element));
|
||||
clearElementAnimationState(node);
|
||||
}
|
||||
notifyProgress(runner, event, 'close', {});
|
||||
});
|
||||
@@ -557,7 +560,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
|
||||
function notifyProgress(runner, event, phase, data) {
|
||||
runInNextPostDigestOrNow(function() {
|
||||
var callbacks = findCallbacks(parent, element, event);
|
||||
var callbacks = findCallbacks(parentNode, node, event);
|
||||
if (callbacks.length) {
|
||||
// do not optimize this call here to RAF because
|
||||
// we don't know how heavy the callback code here will
|
||||
@@ -567,10 +570,10 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
forEach(callbacks, function(callback) {
|
||||
callback(element, phase, data);
|
||||
});
|
||||
cleanupEventListeners(phase, element);
|
||||
cleanupEventListeners(phase, node);
|
||||
});
|
||||
} else {
|
||||
cleanupEventListeners(phase, element);
|
||||
cleanupEventListeners(phase, node);
|
||||
}
|
||||
});
|
||||
runner.progress(event, phase, data);
|
||||
@@ -585,8 +588,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
}
|
||||
}
|
||||
|
||||
function closeChildAnimations(element) {
|
||||
var node = getDomNode(element);
|
||||
function closeChildAnimations(node) {
|
||||
var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
|
||||
forEach(children, function(child) {
|
||||
var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME), 10);
|
||||
@@ -597,21 +599,16 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
animationDetails.runner.end();
|
||||
/* falls through */
|
||||
case PRE_DIGEST_STATE:
|
||||
activeAnimationsLookup.remove(child);
|
||||
activeAnimationsLookup.delete(child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearElementAnimationState(element) {
|
||||
var node = getDomNode(element);
|
||||
function clearElementAnimationState(node) {
|
||||
node.removeAttribute(NG_ANIMATE_ATTR_NAME);
|
||||
activeAnimationsLookup.remove(node);
|
||||
}
|
||||
|
||||
function isMatchingElement(nodeOrElmA, nodeOrElmB) {
|
||||
return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
|
||||
activeAnimationsLookup.delete(node);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -621,54 +618,54 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
* c) the element is not a child of the body
|
||||
* d) the element is not a child of the $rootElement
|
||||
*/
|
||||
function areAnimationsAllowed(element, parentElement, event) {
|
||||
var bodyElement = jqLite($document[0].body);
|
||||
var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
|
||||
var rootElementDetected = isMatchingElement(element, $rootElement);
|
||||
var parentAnimationDetected = false;
|
||||
var animateChildren;
|
||||
var elementDisabled = disabledElementsLookup.get(getDomNode(element));
|
||||
function areAnimationsAllowed(node, parentNode, event) {
|
||||
var bodyNode = $document[0].body;
|
||||
var rootNode = getDomNode($rootElement);
|
||||
|
||||
var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA);
|
||||
var bodyNodeDetected = (node === bodyNode) || node.nodeName === 'HTML';
|
||||
var rootNodeDetected = (node === rootNode);
|
||||
var parentAnimationDetected = false;
|
||||
var elementDisabled = disabledElementsLookup.get(node);
|
||||
var animateChildren;
|
||||
|
||||
var parentHost = jqLite.data(node, NG_ANIMATE_PIN_DATA);
|
||||
if (parentHost) {
|
||||
parentElement = parentHost;
|
||||
parentNode = getDomNode(parentHost);
|
||||
}
|
||||
|
||||
parentElement = getDomNode(parentElement);
|
||||
|
||||
while (parentElement) {
|
||||
if (!rootElementDetected) {
|
||||
while (parentNode) {
|
||||
if (!rootNodeDetected) {
|
||||
// angular doesn't want to attempt to animate elements outside of the application
|
||||
// therefore we need to ensure that the rootElement is an ancestor of the current element
|
||||
rootElementDetected = isMatchingElement(parentElement, $rootElement);
|
||||
rootNodeDetected = (parentNode === rootNode);
|
||||
}
|
||||
|
||||
if (parentElement.nodeType !== ELEMENT_NODE) {
|
||||
if (parentNode.nodeType !== ELEMENT_NODE) {
|
||||
// no point in inspecting the #document element
|
||||
break;
|
||||
}
|
||||
|
||||
var details = activeAnimationsLookup.get(parentElement) || {};
|
||||
var details = activeAnimationsLookup.get(parentNode) || {};
|
||||
// either an enter, leave or move animation will commence
|
||||
// therefore we can't allow any animations to take place
|
||||
// but if a parent animation is class-based then that's ok
|
||||
if (!parentAnimationDetected) {
|
||||
var parentElementDisabled = disabledElementsLookup.get(parentElement);
|
||||
var parentNodeDisabled = disabledElementsLookup.get(parentNode);
|
||||
|
||||
if (parentElementDisabled === true && elementDisabled !== false) {
|
||||
if (parentNodeDisabled === true && elementDisabled !== false) {
|
||||
// disable animations if the user hasn't explicitly enabled animations on the
|
||||
// current element
|
||||
elementDisabled = true;
|
||||
// element is disabled via parent element, no need to check anything else
|
||||
break;
|
||||
} else if (parentElementDisabled === false) {
|
||||
} else if (parentNodeDisabled === false) {
|
||||
elementDisabled = false;
|
||||
}
|
||||
parentAnimationDetected = details.structural;
|
||||
}
|
||||
|
||||
if (isUndefined(animateChildren) || animateChildren === true) {
|
||||
var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA);
|
||||
var value = jqLite.data(parentNode, NG_ANIMATE_CHILDREN_DATA);
|
||||
if (isDefined(value)) {
|
||||
animateChildren = value;
|
||||
}
|
||||
@@ -677,47 +674,46 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
|
||||
// there is no need to continue traversing at this point
|
||||
if (parentAnimationDetected && animateChildren === false) break;
|
||||
|
||||
if (!bodyElementDetected) {
|
||||
if (!bodyNodeDetected) {
|
||||
// we also need to ensure that the element is or will be a part of the body element
|
||||
// otherwise it is pointless to even issue an animation to be rendered
|
||||
bodyElementDetected = isMatchingElement(parentElement, bodyElement);
|
||||
bodyNodeDetected = (parentNode === bodyNode);
|
||||
}
|
||||
|
||||
if (bodyElementDetected && rootElementDetected) {
|
||||
if (bodyNodeDetected && rootNodeDetected) {
|
||||
// If both body and root have been found, any other checks are pointless,
|
||||
// as no animation data should live outside the application
|
||||
break;
|
||||
}
|
||||
|
||||
if (!rootElementDetected) {
|
||||
// If no rootElement is detected, check if the parentElement is pinned to another element
|
||||
parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA);
|
||||
if (!rootNodeDetected) {
|
||||
// If `rootNode` is not detected, check if `parentNode` is pinned to another element
|
||||
parentHost = jqLite.data(parentNode, NG_ANIMATE_PIN_DATA);
|
||||
if (parentHost) {
|
||||
// The pin target element becomes the next parent element
|
||||
parentElement = getDomNode(parentHost);
|
||||
parentNode = getDomNode(parentHost);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
parentElement = parentElement.parentNode;
|
||||
parentNode = parentNode.parentNode;
|
||||
}
|
||||
|
||||
var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
|
||||
return allowAnimation && rootElementDetected && bodyElementDetected;
|
||||
return allowAnimation && rootNodeDetected && bodyNodeDetected;
|
||||
}
|
||||
|
||||
function markElementAnimationState(element, state, details) {
|
||||
function markElementAnimationState(node, state, details) {
|
||||
details = details || {};
|
||||
details.state = state;
|
||||
|
||||
var node = getDomNode(element);
|
||||
node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
|
||||
|
||||
var oldValue = activeAnimationsLookup.get(node);
|
||||
var newValue = oldValue
|
||||
? extend(oldValue, details)
|
||||
: details;
|
||||
activeAnimationsLookup.put(node, newValue);
|
||||
activeAnimationsLookup.set(node, newValue);
|
||||
}
|
||||
}];
|
||||
}];
|
||||
|
||||
@@ -21,21 +21,21 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
return element.data(RUNNER_STORAGE_KEY);
|
||||
}
|
||||
|
||||
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
|
||||
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) {
|
||||
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$Map', '$$rAFScheduler',
|
||||
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$Map, $$rAFScheduler) {
|
||||
|
||||
var animationQueue = [];
|
||||
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
||||
|
||||
function sortAnimations(animations) {
|
||||
var tree = { children: [] };
|
||||
var i, lookup = new $$HashMap();
|
||||
var i, lookup = new $$Map();
|
||||
|
||||
// this is done first beforehand so that the hashmap
|
||||
// this is done first beforehand so that the map
|
||||
// is filled with a list of the elements that will be animated
|
||||
for (i = 0; i < animations.length; i++) {
|
||||
var animation = animations[i];
|
||||
lookup.put(animation.domNode, animations[i] = {
|
||||
lookup.set(animation.domNode, animations[i] = {
|
||||
domNode: animation.domNode,
|
||||
fn: animation.fn,
|
||||
children: []
|
||||
@@ -54,7 +54,7 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
|
||||
|
||||
var elementNode = entry.domNode;
|
||||
var parentNode = elementNode.parentNode;
|
||||
lookup.put(elementNode, entry);
|
||||
lookup.set(elementNode, entry);
|
||||
|
||||
var parentEntry;
|
||||
while (parentNode) {
|
||||
|
||||
+1
-1
@@ -355,7 +355,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function(elem, attr) {
|
||||
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
|
||||
var fn = $parse(attr.ngClick);
|
||||
return function(scope, elem, attr) {
|
||||
|
||||
if (!isNodeOneOf(elem, nodeBlackList)) {
|
||||
|
||||
Vendored
+35
-20
@@ -37,10 +37,30 @@ angular.mock.$Browser = function() {
|
||||
self.$$lastUrl = self.$$url; // used by url polling fn
|
||||
self.pollFns = [];
|
||||
|
||||
// TODO(vojta): remove this temporary api
|
||||
self.$$completeOutstandingRequest = angular.noop;
|
||||
self.$$incOutstandingRequestCount = angular.noop;
|
||||
// Testability API
|
||||
|
||||
var outstandingRequestCount = 0;
|
||||
var outstandingRequestCallbacks = [];
|
||||
self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
|
||||
self.$$completeOutstandingRequest = function(fn) {
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
outstandingRequestCount--;
|
||||
if (!outstandingRequestCount) {
|
||||
while (outstandingRequestCallbacks.length) {
|
||||
outstandingRequestCallbacks.pop()();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
self.notifyWhenNoOutstandingRequests = function(callback) {
|
||||
if (outstandingRequestCount) {
|
||||
outstandingRequestCallbacks.push(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// register url polling fn
|
||||
|
||||
@@ -65,6 +85,8 @@ angular.mock.$Browser = function() {
|
||||
self.deferredNextId = 0;
|
||||
|
||||
self.defer = function(fn, delay) {
|
||||
// Note that we do not use `$$incOutstandingRequestCount` or `$$completeOutstandingRequest`
|
||||
// in this mock implementation.
|
||||
delay = delay || 0;
|
||||
self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId});
|
||||
self.deferredFns.sort(function(a, b) { return a.time - b.time;});
|
||||
@@ -166,10 +188,6 @@ angular.mock.$Browser.prototype = {
|
||||
|
||||
state: function() {
|
||||
return this.$$state;
|
||||
},
|
||||
|
||||
notifyWhenNoOutstandingRequests: function(fn) {
|
||||
fn();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1295,9 +1313,8 @@ angular.mock.dump = function(object) {
|
||||
});
|
||||
```
|
||||
*/
|
||||
angular.mock.$HttpBackendProvider = function() {
|
||||
this.$get = ['$rootScope', '$timeout', createHttpBackendMock];
|
||||
};
|
||||
angular.mock.$httpBackendDecorator =
|
||||
['$rootScope', '$timeout', '$delegate', createHttpBackendMock];
|
||||
|
||||
/**
|
||||
* General factory function for $httpBackend mock.
|
||||
@@ -1318,7 +1335,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
expectations = [],
|
||||
responses = [],
|
||||
responsesPush = angular.bind(responses, responses.push),
|
||||
copy = angular.copy;
|
||||
copy = angular.copy,
|
||||
// We cache the original backend so that if both ngMock and ngMockE2E override the
|
||||
// service the ngMockE2E version can pass through to the real backend
|
||||
originalHttpBackend = $delegate.$$originalHttpBackend || $delegate;
|
||||
|
||||
function createResponse(status, data, headers, statusText) {
|
||||
if (angular.isFunction(status)) return status;
|
||||
@@ -1403,7 +1423,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
// if $browser specified, we do auto flush all requests
|
||||
($browser ? $browser.defer : responsesPush)(wrapResponse(definition));
|
||||
} else if (definition.passThrough) {
|
||||
$delegate(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers);
|
||||
originalHttpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers);
|
||||
} else throw new Error('No response defined !');
|
||||
return;
|
||||
}
|
||||
@@ -1879,6 +1899,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
responses.length = 0;
|
||||
};
|
||||
|
||||
$httpBackend.$$originalHttpBackend = originalHttpBackend;
|
||||
|
||||
return $httpBackend;
|
||||
|
||||
|
||||
@@ -2376,7 +2398,6 @@ angular.module('ngMock', ['ng']).provider({
|
||||
$exceptionHandler: angular.mock.$ExceptionHandlerProvider,
|
||||
$log: angular.mock.$LogProvider,
|
||||
$interval: angular.mock.$IntervalProvider,
|
||||
$httpBackend: angular.mock.$HttpBackendProvider,
|
||||
$rootElement: angular.mock.$RootElementProvider,
|
||||
$componentController: angular.mock.$ComponentControllerProvider
|
||||
}).config(['$provide', '$compileProvider', function($provide, $compileProvider) {
|
||||
@@ -2384,6 +2405,7 @@ angular.module('ngMock', ['ng']).provider({
|
||||
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
|
||||
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
|
||||
$provide.decorator('$controller', createControllerDecorator($compileProvider));
|
||||
$provide.decorator('$httpBackend', angular.mock.$httpBackendDecorator);
|
||||
}]);
|
||||
|
||||
/**
|
||||
@@ -2398,7 +2420,6 @@ angular.module('ngMock', ['ng']).provider({
|
||||
* the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock.
|
||||
*/
|
||||
angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||
$provide.value('$httpBackend', angular.injector(['ng']).get('$httpBackend'));
|
||||
$provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
|
||||
}]);
|
||||
|
||||
@@ -2964,12 +2985,6 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
|
||||
delete fn.$inject;
|
||||
});
|
||||
|
||||
angular.forEach(currentSpec.$modules, function(module) {
|
||||
if (module && module.$$hashKey) {
|
||||
module.$$hashKey = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
currentSpec.$injector = null;
|
||||
currentSpec.$modules = null;
|
||||
currentSpec.$providerInjector = null;
|
||||
|
||||
+16
-14
@@ -590,11 +590,12 @@ angular.module('ngResource', ['ng']).
|
||||
url = url.replace(/\/+$/, '') || '/';
|
||||
}
|
||||
|
||||
// then replace collapse `/.` if found in the last URL path segment before the query
|
||||
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
|
||||
// Collapse `/.` if found in the last URL path segment before the query.
|
||||
// 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 = protocolAndIpv6 + url.replace(/\/\\\./, '/.');
|
||||
// Replace escaped `/\.` with `/.`.
|
||||
// (If `\.` comes from a param value, it will be encoded as `%5C.`.)
|
||||
config.url = protocolAndIpv6 + url.replace(/\/(\\|%5C)\./, '/.');
|
||||
|
||||
|
||||
// set params - delegate param encoding to $http
|
||||
@@ -636,6 +637,7 @@ angular.module('ngResource', ['ng']).
|
||||
var data = extend({}, this);
|
||||
delete data.$promise;
|
||||
delete data.$resolved;
|
||||
delete data.$cancelRequest;
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -783,18 +785,18 @@ angular.module('ngResource', ['ng']).
|
||||
return value;
|
||||
},
|
||||
(hasError || hasResponseErrorInterceptor) ?
|
||||
function(response) {
|
||||
if (hasError) error(response);
|
||||
return hasResponseErrorInterceptor ?
|
||||
function(response) {
|
||||
if (hasError && !hasResponseErrorInterceptor) {
|
||||
// Avoid `Possibly Unhandled Rejection` error,
|
||||
// but still fulfill the returned promise with a rejection
|
||||
promise.catch(noop);
|
||||
}
|
||||
if (hasError) error(response);
|
||||
return hasResponseErrorInterceptor ?
|
||||
responseErrorInterceptor(response) :
|
||||
$q.reject(response);
|
||||
} :
|
||||
undefined);
|
||||
if (hasError && !hasResponseErrorInterceptor) {
|
||||
// Avoid `Possibly Unhandled Rejection` error,
|
||||
// but still fulfill the returned promise with a rejection
|
||||
promise.catch(noop);
|
||||
}
|
||||
} :
|
||||
undefined);
|
||||
|
||||
if (!isInstanceCall) {
|
||||
// we are creating instance / collection
|
||||
|
||||
+13
-1
@@ -7,6 +7,7 @@
|
||||
var isArray;
|
||||
var isObject;
|
||||
var isDefined;
|
||||
var noop;
|
||||
|
||||
/**
|
||||
* @ngdoc module
|
||||
@@ -54,6 +55,7 @@ function $RouteProvider() {
|
||||
isArray = angular.isArray;
|
||||
isObject = angular.isObject;
|
||||
isDefined = angular.isDefined;
|
||||
noop = angular.noop;
|
||||
|
||||
function inherit(parent, extra) {
|
||||
return angular.extend(Object.create(parent), extra);
|
||||
@@ -350,7 +352,8 @@ function $RouteProvider() {
|
||||
'$injector',
|
||||
'$templateRequest',
|
||||
'$sce',
|
||||
function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) {
|
||||
'$browser',
|
||||
function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce, $browser) {
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
@@ -680,6 +683,8 @@ function $RouteProvider() {
|
||||
|
||||
var nextRoutePromise = $q.resolve(nextRoute);
|
||||
|
||||
$browser.$$incOutstandingRequestCount();
|
||||
|
||||
nextRoutePromise.
|
||||
then(getRedirectionData).
|
||||
then(handlePossibleRedirection).
|
||||
@@ -700,6 +705,13 @@ function $RouteProvider() {
|
||||
if (nextRoute === $route.current) {
|
||||
$rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error);
|
||||
}
|
||||
}).finally(function() {
|
||||
// Because `commitRoute()` is called from a `$rootScope.$evalAsync` block (see
|
||||
// `$locationWatch`), this `$$completeOutstandingRequest()` call will not cause
|
||||
// `outstandingRequestCount` to hit zero. This is important in case we are redirecting
|
||||
// to a new route which also requires some asynchronous work.
|
||||
|
||||
$browser.$$completeOutstandingRequest(noop);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2016 Google, Inc. http://angularjs.org
|
||||
* (c) 2010-2017 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window){
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@
|
||||
|
||||
/* apis.js */
|
||||
"hashKey": false,
|
||||
"HashMap": false,
|
||||
"NgMapShim": false,
|
||||
|
||||
/* urlUtils.js */
|
||||
"urlResolve": false,
|
||||
|
||||
+23
-2
@@ -1684,7 +1684,8 @@ describe('angular', function() {
|
||||
});
|
||||
|
||||
it('should bootstrap from an extension into an extension document for same-origin documents only', function() {
|
||||
if (msie) return; // IE does not support document.currentScript (nor extensions with protocol), so skip test.
|
||||
// IE does not support `document.currentScript` (nor extensions with protocol), so skip test.
|
||||
if (msie) return;
|
||||
|
||||
// Extension URLs are browser-specific, so we must choose a scheme that is supported by the browser to make
|
||||
// sure that the URL is properly parsed.
|
||||
@@ -1715,8 +1716,28 @@ describe('angular', function() {
|
||||
expect(allowAutoBootstrap(fakeDoc)).toBe(false);
|
||||
});
|
||||
|
||||
it('should bootstrap from a script with an empty or missing `src` attribute', function() {
|
||||
// IE does not support `document.currentScript` (nor extensions with protocol), so skip test.
|
||||
if (msie) return;
|
||||
|
||||
// Fake a minimal document object (the actual document.currentScript is readonly).
|
||||
var src;
|
||||
var fakeDoc = {
|
||||
createElement: document.createElement.bind(document),
|
||||
currentScript: {getAttribute: function() { return src; }},
|
||||
location: {origin: 'some-value', protocol: 'http:'}
|
||||
};
|
||||
|
||||
src = null;
|
||||
expect(allowAutoBootstrap(fakeDoc)).toBe(true);
|
||||
|
||||
src = '';
|
||||
expect(allowAutoBootstrap(fakeDoc)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not bootstrap from an extension into a non-extension document', function() {
|
||||
if (msie) return; // IE does not support document.currentScript (nor extensions with protocol), so skip test.
|
||||
// IE does not support `document.currentScript` (nor extensions with protocol), so skip test.
|
||||
if (msie) return;
|
||||
|
||||
var src = 'resource://something';
|
||||
// Fake a minimal document object (the actual document.currentScript is readonly).
|
||||
|
||||
+95
-42
@@ -1,56 +1,109 @@
|
||||
'use strict';
|
||||
|
||||
describe('api', function() {
|
||||
describe('hashKey()', function() {
|
||||
it('should use an existing `$$hashKey`', function() {
|
||||
var obj = {$$hashKey: 'foo'};
|
||||
expect(hashKey(obj)).toBe('foo');
|
||||
});
|
||||
|
||||
describe('HashMap', function() {
|
||||
it('should support a function as `$$hashKey` (and call it)', function() {
|
||||
var obj = {$$hashKey: valueFn('foo')};
|
||||
expect(hashKey(obj)).toBe('foo');
|
||||
});
|
||||
|
||||
it('should create a new `$$hashKey` if none exists (and return it)', function() {
|
||||
var obj = {};
|
||||
expect(hashKey(obj)).toBe(obj.$$hashKey);
|
||||
expect(obj.$$hashKey).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create appropriate `$$hashKey`s for primitive values', function() {
|
||||
expect(hashKey(undefined)).toBe(hashKey(undefined));
|
||||
expect(hashKey(null)).toBe(hashKey(null));
|
||||
expect(hashKey(null)).not.toBe(hashKey(undefined));
|
||||
expect(hashKey(true)).toBe(hashKey(true));
|
||||
expect(hashKey(false)).toBe(hashKey(false));
|
||||
expect(hashKey(false)).not.toBe(hashKey(true));
|
||||
expect(hashKey(42)).toBe(hashKey(42));
|
||||
expect(hashKey(1337)).toBe(hashKey(1337));
|
||||
expect(hashKey(1337)).not.toBe(hashKey(42));
|
||||
expect(hashKey('foo')).toBe(hashKey('foo'));
|
||||
expect(hashKey('foo')).not.toBe(hashKey('bar'));
|
||||
});
|
||||
|
||||
it('should create appropriate `$$hashKey`s for non-primitive values', function() {
|
||||
var fn = function() {};
|
||||
var arr = [];
|
||||
var obj = {};
|
||||
var date = new Date();
|
||||
|
||||
expect(hashKey(fn)).toBe(hashKey(fn));
|
||||
expect(hashKey(fn)).not.toBe(hashKey(function() {}));
|
||||
expect(hashKey(arr)).toBe(hashKey(arr));
|
||||
expect(hashKey(arr)).not.toBe(hashKey([]));
|
||||
expect(hashKey(obj)).toBe(hashKey(obj));
|
||||
expect(hashKey(obj)).not.toBe(hashKey({}));
|
||||
expect(hashKey(date)).toBe(hashKey(date));
|
||||
expect(hashKey(date)).not.toBe(hashKey(new Date()));
|
||||
});
|
||||
|
||||
it('should support a custom `nextUidFn`', function() {
|
||||
var nextUidFn = jasmine.createSpy('nextUidFn').and.returnValues('foo', 'bar', 'baz', 'qux');
|
||||
|
||||
var fn = function() {};
|
||||
var arr = [];
|
||||
var obj = {};
|
||||
var date = new Date();
|
||||
|
||||
hashKey(fn, nextUidFn);
|
||||
hashKey(arr, nextUidFn);
|
||||
hashKey(obj, nextUidFn);
|
||||
hashKey(date, nextUidFn);
|
||||
|
||||
expect(fn.$$hashKey).toBe('function:foo');
|
||||
expect(arr.$$hashKey).toBe('object:bar');
|
||||
expect(obj.$$hashKey).toBe('object:baz');
|
||||
expect(date.$$hashKey).toBe('object:qux');
|
||||
});
|
||||
});
|
||||
|
||||
describe('NgMapShim', function() {
|
||||
it('should do basic crud', function() {
|
||||
var map = new HashMap();
|
||||
var key = {};
|
||||
var value1 = {};
|
||||
var value2 = {};
|
||||
map.put(key, value1);
|
||||
map.put(key, value2);
|
||||
expect(map.get(key)).toBe(value2);
|
||||
expect(map.get({})).toBeUndefined();
|
||||
expect(map.remove(key)).toBe(value2);
|
||||
expect(map.get(key)).toBeUndefined();
|
||||
var map = new NgMapShim();
|
||||
var keys = [{}, {}, {}];
|
||||
var values = [{}, {}, {}];
|
||||
|
||||
map.set(keys[0], values[1]);
|
||||
map.set(keys[0], values[0]);
|
||||
expect(map.get(keys[0])).toBe(values[0]);
|
||||
expect(map.get(keys[1])).toBeUndefined();
|
||||
|
||||
map.set(keys[1], values[1]);
|
||||
map.set(keys[2], values[2]);
|
||||
expect(map.delete(keys[0])).toBe(true);
|
||||
expect(map.delete(keys[0])).toBe(false);
|
||||
|
||||
expect(map.get(keys[0])).toBeUndefined();
|
||||
expect(map.get(keys[1])).toBe(values[1]);
|
||||
expect(map.get(keys[2])).toBe(values[2]);
|
||||
});
|
||||
|
||||
it('should init from an array', function() {
|
||||
var map = new HashMap(['a','b']);
|
||||
expect(map.get('a')).toBe(0);
|
||||
expect(map.get('b')).toBe(1);
|
||||
expect(map.get('c')).toBeUndefined();
|
||||
});
|
||||
it('should be able to deal with `NaN` keys', function() {
|
||||
var map = new NgMapShim();
|
||||
|
||||
it('should maintain hashKey for object keys', function() {
|
||||
var map = new HashMap();
|
||||
var key = {};
|
||||
map.get(key);
|
||||
expect(key.$$hashKey).toBeDefined();
|
||||
});
|
||||
map.set('NaN', 'foo');
|
||||
map.set(NaN, 'bar');
|
||||
map.set(NaN, 'baz');
|
||||
|
||||
it('should maintain hashKey for function keys', function() {
|
||||
var map = new HashMap();
|
||||
var key = function() {};
|
||||
map.get(key);
|
||||
expect(key.$$hashKey).toBeDefined();
|
||||
});
|
||||
expect(map.get('NaN')).toBe('foo');
|
||||
expect(map.get(NaN)).toBe('baz');
|
||||
|
||||
it('should share hashKey between HashMap by default', function() {
|
||||
var map1 = new HashMap(), map2 = new HashMap();
|
||||
var key1 = {}, key2 = {};
|
||||
map1.get(key1);
|
||||
map2.get(key2);
|
||||
expect(key1.$$hashKey).not.toEqual(key2.$$hashKey);
|
||||
});
|
||||
expect(map.delete(NaN)).toBe(true);
|
||||
expect(map.get(NaN)).toBeUndefined();
|
||||
expect(map.get('NaN')).toBe('foo');
|
||||
|
||||
it('should maintain hashKey per HashMap if flag is passed', function() {
|
||||
var map1 = new HashMap([], true), map2 = new HashMap([], true);
|
||||
var key1 = {}, key2 = {};
|
||||
map1.get(key1);
|
||||
map2.get(key2);
|
||||
expect(key1.$$hashKey).toEqual(key2.$$hashKey);
|
||||
expect(map.delete(NaN)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,14 +46,13 @@ describe('injector', function() {
|
||||
it('should resolve dependency graph and instantiate all services just once', function() {
|
||||
var log = [];
|
||||
|
||||
// s1
|
||||
// / | \
|
||||
// / s2 \
|
||||
// / / | \ \
|
||||
// /s3 < s4 > s5
|
||||
// //
|
||||
// s6
|
||||
|
||||
// s1
|
||||
// / | \
|
||||
// / s2 \
|
||||
// / / | \ \
|
||||
// /s3 < s4 > s5
|
||||
// //
|
||||
// s6
|
||||
|
||||
providers('s1', function() { log.push('s1'); return {}; }, {$inject: ['s2', 's5', 's6']});
|
||||
providers('s2', function() { log.push('s2'); return {}; }, {$inject: ['s3', 's4', 's5']});
|
||||
@@ -285,14 +284,6 @@ describe('injector', function() {
|
||||
// eslint-disable-next-line no-eval
|
||||
expect(annotate(eval('a => b => b'))).toEqual(['a']);
|
||||
});
|
||||
|
||||
// Support: Chrome 50-51 only
|
||||
// TODO (gkalpak): Remove when Chrome v52 is released.
|
||||
// it('should be able to inject fat-arrow function', function() {
|
||||
// inject(($injector) => {
|
||||
// expect($injector).toBeDefined();
|
||||
// });
|
||||
// });
|
||||
}
|
||||
|
||||
if (support.classes) {
|
||||
@@ -325,19 +316,6 @@ describe('injector', function() {
|
||||
expect(instance).toEqual(jasmine.any(Clazz));
|
||||
});
|
||||
}
|
||||
|
||||
// Support: Chrome 50-51 only
|
||||
// TODO (gkalpak): Remove when Chrome v52 is released.
|
||||
// it('should be able to invoke classes', function() {
|
||||
// class Test {
|
||||
// constructor($injector) {
|
||||
// this.$injector = $injector;
|
||||
// }
|
||||
// }
|
||||
// var instance = injector.invoke(Test, null, null, 'Test');
|
||||
|
||||
// expect(instance.$injector).toBe(injector);
|
||||
// });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<html ng-app="lettersApp">
|
||||
<body>
|
||||
<div ng-view></div>
|
||||
|
||||
<script src="angular.js"></script>
|
||||
<script src="angular-route.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
angular.
|
||||
module('lettersApp', ['ngRoute']).
|
||||
config(function($routeProvider) {
|
||||
$routeProvider.
|
||||
otherwise(resolveRedirectTo('/foo1')).
|
||||
when('/foo1', resolveRedirectTo('/bar1')).
|
||||
when('/bar1', resolveRedirectTo('/baz1')).
|
||||
when('/baz1', resolveRedirectTo('/qux1')).
|
||||
when('/qux1', {
|
||||
template: '<ul><li ng-repeat="letter in $resolve.letters">{{ letter }}</li></ul>',
|
||||
resolve: resolveLetters()
|
||||
}).
|
||||
when('/foo2', resolveRedirectTo('/bar2')).
|
||||
when('/bar2', resolveRedirectTo('/baz2')).
|
||||
when('/baz2', resolveRedirectTo('/qux2')).
|
||||
when('/qux2', {
|
||||
template: '{{ $resolve.letters.length }}',
|
||||
resolve: resolveLetters()
|
||||
});
|
||||
|
||||
// Helpers
|
||||
function resolveLetters() {
|
||||
return {
|
||||
letters: function($q) {
|
||||
return $q(function(resolve) {
|
||||
window.setTimeout(resolve, 2000, ['a', 'b', 'c', 'd', 'e']);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function resolveRedirectTo(path) {
|
||||
return {
|
||||
resolveRedirectTo: function($q) {
|
||||
return $q(function(resolve) {
|
||||
window.setTimeout(resolve, 250, path);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
describe('ngRoute promises', function() {
|
||||
beforeEach(function() {
|
||||
loadFixture('ng-route-promise');
|
||||
});
|
||||
|
||||
it('should wait for route promises', function() {
|
||||
expect(element.all(by.tagName('li')).count()).toBe(5);
|
||||
});
|
||||
|
||||
it('should time out if the promise takes long enough', function() {
|
||||
// Don't try this at home kids, I'm a protractor dev
|
||||
browser.manage().timeouts().setScriptTimeout(1000);
|
||||
browser.waitForAngular().then(function() {
|
||||
fail('waitForAngular() should have timed out, but didn\'t');
|
||||
}, function(error) {
|
||||
expect(error.message).toContain('Timed out waiting for asynchronous Angular tasks to finish');
|
||||
});
|
||||
});
|
||||
|
||||
it('should wait for route promises when navigating to another route', function() {
|
||||
browser.setLocation('/foo2');
|
||||
expect(element(by.tagName('body')).getText()).toBe('5');
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
// Restore old timeout limit
|
||||
browser.getProcessedConfig().then(function(config) {
|
||||
return browser.manage().timeouts().setScriptTimeout(config.allScriptsTimeout);
|
||||
}).then(done);
|
||||
});
|
||||
});
|
||||
@@ -54,17 +54,16 @@ beforeEach(function() {
|
||||
afterEach(function() {
|
||||
var count, cache;
|
||||
|
||||
// both of these nodes are persisted across tests
|
||||
// and therefore the hashCode may be cached
|
||||
var node = window.document.querySelector('html');
|
||||
if (node) {
|
||||
node.$$hashKey = null;
|
||||
}
|
||||
var bod = window.document.body;
|
||||
if (bod) {
|
||||
bod.$$hashKey = null;
|
||||
}
|
||||
window.document.$$hashKey = null;
|
||||
// These Nodes are persisted across tests.
|
||||
// They used to be assigned a `$$hashKey` when animated, which we needed to clear after each test
|
||||
// to avoid affecting other tests. This is no longer the case, so we are just ensuring that there
|
||||
// is indeed no `$$hachKey` on them.
|
||||
var doc = window.document;
|
||||
var html = doc.querySelector('html');
|
||||
var body = doc.body;
|
||||
expect(doc.$$hashKey).toBeFalsy();
|
||||
expect(html && html.$$hashKey).toBeFalsy();
|
||||
expect(body && body.$$hashKey).toBeFalsy();
|
||||
|
||||
if (this.$injector) {
|
||||
var $rootScope = this.$injector.get('$rootScope');
|
||||
|
||||
@@ -87,15 +87,7 @@ describe('$anchorScroll', function() {
|
||||
|
||||
return function($window) {
|
||||
forEach(elmSpy, function(spy, id) {
|
||||
var count = map[id] || 0;
|
||||
// TODO(gkalpak): `toHaveBeenCalledTimes()` works correctly with 0 since
|
||||
// https://github.com/jasmine/jasmine/commit/342f0eb9a38194ecb8559e7df872c72afc0fe52e
|
||||
// Fix when we upgrade to a version that contains the fix.
|
||||
if (count > 0) {
|
||||
expect(spy).toHaveBeenCalledTimes(count);
|
||||
} else {
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
}
|
||||
expect(spy).toHaveBeenCalledTimes(map[id] || 0);
|
||||
});
|
||||
expect($window.scrollTo).not.toHaveBeenCalled();
|
||||
};
|
||||
@@ -401,14 +393,7 @@ describe('$anchorScroll', function() {
|
||||
|
||||
return function($rootScope, $window) {
|
||||
inject(expectScrollingTo(identifierCountMap));
|
||||
// TODO(gkalpak): `toHaveBeenCalledTimes()` works correctly with 0 since
|
||||
// https://github.com/jasmine/jasmine/commit/342f0eb9a38194ecb8559e7df872c72afc0fe52e
|
||||
// Fix when we upgrade to a version that contains the fix.
|
||||
if (list.length > 0) {
|
||||
expect($window.scrollBy).toHaveBeenCalledTimes(list.length);
|
||||
} else {
|
||||
expect($window.scrollBy).not.toHaveBeenCalled();
|
||||
}
|
||||
expect($window.scrollBy).toHaveBeenCalledTimes(list.length);
|
||||
forEach(list, function(offset, idx) {
|
||||
// Due to sub-pixel rendering, there is a +/-1 error margin in the actual offset
|
||||
var args = $window.scrollBy.calls.argsFor(idx);
|
||||
|
||||
+86
-47
@@ -1881,15 +1881,14 @@ describe('$compile', function() {
|
||||
|
||||
|
||||
it('should throw an error and clear element content if the template fails to load',
|
||||
inject(function($compile, $exceptionHandler, $httpBackend, $rootScope) {
|
||||
inject(function($compile, $httpBackend, $rootScope) {
|
||||
$httpBackend.expect('GET', 'hello.html').respond(404, 'Not Found!');
|
||||
element = $compile('<div><b class="hello">content</b></div>')($rootScope);
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(function() {
|
||||
$httpBackend.flush();
|
||||
}).toThrowMinErr('$compile', 'tpload', 'Failed to load template: hello.html');
|
||||
expect(sortedHtml(element)).toBe('<div><b class="hello"></b></div>');
|
||||
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'tpload',
|
||||
'Failed to load template: hello.html');
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1905,13 +1904,13 @@ describe('$compile', function() {
|
||||
templateUrl: 'template.html'
|
||||
}));
|
||||
});
|
||||
inject(function($compile, $exceptionHandler, $httpBackend) {
|
||||
inject(function($compile, $httpBackend) {
|
||||
$httpBackend.whenGET('template.html').respond('<p>template.html</p>');
|
||||
|
||||
$compile('<div><div class="sync async"></div></div>');
|
||||
$httpBackend.flush();
|
||||
|
||||
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'multidir',
|
||||
expect(function() {
|
||||
$compile('<div><div class="sync async"></div></div>');
|
||||
$httpBackend.flush();
|
||||
}).toThrowMinErr('$compile', 'multidir',
|
||||
'Multiple directives [async, sync] asking for template on: ' +
|
||||
'<div class="sync async">');
|
||||
});
|
||||
@@ -2122,15 +2121,15 @@ describe('$compile', function() {
|
||||
'multiple root elements': '<div></div><div></div>'
|
||||
}, function(directiveTemplate) {
|
||||
|
||||
inject(function($compile, $templateCache, $rootScope, $exceptionHandler) {
|
||||
inject(function($compile, $templateCache, $rootScope) {
|
||||
$templateCache.put('template.html', directiveTemplate);
|
||||
$compile('<p template></p>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($exceptionHandler.errors.pop()).toEqualMinErr('$compile', 'tplrt',
|
||||
'Template for directive \'template\' must have exactly one root element. ' +
|
||||
'template.html'
|
||||
);
|
||||
expect(function() {
|
||||
$compile('<p template></p>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
}).toThrowMinErr('$compile', 'tplrt',
|
||||
'Template for directive \'template\' must have exactly one root element. ' +
|
||||
'template.html');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2657,13 +2656,13 @@ describe('$compile', function() {
|
||||
);
|
||||
|
||||
it('should not allow more than one isolate/new scope creation per element regardless of `templateUrl`',
|
||||
inject(function($exceptionHandler, $httpBackend) {
|
||||
inject(function($httpBackend) {
|
||||
$httpBackend.expect('GET', 'tiscope.html').respond('<div>Hello, world !</div>');
|
||||
|
||||
compile('<div class="tiscope-a; scope-b"></div>');
|
||||
$httpBackend.flush();
|
||||
|
||||
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'multidir',
|
||||
expect(function() {
|
||||
compile('<div class="tiscope-a; scope-b"></div>');
|
||||
$httpBackend.flush();
|
||||
}).toThrowMinErr('$compile', 'multidir',
|
||||
'Multiple directives [scopeB, tiscopeA] asking for new/isolated scope on: ' +
|
||||
'<div class="tiscope-a; scope-b ng-scope">');
|
||||
})
|
||||
@@ -4750,21 +4749,28 @@ describe('$compile', function() {
|
||||
scope: {
|
||||
attr: '@',
|
||||
attrAlias: '@attr',
|
||||
$attrAlias: '@$attr$',
|
||||
ref: '=',
|
||||
refAlias: '= ref',
|
||||
$refAlias: '= $ref$',
|
||||
reference: '=',
|
||||
optref: '=?',
|
||||
optrefAlias: '=? optref',
|
||||
$optrefAlias: '=? $optref$',
|
||||
optreference: '=?',
|
||||
colref: '=*',
|
||||
colrefAlias: '=* colref',
|
||||
$colrefAlias: '=* $colref$',
|
||||
owRef: '<',
|
||||
owRefAlias: '< owRef',
|
||||
$owRefAlias: '< $owRef$',
|
||||
owOptref: '<?',
|
||||
owOptrefAlias: '<? owOptref',
|
||||
$owOptrefAlias: '<? $owOptref$',
|
||||
expr: '&',
|
||||
optExpr: '&?',
|
||||
exprAlias: '&expr',
|
||||
$exprAlias: '&$expr$',
|
||||
constructor: '&?'
|
||||
},
|
||||
link: function(scope) {
|
||||
@@ -5183,45 +5189,50 @@ describe('$compile', function() {
|
||||
|
||||
describe('attribute', function() {
|
||||
it('should copy simple attribute', inject(function() {
|
||||
compile('<div><span my-component attr="some text">');
|
||||
compile('<div><span my-component attr="some text" $attr$="some other text">');
|
||||
|
||||
expect(componentScope.attr).toEqual('some text');
|
||||
expect(componentScope.attrAlias).toEqual('some text');
|
||||
expect(componentScope.$attrAlias).toEqual('some other text');
|
||||
expect(componentScope.attrAlias).toEqual(componentScope.attr);
|
||||
}));
|
||||
|
||||
it('should copy an attribute with spaces', inject(function() {
|
||||
compile('<div><span my-component attr=" some text ">');
|
||||
compile('<div><span my-component attr=" some text " $attr$=" some other text ">');
|
||||
|
||||
expect(componentScope.attr).toEqual(' some text ');
|
||||
expect(componentScope.attrAlias).toEqual(' some text ');
|
||||
expect(componentScope.$attrAlias).toEqual(' some other text ');
|
||||
expect(componentScope.attrAlias).toEqual(componentScope.attr);
|
||||
}));
|
||||
|
||||
it('should set up the interpolation before it reaches the link function', inject(function() {
|
||||
$rootScope.name = 'misko';
|
||||
compile('<div><span my-component attr="hello {{name}}">');
|
||||
compile('<div><span my-component attr="hello {{name}}" $attr$="hi {{name}}">');
|
||||
expect(componentScope.attr).toEqual('hello misko');
|
||||
expect(componentScope.attrAlias).toEqual('hello misko');
|
||||
expect(componentScope.$attrAlias).toEqual('hi misko');
|
||||
}));
|
||||
|
||||
it('should update when interpolated attribute updates', inject(function() {
|
||||
compile('<div><span my-component attr="hello {{name}}">');
|
||||
compile('<div><span my-component attr="hello {{name}}" $attr$="hi {{name}}">');
|
||||
|
||||
$rootScope.name = 'igor';
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(componentScope.attr).toEqual('hello igor');
|
||||
expect(componentScope.attrAlias).toEqual('hello igor');
|
||||
expect(componentScope.$attrAlias).toEqual('hi igor');
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('object reference', function() {
|
||||
it('should update local when origin changes', inject(function() {
|
||||
compile('<div><span my-component ref="name">');
|
||||
compile('<div><span my-component ref="name" $ref$="name">');
|
||||
expect(componentScope.ref).toBeUndefined();
|
||||
expect(componentScope.refAlias).toBe(componentScope.ref);
|
||||
expect(componentScope.$refAlias).toBe(componentScope.ref);
|
||||
|
||||
$rootScope.name = 'misko';
|
||||
$rootScope.$apply();
|
||||
@@ -5229,16 +5240,18 @@ describe('$compile', function() {
|
||||
expect($rootScope.name).toBe('misko');
|
||||
expect(componentScope.ref).toBe('misko');
|
||||
expect(componentScope.refAlias).toBe('misko');
|
||||
expect(componentScope.$refAlias).toBe('misko');
|
||||
|
||||
$rootScope.name = {};
|
||||
$rootScope.$apply();
|
||||
expect(componentScope.ref).toBe($rootScope.name);
|
||||
expect(componentScope.refAlias).toBe($rootScope.name);
|
||||
expect(componentScope.$refAlias).toBe($rootScope.name);
|
||||
}));
|
||||
|
||||
|
||||
it('should update local when both change', inject(function() {
|
||||
compile('<div><span my-component ref="name">');
|
||||
compile('<div><span my-component ref="name" $ref$="name">');
|
||||
$rootScope.name = {mark:123};
|
||||
componentScope.ref = 'misko';
|
||||
|
||||
@@ -5246,6 +5259,7 @@ describe('$compile', function() {
|
||||
expect($rootScope.name).toEqual({mark:123});
|
||||
expect(componentScope.ref).toBe($rootScope.name);
|
||||
expect(componentScope.refAlias).toBe($rootScope.name);
|
||||
expect(componentScope.$refAlias).toBe($rootScope.name);
|
||||
|
||||
$rootScope.name = 'igor';
|
||||
componentScope.ref = {};
|
||||
@@ -5253,6 +5267,7 @@ describe('$compile', function() {
|
||||
expect($rootScope.name).toEqual('igor');
|
||||
expect(componentScope.ref).toBe($rootScope.name);
|
||||
expect(componentScope.refAlias).toBe($rootScope.name);
|
||||
expect(componentScope.$refAlias).toBe($rootScope.name);
|
||||
}));
|
||||
|
||||
it('should not break if local and origin both change to the same value', inject(function() {
|
||||
@@ -5382,19 +5397,22 @@ describe('$compile', function() {
|
||||
|
||||
describe('optional object reference', function() {
|
||||
it('should update local when origin changes', inject(function() {
|
||||
compile('<div><span my-component optref="name">');
|
||||
compile('<div><span my-component optref="name" $optref$="name">');
|
||||
expect(componentScope.optRef).toBeUndefined();
|
||||
expect(componentScope.optRefAlias).toBe(componentScope.optRef);
|
||||
expect(componentScope.$optRefAlias).toBe(componentScope.optRef);
|
||||
|
||||
$rootScope.name = 'misko';
|
||||
$rootScope.$apply();
|
||||
expect(componentScope.optref).toBe($rootScope.name);
|
||||
expect(componentScope.optrefAlias).toBe($rootScope.name);
|
||||
expect(componentScope.$optrefAlias).toBe($rootScope.name);
|
||||
|
||||
$rootScope.name = {};
|
||||
$rootScope.$apply();
|
||||
expect(componentScope.optref).toBe($rootScope.name);
|
||||
expect(componentScope.optrefAlias).toBe($rootScope.name);
|
||||
expect(componentScope.$optrefAlias).toBe($rootScope.name);
|
||||
}));
|
||||
|
||||
it('should not throw exception when reference does not exist', inject(function() {
|
||||
@@ -5402,6 +5420,7 @@ describe('$compile', function() {
|
||||
|
||||
expect(componentScope.optref).toBeUndefined();
|
||||
expect(componentScope.optrefAlias).toBeUndefined();
|
||||
expect(componentScope.$optrefAlias).toBeUndefined();
|
||||
expect(componentScope.optreference).toBeUndefined();
|
||||
}));
|
||||
});
|
||||
@@ -5419,16 +5438,18 @@ describe('$compile', function() {
|
||||
$rootScope.query = '';
|
||||
$rootScope.$apply();
|
||||
|
||||
compile('<div><span my-component colref="collection | filter:query">');
|
||||
compile('<div><span my-component colref="collection | filter:query" $colref$="collection | filter:query">');
|
||||
|
||||
expect(componentScope.colref).toEqual($rootScope.collection);
|
||||
expect(componentScope.colrefAlias).toEqual(componentScope.colref);
|
||||
expect(componentScope.$colrefAlias).toEqual(componentScope.colref);
|
||||
|
||||
$rootScope.query = 'Gab';
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(componentScope.colref).toEqual([$rootScope.collection[0]]);
|
||||
expect(componentScope.colrefAlias).toEqual([$rootScope.collection[0]]);
|
||||
expect(componentScope.$colrefAlias).toEqual([$rootScope.collection[0]]);
|
||||
}));
|
||||
|
||||
it('should update origin scope when isolate scope changes', inject(function() {
|
||||
@@ -5456,10 +5477,11 @@ describe('$compile', function() {
|
||||
|
||||
describe('one-way binding', function() {
|
||||
it('should update isolate when the identity of origin changes', inject(function() {
|
||||
compile('<div><span my-component ow-ref="obj">');
|
||||
compile('<div><span my-component ow-ref="obj" $ow-ref$="obj">');
|
||||
|
||||
expect(componentScope.owRef).toBeUndefined();
|
||||
expect(componentScope.owRefAlias).toBe(componentScope.owRef);
|
||||
expect(componentScope.$owRefAlias).toBe(componentScope.owRef);
|
||||
|
||||
$rootScope.obj = {value: 'initial'};
|
||||
$rootScope.$apply();
|
||||
@@ -5467,12 +5489,14 @@ describe('$compile', function() {
|
||||
expect($rootScope.obj).toEqual({value: 'initial'});
|
||||
expect(componentScope.owRef).toEqual({value: 'initial'});
|
||||
expect(componentScope.owRefAlias).toBe(componentScope.owRef);
|
||||
expect(componentScope.$owRefAlias).toBe(componentScope.owRef);
|
||||
|
||||
// This changes in both scopes because of reference
|
||||
$rootScope.obj.value = 'origin1';
|
||||
$rootScope.$apply();
|
||||
expect(componentScope.owRef.value).toBe('origin1');
|
||||
expect(componentScope.owRefAlias.value).toBe('origin1');
|
||||
expect(componentScope.$owRefAlias.value).toBe('origin1');
|
||||
|
||||
componentScope.owRef = {value: 'isolate1'};
|
||||
componentScope.$apply();
|
||||
@@ -5483,6 +5507,7 @@ describe('$compile', function() {
|
||||
$rootScope.$apply();
|
||||
expect(componentScope.owRef.value).toBe('isolate1');
|
||||
expect(componentScope.owRefAlias.value).toBe('origin2');
|
||||
expect(componentScope.$owRefAlias.value).toBe('origin2');
|
||||
|
||||
// Change does propagate because object identity changes
|
||||
$rootScope.obj = {value: 'origin3'};
|
||||
@@ -5490,10 +5515,11 @@ describe('$compile', function() {
|
||||
expect(componentScope.owRef.value).toBe('origin3');
|
||||
expect(componentScope.owRef).toBe($rootScope.obj);
|
||||
expect(componentScope.owRefAlias).toBe($rootScope.obj);
|
||||
expect(componentScope.$owRefAlias).toBe($rootScope.obj);
|
||||
}));
|
||||
|
||||
it('should update isolate when both change', inject(function() {
|
||||
compile('<div><span my-component ow-ref="name">');
|
||||
compile('<div><span my-component ow-ref="name" $ow-ref$="name">');
|
||||
|
||||
$rootScope.name = {mark:123};
|
||||
componentScope.owRef = 'misko';
|
||||
@@ -5502,6 +5528,7 @@ describe('$compile', function() {
|
||||
expect($rootScope.name).toEqual({mark:123});
|
||||
expect(componentScope.owRef).toBe($rootScope.name);
|
||||
expect(componentScope.owRefAlias).toBe($rootScope.name);
|
||||
expect(componentScope.$owRefAlias).toBe($rootScope.name);
|
||||
|
||||
$rootScope.name = 'igor';
|
||||
componentScope.owRef = {};
|
||||
@@ -5509,6 +5536,7 @@ describe('$compile', function() {
|
||||
expect($rootScope.name).toEqual('igor');
|
||||
expect(componentScope.owRef).toBe($rootScope.name);
|
||||
expect(componentScope.owRefAlias).toBe($rootScope.name);
|
||||
expect(componentScope.$owRefAlias).toBe($rootScope.name);
|
||||
}));
|
||||
|
||||
describe('initialization', function() {
|
||||
@@ -5705,17 +5733,19 @@ describe('$compile', function() {
|
||||
|
||||
it('should not update origin when identity of isolate changes', inject(function() {
|
||||
$rootScope.name = {mark:123};
|
||||
compile('<div><span my-component ow-ref="name">');
|
||||
compile('<div><span my-component ow-ref="name" $ow-ref$="name">');
|
||||
|
||||
expect($rootScope.name).toEqual({mark:123});
|
||||
expect(componentScope.owRef).toBe($rootScope.name);
|
||||
expect(componentScope.owRefAlias).toBe($rootScope.name);
|
||||
expect(componentScope.$owRefAlias).toBe($rootScope.name);
|
||||
|
||||
componentScope.owRef = 'martin';
|
||||
$rootScope.$apply();
|
||||
expect($rootScope.name).toEqual({mark: 123});
|
||||
expect(componentScope.owRef).toBe('martin');
|
||||
expect(componentScope.owRefAlias).toEqual({mark: 123});
|
||||
expect(componentScope.$owRefAlias).toEqual({mark: 123});
|
||||
}));
|
||||
|
||||
|
||||
@@ -5862,20 +5892,23 @@ describe('$compile', function() {
|
||||
|
||||
describe('optional one-way binding', function() {
|
||||
it('should update local when origin changes', inject(function() {
|
||||
compile('<div><span my-component ow-optref="name">');
|
||||
compile('<div><span my-component ow-optref="name" $ow-optref$="name">');
|
||||
|
||||
expect(componentScope.owOptref).toBeUndefined();
|
||||
expect(componentScope.owOptrefAlias).toBe(componentScope.owOptref);
|
||||
expect(componentScope.$owOptrefAlias).toBe(componentScope.owOptref);
|
||||
|
||||
$rootScope.name = 'misko';
|
||||
$rootScope.$apply();
|
||||
expect(componentScope.owOptref).toBe($rootScope.name);
|
||||
expect(componentScope.owOptrefAlias).toBe($rootScope.name);
|
||||
expect(componentScope.$owOptrefAlias).toBe($rootScope.name);
|
||||
|
||||
$rootScope.name = {};
|
||||
$rootScope.$apply();
|
||||
expect(componentScope.owOptref).toBe($rootScope.name);
|
||||
expect(componentScope.owOptrefAlias).toBe($rootScope.name);
|
||||
expect(componentScope.$owOptrefAlias).toBe($rootScope.name);
|
||||
}));
|
||||
|
||||
it('should not throw exception when reference does not exist', inject(function() {
|
||||
@@ -5883,6 +5916,7 @@ describe('$compile', function() {
|
||||
|
||||
expect(componentScope.owOptref).toBeUndefined();
|
||||
expect(componentScope.owOptrefAlias).toBeUndefined();
|
||||
expect(componentScope.$owOptrefAlias).toBeUndefined();
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -5890,17 +5924,19 @@ describe('$compile', function() {
|
||||
|
||||
describe('executable expression', function() {
|
||||
it('should allow expression execution with locals', inject(function() {
|
||||
compile('<div><span my-component expr="count = count + offset">');
|
||||
compile('<div><span my-component expr="count = count + offset" $expr$="count = count + offset">');
|
||||
$rootScope.count = 2;
|
||||
|
||||
expect(typeof componentScope.expr).toBe('function');
|
||||
expect(typeof componentScope.exprAlias).toBe('function');
|
||||
expect(typeof componentScope.$exprAlias).toBe('function');
|
||||
|
||||
expect(componentScope.expr({offset: 1})).toEqual(3);
|
||||
expect($rootScope.count).toEqual(3);
|
||||
|
||||
expect(componentScope.exprAlias({offset: 10})).toEqual(13);
|
||||
expect($rootScope.count).toEqual(13);
|
||||
expect(componentScope.$exprAlias({offset: 10})).toEqual(23);
|
||||
expect($rootScope.count).toEqual(23);
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -5918,17 +5954,21 @@ describe('$compile', function() {
|
||||
expect(componentScope.$$isolateBindings.attr.mode).toBe('@');
|
||||
expect(componentScope.$$isolateBindings.attr.attrName).toBe('attr');
|
||||
expect(componentScope.$$isolateBindings.attrAlias.attrName).toBe('attr');
|
||||
expect(componentScope.$$isolateBindings.$attrAlias.attrName).toBe('$attr$');
|
||||
expect(componentScope.$$isolateBindings.ref.mode).toBe('=');
|
||||
expect(componentScope.$$isolateBindings.ref.attrName).toBe('ref');
|
||||
expect(componentScope.$$isolateBindings.refAlias.attrName).toBe('ref');
|
||||
expect(componentScope.$$isolateBindings.$refAlias.attrName).toBe('$ref$');
|
||||
expect(componentScope.$$isolateBindings.reference.mode).toBe('=');
|
||||
expect(componentScope.$$isolateBindings.reference.attrName).toBe('reference');
|
||||
expect(componentScope.$$isolateBindings.owRef.mode).toBe('<');
|
||||
expect(componentScope.$$isolateBindings.owRef.attrName).toBe('owRef');
|
||||
expect(componentScope.$$isolateBindings.owRefAlias.attrName).toBe('owRef');
|
||||
expect(componentScope.$$isolateBindings.$owRefAlias.attrName).toBe('$owRef$');
|
||||
expect(componentScope.$$isolateBindings.expr.mode).toBe('&');
|
||||
expect(componentScope.$$isolateBindings.expr.attrName).toBe('expr');
|
||||
expect(componentScope.$$isolateBindings.exprAlias.attrName).toBe('expr');
|
||||
expect(componentScope.$$isolateBindings.$exprAlias.attrName).toBe('$expr$');
|
||||
|
||||
var firstComponentScope = componentScope,
|
||||
first$$isolateBindings = componentScope.$$isolateBindings;
|
||||
@@ -8956,18 +8996,17 @@ describe('$compile', function() {
|
||||
}));
|
||||
});
|
||||
|
||||
inject(function($compile, $exceptionHandler, $rootScope, $templateCache) {
|
||||
inject(function($compile, $rootScope, $templateCache) {
|
||||
$templateCache.put('noTransBar.html',
|
||||
'<div>' +
|
||||
// This ng-transclude is invalid. It should throw an error.
|
||||
'<div class="bar" ng-transclude></div>' +
|
||||
'</div>');
|
||||
|
||||
element = $compile('<div trans-foo>content</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($exceptionHandler.errors[0][1]).toBe('<div class="bar" ng-transclude="">');
|
||||
expect($exceptionHandler.errors[0][0]).toEqualMinErr('ngTransclude', 'orphan',
|
||||
expect(function() {
|
||||
element = $compile('<div trans-foo>content</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
}).toThrowMinErr('ngTransclude', 'orphan',
|
||||
'Illegal use of ngTransclude directive in the template! ' +
|
||||
'No parent directive that requires a transclusion found. ' +
|
||||
'Element: <div class="bar" ng-transclude="">');
|
||||
@@ -9780,13 +9819,13 @@ describe('$compile', function() {
|
||||
transclude: 'element'
|
||||
}));
|
||||
});
|
||||
inject(function($compile, $exceptionHandler, $httpBackend) {
|
||||
inject(function($compile, $httpBackend) {
|
||||
$httpBackend.expectGET('template.html').respond('<p second>template.html</p>');
|
||||
|
||||
$compile('<div template first></div>');
|
||||
$httpBackend.flush();
|
||||
|
||||
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'multidir',
|
||||
expect(function() {
|
||||
$compile('<div template first></div>');
|
||||
$httpBackend.flush();
|
||||
}).toThrowMinErr('$compile', 'multidir',
|
||||
'Multiple directives [first, second] asking for transclusion on: <p ');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2787,6 +2787,13 @@ describe('input', function() {
|
||||
helper.changeInputValueTo('3.5');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(3.5);
|
||||
|
||||
// 1.16 % 0.01 === 0.009999999999999896
|
||||
// 1.16 * 100 === 115.99999999999999
|
||||
$rootScope.step = 0.01;
|
||||
helper.changeInputValueTo('1.16');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(1.16);
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -3656,7 +3663,9 @@ describe('input', function() {
|
||||
|
||||
it('should correctly validate even in cases where the JS floating point arithmetic fails',
|
||||
function() {
|
||||
var inputElm = helper.compileInput('<input type="range" ng-model="value" step="0.1" />');
|
||||
$rootScope.step = 0.1;
|
||||
var inputElm = helper.compileInput(
|
||||
'<input type="range" ng-model="value" step="{{step}}" />');
|
||||
var ngModel = inputElm.controller('ngModel');
|
||||
|
||||
expect(inputElm.val()).toBe('');
|
||||
@@ -3681,6 +3690,13 @@ describe('input', function() {
|
||||
helper.changeInputValueTo('3.5');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(3.5);
|
||||
|
||||
// 1.16 % 0.01 === 0.009999999999999896
|
||||
// 1.16 * 100 === 115.99999999999999
|
||||
$rootScope.step = 0.01;
|
||||
helper.changeInputValueTo('1.16');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.value).toBe(1.16);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -4195,6 +4211,26 @@ describe('input', function() {
|
||||
expect(inputElm[0].getAttribute('value')).toBe('something');
|
||||
});
|
||||
|
||||
it('should clear the "dom" value property and attribute when the value is undefined', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-value="value">');
|
||||
|
||||
$rootScope.$apply('value = "something"');
|
||||
|
||||
expect(inputElm[0].value).toBe('something');
|
||||
expect(inputElm[0].getAttribute('value')).toBe('something');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
delete $rootScope.value;
|
||||
});
|
||||
|
||||
expect(inputElm[0].value).toBe('');
|
||||
// Support: IE 9-11
|
||||
// In IE it is not possible to remove the `value` attribute from an input element.
|
||||
if (!msie) {
|
||||
expect(inputElm[0].getAttribute('value')).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
they('should update the $prop "value" property and attribute after the bound expression changes', {
|
||||
input: '<input type="text" ng-value="value">',
|
||||
textarea: '<textarea ng-value="value"></textarea>'
|
||||
|
||||
@@ -1337,6 +1337,48 @@ describe('ngModel', function() {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('override ModelOptions', function() {
|
||||
it('should replace the previous model options', function() {
|
||||
var $options = ctrl.$options;
|
||||
ctrl.$overrideModelOptions({});
|
||||
expect(ctrl.$options).not.toBe($options);
|
||||
});
|
||||
|
||||
it('should set the given options', function() {
|
||||
var $options = ctrl.$options;
|
||||
ctrl.$overrideModelOptions({ debounce: 1000, updateOn: 'blur' });
|
||||
expect(ctrl.$options.getOption('debounce')).toEqual(1000);
|
||||
expect(ctrl.$options.getOption('updateOn')).toEqual('blur');
|
||||
expect(ctrl.$options.getOption('updateOnDefault')).toBe(false);
|
||||
});
|
||||
|
||||
it('should inherit from a parent model options if specified', inject(function($compile, $rootScope) {
|
||||
var element = $compile(
|
||||
'<form name="form" ng-model-options="{debounce: 1000, updateOn: \'blur\'}">' +
|
||||
' <input ng-model="value" name="input">' +
|
||||
'</form>')($rootScope);
|
||||
var ctrl = $rootScope.form.input;
|
||||
ctrl.$overrideModelOptions({ debounce: 2000, '*': '$inherit' });
|
||||
expect(ctrl.$options.getOption('debounce')).toEqual(2000);
|
||||
expect(ctrl.$options.getOption('updateOn')).toEqual('blur');
|
||||
expect(ctrl.$options.getOption('updateOnDefault')).toBe(false);
|
||||
dealoc(element);
|
||||
}));
|
||||
|
||||
it('should not inherit from a parent model options if not specified', inject(function($compile, $rootScope) {
|
||||
var element = $compile(
|
||||
'<form name="form" ng-model-options="{debounce: 1000, updateOn: \'blur\'}">' +
|
||||
' <input ng-model="value" name="input">' +
|
||||
'</form>')($rootScope);
|
||||
var ctrl = $rootScope.form.input;
|
||||
ctrl.$overrideModelOptions({ debounce: 2000 });
|
||||
expect(ctrl.$options.getOption('debounce')).toEqual(2000);
|
||||
expect(ctrl.$options.getOption('updateOn')).toEqual('');
|
||||
expect(ctrl.$options.getOption('updateOnDefault')).toBe(true);
|
||||
dealoc(element);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1529,8 +1529,8 @@ describe('select', function() {
|
||||
'number:1',
|
||||
'boolean:true',
|
||||
'object:null',
|
||||
'object:3',
|
||||
'object:4',
|
||||
'object:5',
|
||||
'number:NaN'
|
||||
);
|
||||
|
||||
@@ -1555,7 +1555,7 @@ describe('select', function() {
|
||||
browserTrigger(element, 'change');
|
||||
|
||||
var arrayVal = ['a'];
|
||||
arrayVal.$$hashKey = 'object:5';
|
||||
arrayVal.$$hashKey = 'object:4';
|
||||
|
||||
expect(scope.selected).toEqual([
|
||||
'string',
|
||||
@@ -1563,7 +1563,7 @@ describe('select', function() {
|
||||
1,
|
||||
true,
|
||||
null,
|
||||
{prop: 'value', $$hashKey: 'object:4'},
|
||||
{prop: 'value', $$hashKey: 'object:3'},
|
||||
arrayVal,
|
||||
NaN
|
||||
]);
|
||||
@@ -1876,10 +1876,10 @@ describe('select', function() {
|
||||
scope.$digest();
|
||||
|
||||
optionElements = element.find('option');
|
||||
expect(element.val()).toBe(prop === 'ngValue' ? 'object:4' : 'C');
|
||||
expect(element.val()).toBe(prop === 'ngValue' ? 'object:3' : 'C');
|
||||
expect(optionElements.length).toEqual(3);
|
||||
expect(optionElements[2].selected).toBe(true);
|
||||
expect(scope.obj.value).toEqual(prop === 'ngValue' ? {name: 'C', $$hashKey: 'object:4'} : 'C');
|
||||
expect(scope.obj.value).toEqual(prop === 'ngValue' ? {name: 'C', $$hashKey: 'object:3'} : 'C');
|
||||
});
|
||||
|
||||
|
||||
@@ -2188,9 +2188,9 @@ describe('select', function() {
|
||||
expect(optionElements.length).toEqual(4);
|
||||
expect(scope.obj.value).toEqual(prop === 'ngValue' ?
|
||||
[
|
||||
{name: 'A', $$hashKey: 'object:4', disabled: true},
|
||||
{name: 'C', $$hashKey: 'object:6'},
|
||||
{name: 'D', $$hashKey: 'object:7', disabled: true}
|
||||
{name: 'A', $$hashKey: 'object:3', disabled: true},
|
||||
{name: 'C', $$hashKey: 'object:5'},
|
||||
{name: 'D', $$hashKey: 'object:6', disabled: true}
|
||||
] :
|
||||
['A', 'C', 'D']
|
||||
);
|
||||
@@ -2242,13 +2242,13 @@ describe('select', function() {
|
||||
scope.$digest();
|
||||
|
||||
optionElements = element.find('option');
|
||||
expect(element.val()).toEqual(prop === 'ngValue' ? ['object:4', 'object:5'] : ['B', 'C']);
|
||||
expect(element.val()).toEqual(prop === 'ngValue' ? ['object:4', 'object:7'] : ['B', 'C']);
|
||||
expect(optionElements.length).toEqual(3);
|
||||
expect(optionElements[1].selected).toBe(true);
|
||||
expect(optionElements[2].selected).toBe(true);
|
||||
expect(scope.obj.value).toEqual(prop === 'ngValue' ?
|
||||
[{ name: 'B', $$hashKey: 'object:4'},
|
||||
{name: 'C', $$hashKey: 'object:5'}] :
|
||||
{name: 'C', $$hashKey: 'object:7'}] :
|
||||
['B', 'C']);
|
||||
});
|
||||
|
||||
@@ -2316,6 +2316,40 @@ describe('select', function() {
|
||||
|
||||
});
|
||||
|
||||
it('should keep the ngModel value when the selected option is recreated by ngRepeat', function() {
|
||||
scope.options = [{ name: 'A'}, { name: 'B'}, { name: 'C'}];
|
||||
scope.obj = {
|
||||
value: 'B'
|
||||
};
|
||||
|
||||
compile(
|
||||
'<select ng-model="obj.value">' +
|
||||
'<option ng-repeat="option in options" value="{{option.name}}">{{option.name}}</option>' +
|
||||
'</select>'
|
||||
);
|
||||
|
||||
var optionElements = element.find('option');
|
||||
expect(optionElements.length).toEqual(3);
|
||||
expect(optionElements[0].value).toBe('A');
|
||||
expect(optionElements[1]).toBeMarkedAsSelected();
|
||||
expect(scope.obj.value).toBe('B');
|
||||
|
||||
scope.$apply(function() {
|
||||
// Only when new objects are used, ngRepeat re-creates the element from scratch
|
||||
scope.options = [{ name: 'B'}, { name: 'C'}, { name: 'D'}];
|
||||
});
|
||||
|
||||
var previouslySelectedOptionElement = optionElements[1];
|
||||
optionElements = element.find('option');
|
||||
|
||||
expect(optionElements.length).toEqual(3);
|
||||
expect(optionElements[0].value).toBe('B');
|
||||
expect(optionElements[0]).toBeMarkedAsSelected();
|
||||
expect(scope.obj.value).toBe('B');
|
||||
// Ensure the assumption that the element is re-created is true
|
||||
expect(previouslySelectedOptionElement).not.toBe(optionElements[0]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
+71
-5
@@ -710,6 +710,7 @@ describe('$location', function() {
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should not infinitely digest when using a semicolon in initial path', function() {
|
||||
initService({html5Mode:true,supportHistory:true});
|
||||
mockUpBrowser({initialUrl:'http://localhost:9876/;jsessionid=foo', baseHref:'/'});
|
||||
@@ -721,6 +722,63 @@ describe('$location', function() {
|
||||
});
|
||||
|
||||
|
||||
describe('when changing the browser URL/history directly during a `$digest`', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
initService({supportHistory: true});
|
||||
mockUpBrowser({initialUrl: 'http://foo.bar/', baseHref: '/'});
|
||||
});
|
||||
|
||||
|
||||
it('should correctly update `$location` from history and not digest infinitely', inject(
|
||||
function($browser, $location, $rootScope, $window) {
|
||||
$location.url('baz');
|
||||
$rootScope.$digest();
|
||||
|
||||
var originalUrl = $window.location.href;
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.$evalAsync(function() {
|
||||
$window.history.pushState({}, null, originalUrl + '/qux');
|
||||
});
|
||||
});
|
||||
|
||||
expect($browser.url()).toBe('http://foo.bar/#!/baz/qux');
|
||||
expect($location.absUrl()).toBe('http://foo.bar/#!/baz/qux');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.$evalAsync(function() {
|
||||
$window.history.replaceState({}, null, originalUrl + '/quux');
|
||||
});
|
||||
});
|
||||
|
||||
expect($browser.url()).toBe('http://foo.bar/#!/baz/quux');
|
||||
expect($location.absUrl()).toBe('http://foo.bar/#!/baz/quux');
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
it('should correctly update `$location` from URL and not digest infinitely', inject(
|
||||
function($browser, $location, $rootScope, $window) {
|
||||
$location.url('baz');
|
||||
$rootScope.$digest();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.$evalAsync(function() {
|
||||
$window.location.href += '/qux';
|
||||
});
|
||||
});
|
||||
|
||||
jqLite($window).triggerHandler('hashchange');
|
||||
|
||||
expect($browser.url()).toBe('http://foo.bar/#!/baz/qux');
|
||||
expect($location.absUrl()).toBe('http://foo.bar/#!/baz/qux');
|
||||
})
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
|
||||
function updatePathOnLocationChangeSuccessTo(newPath) {
|
||||
inject(function($rootScope, $location) {
|
||||
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
|
||||
@@ -1039,11 +1097,21 @@ describe('$location', function() {
|
||||
});
|
||||
|
||||
it('should update $location when browser state changes', function() {
|
||||
initService({html5Mode:true, supportHistory: true});
|
||||
mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'});
|
||||
inject(function($location, $window) {
|
||||
initService({html5Mode: true, supportHistory: true});
|
||||
mockUpBrowser({initialUrl: 'http://new.com/a/b/', baseHref: '/a/b/'});
|
||||
inject(function($location, $rootScope, $window) {
|
||||
$window.history.pushState({b: 3});
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.state()).toEqual({b: 3});
|
||||
|
||||
$window.history.pushState({b: 4}, null, $window.location.href + 'c?d=e#f');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toBe('/c');
|
||||
expect($location.search()).toEqual({d: 'e'});
|
||||
expect($location.hash()).toBe('f');
|
||||
expect($location.state()).toEqual({b: 4});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2666,12 +2734,10 @@ describe('$location', function() {
|
||||
replaceState: function(state, title, url) {
|
||||
win.history.state = copy(state);
|
||||
if (url) win.location.href = url;
|
||||
jqLite(win).triggerHandler('popstate');
|
||||
},
|
||||
pushState: function(state, title, url) {
|
||||
win.history.state = copy(state);
|
||||
if (url) win.location.href = url;
|
||||
jqLite(win).triggerHandler('popstate');
|
||||
}
|
||||
};
|
||||
win.addEventListener = angular.noop;
|
||||
|
||||
+101
-19
@@ -2635,25 +2635,6 @@ describe('parser', function() {
|
||||
expect(log).toEqual('');
|
||||
}));
|
||||
|
||||
it('should work with expensive checks', inject(function($parse, $rootScope, log) {
|
||||
var fn = $parse('::foo', null, true);
|
||||
$rootScope.$watch(fn, function(value, old) { if (value !== old) log(value); });
|
||||
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.$$watchers.length).toBe(1);
|
||||
|
||||
$rootScope.foo = 'bar';
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.$$watchers.length).toBe(0);
|
||||
expect(log).toEqual('bar');
|
||||
log.reset();
|
||||
|
||||
$rootScope.foo = 'man';
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.$$watchers.length).toBe(0);
|
||||
expect(log).toEqual('');
|
||||
}));
|
||||
|
||||
it('should have a stable value if at the end of a $digest it has a defined value', inject(function($parse, $rootScope, log) {
|
||||
var fn = $parse('::foo');
|
||||
$rootScope.$watch(fn, function(value, old) { if (value !== old) log(value); });
|
||||
@@ -3118,6 +3099,107 @@ describe('parser', function() {
|
||||
scope.$digest();
|
||||
expect(objB.value).toBe(scope.input);
|
||||
}));
|
||||
|
||||
it('should watch ES6 object computed property changes', function() {
|
||||
var count = 0;
|
||||
var values = [];
|
||||
|
||||
scope.$watch('{[a]: true}', function(val) {
|
||||
count++;
|
||||
values.push(val);
|
||||
}, true);
|
||||
|
||||
scope.$digest();
|
||||
expect(count).toBe(1);
|
||||
expect(values[0]).toEqual({'undefined': true});
|
||||
|
||||
scope.$digest();
|
||||
expect(count).toBe(1);
|
||||
expect(values[0]).toEqual({'undefined': true});
|
||||
|
||||
scope.a = true;
|
||||
scope.$digest();
|
||||
expect(count).toBe(2);
|
||||
expect(values[1]).toEqual({'true': true});
|
||||
|
||||
scope.a = 'abc';
|
||||
scope.$digest();
|
||||
expect(count).toBe(3);
|
||||
expect(values[2]).toEqual({'abc': true});
|
||||
|
||||
scope.a = undefined;
|
||||
scope.$digest();
|
||||
expect(count).toBe(4);
|
||||
expect(values[3]).toEqual({'undefined': true});
|
||||
});
|
||||
|
||||
it('should support watching literals', inject(function($parse) {
|
||||
var lastVal = NaN;
|
||||
var callCount = 0;
|
||||
var listener = function(val) { callCount++; lastVal = val; };
|
||||
|
||||
scope.$watch('{val: val}', listener);
|
||||
|
||||
scope.$apply('val = 1');
|
||||
expect(callCount).toBe(1);
|
||||
expect(lastVal).toEqual({val: 1});
|
||||
|
||||
scope.$apply('val = []');
|
||||
expect(callCount).toBe(2);
|
||||
expect(lastVal).toEqual({val: []});
|
||||
|
||||
scope.$apply('val = []');
|
||||
expect(callCount).toBe(3);
|
||||
expect(lastVal).toEqual({val: []});
|
||||
|
||||
scope.$apply('val = {}');
|
||||
expect(callCount).toBe(4);
|
||||
expect(lastVal).toEqual({val: {}});
|
||||
}));
|
||||
|
||||
it('should only watch the direct inputs to literals', inject(function($parse) {
|
||||
var lastVal = NaN;
|
||||
var callCount = 0;
|
||||
var listener = function(val) { callCount++; lastVal = val; };
|
||||
|
||||
scope.$watch('{val: val}', listener);
|
||||
|
||||
scope.$apply('val = 1');
|
||||
expect(callCount).toBe(1);
|
||||
expect(lastVal).toEqual({val: 1});
|
||||
|
||||
scope.$apply('val = [2]');
|
||||
expect(callCount).toBe(2);
|
||||
expect(lastVal).toEqual({val: [2]});
|
||||
|
||||
scope.$apply('val.push(3)');
|
||||
expect(callCount).toBe(2);
|
||||
|
||||
scope.$apply('val.length = 0');
|
||||
expect(callCount).toBe(2);
|
||||
}));
|
||||
|
||||
it('should only watch the direct inputs to nested literals', inject(function($parse) {
|
||||
var lastVal = NaN;
|
||||
var callCount = 0;
|
||||
var listener = function(val) { callCount++; lastVal = val; };
|
||||
|
||||
scope.$watch('[{val: [val]}]', listener);
|
||||
|
||||
scope.$apply('val = 1');
|
||||
expect(callCount).toBe(1);
|
||||
expect(lastVal).toEqual([{val: [1]}]);
|
||||
|
||||
scope.$apply('val = [2]');
|
||||
expect(callCount).toBe(2);
|
||||
expect(lastVal).toEqual([{val: [[2]]}]);
|
||||
|
||||
scope.$apply('val.push(3)');
|
||||
expect(callCount).toBe(2);
|
||||
|
||||
scope.$apply('val.length = 0');
|
||||
expect(callCount).toBe(2);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('locals', function() {
|
||||
|
||||
@@ -45,6 +45,25 @@ describe('$sniffer', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should be true on NW.js apps (which look similar to Chrome Packaged Apps)', function() {
|
||||
var mockWindow = {
|
||||
history: {
|
||||
pushState: noop
|
||||
},
|
||||
chrome: {
|
||||
app: {
|
||||
runtime: {}
|
||||
}
|
||||
},
|
||||
nw: {
|
||||
process: {}
|
||||
}
|
||||
};
|
||||
|
||||
expect(sniffer(mockWindow).history).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should be false on Chrome Packaged Apps', function() {
|
||||
// Chrome Packaged Apps are not allowed to access `window.history.pushState`.
|
||||
// In Chrome, `window.app` might be available in "normal" webpages, but `window.app.runtime`
|
||||
|
||||
@@ -168,7 +168,7 @@ describe('animations', function() {
|
||||
inject(function($animate, $rootScope) {
|
||||
$animate.enter(element, parent);
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
expect(element[0].parentNode).toEqual(parent[0]);
|
||||
|
||||
hidden = false;
|
||||
@@ -188,7 +188,7 @@ describe('animations', function() {
|
||||
|
||||
$animate.enter(element, parent);
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
element.addClass('only-allow-this-animation');
|
||||
|
||||
@@ -208,7 +208,7 @@ describe('animations', function() {
|
||||
|
||||
$animate.enter(svgElement, parent);
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
svgElement.attr('class', 'element only-allow-this-animation-svg');
|
||||
|
||||
@@ -290,7 +290,7 @@ describe('animations', function() {
|
||||
$animate.leave(element);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
expect(element[0].parentNode).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -314,9 +314,9 @@ describe('animations', function() {
|
||||
|
||||
$animate.enter(element, parent);
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
}));
|
||||
|
||||
it('should disable all animations on the given element',
|
||||
@@ -328,15 +328,15 @@ describe('animations', function() {
|
||||
expect($animate.enabled(element)).toBeFalsy();
|
||||
|
||||
$animate.addClass(element, 'red');
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
$animate.enabled(element, true);
|
||||
expect($animate.enabled(element)).toBeTruthy();
|
||||
|
||||
$animate.addClass(element, 'blue');
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeTruthy();
|
||||
}));
|
||||
@@ -347,14 +347,14 @@ describe('animations', function() {
|
||||
$animate.enabled(parent, false);
|
||||
|
||||
$animate.enter(element, parent);
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
$animate.enabled(parent, true);
|
||||
|
||||
$animate.enter(element, parent);
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeTruthy();
|
||||
}));
|
||||
@@ -370,11 +370,11 @@ describe('animations', function() {
|
||||
|
||||
$animate.addClass(element, 'red');
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
$animate.addClass(child, 'red');
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
$animate.enabled(element, true);
|
||||
|
||||
@@ -402,7 +402,7 @@ describe('animations', function() {
|
||||
$rootScope.items = [1,2,3,4,5];
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
}));
|
||||
|
||||
it('should not attempt to perform an animation on a text node element',
|
||||
@@ -414,7 +414,7 @@ describe('animations', function() {
|
||||
$animate.addClass(textNode, 'some-class');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
}));
|
||||
|
||||
it('should not attempt to perform an animation on an empty jqLite collection',
|
||||
@@ -426,7 +426,7 @@ describe('animations', function() {
|
||||
$animate.addClass(emptyNode, 'some-class');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
})
|
||||
);
|
||||
|
||||
@@ -439,7 +439,7 @@ describe('animations', function() {
|
||||
|
||||
$animate.leave(textNode);
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
expect(textNode[0].parentNode).not.toBe(parentNode);
|
||||
}));
|
||||
|
||||
@@ -455,10 +455,32 @@ describe('animations', function() {
|
||||
|
||||
$animate.leave(commentNode);
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
expect(commentNode[0].parentNode).not.toBe(parentNode);
|
||||
}));
|
||||
|
||||
it('enter() should animate a transcluded clone with `templateUrl`', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('foo', function() {
|
||||
return {templateUrl: 'foo.html'};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($animate, $compile, $rootScope, $templateCache) {
|
||||
parent.append(jqLite('<foo ng-if="showFoo"></foo>'));
|
||||
$templateCache.put('foo.html', '<div>FOO</div>');
|
||||
$compile(parent)($rootScope);
|
||||
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
$rootScope.$apply('showFoo = true');
|
||||
|
||||
expect(parent.text()).toBe('parentFOO');
|
||||
expect(capturedAnimation[0].html()).toBe('<div>FOO</div>');
|
||||
expect(capturedAnimation[1]).toBe('enter');
|
||||
});
|
||||
});
|
||||
|
||||
it('enter() should issue an enter animation and fire the DOM operation right away before the animation kicks off', inject(function($animate, $rootScope) {
|
||||
expect(parent.children().length).toBe(0);
|
||||
|
||||
@@ -667,13 +689,13 @@ describe('animations', function() {
|
||||
|
||||
$animate.removeClass(element, 'something-to-remove');
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
element.addClass('something-to-add');
|
||||
|
||||
$animate.addClass(element, 'something-to-add');
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -689,7 +711,7 @@ describe('animations', function() {
|
||||
parent.append(element);
|
||||
$animate.animate(element, null, toStyle);
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -700,7 +722,7 @@ describe('animations', function() {
|
||||
parent.append(element);
|
||||
$animate.animate(element, fromStyle);
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
}));
|
||||
|
||||
it('should perform an animation if only from styles are provided as well as any valid classes',
|
||||
@@ -712,7 +734,7 @@ describe('animations', function() {
|
||||
var options = { removeClass: 'goop' };
|
||||
$animate.animate(element, fromStyle, null, null, options);
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
fromStyle = { color: 'blue' };
|
||||
options = { addClass: 'goop' };
|
||||
@@ -816,11 +838,11 @@ describe('animations', function() {
|
||||
|
||||
var elm1 = $compile('<div class="animated"></div>')($rootScope);
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
$animate.addClass(elm1, 'klass2');
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
}));
|
||||
|
||||
it('should skip animations if the element is attached to the $rootElement, but not apart of the body',
|
||||
@@ -834,22 +856,22 @@ describe('animations', function() {
|
||||
newParent.append($rootElement);
|
||||
$rootElement.append(elm1);
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
$animate.addClass(elm1, 'klass2');
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
}));
|
||||
|
||||
it('should skip the animation if the element is removed from the DOM before the post digest kicks in',
|
||||
inject(function($animate, $rootScope) {
|
||||
|
||||
$animate.enter(element, parent);
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
element.remove();
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
}));
|
||||
|
||||
it('should be blocked when there is an ongoing structural parent animation occurring',
|
||||
@@ -857,7 +879,7 @@ describe('animations', function() {
|
||||
|
||||
parent.append(element);
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
$animate.move(parent, parent2);
|
||||
$rootScope.$digest();
|
||||
|
||||
@@ -867,7 +889,7 @@ describe('animations', function() {
|
||||
|
||||
$animate.addClass(element, 'blue');
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
}));
|
||||
|
||||
it('should disable all child animations for atleast one turn when a structural animation is issued',
|
||||
@@ -917,7 +939,7 @@ describe('animations', function() {
|
||||
|
||||
parent.append(element);
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
$animate.addClass(parent, 'rogers');
|
||||
$rootScope.$digest();
|
||||
|
||||
@@ -940,7 +962,7 @@ describe('animations', function() {
|
||||
$animate.addClass(element, 'rumlow');
|
||||
$animate.move(parent, null, parent2);
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
expect(capturedAnimationHistory.length).toBe(0);
|
||||
$rootScope.$digest();
|
||||
|
||||
@@ -1193,12 +1215,12 @@ describe('animations', function() {
|
||||
inject(function($animate, $rootScope) {
|
||||
|
||||
$animate.enter(element, parent);
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
$animate.addClass(element, 'red');
|
||||
expect(element).not.toHaveClass('red');
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(capturedAnimation[1]).toBe('enter');
|
||||
@@ -1227,7 +1249,7 @@ describe('animations', function() {
|
||||
$animate.removeClass(element, 'red');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
$animate.addClass(element, 'blue');
|
||||
$rootScope.$digest();
|
||||
@@ -1344,7 +1366,7 @@ describe('animations', function() {
|
||||
$animate.removeClass(element, 'four');
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
}));
|
||||
|
||||
it('but not skip the animation if it is a structural animation and if there are no classes to be animated',
|
||||
@@ -1385,7 +1407,7 @@ describe('animations', function() {
|
||||
expect(capturedAnimation[1]).toBe('leave');
|
||||
|
||||
// $$hashKey causes comparison issues
|
||||
expect(element.parent()[0]).toEqual(parent[0]);
|
||||
expect(element.parent()[0]).toBe(parent[0]);
|
||||
|
||||
options = capturedAnimation[2];
|
||||
expect(options.addClass).toEqual('pink');
|
||||
@@ -1632,7 +1654,7 @@ describe('animations', function() {
|
||||
|
||||
$animate.addClass(animateElement, 'red');
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
// Pin the element to the app root to enable animations
|
||||
$animate.pin(pinElement, $rootElement);
|
||||
@@ -1676,13 +1698,13 @@ describe('animations', function() {
|
||||
|
||||
$animate.addClass(animateElement, 'red');
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
$animate.pin(pinElement, pinTargetElement);
|
||||
|
||||
$animate.addClass(animateElement, 'blue');
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
dealoc(pinElement);
|
||||
});
|
||||
@@ -1720,7 +1742,7 @@ describe('animations', function() {
|
||||
|
||||
$animate.addClass(animateElement, 'blue');
|
||||
$rootScope.$digest();
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
expect(capturedAnimation).toBeNull();
|
||||
|
||||
$animate.enabled(pinHostElement, true);
|
||||
|
||||
@@ -2437,7 +2459,6 @@ describe('animations', function() {
|
||||
|
||||
return function($rootElement, $q, $animate, $$AnimateRunner, $document) {
|
||||
defaultFakeAnimationRunner = new $$AnimateRunner();
|
||||
$animate.enabled(true);
|
||||
|
||||
element = jqLite('<div class="element">element</div>');
|
||||
parent = jqLite('<div class="parent1">parent</div>');
|
||||
@@ -2445,7 +2466,6 @@ describe('animations', function() {
|
||||
|
||||
$rootElement.append(parent);
|
||||
$rootElement.append(parent2);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
};
|
||||
}));
|
||||
|
||||
|
||||
Vendored
+12
-19
@@ -795,23 +795,6 @@ describe('ngMock', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('module cleanup', function() {
|
||||
function testFn() {
|
||||
|
||||
}
|
||||
|
||||
it('should add hashKey to module function', function() {
|
||||
module(testFn);
|
||||
inject(function() {
|
||||
expect(testFn.$$hashKey).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should cleanup hashKey after previous test', function() {
|
||||
expect(testFn.$$hashKey).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('$inject cleanup', function() {
|
||||
function testFn() {
|
||||
|
||||
@@ -2432,13 +2415,15 @@ describe('ngMock', function() {
|
||||
|
||||
describe('ngMockE2E', function() {
|
||||
describe('$httpBackend', function() {
|
||||
var hb, realHttpBackend, callback;
|
||||
var hb, realHttpBackend, realHttpBackendBrowser, callback;
|
||||
|
||||
beforeEach(function() {
|
||||
callback = jasmine.createSpy('callback');
|
||||
angular.module('ng').config(function($provide) {
|
||||
realHttpBackend = jasmine.createSpy('real $httpBackend');
|
||||
$provide.value('$httpBackend', realHttpBackend);
|
||||
$provide.factory('$httpBackend', ['$browser', function($browser) {
|
||||
return realHttpBackend.and.callFake(function() { realHttpBackendBrowser = $browser; });
|
||||
}]);
|
||||
});
|
||||
module('ngMockE2E');
|
||||
inject(function($injector) {
|
||||
@@ -2477,6 +2462,14 @@ describe('ngMockE2E', function() {
|
||||
expect(realHttpBackend).not.toHaveBeenCalled();
|
||||
expect(callback).toHaveBeenCalledOnceWith(200, 'passThrough override', '', '');
|
||||
}));
|
||||
|
||||
it('should pass through to an httpBackend that uses the same $browser service', inject(function($browser) {
|
||||
hb.when('GET', /\/passThrough\/.*/).passThrough();
|
||||
hb('GET', '/passThrough/23');
|
||||
|
||||
expect(realHttpBackend).toHaveBeenCalledOnce();
|
||||
expect(realHttpBackendBrowser).toBe($browser);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -777,6 +777,24 @@ describe('basic usage', function() {
|
||||
expect(json).toEqual({id: 123, number: '9876', $myProp: 'still here'});
|
||||
});
|
||||
|
||||
it('should not include $cancelRequest when resource is toJson\'ed', function() {
|
||||
$httpBackend.whenGET('/CreditCard').respond({});
|
||||
|
||||
var CreditCard = $resource('/CreditCard', {}, {
|
||||
get: {
|
||||
method: 'GET',
|
||||
cancellable: true
|
||||
}
|
||||
});
|
||||
|
||||
var card = CreditCard.get();
|
||||
var json = card.toJSON();
|
||||
|
||||
expect(card.$cancelRequest).toBeDefined();
|
||||
expect(json.$cancelRequest).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
describe('promise api', function() {
|
||||
|
||||
var $rootScope;
|
||||
@@ -1438,6 +1456,18 @@ describe('basic usage', function() {
|
||||
$httpBackend.expect('POST', '/users/.json').respond();
|
||||
$resource('/users/\\.json').save({});
|
||||
});
|
||||
it('should work with save() if dynamic params', function() {
|
||||
$httpBackend.expect('POST', '/users/.json').respond();
|
||||
$resource('/users/:json', {json: '\\.json'}).save({});
|
||||
});
|
||||
it('should work with query() if dynamic params', function() {
|
||||
$httpBackend.expect('GET', '/users/.json').respond();
|
||||
$resource('/users/:json', {json: '\\.json'}).query();
|
||||
});
|
||||
it('should work with get() if dynamic params', function() {
|
||||
$httpBackend.expect('GET', '/users/.json').respond();
|
||||
$resource('/users/:json', {json: '\\.json'}).get();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1691,6 +1721,76 @@ describe('handling rejections', function() {
|
||||
expect($exceptionHandler.errors[0]).toMatch(/^Possibly unhandled rejection/);
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
it('should not swallow exceptions in success callback when error callback is provided',
|
||||
function() {
|
||||
$httpBackend.expectGET('/CreditCard/123').respond(null);
|
||||
var CreditCard = $resource('/CreditCard/:id');
|
||||
var cc = CreditCard.get({id: 123},
|
||||
function(res) { throw new Error('should be caught'); },
|
||||
function() {});
|
||||
|
||||
$httpBackend.flush();
|
||||
expect($exceptionHandler.errors.length).toBe(1);
|
||||
expect($exceptionHandler.errors[0]).toMatch(/^Error: should be caught/);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
it('should not swallow exceptions in success callback when error callback is not provided',
|
||||
function() {
|
||||
$httpBackend.expectGET('/CreditCard/123').respond(null);
|
||||
var CreditCard = $resource('/CreditCard/:id');
|
||||
var cc = CreditCard.get({id: 123},
|
||||
function(res) { throw new Error('should be caught'); });
|
||||
|
||||
$httpBackend.flush();
|
||||
expect($exceptionHandler.errors.length).toBe(1);
|
||||
expect($exceptionHandler.errors[0]).toMatch(/^Error: should be caught/);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
it('should not swallow exceptions in success callback when error callback is provided and has responseError interceptor',
|
||||
function() {
|
||||
$httpBackend.expectGET('/CreditCard/123').respond(null);
|
||||
var CreditCard = $resource('/CreditCard/:id', null, {
|
||||
get: {
|
||||
method: 'GET',
|
||||
interceptor: {responseError: function() {}}
|
||||
}
|
||||
});
|
||||
|
||||
var cc = CreditCard.get({id: 123},
|
||||
function(res) { throw new Error('should be caught'); },
|
||||
function() {});
|
||||
|
||||
$httpBackend.flush();
|
||||
expect($exceptionHandler.errors.length).toBe(1);
|
||||
expect($exceptionHandler.errors[0]).toMatch(/^Error: should be caught/);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
it('should not swallow exceptions in success callback when error callback is not provided and has responseError interceptor',
|
||||
function() {
|
||||
$httpBackend.expectGET('/CreditCard/123').respond(null);
|
||||
var CreditCard = $resource('/CreditCard/:id', null, {
|
||||
get: {
|
||||
method: 'GET',
|
||||
interceptor: {responseError: function() {}}
|
||||
}
|
||||
});
|
||||
|
||||
var cc = CreditCard.get({id: 123},
|
||||
function(res) { throw new Error('should be caught'); });
|
||||
|
||||
$httpBackend.flush();
|
||||
expect($exceptionHandler.errors.length).toBe(1);
|
||||
expect($exceptionHandler.errors[0]).toMatch(/^Error: should be caught/);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('cancelling requests', function() {
|
||||
|
||||
@@ -2082,4 +2082,188 @@ describe('$route', function() {
|
||||
expect(function() { $route.updateParams(); }).toThrowMinErr('ngRoute', 'norout');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('testability', function() {
|
||||
it('should wait for $resolve promises before calling callbacks', function() {
|
||||
var deferred;
|
||||
|
||||
module(function($provide, $routeProvider) {
|
||||
$routeProvider.when('/path', {
|
||||
template: '',
|
||||
resolve: {
|
||||
a: function($q) {
|
||||
deferred = $q.defer();
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($location, $route, $rootScope, $httpBackend, $$testability) {
|
||||
$location.path('/path');
|
||||
$rootScope.$digest();
|
||||
|
||||
var callback = jasmine.createSpy('callback');
|
||||
$$testability.whenStable(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
deferred.resolve();
|
||||
$rootScope.$digest();
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call callback after $resolve promises are rejected', function() {
|
||||
var deferred;
|
||||
|
||||
module(function($provide, $routeProvider) {
|
||||
$routeProvider.when('/path', {
|
||||
template: '',
|
||||
resolve: {
|
||||
a: function($q) {
|
||||
deferred = $q.defer();
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($location, $route, $rootScope, $httpBackend, $$testability) {
|
||||
$location.path('/path');
|
||||
$rootScope.$digest();
|
||||
|
||||
var callback = jasmine.createSpy('callback');
|
||||
$$testability.whenStable(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
deferred.reject();
|
||||
$rootScope.$digest();
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should wait for resolveRedirectTo promises before calling callbacks', function() {
|
||||
var deferred;
|
||||
|
||||
module(function($provide, $routeProvider) {
|
||||
$routeProvider.when('/path', {
|
||||
resolveRedirectTo: function($q) {
|
||||
deferred = $q.defer();
|
||||
return deferred.promise;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($location, $route, $rootScope, $httpBackend, $$testability) {
|
||||
$location.path('/path');
|
||||
$rootScope.$digest();
|
||||
|
||||
var callback = jasmine.createSpy('callback');
|
||||
$$testability.whenStable(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
deferred.resolve();
|
||||
$rootScope.$digest();
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call callback after resolveRedirectTo promises are rejected', function() {
|
||||
var deferred;
|
||||
|
||||
module(function($provide, $routeProvider) {
|
||||
$routeProvider.when('/path', {
|
||||
resolveRedirectTo: function($q) {
|
||||
deferred = $q.defer();
|
||||
return deferred.promise;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($location, $route, $rootScope, $httpBackend, $$testability) {
|
||||
$location.path('/path');
|
||||
$rootScope.$digest();
|
||||
|
||||
var callback = jasmine.createSpy('callback');
|
||||
$$testability.whenStable(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
deferred.reject();
|
||||
$rootScope.$digest();
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should wait for all route promises before calling callbacks', function() {
|
||||
var deferreds = {};
|
||||
|
||||
module(function($provide, $routeProvider) {
|
||||
// While normally `$browser.defer()` modifies the `outstandingRequestCount`, the mocked
|
||||
// version (provided by `ngMock`) does not. This doesn't matter in most tests, but in this
|
||||
// case we need the `outstandingRequestCount` logic to ensure that we don't call the
|
||||
// `$$testability.whenStable()` callbacks part way through a `$rootScope.$evalAsync` block.
|
||||
// See ngRoute's commitRoute()'s finally() block for details.
|
||||
$provide.decorator('$browser', function($delegate) {
|
||||
var oldDefer = $delegate.defer;
|
||||
var newDefer = function(fn, delay) {
|
||||
var requestCountAwareFn = function() { $delegate.$$completeOutstandingRequest(fn); };
|
||||
$delegate.$$incOutstandingRequestCount();
|
||||
return oldDefer.call($delegate, requestCountAwareFn, delay);
|
||||
};
|
||||
|
||||
$delegate.defer = angular.extend(newDefer, oldDefer);
|
||||
|
||||
return $delegate;
|
||||
});
|
||||
|
||||
addRouteWithAsyncRedirect('/foo', '/bar');
|
||||
addRouteWithAsyncRedirect('/bar', '/baz');
|
||||
addRouteWithAsyncRedirect('/baz', '/qux');
|
||||
$routeProvider.when('/qux', {
|
||||
template: '',
|
||||
resolve: {
|
||||
a: function($q) {
|
||||
var deferred = deferreds['/qux'] = $q.defer();
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Helpers
|
||||
function addRouteWithAsyncRedirect(fromPath, toPath) {
|
||||
$routeProvider.when(fromPath, {
|
||||
resolveRedirectTo: function($q) {
|
||||
var deferred = deferreds[fromPath] = $q.defer();
|
||||
return deferred.promise.then(function() { return toPath; });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
inject(function($browser, $location, $rootScope, $route, $$testability) {
|
||||
$location.path('/foo');
|
||||
$rootScope.$digest();
|
||||
|
||||
var callback = jasmine.createSpy('callback');
|
||||
$$testability.whenStable(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
deferreds['/foo'].resolve();
|
||||
$browser.defer.flush();
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
deferreds['/bar'].resolve();
|
||||
$browser.defer.flush();
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
deferreds['/baz'].resolve();
|
||||
$browser.defer.flush();
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
deferreds['/qux'].resolve();
|
||||
$browser.defer.flush();
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user