Compare commits

...

81 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
Martin Staffa 9e305948e4 fix(select): use strict comparison for isSelected with selectAs
Closes #9639
Closes #9949
2014-11-07 13:22:01 -05:00
Lucas Galfaso ed99821e4d fix($parse): stateful interceptors override an undefined expression
When using `$parse` with a stateful interceptor and the expression
is `undefined`, then return the result from the interceptor

NOTE from Igor: this is not the best solution. We need to refactor
this and one-time + $interpolate code to properly fix this. @caitp
is on it. See discussion in the PR.

Closes #9821
Closes #9825
2014-11-07 10:17:32 -08:00
Chirayu Krishnappa e057a9aa39 fix($parse, events): prevent accidental misuse of properties on $event 2014-11-06 19:30:48 -08:00
Chirayu Krishnappa e676d642f5 fix($parse): add quick check for Function constructor in fast path 2014-11-06 19:30:48 -08:00
Lucas Galfaso 288b531626 docs(CHANGELOG): Document breaking change from 23bc92b1
Document the breaking change from 23bc92b1

Closes #9947
2014-11-06 15:27:44 -05:00
rsperberg 7a4df50480 docs(guide/concepts): spell "Angular" with cap "A", fix typos
In these two instances, Angular was spelled with a lower-case "a." All occurrences should be spelled
consistently.

Compound adjectives preceding the noun they modify should generally be hyphenated (cf Chicago Manual
of Style, 6.40), e.g., "so-called directives."

Closes #9896
2014-11-06 14:16:16 -05:00
Adir 6550198003 docs(guide,tutorial): fix outdated Protractor API link
Link to the gh-pages deployment of protractor docs, it's much easier on the eyes.

Closes #9946
2014-11-06 13:57:50 -05:00
Marcy Sutton c6909ed144 docs(guide/accessibility): Add in-depth guide
Closes #9930
2014-11-06 10:17:37 -05:00
Marcy Sutton 187e43185d feat(ngAria): announce ngMessages with aria-live
By including the `ngAria` module, `ngMessages` will automatically include the aria-live
attribute with an assertive voice, allowing validation messages to be spoken throuhg a
screenreader.

Closes #9834
2014-11-05 17:50:18 -05:00
Peter Bacon Darwin 91834bcf37 test($compile): use ngMock.Scope.$countChildScopes() 2014-11-05 20:41:51 +00:00
Dave Longley 841c090755 fix($compile): do not rebind parent bound transclude functions
The `$compile` public API documentation indicates that
a transclude function may be passed as a second parameter, but
it was not clear that this is **not** the same function that is given
to directive link functions as the `transcludeFn` parameter.

We would like to be able to pass in a transclude function the public
linking function that is returned from `$compile` if we wish to, for
example, use lazy compilation inside a directive.

Doing so, however, highlighted two bugs:

* First, the transclude function would get rebound, incorrectly, changing
its scope from its original binding.
* Second, the `containingScope` would not be updated and the wrong
`$parent` would be assigned to the `transcludedScope` resulting in a
memory leak.

This patch fixes both of these issues.

It also converts various private optional positional parameters on `publicLinkFn`
into an `options` parameter, which is an object hash. The new `options`
parameter is documented as part of the public API.

Thanks to @lgalfaso, @tbosch, and @petebacondarwin.

Closes #9413
2014-11-05 20:41:51 +00:00
Kent C. Dodds da960544f1 docs(guide/Running in Production): ng-strict-di
Adding note about Strict DI mode.

Closes #9908
2014-11-05 14:43:13 -05:00
Igor Minar 74981c9f20 feat(ngMock): decorator that adds Scope#$countChildScopes and Scope#$countWatchers
When writing tests it's often useful to check the number of child scopes
or watchers within the current current scope subtree. Common use-case for advanced
directives is to test that the directive is properly cleaning up after itself. These
new methods simplify writing assertions that verify that child scopes were properly
destroyed or that watchers were deregistered.

Closes #9926
Closes #9871
2014-11-05 13:23:19 -05:00
danielmbarlow dc4b06559f docs(tutorial/step_12): added 'see phone-detail change'
This one caught me out for a while because, despite the note underneath, I didn't notice the addition
of <div class="phone-images"> and it's repeater until later.

Closes #9924
2014-11-05 12:13:57 -05:00
danielmbarlow 56138bdd63 docs(tutorial/step_12): small change to overview
The bullet points at the beginning of the article were a little hard to understand because they
didn't follow the grammatical form of the preceding articles. I hope these small modifications make
it a little easier for someone else to read.

Closes #9922
2014-11-05 09:08:56 -05:00
danielmbarlow 0ccc4fcd89 docs(tutorial/step_05): explain need for $httpBackend.flush in tests
There is an excellent explanation for the need for this in the documentation that may be helpful to
tutorial users, so I added a link to it.

Closes #9919
2014-11-05 08:36:37 -05:00
Julie Ralph b7e2b01bb5 chore(ci): update protractor to version 1.4.0 2014-11-04 17:16:19 -08:00
Tobias Gesellchen 0c19482d03 chore(.gitignore): ignore IntelliJ IDEA module files
Ignore IntelliJ IDEA modules files in the git repository

Closes #5273
2014-11-04 12:46:31 -05:00
Tero Parviainen 0f7bcfdf93 docs(guide/scope): describe watch depths
Describe and visualize the three watch strategies: By reference, by collection items, and by value.

Closes #9388
2014-11-03 12:44:16 -08:00
Loring Dodge 9a26ab5870 docs($route): replace comma with "and"
Remove a comma and replace with "and" on line 444

Closes #9889
2014-11-03 20:58:46 +01:00
jbnizet d906ed3100 docs(input): replace dateTimeLocal by datetime-local
Fixes #9856
Closes #9870
2014-11-02 19:58:32 +01:00
Martin Staffa 0b16d10d2d docs(ngValue): clarify the limitations of ngValue and option elements 2014-11-02 19:47:09 +01:00
Martin Staffa e0198c1cda docs(ngModelController): add $name property 2014-11-02 18:43:52 +01:00
Martin Staffa ba731ab850 docs(ngModelController): remove bogus @param fields 2014-11-02 18:43:52 +01:00
Caitlin Potter e69c1806a8 style(routeSpec.js): make jshint happy 2014-11-01 18:11:51 -04:00
cmichal b4770582f8 fix(ngRoute): allow proto inherited properties in route params object
copy route params with angular.copy before using angular.extend which looks only for enumerable own
properties

Closes #8181
Closes #9731
2014-11-01 18:08:24 -04:00
Henry Zhu 2a2fd14c08 docs(guide/Forms): clarify ngModel behavior for validation
Bueno!

