Compare commits
53 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 |
@@ -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"],
|
||||
|
||||
@@ -1,3 +1,49 @@
|
||||
<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)
|
||||
|
||||
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'
|
||||
],
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -12,7 +12,7 @@ assistive technologies used by persons with disabilities.
|
||||
|
||||
##Including ngAria
|
||||
|
||||
Using ngAria is as simple as requiring the ngAria module in your application. ngAria hooks into
|
||||
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.
|
||||
|
||||
@@ -20,6 +20,15 @@ at runtime.
|
||||
angular.module('myApp', ['ngAria'])...
|
||||
```
|
||||
|
||||
###Using ngAria
|
||||
Most of what ngAria does is only visible "under the hood". To see the module in action, once you've
|
||||
added it as a dependency, you can test a few things:
|
||||
* Using your favorite element inspector, look for ngAria attributes in your own code.
|
||||
* 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>
|
||||
@@ -31,7 +40,7 @@ Currently, ngAria interfaces with the following directives:
|
||||
|
||||
<h2 id="ngmodel">ngModel</h2>
|
||||
|
||||
Most of ngAria's heavy lifting happens in the [ngModel](https://docs.angularjs.org/api/ng/directive/ngModel)
|
||||
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`.
|
||||
|
||||
@@ -47,15 +56,80 @@ attributes (if they have not been explicitly specified by the developer):
|
||||
|
||||
###Example
|
||||
|
||||
```html
|
||||
<md-checkbox ng-model="val" required>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<md-checkbox ng-model="val" required aria-required="true" tabIndex="0">
|
||||
```
|
||||
<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
|
||||
@@ -106,6 +180,24 @@ screen reader users won't accidentally focus on "mystery elements". Managing tab
|
||||
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
|
||||
@@ -116,26 +208,128 @@ The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redun
|
||||
`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">ngClick and ngDblClick</h2>
|
||||
If `ngClick` or `ngDblClick` 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. I have recommended this also bind
|
||||
`ng-keypress` to be more useful; the conversation is [currently ongoing](https://github.com/angular/angular.js/issues/9254).
|
||||
<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:
|
||||
you can use the {@link ngAria.$ariaProvider#config config} method. Just keep in mind this will
|
||||
tell ngAria to ignore the attribute globally.
|
||||
|
||||
```
|
||||
angular.module('myApp', ['ngAria'], function config($ariaProvider) {
|
||||
$ariaProvider.config({
|
||||
tabindex: false
|
||||
<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
|
||||
|
||||
@@ -164,6 +358,7 @@ Accessibility best practices that apply to web apps in general also apply to Ang
|
||||
|
||||
* [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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -154,7 +154,7 @@ First, there is a new JavaScript file that contains a so-called <a name="control
|
||||
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
|
||||
and all of the element's children.
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ 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}
|
||||
@@ -108,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
|
||||
|
||||
@@ -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}
|
||||
@@ -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
|
||||
|
||||
@@ -258,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:
|
||||
|
||||
@@ -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)!");
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -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.
|
||||
*
|
||||
@@ -1029,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);
|
||||
@@ -1232,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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2045,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) &&
|
||||
@@ -2074,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;
|
||||
@@ -2293,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.
|
||||
*
|
||||
|
||||
+48
-23
@@ -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.
|
||||
@@ -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
|
||||
@@ -1641,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
|
||||
|
||||
@@ -2577,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) {
|
||||
@@ -2868,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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
@@ -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);
|
||||
|
||||
+119
-145
@@ -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('}');
|
||||
@@ -1251,13 +1217,21 @@ 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
|
||||
// initial value is defined (for bind-once)
|
||||
return isDefined(value) || interceptorFn.$stateful ? result : value;
|
||||
return isDefined(value) ? result : value;
|
||||
};
|
||||
|
||||
// Propagate $$watchDelegates other then inputsWatchDelegate
|
||||
|
||||
+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
|
||||
|
||||
+8
-8
@@ -773,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
|
||||
@@ -810,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);
|
||||
@@ -1161,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]) {
|
||||
@@ -1235,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);
|
||||
|
||||
+82
-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.
|
||||
|
||||
Vendored
+20
-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]();
|
||||
}
|
||||
|
||||
|
||||
+18
-4
@@ -146,6 +146,9 @@ function $RouteProvider() {
|
||||
if (angular.isUndefined(routeCopy.reloadOnSearch)) {
|
||||
routeCopy.reloadOnSearch = true;
|
||||
}
|
||||
if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) {
|
||||
routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch;
|
||||
}
|
||||
routes[path] = angular.extend(
|
||||
routeCopy,
|
||||
path && pathRegExp(path, routeCopy)
|
||||
@@ -153,9 +156,9 @@ function $RouteProvider() {
|
||||
|
||||
// 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},
|
||||
@@ -166,6 +169,17 @@ function $RouteProvider() {
|
||||
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
|
||||
@@ -634,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);
|
||||
}
|
||||
|
||||
|
||||
+16
-16
@@ -275,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>' +
|
||||
@@ -300,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({
|
||||
@@ -355,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');
|
||||
@@ -1587,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">');
|
||||
});
|
||||
});
|
||||
@@ -2979,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() {
|
||||
@@ -3558,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);
|
||||
@@ -4335,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);
|
||||
@@ -5147,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);
|
||||
}
|
||||
|
||||
@@ -5163,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();
|
||||
}
|
||||
});
|
||||
@@ -5900,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);
|
||||
}
|
||||
|
||||
@@ -5913,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);
|
||||
}
|
||||
});
|
||||
@@ -5976,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);
|
||||
});
|
||||
@@ -5988,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));
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -5996,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}}]");
|
||||
}));
|
||||
|
||||
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
|
||||
@@ -2192,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() {
|
||||
|
||||
+135
-85
@@ -22,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);
|
||||
@@ -34,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);
|
||||
@@ -46,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);
|
||||
@@ -62,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);
|
||||
@@ -70,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() {
|
||||
@@ -85,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() {
|
||||
@@ -93,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() {
|
||||
@@ -155,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) {
|
||||
@@ -173,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() {
|
||||
@@ -192,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() {
|
||||
@@ -228,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() {
|
||||
@@ -249,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() {
|
||||
@@ -269,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());
|
||||
@@ -359,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() {
|
||||
@@ -438,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() {
|
||||
@@ -471,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);
|
||||
});
|
||||
@@ -506,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() {
|
||||
@@ -520,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() {
|
||||
@@ -617,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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
+7
-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 + ';';
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('widgets', function() {
|
||||
* Custom value parser for futures.
|
||||
*/
|
||||
function checkboxParser(value) {
|
||||
return angular.fromJson(value.substring(value.indexOf('=')+1));
|
||||
return angular.fromJson(value.substring(value.indexOf('=') + 1));
|
||||
}
|
||||
|
||||
input('checkbox.tea').check();
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
describe('toDebugString', function() {
|
||||
it('should convert its argument to a string', function() {
|
||||
expect(toDebugString('string')).toEqual('string');
|
||||
expect(toDebugString(123)).toEqual('123');
|
||||
expect(toDebugString({a:{b:'c'}})).toEqual('{"a":{"b":"c"}}');
|
||||
expect(toDebugString(function fn() { var a = 10; })).toEqual('function fn()');
|
||||
expect(toDebugString()).toEqual('undefined');
|
||||
var a = { };
|
||||
a.a = a;
|
||||
expect(toDebugString(a)).toEqual('{"a":"<<already seen>>"}');
|
||||
expect(toDebugString([a,a])).toEqual('[{"a":"<<already seen>>"},"<<already seen>>"]');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user