Compare commits

..

53 Commits

Author SHA1 Message Date
Jeff Cross 4d4e6036a9 chore(docs): add favicon to docs app 2014-11-17 09:32:21 -08:00
samuel durand 4d885cbd62 docs($anchorScrollProvider): remove repeated word
Closes #10093
2014-11-17 14:44:23 +00:00
Brian Westrich eec2020518 docs(tutorial/step-5): include sort and filter in json view experiment
Closes #10082
2014-11-17 14:33:50 +00:00
Brian Westrich 2901c53f42 docs(tutorial/step-4): "unknown" option is actually blank
The name 'unknown' doesn't appear as a choice, the new choice is just blank.
Side note: once I choose one of the non-blank options, I no longer see the blank option.

Closes #10079
2014-11-17 13:47:47 +00:00
Peter Bacon Darwin e80053d91f fix($rootScope): handle cyclic references in scopes when creating error messages
Use the new private function `stringify` to convert scope values to strings,
since this can cope with cyclic references and other oddities.

Closes #10085
2014-11-17 13:38:04 +00:00
Peter Bacon Darwin fa12c3c86a fix(ngRepeat): support cyclic object references in error messages
Now that `minErr` can cope with objects that cannot be normally stringified
to JSON, just pass the error arguments straight through without trying to
stringify them first.

Closes #9838
Closes #10065
Closes #10085
2014-11-17 13:37:58 +00:00
Peter Bacon Darwin cf43ccdf9b fix(minErr): stringify non-JSON compatible objects in error messages
Fix the JSON stringification to output a more meaningful string when an
object cannot be normally converted to a JSON string, such as when the
object contains cyclic references that would cause `JSON.stringify()`
to throw an error.

Closes #10085
2014-11-17 13:37:52 +00:00
Shahar Talmi a9352c19ce feat($location): allow to location to be changed during $locationChangeStart
Closes #9607
Closes #9678
2014-11-15 23:25:21 +00:00
Jakub Hampl 6f19a6fd33 fix($http): don't parse single space responses as JSON
Closes #9907
2014-11-15 12:43:24 +01:00
Kent C. Dodds f30163e63a docs(nav): highlight current nav-index-listing
Color the current nav-index-listing item dark red
to make it easier to know where you are.

Closes #9970
Closes #9974
2014-11-15 11:30:23 +01:00
Marcy Sutton 5b23bc9b07 docs(ngAria): Add Usage Details and Examples
Closes #10031
2014-11-14 18:08:42 -05:00
Marcy Sutton 9d1e87a3f1 docs(guide/accessibility): Content updates
Also includes new section on ngMessages
2014-11-14 18:06:59 -05:00
Peter Bacon Darwin 6604c23614 fix(select): ensure the label attribute is updated in Internet Explorer
Only changing the `<option>` text value is not enough to trigger a render
change in IE. We need to explicit update the `label` property too.

Closes #9621
Closes #10042
2014-11-14 21:17:28 +00:00
Peter Bacon Darwin 195deca6e2 test(select): refactor option elements expectations to use toEqualOption matcher
By using a new matcher our tests become less brittle with respect to unimportant
extra attributes.
2014-11-14 21:16:42 +00:00
Caitlin Potter 85eb9660ef fix(ngPattern): match behaviour of native HTML pattern attribute
From https://html.spec.whatwg.org/multipage/forms.html#attr-input-pattern

> The compiled pattern regular expression, when matched against a string, must have its start
anchored to the start of the string and its end anchored to the end of the string.

Closes #9881
Closes #9888
2014-11-14 20:18:57 +00:00
David Stensland d81ff8885b fix(ngMock): call $interval callbacks even when invokeApply is false
Make ngMock.$interval behave like ng.$interval

Closes #10032
2014-11-14 11:42:24 -05:00
Caitlin Potter eca14d98a4 chore($q): make jscs happy
jscs loves to be happy and does not love trailing whitespace.
2014-11-14 11:11:39 -05:00
Bernie Telles 22ecbc50f4 docs($q): explain what the $q service does in description
Explain what the $q service does in description, instead of origin document.

The original explanation was less accessible to people new to promises and JS in general.

Closes #10056
2014-11-14 11:09:37 -05:00
Chatchavan Wacharamanotham 7f857e44a2 docs(guide/compiler): replaced 'locals' with 'scope'
In "Understanding How Scopes Work with Transcluded Directives" section, a text referred to an
obsolete 'locals' instead of 'scope'.

Closes #10018
2014-11-14 11:03:24 -05:00
Caitlin Potter be6920b356 test(orderBy): add test cases for ordering array-like objects
Closes #10060
2014-11-14 10:02:23 -05:00
Karol Wypchło 8eabc5463c perf(orderBy): copy array with slice instead of for loop
Use array slice method to copy entire array instead of a for loop
http://jsperf.com/new-array-vs-splice-vs-slice/54