Closes #9866
2014-10-31 18:08:22 -04:00
Jeff Cross e4eb382f2d docs(changelog): add release notes for 1.3.1 spectral-lobster 2014-10-31 10:39:14 -07:00
102 changed files with 2564 additions and 812 deletions
+1
View File
@@ -13,6 +13,7 @@ angular.js.tmproj
bower_components/
angular.xcodeproj
.idea
*.iml
.agignore
.lvimrc
libpeerconnection.log
+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"],
+104
View File
@@ -1,3 +1,105 @@
<a name="1.3.2"></a>
# 1.3.2 cardiovasculatory-magnification (2014-11-07)
## Bug Fixes
- **$compile:** do not rebind parent bound transclude functions
([841c0907](https://github.com/angular/angular.js/commit/841c0907556f525dbc4223609d808319fe0dd7e2),
[#9413](https://github.com/angular/angular.js/issues/9413))
- **$parse:**
- stateful interceptors override an `undefined` expression
([ed99821e](https://github.com/angular/angular.js/commit/ed99821e4dc621864f7e2d9a6b5305fca27fb7fa),
[#9821](https://github.com/angular/angular.js/issues/9821), [#9825](https://github.com/angular/angular.js/issues/9825))
- add quick check for Function constructor in fast path
([e676d642](https://github.com/angular/angular.js/commit/e676d642f5feb8d3ba88944634afb479ba525c36))
- **$parse, events:** prevent accidental misuse of properties on $event
([e057a9aa](https://github.com/angular/angular.js/commit/e057a9aa398ead209bd6bbf76e22d2d5562904fb))
- **ngRoute:** allow proto inherited properties in route params object
([b4770582](https://github.com/angular/angular.js/commit/b4770582f84f26c8ff7f2320a36a6b0ceff6e6cc),
[#8181](https://github.com/angular/angular.js/issues/8181), [#9731](https://github.com/angular/angular.js/issues/9731))
- **select:** use strict comparison for isSelected with selectAs
([9e305948](https://github.com/angular/angular.js/commit/9e305948e4965fb86b0c79985dc6e8c59a9c66af),
[#9639](https://github.com/angular/angular.js/issues/9639), [#9949](https://github.com/angular/angular.js/issues/9949))
## Features
- **ngAria:** announce ngMessages with aria-live
([187e4318](https://github.com/angular/angular.js/commit/187e43185dfb1bce6a318d95958c73cfb789d33c),
[#9834](https://github.com/angular/angular.js/issues/9834))
- **ngMock:** decorator that adds Scope#$countChildScopes and Scope#$countWatchers
([74981c9f](https://github.com/angular/angular.js/commit/74981c9f208b3617cbf00beafd61138d25c5d546),
[#9926](https://github.com/angular/angular.js/issues/9926), [#9871](https://github.com/angular/angular.js/issues/9871))
## Security Note
This release also contains security fixes for expression sandbox bypasses.
These issues affect only applications with known server-side XSS holes that are also using [CSP](https://developer.mozilla.org/en-US/docs/Web/Security/CSP) to secure their client-side code. If your application falls into this rare category, we recommend updating your version of Angular.
We'd like to thank security researches [Sebastian Lekies](https://twitter.com/sebastianlekies), [Jann Horn](http://thejh.net/), and [Gábor Molnár](https://twitter.com/molnar_g) for reporting these issues to us.
We also added a documentation page focused on security, which contains some of the best practices, DOs and DON'Ts. Please check out [https://docs.angularjs.org/guide/security](https://docs.angularjs.org/guide/security).
<a name="1.3.1"></a>
# 1.3.1 spectral-lobster (2014-10-31)
## Bug Fixes
- **$compile:** returning null when an optional controller is not found
([2cd5b4ec](https://github.com/angular/angular.js/commit/2cd5b4ec4409a818ccd33a6fbdeb99a3443a1809),
[#9404](https://github.com/angular/angular.js/issues/9404), [#9392](https://github.com/angular/angular.js/issues/9392))
- **$observe:** check if the attribute is undefined
([531a8de7](https://github.com/angular/angular.js/commit/531a8de72c439d8ddd064874bf364c00cedabb11),
[#9707](https://github.com/angular/angular.js/issues/9707), [#9720](https://github.com/angular/angular.js/issues/9720))
- **$parse:** support dirty-checking objects with null prototype
([28661d1a](https://github.com/angular/angular.js/commit/28661d1a8cc3a8454bad7ae531e027b1256476c9),
[#9568](https://github.com/angular/angular.js/issues/9568))
- **$sce:** use msie instead of $document[0].documentMode
([45252c3a](https://github.com/angular/angular.js/commit/45252c3a545336a0bac93be6ee28cde6afaa3cb4),
[#9661](https://github.com/angular/angular.js/issues/9661))
- **$templateRequest:** ignore JSON Content-Type header and content
([1bd473eb](https://github.com/angular/angular.js/commit/1bd473eb4587900086e0b6b308dcf1dcfe9760d9),
[#5756](https://github.com/angular/angular.js/issues/5756), [#9619](https://github.com/angular/angular.js/issues/9619))
- **i18n:** rename datetimeSymbols to be camelCase
([94f5a285](https://github.com/angular/angular.js/commit/94f5a285bfcf04d800afc462a7a37a3469d77f1a))
- **loader:** fix double spaces
([8b2f1a47](https://github.com/angular/angular.js/commit/8b2f1a47b584ceb98689f48538a2af73cd65dfd8),
[#9630](https://github.com/angular/angular.js/issues/9630))
- **ngMock:** $httpBackend should match data containing Date objects correctly
([1025f6eb](https://github.com/angular/angular.js/commit/1025f6ebf4e5933a12920889be00cd8ac8a106fa),
[#5127](https://github.com/angular/angular.js/issues/5127))
- **ngSanitize:** attribute name: xmlns:href -> xlink:href
([4cccf0f2](https://github.com/angular/angular.js/commit/4cccf0f2a89b002d63cb443e1e7b15f76dcef425),
[#9769](https://github.com/angular/angular.js/issues/9769))
- **select:** assign result of track exp to element value
([4b4098bf](https://github.com/angular/angular.js/commit/4b4098bfcae64f69c70a22393de1f3d9a0d3dc46),
[#9718](https://github.com/angular/angular.js/issues/9718), [#9592](https://github.com/angular/angular.js/issues/9592))
- **templateRequest:** allow empty html template
([52ceec22](https://github.com/angular/angular.js/commit/52ceec2229dc132b76da4e022c91474344f2d906),
[#9581](https://github.com/angular/angular.js/issues/9581))
- **testability:** escape regex chars in `findBindings` if using `exactMatch`
([02aa4f4b](https://github.com/angular/angular.js/commit/02aa4f4b85ee15922a1f2de8ba78f562c18518d0),
[#9595](https://github.com/angular/angular.js/issues/9595), [#9600](https://github.com/angular/angular.js/issues/9600))
## Features
- **$compile:** allow $watchCollection to be used in bi-directional bindings
([40bbc981](https://github.com/angular/angular.js/commit/40bbc9817845bf75581daee5d0ec30980affb0f5),
[#9725](https://github.com/angular/angular.js/issues/9725))
- **ngSanitize:** accept SVG elements and attributes
([a54b25d7](https://github.com/angular/angular.js/commit/a54b25d77999a85701dfc5396fef78e586a99667),
[#9578](https://github.com/angular/angular.js/issues/9578), [#9751](https://github.com/angular/angular.js/issues/9751))
<a name="1.3.0"></a>
# 1.3.0 superluminal-nudge (2014-10-13)
@@ -1452,6 +1554,8 @@ Closes #8230
- **jQuery:** due to [9e7cb3c3](https://github.com/angular/angular.js/commit/9e7cb3c37543008e6236bb5a2c4536df2e1e43a9),
Angular no longer supports jQuery versions below 2.1.1.
- **$q:** due to [23bc92b1](https://github.com/angular/angular.js/commit/23bc92b17df882a907fb326320f0622717fefe7b),
Promises methods are no longer enumerated when using for-loops with `hasOwnProperty` check. E.g. `angular.extends`
<a name="1.2.22"></a>
+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'
],
+2 -1
View File
@@ -71,7 +71,8 @@ app.controller('DataController', function($scope, $rootScope) {
date2: new Date(Math.random()*Date.now()),
func: function(){ return star; },
obj: data[i-1],
keys: data[i-1] && (data[i-1].keys || Object.keys(data[i-1]))
keys: data[i-1] && (data[i-1].keys || Object.keys(data[i-1])),
constructor: data[i-1]
});
}
@@ -16,6 +16,12 @@
<label for="complexPath">Complex Paths</label>
</li>
<li>
<input type="radio" ng-model="expressionType" value="constructorPath" id="constructorPath">
<label for="constructorPath">Constructor Paths</label>
($parse special cases "constructor" for security)
</li>
<li>
<input type="radio" ng-model="expressionType" value="fieldAccess" id="fieldAccess">
<label for="fieldAccess">Field Accessors</label>
@@ -77,6 +83,17 @@
<span bm-pe-watch="row.keys"></span>
</li>
<li ng-switch-when="constructorPath" ng-repeat="(rowIdx, row) in ::data">
<span bm-pe-watch="row.index"></span>
<span bm-pe-watch="row.constructor.index"></span>
<span bm-pe-watch="row.constructor.index"></span>
<span bm-pe-watch="row.constructor.index"></span>
<span bm-pe-watch="row.constructor.constructor.index"></span>
<span bm-pe-watch="row.constructor.constructor.index"></span>
<span bm-pe-watch="row.constructor.constructor.constructor.index"></span>
<span bm-pe-watch="row.constructor.constructor.constructor.index"></span>
</li>
<li ng-switch-when="complexPath" ng-repeat="(rowIdx, row) in ::data">
<span bm-pe-watch="row.index"></span>
<span bm-pe-watch="row.num0"></span>
+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.
+335 -31
View File
@@ -6,56 +6,360 @@
# Accessibility with ngAria
You can use the `ngAria` module to have common ARIA attributes automatically applied when you
use certain directives. To enable `ngAria`, just require the module into your application and
the code will hook into your ng-show/ng-hide, input, textarea, button, select and
ng-required directives and add the appropriate ARIA states and properties.
The goal of ngAria is to improve Angular's default accessibility by enabling common
[ARIA](http://www.w3.org/TR/wai-aria/) attributes that convey state or semantic information for
assistive technologies used by persons with disabilities.
Currently, the following attributes are implemented:
* aria-hidden
* aria-checked
* aria-disabled
* aria-required
* aria-invalid
* aria-multiline
* aria-valuenow
* aria-valuemin
* aria-valuemax
* tabindex
##Including ngAria
You can disable individual attributes by using the `{@link ngAria.$ariaProvider#config config}` method.
###Example
Using {@link ngAria ngAria} is as simple as requiring the ngAria module in your application. ngAria hooks into
standard AngularJS directives and quietly injects accessibility support into your application
at runtime.
```js
angular.module('myApp', ['ngAria'])...
```
Elements using `ng-model` with `required` or `ngRequired` directives will automatically have
`aria-required` attributes with the proper corresponding values.
###Using ngAria
Most of what ngAria does is only visible "under the hood". To see the module in action, once you've
added it as a dependency, you can test a few things:
* Using your favorite element inspector, look for ngAria attributes in your own code.
* Test using your keyboard to ensure `tabindex` is used correctly.
* Fire up a screen reader such as VoiceOver to listen for ARIA support.
[Helpful screen reader tips.](http://webaim.org/articles/screenreader_testing/)
##Supported directives
Currently, ngAria interfaces with the following directives:
* <a href="#ngmodel">ngModel</a>
* <a href="#ngdisabled">ngDisabled</a>
* <a href="#ngshow">ngShow</a>
* <a href="#nghide">ngHide</a>
* <a href="#ngclick-and-ngdblclick">ngClick</a>
* <a href="#ngclick-and-ngdblclick">ngDblClick</a>
<h2 id="ngmodel">ngModel</h2>
Most of ngAria's heavy lifting happens in the {@link ngModel ngModel}
directive. For elements using ngModel, special attention is paid by ngAria if that element also
has a a role or type of `checkbox`, `radio`, `range` or `textbox`.
For those elements using ngModel, ngAria will dynamically bind and update the following ARIA
attributes (if they have not been explicitly specified by the developer):
* aria-checked
* aria-valuemin
* aria-valuemax
* aria-valuenow
* aria-invalid
* aria-required
###Example
<example module="ngAria_ngModelExample" deps="angular-aria.js">
<file name="index.html">
<style>
[role=checkbox] {
cursor: pointer;
display: inline-block;
}
[role=checkbox] .icon:before {
content: '\2610';
display: inline-block;
font-size: 2em;
line-height: 1;
vertical-align: middle;
speak: none;
}
[role=checkbox].active .icon:before {
content: '\2611';
}
pre {
white-space: pre-wrap;
}
</style>
<div>
<form ng-controller="formsController">
<some-checkbox role="checkbox" ng-model="checked" ng-class="{active: checked}"
ng-disabled="isDisabled" ng-click="toggleCheckbox()"
aria-label="Custom Checkbox" show-attrs>
<span class="icon" aria-hidden="true"></span>
Custom Checkbox
</some-checkbox>
</form>
</div>
<script>
var app = angular.module('ngAria_ngModelExample', ['ngAria'])
.controller('formsController', function($scope){
$scope.checked = false;
$scope.toggleCheckbox = function(){
$scope.checked = !$scope.checked;
}
})
.directive('someCheckbox', function(){
return {
restrict: 'E',
link: function($scope, $el, $attrs) {
$el.on('keypress', function(event){
event.preventDefault();
if(event.keyCode === 32 || event.keyCode === 13){
$scope.toggleCheckbox();
$scope.$apply();
}
});
}
}
})
.directive('showAttrs', function() {
return function($scope, $el, $attrs) {
var pre = document.createElement('pre');
$el.after(pre);
$scope.$watch(function() {
var $attrs = {};
Array.prototype.slice.call($el[0].attributes, 0).forEach(function(item) {
if (item.name !== 'show-$attrs') {
$attrs[item.name] = item.value;
}
});
return $attrs;
}, function(newAttrs, oldAttrs) {
pre.textContent = JSON.stringify(newAttrs, null, 2);
}, true);
}
});
</script>
</file>
</example>
ngAria will also add `tabIndex`, ensuring custom elements with these roles will be reachable from
the keyboard. It is still up to **you** as a developer to **ensure custom controls will be
operable** from the keybard. Think of `ng-click` on a `<div>` or `<md-checkbox>`: you still need
to bind `ng-keypress` to make it fully operable from the keyboard. As a rule, any time you create
a widget involving user interaction, be sure to test it with your keyboard and at least one mobile
and desktop screen reader (preferably more).
<h2 id="ngdisabled">ngDisabled</h2>
The `disabled` attribute is only valid for certain elements such as `button`, `input` and
`textarea`. To properly disable custom element directives such as `<md-checkbox>` or `<taco-tab>`,
using ngAria with [ngDisabled](https://docs.angularjs.org/api/ng/directive/ngDisabled) will also
add `aria-disabled`. This tells assistive technologies when a non-native input is disabled, helping
custom controls to be more accessible.
###Example
```html
<material-input ng-model="val" required>
<md-checkbox ng-disabled="disabled">
```
Becomes:
```html
<material-input ng-model="val" required aria-required="true">
<md-checkbox disabled aria-disabled="true">
```
ngAria is just a starting point. You'll have to manually choose how to implement some
accessibility features.
>You can check whether a control is legitimately disabled for a screen reader by visiting
[chrome://accessibility](chrome://accessibility).
For instance, you may want to add `ng-keypress` bindings alongside `ng-click` to make keyboard
navigation easier.
<h2 id="ngshow">ngShow</h2>
>The [ngShow](https://docs.angularjs.org/api/ng/directive/ngShow) directive shows or hides the
given HTML element based on the expression provided to the `ngShow` attribute. The element is
shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
## Additional Resources
In its default setup, ngAria for `ngShow` is actually redundant. It toggles `aria-hidden` on the
directive when it is hidden or shown. However, the default CSS of `display: none !important`,
already hides child elements from a screen reader. It becomes more useful when the default
CSS is overridden with properties that dont affect assistive technologies, such as `opacity`
or `transform`. By toggling `aria-hidden` dynamically with ngAria, we can ensure content visually
hidden with this technique will not be read aloud in a screen reader.
One caveat with this combination of CSS and `aria-hidden`: you must also remove links and other
interactive child elements from the tab order using `tabIndex=“-1”` on each control. This ensures
screen reader users won't accidentally focus on "mystery elements". Managing tab index on every
child control can be complex and affect performance, so 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
given HTML element based on the expression provided to the `ngHide` attribute. The element is
shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redundant. It toggles
`aria-hidden` on the directive when it is hidden or shown, but the content is already hidden with
`display: none`. See explanation for <a href="#ngshow">ngShow</a> when overriding the default CSS.
<h2 id="ngclick-and-ngdblclick">ngClick and ngDblclick</h2>
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex` if it isn't there already.
Even with this, you must currently still add `ng-keypress` to non-interactive elements such as `div`
or `taco-button` to enable keyboard access. Conversation is currently ongoing about whether ngAria
should also bind `ng-keypress`.
<h3>Example</h3>
```html
<div ng-click="toggleMenu()"></div>
```
Becomes:
```html
<div ng-click="toggleMenu()" tabindex="0"></div>
```
*Note: ngAria still requires `ng-keypress` to be added manually to non-native controls like divs.*
<h2 id="ngmessages">ngMessages</h2>
The new ngMessages module makes it easy to display form validation or other messages with priority
sequencing and animation. To expose these visual messages to screen readers,
ngAria injects `aria-live="polite"`, causing them to be read aloud any time a message is shown,
regardless of the user's focus location.
###Example
```html
<div ng-messages="myForm.myName.$error">
<div ng-message="required">You did not enter a field</div>
<div ng-message="maxlength">Your field is too long</div>
</div>
```
Becomes:
```html
<div ng-messages="myForm.myName.$error" aria-live="polite">
<div ng-message="required">You did not enter a field</div>
<div ng-message="maxlength">Your field is too long</div>
</div>
```
##Disabling attributes
The attribute magic of ngAria may not work for every scenario. To disable individual attributes,
you can use the {@link ngAria.$ariaProvider#config config} method. Just keep in mind this will
tell ngAria to ignore the attribute globally.
<example module="ngAria_ngDisabledExample" deps="angular-aria.js">
<file name="index.html">
<style>
[role=checkbox] {
cursor: pointer;
display: inline-block;
}
[role=checkbox] .icon:before {
content: '\2610';
display: inline-block;
font-size: 2em;
line-height: 1;
vertical-align: middle;
speak: none;
}
[role=checkbox].active .icon:before {
content: '\2611';
}
</style>
<form ng-controller="formsController">
<div ng-model="someModel" show-attrs>
Div with ngModel and aria-invalid disabled
</div>
<div role="checkbox" ng-model="checked" ng-class="{active: checked}"
aria-label="Custom Checkbox" ng-click="toggleCheckbox()" some-checkbox show-attrs>
<span class="icon" aria-hidden="true"></span>
Custom Checkbox for comparison
</div>
</form>
<script>
angular.module('ngAria_ngDisabledExample', ['ngAria'], function config($ariaProvider) {
$ariaProvider.config({
ariaInvalid: false,
tabindex: true
});
})
.controller('formsController', function($scope){
$scope.checked = false;
$scope.toggleCheckbox = function(){
$scope.checked = !$scope.checked;
}
})
.directive('someCheckbox', function(){
return {
restrict: 'A',
link: function($scope, $el, $attrs) {
$el.on('keypress', function(event){
event.preventDefault();
if(event.keyCode === 32 || event.keyCode === 13){
$scope.toggleCheckbox();
$scope.$apply();
}
});
}
}
})
.directive('showAttrs', function() {
return function(scope, el, attrs) {
var pre = document.createElement('pre');
el.after(pre);
scope.$watch(function() {
var attrs = {};
Array.prototype.slice.call(el[0].attributes, 0).forEach(function(item) {
if (item.name !== 'show-attrs') {
attrs[item.name] = item.value;
}
});
return attrs;
}, function(newAttrs, oldAttrs) {
pre.textContent = JSON.stringify(newAttrs, null, 2);
}, true);
}
});
</script>
</file>
</example>
##Common Accessibility Patterns
Accessibility best practices that apply to web apps in general also apply to Angular.
* [A11Y Project](http://a11yproject.com/)
* [WebAim](http://webaim.org/)
* [Using WAI-ARIA in HTML](http://www.w3.org/TR/2014/WD-aria-in-html-20140626/)
* [Apps For All: Coding Accessible Web Applications](https://shop.smashingmagazine.com/apps-for-all-coding-accessible-web-applications.html)
* **Text alternatives**: Add alternate text content to make visual information accessible using
[these W3C guidelines](http://www.w3.org/TR/html-alt-techniques/). The appropriate technique
depends on the specific markup but can be accomplished using offscreen spans, `aria-label` or
label elements, image `alt` attributes, `figure`/`figcaption` elements and more.
* **HTML Semantics**: If you're creating custom element directives, Web Components or HTML in
general, use native elements wherever possible to utilize built-in events and properties.
Alternatively, use ARIA to communicate semantic meaning. See [notes on ARIA use](http://www.w3.org/TR/aria-in-html/#notes-on-aria-use-in-html).
* **Focus management**: Guide the user around the app as views are appended/removed.
Focus should *never* be lost, as this causes unexpected behavior and much confusion (referred to
as "freak-out mode").
* **Announcing changes**: When filtering or other UI messaging happens away from the user's focus,
notify with [ARIA Live Regions](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions).
* **Color contrast and scale**: Make sure content is legible and interactive controls are usable
at all screen sizes. Consider configurable UI themes for people with color blindness, low vision
or other visual impairments.
* **Progressive enhancement**: Some users do not browse with JavaScript enabled or do not have
the latest browser. An accessible message about site requirements can inform users and improve
the experience.
## Additional Resources
* [Using ARIA in HTML](http://www.w3.org/TR/aria-in-html/)
* [AngularJS Accessibility at ngEurope](https://www.youtube.com/watch?v=dmYDggEgU-s&list=UUEGUP3TJJfMsEM_1y8iviSQ)
* [Testing with Screen Readers](http://webaim.org/articles/screenreader_testing/)
* [Chrome Accessibility Developer Tools](https://chrome.google.com/webstore/detail/accessibility-developer-t/fpkknkljclfencbdbgkenhalefipecmb?hl=en)
* [W3C Accessibility Testing](http://www.w3.org/wiki/Accessibility_testing)
* [WebAIM](http://webaim.org)
* [A11y Project](http://a11yproject.com)
+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
+7 -7
View File
@@ -56,10 +56,10 @@ Try out the Live Preview above, and then let's walk through the example and desc
This looks like normal HTML, with some new markup. In Angular, a file like this is called a
<a name="template">"{@link templates template}"</a>. When Angular starts your application, it parses and
processes this new markup from the template using the so called <a name="compiler">"{@link compiler compiler}"</a>.
processes this new markup from the template using the so-called <a name="compiler">"{@link compiler compiler}"</a>.
The loaded, transformed and rendered DOM is then called the <a name="view">"view"</a>.
The first kind of new markup are the so called <a name="directive">"{@link directive directives}"</a>.
The first kind of new markup are the so-called <a name="directive">"{@link directive directives}"</a>.
They apply special behavior to attributes or elements in the HTML. In the example above we use the
{@link ng.directive:ngApp `ng-app`} attribute, which is linked to a directive that automatically
initializes our application. Angular also defines a directive for the {@link ng.directive:input `input`}
@@ -89,7 +89,7 @@ A filter formats the value of an expression for display to the user.
In the example above, the filter {@link ng.filter:currency `currency`} formats a number
into an output that looks like money.
The important thing in the example is that angular provides _live_ bindings:
The important thing in the example is that Angular provides _live_ bindings:
Whenever the input values change, the value of the expressions are automatically
recalculated and the DOM is updated with their values.
The concept behind this is <a name="databinding">"{@link databinding two-way data binding}"</a>.
@@ -150,13 +150,13 @@ different currencies and also pay the invoice.
What changed?
First, there is a new JavaScript file that contains a so called <a name="controller">"{@link controller controller}"</a>.
First, there is a new JavaScript file that contains a so-called <a name="controller">"{@link controller controller}"</a>.
More exactly, the file contains a constructor function that creates the actual controller instance.
The purpose of controllers is to expose variables and functionality to expressions and directives.
Besides the new file that contains the controller code we also added a
Besides the new file that contains the controller code we also added an
{@link ng.directive:ngController `ng-controller`} directive to the HTML.
This directive tells angular that the new `InvoiceController` is responsible for the element with the directive
This directive tells Angular that the new `InvoiceController` is responsible for the element with the directive
and all of the element's children.
The syntax `InvoiceController as invoice` tells Angular to instantiate the controller
and save it in the variable `invoice` in the current scope.
@@ -263,7 +263,7 @@ services, ...) is created and wired using dependency injection. Within Angular,
the DI container is called the <a name="injector">"{@link di injector}"</a>.
To use DI, there needs to be a place where all the things that should work together are registered.
In Angular, this is the purpose of the so called <a name="module">"{@link module modules}"</a>.
In Angular, this is the purpose of the so-called <a name="module">"{@link module modules}"</a>.
When Angular starts, it will use the configuration of the module with the name defined by the `ng-app` directive,
including the configuration of all modules that this module depends on.
+2 -2
View File
@@ -27,8 +27,8 @@ Protractor is a [Node.js](http://nodejs.org) program, and runs end to end tests
written in JavaScript and run with node. Protractor uses [WebDriver](https://code.google.com/p/selenium/wiki/GettingStarted)
to control browsers and simulate user actions.
For more information on Protractor, view [getting started](https://github.com/angular/protractor/blob/master/docs/getting-started.md)
or the [api docs](https://github.com/angular/protractor/blob/master/docs/api.md).
For more information on Protractor, view [getting started](http://angular.github.io/protractor/#/getting-started)
or the [api docs](http://angular.github.io/protractor/#/api).
Protractor uses [Jasmine](http://jasmine.github.io/1.3/introduction.html) for its test syntax.
As in unit testing, a test file is comprised of one or
+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>
```
+3
View File
@@ -56,6 +56,9 @@ for other directives to augment its behavior.
Note that `novalidate` is used to disable browser's native form validation.
The value of `ngModel` won't be set unless it passes validation for the input field.
For example: inputs of type `email` must have a value in the form of `user@domain`.
# Using CSS classes
+3 -1
View File
@@ -41,9 +41,10 @@ In Angular applications, you move the job of filling page templates with data fr
### Other AngularJS Features
* **Animation:** {@link guide/animations Core concepts}, {@link ngAnimate ngAnimate API}, and [Animation in AngularJS 1.2](http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html)
* **Security:** {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
* **Security:** {@link guide/security Security Docs}, {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
* **Internationalization and Localization:** {@link guide/i18n Angular Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/)
* **Mobile:** {@link ngTouch Touch events}
* **Accessibility:** {@link guide/accessibility ngAria}
### Testing
@@ -107,6 +108,7 @@ This is a short list of libraries with specific support and documentation for wo
* [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller
* [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner
* [AngularJS : Novice to Ninja](http://www.amazon.in/AngularJS-Novice-Ninja-Sandeep-Panda/dp/0992279453) by Sandeep Panda
* [AngularJS UI Development](http://www.amazon.com/AngularJS-UI-Development-Amit-Ghart-ebook/dp/B00OXVAK7A) by Amit Gharat and Matthias Nehlsen
###Videos:
* [egghead.io](http://egghead.io/)
+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
+32
View File
@@ -41,3 +41,35 @@ The page should reload and the debug information should now be available.
For more see the docs pages on {@link ng.$compileProvider#debugInfoEnabled `$compileProvider`}
and {@link angular.reloadWithDebugInfo `angular.reloadWithDebugInfo`}.
## Strict DI Mode
Using strict di mode in your production application will throw errors when a injectable
function is not
{@link di#dependency-annotation annotated explicitly}. Strict di mode is intended to help
you make sure that your code will work when minified. However, it also will force you to
make sure that your injectable functions are explicitly annotated which will improve
angular's performance when injecting dependencies in your injectable functions because it
doesn't have to dynamically discover a function's dependencies. It is recommended to
automate the explicit annotation via a tool like
[ng-annotate](https://github.com/olov/ng-annotate) when you deploy to production (and enable
strict di mode)
To enable strict di mode, you have two options:
```html
<div ng-app="myApp" ng-strict-di>
<!-- your app here -->
</div>
```
or
```js
angular.bootstrap(document, ['myApp'], {
strictDi: true
});
```
For more information, see the
{@link di#using-strict-dependency-injection DI Guide}.
+12
View File
@@ -339,6 +339,18 @@ the dirty checking function must be efficient. Care should be taken that the dir
function does not do any DOM access, as DOM access is orders of magnitude slower than property
access on JavaScript object.
### Scope `$watch` Depths
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-scope-watch-strategies.png">
Dirty checking can be done with three strategies: By reference, by collection contents, and by value. The strategies differ in the kinds of changes they detect, and in their performance characteristics.
- Watching *by reference* ({@link
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener)`) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object, changes inside it are not detected. This is the most efficient stategy.
- Watching *collection contents* ({@link
ng.$rootScope.Scope#$watchCollection scope.$watchCollection} `(watchExpression, listener)`) detects changes that occur inside an array or an object: When items are added, removed, or reordered. The detection is shallow - it does not reach into nested collections. Watching collection contents is more expensive than watching by reference, because copies of the collection contents need to be maintained. However, the strategy attempts to minimize the amount of copying required.
- Watching *by value* ({@link
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener, true)`) detects any change in an arbitrarily nested data structure. It is the most powerful change detection strategy, but also the most expensive. A full traversal of the nested data structure is needed on each digest, and a full copy of it needs to be held in memory.
## Integration with the browser event loop
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-runtime.png">
+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
@@ -120,7 +120,7 @@ really is that easy to set up any functional, readable, end-to-end test.
### Running End to End Tests with Protractor
Even though the syntax of this test looks very much like our controller unit test written with
Jasmine, the end-to-end test uses APIs of [Protractor](https://github.com/angular/protractor). Read
about the Protractor APIs at https://github.com/angular/protractor/blob/master/docs/api.md.
about the Protractor APIs at http://angular.github.io/protractor/#/api.
Much like Karma is the test runner for unit tests, we use Protractor to run end-to-end tests.
Try it with `npm run protractor`. End-to-end tests are slow, so unlike with unit tests, Protractor
+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
+5 -3
View File
@@ -236,7 +236,9 @@ the response is received:
```
* We flush the request queue in the browser by calling `$httpBackend.flush()`. This causes the
promise returned by the `$http` service to be resolved with the trained response.
promise returned by the `$http` service to be resolved with the trained response. See
'Flushing HTTP requests' in the {@link ngMock.$httpBackend mock $httpBackend} documentation for
a full explanation of why this is necessary.
* We make the assertions, verifying that the phone model now exists on the scope.
@@ -256,8 +258,8 @@ You should now see the following output in the Karma tab:
# Experiments
* At the bottom of `index.html`, add a `<pre>{{phones | json}}</pre>` binding to see the list of phones
displayed in json format.
* At the bottom of `index.html`, add a `<pre>{{phones | filter:query | orderBy:orderProp | json}}</pre>`
binding to see the list of phones displayed in json format.
* In the `PhoneListCtrl` controller, pre-process the http response by limiting the number of phones
to the first 5 in the list. Use the following code in the `$http` callback:
+1 -1
View File
@@ -184,7 +184,7 @@ You can now rerun `npm run protractor` to see the tests run.
# Experiments
* Using the [Protractor API](https://github.com/angular/protractor/blob/master/docs/api.md),
* Using the [Protractor API](http://angular.github.io/protractor/#/api),
write a test that verifies that we display 4 thumbnail images on the Nexus S details page.
+4 -3
View File
@@ -9,8 +9,8 @@
In this final step, we will enhance our phonecat web application by attaching CSS and JavaScript
animations on top of the template code we created before.
* Used the `ngAnimate` to enable animations throughout the application.
* Common `ng` directives automatically trigger hooks for animations to tap into.
* We now use the `ngAnimate` module to enable animations throughout the application.
* We also use common `ng` directives to automatically trigger hooks for animations to tap into.
* When an animation is found then the animation will run in between the standard DOM operation that
is being issued on the element at the given time (e.g. inserting and removing nodes on
{@link ngRepeat `ngRepeat`} or adding and removing classes on
@@ -369,7 +369,8 @@ occur whenever the CSS class itself changes.
Whenever a new phone thumbnail is selected, the state changes and the `.active` CSS class is added
to the matching profile image and the animation plays.
Let's get started and tweak our HTML code on the `phone-detail.html` page first:
Let's get started and tweak our HTML code on the `phone-detail.html` page first. Notice that we
have changed the way we display our large image:
__`app/partials/phone-detail.html`.__
Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

+6 -6
View File
@@ -4810,7 +4810,7 @@
}
},
"protractor": {
"version": "1.3.1",
"version": "1.4.0",
"dependencies": {
"request": {
"version": "2.36.0",
@@ -4834,7 +4834,7 @@
"version": "0.12.1",
"dependencies": {
"punycode": {
"version": "1.3.1"
"version": "1.3.2"
}
}
},
@@ -4842,7 +4842,7 @@
"version": "0.1.4",
"dependencies": {
"combined-stream": {
"version": "0.0.5",
"version": "0.0.7",
"dependencies": {
"delayed-stream": {
"version": "0.0.5"
@@ -4897,7 +4897,7 @@
}
},
"selenium-webdriver": {
"version": "2.43.5",
"version": "2.44.0",
"dependencies": {
"tmp": {
"version": "0.0.24"
@@ -4906,7 +4906,7 @@
"version": "0.4.4",
"dependencies": {
"sax": {
"version": "0.6.0"
"version": "0.6.1"
},
"xmlbuilder": {
"version": "2.4.4",
@@ -4966,7 +4966,7 @@
"version": "1.0.0"
},
"source-map-support": {
"version": "0.2.7",
"version": "0.2.8",
"dependencies": {
"source-map": {
"version": "0.1.32",
+1 -1
View File
@@ -52,7 +52,7 @@
"marked": "~0.3.0",
"node-html-encoder": "0.0.2",
"promises-aplus-tests": "~2.1.0",
"protractor": "1.3.1",
"protractor": "1.4.0",
"q": "~1.0.0",
"q-io": "^1.10.9",
"qq": "^0.3.5",
+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)!");
}
}
+58 -14
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.
*
@@ -622,10 +622,17 @@
*
*
* @param {string|DOMElement} element Element or HTML string to compile into a template function.
* @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives.
* @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
*
* <div class="alert alert-error">
* **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
* e.g. will not use the right outer scope. Please pass the transclude function as a
* `parentBoundTranscludeFn` to the link function instead.
* </div>
*
* @param {number} maxPriority only apply directives lower than given priority (Only effects the
* root element(s), not their children)
* @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template
* @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
* (a DOM element/tree) to a scope. Where:
*
* * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
@@ -637,6 +644,19 @@
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
* * `scope` - is the current scope with which the linking function is working with.
*
* * `options` - An optional object hash with linking options. If `options` is provided, then the following
* keys may be used to control linking behavior:
*
* * `parentBoundTranscludeFn` - the transclude function made available to
* directives; if given, it will be passed through to the link functions of
* directives found in `element` during compilation.
* * `transcludeControllers` - an object hash with keys that map controller names
* to controller instances; if given, it will make the controllers
* available to directives.
* * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
* the cloned elements; only needed for transcludes that are allowed to contain non html
* elements (e.g. SVG elements). See also the directive.controller property.
*
* Calling the linking function returns the element of the template. It is either the original
* element passed in, or the clone of the element if the `cloneAttachFn` is provided.
*
@@ -1009,16 +1029,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// for each tuples
var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
for (var i=0; i<nbrUrisWith2parts; i++) {
var innerIdx = i*2;
for (var i = 0; i < nbrUrisWith2parts; i++) {
var innerIdx = i * 2;
// sanitize the uri
result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
// add the descriptor
result += (" " + trim(rawUris[innerIdx+1]));
result += (" " + trim(rawUris[innerIdx + 1]));
}
// split the last item into uri and descriptor
var lastTuple = trim(rawUris[i*2]).split(/\s/);
var lastTuple = trim(rawUris[i * 2]).split(/\s/);
// sanitize the last uri
result += $$sanitizeUri(trim(lastTuple[0]), true);
@@ -1155,8 +1175,22 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
maxPriority, ignoreDirective, previousCompileContext);
compile.$$addScopeClass($compileNodes);
var namespace = null;
return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement) {
return function publicLinkFn(scope, cloneConnectFn, options) {
assertArg(scope, 'scope');
options = options || {};
var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
transcludeControllers = options.transcludeControllers,
futureParentElement = options.futureParentElement;
// When `parentBoundTranscludeFn` is passed, it is a
// `controllersBoundTransclude` function (it was previously passed
// as `transclude` to directive.link) so we must unwrap it to get
// its `boundTranscludeFn`
if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
}
if (!namespace) {
namespace = detectNamespaceForChildElements(futureParentElement);
}
@@ -1198,7 +1232,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (!node) {
return 'html';
} else {
return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg': 'html';
return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html';
}
}
@@ -1326,7 +1360,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
transcludedScope.$$transcluded = true;
}
return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
return transcludeFn(transcludedScope, cloneFn, {
parentBoundTranscludeFn: previousBoundTranscludeFn,
transcludeControllers: controllers,
futureParentElement: futureParentElement
});
};
return boundTranscludeFn;
@@ -1794,7 +1832,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
isolateScope = scope.$new(true);
}
transcludeFn = boundTranscludeFn && controllersBoundTransclude;
if (boundTranscludeFn) {
// track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
// is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
transcludeFn = controllersBoundTransclude;
transcludeFn.$$boundTransclude = boundTranscludeFn;
}
if (controllerDirectives) {
// TODO: merge `controllers` and `elementControllers` into single object.
controllers = {};
@@ -2001,7 +2045,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var match = null;
if (hasDirectives.hasOwnProperty(name)) {
for (var directive, directives = $injector.get(name + Suffix),
i = 0, ii = directives.length; i<ii; i++) {
i = 0, ii = directives.length; i < ii; i++) {
try {
directive = directives[i];
if ((maxPriority === undefined || maxPriority > directive.priority) &&
@@ -2030,7 +2074,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function directiveIsMultiElement(name) {
if (hasDirectives.hasOwnProperty(name)) {
for (var directive, directives = $injector.get(name + Suffix),
i = 0, ii = directives.length; i<ii; i++) {
i = 0, ii = directives.length; i < ii; i++) {
directive = directives[i];
if (directive.multiElement) {
return true;
@@ -2249,7 +2293,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
case 'svg':
case 'math':
var wrapper = document.createElement('div');
wrapper.innerHTML = '<'+type+'>'+template+'</'+type+'>';
wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
return wrapper.childNodes[0].childNodes;
default:
return template;
+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.
*
+60 -32
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.
@@ -200,7 +205,7 @@ var inputType = {
/**
* @ngdoc input
* @name input[dateTimeLocal]
* @name input[datetime-local]
*
* @description
* Input with datetime validation and transformation. In browsers that do not yet support
@@ -585,9 +590,14 @@ var inputType = {
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
* maxlength.
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive.
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
* a RegExp found by evaluating the Angular expression given in the attribute value.
* If the expression evaluates to a RegExp object then this is used directly.
* If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
* characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
* @param {string=} ngChange Angular expression to be executed when input changes due to user
* interaction with the input element.
*
@@ -667,9 +677,14 @@ var inputType = {
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
* maxlength.
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive.
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
* a RegExp found by evaluating the Angular expression given in the attribute value.
* If the expression evaluates to a RegExp object then this is used directly.
* If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
* characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
* @param {string=} ngChange Angular expression to be executed when input changes due to user
* interaction with the input element.
*
@@ -750,9 +765,14 @@ var inputType = {
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
* maxlength.
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive.
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
* a RegExp found by evaluating the Angular expression given in the attribute value.
* If the expression evaluates to a RegExp object then this is used directly.
* If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$`
* characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`.
* @param {string=} ngChange Angular expression to be executed when input changes due to user
* interaction with the input element.
*
@@ -1063,8 +1083,8 @@ function createDateParser(regexp, mapping) {
// When a date is JSON'ified to wraps itself inside of an extra
// set of double quotes. This makes the date parsing code unable
// to match the date string and parse it as a date.
if (iso.charAt(0) == '"' && iso.charAt(iso.length-1) == '"') {
iso = iso.substring(1, iso.length-1);
if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') {
iso = iso.substring(1, iso.length - 1);
}
if (ISO_DATE_REGEXP.test(iso)) {
return new Date(iso);
@@ -1511,12 +1531,17 @@ var VALID_CLASS = 'ng-valid',
* @property {string} $viewValue Actual string value in the view.
* @property {*} $modelValue The value in the model that the control is bound to.
* @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
the control reads value from the DOM. The functions are called in array order, each passing the value
through to the next. The last return value is forwarded to the $validators collection.
Used to sanitize / convert the value.
Returning undefined from a parser means a parse error occurred. No $validators will
run and the 'ngModel' will not be updated until the parse error is resolved. The parse error is stored
in 'ngModel.$error.parse'.
the control reads value from the DOM. The functions are called in array order, each passing
its return value through to the next. The last return value is forwarded to the
{@link ngModel.NgModelController#$validators `$validators`} collection.
Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
`$viewValue`}.
Returning `undefined` from a parser means a parse error occurred. In that case,
no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
is set to `true`. The parse error is stored in `ngModel.$error.parse`.
*
* @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
@@ -1576,9 +1601,6 @@ var VALID_CLASS = 'ng-valid',
* };
* ```
*
* @param {string} name The name of the validator.
* @param {Function} validationFn The validation function that will be run.
*
* @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
* view value has changed. It is called with no arguments, and its return value is ignored.
* This can be used in place of additional $watches against the model value.
@@ -1592,6 +1614,7 @@ var VALID_CLASS = 'ng-valid',
* @property {boolean} $dirty True if user has already interacted with the control.
* @property {boolean} $valid True if there is no error.
* @property {boolean} $invalid True if at least one error on the control.
* @property {string} $name The name attribute of the control.
*
* @description
*
@@ -1643,7 +1666,7 @@ var VALID_CLASS = 'ng-valid',
// Listen for change events to enable binding
element.on('blur keyup change', function() {
scope.$apply(read);
scope.$evalAsync(read);
});
read(); // initialize
@@ -2292,7 +2315,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* - {@link input[email] email}
* - {@link input[url] url}
* - {@link input[date] date}
* - {@link input[dateTimeLocal] dateTimeLocal}
* - {@link input[datetime-local] datetime-local}
* - {@link input[time] time}
* - {@link input[month] month}
* - {@link input[week] week}
@@ -2579,7 +2602,7 @@ var patternDirective = function() {
var regexp, patternExp = attr.ngPattern || attr.pattern;
attr.$observe('pattern', function(regex) {
if (isString(regex) && regex.length > 0) {
regex = new RegExp(regex);
regex = new RegExp('^' + regex + '$');
}
if (regex && !regex.test) {
@@ -2772,12 +2795,17 @@ var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
* @name ngValue
*
* @description
* Binds the given expression to the value of `option` or `input[radio]`, so
* that when the element is selected, the `ngModel` of that element is set to
* Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`},
* so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to
* the bound value.
*
* `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as
* shown below.
* `ngValue` is useful when dynamically generating lists of radio buttons using
* {@link ngRepeat `ngRepeat`}, as shown below.
*
* Likewise, `ngValue` can be used to generate `<option>` elements for
* the {@link select `select`} element. In that case however, only strings are supported
* for the `value `attribute, so the resulting `ngModel` will always be a string.
* Support for `select` models with non-string values is available via `ngOptions`.
*
* @element input
* @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
@@ -2865,7 +2893,7 @@ var ngValueDirective = function() {
* `ngModelOptions` has an effect on the element it's declared on and its descendants.
*
* @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
* - `updateOn`: string specifying which event should be the input bound to. You can set several
* - `updateOn`: string specifying which event should the input be bound to. You can set several
* events using an space delimited list. There is a special event called `default` that
* matches the default events belonging of the control.
* - `debounce`: integer value which contains the debounce model update value in milliseconds. A
+5 -1
View File
@@ -53,7 +53,11 @@ forEach(
return {
restrict: 'A',
compile: function($element, attr) {
var fn = $parse(attr[directiveName]);
// We expose the powerful $event object on the scope that provides access to the Window,
// etc. that isn't protected by the fast paths in $parse. We explicitly request better
// checks at the cost of speed since event handler expressions are not executed as
// frequently as regular change detection.
var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
return function ngEventHandler(scope, element) {
element.on(eventName, function(event) {
var callback = function() {
+1 -1
View File
@@ -284,7 +284,7 @@ var ngIncludeFillContentDirective = ['$compile',
$compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
function namespaceAdaptedClone(clone) {
$element.append(clone);
}, undefined, undefined, $element);
}, {futureParentElement: $element});
return;
}
+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};
+5 -3
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.
*
@@ -483,7 +483,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
if (multiple) {
return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value)));
} else {
return viewValue == callExpression(compareValueFn, key, value);
return viewValue === callExpression(compareValueFn, key, value);
}
};
}
@@ -616,13 +616,14 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
lastElement = null; // start at the beginning
for (index = 0, length = optionGroup.length; index < length; index++) {
option = optionGroup[index];
if ((existingOption = existingOptions[index+1])) {
if ((existingOption = existingOptions[index + 1])) {
// reuse elements
lastElement = existingOption.element;
if (existingOption.label !== option.label) {
updateLabelMap(labelMap, existingOption.label, false);
updateLabelMap(labelMap, option.label, true);
lastElement.text(existingOption.label = option.label);
lastElement.prop('label', existingOption.label);
}
if (existingOption.id !== option.id) {
lastElement.val(existingOption.id = option.id);
@@ -652,6 +653,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
.val(option.id)
.prop('selected', option.selected)
.attr('selected', option.selected)
.prop('label', option.label)
.text(option.label);
}
+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);
+181 -168
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('}');
@@ -852,64 +818,85 @@ function setter(obj, path, setValue, fullExp) {
return setValue;
}
var getterFnCache = createMap();
var getterFnCacheDefault = createMap();
var getterFnCacheExpensive = createMap();
function isPossiblyDangerousMemberName(name) {
return name == 'constructor';
}
/**
* Implementation of the "Black Hole" variant from:
* - http://jsperf.com/angularjs-parse-getter/4
* - http://jsperf.com/path-evaluation-simplified/7
*/
function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) {
function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, expensiveChecks) {
ensureSafeMemberName(key0, fullExp);
ensureSafeMemberName(key1, fullExp);
ensureSafeMemberName(key2, fullExp);
ensureSafeMemberName(key3, fullExp);
ensureSafeMemberName(key4, fullExp);
var eso = function(o) {
return ensureSafeObject(o, fullExp);
};
var eso0 = (expensiveChecks || isPossiblyDangerousMemberName(key0)) ? eso : identity;
var eso1 = (expensiveChecks || isPossiblyDangerousMemberName(key1)) ? eso : identity;
var eso2 = (expensiveChecks || isPossiblyDangerousMemberName(key2)) ? eso : identity;
var eso3 = (expensiveChecks || isPossiblyDangerousMemberName(key3)) ? eso : identity;
var eso4 = (expensiveChecks || isPossiblyDangerousMemberName(key4)) ? eso : identity;
return function cspSafeGetter(scope, locals) {
var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
if (pathVal == null) return pathVal;
pathVal = pathVal[key0];
pathVal = eso0(pathVal[key0]);
if (!key1) return pathVal;
if (pathVal == null) return undefined;
pathVal = pathVal[key1];
pathVal = eso1(pathVal[key1]);
if (!key2) return pathVal;
if (pathVal == null) return undefined;
pathVal = pathVal[key2];
pathVal = eso2(pathVal[key2]);
if (!key3) return pathVal;
if (pathVal == null) return undefined;
pathVal = pathVal[key3];
pathVal = eso3(pathVal[key3]);
if (!key4) return pathVal;
if (pathVal == null) return undefined;
pathVal = pathVal[key4];
pathVal = eso4(pathVal[key4]);
return pathVal;
};
}
function getterFn(path, options, fullExp) {
var fn = getterFnCache[path];
function getterFnWithEnsureSafeObject(fn, fullExpression) {
return function(s, l) {
return fn(s, l, ensureSafeObject, fullExpression);
};
}
function getterFn(path, options, fullExp) {
var expensiveChecks = options.expensiveChecks;
var getterFnCache = (expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault);
var fn = getterFnCache[path];
if (fn) return fn;
var pathKeys = path.split('.'),
pathKeysLength = pathKeys.length;
// http://jsperf.com/angularjs-parse-getter/6
if (options.csp) {
if (pathKeysLength < 6) {
fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp);
fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, expensiveChecks);
} else {
fn = function cspSafeGetter(scope, locals) {
var i = 0, val;
do {
val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++],
pathKeys[i++], fullExp)(scope, locals);
pathKeys[i++], fullExp, expensiveChecks)(scope, locals);
locals = undefined; // clear after first iteration
scope = val;
@@ -919,22 +906,33 @@ function getterFn(path, options, fullExp) {
}
} else {
var code = '';
if (expensiveChecks) {
code += 's = eso(s, fe);\nl = eso(l, fe);\n';
}
var needsEnsureSafeObject = expensiveChecks;
forEach(pathKeys, function(key, index) {
ensureSafeMemberName(key, fullExp);
code += 'if(s == null) return undefined;\n' +
's='+ (index
var lookupJs = (index
// we simply dereference 's' on any .dot notation
? 's'
// but if we are first then we check locals first, and if so read it first
: '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key + ';\n';
: '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key;
if (expensiveChecks || isPossiblyDangerousMemberName(key)) {
lookupJs = 'eso(' + lookupJs + ', fe)';
needsEnsureSafeObject = true;
}
code += 'if(s == null) return undefined;\n' +
's=' + lookupJs + ';\n';
});
code += 'return s;';
/* jshint -W054 */
var evaledFnGetter = new Function('s', 'l', code); // s=scope, l=locals
var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject
/* jshint +W054 */
evaledFnGetter.toString = valueFn(code);
if (needsEnsureSafeObject) {
evaledFnGetter = getterFnWithEnsureSafeObject(evaledFnGetter, fullExp);
}
fn = evaledFnGetter;
}
@@ -1004,15 +1002,20 @@ function getValueOf(value) {
* service.
*/
function $ParseProvider() {
var cache = createMap();
var cacheDefault = createMap();
var cacheExpensive = createMap();
var $parseOptions = {
csp: false
};
this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
$parseOptions.csp = $sniffer.csp;
var $parseOptions = {
csp: $sniffer.csp,
expensiveChecks: false
},
$parseOptionsExpensive = {
csp: $sniffer.csp,
expensiveChecks: true
};
function wrapSharedExpression(exp) {
var wrapped = exp;
@@ -1029,13 +1032,14 @@ function $ParseProvider() {
return wrapped;
}
return function $parse(exp, interceptorFn) {
return function $parse(exp, interceptorFn, expensiveChecks) {
var parsedExpression, oneTime, cacheKey;
switch (typeof exp) {
case 'string':
cacheKey = exp = exp.trim();
var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
parsedExpression = cache[cacheKey];
if (!parsedExpression) {
@@ -1044,8 +1048,9 @@ function $ParseProvider() {
exp = exp.substring(2);
}
var lexer = new Lexer($parseOptions);
var parser = new Parser(lexer, $filter, $parseOptions);
var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
var lexer = new Lexer(parseOptions);
var parser = new Parser(lexer, $filter, parseOptions);
parsedExpression = parser.parse(exp);
if (parsedExpression.constant) {
@@ -1212,8 +1217,16 @@ function $ParseProvider() {
function addInterceptor(parsedExpression, interceptorFn) {
if (!interceptorFn) return parsedExpression;
var watchDelegate = parsedExpression.$$watchDelegate;
var fn = function interceptedExpression(scope, locals) {
var regularWatch =
watchDelegate !== oneTimeLiteralWatchDelegate &&
watchDelegate !== oneTimeWatchDelegate;
var fn = regularWatch ? function regularInterceptedExpression(scope, locals) {
var value = parsedExpression(scope, locals);
return interceptorFn(value, scope, locals);
} : function oneTimeInterceptedExpression(scope, locals) {
var value = parsedExpression(scope, locals);
var result = interceptorFn(value, scope, locals);
// we only return the interceptor's result if the
+6 -6
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
+15 -8
View File
@@ -113,6 +113,10 @@ function $RootScopeProvider() {
expect(parent.salutation).toEqual('Hello');
* ```
*
* When interacting with `Scope` in tests, additional helper methods are available on the
* instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
* details.
*
*
* @param {Object.<string, function()>=} providers Map of service factory which need to be
* provided for the current scope. Defaults to {@link ng}.
@@ -551,6 +555,9 @@ function $RootScopeProvider() {
newValue = _value;
var newLength, key, bothNaN, newItem, oldItem;
// If the new value is undefined, then return undefined as the watch may be a one-time watch
if (isUndefined(newValue)) return;
if (!isObject(newValue)) { // if primitive
if (oldValue !== newValue) {
oldValue = newValue;
@@ -766,11 +773,11 @@ function $RootScopeProvider() {
if (ttl < 5) {
logIdx = 4 - ttl;
if (!watchLog[logIdx]) watchLog[logIdx] = [];
logMsg = (isFunction(watch.exp))
? 'fn: ' + (watch.exp.name || watch.exp.toString())
: watch.exp;
logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
watchLog[logIdx].push(logMsg);
watchLog[logIdx].push({
msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
newVal: value,
oldVal: last
});
}
} else if (watch === lastDirtyWatch) {
// If the most recently dirty watcher is now clean, short circuit since the remaining watchers
@@ -803,7 +810,7 @@ function $RootScopeProvider() {
throw $rootScopeMinErr('infdig',
'{0} $digest() iterations reached. Aborting!\n' +
'Watchers fired in the last 5 iterations: {1}',
TTL, toJson(watchLog));
TTL, watchLog);
}
} while (dirty || asyncQueue.length);
@@ -1154,7 +1161,7 @@ function $RootScopeProvider() {
do {
namedListeners = scope.$$listeners[name] || empty;
event.currentScope = scope;
for (i=0, length=namedListeners.length; i<length; i++) {
for (i = 0, length = namedListeners.length; i < length; i++) {
// if listeners were deregistered, defragment the array
if (!namedListeners[i]) {
@@ -1228,7 +1235,7 @@ function $RootScopeProvider() {
while ((current = next)) {
event.currentScope = current;
listeners = current.$$listeners[name] || [];
for (i=0, length = listeners.length; i<length; i++) {
for (i = 0, length = listeners.length; i < length; i++) {
// if listeners were deregistered, defragment the array
if (!listeners[i]) {
listeners.splice(i, 1);
+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);
+93 -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.
@@ -238,5 +298,16 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
.directive('ngDisabled', ['$aria', function($aria) {
return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
}])
.directive('ngMessages', function() {
return {
restrict: 'A',
require: '?ngMessages',
link: function(scope, elem, attr, ngMessages) {
if (!elem.attr('aria-live')) {
elem.attr('aria-live', 'assertive');
}
}
};
})
.directive('ngClick', ngAriaTabindex)
.directive('ngDblclick', ngAriaTabindex);
+107 -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]();
}
@@ -1823,6 +1827,7 @@ angular.module('ngMock', ['ng']).provider({
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
}]);
/**
@@ -2031,6 +2036,92 @@ angular.mock.e2e.$httpBackendDecorator =
['$rootScope', '$delegate', '$browser', createHttpBackendMock];
/**
* @ngdoc type
* @name $rootScope.Scope
* @module ngMock
* @description
* {@link ng.$rootScope.Scope Scope} type decorated with helper methods useful for testing. These
* methods are automatically available on any {@link ng.$rootScope.Scope Scope} instance when
* `ngMock` module is loaded.
*
* In addition to all the regular `Scope` methods, the following helper methods are available:
*/
angular.mock.$RootScopeDecorator = function($delegate) {
var $rootScopePrototype = Object.getPrototypeOf($delegate);
$rootScopePrototype.$countChildScopes = countChildScopes;
$rootScopePrototype.$countWatchers = countWatchers;
return $delegate;
// ------------------------------------------------------------------------------------------ //
/**
* @ngdoc method
* @name $rootScope.Scope#$countChildScopes
* @module ngMock
* @description
* Counts all the direct and indirect child scopes of the current scope.
*
* The current scope is excluded from the count. The count includes all isolate child scopes.
*
* @returns {number} Total number of child scopes.
*/
function countChildScopes() {
// jshint validthis: true
var count = 0; // exclude the current scope
var pendingChildHeads = [this.$$childHead];
var currentScope;
while (pendingChildHeads.length) {
currentScope = pendingChildHeads.shift();
while (currentScope) {
count += 1;
pendingChildHeads.push(currentScope.$$childHead);
currentScope = currentScope.$$nextSibling;
}
}
return count;
}
/**
* @ngdoc method
* @name $rootScope.Scope#$countWatchers
* @module ngMock
* @description
* Counts all the watchers of direct and indirect child scopes of the current scope.
*
* The watchers of the current scope are included in the count and so are all the watchers of
* isolate child scopes.
*
* @returns {number} Total number of watchers.
*/
function countWatchers() {
// jshint validthis: true
var count = this.$$watchers ? this.$$watchers.length : 0; // include the current scope
var pendingChildHeads = [this.$$childHead];
var currentScope;
while (pendingChildHeads.length) {
currentScope = pendingChildHeads.shift();
while (currentScope) {
count += currentScope.$$watchers ? currentScope.$$watchers.length : 0;
pendingChildHeads.push(currentScope.$$childHead);
currentScope = currentScope.$$nextSibling;
}
}
return count;
}
};
if (window.jasmine || window.mocha) {
var currentSpec = null,
+27 -9
View File
@@ -141,27 +141,45 @@ function $RouteProvider() {
* Adds a new route definition to the `$route` service.
*/
this.when = function(path, route) {
//copy original route object to preserve params inherited from proto chain
var routeCopy = angular.copy(route);
if (angular.isUndefined(routeCopy.reloadOnSearch)) {
routeCopy.reloadOnSearch = true;
}
if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) {
routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch;
}
routes[path] = angular.extend(
{reloadOnSearch: true},
route,
path && pathRegExp(path, route)
routeCopy,
path && pathRegExp(path, routeCopy)
);
// create redirection for trailing slashes
if (path) {
var redirectPath = (path[path.length-1] == '/')
? path.substr(0, path.length-1)
: path +'/';
var redirectPath = (path[path.length - 1] == '/')
? path.substr(0, path.length - 1)
: path + '/';
routes[redirectPath] = angular.extend(
{redirectTo: path},
pathRegExp(redirectPath, route)
pathRegExp(redirectPath, routeCopy)
);
}
return this;
};
/**
* @ngdoc property
* @name $routeProvider#caseInsensitiveMatch
* @description
*
* A boolean property indicating if routes defined
* using this provider should be matched using a case sensitive
* algorithm. Defaults to `false`.
*/
this.caseInsensitiveMatch = false;
/**
* @param path {string} path
* @param opts {Object} options
@@ -437,7 +455,7 @@ function $RouteProvider() {
* {@link ng.$location $location} hasn't changed.
*
* As a result of that, {@link ngRoute.directive:ngView ngView}
* creates new scope, reinstantiates the controller.
* creates new scope and reinstantiates the controller.
*/
reload: function() {
forceReload = true;
@@ -630,7 +648,7 @@ function $RouteProvider() {
*/
function interpolate(string, params) {
var result = [];
angular.forEach((string||'').split(':'), function(segment, i) {
angular.forEach((string || '').split(':'), function(segment, i) {
if (i === 0) {
result.push(segment);
} else {
+19 -19
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);
}
+162 -49
View File
@@ -26,6 +26,17 @@ describe('$compile', function() {
return !!d.toString().match(/SVGForeignObject/);
}
function getChildScopes(scope) {
var children = [];
if (!scope.$$childHead) { return children; }
var childScope = scope.$$childHead;
do {
children.push(childScope);
children = children.concat(getChildScopes(childScope));
} while ((childScope = childScope.$$nextSibling));
return children;
}
var element, directive, $compile, $rootScope;
beforeEach(module(provideLog, function($provide, $compileProvider) {
@@ -264,7 +275,7 @@ describe('$compile', function() {
}));
// NOTE: This test may be redundant.
it('should handle custom svg containers that transclude to foreignObject'+
it('should handle custom svg containers that transclude to foreignObject' +
' that transclude to custom svg containers that transclude to custom elements', inject(function() {
element = jqLite('<div><svg-container>' +
'<my-foreign-object><svg-container><svg-circle></svg-circle></svg-container></my-foreign-object>' +
@@ -289,7 +300,7 @@ describe('$compile', function() {
}));
it('should support directives with SVG templates and a slow url '+
it('should support directives with SVG templates and a slow url ' +
'that are stamped out later by a transcluding directive', function() {
module(function() {
directive('svgCircleUrl', valueFn({
@@ -344,8 +355,8 @@ describe('$compile', function() {
it('should not wrap root whitespace text nodes in spans', function() {
element = jqLite(
'<div> <div>A</div>\n '+ // The spaces and newlines here should not get wrapped
'<div>B</div>C\t\n '+ // The "C", tabs and spaces here will be wrapped
'<div> <div>A</div>\n ' + // The spaces and newlines here should not get wrapped
'<div>B</div>C\t\n ' + // The "C", tabs and spaces here will be wrapped
'</div>');
$compile(element.contents())($rootScope);
var spans = element.find('span');
@@ -1576,7 +1587,7 @@ describe('$compile', function() {
expect(function() {
$compile('<div><div class="sync async"></div></div>');
$httpBackend.flush();
}).toThrowMinErr('$compile', 'multidir', 'Multiple directives [async, sync] asking for template on: '+
}).toThrowMinErr('$compile', 'multidir', 'Multiple directives [async, sync] asking for template on: ' +
'<div class="sync async">');
});
});
@@ -2968,8 +2979,8 @@ describe('$compile', function() {
});
inject(function($compile, $rootScope) {
element = $compile('<div><div ng-repeat="i in items">'+
'<span some="id_{{i.id}}" observer></span>'+
element = $compile('<div><div ng-repeat="i in items">' +
'<span some="id_{{i.id}}" observer></span>' +
'</div></div>')($rootScope);
$rootScope.$apply(function() {
@@ -3356,6 +3367,31 @@ describe('$compile', function() {
});
});
it('should continue with a digets cycle when there is a two-way binding from the child to the parent', function() {
module(function() {
directive('hello', function() {
return {
restrict: 'E',
scope: { greeting: '=' },
template: '<button ng-click="setGreeting()">Say hi!</button>',
link: function(scope) {
scope.setGreeting = function() { scope.greeting = 'Hello!'; };
}
};
});
});
inject(function($rootScope) {
compile('<div>' +
'<p>{{greeting}}</p>' +
'<div><hello greeting="greeting"></hello></div>' +
'</div>');
$rootScope.$digest();
browserTrigger(element.find('button'), 'click');
expect(element.find('p').text()).toBe('Hello!');
});
});
});
@@ -3522,7 +3558,7 @@ describe('$compile', function() {
function test(literalString, literalValue) {
compile('<div><span my-component reference="'+literalString+'">');
compile('<div><span my-component reference="' + literalString + '">');
$rootScope.$apply();
expect(componentScope.reference).toBe(literalValue);
@@ -4299,7 +4335,7 @@ describe('$compile', function() {
expect(asyncCtrlSpy).not.toHaveBeenCalled();
$templateCache.put('myDirectiveAsync.html', '<div>Hello!</div>');
element = $compile('<div>'+
element = $compile('<div>' +
'<span xmy-directive-sync></span>' +
'<span my-directive-async></span>' +
'</div>')($rootScope);
@@ -5111,7 +5147,7 @@ describe('$compile', function() {
},
link: function(scope, el, attr, ctrl, $transclude) {
var i;
for (i=0; i<cloneCount; i++) {
for (i = 0; i < cloneCount; i++) {
$transclude(cloneAttach);
}
@@ -5127,7 +5163,7 @@ describe('$compile', function() {
expect(transcludeCtrl).toBeDefined();
expect(element.data('$transcludeController')).toBe(transcludeCtrl);
for (i=0; i<cloneCount; i++) {
for (i = 0; i < cloneCount; i++) {
expect(children.eq(i).data('$transcludeController')).toBeUndefined();
}
});
@@ -5209,26 +5245,103 @@ describe('$compile', function() {
});
// see issue https://github.com/angular/angular.js/issues/9413
describe('passing a parent bound transclude function to the link ' +
'function returned from `$compile`', function() {
beforeEach(module(function() {
directive('lazyCompile', function($compile) {
return {
compile: function(tElement, tAttrs) {
var content = tElement.contents();
tElement.empty();
return function(scope, element, attrs, ctrls, transcludeFn) {
element.append(content);
$compile(content)(scope, undefined, {
parentBoundTranscludeFn: transcludeFn
});
};
}
};
});
directive('toggle', valueFn({
scope: {t: '=toggle'},
transclude: true,
template: '<div ng-if="t"><lazy-compile><div ng-transclude></div></lazy-compile></div>'
}));
}));
it('should preserve the bound scope', function() {
inject(function($compile, $rootScope) {
element = $compile(
'<div>' +
'<div ng-init="outer=true"></div>' +
'<div toggle="t">' +
'<span ng-if="outer">Success</span><span ng-if="!outer">Error</span>' +
'</div>' +
'</div>')($rootScope);
$rootScope.$apply('t = false');
expect($rootScope.$countChildScopes()).toBe(1);
expect(element.text()).toBe('');
$rootScope.$apply('t = true');
expect($rootScope.$countChildScopes()).toBe(4);
expect(element.text()).toBe('Success');
$rootScope.$apply('t = false');
expect($rootScope.$countChildScopes()).toBe(1);
expect(element.text()).toBe('');
$rootScope.$apply('t = true');
expect($rootScope.$countChildScopes()).toBe(4);
expect(element.text()).toBe('Success');
});
});
it('should preserve the bound scope when using recursive transclusion', function() {
directive('recursiveTransclude', valueFn({
transclude: true,
template: '<div><lazy-compile><div ng-transclude></div></lazy-compile></div>'
}));
inject(function($compile, $rootScope) {
element = $compile(
'<div>' +
'<div ng-init="outer=true"></div>' +
'<div toggle="t">' +
'<div recursive-transclude>' +
'<span ng-if="outer">Success</span><span ng-if="!outer">Error</span>' +
'</div>' +
'</div>' +
'</div>')($rootScope);
$rootScope.$apply('t = false');
expect($rootScope.$countChildScopes()).toBe(1);
expect(element.text()).toBe('');
$rootScope.$apply('t = true');
expect($rootScope.$countChildScopes()).toBe(4);
expect(element.text()).toBe('Success');
$rootScope.$apply('t = false');
expect($rootScope.$countChildScopes()).toBe(1);
expect(element.text()).toBe('');
$rootScope.$apply('t = true');
expect($rootScope.$countChildScopes()).toBe(4);
expect(element.text()).toBe('Success');
});
});
});
// see issue https://github.com/angular/angular.js/issues/9095
describe('removing a transcluded element', function() {
function countScopes($rootScope) {
return [$rootScope].concat(
getChildScopes($rootScope)
).length;
}
function getChildScopes(scope) {
var children = [];
if (!scope.$$childHead) { return children; }
var childScope = scope.$$childHead;
do {
children.push(childScope);
children = children.concat(getChildScopes(childScope));
} while ((childScope = childScope.$$nextSibling));
return children;
}
beforeEach(module(function() {
directive('toggle', function() {
return {
@@ -5251,22 +5364,22 @@ describe('$compile', function() {
$rootScope.$apply('t = true');
expect(element.text()).toContain('msg-1');
// Expected scopes: $rootScope, ngIf, transclusion, ngRepeat
expect(countScopes($rootScope)).toEqual(4);
expect($rootScope.$countChildScopes()).toBe(3);
$rootScope.$apply('t = false');
expect(element.text()).not.toContain('msg-1');
// Expected scopes: $rootScope
expect(countScopes($rootScope)).toEqual(1);
expect($rootScope.$countChildScopes()).toBe(0);
$rootScope.$apply('t = true');
expect(element.text()).toContain('msg-1');
// Expected scopes: $rootScope, ngIf, transclusion, ngRepeat
expect(countScopes($rootScope)).toEqual(4);
expect($rootScope.$countChildScopes()).toBe(3);
$rootScope.$apply('t = false');
expect(element.text()).not.toContain('msg-1');
// Expected scopes: $rootScope
expect(countScopes($rootScope)).toEqual(1);
expect($rootScope.$countChildScopes()).toBe(0);
}));
@@ -5283,22 +5396,22 @@ describe('$compile', function() {
$rootScope.$apply('t = true');
expect(element.text()).toContain('msg-1msg-1');
// Expected scopes: $rootScope, ngIf, transclusion, ngRepeat
expect(countScopes($rootScope)).toEqual(4);
expect($rootScope.$countChildScopes()).toBe(3);
$rootScope.$apply('t = false');
expect(element.text()).not.toContain('msg-1msg-1');
// Expected scopes: $rootScope
expect(countScopes($rootScope)).toEqual(1);
expect($rootScope.$countChildScopes()).toBe(0);
$rootScope.$apply('t = true');
expect(element.text()).toContain('msg-1msg-1');
// Expected scopes: $rootScope, ngIf, transclusion, ngRepeat
expect(countScopes($rootScope)).toEqual(4);
expect($rootScope.$countChildScopes()).toBe(3);
$rootScope.$apply('t = false');
expect(element.text()).not.toContain('msg-1msg-1');
// Expected scopes: $rootScope
expect(countScopes($rootScope)).toEqual(1);
expect($rootScope.$countChildScopes()).toBe(0);
}));
@@ -5314,22 +5427,22 @@ describe('$compile', function() {
$rootScope.$apply('t = true');
expect(element.html()).toContain('some comment');
// Expected scopes: $rootScope, ngIf, transclusion
expect(countScopes($rootScope)).toEqual(3);
expect($rootScope.$countChildScopes()).toBe(2);
$rootScope.$apply('t = false');
expect(element.html()).not.toContain('some comment');
// Expected scopes: $rootScope
expect(countScopes($rootScope)).toEqual(1);
expect($rootScope.$countChildScopes()).toBe(0);
$rootScope.$apply('t = true');
expect(element.html()).toContain('some comment');
// Expected scopes: $rootScope, ngIf, transclusion
expect(countScopes($rootScope)).toEqual(3);
expect($rootScope.$countChildScopes()).toBe(2);
$rootScope.$apply('t = false');
expect(element.html()).not.toContain('some comment');
// Expected scopes: $rootScope
expect(countScopes($rootScope)).toEqual(1);
expect($rootScope.$countChildScopes()).toBe(0);
}));
it('should not leak the transclude scope if the transcluded contains only text nodes',
@@ -5344,22 +5457,22 @@ describe('$compile', function() {
$rootScope.$apply('t = true');
expect(element.html()).toContain('some text');
// Expected scopes: $rootScope, ngIf, transclusion
expect(countScopes($rootScope)).toEqual(3);
expect($rootScope.$countChildScopes()).toBe(2);
$rootScope.$apply('t = false');
expect(element.html()).not.toContain('some text');
// Expected scopes: $rootScope
expect(countScopes($rootScope)).toEqual(1);
expect($rootScope.$countChildScopes()).toBe(0);
$rootScope.$apply('t = true');
expect(element.html()).toContain('some text');
// Expected scopes: $rootScope, ngIf, transclusion
expect(countScopes($rootScope)).toEqual(3);
expect($rootScope.$countChildScopes()).toBe(2);
$rootScope.$apply('t = false');
expect(element.html()).not.toContain('some text');
// Expected scopes: $rootScope
expect(countScopes($rootScope)).toEqual(1);
expect($rootScope.$countChildScopes()).toBe(0);
}));
it('should mark as destroyed all sub scopes of the scope being destroyed',
@@ -5787,7 +5900,7 @@ describe('$compile', function() {
},
link: function(scope, el, attr, ctrl, $transclude) {
var i;
for (i=0; i<cloneCount; i++) {
for (i = 0; i < cloneCount; i++) {
$transclude(cloneAttach);
}
@@ -5800,7 +5913,7 @@ describe('$compile', function() {
inject(function($compile) {
element = $compile('<div><div transclude></div></div>')($rootScope);
var children = element.children(), i;
for (i=0; i<cloneCount; i++) {
for (i = 0; i < cloneCount; i++) {
expect(children.eq(i).data('$transcludeController')).toBe(transcludeCtrl);
}
});
@@ -5863,7 +5976,7 @@ describe('$compile', function() {
return {
transclude: 'element',
link: function(scope, element, attr, controllers, transclude) {
log('innerAgain:'+lowercase(nodeName_(element))+':'+trim(element[0].data));
log('innerAgain:' + lowercase(nodeName_(element)) + ':' + trim(element[0].data));
transclude(scope, function(clone) {
element.parent().append(clone);
});
@@ -5875,7 +5988,7 @@ describe('$compile', function() {
replace: true,
templateUrl: 'inner.html',
link: function(scope, element) {
log('inner:'+lowercase(nodeName_(element))+':'+trim(element[0].data));
log('inner:' + lowercase(nodeName_(element)) + ':' + trim(element[0].data));
}
};
});
@@ -5883,7 +5996,7 @@ describe('$compile', function() {
return {
transclude: 'element',
link: function(scope, element, attrs, controllers, transclude) {
log('outer:'+lowercase(nodeName_(element))+':'+trim(element[0].data));
log('outer:' + lowercase(nodeName_(element)) + ':' + trim(element[0].data));
transclude(scope, function(clone) {
element.parent().append(clone);
});
+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}}]");
}));
+19
View File
@@ -90,6 +90,25 @@ describe('event directives', function() {
});
describe('security', function() {
it('should allow access to the $event object', inject(function($rootScope, $compile) {
var scope = $rootScope.$new();
element = $compile('<button ng-click="e = $event">BTN</button>')(scope);
element.triggerHandler('click');
expect(scope.e.target).toBe(element[0]);
}));
it('should block access to DOM nodes (e.g. exposed via $event)', inject(function($rootScope, $compile) {
var scope = $rootScope.$new();
element = $compile('<button ng-click="e = $event.target">BTN</button>')(scope);
expect(function() {
element.triggerHandler('click');
}).toThrowMinErr(
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is disallowed! ' +
'Expression: e = $event.target');
}));
});
describe('blur', function() {
describe('call the listener asynchronously during $apply', function() {
+82 -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');
});
@@ -1629,6 +1666,20 @@ describe('select', function() {
expect(element.val()).toEqual('?');
expect(element.find('option').eq(0).attr('selected')).toEqual('selected');
});
it('should select the correct option for selectAs and falsy values', function() {
scope.values = [{value: 0, label: 'zero'}, {value: 1, label: 'one'}];
scope.selected = '';
createSelect({
'ng-model': 'selected',
'ng-options': 'option.value as option.label for option in values'
});
var option = element.find('option').eq(0);
expect(option.val()).toBe('?');
expect(option.text()).toBe('');
});
});
@@ -2178,8 +2229,8 @@ describe('select', function() {
it('should throw an exception if an option value interpolates to "hasOwnProperty"', function() {
scope.hasOwnPropertyOption = "hasOwnProperty";
expect(function() {
compile('<select ng-model="x">'+
'<option>{{hasOwnPropertyOption}}</option>'+
compile('<select ng-model="x">' +
'<option>{{hasOwnPropertyOption}}</option>' +
'</select>');
}).toThrowMinErr('ng','badname', 'hasOwnProperty is not a valid "option value" name');
});
+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() {
+174 -88
View File
@@ -3,9 +3,11 @@
describe('parser', function() {
beforeEach(function() {
/* global getterFnCache: true */
// clear cache
getterFnCache = createMap();
/* global getterFnCacheDefault: true */
/* global getterFnCacheExpensive: true */
// clear caches
getterFnCacheDefault = createMap();
getterFnCacheExpensive = createMap();
});
@@ -20,11 +22,30 @@ describe('parser', function() {
};
});
it('should only match number chars with isNumber', function() {
expect(Lexer.prototype.isNumber('0')).toBe(true);
expect(Lexer.prototype.isNumber('')).toBeFalsy();
expect(Lexer.prototype.isNumber(' ')).toBeFalsy();
expect(Lexer.prototype.isNumber(0)).toBeFalsy();
expect(Lexer.prototype.isNumber(false)).toBeFalsy();
expect(Lexer.prototype.isNumber(true)).toBeFalsy();
expect(Lexer.prototype.isNumber(undefined)).toBeFalsy();
expect(Lexer.prototype.isNumber(null)).toBeFalsy();
});
it('should tokenize a string', function() {
var tokens = lex("a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\"");
var i = 0;
expect(tokens[i].index).toEqual(0);
expect(tokens[i].text).toEqual('a.bc');
expect(tokens[i].text).toEqual('a');
i++;
expect(tokens[i].index).toEqual(1);
expect(tokens[i].text).toEqual('.');
i++;
expect(tokens[i].index).toEqual(2);
expect(tokens[i].text).toEqual('bc');
i++;
expect(tokens[i].index).toEqual(4);
@@ -32,7 +53,9 @@ describe('parser', function() {
i++;
expect(tokens[i].index).toEqual(5);
expect(tokens[i].text).toEqual(22);
expect(tokens[i].text).toEqual('22');
expect(tokens[i].value).toEqual(22);
expect(tokens[i].constant).toEqual(true);
i++;
expect(tokens[i].index).toEqual(7);
@@ -44,7 +67,9 @@ describe('parser', function() {
i++;
expect(tokens[i].index).toEqual(9);
expect(tokens[i].text).toEqual(1.3);
expect(tokens[i].text).toEqual('1.3');
expect(tokens[i].value).toEqual(1.3);
expect(tokens[i].constant).toEqual(true);
i++;
expect(tokens[i].index).toEqual(12);
@@ -60,7 +85,7 @@ describe('parser', function() {
i++;
expect(tokens[i].index).toEqual(15);
expect(tokens[i].string).toEqual("a'c");
expect(tokens[i].value).toEqual("a'c");
i++;
expect(tokens[i].index).toEqual(21);
@@ -68,14 +93,15 @@ describe('parser', function() {
i++;
expect(tokens[i].index).toEqual(22);
expect(tokens[i].string).toEqual('d"e');
expect(tokens[i].value).toEqual('d"e');
});
it('should tokenize identifiers with spaces after dots', function() {
var tokens = lex('foo. bar');
expect(tokens[0].text).toEqual('foo');
expect(tokens[1].text).toEqual('.');
expect(tokens[2].text).toEqual('bar');
it('should tokenize identifiers with spaces around dots the same as without spaces', function() {
function getText(t) { return t.text; }
var spaces = lex('foo. bar . baz').map(getText);
var noSpaces = lex('foo.bar.baz').map(getText);
expect(spaces).toEqual(noSpaces);
});
it('should tokenize undefined', function() {
@@ -83,7 +109,6 @@ describe('parser', function() {
var i = 0;
expect(tokens[i].index).toEqual(0);
expect(tokens[i].text).toEqual('undefined');
expect(undefined).toEqual(tokens[i].fn());
});
it('should tokenize quoted string', function() {
@@ -91,23 +116,23 @@ describe('parser', function() {
var tokens = lex(str);
expect(tokens[1].index).toEqual(1);
expect(tokens[1].string).toEqual("'");
expect(tokens[1].value).toEqual("'");
expect(tokens[3].index).toEqual(7);
expect(tokens[3].string).toEqual('"');
expect(tokens[3].value).toEqual('"');
});
it('should tokenize escaped quoted string', function() {
var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
var tokens = lex(str);
expect(tokens[0].string).toEqual('"\n\f\r\t\v\u00A0');
expect(tokens[0].value).toEqual('"\n\f\r\t\v\u00A0');
});
it('should tokenize unicode', function() {
var tokens = lex('"\\u00A0"');
expect(tokens.length).toEqual(1);
expect(tokens[0].string).toEqual('\u00a0');
expect(tokens[0].value).toEqual('\u00a0');
});
it('should ignore whitespace', function() {
@@ -153,12 +178,12 @@ describe('parser', function() {
it('should tokenize method invocation', function() {
var tokens = lex("a.b.c (d) - e.f()");
expect(tokens.map(function(t) { return t.text;})).
toEqual(['a.b', '.', 'c', '(', 'd', ')', '-', 'e', '.', 'f', '(', ')']);
toEqual(['a', '.', 'b', '.', 'c', '(', 'd', ')', '-', 'e', '.', 'f', '(', ')']);
});
it('should tokenize number', function() {
var tokens = lex("0.5");
expect(tokens[0].text).toEqual(0.5);
expect(tokens[0].value).toEqual(0.5);
});
it('should tokenize negative number', inject(function($rootScope) {
@@ -171,11 +196,11 @@ describe('parser', function() {
it('should tokenize number with exponent', inject(function($rootScope) {
var tokens = lex("0.5E-10");
expect(tokens[0].text).toEqual(0.5E-10);
expect(tokens[0].value).toEqual(0.5E-10);
expect($rootScope.$eval("0.5E-10")).toEqual(0.5E-10);
tokens = lex("0.5E+10");
expect(tokens[0].text).toEqual(0.5E+10);
expect(tokens[0].value).toEqual(0.5E+10);
}));
it('should throws exception for invalid exponent', function() {
@@ -190,7 +215,7 @@ describe('parser', function() {
it('should tokenize number starting with a dot', function() {
var tokens = lex(".5");
expect(tokens[0].text).toEqual(0.5);
expect(tokens[0].value).toEqual(0.5);
});
it('should throw error on invalid unicode', function() {
@@ -226,10 +251,10 @@ describe('parser', function() {
expect(scope.$eval("-1")).toEqual(-1);
expect(scope.$eval("1 + 2.5")).toEqual(3.5);
expect(scope.$eval("1 + -2.5")).toEqual(-1.5);
expect(scope.$eval("1+2*3/4")).toEqual(1+2*3/4);
expect(scope.$eval("0--1+1.5")).toEqual(0- -1 + 1.5);
expect(scope.$eval("-0--1++2*-3/-4")).toEqual(-0- -1+ +2*-3/-4);
expect(scope.$eval("1/2*3")).toEqual(1/2*3);
expect(scope.$eval("1+2*3/4")).toEqual(1 + 2 * 3 / 4);
expect(scope.$eval("0--1+1.5")).toEqual(0 - -1 + 1.5);
expect(scope.$eval("-0--1++2*-3/-4")).toEqual(-0 - -1 + +2 * -3 / -4);
expect(scope.$eval("1/2*3")).toEqual(1 / 2 * 3);
});
it('should parse comparison', function() {
@@ -247,16 +272,16 @@ describe('parser', function() {
expect(scope.$eval("1!=2")).toBeTruthy();
expect(scope.$eval("1<2")).toBeTruthy();
expect(scope.$eval("1<=1")).toBeTruthy();
expect(scope.$eval("1>2")).toEqual(1>2);
expect(scope.$eval("2>=1")).toEqual(2>=1);
expect(scope.$eval("true==2<3")).toEqual(true == 2<3);
expect(scope.$eval("true===2<3")).toEqual(true === 2<3);
expect(scope.$eval("1>2")).toEqual(1 > 2);
expect(scope.$eval("2>=1")).toEqual(2 >= 1);
expect(scope.$eval("true==2<3")).toEqual(true == 2 < 3);
expect(scope.$eval("true===2<3")).toEqual(true === 2 < 3);
});
it('should parse logical', function() {
expect(scope.$eval("0&&2")).toEqual(0&&2);
expect(scope.$eval("0||2")).toEqual(0||2);
expect(scope.$eval("0||1&&2")).toEqual(0||1&&2);
expect(scope.$eval("0&&2")).toEqual(0 && 2);
expect(scope.$eval("0||2")).toEqual(0 || 2);
expect(scope.$eval("0||1&&2")).toEqual(0 || 1 && 2);
});
it('should parse ternary', function() {
@@ -267,54 +292,54 @@ describe('parser', function() {
var identity = scope.identity = function(x) { return x; };
// Simple.
expect(scope.$eval('0?0:2')).toEqual(0?0:2);
expect(scope.$eval('1?0:2')).toEqual(1?0:2);
expect(scope.$eval('0?0:2')).toEqual(0 ? 0 : 2);
expect(scope.$eval('1?0:2')).toEqual(1 ? 0 : 2);
// Nested on the left.
expect(scope.$eval('0?0?0:0:2')).toEqual(0?0?0:0:2);
expect(scope.$eval('1?0?0:0:2')).toEqual(1?0?0:0:2);
expect(scope.$eval('0?1?0:0:2')).toEqual(0?1?0:0:2);
expect(scope.$eval('0?0?1:0:2')).toEqual(0?0?1:0:2);
expect(scope.$eval('0?0?0:2:3')).toEqual(0?0?0:2:3);
expect(scope.$eval('1?1?0:0:2')).toEqual(1?1?0:0:2);
expect(scope.$eval('1?1?1:0:2')).toEqual(1?1?1:0:2);
expect(scope.$eval('1?1?1:2:3')).toEqual(1?1?1:2:3);
expect(scope.$eval('1?1?1:2:3')).toEqual(1?1?1:2:3);
expect(scope.$eval('0?0?0:0:2')).toEqual(0 ? 0 ? 0 : 0 : 2);
expect(scope.$eval('1?0?0:0:2')).toEqual(1 ? 0 ? 0 : 0 : 2);
expect(scope.$eval('0?1?0:0:2')).toEqual(0 ? 1 ? 0 : 0 : 2);
expect(scope.$eval('0?0?1:0:2')).toEqual(0 ? 0 ? 1 : 0 : 2);
expect(scope.$eval('0?0?0:2:3')).toEqual(0 ? 0 ? 0 : 2 : 3);
expect(scope.$eval('1?1?0:0:2')).toEqual(1 ? 1 ? 0 : 0 : 2);
expect(scope.$eval('1?1?1:0:2')).toEqual(1 ? 1 ? 1 : 0 : 2);
expect(scope.$eval('1?1?1:2:3')).toEqual(1 ? 1 ? 1 : 2 : 3);
expect(scope.$eval('1?1?1:2:3')).toEqual(1 ? 1 ? 1 : 2 : 3);
// Nested on the right.
expect(scope.$eval('0?0:0?0:2')).toEqual(0?0:0?0:2);
expect(scope.$eval('1?0:0?0:2')).toEqual(1?0:0?0:2);
expect(scope.$eval('0?1:0?0:2')).toEqual(0?1:0?0:2);
expect(scope.$eval('0?0:1?0:2')).toEqual(0?0:1?0:2);
expect(scope.$eval('0?0:0?2:3')).toEqual(0?0:0?2:3);
expect(scope.$eval('1?1:0?0:2')).toEqual(1?1:0?0:2);
expect(scope.$eval('1?1:1?0:2')).toEqual(1?1:1?0:2);
expect(scope.$eval('1?1:1?2:3')).toEqual(1?1:1?2:3);
expect(scope.$eval('1?1:1?2:3')).toEqual(1?1:1?2:3);
expect(scope.$eval('0?0:0?0:2')).toEqual(0 ? 0 : 0 ? 0 : 2);
expect(scope.$eval('1?0:0?0:2')).toEqual(1 ? 0 : 0 ? 0 : 2);
expect(scope.$eval('0?1:0?0:2')).toEqual(0 ? 1 : 0 ? 0 : 2);
expect(scope.$eval('0?0:1?0:2')).toEqual(0 ? 0 : 1 ? 0 : 2);
expect(scope.$eval('0?0:0?2:3')).toEqual(0 ? 0 : 0 ? 2 : 3);
expect(scope.$eval('1?1:0?0:2')).toEqual(1 ? 1 : 0 ? 0 : 2);
expect(scope.$eval('1?1:1?0:2')).toEqual(1 ? 1 : 1 ? 0 : 2);
expect(scope.$eval('1?1:1?2:3')).toEqual(1 ? 1 : 1 ? 2 : 3);
expect(scope.$eval('1?1:1?2:3')).toEqual(1 ? 1 : 1 ? 2 : 3);
// Precedence with respect to logical operators.
expect(scope.$eval('0&&1?0:1')).toEqual(0&&1?0:1);
expect(scope.$eval('1||0?0:0')).toEqual(1||0?0:0);
expect(scope.$eval('0&&1?0:1')).toEqual(0 && 1 ? 0 : 1);
expect(scope.$eval('1||0?0:0')).toEqual(1 || 0 ? 0 : 0);
expect(scope.$eval('0?0&&1:2')).toEqual(0?0&&1:2);
expect(scope.$eval('0?1&&1:2')).toEqual(0?1&&1:2);
expect(scope.$eval('0?0||0:1')).toEqual(0?0||0:1);
expect(scope.$eval('0?0||1:2')).toEqual(0?0||1:2);
expect(scope.$eval('0?0&&1:2')).toEqual(0 ? 0 && 1 : 2);
expect(scope.$eval('0?1&&1:2')).toEqual(0 ? 1 && 1 : 2);
expect(scope.$eval('0?0||0:1')).toEqual(0 ? 0 || 0 : 1);
expect(scope.$eval('0?0||1:2')).toEqual(0 ? 0 || 1 : 2);
expect(scope.$eval('1?0&&1:2')).toEqual(1?0&&1:2);
expect(scope.$eval('1?1&&1:2')).toEqual(1?1&&1:2);
expect(scope.$eval('1?0||0:1')).toEqual(1?0||0:1);
expect(scope.$eval('1?0||1:2')).toEqual(1?0||1:2);
expect(scope.$eval('1?0&&1:2')).toEqual(1 ? 0 && 1 : 2);
expect(scope.$eval('1?1&&1:2')).toEqual(1 ? 1 && 1 : 2);
expect(scope.$eval('1?0||0:1')).toEqual(1 ? 0 || 0 : 1);
expect(scope.$eval('1?0||1:2')).toEqual(1 ? 0 || 1 : 2);
expect(scope.$eval('0?1:0&&1')).toEqual(0?1:0&&1);
expect(scope.$eval('0?2:1&&1')).toEqual(0?2:1&&1);
expect(scope.$eval('0?1:0||0')).toEqual(0?1:0||0);
expect(scope.$eval('0?2:0||1')).toEqual(0?2:0||1);
expect(scope.$eval('0?1:0&&1')).toEqual(0 ? 1 : 0 && 1);
expect(scope.$eval('0?2:1&&1')).toEqual(0 ? 2 : 1 && 1);
expect(scope.$eval('0?1:0||0')).toEqual(0 ? 1 : 0 || 0);
expect(scope.$eval('0?2:0||1')).toEqual(0 ? 2 : 0 || 1);
expect(scope.$eval('1?1:0&&1')).toEqual(1?1:0&&1);
expect(scope.$eval('1?2:1&&1')).toEqual(1?2:1&&1);
expect(scope.$eval('1?1:0||0')).toEqual(1?1:0||0);
expect(scope.$eval('1?2:0||1')).toEqual(1?2:0||1);
expect(scope.$eval('1?1:0&&1')).toEqual(1 ? 1 : 0 && 1);
expect(scope.$eval('1?2:1&&1')).toEqual(1 ? 2 : 1 && 1);
expect(scope.$eval('1?1:0||0')).toEqual(1 ? 1 : 0 || 0);
expect(scope.$eval('1?2:0||1')).toEqual(1 ? 2 : 0 || 1);
// Function calls.
expect(scope.$eval('returnTrue() ? returnString() : returnInt()')).toEqual(returnTrue() ? returnString() : returnInt());
@@ -357,18 +382,26 @@ describe('parser', function() {
expect(scope.$eval("a . \nb", scope)).toEqual(4);
});
it('should handle white-spaces around dots in method invocations', function() {
scope.a = {b: function() { return this.c; }, c: 4};
expect(scope.$eval("a . b ()", scope)).toEqual(4);
expect(scope.$eval("a. b ()", scope)).toEqual(4);
expect(scope.$eval("a .b ()", scope)).toEqual(4);
expect(scope.$eval("a \n . \nb \n ()", scope)).toEqual(4);
});
it('should throw syntax error exception for identifiers ending with a dot', function() {
scope.a = {b: 4};
expect(function() {
scope.$eval("a.", scope);
}).toThrowMinErr('$parse', 'syntax',
"Token 'null' is an unexpected token at column 2 of the expression [a.] starting at [.].");
}).toThrowMinErr('$parse', 'ueoe',
"Unexpected end of expression: a.");
expect(function() {
scope.$eval("a .", scope);
}).toThrowMinErr('$parse', 'syntax',
"Token 'null' is an unexpected token at column 3 of the expression [a .] starting at [.].");
}).toThrowMinErr('$parse', 'ueoe',
"Unexpected end of expression: a .");
});
it('should resolve deeply nested paths (important for CSP mode)', function() {
@@ -436,7 +469,7 @@ describe('parser', function() {
});
it('should evaluate grouped expressions', function() {
expect(scope.$eval("(1+2)*3")).toEqual((1+2)*3);
expect(scope.$eval("(1+2)*3")).toEqual((1 + 2) * 3);
});
it('should evaluate assignments', function() {
@@ -469,7 +502,7 @@ describe('parser', function() {
it('should evaluate function call with arguments', function() {
scope.add = function(a, b) {
return a+b;
return a + b;
};
expect(scope.$eval("add(1,2)")).toEqual(3);
});
@@ -504,13 +537,32 @@ describe('parser', function() {
});
it('should evaluate object', function() {
expect(toJson(scope.$eval("{}"))).toEqual("{}");
expect(toJson(scope.$eval("{a:'b'}"))).toEqual('{"a":"b"}');
expect(toJson(scope.$eval("{'a':'b'}"))).toEqual('{"a":"b"}');
expect(toJson(scope.$eval("{\"a\":'b'}"))).toEqual('{"a":"b"}');
expect(toJson(scope.$eval("{a:'b',}"))).toEqual('{"a":"b"}');
expect(toJson(scope.$eval("{'a':'b',}"))).toEqual('{"a":"b"}');
expect(toJson(scope.$eval("{\"a\":'b',}"))).toEqual('{"a":"b"}');
expect(scope.$eval("{}")).toEqual({});
expect(scope.$eval("{a:'b'}")).toEqual({a:"b"});
expect(scope.$eval("{'a':'b'}")).toEqual({a:"b"});
expect(scope.$eval("{\"a\":'b'}")).toEqual({a:"b"});
expect(scope.$eval("{a:'b',}")).toEqual({a:"b"});
expect(scope.$eval("{'a':'b',}")).toEqual({a:"b"});
expect(scope.$eval("{\"a\":'b',}")).toEqual({a:"b"});
expect(scope.$eval("{'0':1}")).toEqual({0:1});
expect(scope.$eval("{0:1}")).toEqual({0:1});
expect(scope.$eval("{1:1}")).toEqual({1:1});
expect(scope.$eval("{null:1}")).toEqual({null:1});
expect(scope.$eval("{'null':1}")).toEqual({null:1});
expect(scope.$eval("{false:1}")).toEqual({false:1});
expect(scope.$eval("{'false':1}")).toEqual({false:1});
expect(scope.$eval("{'':1,}")).toEqual({"":1});
});
it('should throw syntax error exception for non constant/identifier JSON keys', function() {
expect(function() { scope.$eval("{[:0}"); }).toThrowMinErr("$parse", "syntax",
"Syntax Error: Token '[' invalid key at column 2 of the expression [{[:0}] starting at [[:0}]");
expect(function() { scope.$eval("{{:0}"); }).toThrowMinErr("$parse", "syntax",
"Syntax Error: Token '{' invalid key at column 2 of the expression [{{:0}] starting at [{:0}]");
expect(function() { scope.$eval("{?:0}"); }).toThrowMinErr("$parse", "syntax",
"Syntax Error: Token '?' invalid key at column 2 of the expression [{?:0}] starting at [?:0}]");
expect(function() { scope.$eval("{):0}"); }).toThrowMinErr("$parse", "syntax",
"Syntax Error: Token ')' invalid key at column 2 of the expression [{):0}] starting at [):0}]");
});
it('should evaluate object access', function() {
@@ -518,8 +570,8 @@ describe('parser', function() {
});
it('should evaluate JSON', function() {
expect(toJson(scope.$eval("[{}]"))).toEqual("[{}]");
expect(toJson(scope.$eval("[{a:[]}, {b:1}]"))).toEqual('[{"a":[]},{"b":1}]');
expect(scope.$eval("[{}]")).toEqual([{}]);
expect(scope.$eval("[{a:[]}, {b:1}]")).toEqual([{a:[]}, {b:1}]);
});
it('should evaluate multiple statements', function() {
@@ -615,7 +667,7 @@ describe('parser', function() {
/* jshint -W018 */
expect(scope.$eval("!false || true")).toEqual(!false || true);
expect(scope.$eval("!11 == 10")).toEqual(!11 == 10);
expect(scope.$eval("12/6/2")).toEqual(12/6/2);
expect(scope.$eval("12/6/2")).toEqual(12 / 6 / 2);
});
it('should evaluate exclamation mark', function() {
@@ -672,6 +724,24 @@ describe('parser', function() {
describe('sandboxing', function() {
describe('Function constructor', function() {
it('should not tranverse the Function constructor in the getter', function() {
expect(function() {
scope.$eval('{}.toString.constructor');
}).toThrowMinErr(
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
'Expression: {}.toString.constructor');
});
it('should not allow access to the Function prototype in the getter', function() {
expect(function() {
scope.$eval('toString.constructor.prototype');
}).toThrowMinErr(
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
'Expression: toString.constructor.prototype');
});
it('should NOT allow access to Function constructor in getter', function() {
expect(function() {
@@ -765,6 +835,22 @@ describe('parser', function() {
'Expression: foo["bar"]');
});
describe('expensiveChecks', function() {
it('should block access to window object even when aliased', inject(function($parse, $window) {
scope.foo = {w: $window};
// This isn't blocked for performance.
expect(scope.$eval($parse('foo.w'))).toBe($window);
// Event handlers use the more expensive path for better protection since they expose
// the $event object on the scope.
expect(function() {
scope.$eval($parse('foo.w', null, true));
}).toThrowMinErr(
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
'Expression: foo.w');
}));
});
});
describe('Function prototype functions', function() {
+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);
}
+10
View File
@@ -400,6 +400,16 @@ describe('$aria', function() {
});
});
describe('announcing ngMessages', function() {
beforeEach(injectScopeAndCompiler);
it('should attach aria-live', function() {
var element = [
$compile('<div ng-messages="myForm.myName.$error">')(scope)
];
expectAriaAttrOnEachElement(element, 'aria-live', "assertive");
});
});
describe('aria-value when disabled', function() {
beforeEach(configAriaProvider({
+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 += '*';
}
+175 -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 + ';';
};
}
@@ -1507,6 +1511,174 @@ describe('ngMock', function() {
expect($rootElement.text()).toEqual('');
}));
});
describe('$rootScopeDecorator', function() {
describe('$countChildScopes', function() {
it('should return 0 when no child scopes', inject(function($rootScope) {
expect($rootScope.$countChildScopes()).toBe(0);
var childScope = $rootScope.$new();
expect($rootScope.$countChildScopes()).toBe(1);
expect(childScope.$countChildScopes()).toBe(0);
var grandChildScope = childScope.$new();
expect(childScope.$countChildScopes()).toBe(1);
expect(grandChildScope.$countChildScopes()).toBe(0);
}));
it('should correctly navigate complex scope tree', inject(function($rootScope) {
var child;
$rootScope.$new();
$rootScope.$new().$new().$new();
child = $rootScope.$new().$new();
child.$new();
child.$new();
child.$new().$new().$new();
expect($rootScope.$countChildScopes()).toBe(11);
}));
it('should provide the current count even after child destructions', inject(function($rootScope) {
expect($rootScope.$countChildScopes()).toBe(0);
var childScope1 = $rootScope.$new();
expect($rootScope.$countChildScopes()).toBe(1);
var childScope2 = $rootScope.$new();
expect($rootScope.$countChildScopes()).toBe(2);
childScope1.$destroy();
expect($rootScope.$countChildScopes()).toBe(1);
childScope2.$destroy();
expect($rootScope.$countChildScopes()).toBe(0);
}));
it('should work with isolate scopes', inject(function($rootScope) {
/*
RS
|
CIS
/ \
GCS GCIS
*/
var childIsolateScope = $rootScope.$new(true);
expect($rootScope.$countChildScopes()).toBe(1);
var grandChildScope = childIsolateScope.$new();
expect($rootScope.$countChildScopes()).toBe(2);
expect(childIsolateScope.$countChildScopes()).toBe(1);
var grandChildIsolateScope = childIsolateScope.$new(true);
expect($rootScope.$countChildScopes()).toBe(3);
expect(childIsolateScope.$countChildScopes()).toBe(2);
childIsolateScope.$destroy();
expect($rootScope.$countChildScopes()).toBe(0);
}));
});
describe('$countWatchers', function() {
it('should return the sum of watchers for the current scope and all of its children', inject(
function($rootScope) {
expect($rootScope.$countWatchers()).toBe(0);
var childScope = $rootScope.$new();
expect($rootScope.$countWatchers()).toBe(0);
childScope.$watch('foo');
expect($rootScope.$countWatchers()).toBe(1);
expect(childScope.$countWatchers()).toBe(1);
$rootScope.$watch('bar');
childScope.$watch('baz');
expect($rootScope.$countWatchers()).toBe(3);
expect(childScope.$countWatchers()).toBe(2);
}));
it('should correctly navigate complex scope tree', inject(function($rootScope) {
var child;
$rootScope.$watch('foo1');
$rootScope.$new();
$rootScope.$new().$new().$new();
child = $rootScope.$new().$new();
child.$watch('foo2');
child.$new();
child.$new();
child = child.$new().$new().$new();
child.$watch('foo3');
child.$watch('foo4');
expect($rootScope.$countWatchers()).toBe(4);
}));
it('should provide the current count even after child destruction and watch deregistration',
inject(function($rootScope) {
var deregisterWatch1 = $rootScope.$watch('exp1');
var childScope = $rootScope.$new();
childScope.$watch('exp2');
expect($rootScope.$countWatchers()).toBe(2);
childScope.$destroy();
expect($rootScope.$countWatchers()).toBe(1);
deregisterWatch1();
expect($rootScope.$countWatchers()).toBe(0);
}));
it('should work with isolate scopes', inject(function($rootScope) {
/*
RS=1
|
CIS=1
/ \
GCS=1 GCIS=1
*/
$rootScope.$watch('exp1');
expect($rootScope.$countWatchers()).toBe(1);
var childIsolateScope = $rootScope.$new(true);
childIsolateScope.$watch('exp2');
expect($rootScope.$countWatchers()).toBe(2);
expect(childIsolateScope.$countWatchers()).toBe(1);
var grandChildScope = childIsolateScope.$new();
grandChildScope.$watch('exp3');
var grandChildIsolateScope = childIsolateScope.$new(true);
grandChildIsolateScope.$watch('exp4');
expect($rootScope.$countWatchers()).toBe(4);
expect(childIsolateScope.$countWatchers()).toBe(3);
expect(grandChildScope.$countWatchers()).toBe(1);
expect(grandChildIsolateScope.$countWatchers()).toBe(1);
childIsolateScope.$destroy();
expect($rootScope.$countWatchers()).toBe(1);
}));
});
});
});
+70
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) {
@@ -294,6 +349,21 @@ describe('$route', function() {
$rootScope.$digest();
expect($route.current).toBeDefined();
}));
it("should use route params inherited from prototype chain", function() {
function BaseRoute() {}
BaseRoute.prototype.templateUrl = 'foo.html';
module(function($routeProvider) {
$routeProvider.when('/foo', new BaseRoute());
});
inject(function($route, $location, $rootScope) {
$location.path('/foo');
$rootScope.$digest();
expect($route.current.templateUrl).toBe('foo.html');
});
});
});
+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;
}
});

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