Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d4e6036a9 | |||
| 4d885cbd62 | |||
| eec2020518 | |||
| 2901c53f42 | |||
| e80053d91f | |||
| fa12c3c86a | |||
| cf43ccdf9b | |||
| a9352c19ce | |||
| 6f19a6fd33 | |||
| f30163e63a | |||
| 5b23bc9b07 | |||
| 9d1e87a3f1 | |||
| 6604c23614 | |||
| 195deca6e2 | |||
| 85eb9660ef | |||
| d81ff8885b | |||
| eca14d98a4 | |||
| 22ecbc50f4 | |||
| 7f857e44a2 | |||
| be6920b356 | |||
| 8eabc5463c | |||
| 14ff529fbb | |||
| fbad280570 | |||
| 8b775a0d58 | |||
| 0bbc6ee481 | |||
| 381b185117 | |||
| fa0d8c47c3 | |||
| 7e233eb58a | |||
| b7afd11d26 | |||
| 8582088b36 | |||
| 0db573b749 | |||
| 5e78af769e | |||
| 804e75045c | |||
| ebc3b7b1c3 | |||
| 830846f664 | |||
| 0462ee6659 | |||
| 9cc6835819 | |||
| ee1fc1dc13 | |||
| 41b36e689c | |||
| 52545e5a74 | |||
| 2abea7514a | |||
| f157d02793 | |||
| 77d8ae1d45 | |||
| e21b6ff3ff | |||
| 06016bb12c | |||
| 50e72fcae1 | |||
| b84e62bd28 | |||
| b6fd184a93 | |||
| 1db9e617ce | |||
| 0918f146e4 | |||
| 6dfd938bbc | |||
| 4f5a60bcca | |||
| e593939411 | |||
| 9e305948e4 | |||
| ed99821e4d | |||
| e057a9aa39 | |||
| e676d642f5 | |||
| 288b531626 | |||
| 7a4df50480 | |||
| 6550198003 | |||
| c6909ed144 | |||
| 187e43185d | |||
| 91834bcf37 | |||
| 841c090755 | |||
| da960544f1 | |||
| 74981c9f20 | |||
| dc4b06559f | |||
| 56138bdd63 | |||
| 0ccc4fcd89 | |||
| b7e2b01bb5 | |||
| 0c19482d03 | |||
| 0f7bcfdf93 | |||
| 9a26ab5870 | |||
| d906ed3100 | |||
| 0b16d10d2d | |||
| e0198c1cda | |||
| ba731ab850 | |||
| e69c1806a8 | |||
| b4770582f8 | |||
| 2a2fd14c08 | |||
| e4eb382f2d |
@@ -13,6 +13,7 @@ angular.js.tmproj
|
||||
bower_components/
|
||||
angular.xcodeproj
|
||||
.idea
|
||||
*.iml
|
||||
.agignore
|
||||
.lvimrc
|
||||
libpeerconnection.log
|
||||
|
||||
@@ -23,8 +23,16 @@
|
||||
"disallowTrailingWhitespace": true,
|
||||
"requireCommaBeforeLineBreak": true,
|
||||
"requireLineFeedAtFileEnd": true,
|
||||
"requireSpaceAfterBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
|
||||
"requireSpaceBeforeBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
|
||||
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
|
||||
"requireSpaceBeforeBlockStatements": true,
|
||||
"requireSpacesInConditionalExpression": {
|
||||
"afterTest": true,
|
||||
"beforeConsequent": true,
|
||||
"afterConsequent": true,
|
||||
"beforeAlternate": true
|
||||
},
|
||||
"requireSpacesInFunction": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
},
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
{
|
||||
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
|
||||
"requireSpaceAfterBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
|
||||
"requireSpaceBeforeBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
|
||||
"disallowImplicitTypeConversion": ["string"],
|
||||
"disallowMultipleLineBreaks": true,
|
||||
"disallowKeywordsOnNewLine": ["else"],
|
||||
|
||||
+104
@@ -1,3 +1,105 @@
|
||||
<a name="1.3.2"></a>
|
||||
# 1.3.2 cardiovasculatory-magnification (2014-11-07)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** do not rebind parent bound transclude functions
|
||||
([841c0907](https://github.com/angular/angular.js/commit/841c0907556f525dbc4223609d808319fe0dd7e2),
|
||||
[#9413](https://github.com/angular/angular.js/issues/9413))
|
||||
- **$parse:**
|
||||
- stateful interceptors override an `undefined` expression
|
||||
([ed99821e](https://github.com/angular/angular.js/commit/ed99821e4dc621864f7e2d9a6b5305fca27fb7fa),
|
||||
[#9821](https://github.com/angular/angular.js/issues/9821), [#9825](https://github.com/angular/angular.js/issues/9825))
|
||||
- add quick check for Function constructor in fast path
|
||||
([e676d642](https://github.com/angular/angular.js/commit/e676d642f5feb8d3ba88944634afb479ba525c36))
|
||||
- **$parse, events:** prevent accidental misuse of properties on $event
|
||||
([e057a9aa](https://github.com/angular/angular.js/commit/e057a9aa398ead209bd6bbf76e22d2d5562904fb))
|
||||
- **ngRoute:** allow proto inherited properties in route params object
|
||||
([b4770582](https://github.com/angular/angular.js/commit/b4770582f84f26c8ff7f2320a36a6b0ceff6e6cc),
|
||||
[#8181](https://github.com/angular/angular.js/issues/8181), [#9731](https://github.com/angular/angular.js/issues/9731))
|
||||
- **select:** use strict comparison for isSelected with selectAs
|
||||
([9e305948](https://github.com/angular/angular.js/commit/9e305948e4965fb86b0c79985dc6e8c59a9c66af),
|
||||
[#9639](https://github.com/angular/angular.js/issues/9639), [#9949](https://github.com/angular/angular.js/issues/9949))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngAria:** announce ngMessages with aria-live
|
||||
([187e4318](https://github.com/angular/angular.js/commit/187e43185dfb1bce6a318d95958c73cfb789d33c),
|
||||
[#9834](https://github.com/angular/angular.js/issues/9834))
|
||||
- **ngMock:** decorator that adds Scope#$countChildScopes and Scope#$countWatchers
|
||||
([74981c9f](https://github.com/angular/angular.js/commit/74981c9f208b3617cbf00beafd61138d25c5d546),
|
||||
[#9926](https://github.com/angular/angular.js/issues/9926), [#9871](https://github.com/angular/angular.js/issues/9871))
|
||||
|
||||
|
||||
## Security Note
|
||||
|
||||
This release also contains security fixes for expression sandbox bypasses.
|
||||
|
||||
These issues affect only applications with known server-side XSS holes that are also using [CSP](https://developer.mozilla.org/en-US/docs/Web/Security/CSP) to secure their client-side code. If your application falls into this rare category, we recommend updating your version of Angular.
|
||||
|
||||
We'd like to thank security researches [Sebastian Lekies](https://twitter.com/sebastianlekies), [Jann Horn](http://thejh.net/), and [Gábor Molnár](https://twitter.com/molnar_g) for reporting these issues to us.
|
||||
|
||||
We also added a documentation page focused on security, which contains some of the best practices, DOs and DON'Ts. Please check out [https://docs.angularjs.org/guide/security](https://docs.angularjs.org/guide/security).
|
||||
|
||||
|
||||
|
||||
<a name="1.3.1"></a>
|
||||
# 1.3.1 spectral-lobster (2014-10-31)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** returning null when an optional controller is not found
|
||||
([2cd5b4ec](https://github.com/angular/angular.js/commit/2cd5b4ec4409a818ccd33a6fbdeb99a3443a1809),
|
||||
[#9404](https://github.com/angular/angular.js/issues/9404), [#9392](https://github.com/angular/angular.js/issues/9392))
|
||||
- **$observe:** check if the attribute is undefined
|
||||
([531a8de7](https://github.com/angular/angular.js/commit/531a8de72c439d8ddd064874bf364c00cedabb11),
|
||||
[#9707](https://github.com/angular/angular.js/issues/9707), [#9720](https://github.com/angular/angular.js/issues/9720))
|
||||
- **$parse:** support dirty-checking objects with null prototype
|
||||
([28661d1a](https://github.com/angular/angular.js/commit/28661d1a8cc3a8454bad7ae531e027b1256476c9),
|
||||
[#9568](https://github.com/angular/angular.js/issues/9568))
|
||||
- **$sce:** use msie instead of $document[0].documentMode
|
||||
([45252c3a](https://github.com/angular/angular.js/commit/45252c3a545336a0bac93be6ee28cde6afaa3cb4),
|
||||
[#9661](https://github.com/angular/angular.js/issues/9661))
|
||||
- **$templateRequest:** ignore JSON Content-Type header and content
|
||||
([1bd473eb](https://github.com/angular/angular.js/commit/1bd473eb4587900086e0b6b308dcf1dcfe9760d9),
|
||||
[#5756](https://github.com/angular/angular.js/issues/5756), [#9619](https://github.com/angular/angular.js/issues/9619))
|
||||
- **i18n:** rename datetimeSymbols to be camelCase
|
||||
([94f5a285](https://github.com/angular/angular.js/commit/94f5a285bfcf04d800afc462a7a37a3469d77f1a))
|
||||
- **loader:** fix double spaces
|
||||
([8b2f1a47](https://github.com/angular/angular.js/commit/8b2f1a47b584ceb98689f48538a2af73cd65dfd8),
|
||||
[#9630](https://github.com/angular/angular.js/issues/9630))
|
||||
- **ngMock:** $httpBackend should match data containing Date objects correctly
|
||||
([1025f6eb](https://github.com/angular/angular.js/commit/1025f6ebf4e5933a12920889be00cd8ac8a106fa),
|
||||
[#5127](https://github.com/angular/angular.js/issues/5127))
|
||||
- **ngSanitize:** attribute name: xmlns:href -> xlink:href
|
||||
([4cccf0f2](https://github.com/angular/angular.js/commit/4cccf0f2a89b002d63cb443e1e7b15f76dcef425),
|
||||
[#9769](https://github.com/angular/angular.js/issues/9769))
|
||||
- **select:** assign result of track exp to element value
|
||||
([4b4098bf](https://github.com/angular/angular.js/commit/4b4098bfcae64f69c70a22393de1f3d9a0d3dc46),
|
||||
[#9718](https://github.com/angular/angular.js/issues/9718), [#9592](https://github.com/angular/angular.js/issues/9592))
|
||||
- **templateRequest:** allow empty html template
|
||||
([52ceec22](https://github.com/angular/angular.js/commit/52ceec2229dc132b76da4e022c91474344f2d906),
|
||||
[#9581](https://github.com/angular/angular.js/issues/9581))
|
||||
- **testability:** escape regex chars in `findBindings` if using `exactMatch`
|
||||
([02aa4f4b](https://github.com/angular/angular.js/commit/02aa4f4b85ee15922a1f2de8ba78f562c18518d0),
|
||||
[#9595](https://github.com/angular/angular.js/issues/9595), [#9600](https://github.com/angular/angular.js/issues/9600))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** allow $watchCollection to be used in bi-directional bindings
|
||||
([40bbc981](https://github.com/angular/angular.js/commit/40bbc9817845bf75581daee5d0ec30980affb0f5),
|
||||
[#9725](https://github.com/angular/angular.js/issues/9725))
|
||||
- **ngSanitize:** accept SVG elements and attributes
|
||||
([a54b25d7](https://github.com/angular/angular.js/commit/a54b25d77999a85701dfc5396fef78e586a99667),
|
||||
[#9578](https://github.com/angular/angular.js/issues/9578), [#9751](https://github.com/angular/angular.js/issues/9751))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.3.0"></a>
|
||||
# 1.3.0 superluminal-nudge (2014-10-13)
|
||||
|
||||
@@ -1452,6 +1554,8 @@ Closes #8230
|
||||
|
||||
- **jQuery:** due to [9e7cb3c3](https://github.com/angular/angular.js/commit/9e7cb3c37543008e6236bb5a2c4536df2e1e43a9),
|
||||
Angular no longer supports jQuery versions below 2.1.1.
|
||||
- **$q:** due to [23bc92b1](https://github.com/angular/angular.js/commit/23bc92b17df882a907fb326320f0622717fefe7b),
|
||||
Promises methods are no longer enumerated when using for-loops with `hasOwnProperty` check. E.g. `angular.extends`
|
||||
|
||||
|
||||
<a name="1.2.22"></a>
|
||||
|
||||
Vendored
+2
@@ -5,6 +5,7 @@ var angularFiles = {
|
||||
'src/minErr.js',
|
||||
'src/Angular.js',
|
||||
'src/loader.js',
|
||||
'src/stringify.js',
|
||||
'src/AngularPublic.js',
|
||||
'src/jqLite.js',
|
||||
'src/apis.js',
|
||||
@@ -73,6 +74,7 @@ var angularFiles = {
|
||||
],
|
||||
|
||||
'angularLoader': [
|
||||
'stringify.js',
|
||||
'src/minErr.js',
|
||||
'src/loader.js'
|
||||
],
|
||||
|
||||
@@ -71,7 +71,8 @@ app.controller('DataController', function($scope, $rootScope) {
|
||||
date2: new Date(Math.random()*Date.now()),
|
||||
func: function(){ return star; },
|
||||
obj: data[i-1],
|
||||
keys: data[i-1] && (data[i-1].keys || Object.keys(data[i-1]))
|
||||
keys: data[i-1] && (data[i-1].keys || Object.keys(data[i-1])),
|
||||
constructor: data[i-1]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
<label for="complexPath">Complex Paths</label>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="radio" ng-model="expressionType" value="constructorPath" id="constructorPath">
|
||||
<label for="constructorPath">Constructor Paths</label>
|
||||
($parse special cases "constructor" for security)
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="radio" ng-model="expressionType" value="fieldAccess" id="fieldAccess">
|
||||
<label for="fieldAccess">Field Accessors</label>
|
||||
@@ -77,6 +83,17 @@
|
||||
<span bm-pe-watch="row.keys"></span>
|
||||
</li>
|
||||
|
||||
<li ng-switch-when="constructorPath" ng-repeat="(rowIdx, row) in ::data">
|
||||
<span bm-pe-watch="row.index"></span>
|
||||
<span bm-pe-watch="row.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.constructor.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.constructor.constructor.index"></span>
|
||||
</li>
|
||||
|
||||
<li ng-switch-when="complexPath" ng-repeat="(rowIdx, row) in ::data">
|
||||
<span bm-pe-watch="row.index"></span>
|
||||
<span bm-pe-watch="row.num0"></span>
|
||||
|
||||
@@ -128,6 +128,10 @@ h1,h2,h3,h4,h5,h6 {
|
||||
margin-bottom:5px;
|
||||
}
|
||||
|
||||
.nav-index-group .nav-index-listing.current a {
|
||||
color: #B52E31;
|
||||
}
|
||||
|
||||
.nav-breadcrumb {
|
||||
margin:4px 0;
|
||||
padding:0;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -13,6 +13,7 @@ angular.module('DocsController', [])
|
||||
$scope.navClass = function(navItem) {
|
||||
return {
|
||||
active: navItem.href && this.currentPage && this.currentPage.path,
|
||||
current: this.currentPage && this.currentPage.path === navItem.href,
|
||||
'nav-index-section': navItem.type === 'section'
|
||||
};
|
||||
};
|
||||
|
||||
@@ -161,6 +161,27 @@ or JavaScript callbacks.
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## {@link ngAria ngAria}
|
||||
|
||||
Use ngAria to inject common accessibility attributes into directives and improve the experience for users with disabilities.
|
||||
|
||||
<div class="alert alert-info">Include the **angular-aria.js** file and set ngAria as a dependency for this to work in your application.</div>
|
||||
|
||||
<table class="definition-table spaced">
|
||||
<tr>
|
||||
<td>
|
||||
{@link ngAria#service Services}
|
||||
</td>
|
||||
<td>
|
||||
<p>
|
||||
The {@link ngAria.$aria $aria} service contains helper methods for applying ARIA attributes to HTML.
|
||||
<p>
|
||||
<p>
|
||||
{@link ngAria.$ariaProvider $ariaProvider} is used for configuring ARIA attributes.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## {@link ngResource ngResource}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ Attempting to inject one controller into another will also throw an `Unknown pro
|
||||
|
||||
```
|
||||
angular.module('myModule', [])
|
||||
.controller('MyFirstController', function() { /* ... */ });
|
||||
.controller('MyFirstController', function() { /* ... */ })
|
||||
.controller('MySecondController', ['MyFirstController', function(MyFirstController) {
|
||||
// This controller throws an unknown provider error because
|
||||
// MyFirstController cannot be injected.
|
||||
|
||||
@@ -6,56 +6,360 @@
|
||||
|
||||
# Accessibility with ngAria
|
||||
|
||||
You can use the `ngAria` module to have common ARIA attributes automatically applied when you
|
||||
use certain directives. To enable `ngAria`, just require the module into your application and
|
||||
the code will hook into your ng-show/ng-hide, input, textarea, button, select and
|
||||
ng-required directives and add the appropriate ARIA states and properties.
|
||||
The goal of ngAria is to improve Angular's default accessibility by enabling common
|
||||
[ARIA](http://www.w3.org/TR/wai-aria/) attributes that convey state or semantic information for
|
||||
assistive technologies used by persons with disabilities.
|
||||
|
||||
Currently, the following attributes are implemented:
|
||||
* aria-hidden
|
||||
* aria-checked
|
||||
* aria-disabled
|
||||
* aria-required
|
||||
* aria-invalid
|
||||
* aria-multiline
|
||||
* aria-valuenow
|
||||
* aria-valuemin
|
||||
* aria-valuemax
|
||||
* tabindex
|
||||
##Including ngAria
|
||||
|
||||
You can disable individual attributes by using the `{@link ngAria.$ariaProvider#config config}` method.
|
||||
|
||||
###Example
|
||||
Using {@link ngAria ngAria} is as simple as requiring the ngAria module in your application. ngAria hooks into
|
||||
standard AngularJS directives and quietly injects accessibility support into your application
|
||||
at runtime.
|
||||
|
||||
```js
|
||||
angular.module('myApp', ['ngAria'])...
|
||||
```
|
||||
|
||||
Elements using `ng-model` with `required` or `ngRequired` directives will automatically have
|
||||
`aria-required` attributes with the proper corresponding values.
|
||||
###Using ngAria
|
||||
Most of what ngAria does is only visible "under the hood". To see the module in action, once you've
|
||||
added it as a dependency, you can test a few things:
|
||||
* Using your favorite element inspector, look for ngAria attributes in your own code.
|
||||
* Test using your keyboard to ensure `tabindex` is used correctly.
|
||||
* Fire up a screen reader such as VoiceOver to listen for ARIA support.
|
||||
[Helpful screen reader tips.](http://webaim.org/articles/screenreader_testing/)
|
||||
|
||||
##Supported directives
|
||||
Currently, ngAria interfaces with the following directives:
|
||||
|
||||
* <a href="#ngmodel">ngModel</a>
|
||||
* <a href="#ngdisabled">ngDisabled</a>
|
||||
* <a href="#ngshow">ngShow</a>
|
||||
* <a href="#nghide">ngHide</a>
|
||||
* <a href="#ngclick-and-ngdblclick">ngClick</a>
|
||||
* <a href="#ngclick-and-ngdblclick">ngDblClick</a>
|
||||
|
||||
<h2 id="ngmodel">ngModel</h2>
|
||||
|
||||
Most of ngAria's heavy lifting happens in the {@link ngModel ngModel}
|
||||
directive. For elements using ngModel, special attention is paid by ngAria if that element also
|
||||
has a a role or type of `checkbox`, `radio`, `range` or `textbox`.
|
||||
|
||||
For those elements using ngModel, ngAria will dynamically bind and update the following ARIA
|
||||
attributes (if they have not been explicitly specified by the developer):
|
||||
|
||||
* aria-checked
|
||||
* aria-valuemin
|
||||
* aria-valuemax
|
||||
* aria-valuenow
|
||||
* aria-invalid
|
||||
* aria-required
|
||||
|
||||
###Example
|
||||
|
||||
<example module="ngAria_ngModelExample" deps="angular-aria.js">
|
||||
<file name="index.html">
|
||||
<style>
|
||||
[role=checkbox] {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
[role=checkbox] .icon:before {
|
||||
content: '\2610';
|
||||
display: inline-block;
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
speak: none;
|
||||
}
|
||||
[role=checkbox].active .icon:before {
|
||||
content: '\2611';
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<form ng-controller="formsController">
|
||||
<some-checkbox role="checkbox" ng-model="checked" ng-class="{active: checked}"
|
||||
ng-disabled="isDisabled" ng-click="toggleCheckbox()"
|
||||
aria-label="Custom Checkbox" show-attrs>
|
||||
<span class="icon" aria-hidden="true"></span>
|
||||
Custom Checkbox
|
||||
</some-checkbox>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
var app = angular.module('ngAria_ngModelExample', ['ngAria'])
|
||||
.controller('formsController', function($scope){
|
||||
$scope.checked = false;
|
||||
$scope.toggleCheckbox = function(){
|
||||
$scope.checked = !$scope.checked;
|
||||
}
|
||||
})
|
||||
.directive('someCheckbox', function(){
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function($scope, $el, $attrs) {
|
||||
$el.on('keypress', function(event){
|
||||
event.preventDefault();
|
||||
if(event.keyCode === 32 || event.keyCode === 13){
|
||||
$scope.toggleCheckbox();
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.directive('showAttrs', function() {
|
||||
return function($scope, $el, $attrs) {
|
||||
var pre = document.createElement('pre');
|
||||
$el.after(pre);
|
||||
$scope.$watch(function() {
|
||||
var $attrs = {};
|
||||
Array.prototype.slice.call($el[0].attributes, 0).forEach(function(item) {
|
||||
if (item.name !== 'show-$attrs') {
|
||||
$attrs[item.name] = item.value;
|
||||
}
|
||||
});
|
||||
return $attrs;
|
||||
}, function(newAttrs, oldAttrs) {
|
||||
pre.textContent = JSON.stringify(newAttrs, null, 2);
|
||||
}, true);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</file>
|
||||
</example>
|
||||
|
||||
ngAria will also add `tabIndex`, ensuring custom elements with these roles will be reachable from
|
||||
the keyboard. It is still up to **you** as a developer to **ensure custom controls will be
|
||||
operable** from the keybard. Think of `ng-click` on a `<div>` or `<md-checkbox>`: you still need
|
||||
to bind `ng-keypress` to make it fully operable from the keyboard. As a rule, any time you create
|
||||
a widget involving user interaction, be sure to test it with your keyboard and at least one mobile
|
||||
and desktop screen reader (preferably more).
|
||||
|
||||
<h2 id="ngdisabled">ngDisabled</h2>
|
||||
|
||||
The `disabled` attribute is only valid for certain elements such as `button`, `input` and
|
||||
`textarea`. To properly disable custom element directives such as `<md-checkbox>` or `<taco-tab>`,
|
||||
using ngAria with [ngDisabled](https://docs.angularjs.org/api/ng/directive/ngDisabled) will also
|
||||
add `aria-disabled`. This tells assistive technologies when a non-native input is disabled, helping
|
||||
custom controls to be more accessible.
|
||||
|
||||
###Example
|
||||
|
||||
```html
|
||||
<material-input ng-model="val" required>
|
||||
<md-checkbox ng-disabled="disabled">
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<material-input ng-model="val" required aria-required="true">
|
||||
<md-checkbox disabled aria-disabled="true">
|
||||
```
|
||||
|
||||
ngAria is just a starting point. You'll have to manually choose how to implement some
|
||||
accessibility features.
|
||||
>You can check whether a control is legitimately disabled for a screen reader by visiting
|
||||
[chrome://accessibility](chrome://accessibility).
|
||||
|
||||
For instance, you may want to add `ng-keypress` bindings alongside `ng-click` to make keyboard
|
||||
navigation easier.
|
||||
<h2 id="ngshow">ngShow</h2>
|
||||
|
||||
>The [ngShow](https://docs.angularjs.org/api/ng/directive/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.
|
||||
|
||||
## Additional Resources
|
||||
In its default setup, ngAria for `ngShow` is actually redundant. It toggles `aria-hidden` on the
|
||||
directive when it is hidden or shown. However, the default CSS of `display: none !important`,
|
||||
already hides child elements from a screen reader. It becomes more useful when the default
|
||||
CSS is overridden with properties that don’t affect assistive technologies, such as `opacity`
|
||||
or `transform`. By toggling `aria-hidden` dynamically with ngAria, we can ensure content visually
|
||||
hidden with this technique will not be read aloud in a screen reader.
|
||||
|
||||
One caveat with this combination of CSS and `aria-hidden`: you must also remove links and other
|
||||
interactive child elements from the tab order using `tabIndex=“-1”` on each control. This ensures
|
||||
screen reader users won't accidentally focus on "mystery elements". Managing tab index on every
|
||||
child control can be complex and affect performance, so it’s best to just stick with the default
|
||||
`display: none` CSS. See the [fourth rule of ARIA use](http://www.w3.org/TR/aria-in-html/#fourth-rule-of-aria-use).
|
||||
|
||||
###Example
|
||||
```css
|
||||
.ng-hide {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
}
|
||||
```
|
||||
```html
|
||||
<div ng-show="false" class="ng-hide" aria-hidden="true"></div>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<div ng-show="true" aria-hidden="false"></div>
|
||||
```
|
||||
*Note: Child links, buttons or other interactive controls must also be removed from the tab order.*
|
||||
|
||||
<h2 id="nghide">ngHide</h2>
|
||||
|
||||
>The [ngHide](https://docs.angularjs.org/api/ng/directive/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 default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redundant. It toggles
|
||||
`aria-hidden` on the directive when it is hidden or shown, but the content is already hidden with
|
||||
`display: none`. See explanation for <a href="#ngshow">ngShow</a> when overriding the default CSS.
|
||||
|
||||
<h2 id="ngclick-and-ngdblclick">ngClick and ngDblclick</h2>
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex` if it isn't there already.
|
||||
Even with this, you must currently still add `ng-keypress` to non-interactive elements such as `div`
|
||||
or `taco-button` to enable keyboard access. Conversation is currently ongoing about whether ngAria
|
||||
should also bind `ng-keypress`.
|
||||
|
||||
<h3>Example</h3>
|
||||
```html
|
||||
<div ng-click="toggleMenu()"></div>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
```html
|
||||
<div ng-click="toggleMenu()" tabindex="0"></div>
|
||||
```
|
||||
*Note: ngAria still requires `ng-keypress` to be added manually to non-native controls like divs.*
|
||||
|
||||
<h2 id="ngmessages">ngMessages</h2>
|
||||
|
||||
The new ngMessages module makes it easy to display form validation or other messages with priority
|
||||
sequencing and animation. To expose these visual messages to screen readers,
|
||||
ngAria injects `aria-live="polite"`, causing them to be read aloud any time a message is shown,
|
||||
regardless of the user's focus location.
|
||||
###Example
|
||||
|
||||
```html
|
||||
<div ng-messages="myForm.myName.$error">
|
||||
<div ng-message="required">You did not enter a field</div>
|
||||
<div ng-message="maxlength">Your field is too long</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<div ng-messages="myForm.myName.$error" aria-live="polite">
|
||||
<div ng-message="required">You did not enter a field</div>
|
||||
<div ng-message="maxlength">Your field is too long</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
##Disabling attributes
|
||||
The attribute magic of ngAria may not work for every scenario. To disable individual attributes,
|
||||
you can use the {@link ngAria.$ariaProvider#config config} method. Just keep in mind this will
|
||||
tell ngAria to ignore the attribute globally.
|
||||
|
||||
<example module="ngAria_ngDisabledExample" deps="angular-aria.js">
|
||||
<file name="index.html">
|
||||
<style>
|
||||
[role=checkbox] {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
[role=checkbox] .icon:before {
|
||||
content: '\2610';
|
||||
display: inline-block;
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
speak: none;
|
||||
}
|
||||
[role=checkbox].active .icon:before {
|
||||
content: '\2611';
|
||||
}
|
||||
</style>
|
||||
<form ng-controller="formsController">
|
||||
<div ng-model="someModel" show-attrs>
|
||||
Div with ngModel and aria-invalid disabled
|
||||
</div>
|
||||
<div role="checkbox" ng-model="checked" ng-class="{active: checked}"
|
||||
aria-label="Custom Checkbox" ng-click="toggleCheckbox()" some-checkbox show-attrs>
|
||||
<span class="icon" aria-hidden="true"></span>
|
||||
Custom Checkbox for comparison
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
angular.module('ngAria_ngDisabledExample', ['ngAria'], function config($ariaProvider) {
|
||||
$ariaProvider.config({
|
||||
ariaInvalid: false,
|
||||
tabindex: true
|
||||
});
|
||||
})
|
||||
.controller('formsController', function($scope){
|
||||
$scope.checked = false;
|
||||
$scope.toggleCheckbox = function(){
|
||||
$scope.checked = !$scope.checked;
|
||||
}
|
||||
})
|
||||
.directive('someCheckbox', function(){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $el, $attrs) {
|
||||
$el.on('keypress', function(event){
|
||||
event.preventDefault();
|
||||
if(event.keyCode === 32 || event.keyCode === 13){
|
||||
$scope.toggleCheckbox();
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.directive('showAttrs', function() {
|
||||
return function(scope, el, attrs) {
|
||||
var pre = document.createElement('pre');
|
||||
el.after(pre);
|
||||
scope.$watch(function() {
|
||||
var attrs = {};
|
||||
Array.prototype.slice.call(el[0].attributes, 0).forEach(function(item) {
|
||||
if (item.name !== 'show-attrs') {
|
||||
attrs[item.name] = item.value;
|
||||
}
|
||||
});
|
||||
return attrs;
|
||||
}, function(newAttrs, oldAttrs) {
|
||||
pre.textContent = JSON.stringify(newAttrs, null, 2);
|
||||
}, true);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</file>
|
||||
</example>
|
||||
|
||||
##Common Accessibility Patterns
|
||||
|
||||
Accessibility best practices that apply to web apps in general also apply to Angular.
|
||||
|
||||
* [A11Y Project](http://a11yproject.com/)
|
||||
* [WebAim](http://webaim.org/)
|
||||
* [Using WAI-ARIA in HTML](http://www.w3.org/TR/2014/WD-aria-in-html-20140626/)
|
||||
* [Apps For All: Coding Accessible Web Applications](https://shop.smashingmagazine.com/apps-for-all-coding-accessible-web-applications.html)
|
||||
* **Text alternatives**: Add alternate text content to make visual information accessible using
|
||||
[these W3C guidelines](http://www.w3.org/TR/html-alt-techniques/). The appropriate technique
|
||||
depends on the specific markup but can be accomplished using offscreen spans, `aria-label` or
|
||||
label elements, image `alt` attributes, `figure`/`figcaption` elements and more.
|
||||
* **HTML Semantics**: If you're creating custom element directives, Web Components or HTML in
|
||||
general, use native elements wherever possible to utilize built-in events and properties.
|
||||
Alternatively, use ARIA to communicate semantic meaning. See [notes on ARIA use](http://www.w3.org/TR/aria-in-html/#notes-on-aria-use-in-html).
|
||||
* **Focus management**: Guide the user around the app as views are appended/removed.
|
||||
Focus should *never* be lost, as this causes unexpected behavior and much confusion (referred to
|
||||
as "freak-out mode").
|
||||
* **Announcing changes**: When filtering or other UI messaging happens away from the user's focus,
|
||||
notify with [ARIA Live Regions](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions).
|
||||
* **Color contrast and scale**: Make sure content is legible and interactive controls are usable
|
||||
at all screen sizes. Consider configurable UI themes for people with color blindness, low vision
|
||||
or other visual impairments.
|
||||
* **Progressive enhancement**: Some users do not browse with JavaScript enabled or do not have
|
||||
the latest browser. An accessible message about site requirements can inform users and improve
|
||||
the experience.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
* [Using ARIA in HTML](http://www.w3.org/TR/aria-in-html/)
|
||||
* [AngularJS Accessibility at ngEurope](https://www.youtube.com/watch?v=dmYDggEgU-s&list=UUEGUP3TJJfMsEM_1y8iviSQ)
|
||||
* [Testing with Screen Readers](http://webaim.org/articles/screenreader_testing/)
|
||||
* [Chrome Accessibility Developer Tools](https://chrome.google.com/webstore/detail/accessibility-developer-t/fpkknkljclfencbdbgkenhalefipecmb?hl=en)
|
||||
* [W3C Accessibility Testing](http://www.w3.org/wiki/Accessibility_testing)
|
||||
* [WebAIM](http://webaim.org)
|
||||
* [A11y Project](http://a11yproject.com)
|
||||
|
||||
@@ -145,7 +145,7 @@ This is the sequence that your code should follow:
|
||||
|
||||
## Deferred Bootstrap
|
||||
|
||||
This feature enables tools like [Batarang](github.com/angular/angularjs-batarang) and test runners
|
||||
This feature enables tools like [Batarang](https://github.com/angular/angularjs-batarang) and test runners
|
||||
to hook into angular's bootstrap process and sneak in more modules
|
||||
into the DI registry which can replace or augment DI services for
|
||||
the purpose of instrumentation or mocking out heavy dependencies.
|
||||
|
||||
@@ -332,7 +332,7 @@ The first issue we have to solve is that the dialog box template expects `title`
|
||||
But we would like the template's scope property `title` to be the result of interpolating the
|
||||
`<dialog>` element's `title` attribute (i.e. `"Hello {{username}}"`). Furthermore, the buttons expect
|
||||
the `onOk` and `onCancel` functions to be present in the scope. This limits the usefulness of the
|
||||
widget. To solve the mapping issue we use the `locals` to create local variables which the template
|
||||
widget. To solve the mapping issue we use the `scope` to create local variables which the template
|
||||
expects as follows:
|
||||
|
||||
```js
|
||||
|
||||
@@ -56,10 +56,10 @@ Try out the Live Preview above, and then let's walk through the example and desc
|
||||
|
||||
This looks like normal HTML, with some new markup. In Angular, a file like this is called a
|
||||
<a name="template">"{@link templates template}"</a>. When Angular starts your application, it parses and
|
||||
processes this new markup from the template using the so called <a name="compiler">"{@link compiler compiler}"</a>.
|
||||
processes this new markup from the template using the so-called <a name="compiler">"{@link compiler compiler}"</a>.
|
||||
The loaded, transformed and rendered DOM is then called the <a name="view">"view"</a>.
|
||||
|
||||
The first kind of new markup are the so called <a name="directive">"{@link directive directives}"</a>.
|
||||
The first kind of new markup are the so-called <a name="directive">"{@link directive directives}"</a>.
|
||||
They apply special behavior to attributes or elements in the HTML. In the example above we use the
|
||||
{@link ng.directive:ngApp `ng-app`} attribute, which is linked to a directive that automatically
|
||||
initializes our application. Angular also defines a directive for the {@link ng.directive:input `input`}
|
||||
@@ -89,7 +89,7 @@ A filter formats the value of an expression for display to the user.
|
||||
In the example above, the filter {@link ng.filter:currency `currency`} formats a number
|
||||
into an output that looks like money.
|
||||
|
||||
The important thing in the example is that angular provides _live_ bindings:
|
||||
The important thing in the example is that Angular provides _live_ bindings:
|
||||
Whenever the input values change, the value of the expressions are automatically
|
||||
recalculated and the DOM is updated with their values.
|
||||
The concept behind this is <a name="databinding">"{@link databinding two-way data binding}"</a>.
|
||||
@@ -150,13 +150,13 @@ different currencies and also pay the invoice.
|
||||
|
||||
What changed?
|
||||
|
||||
First, there is a new JavaScript file that contains a so called <a name="controller">"{@link controller controller}"</a>.
|
||||
First, there is a new JavaScript file that contains a so-called <a name="controller">"{@link controller controller}"</a>.
|
||||
More exactly, the file contains a constructor function that creates the actual controller instance.
|
||||
The purpose of controllers is to expose variables and functionality to expressions and directives.
|
||||
|
||||
Besides the new file that contains the controller code we also added a
|
||||
Besides the new file that contains the controller code we also added an
|
||||
{@link ng.directive:ngController `ng-controller`} directive to the HTML.
|
||||
This directive tells angular that the new `InvoiceController` is responsible for the element with the directive
|
||||
This directive tells Angular that the new `InvoiceController` is responsible for the element with the directive
|
||||
and all of the element's children.
|
||||
The syntax `InvoiceController as invoice` tells Angular to instantiate the controller
|
||||
and save it in the variable `invoice` in the current scope.
|
||||
@@ -263,7 +263,7 @@ services, ...) is created and wired using dependency injection. Within Angular,
|
||||
the DI container is called the <a name="injector">"{@link di injector}"</a>.
|
||||
|
||||
To use DI, there needs to be a place where all the things that should work together are registered.
|
||||
In Angular, this is the purpose of the so called <a name="module">"{@link module modules}"</a>.
|
||||
In Angular, this is the purpose of the so-called <a name="module">"{@link module modules}"</a>.
|
||||
When Angular starts, it will use the configuration of the module with the name defined by the `ng-app` directive,
|
||||
including the configuration of all modules that this module depends on.
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ Protractor is a [Node.js](http://nodejs.org) program, and runs end to end tests
|
||||
written in JavaScript and run with node. Protractor uses [WebDriver](https://code.google.com/p/selenium/wiki/GettingStarted)
|
||||
to control browsers and simulate user actions.
|
||||
|
||||
For more information on Protractor, view [getting started](https://github.com/angular/protractor/blob/master/docs/getting-started.md)
|
||||
or the [api docs](https://github.com/angular/protractor/blob/master/docs/api.md).
|
||||
For more information on Protractor, view [getting started](http://angular.github.io/protractor/#/getting-started)
|
||||
or the [api docs](http://angular.github.io/protractor/#/api).
|
||||
|
||||
Protractor uses [Jasmine](http://jasmine.github.io/1.3/introduction.html) for its test syntax.
|
||||
As in unit testing, a test file is comprised of one or
|
||||
|
||||
@@ -164,6 +164,12 @@ expression. The reason behind this is core to the Angular philosophy that applic
|
||||
be in controllers, not the views. If you need a real conditional, loop, or to throw from a view
|
||||
expression, delegate to a JavaScript method instead.
|
||||
|
||||
## No RegExp creation with literal notation
|
||||
|
||||
You can't create regular expressions from within AngularJS expressions. This is to avoid complex
|
||||
model transformation logic inside templates. Such logic is better placed in a controller or in a dedicated
|
||||
filter where it can be tested properly.
|
||||
|
||||
## `$event`
|
||||
|
||||
Directives like {@link ng.directive:ngClick `ngClick`} and {@link ng.directive:ngFocus `ngFocus`}
|
||||
@@ -314,7 +320,7 @@ someModule.directive('someDirective', function() {
|
||||
```
|
||||
|
||||
```html
|
||||
<div some-directive name=“::myName” color=“My color is {{::myColor}}”></div>
|
||||
<div some-directive name="::myName" color="My color is {{::myColor}}"></div>
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@ for other directives to augment its behavior.
|
||||
|
||||
Note that `novalidate` is used to disable browser's native form validation.
|
||||
|
||||
The value of `ngModel` won't be set unless it passes validation for the input field.
|
||||
For example: inputs of type `email` must have a value in the form of `user@domain`.
|
||||
|
||||
|
||||
|
||||
# Using CSS classes
|
||||
|
||||
@@ -41,9 +41,10 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
### Other AngularJS Features
|
||||
|
||||
* **Animation:** {@link guide/animations Core concepts}, {@link ngAnimate ngAnimate API}, and [Animation in AngularJS 1.2](http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html)
|
||||
* **Security:** {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
|
||||
* **Security:** {@link guide/security Security Docs}, {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
|
||||
* **Internationalization and Localization:** {@link guide/i18n Angular Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/)
|
||||
* **Mobile:** {@link ngTouch Touch events}
|
||||
* **Accessibility:** {@link guide/accessibility ngAria}
|
||||
|
||||
### Testing
|
||||
|
||||
@@ -107,6 +108,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller
|
||||
* [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner
|
||||
* [AngularJS : Novice to Ninja](http://www.amazon.in/AngularJS-Novice-Ninja-Sandeep-Panda/dp/0992279453) by Sandeep Panda
|
||||
* [AngularJS UI Development](http://www.amazon.com/AngularJS-UI-Development-Amit-Ghart-ebook/dp/B00OXVAK7A) by Amit Gharat and Matthias Nehlsen
|
||||
|
||||
###Videos:
|
||||
* [egghead.io](http://egghead.io/)
|
||||
|
||||
@@ -23,7 +23,7 @@ This is to disallow changing the behaviour of existing functions
|
||||
in an unforseen fashion.
|
||||
- due to [6081f207](https://github.com/angular/angular.js/commit/6081f20769e64a800ee8075c168412b21f026d99),
|
||||
|
||||
The (deprecated) __proto__ propery does not work inside angular expressions
|
||||
The (deprecated) __proto__ property does not work inside angular expressions
|
||||
anymore.
|
||||
- due to [48fa3aad](https://github.com/angular/angular.js/commit/48fa3aadd546036c7e69f71046f659ab1de244c6),
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ The above is a suggestion. Tailor it to your needs.
|
||||
# Module Loading & Dependencies
|
||||
|
||||
A module is a collection of configuration and run blocks which get applied to the application
|
||||
during the bootstrap process. In its simplest form the module consist of collection of two kinds
|
||||
during the bootstrap process. In its simplest form the module consist of a collection of two kinds
|
||||
of blocks:
|
||||
|
||||
1. **Configuration blocks** - get executed during the provider registrations and configuration
|
||||
|
||||
@@ -41,3 +41,35 @@ The page should reload and the debug information should now be available.
|
||||
|
||||
For more see the docs pages on {@link ng.$compileProvider#debugInfoEnabled `$compileProvider`}
|
||||
and {@link angular.reloadWithDebugInfo `angular.reloadWithDebugInfo`}.
|
||||
|
||||
## Strict DI Mode
|
||||
|
||||
Using strict di mode in your production application will throw errors when a injectable
|
||||
function is not
|
||||
{@link di#dependency-annotation annotated explicitly}. Strict di mode is intended to help
|
||||
you make sure that your code will work when minified. However, it also will force you to
|
||||
make sure that your injectable functions are explicitly annotated which will improve
|
||||
angular's performance when injecting dependencies in your injectable functions because it
|
||||
doesn't have to dynamically discover a function's dependencies. It is recommended to
|
||||
automate the explicit annotation via a tool like
|
||||
[ng-annotate](https://github.com/olov/ng-annotate) when you deploy to production (and enable
|
||||
strict di mode)
|
||||
|
||||
To enable strict di mode, you have two options:
|
||||
|
||||
```html
|
||||
<div ng-app="myApp" ng-strict-di>
|
||||
<!-- your app here -->
|
||||
</div>
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
angular.bootstrap(document, ['myApp'], {
|
||||
strictDi: true
|
||||
});
|
||||
```
|
||||
|
||||
For more information, see the
|
||||
{@link di#using-strict-dependency-injection DI Guide}.
|
||||
|
||||
@@ -339,6 +339,18 @@ the dirty checking function must be efficient. Care should be taken that the dir
|
||||
function does not do any DOM access, as DOM access is orders of magnitude slower than property
|
||||
access on JavaScript object.
|
||||
|
||||
### Scope `$watch` Depths
|
||||
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-scope-watch-strategies.png">
|
||||
|
||||
Dirty checking can be done with three strategies: By reference, by collection contents, and by value. The strategies differ in the kinds of changes they detect, and in their performance characteristics.
|
||||
|
||||
- Watching *by reference* ({@link
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener)`) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object, changes inside it are not detected. This is the most efficient stategy.
|
||||
- Watching *collection contents* ({@link
|
||||
ng.$rootScope.Scope#$watchCollection scope.$watchCollection} `(watchExpression, listener)`) detects changes that occur inside an array or an object: When items are added, removed, or reordered. The detection is shallow - it does not reach into nested collections. Watching collection contents is more expensive than watching by reference, because copies of the collection contents need to be maintained. However, the strategy attempts to minimize the amount of copying required.
|
||||
- Watching *by value* ({@link
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener, true)`) detects any change in an arbitrarily nested data structure. It is the most powerful change detection strategy, but also the most expensive. A full traversal of the nested data structure is needed on each digest, and a full copy of it needs to be held in memory.
|
||||
|
||||
## Integration with the browser event loop
|
||||
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-runtime.png">
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
@ngdoc overview
|
||||
@name Security
|
||||
@sortOrder 525
|
||||
@description
|
||||
|
||||
# Security
|
||||
|
||||
This document explains some of AngularJS's security features and best practices that you should
|
||||
keep in mind as you build your application.
|
||||
|
||||
|
||||
## Expression Sandboxing
|
||||
|
||||
AngularJS's expressions are sandboxed not for security reasons, but instead to maintain a proper
|
||||
separation of application responsibilities. For example, access to `window` is disallowed
|
||||
because it makes it easy to introduce brittle global state into your application.
|
||||
|
||||
However, this sandbox is not intended to stop attackers who can edit the template before it's
|
||||
processed by Angular. It may be possible to run arbitrary JavaScript inside double-curly bindings
|
||||
if an attacker can modify them.
|
||||
|
||||
But if an attacker can change arbitrary HTML templates, there's nothing stopping them from doing:
|
||||
|
||||
```html
|
||||
<script>somethingEvil();</script>
|
||||
```
|
||||
|
||||
It's better to design your application in such a way that users cannot change client-side templates.
|
||||
For instance:
|
||||
|
||||
* Do not mix client and server templates
|
||||
* Do not use user input to generate templates dynamically
|
||||
* Do not run user input through `$scope.$eval`
|
||||
* Consider using {@link ng.directive:ngCsp CSP} (but don't rely only on CSP)
|
||||
|
||||
## Mixing client-side and server-side templates
|
||||
|
||||
In general, we recommend against this because it can create unintended XSS vectors.
|
||||
|
||||
However, it's ok to mix server-side templating in the bootstrap template (`index.html`) as long
|
||||
as user input cannot be used on the server to output html that would then be processed by Angular
|
||||
in a way that would cause allow for arbitrary code execution.
|
||||
|
||||
For instance, you can use server-side templating to dynamically generate CSS, URLs, etc, but not
|
||||
for generating templates that are bootstrapped/compiled by Angular.
|
||||
|
||||
|
||||
## Reporting a security issue
|
||||
|
||||
Email us at [security@angularjs.org](mailto:security@angularjs.org) to report any potential
|
||||
security issues in AngularJS.
|
||||
|
||||
Please keep in mind the above points about Angular's expression language.
|
||||
|
||||
|
||||
## See also
|
||||
|
||||
* {@link ng.directive:ngCsp Content Security Policy}
|
||||
* {@link ng.$sce Strict Contextual Escaping}
|
||||
* {@link ngSanitize.$sanitize $sanitize}
|
||||
@@ -120,7 +120,7 @@ really is that easy to set up any functional, readable, end-to-end test.
|
||||
### Running End to End Tests with Protractor
|
||||
Even though the syntax of this test looks very much like our controller unit test written with
|
||||
Jasmine, the end-to-end test uses APIs of [Protractor](https://github.com/angular/protractor). Read
|
||||
about the Protractor APIs at https://github.com/angular/protractor/blob/master/docs/api.md.
|
||||
about the Protractor APIs at http://angular.github.io/protractor/#/api.
|
||||
|
||||
Much like Karma is the test runner for unit tests, we use Protractor to run end-to-end tests.
|
||||
Try it with `npm run protractor`. End-to-end tests are slow, so unlike with unit tests, Protractor
|
||||
|
||||
@@ -182,7 +182,7 @@ You can now rerun `npm run protractor` to see the tests run.
|
||||
# Experiments
|
||||
|
||||
* In the `PhoneListCtrl` controller, remove the statement that sets the `orderProp` value and
|
||||
you'll see that Angular will temporarily add a new "unknown" option to the drop-down list and the
|
||||
you'll see that Angular will temporarily add a new blank ("unknown") option to the drop-down list and the
|
||||
ordering will default to unordered/natural order.
|
||||
|
||||
* Add an `{{orderProp}}` binding into the `index.html` template to display its current value as
|
||||
|
||||
@@ -236,7 +236,9 @@ the response is received:
|
||||
```
|
||||
|
||||
* We flush the request queue in the browser by calling `$httpBackend.flush()`. This causes the
|
||||
promise returned by the `$http` service to be resolved with the trained response.
|
||||
promise returned by the `$http` service to be resolved with the trained response. See
|
||||
'Flushing HTTP requests' in the {@link ngMock.$httpBackend mock $httpBackend} documentation for
|
||||
a full explanation of why this is necessary.
|
||||
|
||||
* We make the assertions, verifying that the phone model now exists on the scope.
|
||||
|
||||
@@ -256,8 +258,8 @@ You should now see the following output in the Karma tab:
|
||||
|
||||
# Experiments
|
||||
|
||||
* At the bottom of `index.html`, add a `<pre>{{phones | json}}</pre>` binding to see the list of phones
|
||||
displayed in json format.
|
||||
* At the bottom of `index.html`, add a `<pre>{{phones | filter:query | orderBy:orderProp | json}}</pre>`
|
||||
binding to see the list of phones displayed in json format.
|
||||
|
||||
* In the `PhoneListCtrl` controller, pre-process the http response by limiting the number of phones
|
||||
to the first 5 in the list. Use the following code in the `$http` callback:
|
||||
|
||||
@@ -184,7 +184,7 @@ You can now rerun `npm run protractor` to see the tests run.
|
||||
|
||||
# Experiments
|
||||
|
||||
* Using the [Protractor API](https://github.com/angular/protractor/blob/master/docs/api.md),
|
||||
* Using the [Protractor API](http://angular.github.io/protractor/#/api),
|
||||
write a test that verifies that we display 4 thumbnail images on the Nexus S details page.
|
||||
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
In this final step, we will enhance our phonecat web application by attaching CSS and JavaScript
|
||||
animations on top of the template code we created before.
|
||||
|
||||
* Used the `ngAnimate` to enable animations throughout the application.
|
||||
* Common `ng` directives automatically trigger hooks for animations to tap into.
|
||||
* We now use the `ngAnimate` module to enable animations throughout the application.
|
||||
* We also use common `ng` directives to automatically trigger hooks for animations to tap into.
|
||||
* When an animation is found then the animation will run in between the standard DOM operation that
|
||||
is being issued on the element at the given time (e.g. inserting and removing nodes on
|
||||
{@link ngRepeat `ngRepeat`} or adding and removing classes on
|
||||
@@ -369,7 +369,8 @@ occur whenever the CSS class itself changes.
|
||||
Whenever a new phone thumbnail is selected, the state changes and the `.active` CSS class is added
|
||||
to the matching profile image and the animation plays.
|
||||
|
||||
Let's get started and tweak our HTML code on the `phone-detail.html` page first:
|
||||
Let's get started and tweak our HTML code on the `phone-detail.html` page first. Notice that we
|
||||
have changed the way we display our large image:
|
||||
|
||||
__`app/partials/phone-detail.html`.__
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
Generated
+6
-6
@@ -4810,7 +4810,7 @@
|
||||
}
|
||||
},
|
||||
"protractor": {
|
||||
"version": "1.3.1",
|
||||
"version": "1.4.0",
|
||||
"dependencies": {
|
||||
"request": {
|
||||
"version": "2.36.0",
|
||||
@@ -4834,7 +4834,7 @@
|
||||
"version": "0.12.1",
|
||||
"dependencies": {
|
||||
"punycode": {
|
||||
"version": "1.3.1"
|
||||
"version": "1.3.2"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4842,7 +4842,7 @@
|
||||
"version": "0.1.4",
|
||||
"dependencies": {
|
||||
"combined-stream": {
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.7",
|
||||
"dependencies": {
|
||||
"delayed-stream": {
|
||||
"version": "0.0.5"
|
||||
@@ -4897,7 +4897,7 @@
|
||||
}
|
||||
},
|
||||
"selenium-webdriver": {
|
||||
"version": "2.43.5",
|
||||
"version": "2.44.0",
|
||||
"dependencies": {
|
||||
"tmp": {
|
||||
"version": "0.0.24"
|
||||
@@ -4906,7 +4906,7 @@
|
||||
"version": "0.4.4",
|
||||
"dependencies": {
|
||||
"sax": {
|
||||
"version": "0.6.0"
|
||||
"version": "0.6.1"
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "2.4.4",
|
||||
@@ -4966,7 +4966,7 @@
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.8",
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.1.32",
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@
|
||||
"marked": "~0.3.0",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"promises-aplus-tests": "~2.1.0",
|
||||
"protractor": "1.3.1",
|
||||
"protractor": "1.4.0",
|
||||
"q": "~1.0.0",
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"angularModule": false,
|
||||
"nodeName_": false,
|
||||
"uid": false,
|
||||
"toDebugString": false,
|
||||
|
||||
"REGEX_STRING_REGEXP" : false,
|
||||
"lowercase": false,
|
||||
|
||||
+4
-4
@@ -634,7 +634,7 @@ function includes(array, obj) {
|
||||
|
||||
function arrayRemove(array, value) {
|
||||
var index = array.indexOf(value);
|
||||
if (index >=0)
|
||||
if (index >= 0)
|
||||
array.splice(index, 1);
|
||||
return value;
|
||||
}
|
||||
@@ -835,7 +835,7 @@ function equals(o1, o2) {
|
||||
if (isArray(o1)) {
|
||||
if (!isArray(o2)) return false;
|
||||
if ((length = o1.length) == o2.length) {
|
||||
for (key=0; key<length; key++) {
|
||||
for (key = 0; key < length; key++) {
|
||||
if (!equals(o1[key], o2[key])) return false;
|
||||
}
|
||||
return true;
|
||||
@@ -921,7 +921,7 @@ function bind(self, fn) {
|
||||
return curryArgs.length
|
||||
? function() {
|
||||
return arguments.length
|
||||
? fn.apply(self, curryArgs.concat(slice.call(arguments, 0)))
|
||||
? fn.apply(self, concat(curryArgs, arguments, 0))
|
||||
: fn.apply(self, curryArgs);
|
||||
}
|
||||
: function() {
|
||||
@@ -1121,7 +1121,7 @@ var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
|
||||
function getNgAttribute(element, ngAttr) {
|
||||
var attr, i, ii = ngAttrPrefixes.length;
|
||||
element = jqLite(element);
|
||||
for (i=0; i<ii; ++i) {
|
||||
for (i = 0; i < ii; ++i) {
|
||||
attr = ngAttrPrefixes[i] + ngAttr;
|
||||
if (isString(attr = element.attr(attr))) {
|
||||
return attr;
|
||||
|
||||
+1
-1
@@ -597,7 +597,7 @@ forEach({
|
||||
}
|
||||
} else {
|
||||
return (element[name] ||
|
||||
(element.attributes.getNamedItem(name)|| noop).specified)
|
||||
(element.attributes.getNamedItem(name) || noop).specified)
|
||||
? lowercasedName
|
||||
: undefined;
|
||||
}
|
||||
|
||||
+4
-21
@@ -37,31 +37,14 @@ function minErr(module, ErrorConstructor) {
|
||||
prefix = '[' + (module ? module + ':' : '') + code + '] ',
|
||||
template = arguments[1],
|
||||
templateArgs = arguments,
|
||||
stringify = function(obj) {
|
||||
if (typeof obj === 'function') {
|
||||
return obj.toString().replace(/ \{[\s\S]*$/, '');
|
||||
} else if (typeof obj === 'undefined') {
|
||||
return 'undefined';
|
||||
} else if (typeof obj !== 'string') {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
message, i;
|
||||
|
||||
message = prefix + template.replace(/\{\d+\}/g, function(match) {
|
||||
var index = +match.slice(1, -1), arg;
|
||||
|
||||
if (index + 2 < templateArgs.length) {
|
||||
arg = templateArgs[index + 2];
|
||||
if (typeof arg === 'function') {
|
||||
return arg.toString().replace(/ ?\{[\s\S]*$/, '');
|
||||
} else if (typeof arg === 'undefined') {
|
||||
return 'undefined';
|
||||
} else if (typeof arg !== 'string') {
|
||||
return toJson(arg);
|
||||
}
|
||||
return arg;
|
||||
return toDebugString(templateArgs[index + 2]);
|
||||
}
|
||||
return match;
|
||||
});
|
||||
@@ -69,8 +52,8 @@ function minErr(module, ErrorConstructor) {
|
||||
message = message + '\nhttp://errors.angularjs.org/"NG_VERSION_FULL"/' +
|
||||
(module ? module + '/' : '') + code;
|
||||
for (i = 2; i < arguments.length; i++) {
|
||||
message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
|
||||
encodeURIComponent(stringify(arguments[i]));
|
||||
message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
|
||||
encodeURIComponent(toDebugString(arguments[i]));
|
||||
}
|
||||
return new ErrorConstructor(message);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ function $AnchorScrollProvider() {
|
||||
* @name $anchorScrollProvider#disableAutoScrolling
|
||||
*
|
||||
* @description
|
||||
* By default, {@link ng.$anchorScroll $anchorScroll()} will automatically will detect changes to
|
||||
* By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to
|
||||
* {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br />
|
||||
* Use this method to disable automatic scrolling.
|
||||
*
|
||||
|
||||
+3
-4
@@ -19,8 +19,7 @@
|
||||
/**
|
||||
* @param {object} window The global window object.
|
||||
* @param {object} document jQuery wrapped document.
|
||||
* @param {function()} XHR XMLHttpRequest constructor.
|
||||
* @param {object} $log console.log or an object with the same interface.
|
||||
* @param {object} $log window.console or an object with the same interface.
|
||||
* @param {object} $sniffer $sniffer service
|
||||
*/
|
||||
function Browser(window, document, $log, $sniffer) {
|
||||
@@ -370,8 +369,8 @@ function Browser(window, document, $log, $sniffer) {
|
||||
// - 20 cookies per unique domain
|
||||
// - 4096 bytes per cookie
|
||||
if (cookieLength > 4096) {
|
||||
$log.warn("Cookie '"+ name +
|
||||
"' possibly not set or overflowed because it was too large ("+
|
||||
$log.warn("Cookie '" + name +
|
||||
"' possibly not set or overflowed because it was too large (" +
|
||||
cookieLength + " > 4096 bytes)!");
|
||||
}
|
||||
}
|
||||
|
||||
+58
-14
@@ -180,7 +180,7 @@
|
||||
*
|
||||
*
|
||||
* #### `bindToController`
|
||||
* When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController` will
|
||||
* When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
|
||||
* allow a component to have its properties bound to the controller, rather than to scope. When the controller
|
||||
* is instantiated, the initial values of the isolate scope bindings are already available.
|
||||
*
|
||||
@@ -622,10 +622,17 @@
|
||||
*
|
||||
*
|
||||
* @param {string|DOMElement} element Element or HTML string to compile into a template function.
|
||||
* @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives.
|
||||
* @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
|
||||
*
|
||||
* <div class="alert alert-error">
|
||||
* **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
|
||||
* e.g. will not use the right outer scope. Please pass the transclude function as a
|
||||
* `parentBoundTranscludeFn` to the link function instead.
|
||||
* </div>
|
||||
*
|
||||
* @param {number} maxPriority only apply directives lower than given priority (Only effects the
|
||||
* root element(s), not their children)
|
||||
* @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template
|
||||
* @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
|
||||
* (a DOM element/tree) to a scope. Where:
|
||||
*
|
||||
* * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
|
||||
@@ -637,6 +644,19 @@
|
||||
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
|
||||
* * `scope` - is the current scope with which the linking function is working with.
|
||||
*
|
||||
* * `options` - An optional object hash with linking options. If `options` is provided, then the following
|
||||
* keys may be used to control linking behavior:
|
||||
*
|
||||
* * `parentBoundTranscludeFn` - the transclude function made available to
|
||||
* directives; if given, it will be passed through to the link functions of
|
||||
* directives found in `element` during compilation.
|
||||
* * `transcludeControllers` - an object hash with keys that map controller names
|
||||
* to controller instances; if given, it will make the controllers
|
||||
* available to directives.
|
||||
* * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
|
||||
* the cloned elements; only needed for transcludes that are allowed to contain non html
|
||||
* elements (e.g. SVG elements). See also the directive.controller property.
|
||||
*
|
||||
* Calling the linking function returns the element of the template. It is either the original
|
||||
* element passed in, or the clone of the element if the `cloneAttachFn` is provided.
|
||||
*
|
||||
@@ -1009,16 +1029,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
// for each tuples
|
||||
var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
|
||||
for (var i=0; i<nbrUrisWith2parts; i++) {
|
||||
var innerIdx = i*2;
|
||||
for (var i = 0; i < nbrUrisWith2parts; i++) {
|
||||
var innerIdx = i * 2;
|
||||
// sanitize the uri
|
||||
result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
|
||||
// add the descriptor
|
||||
result += (" " + trim(rawUris[innerIdx+1]));
|
||||
result += (" " + trim(rawUris[innerIdx + 1]));
|
||||
}
|
||||
|
||||
// split the last item into uri and descriptor
|
||||
var lastTuple = trim(rawUris[i*2]).split(/\s/);
|
||||
var lastTuple = trim(rawUris[i * 2]).split(/\s/);
|
||||
|
||||
// sanitize the last uri
|
||||
result += $$sanitizeUri(trim(lastTuple[0]), true);
|
||||
@@ -1155,8 +1175,22 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
maxPriority, ignoreDirective, previousCompileContext);
|
||||
compile.$$addScopeClass($compileNodes);
|
||||
var namespace = null;
|
||||
return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement) {
|
||||
return function publicLinkFn(scope, cloneConnectFn, options) {
|
||||
assertArg(scope, 'scope');
|
||||
|
||||
options = options || {};
|
||||
var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
|
||||
transcludeControllers = options.transcludeControllers,
|
||||
futureParentElement = options.futureParentElement;
|
||||
|
||||
// When `parentBoundTranscludeFn` is passed, it is a
|
||||
// `controllersBoundTransclude` function (it was previously passed
|
||||
// as `transclude` to directive.link) so we must unwrap it to get
|
||||
// its `boundTranscludeFn`
|
||||
if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
|
||||
parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
|
||||
}
|
||||
|
||||
if (!namespace) {
|
||||
namespace = detectNamespaceForChildElements(futureParentElement);
|
||||
}
|
||||
@@ -1198,7 +1232,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (!node) {
|
||||
return 'html';
|
||||
} else {
|
||||
return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg': 'html';
|
||||
return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1326,7 +1360,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
transcludedScope.$$transcluded = true;
|
||||
}
|
||||
|
||||
return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
|
||||
return transcludeFn(transcludedScope, cloneFn, {
|
||||
parentBoundTranscludeFn: previousBoundTranscludeFn,
|
||||
transcludeControllers: controllers,
|
||||
futureParentElement: futureParentElement
|
||||
});
|
||||
};
|
||||
|
||||
return boundTranscludeFn;
|
||||
@@ -1794,7 +1832,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
isolateScope = scope.$new(true);
|
||||
}
|
||||
|
||||
transcludeFn = boundTranscludeFn && controllersBoundTransclude;
|
||||
if (boundTranscludeFn) {
|
||||
// track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
|
||||
// is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
|
||||
transcludeFn = controllersBoundTransclude;
|
||||
transcludeFn.$$boundTransclude = boundTranscludeFn;
|
||||
}
|
||||
|
||||
if (controllerDirectives) {
|
||||
// TODO: merge `controllers` and `elementControllers` into single object.
|
||||
controllers = {};
|
||||
@@ -2001,7 +2045,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var match = null;
|
||||
if (hasDirectives.hasOwnProperty(name)) {
|
||||
for (var directive, directives = $injector.get(name + Suffix),
|
||||
i = 0, ii = directives.length; i<ii; i++) {
|
||||
i = 0, ii = directives.length; i < ii; i++) {
|
||||
try {
|
||||
directive = directives[i];
|
||||
if ((maxPriority === undefined || maxPriority > directive.priority) &&
|
||||
@@ -2030,7 +2074,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
function directiveIsMultiElement(name) {
|
||||
if (hasDirectives.hasOwnProperty(name)) {
|
||||
for (var directive, directives = $injector.get(name + Suffix),
|
||||
i = 0, ii = directives.length; i<ii; i++) {
|
||||
i = 0, ii = directives.length; i < ii; i++) {
|
||||
directive = directives[i];
|
||||
if (directive.multiElement) {
|
||||
return true;
|
||||
@@ -2249,7 +2293,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
case 'svg':
|
||||
case 'math':
|
||||
var wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = '<'+type+'>'+template+'</'+type+'>';
|
||||
wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
|
||||
return wrapper.childNodes[0].childNodes;
|
||||
default:
|
||||
return template;
|
||||
|
||||
@@ -59,6 +59,10 @@ function $ControllerProvider() {
|
||||
* * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
|
||||
* `window` object (not recommended)
|
||||
*
|
||||
* The string can use the `controller as property` syntax, where the controller instance is published
|
||||
* as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
|
||||
* to work correctly.
|
||||
*
|
||||
* @param {Object} locals Injection locals for Controller.
|
||||
* @return {Object} Instance of given controller.
|
||||
*
|
||||
|
||||
+60
-32
@@ -42,9 +42,14 @@ var inputType = {
|
||||
* minlength.
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
||||
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
||||
* patterns defined as scope expressions.
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object then this is used directly.
|
||||
* If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
|
||||
* characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
|
||||
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
||||
* interaction with the input element.
|
||||
* @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
|
||||
@@ -200,7 +205,7 @@ var inputType = {
|
||||
|
||||
/**
|
||||
* @ngdoc input
|
||||
* @name input[dateTimeLocal]
|
||||
* @name input[datetime-local]
|
||||
*
|
||||
* @description
|
||||
* Input with datetime validation and transformation. In browsers that do not yet support
|
||||
@@ -585,9 +590,14 @@ var inputType = {
|
||||
* minlength.
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
||||
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
||||
* patterns defined as scope expressions.
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object then this is used directly.
|
||||
* If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
|
||||
* characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
|
||||
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
||||
* interaction with the input element.
|
||||
*
|
||||
@@ -667,9 +677,14 @@ var inputType = {
|
||||
* minlength.
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
||||
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
||||
* patterns defined as scope expressions.
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object then this is used directly.
|
||||
* If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
|
||||
* characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
|
||||
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
||||
* interaction with the input element.
|
||||
*
|
||||
@@ -750,9 +765,14 @@ var inputType = {
|
||||
* minlength.
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
||||
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
||||
* patterns defined as scope expressions.
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object then this is used directly.
|
||||
* If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
|
||||
* characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
|
||||
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
||||
* interaction with the input element.
|
||||
*
|
||||
@@ -1063,8 +1083,8 @@ function createDateParser(regexp, mapping) {
|
||||
// When a date is JSON'ified to wraps itself inside of an extra
|
||||
// set of double quotes. This makes the date parsing code unable
|
||||
// to match the date string and parse it as a date.
|
||||
if (iso.charAt(0) == '"' && iso.charAt(iso.length-1) == '"') {
|
||||
iso = iso.substring(1, iso.length-1);
|
||||
if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') {
|
||||
iso = iso.substring(1, iso.length - 1);
|
||||
}
|
||||
if (ISO_DATE_REGEXP.test(iso)) {
|
||||
return new Date(iso);
|
||||
@@ -1511,12 +1531,17 @@ var VALID_CLASS = 'ng-valid',
|
||||
* @property {string} $viewValue Actual string value in the view.
|
||||
* @property {*} $modelValue The value in the model that the control is bound to.
|
||||
* @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
|
||||
the control reads value from the DOM. The functions are called in array order, each passing the value
|
||||
through to the next. The last return value is forwarded to the $validators collection.
|
||||
Used to sanitize / convert the value.
|
||||
Returning undefined from a parser means a parse error occurred. No $validators will
|
||||
run and the 'ngModel' will not be updated until the parse error is resolved. The parse error is stored
|
||||
in 'ngModel.$error.parse'.
|
||||
the control reads value from the DOM. The functions are called in array order, each passing
|
||||
its return value through to the next. The last return value is forwarded to the
|
||||
{@link ngModel.NgModelController#$validators `$validators`} collection.
|
||||
|
||||
Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
|
||||
`$viewValue`}.
|
||||
|
||||
Returning `undefined` from a parser means a parse error occurred. In that case,
|
||||
no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
|
||||
will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
|
||||
is set to `true`. The parse error is stored in `ngModel.$error.parse`.
|
||||
|
||||
*
|
||||
* @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
|
||||
@@ -1576,9 +1601,6 @@ var VALID_CLASS = 'ng-valid',
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @param {string} name The name of the validator.
|
||||
* @param {Function} validationFn The validation function that will be run.
|
||||
*
|
||||
* @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
|
||||
* view value has changed. It is called with no arguments, and its return value is ignored.
|
||||
* This can be used in place of additional $watches against the model value.
|
||||
@@ -1592,6 +1614,7 @@ var VALID_CLASS = 'ng-valid',
|
||||
* @property {boolean} $dirty True if user has already interacted with the control.
|
||||
* @property {boolean} $valid True if there is no error.
|
||||
* @property {boolean} $invalid True if at least one error on the control.
|
||||
* @property {string} $name The name attribute of the control.
|
||||
*
|
||||
* @description
|
||||
*
|
||||
@@ -1643,7 +1666,7 @@ var VALID_CLASS = 'ng-valid',
|
||||
|
||||
// Listen for change events to enable binding
|
||||
element.on('blur keyup change', function() {
|
||||
scope.$apply(read);
|
||||
scope.$evalAsync(read);
|
||||
});
|
||||
read(); // initialize
|
||||
|
||||
@@ -2292,7 +2315,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* - {@link input[email] email}
|
||||
* - {@link input[url] url}
|
||||
* - {@link input[date] date}
|
||||
* - {@link input[dateTimeLocal] dateTimeLocal}
|
||||
* - {@link input[datetime-local] datetime-local}
|
||||
* - {@link input[time] time}
|
||||
* - {@link input[month] month}
|
||||
* - {@link input[week] week}
|
||||
@@ -2579,7 +2602,7 @@ var patternDirective = function() {
|
||||
var regexp, patternExp = attr.ngPattern || attr.pattern;
|
||||
attr.$observe('pattern', function(regex) {
|
||||
if (isString(regex) && regex.length > 0) {
|
||||
regex = new RegExp(regex);
|
||||
regex = new RegExp('^' + regex + '$');
|
||||
}
|
||||
|
||||
if (regex && !regex.test) {
|
||||
@@ -2772,12 +2795,17 @@ var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
|
||||
* @name ngValue
|
||||
*
|
||||
* @description
|
||||
* Binds the given expression to the value of `option` or `input[radio]`, so
|
||||
* that when the element is selected, the `ngModel` of that element is set to
|
||||
* Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`},
|
||||
* so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to
|
||||
* the bound value.
|
||||
*
|
||||
* `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as
|
||||
* shown below.
|
||||
* `ngValue` is useful when dynamically generating lists of radio buttons using
|
||||
* {@link ngRepeat `ngRepeat`}, as shown below.
|
||||
*
|
||||
* Likewise, `ngValue` can be used to generate `<option>` elements for
|
||||
* the {@link select `select`} element. In that case however, only strings are supported
|
||||
* for the `value `attribute, so the resulting `ngModel` will always be a string.
|
||||
* Support for `select` models with non-string values is available via `ngOptions`.
|
||||
*
|
||||
* @element input
|
||||
* @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
|
||||
@@ -2865,7 +2893,7 @@ var ngValueDirective = function() {
|
||||
* `ngModelOptions` has an effect on the element it's declared on and its descendants.
|
||||
*
|
||||
* @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
|
||||
* - `updateOn`: string specifying which event should be the input bound to. You can set several
|
||||
* - `updateOn`: string specifying which event should the input be bound to. You can set several
|
||||
* events using an space delimited list. There is a special event called `default` that
|
||||
* matches the default events belonging of the control.
|
||||
* - `debounce`: integer value which contains the debounce model update value in milliseconds. A
|
||||
|
||||
@@ -53,7 +53,11 @@ forEach(
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function($element, attr) {
|
||||
var fn = $parse(attr[directiveName]);
|
||||
// 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);
|
||||
return function ngEventHandler(scope, element) {
|
||||
element.on(eventName, function(event) {
|
||||
var callback = function() {
|
||||
|
||||
@@ -284,7 +284,7 @@ var ngIncludeFillContentDirective = ['$compile',
|
||||
$compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
|
||||
function namespaceAdaptedClone(clone) {
|
||||
$element.append(clone);
|
||||
}, undefined, undefined, $element);
|
||||
}, {futureParentElement: $element});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -366,7 +366,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
});
|
||||
throw ngRepeatMinErr('dupes',
|
||||
"Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",
|
||||
expression, trackById, toJson(value));
|
||||
expression, trackById, value);
|
||||
} else {
|
||||
// new never before seen block
|
||||
nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined};
|
||||
|
||||
@@ -18,7 +18,7 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
|
||||
* similar result. However, the `ngOptions` provides some benefits such as reducing memory and
|
||||
* increasing speed by not creating a new scope for each repeated instance, as well as providing
|
||||
* more flexibility in how the `select`'s model is assigned via `select as`. `ngOptions should be
|
||||
* more flexibility in how the `select`'s model is assigned via `select as`. `ngOptions` should be
|
||||
* used when the `select` model needs to be bound to a non-string value. This is because an option
|
||||
* element can only be bound to string values at present.
|
||||
*
|
||||
@@ -483,7 +483,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
if (multiple) {
|
||||
return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value)));
|
||||
} else {
|
||||
return viewValue == callExpression(compareValueFn, key, value);
|
||||
return viewValue === callExpression(compareValueFn, key, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -616,13 +616,14 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
lastElement = null; // start at the beginning
|
||||
for (index = 0, length = optionGroup.length; index < length; index++) {
|
||||
option = optionGroup[index];
|
||||
if ((existingOption = existingOptions[index+1])) {
|
||||
if ((existingOption = existingOptions[index + 1])) {
|
||||
// reuse elements
|
||||
lastElement = existingOption.element;
|
||||
if (existingOption.label !== option.label) {
|
||||
updateLabelMap(labelMap, existingOption.label, false);
|
||||
updateLabelMap(labelMap, option.label, true);
|
||||
lastElement.text(existingOption.label = option.label);
|
||||
lastElement.prop('label', existingOption.label);
|
||||
}
|
||||
if (existingOption.id !== option.id) {
|
||||
lastElement.val(existingOption.id = option.id);
|
||||
@@ -652,6 +653,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
.val(option.id)
|
||||
.prop('selected', option.selected)
|
||||
.attr('selected', option.selected)
|
||||
.prop('label', option.label)
|
||||
.text(option.label);
|
||||
}
|
||||
|
||||
|
||||
@@ -147,8 +147,8 @@ function filterFilter() {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
text = (''+text).toLowerCase();
|
||||
return (''+obj).toLowerCase().indexOf(text) > -1;
|
||||
text = ('' + text).toLowerCase();
|
||||
return ('' + obj).toLowerCase().indexOf(text) > -1;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
if (whole.length >= (lgroup + group)) {
|
||||
pos = whole.length - lgroup;
|
||||
for (i = 0; i < pos; i++) {
|
||||
if ((pos - i)%group === 0 && i !== 0) {
|
||||
if ((pos - i) % group === 0 && i !== 0) {
|
||||
formatedText += groupSep;
|
||||
}
|
||||
formatedText += whole.charAt(i);
|
||||
@@ -195,7 +195,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
}
|
||||
|
||||
for (i = pos; i < whole.length; i++) {
|
||||
if ((whole.length - i)%lgroup === 0 && i !== 0) {
|
||||
if ((whole.length - i) % lgroup === 0 && i !== 0) {
|
||||
formatedText += groupSep;
|
||||
}
|
||||
formatedText += whole.charAt(i);
|
||||
@@ -435,10 +435,10 @@ function dateFilter($locale) {
|
||||
tzMin = int(match[9] + match[11]);
|
||||
}
|
||||
dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3]));
|
||||
var h = int(match[4]||0) - tzHour;
|
||||
var m = int(match[5]||0) - tzMin;
|
||||
var s = int(match[6]||0);
|
||||
var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000);
|
||||
var h = int(match[4] || 0) - tzHour;
|
||||
var m = int(match[5] || 0) - tzMin;
|
||||
var s = int(match[6] || 0);
|
||||
var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
|
||||
timeSetter.call(date, h, m, s, ms);
|
||||
return date;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ function limitToFilter() {
|
||||
n = input.length;
|
||||
}
|
||||
|
||||
for (; i<n; i++) {
|
||||
for (; i < n; i++) {
|
||||
out.push(input[i]);
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ orderByFilter.$inject = ['$parse'];
|
||||
function orderByFilter($parse) {
|
||||
return function(array, sortPredicate, reverseOrder) {
|
||||
if (!(isArrayLike(array))) return array;
|
||||
sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
|
||||
sortPredicate = isArray(sortPredicate) ? sortPredicate : [sortPredicate];
|
||||
if (sortPredicate.length === 0) { sortPredicate = ['+']; }
|
||||
sortPredicate = sortPredicate.map(function(predicate) {
|
||||
var descending = false, get = predicate || identity;
|
||||
@@ -146,9 +146,7 @@ function orderByFilter($parse) {
|
||||
return compare(get(a),get(b));
|
||||
}, descending);
|
||||
});
|
||||
var arrayCopy = [];
|
||||
for (var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
|
||||
return arrayCopy.sort(reverseComparator(comparator, reverseOrder));
|
||||
return slice.call(array).sort(reverseComparator(comparator, reverseOrder));
|
||||
|
||||
function comparator(o1, o2) {
|
||||
for (var i = 0; i < sortPredicate.length; i++) {
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ function defaultHttpResponseTransform(data, headers) {
|
||||
// strip json vulnerability protection prefix
|
||||
data = data.replace(JSON_PROTECTION_PREFIX, '');
|
||||
var contentType = headers('Content-Type');
|
||||
if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) ||
|
||||
if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0 && data.trim()) ||
|
||||
(JSON_START.test(data) && JSON_END.test(data))) {
|
||||
data = fromJson(data);
|
||||
}
|
||||
|
||||
@@ -301,7 +301,8 @@ function $InterpolateProvider() {
|
||||
|
||||
function parseStringifyInterceptor(value) {
|
||||
try {
|
||||
return stringify(getValue(value));
|
||||
value = getValue(value);
|
||||
return allOrNothing && !isDefined(value) ? value : stringify(value);
|
||||
} catch (err) {
|
||||
var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text,
|
||||
err.toString());
|
||||
|
||||
+28
-13
@@ -22,8 +22,8 @@ function encodePath(path) {
|
||||
return segments.join('/');
|
||||
}
|
||||
|
||||
function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
|
||||
var parsedUrl = urlResolve(absoluteUrl, appBase);
|
||||
function parseAbsoluteUrl(absoluteUrl, locationObj) {
|
||||
var parsedUrl = urlResolve(absoluteUrl);
|
||||
|
||||
locationObj.$$protocol = parsedUrl.protocol;
|
||||
locationObj.$$host = parsedUrl.hostname;
|
||||
@@ -31,12 +31,12 @@ function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
|
||||
}
|
||||
|
||||
|
||||
function parseAppUrl(relativeUrl, locationObj, appBase) {
|
||||
function parseAppUrl(relativeUrl, locationObj) {
|
||||
var prefixed = (relativeUrl.charAt(0) !== '/');
|
||||
if (prefixed) {
|
||||
relativeUrl = '/' + relativeUrl;
|
||||
}
|
||||
var match = urlResolve(relativeUrl, appBase);
|
||||
var match = urlResolve(relativeUrl);
|
||||
locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
|
||||
match.pathname.substring(1) : match.pathname);
|
||||
locationObj.$$search = parseKeyValue(match.search);
|
||||
@@ -91,7 +91,7 @@ function LocationHtml5Url(appBase, basePrefix) {
|
||||
this.$$html5 = true;
|
||||
basePrefix = basePrefix || '';
|
||||
var appBaseNoFile = stripFile(appBase);
|
||||
parseAbsoluteUrl(appBase, this, appBase);
|
||||
parseAbsoluteUrl(appBase, this);
|
||||
|
||||
|
||||
/**
|
||||
@@ -106,7 +106,7 @@ function LocationHtml5Url(appBase, basePrefix) {
|
||||
appBaseNoFile);
|
||||
}
|
||||
|
||||
parseAppUrl(pathUrl, this, appBase);
|
||||
parseAppUrl(pathUrl, this);
|
||||
|
||||
if (!this.$$path) {
|
||||
this.$$path = '/';
|
||||
@@ -169,7 +169,7 @@ function LocationHtml5Url(appBase, basePrefix) {
|
||||
function LocationHashbangUrl(appBase, hashPrefix) {
|
||||
var appBaseNoFile = stripFile(appBase);
|
||||
|
||||
parseAbsoluteUrl(appBase, this, appBase);
|
||||
parseAbsoluteUrl(appBase, this);
|
||||
|
||||
|
||||
/**
|
||||
@@ -189,7 +189,7 @@ function LocationHashbangUrl(appBase, hashPrefix) {
|
||||
throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url,
|
||||
hashPrefix);
|
||||
}
|
||||
parseAppUrl(withoutHashUrl, this, appBase);
|
||||
parseAppUrl(withoutHashUrl, this);
|
||||
|
||||
this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
|
||||
|
||||
@@ -829,11 +829,19 @@ function $LocationProvider() {
|
||||
$rootScope.$evalAsync(function() {
|
||||
var oldUrl = $location.absUrl();
|
||||
var oldState = $location.$$state;
|
||||
var defaultPrevented;
|
||||
|
||||
$location.$$parse(newUrl);
|
||||
$location.$$state = newState;
|
||||
if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
|
||||
newState, oldState).defaultPrevented) {
|
||||
|
||||
defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
|
||||
newState, oldState).defaultPrevented;
|
||||
|
||||
// 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;
|
||||
setBrowserUrlWithFallback(oldUrl, false, oldState);
|
||||
@@ -857,13 +865,20 @@ function $LocationProvider() {
|
||||
initializing = false;
|
||||
|
||||
$rootScope.$evalAsync(function() {
|
||||
if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl,
|
||||
$location.$$state, oldState).defaultPrevented) {
|
||||
var newUrl = $location.absUrl();
|
||||
var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
|
||||
$location.$$state, oldState).defaultPrevented;
|
||||
|
||||
// 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($location.absUrl(), currentReplace,
|
||||
setBrowserUrlWithFallback(newUrl, currentReplace,
|
||||
oldState === $location.$$state ? null : $location.$$state);
|
||||
}
|
||||
afterLocationChange(oldUrl, oldState);
|
||||
|
||||
+181
-168
@@ -5,7 +5,7 @@ var $parseMinErr = minErr('$parse');
|
||||
// Sandboxing Angular Expressions
|
||||
// ------------------------------
|
||||
// Angular expressions are generally considered safe because these expressions only have direct
|
||||
// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by
|
||||
// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
|
||||
// obtaining a reference to native JS functions such as the Function constructor.
|
||||
//
|
||||
// As an example, consider the following Angular expression:
|
||||
@@ -14,7 +14,7 @@ var $parseMinErr = minErr('$parse');
|
||||
//
|
||||
// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
|
||||
// against the expression language, but not to prevent exploits that were enabled by exposing
|
||||
// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good
|
||||
// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
|
||||
// practice and therefore we are not even trying to protect against interaction with an object
|
||||
// explicitly exposed in this way.
|
||||
//
|
||||
@@ -22,6 +22,8 @@ var $parseMinErr = minErr('$parse');
|
||||
// window or some DOM object that has a reference to window is published onto a Scope.
|
||||
// Similarly we prevent invocations of function known to be dangerous, as well as assignments to
|
||||
// native objects.
|
||||
//
|
||||
// See https://docs.angularjs.org/guide/security
|
||||
|
||||
|
||||
function ensureSafeMemberName(name, fullExpression) {
|
||||
@@ -30,7 +32,7 @@ function ensureSafeMemberName(name, fullExpression) {
|
||||
|| name === "__proto__") {
|
||||
throw $parseMinErr('isecfld',
|
||||
'Attempting to access a disallowed field in Angular expressions! '
|
||||
+'Expression: {0}', fullExpression);
|
||||
+ 'Expression: {0}', fullExpression);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
@@ -107,24 +109,24 @@ var OPERATORS = extend(createMap(), {
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return isDefined(b)?b:undefined;},
|
||||
return isDefined(b) ? b : undefined;},
|
||||
'-':function(self, locals, a, b) {
|
||||
a=a(self, locals); b=b(self, locals);
|
||||
return (isDefined(a)?a:0)-(isDefined(b)?b:0);
|
||||
return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0);
|
||||
},
|
||||
'*':function(self, locals, a, b) {return a(self, locals)*b(self, locals);},
|
||||
'/':function(self, locals, a, b) {return a(self, locals)/b(self, locals);},
|
||||
'%':function(self, locals, a, b) {return a(self, locals)%b(self, locals);},
|
||||
'===':function(self, locals, a, b) {return a(self, locals)===b(self, locals);},
|
||||
'!==':function(self, locals, a, b) {return a(self, locals)!==b(self, locals);},
|
||||
'==':function(self, locals, a, b) {return a(self, locals)==b(self, locals);},
|
||||
'!=':function(self, locals, a, b) {return a(self, locals)!=b(self, locals);},
|
||||
'<':function(self, locals, a, b) {return a(self, locals)<b(self, locals);},
|
||||
'>':function(self, locals, a, b) {return a(self, locals)>b(self, locals);},
|
||||
'<=':function(self, locals, a, b) {return a(self, locals)<=b(self, locals);},
|
||||
'>=':function(self, locals, a, b) {return a(self, locals)>=b(self, locals);},
|
||||
'&&':function(self, locals, a, b) {return a(self, locals)&&b(self, locals);},
|
||||
'||':function(self, locals, a, b) {return a(self, locals)||b(self, locals);},
|
||||
'*':function(self, locals, a, b) {return a(self, locals) * b(self, locals);},
|
||||
'/':function(self, locals, a, b) {return a(self, locals) / b(self, locals);},
|
||||
'%':function(self, locals, a, b) {return a(self, locals) % b(self, locals);},
|
||||
'===':function(self, locals, a, b) {return a(self, locals) === b(self, locals);},
|
||||
'!==':function(self, locals, a, b) {return a(self, locals) !== b(self, locals);},
|
||||
'==':function(self, locals, a, b) {return a(self, locals) == b(self, locals);},
|
||||
'!=':function(self, locals, a, b) {return a(self, locals) != b(self, locals);},
|
||||
'<':function(self, locals, a, b) {return a(self, locals) < b(self, locals);},
|
||||
'>':function(self, locals, a, b) {return a(self, locals) > b(self, locals);},
|
||||
'<=':function(self, locals, a, b) {return a(self, locals) <= b(self, locals);},
|
||||
'>=':function(self, locals, a, b) {return a(self, locals) >= b(self, locals);},
|
||||
'&&':function(self, locals, a, b) {return a(self, locals) && b(self, locals);},
|
||||
'||':function(self, locals, a, b) {return a(self, locals) || b(self, locals);},
|
||||
'!':function(self, locals, a) {return !a(self, locals);},
|
||||
|
||||
//Tokenized as operators but parsed as assignment/filters
|
||||
@@ -150,44 +152,31 @@ Lexer.prototype = {
|
||||
lex: function(text) {
|
||||
this.text = text;
|
||||
this.index = 0;
|
||||
this.ch = undefined;
|
||||
this.tokens = [];
|
||||
|
||||
while (this.index < this.text.length) {
|
||||
this.ch = this.text.charAt(this.index);
|
||||
if (this.is('"\'')) {
|
||||
this.readString(this.ch);
|
||||
} else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) {
|
||||
var ch = this.text.charAt(this.index);
|
||||
if (ch === '"' || ch === "'") {
|
||||
this.readString(ch);
|
||||
} else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
|
||||
this.readNumber();
|
||||
} else if (this.isIdent(this.ch)) {
|
||||
} else if (this.isIdent(ch)) {
|
||||
this.readIdent();
|
||||
} else if (this.is('(){}[].,;:?')) {
|
||||
this.tokens.push({
|
||||
index: this.index,
|
||||
text: this.ch
|
||||
});
|
||||
} else if (this.is(ch, '(){}[].,;:?')) {
|
||||
this.tokens.push({index: this.index, text: ch});
|
||||
this.index++;
|
||||
} else if (this.isWhitespace(this.ch)) {
|
||||
} else if (this.isWhitespace(ch)) {
|
||||
this.index++;
|
||||
} else {
|
||||
var ch2 = this.ch + this.peek();
|
||||
var ch2 = ch + this.peek();
|
||||
var ch3 = ch2 + this.peek(2);
|
||||
var fn = OPERATORS[this.ch];
|
||||
var fn2 = OPERATORS[ch2];
|
||||
var fn3 = OPERATORS[ch3];
|
||||
if (fn3) {
|
||||
this.tokens.push({index: this.index, text: ch3, fn: fn3});
|
||||
this.index += 3;
|
||||
} else if (fn2) {
|
||||
this.tokens.push({index: this.index, text: ch2, fn: fn2});
|
||||
this.index += 2;
|
||||
} else if (fn) {
|
||||
this.tokens.push({
|
||||
index: this.index,
|
||||
text: this.ch,
|
||||
fn: fn
|
||||
});
|
||||
this.index += 1;
|
||||
var op1 = OPERATORS[ch];
|
||||
var op2 = OPERATORS[ch2];
|
||||
var op3 = OPERATORS[ch3];
|
||||
if (op1 || op2 || op3) {
|
||||
var token = op3 ? ch3 : (op2 ? ch2 : ch);
|
||||
this.tokens.push({index: this.index, text: token, operator: true});
|
||||
this.index += token.length;
|
||||
} else {
|
||||
this.throwError('Unexpected next character ', this.index, this.index + 1);
|
||||
}
|
||||
@@ -196,8 +185,8 @@ Lexer.prototype = {
|
||||
return this.tokens;
|
||||
},
|
||||
|
||||
is: function(chars) {
|
||||
return chars.indexOf(this.ch) !== -1;
|
||||
is: function(ch, chars) {
|
||||
return chars.indexOf(ch) !== -1;
|
||||
},
|
||||
|
||||
peek: function(i) {
|
||||
@@ -206,7 +195,7 @@ Lexer.prototype = {
|
||||
},
|
||||
|
||||
isNumber: function(ch) {
|
||||
return ('0' <= ch && ch <= '9');
|
||||
return ('0' <= ch && ch <= '9') && typeof ch === "string";
|
||||
},
|
||||
|
||||
isWhitespace: function(ch) {
|
||||
@@ -259,79 +248,28 @@ Lexer.prototype = {
|
||||
}
|
||||
this.index++;
|
||||
}
|
||||
number = 1 * number;
|
||||
this.tokens.push({
|
||||
index: start,
|
||||
text: number,
|
||||
constant: true,
|
||||
fn: function() { return number; }
|
||||
value: Number(number)
|
||||
});
|
||||
},
|
||||
|
||||
readIdent: function() {
|
||||
var expression = this.text;
|
||||
|
||||
var ident = '';
|
||||
var start = this.index;
|
||||
|
||||
var lastDot, peekIndex, methodName, ch;
|
||||
|
||||
while (this.index < this.text.length) {
|
||||
ch = this.text.charAt(this.index);
|
||||
if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) {
|
||||
if (ch === '.') lastDot = this.index;
|
||||
ident += ch;
|
||||
} else {
|
||||
var ch = this.text.charAt(this.index);
|
||||
if (!(this.isIdent(ch) || this.isNumber(ch))) {
|
||||
break;
|
||||
}
|
||||
this.index++;
|
||||
}
|
||||
|
||||
//check if the identifier ends with . and if so move back one char
|
||||
if (lastDot && ident[ident.length - 1] === '.') {
|
||||
this.index--;
|
||||
ident = ident.slice(0, -1);
|
||||
lastDot = ident.lastIndexOf('.');
|
||||
if (lastDot === -1) {
|
||||
lastDot = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
//check if this is not a method invocation and if it is back out to last dot
|
||||
if (lastDot) {
|
||||
peekIndex = this.index;
|
||||
while (peekIndex < this.text.length) {
|
||||
ch = this.text.charAt(peekIndex);
|
||||
if (ch === '(') {
|
||||
methodName = ident.substr(lastDot - start + 1);
|
||||
ident = ident.substr(0, lastDot - start);
|
||||
this.index = peekIndex;
|
||||
break;
|
||||
}
|
||||
if (this.isWhitespace(ch)) {
|
||||
peekIndex++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.tokens.push({
|
||||
index: start,
|
||||
text: ident,
|
||||
fn: CONSTANTS[ident] || getterFn(ident, this.options, expression)
|
||||
text: this.text.slice(start, this.index),
|
||||
identifier: true
|
||||
});
|
||||
|
||||
if (methodName) {
|
||||
this.tokens.push({
|
||||
index: lastDot,
|
||||
text: '.'
|
||||
});
|
||||
this.tokens.push({
|
||||
index: lastDot + 1,
|
||||
text: methodName
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
readString: function(quote) {
|
||||
@@ -362,9 +300,8 @@ Lexer.prototype = {
|
||||
this.tokens.push({
|
||||
index: start,
|
||||
text: rawString,
|
||||
string: string,
|
||||
constant: true,
|
||||
fn: function() { return string; }
|
||||
value: string
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
@@ -425,16 +362,12 @@ Parser.prototype = {
|
||||
primary = this.arrayDeclaration();
|
||||
} else if (this.expect('{')) {
|
||||
primary = this.object();
|
||||
} else if (this.peek().identifier) {
|
||||
primary = this.identifier();
|
||||
} else if (this.peek().constant) {
|
||||
primary = this.constant();
|
||||
} else {
|
||||
var token = this.expect();
|
||||
primary = token.fn;
|
||||
if (!primary) {
|
||||
this.throwError('not a primary expression', token);
|
||||
}
|
||||
if (token.constant) {
|
||||
primary.constant = true;
|
||||
primary.literal = true;
|
||||
}
|
||||
this.throwError('not a primary expression', this.peek());
|
||||
}
|
||||
|
||||
var next, context;
|
||||
@@ -468,8 +401,11 @@ Parser.prototype = {
|
||||
},
|
||||
|
||||
peek: function(e1, e2, e3, e4) {
|
||||
if (this.tokens.length > 0) {
|
||||
var token = this.tokens[0];
|
||||
return this.peekAhead(0, e1, e2, e3, e4);
|
||||
},
|
||||
peekAhead: function(i, e1, e2, e3, e4) {
|
||||
if (this.tokens.length > i) {
|
||||
var token = this.tokens[i];
|
||||
var t = token.text;
|
||||
if (t === e1 || t === e2 || t === e3 || t === e4 ||
|
||||
(!e1 && !e2 && !e3 && !e4)) {
|
||||
@@ -489,12 +425,19 @@ Parser.prototype = {
|
||||
},
|
||||
|
||||
consume: function(e1) {
|
||||
if (!this.expect(e1)) {
|
||||
if (this.tokens.length === 0) {
|
||||
throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
|
||||
}
|
||||
|
||||
var token = this.expect(e1);
|
||||
if (!token) {
|
||||
this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
|
||||
}
|
||||
return token;
|
||||
},
|
||||
|
||||
unaryFn: function(fn, right) {
|
||||
unaryFn: function(op, right) {
|
||||
var fn = OPERATORS[op];
|
||||
return extend(function $parseUnaryFn(self, locals) {
|
||||
return fn(self, locals, right);
|
||||
}, {
|
||||
@@ -503,7 +446,8 @@ Parser.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
binaryFn: function(left, fn, right, isBranching) {
|
||||
binaryFn: function(left, op, right, isBranching) {
|
||||
var fn = OPERATORS[op];
|
||||
return extend(function $parseBinaryFn(self, locals) {
|
||||
return fn(self, locals, left, right);
|
||||
}, {
|
||||
@@ -512,6 +456,28 @@ Parser.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
identifier: function() {
|
||||
var id = this.consume().text;
|
||||
|
||||
//Continue reading each `.identifier` unless it is a method invocation
|
||||
while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2, '(')) {
|
||||
id += this.consume().text + this.consume().text;
|
||||
}
|
||||
|
||||
return CONSTANTS[id] || getterFn(id, this.options, this.text);
|
||||
},
|
||||
|
||||
constant: function() {
|
||||
var value = this.consume().value;
|
||||
|
||||
return extend(function $parseConstant() {
|
||||
return value;
|
||||
}, {
|
||||
constant: true,
|
||||
literal: true
|
||||
});
|
||||
},
|
||||
|
||||
statements: function() {
|
||||
var statements = [];
|
||||
while (true) {
|
||||
@@ -543,8 +509,7 @@ Parser.prototype = {
|
||||
},
|
||||
|
||||
filter: function(inputFn) {
|
||||
var token = this.expect();
|
||||
var fn = this.$filter(token.text);
|
||||
var fn = this.$filter(this.consume().text);
|
||||
var argsFn;
|
||||
var args;
|
||||
|
||||
@@ -607,7 +572,7 @@ Parser.prototype = {
|
||||
var token;
|
||||
if ((token = this.expect('?'))) {
|
||||
middle = this.assignment();
|
||||
if ((token = this.expect(':'))) {
|
||||
if (this.consume(':')) {
|
||||
var right = this.assignment();
|
||||
|
||||
return extend(function $parseTernary(self, locals) {
|
||||
@@ -615,9 +580,6 @@ Parser.prototype = {
|
||||
}, {
|
||||
constant: left.constant && middle.constant && right.constant
|
||||
});
|
||||
|
||||
} else {
|
||||
this.throwError('expected :', token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,7 +590,7 @@ Parser.prototype = {
|
||||
var left = this.logicalAND();
|
||||
var token;
|
||||
while ((token = this.expect('||'))) {
|
||||
left = this.binaryFn(left, token.fn, this.logicalAND(), true);
|
||||
left = this.binaryFn(left, token.text, this.logicalAND(), true);
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -637,7 +599,7 @@ Parser.prototype = {
|
||||
var left = this.equality();
|
||||
var token;
|
||||
if ((token = this.expect('&&'))) {
|
||||
left = this.binaryFn(left, token.fn, this.logicalAND(), true);
|
||||
left = this.binaryFn(left, token.text, this.logicalAND(), true);
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -646,7 +608,7 @@ Parser.prototype = {
|
||||
var left = this.relational();
|
||||
var token;
|
||||
if ((token = this.expect('==','!=','===','!=='))) {
|
||||
left = this.binaryFn(left, token.fn, this.equality());
|
||||
left = this.binaryFn(left, token.text, this.equality());
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -655,7 +617,7 @@ Parser.prototype = {
|
||||
var left = this.additive();
|
||||
var token;
|
||||
if ((token = this.expect('<', '>', '<=', '>='))) {
|
||||
left = this.binaryFn(left, token.fn, this.relational());
|
||||
left = this.binaryFn(left, token.text, this.relational());
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -664,7 +626,7 @@ Parser.prototype = {
|
||||
var left = this.multiplicative();
|
||||
var token;
|
||||
while ((token = this.expect('+','-'))) {
|
||||
left = this.binaryFn(left, token.fn, this.multiplicative());
|
||||
left = this.binaryFn(left, token.text, this.multiplicative());
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -673,7 +635,7 @@ Parser.prototype = {
|
||||
var left = this.unary();
|
||||
var token;
|
||||
while ((token = this.expect('*','/','%'))) {
|
||||
left = this.binaryFn(left, token.fn, this.unary());
|
||||
left = this.binaryFn(left, token.text, this.unary());
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -683,9 +645,9 @@ Parser.prototype = {
|
||||
if (this.expect('+')) {
|
||||
return this.primary();
|
||||
} else if ((token = this.expect('-'))) {
|
||||
return this.binaryFn(Parser.ZERO, token.fn, this.unary());
|
||||
return this.binaryFn(Parser.ZERO, token.text, this.unary());
|
||||
} else if ((token = this.expect('!'))) {
|
||||
return this.unaryFn(token.fn, this.unary());
|
||||
return this.unaryFn(token.text, this.unary());
|
||||
} else {
|
||||
return this.primary();
|
||||
}
|
||||
@@ -693,7 +655,7 @@ Parser.prototype = {
|
||||
|
||||
fieldAccess: function(object) {
|
||||
var expression = this.text;
|
||||
var field = this.expect().text;
|
||||
var field = this.consume().text;
|
||||
var getter = getterFn(field, this.options, expression);
|
||||
|
||||
return extend(function $parseFieldAccess(scope, locals, self) {
|
||||
@@ -778,8 +740,7 @@ Parser.prototype = {
|
||||
// Support trailing commas per ES5.1.
|
||||
break;
|
||||
}
|
||||
var elementFn = this.expression();
|
||||
elementFns.push(elementFn);
|
||||
elementFns.push(this.expression());
|
||||
} while (this.expect(','));
|
||||
}
|
||||
this.consume(']');
|
||||
@@ -805,11 +766,16 @@ Parser.prototype = {
|
||||
// Support trailing commas per ES5.1.
|
||||
break;
|
||||
}
|
||||
var token = this.expect();
|
||||
keys.push(token.string || token.text);
|
||||
var token = this.consume();
|
||||
if (token.constant) {
|
||||
keys.push(token.value);
|
||||
} else if (token.identifier) {
|
||||
keys.push(token.text);
|
||||
} else {
|
||||
this.throwError("invalid key", token);
|
||||
}
|
||||
this.consume(':');
|
||||
var value = this.expression();
|
||||
valueFns.push(value);
|
||||
valueFns.push(this.expression());
|
||||
} while (this.expect(','));
|
||||
}
|
||||
this.consume('}');
|
||||
@@ -852,64 +818,85 @@ function setter(obj, path, setValue, fullExp) {
|
||||
return setValue;
|
||||
}
|
||||
|
||||
var getterFnCache = createMap();
|
||||
var getterFnCacheDefault = createMap();
|
||||
var getterFnCacheExpensive = createMap();
|
||||
|
||||
function isPossiblyDangerousMemberName(name) {
|
||||
return name == 'constructor';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the "Black Hole" variant from:
|
||||
* - http://jsperf.com/angularjs-parse-getter/4
|
||||
* - http://jsperf.com/path-evaluation-simplified/7
|
||||
*/
|
||||
function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) {
|
||||
function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, expensiveChecks) {
|
||||
ensureSafeMemberName(key0, fullExp);
|
||||
ensureSafeMemberName(key1, fullExp);
|
||||
ensureSafeMemberName(key2, fullExp);
|
||||
ensureSafeMemberName(key3, fullExp);
|
||||
ensureSafeMemberName(key4, fullExp);
|
||||
var eso = function(o) {
|
||||
return ensureSafeObject(o, fullExp);
|
||||
};
|
||||
var eso0 = (expensiveChecks || isPossiblyDangerousMemberName(key0)) ? eso : identity;
|
||||
var eso1 = (expensiveChecks || isPossiblyDangerousMemberName(key1)) ? eso : identity;
|
||||
var eso2 = (expensiveChecks || isPossiblyDangerousMemberName(key2)) ? eso : identity;
|
||||
var eso3 = (expensiveChecks || isPossiblyDangerousMemberName(key3)) ? eso : identity;
|
||||
var eso4 = (expensiveChecks || isPossiblyDangerousMemberName(key4)) ? eso : identity;
|
||||
|
||||
return function cspSafeGetter(scope, locals) {
|
||||
var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
|
||||
|
||||
if (pathVal == null) return pathVal;
|
||||
pathVal = pathVal[key0];
|
||||
pathVal = eso0(pathVal[key0]);
|
||||
|
||||
if (!key1) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key1];
|
||||
pathVal = eso1(pathVal[key1]);
|
||||
|
||||
if (!key2) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key2];
|
||||
pathVal = eso2(pathVal[key2]);
|
||||
|
||||
if (!key3) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key3];
|
||||
pathVal = eso3(pathVal[key3]);
|
||||
|
||||
if (!key4) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key4];
|
||||
pathVal = eso4(pathVal[key4]);
|
||||
|
||||
return pathVal;
|
||||
};
|
||||
}
|
||||
|
||||
function getterFn(path, options, fullExp) {
|
||||
var fn = getterFnCache[path];
|
||||
function getterFnWithEnsureSafeObject(fn, fullExpression) {
|
||||
return function(s, l) {
|
||||
return fn(s, l, ensureSafeObject, fullExpression);
|
||||
};
|
||||
}
|
||||
|
||||
function getterFn(path, options, fullExp) {
|
||||
var expensiveChecks = options.expensiveChecks;
|
||||
var getterFnCache = (expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault);
|
||||
var fn = getterFnCache[path];
|
||||
if (fn) return fn;
|
||||
|
||||
|
||||
var pathKeys = path.split('.'),
|
||||
pathKeysLength = pathKeys.length;
|
||||
|
||||
// http://jsperf.com/angularjs-parse-getter/6
|
||||
if (options.csp) {
|
||||
if (pathKeysLength < 6) {
|
||||
fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp);
|
||||
fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, expensiveChecks);
|
||||
} else {
|
||||
fn = function cspSafeGetter(scope, locals) {
|
||||
var i = 0, val;
|
||||
do {
|
||||
val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++],
|
||||
pathKeys[i++], fullExp)(scope, locals);
|
||||
pathKeys[i++], fullExp, expensiveChecks)(scope, locals);
|
||||
|
||||
locals = undefined; // clear after first iteration
|
||||
scope = val;
|
||||
@@ -919,22 +906,33 @@ function getterFn(path, options, fullExp) {
|
||||
}
|
||||
} else {
|
||||
var code = '';
|
||||
if (expensiveChecks) {
|
||||
code += 's = eso(s, fe);\nl = eso(l, fe);\n';
|
||||
}
|
||||
var needsEnsureSafeObject = expensiveChecks;
|
||||
forEach(pathKeys, function(key, index) {
|
||||
ensureSafeMemberName(key, fullExp);
|
||||
code += 'if(s == null) return undefined;\n' +
|
||||
's='+ (index
|
||||
var lookupJs = (index
|
||||
// we simply dereference 's' on any .dot notation
|
||||
? 's'
|
||||
// but if we are first then we check locals first, and if so read it first
|
||||
: '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key + ';\n';
|
||||
: '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key;
|
||||
if (expensiveChecks || isPossiblyDangerousMemberName(key)) {
|
||||
lookupJs = 'eso(' + lookupJs + ', fe)';
|
||||
needsEnsureSafeObject = true;
|
||||
}
|
||||
code += 'if(s == null) return undefined;\n' +
|
||||
's=' + lookupJs + ';\n';
|
||||
});
|
||||
code += 'return s;';
|
||||
|
||||
/* jshint -W054 */
|
||||
var evaledFnGetter = new Function('s', 'l', code); // s=scope, l=locals
|
||||
var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject
|
||||
/* jshint +W054 */
|
||||
evaledFnGetter.toString = valueFn(code);
|
||||
|
||||
if (needsEnsureSafeObject) {
|
||||
evaledFnGetter = getterFnWithEnsureSafeObject(evaledFnGetter, fullExp);
|
||||
}
|
||||
fn = evaledFnGetter;
|
||||
}
|
||||
|
||||
@@ -1004,15 +1002,20 @@ function getValueOf(value) {
|
||||
* service.
|
||||
*/
|
||||
function $ParseProvider() {
|
||||
var cache = createMap();
|
||||
var cacheDefault = createMap();
|
||||
var cacheExpensive = createMap();
|
||||
|
||||
var $parseOptions = {
|
||||
csp: false
|
||||
};
|
||||
|
||||
|
||||
this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
|
||||
$parseOptions.csp = $sniffer.csp;
|
||||
var $parseOptions = {
|
||||
csp: $sniffer.csp,
|
||||
expensiveChecks: false
|
||||
},
|
||||
$parseOptionsExpensive = {
|
||||
csp: $sniffer.csp,
|
||||
expensiveChecks: true
|
||||
};
|
||||
|
||||
function wrapSharedExpression(exp) {
|
||||
var wrapped = exp;
|
||||
@@ -1029,13 +1032,14 @@ function $ParseProvider() {
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
return function $parse(exp, interceptorFn) {
|
||||
return function $parse(exp, interceptorFn, expensiveChecks) {
|
||||
var parsedExpression, oneTime, cacheKey;
|
||||
|
||||
switch (typeof exp) {
|
||||
case 'string':
|
||||
cacheKey = exp = exp.trim();
|
||||
|
||||
var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
|
||||
parsedExpression = cache[cacheKey];
|
||||
|
||||
if (!parsedExpression) {
|
||||
@@ -1044,8 +1048,9 @@ function $ParseProvider() {
|
||||
exp = exp.substring(2);
|
||||
}
|
||||
|
||||
var lexer = new Lexer($parseOptions);
|
||||
var parser = new Parser(lexer, $filter, $parseOptions);
|
||||
var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
|
||||
var lexer = new Lexer(parseOptions);
|
||||
var parser = new Parser(lexer, $filter, parseOptions);
|
||||
parsedExpression = parser.parse(exp);
|
||||
|
||||
if (parsedExpression.constant) {
|
||||
@@ -1212,8 +1217,16 @@ function $ParseProvider() {
|
||||
|
||||
function addInterceptor(parsedExpression, interceptorFn) {
|
||||
if (!interceptorFn) return parsedExpression;
|
||||
var watchDelegate = parsedExpression.$$watchDelegate;
|
||||
|
||||
var fn = function interceptedExpression(scope, locals) {
|
||||
var regularWatch =
|
||||
watchDelegate !== oneTimeLiteralWatchDelegate &&
|
||||
watchDelegate !== oneTimeWatchDelegate;
|
||||
|
||||
var fn = regularWatch ? function regularInterceptedExpression(scope, locals) {
|
||||
var value = parsedExpression(scope, locals);
|
||||
return interceptorFn(value, scope, locals);
|
||||
} : function oneTimeInterceptedExpression(scope, locals) {
|
||||
var value = parsedExpression(scope, locals);
|
||||
var result = interceptorFn(value, scope, locals);
|
||||
// we only return the interceptor's result if the
|
||||
|
||||
+6
-6
@@ -6,7 +6,11 @@
|
||||
* @requires $rootScope
|
||||
*
|
||||
* @description
|
||||
* A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
|
||||
* A service that helps you run functions asynchronously, and use their return values (or exceptions)
|
||||
* when they are done processing.
|
||||
*
|
||||
* This is an implementation of promises/deferred objects inspired by
|
||||
* [Kris Kowal's Q](https://github.com/kriskowal/q).
|
||||
*
|
||||
* $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
|
||||
* implementations, and the other which resembles ES6 promises to some degree.
|
||||
@@ -142,16 +146,12 @@
|
||||
*
|
||||
* - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
|
||||
*
|
||||
* - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise,
|
||||
* - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise,
|
||||
* but to do so without modifying the final value. This is useful to release resources or do some
|
||||
* clean-up that needs to be done whether the promise was rejected or resolved. See the [full
|
||||
* specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
|
||||
* more information.
|
||||
*
|
||||
* Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as
|
||||
* property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to
|
||||
* make your code IE8 and Android 2.x compatible.
|
||||
*
|
||||
* # Chaining promises
|
||||
*
|
||||
* Because calling the `then` method of a promise returns a new derived promise, it is easily
|
||||
|
||||
+15
-8
@@ -113,6 +113,10 @@ function $RootScopeProvider() {
|
||||
expect(parent.salutation).toEqual('Hello');
|
||||
* ```
|
||||
*
|
||||
* When interacting with `Scope` in tests, additional helper methods are available on the
|
||||
* instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
|
||||
* details.
|
||||
*
|
||||
*
|
||||
* @param {Object.<string, function()>=} providers Map of service factory which need to be
|
||||
* provided for the current scope. Defaults to {@link ng}.
|
||||
@@ -551,6 +555,9 @@ function $RootScopeProvider() {
|
||||
newValue = _value;
|
||||
var newLength, key, bothNaN, newItem, oldItem;
|
||||
|
||||
// If the new value is undefined, then return undefined as the watch may be a one-time watch
|
||||
if (isUndefined(newValue)) return;
|
||||
|
||||
if (!isObject(newValue)) { // if primitive
|
||||
if (oldValue !== newValue) {
|
||||
oldValue = newValue;
|
||||
@@ -766,11 +773,11 @@ function $RootScopeProvider() {
|
||||
if (ttl < 5) {
|
||||
logIdx = 4 - ttl;
|
||||
if (!watchLog[logIdx]) watchLog[logIdx] = [];
|
||||
logMsg = (isFunction(watch.exp))
|
||||
? 'fn: ' + (watch.exp.name || watch.exp.toString())
|
||||
: watch.exp;
|
||||
logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
|
||||
watchLog[logIdx].push(logMsg);
|
||||
watchLog[logIdx].push({
|
||||
msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
|
||||
newVal: value,
|
||||
oldVal: last
|
||||
});
|
||||
}
|
||||
} else if (watch === lastDirtyWatch) {
|
||||
// If the most recently dirty watcher is now clean, short circuit since the remaining watchers
|
||||
@@ -803,7 +810,7 @@ function $RootScopeProvider() {
|
||||
throw $rootScopeMinErr('infdig',
|
||||
'{0} $digest() iterations reached. Aborting!\n' +
|
||||
'Watchers fired in the last 5 iterations: {1}',
|
||||
TTL, toJson(watchLog));
|
||||
TTL, watchLog);
|
||||
}
|
||||
|
||||
} while (dirty || asyncQueue.length);
|
||||
@@ -1154,7 +1161,7 @@ function $RootScopeProvider() {
|
||||
do {
|
||||
namedListeners = scope.$$listeners[name] || empty;
|
||||
event.currentScope = scope;
|
||||
for (i=0, length=namedListeners.length; i<length; i++) {
|
||||
for (i = 0, length = namedListeners.length; i < length; i++) {
|
||||
|
||||
// if listeners were deregistered, defragment the array
|
||||
if (!namedListeners[i]) {
|
||||
@@ -1228,7 +1235,7 @@ function $RootScopeProvider() {
|
||||
while ((current = next)) {
|
||||
event.currentScope = current;
|
||||
listeners = current.$$listeners[name] || [];
|
||||
for (i=0, length = listeners.length; i<length; i++) {
|
||||
for (i = 0, length = listeners.length; i < length; i++) {
|
||||
// if listeners were deregistered, defragment the array
|
||||
if (!listeners[i]) {
|
||||
listeners.splice(i, 1);
|
||||
|
||||
@@ -63,7 +63,7 @@ function $$SanitizeUriProvider() {
|
||||
var normalizedVal;
|
||||
normalizedVal = urlResolve(uri).href;
|
||||
if (normalizedVal !== '' && !normalizedVal.match(regex)) {
|
||||
return 'unsafe:'+normalizedVal;
|
||||
return 'unsafe:' + normalizedVal;
|
||||
}
|
||||
return uri;
|
||||
};
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ function $SnifferProvider() {
|
||||
transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
|
||||
animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
|
||||
|
||||
if (android && (!transitions||!animations)) {
|
||||
if (android && (!transitions || !animations)) {
|
||||
transitions = isString(document.body.style.webkitTransition);
|
||||
animations = isString(document.body.style.webkitAnimation);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ function $TemplateRequestProvider() {
|
||||
if (isArray(transformResponse)) {
|
||||
var original = transformResponse;
|
||||
transformResponse = [];
|
||||
for (var i=0; i<original.length; ++i) {
|
||||
for (var i = 0; i < original.length; ++i) {
|
||||
var transformer = original[i];
|
||||
if (transformer !== defaultHttpResponseTransform) {
|
||||
transformResponse.push(transformer);
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@
|
||||
// exactly the behavior needed here. There is little value is mocking these out for this
|
||||
// service.
|
||||
var urlParsingNode = document.createElement("a");
|
||||
var originUrl = urlResolve(window.location.href, true);
|
||||
var originUrl = urlResolve(window.location.href);
|
||||
|
||||
|
||||
/**
|
||||
@@ -62,7 +62,7 @@ var originUrl = urlResolve(window.location.href, true);
|
||||
* | pathname | The pathname, beginning with "/"
|
||||
*
|
||||
*/
|
||||
function urlResolve(url, base) {
|
||||
function urlResolve(url) {
|
||||
var href = url;
|
||||
|
||||
if (msie) {
|
||||
|
||||
@@ -1883,7 +1883,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
//the jqLite object, so we're safe to use a single variable to house
|
||||
//the styles since there is always only one element being animated
|
||||
var oldStyle = node.getAttribute('style') || '';
|
||||
if (oldStyle.charAt(oldStyle.length-1) !== ';') {
|
||||
if (oldStyle.charAt(oldStyle.length - 1) !== ';') {
|
||||
oldStyle += ';';
|
||||
}
|
||||
node.setAttribute('style', oldStyle + ' ' + style);
|
||||
|
||||
+93
-22
@@ -5,33 +5,49 @@
|
||||
* @name ngAria
|
||||
* @description
|
||||
*
|
||||
* The `ngAria` module provides support for adding <abbr title="Accessible Rich Internet Applications">ARIA</abbr>
|
||||
* attributes that convey state or semantic information about the application in order to allow assistive technologies
|
||||
* to convey appropriate information to persons with disabilities.
|
||||
* The `ngAria` module provides support for common
|
||||
* [<abbr title="Accessible Rich Internet Applications">ARIA</abbr>](http://www.w3.org/TR/wai-aria/)
|
||||
* attributes that convey state or semantic information about the application for users
|
||||
* of assistive technologies, such as screen readers.
|
||||
*
|
||||
* <div doc-module-components="ngAria"></div>
|
||||
*
|
||||
* # Usage
|
||||
* To enable the addition of the ARIA tags, just require the module into your application and the tags will
|
||||
* hook into your ng-show/ng-hide, input, textarea, button, select and ng-required directives and adds the
|
||||
* appropriate ARIA attributes.
|
||||
* ## Usage
|
||||
*
|
||||
* Currently, the following ARIA attributes are implemented:
|
||||
* For ngAria to do its magic, simply include the module as a dependency. The directives supported
|
||||
* by ngAria are:
|
||||
* `ngModel`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, `ngDblClick`, and `ngMessages`.
|
||||
*
|
||||
* + aria-hidden
|
||||
* + aria-checked
|
||||
* + aria-disabled
|
||||
* + aria-required
|
||||
* + aria-invalid
|
||||
* + aria-multiline
|
||||
* + aria-valuenow
|
||||
* + aria-valuemin
|
||||
* + aria-valuemax
|
||||
* + tabindex
|
||||
* Below is a more detailed breakdown of the attributes handled by ngAria:
|
||||
*
|
||||
* You can disable individual ARIA attributes by using the {@link ngAria.$ariaProvider#config config} method.
|
||||
* | Directive | Supported Attributes |
|
||||
* |---------------------------------------------|----------------------------------------------------------------------------------------|
|
||||
* | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required |
|
||||
* | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
|
||||
* | {@link ng.directive:ngShow ngShow} | aria-hidden |
|
||||
* | {@link ng.directive:ngHide ngHide} | aria-hidden |
|
||||
* | {@link ng.directive:ngClick ngClick} | tabindex |
|
||||
* | {@link ng.directive:ngDblclick ngDblclick} | tabindex |
|
||||
* | {@link module:ngMessages ngMessages} | aria-live |
|
||||
*
|
||||
* Find out more information about each directive by reading the
|
||||
* {@link guide/accessibility ngAria Developer Guide}.
|
||||
*
|
||||
* ##Example
|
||||
* Using ngDisabled with ngAria:
|
||||
* ```html
|
||||
* <md-checkbox ng-disabled="disabled">
|
||||
* ```
|
||||
* Becomes:
|
||||
* ```html
|
||||
* <md-checkbox ng-disabled="disabled" aria-disabled="true">
|
||||
* ```
|
||||
*
|
||||
* ##Disabling Attributes
|
||||
* It's possible to disable individual attributes added by ngAria with the
|
||||
* {@link ngAria.$ariaProvider#config config} method. For more details, see the
|
||||
* {@link guide/accessibility Developer Guide}.
|
||||
*/
|
||||
|
||||
/* global -ngAriaModule */
|
||||
var ngAriaModule = angular.module('ngAria', ['ng']).
|
||||
provider('$aria', $AriaProvider);
|
||||
@@ -42,10 +58,20 @@ var ngAriaModule = angular.module('ngAria', ['ng']).
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* Used for configuring ARIA attributes.
|
||||
* Used for configuring the ARIA attributes injected and managed by ngAria.
|
||||
*
|
||||
* ```js
|
||||
* angular.module('myApp', ['ngAria'], function config($ariaProvider) {
|
||||
* $ariaProvider.config({
|
||||
* ariaValue: true,
|
||||
* tabindex: false
|
||||
* });
|
||||
* });
|
||||
*```
|
||||
*
|
||||
* ## Dependencies
|
||||
* Requires the {@link ngAria} module to be installed.
|
||||
*
|
||||
*/
|
||||
function $AriaProvider() {
|
||||
var config = {
|
||||
@@ -108,7 +134,41 @@ function $AriaProvider() {
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* Contains helper methods for applying ARIA attributes to HTML
|
||||
* The $aria service contains helper methods for applying common
|
||||
* [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives.
|
||||
*
|
||||
* ngAria injects common accessibility attributes that tell assistive technologies when HTML
|
||||
* elements are enabled, selected, hidden, and more. To see how this is performed with ngAria,
|
||||
* let's review a code snippet from ngAria itself:
|
||||
*
|
||||
*```js
|
||||
* ngAriaModule.directive('ngDisabled', ['$aria', function($aria) {
|
||||
* return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
|
||||
* }])
|
||||
*```
|
||||
* Shown above, the ngAria module creates a directive with the same signature as the
|
||||
* traditional `ng-disabled` directive. But this ngAria version is dedicated to
|
||||
* solely managing accessibility attributes. The internal `$aria` service is used to watch the
|
||||
* boolean attribute `ngDisabled`. If it has not been explicitly set by the developer,
|
||||
* `aria-disabled` is injected as an attribute with its value synchronized to the value in
|
||||
* `ngDisabled`.
|
||||
*
|
||||
* Because ngAria hooks into the `ng-disabled` directive, developers do not have to do
|
||||
* anything to enable this feature. The `aria-disabled` attribute is automatically managed
|
||||
* simply as a silent side-effect of using `ng-disabled` with the ngAria module.
|
||||
*
|
||||
* The full list of directives that interface with ngAria:
|
||||
* * **ngModel**
|
||||
* * **ngShow**
|
||||
* * **ngHide**
|
||||
* * **ngClick**
|
||||
* * **ngDblclick**
|
||||
* * **ngMessages**
|
||||
* * **ngDisabled**
|
||||
*
|
||||
* Read the {@link guide/accessibility ngAria Developer Guide} for a thorough explanation of each
|
||||
* directive.
|
||||
*
|
||||
*
|
||||
* ## Dependencies
|
||||
* Requires the {@link ngAria} module to be installed.
|
||||
@@ -238,5 +298,16 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
.directive('ngDisabled', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
|
||||
}])
|
||||
.directive('ngMessages', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngMessages',
|
||||
link: function(scope, elem, attr, ngMessages) {
|
||||
if (!elem.attr('aria-live')) {
|
||||
elem.attr('aria-live', 'assertive');
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
.directive('ngClick', ngAriaTabindex)
|
||||
.directive('ngDblclick', ngAriaTabindex);
|
||||
|
||||
Vendored
+107
-16
@@ -110,7 +110,7 @@ angular.mock.$Browser = function() {
|
||||
self.defer.now += delay;
|
||||
} else {
|
||||
if (self.deferredFns.length) {
|
||||
self.defer.now = self.deferredFns[self.deferredFns.length-1].time;
|
||||
self.defer.now = self.deferredFns[self.deferredFns.length - 1].time;
|
||||
} else {
|
||||
throw new Error('No deferred tasks to be flushed');
|
||||
}
|
||||
@@ -421,7 +421,7 @@ angular.mock.$LogProvider = function() {
|
||||
});
|
||||
});
|
||||
if (errors.length) {
|
||||
errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or "+
|
||||
errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or " +
|
||||
"an expected log message was not checked and removed:");
|
||||
errors.push('');
|
||||
throw new Error(errors.join('\n---------\n'));
|
||||
@@ -454,17 +454,17 @@ angular.mock.$LogProvider = function() {
|
||||
* @returns {promise} A promise which will be notified on each iteration.
|
||||
*/
|
||||
angular.mock.$IntervalProvider = function() {
|
||||
this.$get = ['$rootScope', '$q',
|
||||
function($rootScope, $q) {
|
||||
this.$get = ['$browser', '$rootScope', '$q', '$$q',
|
||||
function($browser, $rootScope, $q, $$q) {
|
||||
var repeatFns = [],
|
||||
nextRepeatId = 0,
|
||||
now = 0;
|
||||
|
||||
var $interval = function(fn, delay, count, invokeApply) {
|
||||
var deferred = $q.defer(),
|
||||
promise = deferred.promise,
|
||||
iteration = 0,
|
||||
skipApply = (angular.isDefined(invokeApply) && !invokeApply);
|
||||
var iteration = 0,
|
||||
skipApply = (angular.isDefined(invokeApply) && !invokeApply),
|
||||
deferred = (skipApply ? $$q : $q).defer(),
|
||||
promise = deferred.promise;
|
||||
|
||||
count = (angular.isDefined(count)) ? count : 0;
|
||||
promise.then(null, null, fn);
|
||||
@@ -487,7 +487,11 @@ angular.mock.$IntervalProvider = function() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!skipApply) $rootScope.$apply();
|
||||
if (skipApply) {
|
||||
$browser.defer.flush();
|
||||
} else {
|
||||
$rootScope.$apply();
|
||||
}
|
||||
}
|
||||
|
||||
repeatFns.push({
|
||||
@@ -574,10 +578,10 @@ function jsonStringToDate(string) {
|
||||
tzMin = int(match[9] + match[11]);
|
||||
}
|
||||
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
|
||||
date.setUTCHours(int(match[4]||0) - tzHour,
|
||||
int(match[5]||0) - tzMin,
|
||||
int(match[6]||0),
|
||||
int(match[7]||0));
|
||||
date.setUTCHours(int(match[4] || 0) - tzHour,
|
||||
int(match[5] || 0) - tzMin,
|
||||
int(match[6] || 0),
|
||||
int(match[7] || 0));
|
||||
return date;
|
||||
}
|
||||
return string;
|
||||
@@ -656,7 +660,7 @@ angular.mock.TzDate = function(offset, timestamp) {
|
||||
}
|
||||
|
||||
var localOffset = new Date(timestamp).getTimezoneOffset();
|
||||
self.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
|
||||
self.offsetDiff = localOffset * 60 * 1000 - offset * 1000 * 60 * 60;
|
||||
self.date = new Date(timestamp + self.offsetDiff);
|
||||
|
||||
self.getTime = function() {
|
||||
@@ -808,7 +812,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
|
||||
animate.queue.push({
|
||||
event: method,
|
||||
element: arguments[0],
|
||||
options: arguments[arguments.length-1],
|
||||
options: arguments[arguments.length - 1],
|
||||
args: arguments
|
||||
});
|
||||
return $delegate[method].apply($delegate, arguments);
|
||||
@@ -1763,7 +1767,7 @@ angular.mock.$RAFDecorator = ['$delegate', function($delegate) {
|
||||
}
|
||||
|
||||
var length = queue.length;
|
||||
for (var i=0;i<length;i++) {
|
||||
for (var i = 0; i < length; i++) {
|
||||
queue[i]();
|
||||
}
|
||||
|
||||
@@ -1823,6 +1827,7 @@ angular.module('ngMock', ['ng']).provider({
|
||||
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
|
||||
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
|
||||
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
|
||||
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
|
||||
}]);
|
||||
|
||||
/**
|
||||
@@ -2031,6 +2036,92 @@ angular.mock.e2e.$httpBackendDecorator =
|
||||
['$rootScope', '$delegate', '$browser', createHttpBackendMock];
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc type
|
||||
* @name $rootScope.Scope
|
||||
* @module ngMock
|
||||
* @description
|
||||
* {@link ng.$rootScope.Scope Scope} type decorated with helper methods useful for testing. These
|
||||
* methods are automatically available on any {@link ng.$rootScope.Scope Scope} instance when
|
||||
* `ngMock` module is loaded.
|
||||
*
|
||||
* In addition to all the regular `Scope` methods, the following helper methods are available:
|
||||
*/
|
||||
angular.mock.$RootScopeDecorator = function($delegate) {
|
||||
|
||||
var $rootScopePrototype = Object.getPrototypeOf($delegate);
|
||||
|
||||
$rootScopePrototype.$countChildScopes = countChildScopes;
|
||||
$rootScopePrototype.$countWatchers = countWatchers;
|
||||
|
||||
return $delegate;
|
||||
|
||||
// ------------------------------------------------------------------------------------------ //
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $rootScope.Scope#$countChildScopes
|
||||
* @module ngMock
|
||||
* @description
|
||||
* Counts all the direct and indirect child scopes of the current scope.
|
||||
*
|
||||
* The current scope is excluded from the count. The count includes all isolate child scopes.
|
||||
*
|
||||
* @returns {number} Total number of child scopes.
|
||||
*/
|
||||
function countChildScopes() {
|
||||
// jshint validthis: true
|
||||
var count = 0; // exclude the current scope
|
||||
var pendingChildHeads = [this.$$childHead];
|
||||
var currentScope;
|
||||
|
||||
while (pendingChildHeads.length) {
|
||||
currentScope = pendingChildHeads.shift();
|
||||
|
||||
while (currentScope) {
|
||||
count += 1;
|
||||
pendingChildHeads.push(currentScope.$$childHead);
|
||||
currentScope = currentScope.$$nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $rootScope.Scope#$countWatchers
|
||||
* @module ngMock
|
||||
* @description
|
||||
* Counts all the watchers of direct and indirect child scopes of the current scope.
|
||||
*
|
||||
* The watchers of the current scope are included in the count and so are all the watchers of
|
||||
* isolate child scopes.
|
||||
*
|
||||
* @returns {number} Total number of watchers.
|
||||
*/
|
||||
function countWatchers() {
|
||||
// jshint validthis: true
|
||||
var count = this.$$watchers ? this.$$watchers.length : 0; // include the current scope
|
||||
var pendingChildHeads = [this.$$childHead];
|
||||
var currentScope;
|
||||
|
||||
while (pendingChildHeads.length) {
|
||||
currentScope = pendingChildHeads.shift();
|
||||
|
||||
while (currentScope) {
|
||||
count += currentScope.$$watchers ? currentScope.$$watchers.length : 0;
|
||||
pendingChildHeads.push(currentScope.$$childHead);
|
||||
currentScope = currentScope.$$nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (window.jasmine || window.mocha) {
|
||||
|
||||
var currentSpec = null,
|
||||
|
||||
+27
-9
@@ -141,27 +141,45 @@ function $RouteProvider() {
|
||||
* Adds a new route definition to the `$route` service.
|
||||
*/
|
||||
this.when = function(path, route) {
|
||||
//copy original route object to preserve params inherited from proto chain
|
||||
var routeCopy = angular.copy(route);
|
||||
if (angular.isUndefined(routeCopy.reloadOnSearch)) {
|
||||
routeCopy.reloadOnSearch = true;
|
||||
}
|
||||
if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) {
|
||||
routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch;
|
||||
}
|
||||
routes[path] = angular.extend(
|
||||
{reloadOnSearch: true},
|
||||
route,
|
||||
path && pathRegExp(path, route)
|
||||
routeCopy,
|
||||
path && pathRegExp(path, routeCopy)
|
||||
);
|
||||
|
||||
// create redirection for trailing slashes
|
||||
if (path) {
|
||||
var redirectPath = (path[path.length-1] == '/')
|
||||
? path.substr(0, path.length-1)
|
||||
: path +'/';
|
||||
var redirectPath = (path[path.length - 1] == '/')
|
||||
? path.substr(0, path.length - 1)
|
||||
: path + '/';
|
||||
|
||||
routes[redirectPath] = angular.extend(
|
||||
{redirectTo: path},
|
||||
pathRegExp(redirectPath, route)
|
||||
pathRegExp(redirectPath, routeCopy)
|
||||
);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @name $routeProvider#caseInsensitiveMatch
|
||||
* @description
|
||||
*
|
||||
* A boolean property indicating if routes defined
|
||||
* using this provider should be matched using a case sensitive
|
||||
* algorithm. Defaults to `false`.
|
||||
*/
|
||||
this.caseInsensitiveMatch = false;
|
||||
|
||||
/**
|
||||
* @param path {string} path
|
||||
* @param opts {Object} options
|
||||
@@ -437,7 +455,7 @@ function $RouteProvider() {
|
||||
* {@link ng.$location $location} hasn't changed.
|
||||
*
|
||||
* As a result of that, {@link ngRoute.directive:ngView ngView}
|
||||
* creates new scope, reinstantiates the controller.
|
||||
* creates new scope and reinstantiates the controller.
|
||||
*/
|
||||
reload: function() {
|
||||
forceReload = true;
|
||||
@@ -630,7 +648,7 @@ function $RouteProvider() {
|
||||
*/
|
||||
function interpolate(string, params) {
|
||||
var result = [];
|
||||
angular.forEach((string||'').split(':'), function(segment, i) {
|
||||
angular.forEach((string || '').split(':'), function(segment, i) {
|
||||
if (i === 0) {
|
||||
result.push(segment);
|
||||
} else {
|
||||
|
||||
+19
-19
@@ -213,29 +213,29 @@ var validElements = angular.extend({},
|
||||
//Attributes that have href and hence need to be sanitized
|
||||
var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href");
|
||||
|
||||
var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
|
||||
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
|
||||
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
|
||||
'scope,scrolling,shape,size,span,start,summary,target,title,type,'+
|
||||
var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
|
||||
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
|
||||
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
|
||||
'scope,scrolling,shape,size,span,start,summary,target,title,type,' +
|
||||
'valign,value,vspace,width');
|
||||
|
||||
// SVG attributes (without "id" and "name" attributes)
|
||||
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
|
||||
var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,'+
|
||||
'attributeName,attributeType,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,'+
|
||||
'color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,'+
|
||||
'font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,'+
|
||||
'gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,'+
|
||||
'keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,'+
|
||||
'markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,'+
|
||||
'overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,'+
|
||||
'repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,'+
|
||||
'stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,'+
|
||||
'stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,'+
|
||||
'stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,'+
|
||||
'underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,'+
|
||||
'viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,'+
|
||||
'xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,'+
|
||||
var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
|
||||
'attributeName,attributeType,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,' +
|
||||
'color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,' +
|
||||
'font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,' +
|
||||
'gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,' +
|
||||
'keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,' +
|
||||
'markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,' +
|
||||
'overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,' +
|
||||
'repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,' +
|
||||
'stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,' +
|
||||
'stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,' +
|
||||
'stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,' +
|
||||
'underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,' +
|
||||
'viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,' +
|
||||
'xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,' +
|
||||
'zoomAndPan');
|
||||
|
||||
var validAttrs = angular.extend({},
|
||||
|
||||
@@ -116,7 +116,7 @@ angular.scenario.Describe.prototype.it = function(name, body) {
|
||||
*/
|
||||
angular.scenario.Describe.prototype.iit = function(name, body) {
|
||||
this.it.apply(this, arguments);
|
||||
this.its[this.its.length-1].only = true;
|
||||
this.its[this.its.length - 1].only = true;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -211,7 +211,7 @@ angular.scenario.ObjectModel.Spec.prototype.addStep = function(name) {
|
||||
* @return {Object} the step
|
||||
*/
|
||||
angular.scenario.ObjectModel.Spec.prototype.getLastStep = function() {
|
||||
return this.steps[this.steps.length-1];
|
||||
return this.steps[this.steps.length - 1];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -217,10 +217,10 @@ function callerFile(offset) {
|
||||
if (line) {
|
||||
if (line.indexOf('@') !== -1) {
|
||||
// Firefox
|
||||
line = line.substring(line.indexOf('@')+1);
|
||||
line = line.substring(line.indexOf('@') + 1);
|
||||
} else {
|
||||
// Chrome
|
||||
line = line.substring(line.indexOf('(')+1).replace(')', '');
|
||||
line = line.substring(line.indexOf('(') + 1).replace(')', '');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
|
||||
var element = windowJquery(this),
|
||||
bindings;
|
||||
if (bindings = element.data('$binding')) {
|
||||
for (var expressions = [], binding, j=0, jj=bindings.length; j<jj; j++) {
|
||||
for (var expressions = [], binding, j=0, jj=bindings.length; j < jj; j++) {
|
||||
binding = bindings[j];
|
||||
|
||||
if (binding.expressions) {
|
||||
|
||||
@@ -163,7 +163,7 @@ angular.scenario.dsl('expect', function() {
|
||||
*/
|
||||
angular.scenario.dsl('using', function() {
|
||||
return function(selector, label) {
|
||||
this.selector = _jQuery.trim((this.selector||'') + ' ' + selector);
|
||||
this.selector = _jQuery.trim((this.selector || '') + ' ' + selector);
|
||||
if (angular.isString(label) && label.length) {
|
||||
this.label = label + ' ( ' + this.selector + ' )';
|
||||
} else {
|
||||
|
||||
@@ -108,7 +108,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
|
||||
// Splices out the allowable region from the list after it has been used.
|
||||
function checkAllowableRegions(touchCoordinates, x, y) {
|
||||
for (var i = 0; i < touchCoordinates.length; i += 2) {
|
||||
if (hit(touchCoordinates[i], touchCoordinates[i+1], x, y)) {
|
||||
if (hit(touchCoordinates[i], touchCoordinates[i + 1], x, y)) {
|
||||
touchCoordinates.splice(i, i + 2);
|
||||
return true; // allowable region
|
||||
}
|
||||
@@ -173,7 +173,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
|
||||
$timeout(function() {
|
||||
// Remove the allowable region.
|
||||
for (var i = 0; i < touchCoordinates.length; i += 2) {
|
||||
if (touchCoordinates[i] == x && touchCoordinates[i+1] == y) {
|
||||
if (touchCoordinates[i] == x && touchCoordinates[i + 1] == y) {
|
||||
touchCoordinates.splice(i, i + 2);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
/* global: toDebugString: true */
|
||||
|
||||
function serializeObject(obj) {
|
||||
var seen = [];
|
||||
|
||||
return JSON.stringify(obj, function(key, val) {
|
||||
val = toJsonReplacer(key, val);
|
||||
if (isObject(val)) {
|
||||
|
||||
if (seen.indexOf(val) >= 0) return '<<already seen>>';
|
||||
|
||||
seen.push(val);
|
||||
}
|
||||
return val;
|
||||
});
|
||||
}
|
||||
|
||||
function toDebugString(obj) {
|
||||
if (typeof obj === 'function') {
|
||||
return obj.toString().replace(/ \{[\s\S]*$/, '');
|
||||
} else if (typeof obj === 'undefined') {
|
||||
return 'undefined';
|
||||
} else if (typeof obj !== 'string') {
|
||||
return serializeObject(obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
"angularModule": false,
|
||||
"nodeName_": false,
|
||||
"uid": false,
|
||||
"toDebugString": false,
|
||||
|
||||
"lowercase": false,
|
||||
"uppercase": false,
|
||||
|
||||
+8
-8
@@ -202,21 +202,21 @@ describe('Binder', function() {
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(sortedHtml(element)).toBe(
|
||||
'<div>'+
|
||||
'<div>' +
|
||||
'<!-- ngRepeat: m in model -->' +
|
||||
'<div name="a" ng-repeat="m in model">'+
|
||||
'<div name="a" ng-repeat="m in model">' +
|
||||
'<!-- ngRepeat: i in m.item -->' +
|
||||
'<ul name="a1" ng-repeat="i in m.item"></ul>'+
|
||||
'<ul name="a1" ng-repeat="i in m.item"></ul>' +
|
||||
'<!-- end ngRepeat: i in m.item -->' +
|
||||
'<ul name="a2" ng-repeat="i in m.item"></ul>'+
|
||||
'<ul name="a2" ng-repeat="i in m.item"></ul>' +
|
||||
'<!-- end ngRepeat: i in m.item -->' +
|
||||
'</div>'+
|
||||
'</div>' +
|
||||
'<!-- end ngRepeat: m in model -->' +
|
||||
'<div name="b" ng-repeat="m in model">'+
|
||||
'<div name="b" ng-repeat="m in model">' +
|
||||
'<!-- ngRepeat: i in m.item -->' +
|
||||
'<ul name="b1" ng-repeat="i in m.item"></ul>'+
|
||||
'<ul name="b1" ng-repeat="i in m.item"></ul>' +
|
||||
'<!-- end ngRepeat: i in m.item -->' +
|
||||
'<ul name="b2" ng-repeat="i in m.item"></ul>'+
|
||||
'<ul name="b2" ng-repeat="i in m.item"></ul>' +
|
||||
'<!-- end ngRepeat: i in m.item -->' +
|
||||
'</div>' +
|
||||
'<!-- end ngRepeat: m in model -->' +
|
||||
|
||||
@@ -7,7 +7,7 @@ describe('injector', function() {
|
||||
|
||||
beforeEach(module(function($provide, $injector) {
|
||||
providers = function(name, factory, annotations) {
|
||||
$provide.factory(name, extend(factory, annotations||{}));
|
||||
$provide.factory(name, extend(factory, annotations || {}));
|
||||
};
|
||||
providerInjector = $injector;
|
||||
}));
|
||||
@@ -28,7 +28,7 @@ describe('injector', function() {
|
||||
|
||||
it('should inject providers', function() {
|
||||
providers('a', function() {return 'Mi';});
|
||||
providers('b', function(mi) {return mi+'sko';}, {$inject:['a']});
|
||||
providers('b', function(mi) {return mi + 'sko';}, {$inject:['a']});
|
||||
expect(injector.get('b')).toEqual('Misko');
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ module.exports = middlewareFactory;
|
||||
|
||||
function middlewareFactory(base) {
|
||||
base = base || '/e2e';
|
||||
while (base.length && base[base.length-1] === '/') base = base.slice(0, base.length-1);
|
||||
while (base.length && base[base.length - 1] === '/') base = base.slice(0, base.length - 1);
|
||||
var fixture_regexp = new RegExp('^' + base + '/fixtures/([a-zA-Z0-9_-]+)(/(index.html)?)?$');
|
||||
var static_regexp = new RegExp('^' + base + '/fixtures/([a-zA-Z0-9_-]+)(/.*)$');
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ beforeEach(function() {
|
||||
// we need to check element.getAttribute for SVG nodes
|
||||
var hidden = true;
|
||||
forEach(angular.element(element), function(element) {
|
||||
if ((' ' +(element.getAttribute('class') || '') + ' ').indexOf(' ng-hide ') === -1) {
|
||||
if ((' ' + (element.getAttribute('class') || '') + ' ').indexOf(' ng-hide ') === -1) {
|
||||
hidden = false;
|
||||
}
|
||||
});
|
||||
@@ -45,17 +45,17 @@ beforeEach(function() {
|
||||
toBeTouched: cssMatcher('ng-touched', 'ng-untouched'),
|
||||
toBeAPromise: function() {
|
||||
this.message = valueFn(
|
||||
"Expected object " + (this.isNot ? "not ": "") + "to be a promise");
|
||||
"Expected object " + (this.isNot ? "not " : "") + "to be a promise");
|
||||
return isPromiseLike(this.actual);
|
||||
},
|
||||
toBeShown: function() {
|
||||
this.message = valueFn(
|
||||
"Expected element " + (this.isNot ? "": "not ") + "to have 'ng-hide' class");
|
||||
"Expected element " + (this.isNot ? "" : "not ") + "to have 'ng-hide' class");
|
||||
return !isNgElementHidden(this.actual);
|
||||
},
|
||||
toBeHidden: function() {
|
||||
this.message = valueFn(
|
||||
"Expected element " + (this.isNot ? "not ": "") + "to have 'ng-hide' class");
|
||||
"Expected element " + (this.isNot ? "not " : "") + "to have 'ng-hide' class");
|
||||
return isNgElementHidden(this.actual);
|
||||
},
|
||||
|
||||
@@ -168,7 +168,7 @@ beforeEach(function() {
|
||||
return "Expected '" + angular.mock.dump(this.actual) + "' to have class '" + clazz + "'.";
|
||||
};
|
||||
var classes = clazz.trim().split(/\s+/);
|
||||
for (var i=0; i<classes.length; ++i) {
|
||||
for (var i = 0; i < classes.length; ++i) {
|
||||
if (!jqLiteHasClass(this.actual[0], classes[i])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ function sortedHtml(element, showNgClass) {
|
||||
|
||||
if (node.nodeName == "#text") {
|
||||
html += node.nodeValue.
|
||||
replace(/&(\w+[&;\W])?/g, function(match, entity) {return entity?match:'&';}).
|
||||
replace(/&(\w+[&;\W])?/g, function(match, entity) {return entity ? match : '&';}).
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>');
|
||||
} else if (node.nodeName == "#comment") {
|
||||
@@ -169,26 +169,26 @@ function sortedHtml(element, showNgClass) {
|
||||
if (className) {
|
||||
attrs.push(' class="' + className + '"');
|
||||
}
|
||||
for (var i=0; i<attributes.length; i++) {
|
||||
if (i>0 && attributes[i] == attributes[i-1])
|
||||
for (var i = 0; i < attributes.length; i++) {
|
||||
if (i > 0 && attributes[i] == attributes[i - 1])
|
||||
continue; //IE9 creates dupes. Ignore them!
|
||||
|
||||
var attr = attributes[i];
|
||||
if (attr.name.match(/^ng[\:\-]/) ||
|
||||
(attr.value || attr.value === '') &&
|
||||
attr.value !='null' &&
|
||||
attr.value !='auto' &&
|
||||
attr.value !='false' &&
|
||||
attr.value !='inherit' &&
|
||||
(attr.value !='0' || attr.name =='value') &&
|
||||
attr.name !='loop' &&
|
||||
attr.name !='complete' &&
|
||||
attr.name !='maxLength' &&
|
||||
attr.name !='size' &&
|
||||
attr.name !='class' &&
|
||||
attr.name !='start' &&
|
||||
attr.name !='tabIndex' &&
|
||||
attr.name !='style' &&
|
||||
attr.value != 'null' &&
|
||||
attr.value != 'auto' &&
|
||||
attr.value != 'false' &&
|
||||
attr.value != 'inherit' &&
|
||||
(attr.value != '0' || attr.name == 'value') &&
|
||||
attr.name != 'loop' &&
|
||||
attr.name != 'complete' &&
|
||||
attr.name != 'maxLength' &&
|
||||
attr.name != 'size' &&
|
||||
attr.name != 'class' &&
|
||||
attr.name != 'start' &&
|
||||
attr.name != 'tabIndex' &&
|
||||
attr.name != 'style' &&
|
||||
attr.name.substr(0, 6) != 'jQuery') {
|
||||
// in IE we need to check for all of these.
|
||||
if (/ng\d+/.exec(attr.name) ||
|
||||
@@ -220,7 +220,7 @@ function sortedHtml(element, showNgClass) {
|
||||
}
|
||||
for (var css in node.style) {
|
||||
var value = node.style[css];
|
||||
if (isString(value) && isString(css) && css != 'cssText' && value && (1*css != css)) {
|
||||
if (isString(value) && isString(css) && css != 'cssText' && value && (1 * css != css)) {
|
||||
var text = lowercase(css + ': ' + value);
|
||||
if (value != 'false' && style.indexOf(text) == -1) {
|
||||
style.push(text);
|
||||
@@ -240,7 +240,7 @@ function sortedHtml(element, showNgClass) {
|
||||
}
|
||||
html += '>';
|
||||
var children = node.childNodes;
|
||||
for (var j=0; j<children.length; j++) {
|
||||
for (var j = 0; j < children.length; j++) {
|
||||
toString(children[j]);
|
||||
}
|
||||
html += '</' + node.nodeName.toLowerCase() + '>';
|
||||
|
||||
@@ -60,6 +60,13 @@ describe('minErr', function() {
|
||||
toMatch(/^\[test:26\] false: false; zero: 0; null: null; undefined: undefined; emptyStr: /);
|
||||
});
|
||||
|
||||
it('should handle arguments that are objects with cyclic references', function() {
|
||||
var a = { b: { } };
|
||||
a.b.a = a;
|
||||
|
||||
var myError = testError('26', 'a is {0}', a);
|
||||
expect(myError.message).toMatch(/a is {"b":{"a":"<<already seen>>"}}/);
|
||||
});
|
||||
|
||||
it('should preserve interpolation markers when fewer arguments than needed are provided', function() {
|
||||
// this way we can easily see if we are passing fewer args than needed
|
||||
|
||||
@@ -337,7 +337,7 @@ describe('browser', function() {
|
||||
it('should log warnings when 4kb per cookie storage limit is reached', function() {
|
||||
var i, longVal = '', cookieStr;
|
||||
|
||||
for (i=0; i<4083; i++) {
|
||||
for (i = 0; i < 4083; i++) {
|
||||
longVal += 'x';
|
||||
}
|
||||
|
||||
@@ -920,7 +920,7 @@ describe('browser', function() {
|
||||
});
|
||||
}
|
||||
|
||||
describe('update $location when it was changed outside of Angular in sync '+
|
||||
describe('update $location when it was changed outside of Angular in sync ' +
|
||||
'before $digest was called', function() {
|
||||
|
||||
it('should work with no history support, no html5Mode', function() {
|
||||
|
||||
@@ -181,7 +181,7 @@ describe('$cacheFactory', function() {
|
||||
var cache = $cacheFactory('cache1', {capacity: 5});
|
||||
expect(cache.info().size).toBe(0);
|
||||
|
||||
for (var i=0; i<5; i++) {
|
||||
for (var i = 0; i < 5; i++) {
|
||||
cache.put('id' + i, i);
|
||||
}
|
||||
|
||||
|
||||
+162
-49
@@ -26,6 +26,17 @@ describe('$compile', function() {
|
||||
return !!d.toString().match(/SVGForeignObject/);
|
||||
}
|
||||
|
||||
function getChildScopes(scope) {
|
||||
var children = [];
|
||||
if (!scope.$$childHead) { return children; }
|
||||
var childScope = scope.$$childHead;
|
||||
do {
|
||||
children.push(childScope);
|
||||
children = children.concat(getChildScopes(childScope));
|
||||
} while ((childScope = childScope.$$nextSibling));
|
||||
return children;
|
||||
}
|
||||
|
||||
var element, directive, $compile, $rootScope;
|
||||
|
||||
beforeEach(module(provideLog, function($provide, $compileProvider) {
|
||||
@@ -264,7 +275,7 @@ describe('$compile', function() {
|
||||
}));
|
||||
|
||||
// NOTE: This test may be redundant.
|
||||
it('should handle custom svg containers that transclude to foreignObject'+
|
||||
it('should handle custom svg containers that transclude to foreignObject' +
|
||||
' that transclude to custom svg containers that transclude to custom elements', inject(function() {
|
||||
element = jqLite('<div><svg-container>' +
|
||||
'<my-foreign-object><svg-container><svg-circle></svg-circle></svg-container></my-foreign-object>' +
|
||||
@@ -289,7 +300,7 @@ describe('$compile', function() {
|
||||
|
||||
}));
|
||||
|
||||
it('should support directives with SVG templates and a slow url '+
|
||||
it('should support directives with SVG templates and a slow url ' +
|
||||
'that are stamped out later by a transcluding directive', function() {
|
||||
module(function() {
|
||||
directive('svgCircleUrl', valueFn({
|
||||
@@ -344,8 +355,8 @@ describe('$compile', function() {
|
||||
|
||||
it('should not wrap root whitespace text nodes in spans', function() {
|
||||
element = jqLite(
|
||||
'<div> <div>A</div>\n '+ // The spaces and newlines here should not get wrapped
|
||||
'<div>B</div>C\t\n '+ // The "C", tabs and spaces here will be wrapped
|
||||
'<div> <div>A</div>\n ' + // The spaces and newlines here should not get wrapped
|
||||
'<div>B</div>C\t\n ' + // The "C", tabs and spaces here will be wrapped
|
||||
'</div>');
|
||||
$compile(element.contents())($rootScope);
|
||||
var spans = element.find('span');
|
||||
@@ -1576,7 +1587,7 @@ describe('$compile', function() {
|
||||
expect(function() {
|
||||
$compile('<div><div class="sync async"></div></div>');
|
||||
$httpBackend.flush();
|
||||
}).toThrowMinErr('$compile', 'multidir', 'Multiple directives [async, sync] asking for template on: '+
|
||||
}).toThrowMinErr('$compile', 'multidir', 'Multiple directives [async, sync] asking for template on: ' +
|
||||
'<div class="sync async">');
|
||||
});
|
||||
});
|
||||
@@ -2968,8 +2979,8 @@ describe('$compile', function() {
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<div><div ng-repeat="i in items">'+
|
||||
'<span some="id_{{i.id}}" observer></span>'+
|
||||
element = $compile('<div><div ng-repeat="i in items">' +
|
||||
'<span some="id_{{i.id}}" observer></span>' +
|
||||
'</div></div>')($rootScope);
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
@@ -3356,6 +3367,31 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should continue with a digets cycle when there is a two-way binding from the child to the parent', function() {
|
||||
module(function() {
|
||||
directive('hello', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: { greeting: '=' },
|
||||
template: '<button ng-click="setGreeting()">Say hi!</button>',
|
||||
link: function(scope) {
|
||||
scope.setGreeting = function() { scope.greeting = 'Hello!'; };
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($rootScope) {
|
||||
compile('<div>' +
|
||||
'<p>{{greeting}}</p>' +
|
||||
'<div><hello greeting="greeting"></hello></div>' +
|
||||
'</div>');
|
||||
$rootScope.$digest();
|
||||
browserTrigger(element.find('button'), 'click');
|
||||
expect(element.find('p').text()).toBe('Hello!');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -3522,7 +3558,7 @@ describe('$compile', function() {
|
||||
|
||||
|
||||
function test(literalString, literalValue) {
|
||||
compile('<div><span my-component reference="'+literalString+'">');
|
||||
compile('<div><span my-component reference="' + literalString + '">');
|
||||
|
||||
$rootScope.$apply();
|
||||
expect(componentScope.reference).toBe(literalValue);
|
||||
@@ -4299,7 +4335,7 @@ describe('$compile', function() {
|
||||
expect(asyncCtrlSpy).not.toHaveBeenCalled();
|
||||
|
||||
$templateCache.put('myDirectiveAsync.html', '<div>Hello!</div>');
|
||||
element = $compile('<div>'+
|
||||
element = $compile('<div>' +
|
||||
'<span xmy-directive-sync></span>' +
|
||||
'<span my-directive-async></span>' +
|
||||
'</div>')($rootScope);
|
||||
@@ -5111,7 +5147,7 @@ describe('$compile', function() {
|
||||
},
|
||||
link: function(scope, el, attr, ctrl, $transclude) {
|
||||
var i;
|
||||
for (i=0; i<cloneCount; i++) {
|
||||
for (i = 0; i < cloneCount; i++) {
|
||||
$transclude(cloneAttach);
|
||||
}
|
||||
|
||||
@@ -5127,7 +5163,7 @@ describe('$compile', function() {
|
||||
expect(transcludeCtrl).toBeDefined();
|
||||
|
||||
expect(element.data('$transcludeController')).toBe(transcludeCtrl);
|
||||
for (i=0; i<cloneCount; i++) {
|
||||
for (i = 0; i < cloneCount; i++) {
|
||||
expect(children.eq(i).data('$transcludeController')).toBeUndefined();
|
||||
}
|
||||
});
|
||||
@@ -5209,26 +5245,103 @@ describe('$compile', function() {
|
||||
});
|
||||
|
||||
|
||||
// see issue https://github.com/angular/angular.js/issues/9413
|
||||
describe('passing a parent bound transclude function to the link ' +
|
||||
'function returned from `$compile`', function() {
|
||||
|
||||
beforeEach(module(function() {
|
||||
directive('lazyCompile', function($compile) {
|
||||
return {
|
||||
compile: function(tElement, tAttrs) {
|
||||
var content = tElement.contents();
|
||||
tElement.empty();
|
||||
return function(scope, element, attrs, ctrls, transcludeFn) {
|
||||
element.append(content);
|
||||
$compile(content)(scope, undefined, {
|
||||
parentBoundTranscludeFn: transcludeFn
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
directive('toggle', valueFn({
|
||||
scope: {t: '=toggle'},
|
||||
transclude: true,
|
||||
template: '<div ng-if="t"><lazy-compile><div ng-transclude></div></lazy-compile></div>'
|
||||
}));
|
||||
}));
|
||||
|
||||
it('should preserve the bound scope', function() {
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile(
|
||||
'<div>' +
|
||||
'<div ng-init="outer=true"></div>' +
|
||||
'<div toggle="t">' +
|
||||
'<span ng-if="outer">Success</span><span ng-if="!outer">Error</span>' +
|
||||
'</div>' +
|
||||
'</div>')($rootScope);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect($rootScope.$countChildScopes()).toBe(4);
|
||||
expect(element.text()).toBe('Success');
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect($rootScope.$countChildScopes()).toBe(4);
|
||||
expect(element.text()).toBe('Success');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should preserve the bound scope when using recursive transclusion', function() {
|
||||
|
||||
directive('recursiveTransclude', valueFn({
|
||||
transclude: true,
|
||||
template: '<div><lazy-compile><div ng-transclude></div></lazy-compile></div>'
|
||||
}));
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile(
|
||||
'<div>' +
|
||||
'<div ng-init="outer=true"></div>' +
|
||||
'<div toggle="t">' +
|
||||
'<div recursive-transclude>' +
|
||||
'<span ng-if="outer">Success</span><span ng-if="!outer">Error</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>')($rootScope);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect($rootScope.$countChildScopes()).toBe(4);
|
||||
expect(element.text()).toBe('Success');
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect($rootScope.$countChildScopes()).toBe(4);
|
||||
expect(element.text()).toBe('Success');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// see issue https://github.com/angular/angular.js/issues/9095
|
||||
describe('removing a transcluded element', function() {
|
||||
|
||||
function countScopes($rootScope) {
|
||||
return [$rootScope].concat(
|
||||
getChildScopes($rootScope)
|
||||
).length;
|
||||
}
|
||||
|
||||
function getChildScopes(scope) {
|
||||
var children = [];
|
||||
if (!scope.$$childHead) { return children; }
|
||||
var childScope = scope.$$childHead;
|
||||
do {
|
||||
children.push(childScope);
|
||||
children = children.concat(getChildScopes(childScope));
|
||||
} while ((childScope = childScope.$$nextSibling));
|
||||
return children;
|
||||
}
|
||||
|
||||
beforeEach(module(function() {
|
||||
directive('toggle', function() {
|
||||
return {
|
||||
@@ -5251,22 +5364,22 @@ describe('$compile', function() {
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.text()).toContain('msg-1');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion, ngRepeat
|
||||
expect(countScopes($rootScope)).toEqual(4);
|
||||
expect($rootScope.$countChildScopes()).toBe(3);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.text()).not.toContain('msg-1');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.text()).toContain('msg-1');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion, ngRepeat
|
||||
expect(countScopes($rootScope)).toEqual(4);
|
||||
expect($rootScope.$countChildScopes()).toBe(3);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.text()).not.toContain('msg-1');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
|
||||
|
||||
@@ -5283,22 +5396,22 @@ describe('$compile', function() {
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.text()).toContain('msg-1msg-1');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion, ngRepeat
|
||||
expect(countScopes($rootScope)).toEqual(4);
|
||||
expect($rootScope.$countChildScopes()).toBe(3);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.text()).not.toContain('msg-1msg-1');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.text()).toContain('msg-1msg-1');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion, ngRepeat
|
||||
expect(countScopes($rootScope)).toEqual(4);
|
||||
expect($rootScope.$countChildScopes()).toBe(3);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.text()).not.toContain('msg-1msg-1');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
|
||||
|
||||
@@ -5314,22 +5427,22 @@ describe('$compile', function() {
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.html()).toContain('some comment');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion
|
||||
expect(countScopes($rootScope)).toEqual(3);
|
||||
expect($rootScope.$countChildScopes()).toBe(2);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.html()).not.toContain('some comment');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.html()).toContain('some comment');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion
|
||||
expect(countScopes($rootScope)).toEqual(3);
|
||||
expect($rootScope.$countChildScopes()).toBe(2);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.html()).not.toContain('some comment');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
|
||||
it('should not leak the transclude scope if the transcluded contains only text nodes',
|
||||
@@ -5344,22 +5457,22 @@ describe('$compile', function() {
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.html()).toContain('some text');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion
|
||||
expect(countScopes($rootScope)).toEqual(3);
|
||||
expect($rootScope.$countChildScopes()).toBe(2);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.html()).not.toContain('some text');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.html()).toContain('some text');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion
|
||||
expect(countScopes($rootScope)).toEqual(3);
|
||||
expect($rootScope.$countChildScopes()).toBe(2);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.html()).not.toContain('some text');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
|
||||
it('should mark as destroyed all sub scopes of the scope being destroyed',
|
||||
@@ -5787,7 +5900,7 @@ describe('$compile', function() {
|
||||
},
|
||||
link: function(scope, el, attr, ctrl, $transclude) {
|
||||
var i;
|
||||
for (i=0; i<cloneCount; i++) {
|
||||
for (i = 0; i < cloneCount; i++) {
|
||||
$transclude(cloneAttach);
|
||||
}
|
||||
|
||||
@@ -5800,7 +5913,7 @@ describe('$compile', function() {
|
||||
inject(function($compile) {
|
||||
element = $compile('<div><div transclude></div></div>')($rootScope);
|
||||
var children = element.children(), i;
|
||||
for (i=0; i<cloneCount; i++) {
|
||||
for (i = 0; i < cloneCount; i++) {
|
||||
expect(children.eq(i).data('$transcludeController')).toBe(transcludeCtrl);
|
||||
}
|
||||
});
|
||||
@@ -5863,7 +5976,7 @@ describe('$compile', function() {
|
||||
return {
|
||||
transclude: 'element',
|
||||
link: function(scope, element, attr, controllers, transclude) {
|
||||
log('innerAgain:'+lowercase(nodeName_(element))+':'+trim(element[0].data));
|
||||
log('innerAgain:' + lowercase(nodeName_(element)) + ':' + trim(element[0].data));
|
||||
transclude(scope, function(clone) {
|
||||
element.parent().append(clone);
|
||||
});
|
||||
@@ -5875,7 +5988,7 @@ describe('$compile', function() {
|
||||
replace: true,
|
||||
templateUrl: 'inner.html',
|
||||
link: function(scope, element) {
|
||||
log('inner:'+lowercase(nodeName_(element))+':'+trim(element[0].data));
|
||||
log('inner:' + lowercase(nodeName_(element)) + ':' + trim(element[0].data));
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -5883,7 +5996,7 @@ describe('$compile', function() {
|
||||
return {
|
||||
transclude: 'element',
|
||||
link: function(scope, element, attrs, controllers, transclude) {
|
||||
log('outer:'+lowercase(nodeName_(element))+':'+trim(element[0].data));
|
||||
log('outer:' + lowercase(nodeName_(element)) + ':' + trim(element[0].data));
|
||||
transclude(scope, function(clone) {
|
||||
element.parent().append(clone);
|
||||
});
|
||||
|
||||
@@ -165,9 +165,9 @@ describe('form', function() {
|
||||
|
||||
it('should throw an exception if an input has name="hasOwnProperty"', function() {
|
||||
doc = jqLite(
|
||||
'<form name="form">'+
|
||||
'<input name="hasOwnProperty" ng-model="some" />'+
|
||||
'<input name="other" ng-model="someOther" />'+
|
||||
'<form name="form">' +
|
||||
'<input name="hasOwnProperty" ng-model="some" />' +
|
||||
'<input name="other" ng-model="someOther" />' +
|
||||
'</form>');
|
||||
expect(function() {
|
||||
$compile(doc)(scope);
|
||||
|
||||
+107
-60
@@ -1010,7 +1010,7 @@ describe('ngModel', function() {
|
||||
function createInput(type) {
|
||||
inject(function($compile, $rootScope) {
|
||||
scope = $rootScope;
|
||||
inputElm = $compile('<input type="'+type+'" ng-model="val" custom-format/>')($rootScope);
|
||||
inputElm = $compile('<input type="' + type + '" ng-model="val" custom-format/>')($rootScope);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1544,8 +1544,8 @@ describe('input', function() {
|
||||
|
||||
it('should allow overriding the model update trigger event on text inputs', function() {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'ng-model-options="{ updateOn: \'blur\' }"'+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ updateOn: \'blur\' }"' +
|
||||
'/>');
|
||||
|
||||
changeInputValueTo('a');
|
||||
@@ -1556,8 +1556,8 @@ describe('input', function() {
|
||||
|
||||
it('should not dirty the input if nothing was changed before updateOn trigger', function() {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'ng-model-options="{ updateOn: \'blur\' }"'+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ updateOn: \'blur\' }"' +
|
||||
'/>');
|
||||
|
||||
browserTrigger(inputElm, 'blur');
|
||||
@@ -1566,8 +1566,8 @@ describe('input', function() {
|
||||
|
||||
it('should allow overriding the model update trigger event on text areas', function() {
|
||||
compileInput(
|
||||
'<textarea ng-model="name" name="alias" '+
|
||||
'ng-model-options="{ updateOn: \'blur\' }"'+
|
||||
'<textarea ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ updateOn: \'blur\' }"' +
|
||||
'/>');
|
||||
|
||||
changeInputValueTo('a');
|
||||
@@ -1578,8 +1578,8 @@ describe('input', function() {
|
||||
|
||||
it('should bind the element to a list of events', function() {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'ng-model-options="{ updateOn: \'blur mousemove\' }"'+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ updateOn: \'blur mousemove\' }"' +
|
||||
'/>');
|
||||
|
||||
changeInputValueTo('a');
|
||||
@@ -1596,8 +1596,8 @@ describe('input', function() {
|
||||
|
||||
it('should allow keeping the default update behavior on text inputs', function() {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'ng-model-options="{ updateOn: \'default\' }"'+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ updateOn: \'default\' }"' +
|
||||
'/>');
|
||||
|
||||
changeInputValueTo('a');
|
||||
@@ -1607,8 +1607,8 @@ describe('input', function() {
|
||||
|
||||
it('should allow overriding the model update trigger event on checkboxes', function() {
|
||||
compileInput(
|
||||
'<input type="checkbox" ng-model="checkbox" '+
|
||||
'ng-model-options="{ updateOn: \'blur\' }"'+
|
||||
'<input type="checkbox" ng-model="checkbox" ' +
|
||||
'ng-model-options="{ updateOn: \'blur\' }"' +
|
||||
'/>');
|
||||
|
||||
browserTrigger(inputElm, 'click');
|
||||
@@ -1624,8 +1624,8 @@ describe('input', function() {
|
||||
|
||||
it('should allow keeping the default update behavior on checkboxes', function() {
|
||||
compileInput(
|
||||
'<input type="checkbox" ng-model="checkbox" '+
|
||||
'ng-model-options="{ updateOn: \'blur default\' }"'+
|
||||
'<input type="checkbox" ng-model="checkbox" ' +
|
||||
'ng-model-options="{ updateOn: \'blur default\' }"' +
|
||||
'/>');
|
||||
|
||||
browserTrigger(inputElm, 'click');
|
||||
@@ -1638,14 +1638,14 @@ describe('input', function() {
|
||||
|
||||
it('should allow overriding the model update trigger event on radio buttons', function() {
|
||||
compileInput(
|
||||
'<input type="radio" ng-model="color" value="white" '+
|
||||
'ng-model-options="{ updateOn: \'blur\'}"'+
|
||||
'<input type="radio" ng-model="color" value="white" ' +
|
||||
'ng-model-options="{ updateOn: \'blur\'}"' +
|
||||
'/>' +
|
||||
'<input type="radio" ng-model="color" value="red" '+
|
||||
'ng-model-options="{ updateOn: \'blur\'}"'+
|
||||
'<input type="radio" ng-model="color" value="red" ' +
|
||||
'ng-model-options="{ updateOn: \'blur\'}"' +
|
||||
'/>' +
|
||||
'<input type="radio" ng-model="color" value="blue" '+
|
||||
'ng-model-options="{ updateOn: \'blur\'}"'+
|
||||
'<input type="radio" ng-model="color" value="blue" ' +
|
||||
'ng-model-options="{ updateOn: \'blur\'}"' +
|
||||
'/>');
|
||||
|
||||
scope.$apply("color = 'white'");
|
||||
@@ -1660,14 +1660,14 @@ describe('input', function() {
|
||||
|
||||
it('should allow keeping the default update behavior on radio buttons', function() {
|
||||
compileInput(
|
||||
'<input type="radio" ng-model="color" value="white" '+
|
||||
'ng-model-options="{ updateOn: \'blur default\' }"'+
|
||||
'<input type="radio" ng-model="color" value="white" ' +
|
||||
'ng-model-options="{ updateOn: \'blur default\' }"' +
|
||||
'/>' +
|
||||
'<input type="radio" ng-model="color" value="red" '+
|
||||
'ng-model-options="{ updateOn: \'blur default\' }"'+
|
||||
'<input type="radio" ng-model="color" value="red" ' +
|
||||
'ng-model-options="{ updateOn: \'blur default\' }"' +
|
||||
'/>' +
|
||||
'<input type="radio" ng-model="color" value="blue" '+
|
||||
'ng-model-options="{ updateOn: \'blur default\' }"'+
|
||||
'<input type="radio" ng-model="color" value="blue" ' +
|
||||
'ng-model-options="{ updateOn: \'blur default\' }"' +
|
||||
'/>');
|
||||
|
||||
scope.$apply("color = 'white'");
|
||||
@@ -1678,8 +1678,8 @@ describe('input', function() {
|
||||
|
||||
it('should trigger only after timeout in text inputs', inject(function($timeout) {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'ng-model-options="{ debounce: 10000 }"'+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ debounce: 10000 }"' +
|
||||
'/>');
|
||||
|
||||
changeInputValueTo('a');
|
||||
@@ -1695,8 +1695,8 @@ describe('input', function() {
|
||||
|
||||
it('should trigger only after timeout in checkboxes', inject(function($timeout) {
|
||||
compileInput(
|
||||
'<input type="checkbox" ng-model="checkbox" '+
|
||||
'ng-model-options="{ debounce: 10000 }"'+
|
||||
'<input type="checkbox" ng-model="checkbox" ' +
|
||||
'ng-model-options="{ debounce: 10000 }"' +
|
||||
'/>');
|
||||
|
||||
browserTrigger(inputElm, 'click');
|
||||
@@ -1711,11 +1711,11 @@ describe('input', function() {
|
||||
it('should trigger only after timeout in radio buttons', inject(function($timeout) {
|
||||
compileInput(
|
||||
'<input type="radio" ng-model="color" value="white" />' +
|
||||
'<input type="radio" ng-model="color" value="red" '+
|
||||
'ng-model-options="{ debounce: 20000 }"'+
|
||||
'<input type="radio" ng-model="color" value="red" ' +
|
||||
'ng-model-options="{ debounce: 20000 }"' +
|
||||
'/>' +
|
||||
'<input type="radio" ng-model="color" value="blue" '+
|
||||
'ng-model-options="{ debounce: 30000 }"'+
|
||||
'<input type="radio" ng-model="color" value="blue" ' +
|
||||
'ng-model-options="{ debounce: 30000 }"' +
|
||||
'/>');
|
||||
|
||||
browserTrigger(inputElm[0], 'click');
|
||||
@@ -1731,8 +1731,8 @@ describe('input', function() {
|
||||
|
||||
it('should not trigger digest while debouncing', inject(function($timeout) {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'ng-model-options="{ debounce: 10000 }"'+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ debounce: 10000 }"' +
|
||||
'/>');
|
||||
|
||||
var watchSpy = jasmine.createSpy('watchSpy');
|
||||
@@ -1748,11 +1748,11 @@ describe('input', function() {
|
||||
it('should allow selecting different debounce timeouts for each event',
|
||||
inject(function($timeout) {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'ng-model-options="{'+
|
||||
'updateOn: \'default blur\', '+
|
||||
'debounce: { default: 10000, blur: 5000 }'+
|
||||
'}"'+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{' +
|
||||
'updateOn: \'default blur\', ' +
|
||||
'debounce: { default: 10000, blur: 5000 }' +
|
||||
'}"' +
|
||||
'/>');
|
||||
|
||||
changeInputValueTo('a');
|
||||
@@ -1771,9 +1771,9 @@ describe('input', function() {
|
||||
|
||||
|
||||
it('should allow selecting different debounce timeouts for each event on checkboxes', inject(function($timeout) {
|
||||
compileInput('<input type="checkbox" ng-model="checkbox" '+
|
||||
'ng-model-options="{ '+
|
||||
'updateOn: \'default blur\', debounce: { default: 10000, blur: 5000 } }"'+
|
||||
compileInput('<input type="checkbox" ng-model="checkbox" ' +
|
||||
'ng-model-options="{ ' +
|
||||
'updateOn: \'default blur\', debounce: { default: 10000, blur: 5000 } }"' +
|
||||
'/>');
|
||||
|
||||
inputElm[0].checked = false;
|
||||
@@ -1793,9 +1793,9 @@ describe('input', function() {
|
||||
}));
|
||||
|
||||
it('should allow selecting 0 for non-default debounce timeouts for each event on checkboxes', inject(function($timeout) {
|
||||
compileInput('<input type="checkbox" ng-model="checkbox" '+
|
||||
'ng-model-options="{ '+
|
||||
'updateOn: \'default blur\', debounce: { default: 10000, blur: 0 } }"'+
|
||||
compileInput('<input type="checkbox" ng-model="checkbox" ' +
|
||||
'ng-model-options="{ ' +
|
||||
'updateOn: \'default blur\', debounce: { default: 10000, blur: 0 } }"' +
|
||||
'/>');
|
||||
|
||||
inputElm[0].checked = false;
|
||||
@@ -1814,9 +1814,9 @@ describe('input', function() {
|
||||
|
||||
it('should inherit model update settings from ancestor elements', inject(function($timeout) {
|
||||
var doc = $compile(
|
||||
'<form name="test" '+
|
||||
'<form name="test" ' +
|
||||
'ng-model-options="{ debounce: 10000, updateOn: \'blur\' }" >' +
|
||||
'<input type="text" ng-model="name" name="alias" />'+
|
||||
'<input type="text" ng-model="name" name="alias" />' +
|
||||
'</form>')(scope);
|
||||
scope.$digest();
|
||||
|
||||
@@ -1834,7 +1834,7 @@ describe('input', function() {
|
||||
|
||||
it('should flush debounced events when calling $commitViewValue directly', function() {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ debounce: 1000 }" />');
|
||||
|
||||
changeInputValueTo('a');
|
||||
@@ -1845,7 +1845,7 @@ describe('input', function() {
|
||||
|
||||
it('should cancel debounced events when calling $commitViewValue', inject(function($timeout) {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ debounce: 1000 }"/>');
|
||||
|
||||
changeInputValueTo('a');
|
||||
@@ -1859,7 +1859,7 @@ describe('input', function() {
|
||||
|
||||
it('should reset input val if rollbackViewValue called during pending update', function() {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ updateOn: \'blur\' }" />');
|
||||
|
||||
changeInputValueTo('a');
|
||||
@@ -1872,7 +1872,7 @@ describe('input', function() {
|
||||
|
||||
it('should allow canceling pending updates', inject(function($timeout) {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ updateOn: \'blur\' }" />');
|
||||
|
||||
changeInputValueTo('a');
|
||||
@@ -1885,7 +1885,7 @@ describe('input', function() {
|
||||
|
||||
it('should allow canceling debounced updates', inject(function($timeout) {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ debounce: 10000 }" />');
|
||||
|
||||
changeInputValueTo('a');
|
||||
@@ -1899,7 +1899,7 @@ describe('input', function() {
|
||||
|
||||
it('should handle model updates correctly even if rollbackViewValue is not invoked', function() {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ updateOn: \'blur\' }" />');
|
||||
|
||||
changeInputValueTo('a');
|
||||
@@ -1910,7 +1910,7 @@ describe('input', function() {
|
||||
|
||||
it('should reset input val if rollbackViewValue called during debounce', inject(function($timeout) {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'<input type="text" ng-model="name" name="alias" ' +
|
||||
'ng-model-options="{ debounce: 2000 }" />');
|
||||
|
||||
changeInputValueTo('a');
|
||||
@@ -1923,7 +1923,7 @@ describe('input', function() {
|
||||
|
||||
it('should not try to invoke a model if getterSetter is false', function() {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" '+
|
||||
'<input type="text" ng-model="name" ' +
|
||||
'ng-model-options="{ getterSetter: false }" />');
|
||||
|
||||
var spy = scope.name = jasmine.createSpy('setterSpy');
|
||||
@@ -1943,7 +1943,7 @@ describe('input', function() {
|
||||
|
||||
it('should always try to invoke a model if getterSetter is true', function() {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" '+
|
||||
'<input type="text" ng-model="name" ' +
|
||||
'ng-model-options="{ getterSetter: true }" />');
|
||||
|
||||
var spy = scope.name = jasmine.createSpy('setterSpy').andCallFake(function() {
|
||||
@@ -1971,7 +1971,7 @@ describe('input', function() {
|
||||
|
||||
it('should not fail on non-assignable model binding if getterSetter is true', function() {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="accessor(user, \'name\')" '+
|
||||
'<input type="text" ng-model="accessor(user, \'name\')" ' +
|
||||
'ng-model-options="{ getterSetter: true }" />');
|
||||
});
|
||||
|
||||
@@ -2224,6 +2224,53 @@ describe('input', function() {
|
||||
});
|
||||
}).toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/);
|
||||
});
|
||||
|
||||
it('should be invalid if entire string does not match pattern', function() {
|
||||
compileInput('<input type="text" name="test" ng-model="value" pattern="\\d{4}">');
|
||||
changeInputValueTo('1234');
|
||||
expect(scope.form.test.$error.pattern).not.toBe(true);
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
changeInputValueTo('123');
|
||||
expect(scope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
|
||||
changeInputValueTo('12345');
|
||||
expect(scope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should be cope with patterns that start with ^', function() {
|
||||
compileInput('<input type="text" name="test" ng-model="value" pattern="^\\d{4}">');
|
||||
changeInputValueTo('1234');
|
||||
expect(scope.form.test.$error.pattern).not.toBe(true);
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
changeInputValueTo('123');
|
||||
expect(scope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
|
||||
changeInputValueTo('12345');
|
||||
expect(scope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should be cope with patterns that end with $', function() {
|
||||
compileInput('<input type="text" name="test" ng-model="value" pattern="\\d{4}$">');
|
||||
changeInputValueTo('1234');
|
||||
expect(scope.form.test.$error.pattern).not.toBe(true);
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
changeInputValueTo('123');
|
||||
expect(scope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
|
||||
changeInputValueTo('12345');
|
||||
expect(scope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -125,8 +125,8 @@ describe('ngBind*', function() {
|
||||
it('should complain about accidental use of interpolation', inject(function($compile) {
|
||||
expect(function() {
|
||||
$compile('<div ng-bind-html="{{myHtml}}"></div>');
|
||||
}).toThrowMinErr('$parse', 'syntax', "Syntax Error: Token 'myHtml' is unexpected, " +
|
||||
"expecting [:] at column 3 of the expression [{{myHtml}}] starting at [myHtml}}].");
|
||||
}).toThrowMinErr('$parse', 'syntax',
|
||||
"Syntax Error: Token '{' invalid key at column 2 of the expression [{{myHtml}}] starting at [{myHtml}}]");
|
||||
}));
|
||||
|
||||
|
||||
|
||||
@@ -90,6 +90,25 @@ describe('event directives', function() {
|
||||
|
||||
});
|
||||
|
||||
describe('security', function() {
|
||||
it('should allow access to the $event object', inject(function($rootScope, $compile) {
|
||||
var scope = $rootScope.$new();
|
||||
element = $compile('<button ng-click="e = $event">BTN</button>')(scope);
|
||||
element.triggerHandler('click');
|
||||
expect(scope.e.target).toBe(element[0]);
|
||||
}));
|
||||
|
||||
it('should block access to DOM nodes (e.g. exposed via $event)', inject(function($rootScope, $compile) {
|
||||
var scope = $rootScope.$new();
|
||||
element = $compile('<button ng-click="e = $event.target">BTN</button>')(scope);
|
||||
expect(function() {
|
||||
element.triggerHandler('click');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is disallowed! ' +
|
||||
'Expression: e = $event.target');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('blur', function() {
|
||||
|
||||
describe('call the listener asynchronously during $apply', function() {
|
||||
|
||||
@@ -38,8 +38,28 @@ describe('select', function() {
|
||||
};
|
||||
|
||||
return equals(expectedValues, actualValues);
|
||||
},
|
||||
|
||||
toEqualOption: function(value, text, label) {
|
||||
var errors = [];
|
||||
if (this.actual.attr('value') !== value) {
|
||||
errors.push('Expected option value "' + this.actual.attr('value') + '" to equal "' + value + '"');
|
||||
}
|
||||
if (text && this.actual.text() !== text) {
|
||||
errors.push('Expected option value "' + this.actual.attr('value') + '" to equal "' + value + '"');
|
||||
}
|
||||
if (label && this.actual.attr('label') !== label) {
|
||||
errors.push('Expected option value "' + this.actual.attr('value') + '" to equal "' + value + '"');
|
||||
}
|
||||
|
||||
this.message = function() {
|
||||
return errors.join('\n');
|
||||
};
|
||||
|
||||
return errors.length === 0;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -756,9 +776,9 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="?"></option>');
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="10">ten</option>');
|
||||
expect(sortedHtml(options[2])).toEqual('<option value="20">twenty</option>');
|
||||
expect(options.eq(0)).toEqualOption('?', '');
|
||||
expect(options.eq(1)).toEqualOption('10', 'ten');
|
||||
expect(options.eq(2)).toEqualOption('20', 'twenty');
|
||||
});
|
||||
|
||||
it('should preserve value even when reference has changed (single&array)', function() {
|
||||
@@ -1047,9 +1067,9 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="0">A</option>');
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="1">B</option>');
|
||||
expect(sortedHtml(options[2])).toEqual('<option value="2">C</option>');
|
||||
expect(options.eq(0)).toEqualOption('0', 'A');
|
||||
expect(options.eq(1)).toEqualOption('1', 'B');
|
||||
expect(options.eq(2)).toEqualOption('2', 'C');
|
||||
});
|
||||
|
||||
it('should render zero as a valid display value', function() {
|
||||
@@ -1062,9 +1082,9 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="0">0</option>');
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="1">1</option>');
|
||||
expect(sortedHtml(options[2])).toEqual('<option value="2">2</option>');
|
||||
expect(options.eq(0)).toEqualOption('0', '0');
|
||||
expect(options.eq(1)).toEqualOption('1', '1');
|
||||
expect(options.eq(2)).toEqualOption('2', '2');
|
||||
});
|
||||
|
||||
|
||||
@@ -1081,9 +1101,9 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="blue">blue</option>');
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="green">green</option>');
|
||||
expect(sortedHtml(options[2])).toEqual('<option value="red">red</option>');
|
||||
expect(options.eq(0)).toEqualOption('blue', 'blue');
|
||||
expect(options.eq(1)).toEqualOption('green', 'green');
|
||||
expect(options.eq(2)).toEqualOption('red', 'red');
|
||||
expect(options[2].selected).toEqual(true);
|
||||
|
||||
scope.$apply(function() {
|
||||
@@ -1103,7 +1123,7 @@ describe('select', function() {
|
||||
});
|
||||
|
||||
expect(element.find('option').length).toEqual(1); // because we add special empty option
|
||||
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="?"></option>');
|
||||
expect(element.find('option')).toEqualOption('?','');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values.push({name:'A'});
|
||||
@@ -1111,15 +1131,15 @@ describe('select', function() {
|
||||
});
|
||||
|
||||
expect(element.find('option').length).toEqual(1);
|
||||
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
expect(element.find('option')).toEqualOption('0', 'A');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values.push({name:'B'});
|
||||
});
|
||||
|
||||
expect(element.find('option').length).toEqual(2);
|
||||
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
expect(sortedHtml(element.find('option')[1])).toEqual('<option value="1">B</option>');
|
||||
expect(element.find('option').eq(0)).toEqualOption('0', 'A');
|
||||
expect(element.find('option').eq(1)).toEqualOption('1', 'B');
|
||||
});
|
||||
|
||||
|
||||
@@ -1138,15 +1158,15 @@ describe('select', function() {
|
||||
});
|
||||
|
||||
expect(element.find('option').length).toEqual(2);
|
||||
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
expect(sortedHtml(element.find('option')[1])).toEqual('<option value="1">B</option>');
|
||||
expect(element.find('option').eq(0)).toEqualOption('0', 'A');
|
||||
expect(element.find('option').eq(1)).toEqualOption('1', 'B');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values.pop();
|
||||
});
|
||||
|
||||
expect(element.find('option').length).toEqual(1);
|
||||
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
expect(element.find('option')).toEqualOption('0', 'A');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values.pop();
|
||||
@@ -1198,9 +1218,9 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="0">B</option>');
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="1">C</option>');
|
||||
expect(sortedHtml(options[2])).toEqual('<option value="2">D</option>');
|
||||
expect(options.eq(0)).toEqualOption('0', 'B');
|
||||
expect(options.eq(1)).toEqualOption('1', 'C');
|
||||
expect(options.eq(2)).toEqualOption('2', 'D');
|
||||
});
|
||||
|
||||
|
||||
@@ -1244,7 +1264,7 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(1);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="regularProperty">visible</option>');
|
||||
expect(options.eq(0)).toEqualOption('regularProperty', 'visible');
|
||||
});
|
||||
|
||||
it('should allow expressions over multiple lines', function() {
|
||||
@@ -1268,8 +1288,8 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="0">2</option>');
|
||||
expect(sortedHtml(options[2])).toEqual('<option value="1">3</option>');
|
||||
expect(options.eq(1)).toEqualOption('0', '2');
|
||||
expect(options.eq(2)).toEqualOption('1', '3');
|
||||
});
|
||||
|
||||
it('should not update selected property of an option element on digest with no change event',
|
||||
@@ -1300,6 +1320,23 @@ describe('select', function() {
|
||||
expect(scope.selected).toBe(scope.values[0]);
|
||||
});
|
||||
|
||||
// bug fix #9621
|
||||
it('should update the label property', function() {
|
||||
// ng-options="value.name for value in values"
|
||||
// ng-model="selected"
|
||||
createSingleSelect();
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
});
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.eq(0).prop('label')).toEqual('A');
|
||||
expect(options.eq(1).prop('label')).toEqual('B');
|
||||
expect(options.eq(2).prop('label')).toEqual('C');
|
||||
});
|
||||
|
||||
describe('binding', function() {
|
||||
|
||||
it('should bind to scope value', function() {
|
||||
@@ -1426,8 +1463,8 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(2);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="0">C</option>');
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="1">B</option>');
|
||||
expect(options.eq(0)).toEqualOption('0', 'C');
|
||||
expect(options.eq(1)).toEqualOption('1', 'B');
|
||||
});
|
||||
|
||||
|
||||
@@ -1447,8 +1484,8 @@ describe('select', function() {
|
||||
|
||||
var options = element.find('option');
|
||||
expect(options.length).toEqual(2);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="a">C</option>');
|
||||
expect(sortedHtml(options[1])).toEqual('<option value="b">B</option>');
|
||||
expect(options.eq(0)).toEqualOption('a', 'C');
|
||||
expect(options.eq(1)).toEqualOption('b', 'B');
|
||||
});
|
||||
|
||||
|
||||
@@ -1629,6 +1666,20 @@ describe('select', function() {
|
||||
expect(element.val()).toEqual('?');
|
||||
expect(element.find('option').eq(0).attr('selected')).toEqual('selected');
|
||||
});
|
||||
|
||||
|
||||
it('should select the correct option for selectAs and falsy values', function() {
|
||||
scope.values = [{value: 0, label: 'zero'}, {value: 1, label: 'one'}];
|
||||
scope.selected = '';
|
||||
createSelect({
|
||||
'ng-model': 'selected',
|
||||
'ng-options': 'option.value as option.label for option in values'
|
||||
});
|
||||
|
||||
var option = element.find('option').eq(0);
|
||||
expect(option.val()).toBe('?');
|
||||
expect(option.text()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2178,8 +2229,8 @@ describe('select', function() {
|
||||
it('should throw an exception if an option value interpolates to "hasOwnProperty"', function() {
|
||||
scope.hasOwnPropertyOption = "hasOwnProperty";
|
||||
expect(function() {
|
||||
compile('<select ng-model="x">'+
|
||||
'<option>{{hasOwnPropertyOption}}</option>'+
|
||||
compile('<select ng-model="x">' +
|
||||
'<option>{{hasOwnPropertyOption}}</option>' +
|
||||
'</select>');
|
||||
}).toThrowMinErr('ng','badname', 'hasOwnProperty is not a valid "option value" name');
|
||||
});
|
||||
|
||||
@@ -145,7 +145,7 @@ describe('filters', function() {
|
||||
expect(number(+Infinity)).toEqual('');
|
||||
expect(number(-Infinity)).toEqual('');
|
||||
expect(number("1234.5678")).toEqual('1,234.568');
|
||||
expect(number(1/0)).toEqual("");
|
||||
expect(number(1 / 0)).toEqual("");
|
||||
expect(number(1, 2)).toEqual("1.00");
|
||||
expect(number(.1, 2)).toEqual("0.10");
|
||||
expect(number(.01, 2)).toEqual("0.01");
|
||||
@@ -393,7 +393,7 @@ describe('filters', function() {
|
||||
it('should support different degrees of subsecond precision', function() {
|
||||
var format = 'yyyy-MM-dd ss';
|
||||
|
||||
var localDay = new Date(Date.UTC(2003, 9-1, 10, 13, 2, 3, 123)).getDate();
|
||||
var localDay = new Date(Date.UTC(2003, 9 - 1, 10, 13, 2, 3, 123)).getDate();
|
||||
|
||||
expect(date('2003-09-10T13:02:03.12345678Z', format)).toEqual('2003-09-' + localDay + ' 03');
|
||||
expect(date('2003-09-10T13:02:03.1234567Z', format)).toEqual('2003-09-' + localDay + ' 03');
|
||||
|
||||
+191
-77
@@ -1,100 +1,214 @@
|
||||
'use strict';
|
||||
|
||||
describe('Filter: orderBy', function() {
|
||||
var orderBy;
|
||||
var orderBy, orderByFilter;
|
||||
beforeEach(inject(function($filter) {
|
||||
orderBy = $filter('orderBy');
|
||||
orderBy = orderByFilter = $filter('orderBy');
|
||||
}));
|
||||
|
||||
it('should return sorted array if predicate is not provided', function() {
|
||||
expect(orderBy([2, 1, 3])).toEqual([1, 2, 3]);
|
||||
|
||||
expect(orderBy([2, 1, 3], '')).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], [])).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], [''])).toEqual([1, 2, 3]);
|
||||
describe('(Arrays)', function() {
|
||||
it('should return sorted array if predicate is not provided', function() {
|
||||
expect(orderBy([2, 1, 3])).toEqual([1, 2, 3]);
|
||||
|
||||
expect(orderBy([2, 1, 3], '+')).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], ['+'])).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], '')).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], [])).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], [''])).toEqual([1, 2, 3]);
|
||||
|
||||
expect(orderBy([2, 1, 3], '-')).toEqual([3, 2, 1]);
|
||||
expect(orderBy([2, 1, 3], ['-'])).toEqual([3, 2, 1]);
|
||||
});
|
||||
expect(orderBy([2, 1, 3], '+')).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], ['+'])).toEqual([1, 2, 3]);
|
||||
|
||||
it('shouldSortArrayInReverse', function() {
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', true)).toEqualData([{a:15}, {a:2}]);
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', "T")).toEqualData([{a:15}, {a:2}]);
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', "reverse")).toEqualData([{a:15}, {a:2}]);
|
||||
});
|
||||
expect(orderBy([2, 1, 3], '-')).toEqual([3, 2, 1]);
|
||||
expect(orderBy([2, 1, 3], ['-'])).toEqual([3, 2, 1]);
|
||||
});
|
||||
|
||||
it('should sort inherited from array', function() {
|
||||
function BaseCollection() {}
|
||||
BaseCollection.prototype = Array.prototype;
|
||||
var child = new BaseCollection();
|
||||
child.push({a:2});
|
||||
child.push({a:15});
|
||||
|
||||
expect(Array.isArray(child)).toBe(false);
|
||||
expect(child instanceof Array).toBe(true);
|
||||
it('shouldSortArrayInReverse', function() {
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', true)).toEqualData([{a:15}, {a:2}]);
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', "T")).toEqualData([{a:15}, {a:2}]);
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', "reverse")).toEqualData([{a:15}, {a:2}]);
|
||||
});
|
||||
|
||||
expect(orderBy(child, 'a', true)).toEqualData([{a:15}, {a:2}]);
|
||||
});
|
||||
|
||||
it('should sort array by predicate', function() {
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['a', 'b'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['b', 'a'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['+b', '-a'])).toEqualData([{a:15, b:1}, {a:2, b:1}]);
|
||||
it('should sort inherited from array', function() {
|
||||
function BaseCollection() {}
|
||||
BaseCollection.prototype = Array.prototype;
|
||||
var child = new BaseCollection();
|
||||
child.push({a:2});
|
||||
child.push({a:15});
|
||||
|
||||
expect(Array.isArray(child)).toBe(false);
|
||||
expect(child instanceof Array).toBe(true);
|
||||
|
||||
expect(orderBy(child, 'a', true)).toEqualData([{a:15}, {a:2}]);
|
||||
});
|
||||
|
||||
|
||||
it('should sort array by predicate', function() {
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['a', 'b'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['b', 'a'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['+b', '-a'])).toEqualData([{a:15, b:1}, {a:2, b:1}]);
|
||||
});
|
||||
|
||||
|
||||
it('should sort array by date predicate', function() {
|
||||
// same dates
|
||||
expect(orderBy([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2014'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:2 }],
|
||||
['a', 'b']))
|
||||
.toEqualData([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:2 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2014'), b:4 }]);
|
||||
|
||||
// one different date
|
||||
expect(orderBy([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2013'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:2 }],
|
||||
['a', 'b']))
|
||||
.toEqualData([
|
||||
{ a:new Date('01/01/2013'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:2 },
|
||||
{ a:new Date('01/01/2014'), b:3 }]);
|
||||
});
|
||||
|
||||
|
||||
it('should use function', function() {
|
||||
expect(
|
||||
orderBy(
|
||||
[{a:15, b:1},{a:2, b:1}],
|
||||
function(value) { return value.a; })).
|
||||
toEqual([{a:2, b:1},{a:15, b:1}]);
|
||||
});
|
||||
|
||||
|
||||
it('should support string predicates with names containing non-identifier characters', function() {
|
||||
/*jshint -W008 */
|
||||
expect(orderBy([{"Tip %": .25}, {"Tip %": .15}, {"Tip %": .40}], '"Tip %"'))
|
||||
.toEqualData([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}]);
|
||||
expect(orderBy([{"원": 76000}, {"원": 31000}, {"원": 156000}], '"원"'))
|
||||
.toEqualData([{"원": 31000}, {"원": 76000}, {"원": 156000}]);
|
||||
});
|
||||
|
||||
|
||||
it('should throw if quoted string predicate is quoted incorrectly', function() {
|
||||
/*jshint -W008 */
|
||||
expect(function() {
|
||||
return orderBy([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}], '"Tip %\'');
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should sort array by date predicate', function() {
|
||||
// same dates
|
||||
expect(orderBy([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2014'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:2 }],
|
||||
['a', 'b']))
|
||||
.toEqualData([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:2 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2014'), b:4 }]);
|
||||
|
||||
// one different date
|
||||
expect(orderBy([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2013'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:2 }],
|
||||
['a', 'b']))
|
||||
.toEqualData([
|
||||
{ a:new Date('01/01/2013'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:2 },
|
||||
{ a:new Date('01/01/2014'), b:3 }]);
|
||||
});
|
||||
describe('(Array-Like Objects)', function() {
|
||||
function arrayLike(args) {
|
||||
var result = {};
|
||||
var i;
|
||||
for (i = 0; i < args.length; ++i) {
|
||||
result[i] = args[i];
|
||||
}
|
||||
result.length = i;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
it('should use function', function() {
|
||||
expect(
|
||||
orderBy(
|
||||
[{a:15, b:1},{a:2, b:1}],
|
||||
function(value) { return value.a; })).
|
||||
toEqual([{a:2, b:1},{a:15, b:1}]);
|
||||
});
|
||||
beforeEach(inject(function($filter) {
|
||||
orderBy = function(collection) {
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
args[0] = arrayLike(args[0]);
|
||||
return orderByFilter.apply(null, args);
|
||||
};
|
||||
}));
|
||||
|
||||
it('should support string predicates with names containing non-identifier characters', function() {
|
||||
/*jshint -W008 */
|
||||
expect(orderBy([{"Tip %": .25}, {"Tip %": .15}, {"Tip %": .40}], '"Tip %"'))
|
||||
.toEqualData([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}]);
|
||||
expect(orderBy([{"원": 76000}, {"원": 31000}, {"원": 156000}], '"원"'))
|
||||
.toEqualData([{"원": 31000}, {"원": 76000}, {"원": 156000}]);
|
||||
});
|
||||
|
||||
it('should throw if quoted string predicate is quoted incorrectly', function() {
|
||||
/*jshint -W008 */
|
||||
expect(function() {
|
||||
return orderBy([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}], '"Tip %\'');
|
||||
}).toThrow();
|
||||
it('should return sorted array if predicate is not provided', function() {
|
||||
expect(orderBy([2, 1, 3])).toEqual([1, 2, 3]);
|
||||
|
||||
expect(orderBy([2, 1, 3], '')).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], [])).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], [''])).toEqual([1, 2, 3]);
|
||||
|
||||
expect(orderBy([2, 1, 3], '+')).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], ['+'])).toEqual([1, 2, 3]);
|
||||
|
||||
expect(orderBy([2, 1, 3], '-')).toEqual([3, 2, 1]);
|
||||
expect(orderBy([2, 1, 3], ['-'])).toEqual([3, 2, 1]);
|
||||
});
|
||||
|
||||
|
||||
it('shouldSortArrayInReverse', function() {
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', true)).toEqualData([{a:15}, {a:2}]);
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', "T")).toEqualData([{a:15}, {a:2}]);
|
||||
expect(orderBy([{a:15}, {a:2}], 'a', "reverse")).toEqualData([{a:15}, {a:2}]);
|
||||
});
|
||||
|
||||
|
||||
it('should sort array by predicate', function() {
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['a', 'b'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['b', 'a'])).toEqualData([{a:2, b:1}, {a:15, b:1}]);
|
||||
expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['+b', '-a'])).toEqualData([{a:15, b:1}, {a:2, b:1}]);
|
||||
});
|
||||
|
||||
|
||||
it('should sort array by date predicate', function() {
|
||||
// same dates
|
||||
expect(orderBy([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2014'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:2 }],
|
||||
['a', 'b']))
|
||||
.toEqualData([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:2 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2014'), b:4 }]);
|
||||
|
||||
// one different date
|
||||
expect(orderBy([
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:3 },
|
||||
{ a:new Date('01/01/2013'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:2 }],
|
||||
['a', 'b']))
|
||||
.toEqualData([
|
||||
{ a:new Date('01/01/2013'), b:4 },
|
||||
{ a:new Date('01/01/2014'), b:1 },
|
||||
{ a:new Date('01/01/2014'), b:2 },
|
||||
{ a:new Date('01/01/2014'), b:3 }]);
|
||||
});
|
||||
|
||||
|
||||
it('should use function', function() {
|
||||
expect(
|
||||
orderBy(
|
||||
[{a:15, b:1},{a:2, b:1}],
|
||||
function(value) { return value.a; })).
|
||||
toEqual([{a:2, b:1},{a:15, b:1}]);
|
||||
});
|
||||
|
||||
|
||||
it('should support string predicates with names containing non-identifier characters', function() {
|
||||
/*jshint -W008 */
|
||||
expect(orderBy([{"Tip %": .25}, {"Tip %": .15}, {"Tip %": .40}], '"Tip %"'))
|
||||
.toEqualData([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}]);
|
||||
expect(orderBy([{"원": 76000}, {"원": 31000}, {"원": 156000}], '"원"'))
|
||||
.toEqualData([{"원": 31000}, {"원": 76000}, {"원": 156000}]);
|
||||
});
|
||||
|
||||
|
||||
it('should throw if quoted string predicate is quoted incorrectly', function() {
|
||||
/*jshint -W008 */
|
||||
expect(function() {
|
||||
return orderBy([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}], '"Tip %\'');
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1133,6 +1133,17 @@ describe('$http', function() {
|
||||
expect(callback.mostRecentCall.args[0]).toEqual('');
|
||||
});
|
||||
|
||||
it('should not attempt to deserialize json for a blank response whose header contains application/json', function() {
|
||||
//per http spec for Content-Type, HEAD request should return a Content-Type header
|
||||
//set to what the content type would have been if a get was sent
|
||||
$httpBackend.expect('GET', '/url').respond(' ', {'Content-Type': 'application/json'});
|
||||
$http({method: 'GET', url: '/url'}).success(callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
expect(callback.mostRecentCall.args[0]).toEqual(' ');
|
||||
});
|
||||
|
||||
it('should not deserialize tpl beginning with ng expression', function() {
|
||||
$httpBackend.expect('GET', '/url').respond('{{some}}');
|
||||
$http.get('/url').success(callback);
|
||||
|
||||
@@ -1690,6 +1690,95 @@ describe('$location', function() {
|
||||
expect($browser.url()).toEqual('http://server/');
|
||||
}));
|
||||
|
||||
it('should allow redirect during $locationChangeStart',
|
||||
inject(function($location, $browser, $rootScope, $log) {
|
||||
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
|
||||
$log.info('before', newUrl, oldUrl, $browser.url());
|
||||
if (newUrl === 'http://server/#/somePath') {
|
||||
$location.url('/redirectPath');
|
||||
}
|
||||
});
|
||||
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
|
||||
$log.info('after', newUrl, oldUrl, $browser.url());
|
||||
});
|
||||
|
||||
$location.url('/somePath');
|
||||
$rootScope.$apply();
|
||||
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['before', 'http://server/#/somePath', 'http://server/', 'http://server/']);
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['before', 'http://server/#/redirectPath', 'http://server/', 'http://server/']);
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['after', 'http://server/#/redirectPath', 'http://server/',
|
||||
'http://server/#/redirectPath']);
|
||||
|
||||
expect($location.url()).toEqual('/redirectPath');
|
||||
expect($browser.url()).toEqual('http://server/#/redirectPath');
|
||||
})
|
||||
);
|
||||
|
||||
it('should allow redirect during $locationChangeStart even if default prevented',
|
||||
inject(function($location, $browser, $rootScope, $log) {
|
||||
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
|
||||
$log.info('before', newUrl, oldUrl, $browser.url());
|
||||
if (newUrl === 'http://server/#/somePath') {
|
||||
event.preventDefault();
|
||||
$location.url('/redirectPath');
|
||||
}
|
||||
});
|
||||
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
|
||||
$log.info('after', newUrl, oldUrl, $browser.url());
|
||||
});
|
||||
|
||||
$location.url('/somePath');
|
||||
$rootScope.$apply();
|
||||
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['before', 'http://server/#/somePath', 'http://server/', 'http://server/']);
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['before', 'http://server/#/redirectPath', 'http://server/', 'http://server/']);
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['after', 'http://server/#/redirectPath', 'http://server/',
|
||||
'http://server/#/redirectPath']);
|
||||
|
||||
expect($location.url()).toEqual('/redirectPath');
|
||||
expect($browser.url()).toEqual('http://server/#/redirectPath');
|
||||
})
|
||||
);
|
||||
|
||||
it('should allow multiple redirect during $locationChangeStart',
|
||||
inject(function($location, $browser, $rootScope, $log) {
|
||||
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
|
||||
$log.info('before', newUrl, oldUrl, $browser.url());
|
||||
if (newUrl === 'http://server/#/somePath') {
|
||||
$location.url('/redirectPath');
|
||||
} else if (newUrl === 'http://server/#/redirectPath') {
|
||||
$location.url('/redirectPath2');
|
||||
}
|
||||
});
|
||||
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
|
||||
$log.info('after', newUrl, oldUrl, $browser.url());
|
||||
});
|
||||
|
||||
$location.url('/somePath');
|
||||
$rootScope.$apply();
|
||||
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['before', 'http://server/#/somePath', 'http://server/', 'http://server/']);
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['before', 'http://server/#/redirectPath', 'http://server/', 'http://server/']);
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['before', 'http://server/#/redirectPath2', 'http://server/', 'http://server/']);
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['after', 'http://server/#/redirectPath2', 'http://server/',
|
||||
'http://server/#/redirectPath2']);
|
||||
|
||||
expect($location.url()).toEqual('/redirectPath2');
|
||||
expect($browser.url()).toEqual('http://server/#/redirectPath2');
|
||||
})
|
||||
);
|
||||
|
||||
it ('should fire $locationChangeSuccess event when change from browser location bar',
|
||||
inject(function($log, $location, $browser, $rootScope) {
|
||||
$rootScope.$apply(); // clear initial $locationChangeStart
|
||||
@@ -1715,6 +1804,66 @@ describe('$location', function() {
|
||||
})
|
||||
);
|
||||
|
||||
it('should allow redirect during browser url change',
|
||||
inject(function($location, $browser, $rootScope, $log) {
|
||||
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
|
||||
$log.info('before', newUrl, oldUrl, $browser.url());
|
||||
if (newUrl === 'http://server/#/somePath') {
|
||||
$location.url('/redirectPath');
|
||||
}
|
||||
});
|
||||
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
|
||||
$log.info('after', newUrl, oldUrl, $browser.url());
|
||||
});
|
||||
|
||||
$browser.url('http://server/#/somePath');
|
||||
$browser.poll();
|
||||
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['before', 'http://server/#/somePath', 'http://server/',
|
||||
'http://server/#/somePath']);
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['before', 'http://server/#/redirectPath', 'http://server/#/somePath',
|
||||
'http://server/#/somePath']);
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['after', 'http://server/#/redirectPath', 'http://server/#/somePath',
|
||||
'http://server/#/redirectPath']);
|
||||
|
||||
expect($location.url()).toEqual('/redirectPath');
|
||||
expect($browser.url()).toEqual('http://server/#/redirectPath');
|
||||
})
|
||||
);
|
||||
|
||||
it('should allow redirect during browser url change even if default prevented',
|
||||
inject(function($location, $browser, $rootScope, $log) {
|
||||
$rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
|
||||
$log.info('before', newUrl, oldUrl, $browser.url());
|
||||
if (newUrl === 'http://server/#/somePath') {
|
||||
event.preventDefault();
|
||||
$location.url('/redirectPath');
|
||||
}
|
||||
});
|
||||
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
|
||||
$log.info('after', newUrl, oldUrl, $browser.url());
|
||||
});
|
||||
|
||||
$browser.url('http://server/#/somePath');
|
||||
$browser.poll();
|
||||
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['before', 'http://server/#/somePath', 'http://server/',
|
||||
'http://server/#/somePath']);
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['before', 'http://server/#/redirectPath', 'http://server/#/somePath',
|
||||
'http://server/#/somePath']);
|
||||
expect($log.info.logs.shift()).
|
||||
toEqual(['after', 'http://server/#/redirectPath', 'http://server/#/somePath',
|
||||
'http://server/#/redirectPath']);
|
||||
|
||||
expect($location.url()).toEqual('/redirectPath');
|
||||
expect($browser.url()).toEqual('http://server/#/redirectPath');
|
||||
})
|
||||
);
|
||||
|
||||
it('should listen on click events on href and prevent browser default in hashbang mode', function() {
|
||||
module(function() {
|
||||
|
||||
+174
-88
@@ -3,9 +3,11 @@
|
||||
describe('parser', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
/* global getterFnCache: true */
|
||||
// clear cache
|
||||
getterFnCache = createMap();
|
||||
/* global getterFnCacheDefault: true */
|
||||
/* global getterFnCacheExpensive: true */
|
||||
// clear caches
|
||||
getterFnCacheDefault = createMap();
|
||||
getterFnCacheExpensive = createMap();
|
||||
});
|
||||
|
||||
|
||||
@@ -20,11 +22,30 @@ describe('parser', function() {
|
||||
};
|
||||
});
|
||||
|
||||
it('should only match number chars with isNumber', function() {
|
||||
expect(Lexer.prototype.isNumber('0')).toBe(true);
|
||||
expect(Lexer.prototype.isNumber('')).toBeFalsy();
|
||||
expect(Lexer.prototype.isNumber(' ')).toBeFalsy();
|
||||
expect(Lexer.prototype.isNumber(0)).toBeFalsy();
|
||||
expect(Lexer.prototype.isNumber(false)).toBeFalsy();
|
||||
expect(Lexer.prototype.isNumber(true)).toBeFalsy();
|
||||
expect(Lexer.prototype.isNumber(undefined)).toBeFalsy();
|
||||
expect(Lexer.prototype.isNumber(null)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should tokenize a string', function() {
|
||||
var tokens = lex("a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\"");
|
||||
var i = 0;
|
||||
expect(tokens[i].index).toEqual(0);
|
||||
expect(tokens[i].text).toEqual('a.bc');
|
||||
expect(tokens[i].text).toEqual('a');
|
||||
|
||||
i++;
|
||||
expect(tokens[i].index).toEqual(1);
|
||||
expect(tokens[i].text).toEqual('.');
|
||||
|
||||
i++;
|
||||
expect(tokens[i].index).toEqual(2);
|
||||
expect(tokens[i].text).toEqual('bc');
|
||||
|
||||
i++;
|
||||
expect(tokens[i].index).toEqual(4);
|
||||
@@ -32,7 +53,9 @@ describe('parser', function() {
|
||||
|
||||
i++;
|
||||
expect(tokens[i].index).toEqual(5);
|
||||
expect(tokens[i].text).toEqual(22);
|
||||
expect(tokens[i].text).toEqual('22');
|
||||
expect(tokens[i].value).toEqual(22);
|
||||
expect(tokens[i].constant).toEqual(true);
|
||||
|
||||
i++;
|
||||
expect(tokens[i].index).toEqual(7);
|
||||
@@ -44,7 +67,9 @@ describe('parser', function() {
|
||||
|
||||
i++;
|
||||
expect(tokens[i].index).toEqual(9);
|
||||
expect(tokens[i].text).toEqual(1.3);
|
||||
expect(tokens[i].text).toEqual('1.3');
|
||||
expect(tokens[i].value).toEqual(1.3);
|
||||
expect(tokens[i].constant).toEqual(true);
|
||||
|
||||
i++;
|
||||
expect(tokens[i].index).toEqual(12);
|
||||
@@ -60,7 +85,7 @@ describe('parser', function() {
|
||||
|
||||
i++;
|
||||
expect(tokens[i].index).toEqual(15);
|
||||
expect(tokens[i].string).toEqual("a'c");
|
||||
expect(tokens[i].value).toEqual("a'c");
|
||||
|
||||
i++;
|
||||
expect(tokens[i].index).toEqual(21);
|
||||
@@ -68,14 +93,15 @@ describe('parser', function() {
|
||||
|
||||
i++;
|
||||
expect(tokens[i].index).toEqual(22);
|
||||
expect(tokens[i].string).toEqual('d"e');
|
||||
expect(tokens[i].value).toEqual('d"e');
|
||||
});
|
||||
|
||||
it('should tokenize identifiers with spaces after dots', function() {
|
||||
var tokens = lex('foo. bar');
|
||||
expect(tokens[0].text).toEqual('foo');
|
||||
expect(tokens[1].text).toEqual('.');
|
||||
expect(tokens[2].text).toEqual('bar');
|
||||
it('should tokenize identifiers with spaces around dots the same as without spaces', function() {
|
||||
function getText(t) { return t.text; }
|
||||
var spaces = lex('foo. bar . baz').map(getText);
|
||||
var noSpaces = lex('foo.bar.baz').map(getText);
|
||||
|
||||
expect(spaces).toEqual(noSpaces);
|
||||
});
|
||||
|
||||
it('should tokenize undefined', function() {
|
||||
@@ -83,7 +109,6 @@ describe('parser', function() {
|
||||
var i = 0;
|
||||
expect(tokens[i].index).toEqual(0);
|
||||
expect(tokens[i].text).toEqual('undefined');
|
||||
expect(undefined).toEqual(tokens[i].fn());
|
||||
});
|
||||
|
||||
it('should tokenize quoted string', function() {
|
||||
@@ -91,23 +116,23 @@ describe('parser', function() {
|
||||
var tokens = lex(str);
|
||||
|
||||
expect(tokens[1].index).toEqual(1);
|
||||
expect(tokens[1].string).toEqual("'");
|
||||
expect(tokens[1].value).toEqual("'");
|
||||
|
||||
expect(tokens[3].index).toEqual(7);
|
||||
expect(tokens[3].string).toEqual('"');
|
||||
expect(tokens[3].value).toEqual('"');
|
||||
});
|
||||
|
||||
it('should tokenize escaped quoted string', function() {
|
||||
var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
|
||||
var tokens = lex(str);
|
||||
|
||||
expect(tokens[0].string).toEqual('"\n\f\r\t\v\u00A0');
|
||||
expect(tokens[0].value).toEqual('"\n\f\r\t\v\u00A0');
|
||||
});
|
||||
|
||||
it('should tokenize unicode', function() {
|
||||
var tokens = lex('"\\u00A0"');
|
||||
expect(tokens.length).toEqual(1);
|
||||
expect(tokens[0].string).toEqual('\u00a0');
|
||||
expect(tokens[0].value).toEqual('\u00a0');
|
||||
});
|
||||
|
||||
it('should ignore whitespace', function() {
|
||||
@@ -153,12 +178,12 @@ describe('parser', function() {
|
||||
it('should tokenize method invocation', function() {
|
||||
var tokens = lex("a.b.c (d) - e.f()");
|
||||
expect(tokens.map(function(t) { return t.text;})).
|
||||
toEqual(['a.b', '.', 'c', '(', 'd', ')', '-', 'e', '.', 'f', '(', ')']);
|
||||
toEqual(['a', '.', 'b', '.', 'c', '(', 'd', ')', '-', 'e', '.', 'f', '(', ')']);
|
||||
});
|
||||
|
||||
it('should tokenize number', function() {
|
||||
var tokens = lex("0.5");
|
||||
expect(tokens[0].text).toEqual(0.5);
|
||||
expect(tokens[0].value).toEqual(0.5);
|
||||
});
|
||||
|
||||
it('should tokenize negative number', inject(function($rootScope) {
|
||||
@@ -171,11 +196,11 @@ describe('parser', function() {
|
||||
|
||||
it('should tokenize number with exponent', inject(function($rootScope) {
|
||||
var tokens = lex("0.5E-10");
|
||||
expect(tokens[0].text).toEqual(0.5E-10);
|
||||
expect(tokens[0].value).toEqual(0.5E-10);
|
||||
expect($rootScope.$eval("0.5E-10")).toEqual(0.5E-10);
|
||||
|
||||
tokens = lex("0.5E+10");
|
||||
expect(tokens[0].text).toEqual(0.5E+10);
|
||||
expect(tokens[0].value).toEqual(0.5E+10);
|
||||
}));
|
||||
|
||||
it('should throws exception for invalid exponent', function() {
|
||||
@@ -190,7 +215,7 @@ describe('parser', function() {
|
||||
|
||||
it('should tokenize number starting with a dot', function() {
|
||||
var tokens = lex(".5");
|
||||
expect(tokens[0].text).toEqual(0.5);
|
||||
expect(tokens[0].value).toEqual(0.5);
|
||||
});
|
||||
|
||||
it('should throw error on invalid unicode', function() {
|
||||
@@ -226,10 +251,10 @@ describe('parser', function() {
|
||||
expect(scope.$eval("-1")).toEqual(-1);
|
||||
expect(scope.$eval("1 + 2.5")).toEqual(3.5);
|
||||
expect(scope.$eval("1 + -2.5")).toEqual(-1.5);
|
||||
expect(scope.$eval("1+2*3/4")).toEqual(1+2*3/4);
|
||||
expect(scope.$eval("0--1+1.5")).toEqual(0- -1 + 1.5);
|
||||
expect(scope.$eval("-0--1++2*-3/-4")).toEqual(-0- -1+ +2*-3/-4);
|
||||
expect(scope.$eval("1/2*3")).toEqual(1/2*3);
|
||||
expect(scope.$eval("1+2*3/4")).toEqual(1 + 2 * 3 / 4);
|
||||
expect(scope.$eval("0--1+1.5")).toEqual(0 - -1 + 1.5);
|
||||
expect(scope.$eval("-0--1++2*-3/-4")).toEqual(-0 - -1 + +2 * -3 / -4);
|
||||
expect(scope.$eval("1/2*3")).toEqual(1 / 2 * 3);
|
||||
});
|
||||
|
||||
it('should parse comparison', function() {
|
||||
@@ -247,16 +272,16 @@ describe('parser', function() {
|
||||
expect(scope.$eval("1!=2")).toBeTruthy();
|
||||
expect(scope.$eval("1<2")).toBeTruthy();
|
||||
expect(scope.$eval("1<=1")).toBeTruthy();
|
||||
expect(scope.$eval("1>2")).toEqual(1>2);
|
||||
expect(scope.$eval("2>=1")).toEqual(2>=1);
|
||||
expect(scope.$eval("true==2<3")).toEqual(true == 2<3);
|
||||
expect(scope.$eval("true===2<3")).toEqual(true === 2<3);
|
||||
expect(scope.$eval("1>2")).toEqual(1 > 2);
|
||||
expect(scope.$eval("2>=1")).toEqual(2 >= 1);
|
||||
expect(scope.$eval("true==2<3")).toEqual(true == 2 < 3);
|
||||
expect(scope.$eval("true===2<3")).toEqual(true === 2 < 3);
|
||||
});
|
||||
|
||||
it('should parse logical', function() {
|
||||
expect(scope.$eval("0&&2")).toEqual(0&&2);
|
||||
expect(scope.$eval("0||2")).toEqual(0||2);
|
||||
expect(scope.$eval("0||1&&2")).toEqual(0||1&&2);
|
||||
expect(scope.$eval("0&&2")).toEqual(0 && 2);
|
||||
expect(scope.$eval("0||2")).toEqual(0 || 2);
|
||||
expect(scope.$eval("0||1&&2")).toEqual(0 || 1 && 2);
|
||||
});
|
||||
|
||||
it('should parse ternary', function() {
|
||||
@@ -267,54 +292,54 @@ describe('parser', function() {
|
||||
var identity = scope.identity = function(x) { return x; };
|
||||
|
||||
// Simple.
|
||||
expect(scope.$eval('0?0:2')).toEqual(0?0:2);
|
||||
expect(scope.$eval('1?0:2')).toEqual(1?0:2);
|
||||
expect(scope.$eval('0?0:2')).toEqual(0 ? 0 : 2);
|
||||
expect(scope.$eval('1?0:2')).toEqual(1 ? 0 : 2);
|
||||
|
||||
// Nested on the left.
|
||||
expect(scope.$eval('0?0?0:0:2')).toEqual(0?0?0:0:2);
|
||||
expect(scope.$eval('1?0?0:0:2')).toEqual(1?0?0:0:2);
|
||||
expect(scope.$eval('0?1?0:0:2')).toEqual(0?1?0:0:2);
|
||||
expect(scope.$eval('0?0?1:0:2')).toEqual(0?0?1:0:2);
|
||||
expect(scope.$eval('0?0?0:2:3')).toEqual(0?0?0:2:3);
|
||||
expect(scope.$eval('1?1?0:0:2')).toEqual(1?1?0:0:2);
|
||||
expect(scope.$eval('1?1?1:0:2')).toEqual(1?1?1:0:2);
|
||||
expect(scope.$eval('1?1?1:2:3')).toEqual(1?1?1:2:3);
|
||||
expect(scope.$eval('1?1?1:2:3')).toEqual(1?1?1:2:3);
|
||||
expect(scope.$eval('0?0?0:0:2')).toEqual(0 ? 0 ? 0 : 0 : 2);
|
||||
expect(scope.$eval('1?0?0:0:2')).toEqual(1 ? 0 ? 0 : 0 : 2);
|
||||
expect(scope.$eval('0?1?0:0:2')).toEqual(0 ? 1 ? 0 : 0 : 2);
|
||||
expect(scope.$eval('0?0?1:0:2')).toEqual(0 ? 0 ? 1 : 0 : 2);
|
||||
expect(scope.$eval('0?0?0:2:3')).toEqual(0 ? 0 ? 0 : 2 : 3);
|
||||
expect(scope.$eval('1?1?0:0:2')).toEqual(1 ? 1 ? 0 : 0 : 2);
|
||||
expect(scope.$eval('1?1?1:0:2')).toEqual(1 ? 1 ? 1 : 0 : 2);
|
||||
expect(scope.$eval('1?1?1:2:3')).toEqual(1 ? 1 ? 1 : 2 : 3);
|
||||
expect(scope.$eval('1?1?1:2:3')).toEqual(1 ? 1 ? 1 : 2 : 3);
|
||||
|
||||
// Nested on the right.
|
||||
expect(scope.$eval('0?0:0?0:2')).toEqual(0?0:0?0:2);
|
||||
expect(scope.$eval('1?0:0?0:2')).toEqual(1?0:0?0:2);
|
||||
expect(scope.$eval('0?1:0?0:2')).toEqual(0?1:0?0:2);
|
||||
expect(scope.$eval('0?0:1?0:2')).toEqual(0?0:1?0:2);
|
||||
expect(scope.$eval('0?0:0?2:3')).toEqual(0?0:0?2:3);
|
||||
expect(scope.$eval('1?1:0?0:2')).toEqual(1?1:0?0:2);
|
||||
expect(scope.$eval('1?1:1?0:2')).toEqual(1?1:1?0:2);
|
||||
expect(scope.$eval('1?1:1?2:3')).toEqual(1?1:1?2:3);
|
||||
expect(scope.$eval('1?1:1?2:3')).toEqual(1?1:1?2:3);
|
||||
expect(scope.$eval('0?0:0?0:2')).toEqual(0 ? 0 : 0 ? 0 : 2);
|
||||
expect(scope.$eval('1?0:0?0:2')).toEqual(1 ? 0 : 0 ? 0 : 2);
|
||||
expect(scope.$eval('0?1:0?0:2')).toEqual(0 ? 1 : 0 ? 0 : 2);
|
||||
expect(scope.$eval('0?0:1?0:2')).toEqual(0 ? 0 : 1 ? 0 : 2);
|
||||
expect(scope.$eval('0?0:0?2:3')).toEqual(0 ? 0 : 0 ? 2 : 3);
|
||||
expect(scope.$eval('1?1:0?0:2')).toEqual(1 ? 1 : 0 ? 0 : 2);
|
||||
expect(scope.$eval('1?1:1?0:2')).toEqual(1 ? 1 : 1 ? 0 : 2);
|
||||
expect(scope.$eval('1?1:1?2:3')).toEqual(1 ? 1 : 1 ? 2 : 3);
|
||||
expect(scope.$eval('1?1:1?2:3')).toEqual(1 ? 1 : 1 ? 2 : 3);
|
||||
|
||||
// Precedence with respect to logical operators.
|
||||
expect(scope.$eval('0&&1?0:1')).toEqual(0&&1?0:1);
|
||||
expect(scope.$eval('1||0?0:0')).toEqual(1||0?0:0);
|
||||
expect(scope.$eval('0&&1?0:1')).toEqual(0 && 1 ? 0 : 1);
|
||||
expect(scope.$eval('1||0?0:0')).toEqual(1 || 0 ? 0 : 0);
|
||||
|
||||
expect(scope.$eval('0?0&&1:2')).toEqual(0?0&&1:2);
|
||||
expect(scope.$eval('0?1&&1:2')).toEqual(0?1&&1:2);
|
||||
expect(scope.$eval('0?0||0:1')).toEqual(0?0||0:1);
|
||||
expect(scope.$eval('0?0||1:2')).toEqual(0?0||1:2);
|
||||
expect(scope.$eval('0?0&&1:2')).toEqual(0 ? 0 && 1 : 2);
|
||||
expect(scope.$eval('0?1&&1:2')).toEqual(0 ? 1 && 1 : 2);
|
||||
expect(scope.$eval('0?0||0:1')).toEqual(0 ? 0 || 0 : 1);
|
||||
expect(scope.$eval('0?0||1:2')).toEqual(0 ? 0 || 1 : 2);
|
||||
|
||||
expect(scope.$eval('1?0&&1:2')).toEqual(1?0&&1:2);
|
||||
expect(scope.$eval('1?1&&1:2')).toEqual(1?1&&1:2);
|
||||
expect(scope.$eval('1?0||0:1')).toEqual(1?0||0:1);
|
||||
expect(scope.$eval('1?0||1:2')).toEqual(1?0||1:2);
|
||||
expect(scope.$eval('1?0&&1:2')).toEqual(1 ? 0 && 1 : 2);
|
||||
expect(scope.$eval('1?1&&1:2')).toEqual(1 ? 1 && 1 : 2);
|
||||
expect(scope.$eval('1?0||0:1')).toEqual(1 ? 0 || 0 : 1);
|
||||
expect(scope.$eval('1?0||1:2')).toEqual(1 ? 0 || 1 : 2);
|
||||
|
||||
expect(scope.$eval('0?1:0&&1')).toEqual(0?1:0&&1);
|
||||
expect(scope.$eval('0?2:1&&1')).toEqual(0?2:1&&1);
|
||||
expect(scope.$eval('0?1:0||0')).toEqual(0?1:0||0);
|
||||
expect(scope.$eval('0?2:0||1')).toEqual(0?2:0||1);
|
||||
expect(scope.$eval('0?1:0&&1')).toEqual(0 ? 1 : 0 && 1);
|
||||
expect(scope.$eval('0?2:1&&1')).toEqual(0 ? 2 : 1 && 1);
|
||||
expect(scope.$eval('0?1:0||0')).toEqual(0 ? 1 : 0 || 0);
|
||||
expect(scope.$eval('0?2:0||1')).toEqual(0 ? 2 : 0 || 1);
|
||||
|
||||
expect(scope.$eval('1?1:0&&1')).toEqual(1?1:0&&1);
|
||||
expect(scope.$eval('1?2:1&&1')).toEqual(1?2:1&&1);
|
||||
expect(scope.$eval('1?1:0||0')).toEqual(1?1:0||0);
|
||||
expect(scope.$eval('1?2:0||1')).toEqual(1?2:0||1);
|
||||
expect(scope.$eval('1?1:0&&1')).toEqual(1 ? 1 : 0 && 1);
|
||||
expect(scope.$eval('1?2:1&&1')).toEqual(1 ? 2 : 1 && 1);
|
||||
expect(scope.$eval('1?1:0||0')).toEqual(1 ? 1 : 0 || 0);
|
||||
expect(scope.$eval('1?2:0||1')).toEqual(1 ? 2 : 0 || 1);
|
||||
|
||||
// Function calls.
|
||||
expect(scope.$eval('returnTrue() ? returnString() : returnInt()')).toEqual(returnTrue() ? returnString() : returnInt());
|
||||
@@ -357,18 +382,26 @@ describe('parser', function() {
|
||||
expect(scope.$eval("a . \nb", scope)).toEqual(4);
|
||||
});
|
||||
|
||||
it('should handle white-spaces around dots in method invocations', function() {
|
||||
scope.a = {b: function() { return this.c; }, c: 4};
|
||||
expect(scope.$eval("a . b ()", scope)).toEqual(4);
|
||||
expect(scope.$eval("a. b ()", scope)).toEqual(4);
|
||||
expect(scope.$eval("a .b ()", scope)).toEqual(4);
|
||||
expect(scope.$eval("a \n . \nb \n ()", scope)).toEqual(4);
|
||||
});
|
||||
|
||||
it('should throw syntax error exception for identifiers ending with a dot', function() {
|
||||
scope.a = {b: 4};
|
||||
|
||||
expect(function() {
|
||||
scope.$eval("a.", scope);
|
||||
}).toThrowMinErr('$parse', 'syntax',
|
||||
"Token 'null' is an unexpected token at column 2 of the expression [a.] starting at [.].");
|
||||
}).toThrowMinErr('$parse', 'ueoe',
|
||||
"Unexpected end of expression: a.");
|
||||
|
||||
expect(function() {
|
||||
scope.$eval("a .", scope);
|
||||
}).toThrowMinErr('$parse', 'syntax',
|
||||
"Token 'null' is an unexpected token at column 3 of the expression [a .] starting at [.].");
|
||||
}).toThrowMinErr('$parse', 'ueoe',
|
||||
"Unexpected end of expression: a .");
|
||||
});
|
||||
|
||||
it('should resolve deeply nested paths (important for CSP mode)', function() {
|
||||
@@ -436,7 +469,7 @@ describe('parser', function() {
|
||||
});
|
||||
|
||||
it('should evaluate grouped expressions', function() {
|
||||
expect(scope.$eval("(1+2)*3")).toEqual((1+2)*3);
|
||||
expect(scope.$eval("(1+2)*3")).toEqual((1 + 2) * 3);
|
||||
});
|
||||
|
||||
it('should evaluate assignments', function() {
|
||||
@@ -469,7 +502,7 @@ describe('parser', function() {
|
||||
|
||||
it('should evaluate function call with arguments', function() {
|
||||
scope.add = function(a, b) {
|
||||
return a+b;
|
||||
return a + b;
|
||||
};
|
||||
expect(scope.$eval("add(1,2)")).toEqual(3);
|
||||
});
|
||||
@@ -504,13 +537,32 @@ describe('parser', function() {
|
||||
});
|
||||
|
||||
it('should evaluate object', function() {
|
||||
expect(toJson(scope.$eval("{}"))).toEqual("{}");
|
||||
expect(toJson(scope.$eval("{a:'b'}"))).toEqual('{"a":"b"}');
|
||||
expect(toJson(scope.$eval("{'a':'b'}"))).toEqual('{"a":"b"}');
|
||||
expect(toJson(scope.$eval("{\"a\":'b'}"))).toEqual('{"a":"b"}');
|
||||
expect(toJson(scope.$eval("{a:'b',}"))).toEqual('{"a":"b"}');
|
||||
expect(toJson(scope.$eval("{'a':'b',}"))).toEqual('{"a":"b"}');
|
||||
expect(toJson(scope.$eval("{\"a\":'b',}"))).toEqual('{"a":"b"}');
|
||||
expect(scope.$eval("{}")).toEqual({});
|
||||
expect(scope.$eval("{a:'b'}")).toEqual({a:"b"});
|
||||
expect(scope.$eval("{'a':'b'}")).toEqual({a:"b"});
|
||||
expect(scope.$eval("{\"a\":'b'}")).toEqual({a:"b"});
|
||||
expect(scope.$eval("{a:'b',}")).toEqual({a:"b"});
|
||||
expect(scope.$eval("{'a':'b',}")).toEqual({a:"b"});
|
||||
expect(scope.$eval("{\"a\":'b',}")).toEqual({a:"b"});
|
||||
expect(scope.$eval("{'0':1}")).toEqual({0:1});
|
||||
expect(scope.$eval("{0:1}")).toEqual({0:1});
|
||||
expect(scope.$eval("{1:1}")).toEqual({1:1});
|
||||
expect(scope.$eval("{null:1}")).toEqual({null:1});
|
||||
expect(scope.$eval("{'null':1}")).toEqual({null:1});
|
||||
expect(scope.$eval("{false:1}")).toEqual({false:1});
|
||||
expect(scope.$eval("{'false':1}")).toEqual({false:1});
|
||||
expect(scope.$eval("{'':1,}")).toEqual({"":1});
|
||||
});
|
||||
|
||||
it('should throw syntax error exception for non constant/identifier JSON keys', function() {
|
||||
expect(function() { scope.$eval("{[:0}"); }).toThrowMinErr("$parse", "syntax",
|
||||
"Syntax Error: Token '[' invalid key at column 2 of the expression [{[:0}] starting at [[:0}]");
|
||||
expect(function() { scope.$eval("{{:0}"); }).toThrowMinErr("$parse", "syntax",
|
||||
"Syntax Error: Token '{' invalid key at column 2 of the expression [{{:0}] starting at [{:0}]");
|
||||
expect(function() { scope.$eval("{?:0}"); }).toThrowMinErr("$parse", "syntax",
|
||||
"Syntax Error: Token '?' invalid key at column 2 of the expression [{?:0}] starting at [?:0}]");
|
||||
expect(function() { scope.$eval("{):0}"); }).toThrowMinErr("$parse", "syntax",
|
||||
"Syntax Error: Token ')' invalid key at column 2 of the expression [{):0}] starting at [):0}]");
|
||||
});
|
||||
|
||||
it('should evaluate object access', function() {
|
||||
@@ -518,8 +570,8 @@ describe('parser', function() {
|
||||
});
|
||||
|
||||
it('should evaluate JSON', function() {
|
||||
expect(toJson(scope.$eval("[{}]"))).toEqual("[{}]");
|
||||
expect(toJson(scope.$eval("[{a:[]}, {b:1}]"))).toEqual('[{"a":[]},{"b":1}]');
|
||||
expect(scope.$eval("[{}]")).toEqual([{}]);
|
||||
expect(scope.$eval("[{a:[]}, {b:1}]")).toEqual([{a:[]}, {b:1}]);
|
||||
});
|
||||
|
||||
it('should evaluate multiple statements', function() {
|
||||
@@ -615,7 +667,7 @@ describe('parser', function() {
|
||||
/* jshint -W018 */
|
||||
expect(scope.$eval("!false || true")).toEqual(!false || true);
|
||||
expect(scope.$eval("!11 == 10")).toEqual(!11 == 10);
|
||||
expect(scope.$eval("12/6/2")).toEqual(12/6/2);
|
||||
expect(scope.$eval("12/6/2")).toEqual(12 / 6 / 2);
|
||||
});
|
||||
|
||||
it('should evaluate exclamation mark', function() {
|
||||
@@ -672,6 +724,24 @@ describe('parser', function() {
|
||||
|
||||
describe('sandboxing', function() {
|
||||
describe('Function constructor', function() {
|
||||
it('should not tranverse the Function constructor in the getter', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor');
|
||||
|
||||
});
|
||||
|
||||
it('should not allow access to the Function prototype in the getter', function() {
|
||||
expect(function() {
|
||||
scope.$eval('toString.constructor.prototype');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: toString.constructor.prototype');
|
||||
|
||||
});
|
||||
|
||||
it('should NOT allow access to Function constructor in getter', function() {
|
||||
|
||||
expect(function() {
|
||||
@@ -765,6 +835,22 @@ describe('parser', function() {
|
||||
'Expression: foo["bar"]');
|
||||
|
||||
});
|
||||
|
||||
describe('expensiveChecks', function() {
|
||||
it('should block access to window object even when aliased', inject(function($parse, $window) {
|
||||
scope.foo = {w: $window};
|
||||
// This isn't blocked for performance.
|
||||
expect(scope.$eval($parse('foo.w'))).toBe($window);
|
||||
// Event handlers use the more expensive path for better protection since they expose
|
||||
// the $event object on the scope.
|
||||
expect(function() {
|
||||
scope.$eval($parse('foo.w', null, true));
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.w');
|
||||
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Function prototype functions', function() {
|
||||
|
||||
@@ -296,13 +296,13 @@ describe('Scope', function() {
|
||||
|
||||
expect(function() {
|
||||
$rootScope.$digest();
|
||||
}).toThrowMinErr('$rootScope', 'infdig', '100 $digest() iterations reached. Aborting!\n'+
|
||||
}).toThrowMinErr('$rootScope', 'infdig', '100 $digest() iterations reached. Aborting!\n' +
|
||||
'Watchers fired in the last 5 iterations: ' +
|
||||
'[["a; newVal: 96; oldVal: 95","b; newVal: 97; oldVal: 96"],' +
|
||||
'["a; newVal: 97; oldVal: 96","b; newVal: 98; oldVal: 97"],' +
|
||||
'["a; newVal: 98; oldVal: 97","b; newVal: 99; oldVal: 98"],' +
|
||||
'["a; newVal: 99; oldVal: 98","b; newVal: 100; oldVal: 99"],' +
|
||||
'["a; newVal: 100; oldVal: 99","b; newVal: 101; oldVal: 100"]]');
|
||||
'[[{"msg":"a","newVal":96,"oldVal":95},{"msg":"b","newVal":97,"oldVal":96}],' +
|
||||
'[{"msg":"a","newVal":97,"oldVal":96},{"msg":"b","newVal":98,"oldVal":97}],' +
|
||||
'[{"msg":"a","newVal":98,"oldVal":97},{"msg":"b","newVal":99,"oldVal":98}],' +
|
||||
'[{"msg":"a","newVal":99,"oldVal":98},{"msg":"b","newVal":100,"oldVal":99}],' +
|
||||
'[{"msg":"a","newVal":100,"oldVal":99},{"msg":"b","newVal":101,"oldVal":100}]]');
|
||||
|
||||
expect($rootScope.$$phase).toBeNull();
|
||||
});
|
||||
@@ -341,7 +341,7 @@ describe('Scope', function() {
|
||||
|
||||
expect(function() {
|
||||
$rootScope.$digest();
|
||||
}).toThrowMinErr('$rootScope', 'infdig', '10 $digest() iterations reached. Aborting!\n'+
|
||||
}).toThrowMinErr('$rootScope', 'infdig', '10 $digest() iterations reached. Aborting!\n' +
|
||||
'Watchers fired in the last 5 iterations: []');
|
||||
|
||||
expect($rootScope.$$phase).toBeNull();
|
||||
|
||||
+1
-1
@@ -221,7 +221,7 @@ describe('SCE', function() {
|
||||
module(provideLog);
|
||||
inject(function($sce, $rootScope, log) {
|
||||
$rootScope.$watch($sce.parseAsHtml('::foo'), function(value) {
|
||||
log(value+'');
|
||||
log(value + '');
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
@@ -27,10 +27,10 @@ describe('urlUtils', function() {
|
||||
it('should support various combinations of urls - both string and parsed', inject(function($document) {
|
||||
function expectIsSameOrigin(url, expectedValue) {
|
||||
expect(urlIsSameOrigin(url)).toBe(expectedValue);
|
||||
expect(urlIsSameOrigin(urlResolve(url, true))).toBe(expectedValue);
|
||||
expect(urlIsSameOrigin(urlResolve(url))).toBe(expectedValue);
|
||||
}
|
||||
expectIsSameOrigin('path', true);
|
||||
var origin = urlResolve($document[0].location.href, true);
|
||||
var origin = urlResolve($document[0].location.href);
|
||||
expectIsSameOrigin('//' + origin.host + '/path', true);
|
||||
// Different domain.
|
||||
expectIsSameOrigin('http://example.com/path', false);
|
||||
|
||||
@@ -1066,9 +1066,9 @@ describe("ngAnimate", function() {
|
||||
var animationDone;
|
||||
var animationStyles;
|
||||
var proxyAnimation = function() {
|
||||
var limit = arguments.length-1;
|
||||
var limit = arguments.length - 1;
|
||||
animationStyles = arguments[limit];
|
||||
animationDone = arguments[limit-1];
|
||||
animationDone = arguments[limit - 1];
|
||||
};
|
||||
module(function($animateProvider) {
|
||||
$animateProvider.register('.capture', function() {
|
||||
@@ -1439,7 +1439,7 @@ describe("ngAnimate", function() {
|
||||
$timeout.verifyNoPendingTasks();
|
||||
|
||||
expect(elements[0].attr('style')).toBeFalsy();
|
||||
for (i=1;i<5;i++) {
|
||||
for (i = 1; i < 5; i++) {
|
||||
expect(elements[i].attr('style')).not.toMatch(/animation-play-state:\s*paused/);
|
||||
}
|
||||
}));
|
||||
@@ -2815,7 +2815,7 @@ describe("ngAnimate", function() {
|
||||
function capture(event) {
|
||||
return function(element, add, remove, styles, done) {
|
||||
//some animations only have one extra param
|
||||
done = arguments[arguments.length-2]; //the last one is the styles array
|
||||
done = arguments[arguments.length - 2]; //the last one is the styles array
|
||||
captures[event]=done;
|
||||
};
|
||||
}
|
||||
@@ -4288,7 +4288,7 @@ describe("ngAnimate", function() {
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
for (var i=0;i<20;i++) {
|
||||
for (var i = 0; i < 20; i++) {
|
||||
kid = $compile('<div class="kid"></div>')($rootScope);
|
||||
$animate.enter(kid, element);
|
||||
}
|
||||
@@ -4300,8 +4300,8 @@ describe("ngAnimate", function() {
|
||||
dealoc(element);
|
||||
count = 0;
|
||||
|
||||
for (i=0;i<20;i++) {
|
||||
kid = $compile('<div class="kid c-'+i+'"></div>')($rootScope);
|
||||
for (i = 0; i < 20; i++) {
|
||||
kid = $compile('<div class="kid c-' + i + '"></div>')($rootScope);
|
||||
$animate.enter(kid, element);
|
||||
}
|
||||
|
||||
|
||||
@@ -400,6 +400,16 @@ describe('$aria', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('announcing ngMessages', function() {
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
|
||||
it('should attach aria-live', function() {
|
||||
var element = [
|
||||
$compile('<div ng-messages="myForm.myName.$error">')(scope)
|
||||
];
|
||||
expectAriaAttrOnEachElement(element, 'aria-live', "assertive");
|
||||
});
|
||||
});
|
||||
|
||||
describe('aria-value when disabled', function() {
|
||||
beforeEach(configAriaProvider({
|
||||
|
||||
@@ -86,7 +86,7 @@ describe('$cookies', function() {
|
||||
inject(function($cookies, $browser, $rootScope) {
|
||||
var i, longVal;
|
||||
|
||||
for (i=0; i<5000; i++) {
|
||||
for (i = 0; i < 5000; i++) {
|
||||
longVal += '*';
|
||||
}
|
||||
|
||||
|
||||
Vendored
+175
-3
@@ -6,7 +6,7 @@ describe('ngMock', function() {
|
||||
describe('TzDate', function() {
|
||||
|
||||
function minutes(min) {
|
||||
return min*60*1000;
|
||||
return min * 60 * 1000;
|
||||
}
|
||||
|
||||
it('should look like a Date', function() {
|
||||
@@ -321,11 +321,15 @@ describe('ngMock', function() {
|
||||
inject(function($interval, $rootScope) {
|
||||
var applySpy = spyOn($rootScope, '$apply').andCallThrough();
|
||||
|
||||
$interval(noop, 1000, 0, false);
|
||||
var counter = 0;
|
||||
$interval(function increment() { counter++; }, 1000, 0, false);
|
||||
|
||||
expect(applySpy).not.toHaveBeenCalled();
|
||||
expect(counter).toBe(0);
|
||||
|
||||
$interval.flush(2000);
|
||||
expect(applySpy).not.toHaveBeenCalled();
|
||||
expect(counter).toBe(2);
|
||||
}));
|
||||
|
||||
|
||||
@@ -533,7 +537,7 @@ describe('ngMock', function() {
|
||||
|
||||
function logFn(text) {
|
||||
return function() {
|
||||
log += text +';';
|
||||
log += text + ';';
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1507,6 +1511,174 @@ describe('ngMock', function() {
|
||||
expect($rootElement.text()).toEqual('');
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('$rootScopeDecorator', function() {
|
||||
|
||||
describe('$countChildScopes', function() {
|
||||
|
||||
it('should return 0 when no child scopes', inject(function($rootScope) {
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
|
||||
var childScope = $rootScope.$new();
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
expect(childScope.$countChildScopes()).toBe(0);
|
||||
|
||||
var grandChildScope = childScope.$new();
|
||||
expect(childScope.$countChildScopes()).toBe(1);
|
||||
expect(grandChildScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
|
||||
|
||||
it('should correctly navigate complex scope tree', inject(function($rootScope) {
|
||||
var child;
|
||||
|
||||
$rootScope.$new();
|
||||
$rootScope.$new().$new().$new();
|
||||
child = $rootScope.$new().$new();
|
||||
child.$new();
|
||||
child.$new();
|
||||
child.$new().$new().$new();
|
||||
|
||||
expect($rootScope.$countChildScopes()).toBe(11);
|
||||
}));
|
||||
|
||||
|
||||
it('should provide the current count even after child destructions', inject(function($rootScope) {
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
|
||||
var childScope1 = $rootScope.$new();
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
|
||||
var childScope2 = $rootScope.$new();
|
||||
expect($rootScope.$countChildScopes()).toBe(2);
|
||||
|
||||
childScope1.$destroy();
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
|
||||
childScope2.$destroy();
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
|
||||
|
||||
it('should work with isolate scopes', inject(function($rootScope) {
|
||||
/*
|
||||
RS
|
||||
|
|
||||
CIS
|
||||
/ \
|
||||
GCS GCIS
|
||||
*/
|
||||
|
||||
var childIsolateScope = $rootScope.$new(true);
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
|
||||
var grandChildScope = childIsolateScope.$new();
|
||||
expect($rootScope.$countChildScopes()).toBe(2);
|
||||
expect(childIsolateScope.$countChildScopes()).toBe(1);
|
||||
|
||||
var grandChildIsolateScope = childIsolateScope.$new(true);
|
||||
expect($rootScope.$countChildScopes()).toBe(3);
|
||||
expect(childIsolateScope.$countChildScopes()).toBe(2);
|
||||
|
||||
childIsolateScope.$destroy();
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('$countWatchers', function() {
|
||||
|
||||
it('should return the sum of watchers for the current scope and all of its children', inject(
|
||||
function($rootScope) {
|
||||
|
||||
expect($rootScope.$countWatchers()).toBe(0);
|
||||
|
||||
var childScope = $rootScope.$new();
|
||||
expect($rootScope.$countWatchers()).toBe(0);
|
||||
|
||||
childScope.$watch('foo');
|
||||
expect($rootScope.$countWatchers()).toBe(1);
|
||||
expect(childScope.$countWatchers()).toBe(1);
|
||||
|
||||
$rootScope.$watch('bar');
|
||||
childScope.$watch('baz');
|
||||
expect($rootScope.$countWatchers()).toBe(3);
|
||||
expect(childScope.$countWatchers()).toBe(2);
|
||||
}));
|
||||
|
||||
|
||||
it('should correctly navigate complex scope tree', inject(function($rootScope) {
|
||||
var child;
|
||||
|
||||
$rootScope.$watch('foo1');
|
||||
|
||||
$rootScope.$new();
|
||||
$rootScope.$new().$new().$new();
|
||||
|
||||
child = $rootScope.$new().$new();
|
||||
child.$watch('foo2');
|
||||
child.$new();
|
||||
child.$new();
|
||||
child = child.$new().$new().$new();
|
||||
child.$watch('foo3');
|
||||
child.$watch('foo4');
|
||||
|
||||
expect($rootScope.$countWatchers()).toBe(4);
|
||||
}));
|
||||
|
||||
|
||||
it('should provide the current count even after child destruction and watch deregistration',
|
||||
inject(function($rootScope) {
|
||||
|
||||
var deregisterWatch1 = $rootScope.$watch('exp1');
|
||||
|
||||
var childScope = $rootScope.$new();
|
||||
childScope.$watch('exp2');
|
||||
|
||||
expect($rootScope.$countWatchers()).toBe(2);
|
||||
|
||||
childScope.$destroy();
|
||||
expect($rootScope.$countWatchers()).toBe(1);
|
||||
|
||||
deregisterWatch1();
|
||||
expect($rootScope.$countWatchers()).toBe(0);
|
||||
}));
|
||||
|
||||
|
||||
it('should work with isolate scopes', inject(function($rootScope) {
|
||||
/*
|
||||
RS=1
|
||||
|
|
||||
CIS=1
|
||||
/ \
|
||||
GCS=1 GCIS=1
|
||||
*/
|
||||
|
||||
$rootScope.$watch('exp1');
|
||||
expect($rootScope.$countWatchers()).toBe(1);
|
||||
|
||||
var childIsolateScope = $rootScope.$new(true);
|
||||
childIsolateScope.$watch('exp2');
|
||||
expect($rootScope.$countWatchers()).toBe(2);
|
||||
expect(childIsolateScope.$countWatchers()).toBe(1);
|
||||
|
||||
var grandChildScope = childIsolateScope.$new();
|
||||
grandChildScope.$watch('exp3');
|
||||
|
||||
var grandChildIsolateScope = childIsolateScope.$new(true);
|
||||
grandChildIsolateScope.$watch('exp4');
|
||||
|
||||
expect($rootScope.$countWatchers()).toBe(4);
|
||||
expect(childIsolateScope.$countWatchers()).toBe(3);
|
||||
expect(grandChildScope.$countWatchers()).toBe(1);
|
||||
expect(grandChildIsolateScope.$countWatchers()).toBe(1);
|
||||
|
||||
childIsolateScope.$destroy();
|
||||
expect($rootScope.$countWatchers()).toBe(1);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +70,36 @@ describe('$route', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow redirects while handling $routeChangeStart', function() {
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/some', {
|
||||
id: 'some', template: 'Some functionality'
|
||||
});
|
||||
$routeProvider.when('/redirect', {
|
||||
id: 'redirect'
|
||||
});
|
||||
});
|
||||
module(provideLog);
|
||||
inject(function($route, $location, $rootScope, $compile, log) {
|
||||
$rootScope.$on('$routeChangeStart', function(event, next, current) {
|
||||
if (next.id === 'some') {
|
||||
$location.path('/redirect');
|
||||
}
|
||||
});
|
||||
$compile('<div><div ng-view></div></div>')($rootScope);
|
||||
$rootScope.$on('$routeChangeStart', log.fn('routeChangeStart'));
|
||||
$rootScope.$on('$routeChangeError', log.fn('routeChangeError'));
|
||||
$rootScope.$on('$routeChangeSuccess', log.fn('routeChangeSuccess'));
|
||||
$rootScope.$apply(function() {
|
||||
$location.path('/some');
|
||||
});
|
||||
|
||||
expect($route.current.id).toBe('redirect');
|
||||
expect($location.path()).toBe('/redirect');
|
||||
expect(log).toEqual(['routeChangeStart', 'routeChangeStart', 'routeChangeSuccess']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should route and fire change event', function() {
|
||||
var log = '',
|
||||
lastRoute,
|
||||
@@ -237,6 +267,31 @@ describe('$route', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow configuring caseInsensitiveMatch on the route provider level', function() {
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.caseInsensitiveMatch = true;
|
||||
$routeProvider.when('/Blank', {template: 'blank'});
|
||||
$routeProvider.otherwise({template: 'other'});
|
||||
});
|
||||
inject(function($route, $location, $rootScope) {
|
||||
$location.path('/bLaNk');
|
||||
$rootScope.$digest();
|
||||
expect($route.current.template).toBe('blank');
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow overriding provider\'s caseInsensitiveMatch setting on the route level', function() {
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.caseInsensitiveMatch = true;
|
||||
$routeProvider.when('/Blank', {template: 'blank', caseInsensitiveMatch: false});
|
||||
$routeProvider.otherwise({template: 'other'});
|
||||
});
|
||||
inject(function($route, $location, $rootScope) {
|
||||
$location.path('/bLaNk');
|
||||
$rootScope.$digest();
|
||||
expect($route.current.template).toBe('other');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not change route when location is canceled', function() {
|
||||
module(function($routeProvider) {
|
||||
@@ -294,6 +349,21 @@ describe('$route', function() {
|
||||
$rootScope.$digest();
|
||||
expect($route.current).toBeDefined();
|
||||
}));
|
||||
|
||||
it("should use route params inherited from prototype chain", function() {
|
||||
function BaseRoute() {}
|
||||
BaseRoute.prototype.templateUrl = 'foo.html';
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/foo', new BaseRoute());
|
||||
});
|
||||
|
||||
inject(function($route, $location, $rootScope) {
|
||||
$location.path('/foo');
|
||||
$rootScope.$digest();
|
||||
expect($route.current.templateUrl).toBe('foo.html');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -374,7 +374,7 @@ describe('HTML', function() {
|
||||
inject(function($sanitize) {
|
||||
sanitize = $sanitize;
|
||||
});
|
||||
var input = '<a href="'+this.actual+'"></a>';
|
||||
var input = '<a href="' + this.actual + '"></a>';
|
||||
return sanitize(input) === input;
|
||||
},
|
||||
toBeValidImageSrc: function() {
|
||||
@@ -382,7 +382,7 @@ describe('HTML', function() {
|
||||
inject(function($sanitize) {
|
||||
sanitize = $sanitize;
|
||||
});
|
||||
var input = '<img src="'+this.actual+'"/>';
|
||||
var input = '<img src="' + this.actual + '"/>';
|
||||
return sanitize(input) === input;
|
||||
}
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user