Closes #9942
2014-11-14 09:34:16 -05:00
Anton Savchenko 14ff529fbb refact(angular.bind): use concat() rather than duplicating code
Closes #4200
2014-11-13 13:33:24 +00:00
Jason Bedard fbad280570 refactor($parse): separate tokenizing vs parsing more
Fixes `a.b` and `a .b` generating different getterFns
Fixes `{'': ...}` turning into `{"''": ...}`
Fixes `{.: ...}` parsing as `{'.': ...}` instead of throwing
Fixes #9131
2014-11-12 23:36:23 +01:00
Pawel Kozlowski 8b775a0d58 docs(guide/expressions): clarify regexp literals usage in expressions
Closes #10026

Closes #10030
2014-11-12 22:50:21 +01:00
Pawel Kozlowski 0bbc6ee481 docs($route): fix description of the caseInsensitiveMatch property
Closes #10028
2014-11-12 21:02:24 +01:00
Blaise Kal 381b185117 docs(guide/expressions): replace curly quotes with straight quotes in code example
REAL QUOTES HAVE CURVES

Closes #10017
2014-11-12 13:50:09 -05:00
Henrique Ramos Limas fa0d8c47c3 docs($controller): mention "controller as" syntax
Closes #10011
2014-11-12 08:13:43 +00:00
PatrickJS 7e233eb58a docs($q): missing finally notifyCallback API
finally allows for notifyCallback and is missing in the Docs
https://github.com/angular/angular.js/blob/v1.3.2/src/ng/q.js#L288

Closes #10010
2014-11-12 08:04:56 +00:00
Caitlin Potter b7afd11d26 refactor($parse): don't use bind-once interceptor for non-bind-once expressions
Side-effects:
  - Logic for allOrNothing watches now lives in $interpolate rather than $parse

Credit to @jbedard for idea to remove $watch interceptors craziness from $interpolate. Even though
it technically didn't actually work, it was worth a shot, and helped clean things up a bit. Go team!

Closes #9958
Closes #9961
2014-11-11 20:29:36 -05:00
Georgios Kalpakas 8582088b36 docs($q): remove IE8-specific notice
Closes #10008
2014-11-11 21:03:51 +01:00
Pawel Kozlowski 0db573b749 feat($routeProvider): allow setting caseInsensitiveMatch on the provider
Fixes #6477

Closes #9873
2014-11-11 20:20:16 +01:00
codef0rmer 5e78af769e docs(guide/Index): add book AngularJS UI Development
Matthias and I wrote a book on AngularJS which might be helpful for Angular developers.

Merci~!

Closes #9971
2014-11-11 14:13:28 -05:00
AlexChan 804e75045c docs(ngModel.NgModelController) use $evalAsync instead of $apply for event handling
Have the apply called safely during events by using `$evalAsync` rather than `$apply`
This will help ensure that an apply for a user directive is not called during a digest cycle.

Closes #9891
2014-11-11 13:59:17 -05:00
Dim ebc3b7b1c3 docs(minerr/unpr): fix code example
Closes #10000
2014-11-11 12:35:19 +01:00
Andreas Fischer 830846f664 docs(guide/Modules): missing "a" in "a collection"
Closes #10001
2014-11-11 12:24:30 +01:00
yarsh 0462ee6659 docs(ngModelOptions): minor grammer error
Closes #9928
2014-11-11 10:20:32 +01:00
rsperberg 9cc6835819 docs(guide/Conceptual Overview): change "a" to "an" before "ng-controller"
Closes #9895
2014-11-10 20:41:04 +01:00
inphovore ee1fc1dc13 docs(guide/Bootstrap): batarang link correction
Closes #9869
2014-11-10 19:42:20 +01:00
Nicholas Albion 41b36e689c docs($compile): bindToController clarification
It was not clear that `bindToController` is a boolean.

Closes #9835
2014-11-10 19:19:05 +01:00
Georgios Kalpakas 52545e5a74 docs($browser): minor docs fixes
Remove obsolete `XHR` param from the docs and correct
`$log` param description.

Closes #9810
2014-11-10 19:07:15 +01:00
Justin 2abea7514a docs(select): minor markdown syntax fix
Half the sentence was being highlighted as code.

Closes #9983
2014-11-10 12:05:46 -05:00
Martin Staffa f157d02793 docs(ngModelController): clarify parse errors
Closes #9952
2014-11-09 23:31:27 +01:00
Rouven Weßling 77d8ae1d45 refactor($location) Remove unused variables
These were left around in 736c8fbbae

Closes #9482
2014-11-09 16:18:00 +01:00
Henry Zhu e21b6ff3ff style(*): add rule requireSpacesInConditionalExpression
Closes #9973
2014-11-09 15:51:01 +01:00
Henry Zhu 06016bb12c style(*): add rules requireSpace(After|Before)BinaryOperators 2014-11-09 15:51:01 +01:00
Arjunkumar 50e72fcae1 docs(guide/migration): typo fix
spell check propery to property

Closes #9937
2014-11-08 14:56:39 +01:00
micellius b84e62bd28 docs(CHANGELOG): ohmygosh they're different ohmygoshohmygosh
Align versioning format

Closes #9968
2014-11-08 08:11:51 -05:00
Igor Minar b6fd184a93 docs(CHANGELOG): add a security note to the 1.3.2 log 2014-11-07 17:09:44 -08:00
Caitlin Potter 1db9e617ce docs(CHANGELOG): slight fixup 2014-11-07 14:11:10 -05:00
Caitlin Potter 0918f146e4 docs(CHANGELOG): add v1.3.2 changes 2014-11-07 14:09:32 -05:00
Brian Ford 6dfd938bbc docs(guide/index): link to security 2014-11-07 10:32:10 -08:00
Brian Ford 4f5a60bcca docs($parse): formatting, link to security docs 2014-11-07 10:32:10 -08:00
Brian Ford e593939411 docs(security): add security doc 2014-11-07 10:32:10 -08:00
85 changed files with 1644 additions and 703 deletions
+8
View File
@@ -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
},
-2
View File
@@ -5,8 +5,6 @@
{
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
"requireSpaceAfterBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
"requireSpaceBeforeBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
"disallowImplicitTypeConversion": ["string"],
"disallowMultipleLineBreaks": true,
"disallowKeywordsOnNewLine": ["else"],
+46
View File
@@ -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)
+2
View File
@@ -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'
],
+4
View File
@@ -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

+1
View File
@@ -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'
};
};
+21
View File
@@ -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}
+1 -1
View File
@@ -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.
+220 -25
View File
@@ -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 its 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)
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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.
+7 -1
View File
@@ -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>
```
+2 -1
View File
@@ -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/)
+1 -1
View File
@@ -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),
+1 -1
View File
@@ -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
+60
View File
@@ -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}
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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:
+1
View File
@@ -19,6 +19,7 @@
"angularModule": false,
"nodeName_": false,
"uid": false,
"toDebugString": false,
"REGEX_STRING_REGEXP" : false,
"lowercase": false,
+4 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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);
};
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+4
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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};
+4 -2
View File
@@ -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);
}
+2 -2
View File
@@ -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;
};
}
}
+6 -6
View File
@@ -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;
}
+1 -1
View File
@@ -123,7 +123,7 @@ function limitToFilter() {
n = input.length;
}
for (; i<n; i++) {
for (; i < n; i++) {
out.push(input[i]);
}
+2 -4
View File
@@ -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
View File
@@ -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);
}
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+1 -1
View File
@@ -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
View File
@@ -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);
}
+1 -1
View File
@@ -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
View File
@@ -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) {
+1 -1
View File
@@ -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
View File
@@ -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.
+20 -16
View File
@@ -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
View File
@@ -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
View File
@@ -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({},
+1 -1
View File
@@ -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;
};
/**
+1 -1
View File
@@ -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];
};
/**
+3 -3
View File
@@ -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) {
+1 -1
View File
@@ -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 {
+2 -2
View File
@@ -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;
}
+29
View File
@@ -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;
}
+1
View File
@@ -18,6 +18,7 @@
"angularModule": false,
"nodeName_": false,
"uid": false,
"toDebugString": false,
"lowercase": false,
"uppercase": false,
+8 -8
View File
@@ -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 -->' +
+2 -2
View File
@@ -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');
});
+1 -1
View File
@@ -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_-]+)(/.*)$');
+5 -5
View File
@@ -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;
}
+18 -18
View File
@@ -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:'&amp;';}).
replace(/&(\w+[&;\W])?/g, function(match, entity) {return entity ? match : '&amp;';}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
} 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() + '>';
+7
View File
@@ -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
+2 -2
View File
@@ -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() {
+1 -1
View File
@@ -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
View File
@@ -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);
});
+3 -3
View File
@@ -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
View File
@@ -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();
});
});
+2 -2
View File
@@ -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}}]");
}));
+68 -31
View File
@@ -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');
});
+2 -2
View File
@@ -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
View File
@@ -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();
});
});
});
+11
View File
@@ -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);
+149
View File
@@ -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
View File
@@ -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() {
+7 -7
View File
@@ -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
View File
@@ -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();
+2 -2
View File
@@ -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);
+7 -7
View File
@@ -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);
}
+1 -1
View File
@@ -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 += '*';
}
+7 -3
View File
@@ -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 + ';';
};
}
+55
View File
@@ -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) {
+2 -2
View File
@@ -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;
}
});
+1 -1
View File
@@ -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();
+15
View File
@@ -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>>"]');
});
});