Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37310e024d | |||
| 306e626196 | |||
| f31c7492ec | |||
| a1e7eb6360 | |||
| 169e5326d1 | |||
| bb3b65374d | |||
| ac9336b35a | |||
| 75787446ee | |||
| 14409d7a7f | |||
| 370676d4d0 | |||
| 4c218de4d3 | |||
| 929dd15b9b | |||
| 1b9e408ddb | |||
| 7505d126fa | |||
| 7044e55feb | |||
| bd9e894fb7 | |||
| ba7e24ec6c | |||
| e1f98773c7 | |||
| 17d8a520ca | |||
| 84bf883f6d | |||
| 38ff199a3c | |||
| 7ff5ec254e | |||
| a2f2032a20 | |||
| 16833d0fb6 | |||
| 2a8a4e7fad | |||
| beeb64a6f6 | |||
| e49e7d50c5 | |||
| 3b3de3f876 | |||
| eec78e78d6 | |||
| b1f2917696 | |||
| 409bcb3810 | |||
| e49a1433fd | |||
| c26b5e3c88 | |||
| 4d0614fd0d | |||
| 756640f5aa | |||
| d87b7912df | |||
| bff8041bd0 | |||
| e0ee491633 | |||
| 7dfe82e135 | |||
| 9f2a53b33e | |||
| 9128eac501 | |||
| 85e8d5ea67 | |||
| a36863eea3 | |||
| 98da7ade7a | |||
| 846fe1b3ef | |||
| f4648abe03 | |||
| 5f9a1122e2 | |||
| 1afeb37756 | |||
| 7809e75a56 | |||
| 1472c31431 | |||
| d3b839d986 | |||
| c8c2386296 | |||
| d0b5bfa454 | |||
| 1fa2d56ba3 | |||
| efedc643d1 | |||
| 34230b30b7 | |||
| 8354d02805 | |||
| 1426b02980 | |||
| 29aeee2250 | |||
| 6a8348f715 | |||
| 38e2856889 | |||
| a56435f3ae | |||
| 75082c975c | |||
| 31b6bfaaf4 | |||
| 2d74323e3e | |||
| edc2613ed5 | |||
| 6eab8ab187 | |||
| 80ce046fd1 | |||
| 315f320e2e | |||
| ec4fe1bcab | |||
| 3303fe41e4 | |||
| f22e5fd980 | |||
| b11d6c792f | |||
| e713f36d7c | |||
| 8162a1d731 | |||
| 1e6f8543e5 | |||
| 0e293d2a97 | |||
| 434d7a0903 | |||
| a6e6438dae | |||
| 3691d2c15f | |||
| 4cee5fde1b | |||
| 5d042592fc | |||
| f619d032c9 | |||
| 95f5b86240 | |||
| 483d91a624 | |||
| e9339935d4 | |||
| 3e468523b7 | |||
| 99f3931e3d | |||
| 80b78f7461 | |||
| 386c179a94 | |||
| 054893b555 | |||
| e865726e00 | |||
| f9370755d4 | |||
| f906603dd6 | |||
| d7c084f9cf | |||
| 5847fc48e7 | |||
| 1ee9b4ef5e | |||
| 430082e6bd | |||
| fe7d9dedaa | |||
| 029ac8cb80 | |||
| 45b896a16a | |||
| f807d7ab4e |
@@ -12,6 +12,7 @@ angular.js.tmproj
|
||||
bower_components/
|
||||
angular.xcodeproj
|
||||
.idea
|
||||
*.iml
|
||||
.agignore
|
||||
libpeerconnection.log
|
||||
npm-debug.log
|
||||
|
||||
+432
@@ -1,3 +1,433 @@
|
||||
<a name="1.2.27"></a>
|
||||
# 1.2.27 prime-factorization (2014-11-20)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** clear the GCS cache even when no animation is detected
|
||||
([f619d032](https://github.com/angular/angular.js/commit/f619d032c932752313c646b5295bad8a68ef3871),
|
||||
[#8813](https://github.com/angular/angular.js/issues/8813))
|
||||
- **$browser:**
|
||||
- Cache `location.href` only during page reload phase
|
||||
([434d7a09](https://github.com/angular/angular.js/commit/434d7a09039151c1e627ac156213905d06b7df10),
|
||||
[#9235](https://github.com/angular/angular.js/issues/9235), [#9470](https://github.com/angular/angular.js/issues/9470))
|
||||
- don’t use history api when only the hash changes
|
||||
([a6e6438d](https://github.com/angular/angular.js/commit/a6e6438dae1ed92b29608d0b8830b0a7fbb624ef),
|
||||
[#9423](https://github.com/angular/angular.js/issues/9423), [#9424](https://github.com/angular/angular.js/issues/9424))
|
||||
- handle async href on url change in <=IE9
|
||||
([fe7d9ded](https://github.com/angular/angular.js/commit/fe7d9dedaa5ec3b3f56d9eb9c513cf99e40121ce),
|
||||
[#9235](https://github.com/angular/angular.js/issues/9235))
|
||||
- **$http:** add missing shortcut methods and missing docs
|
||||
([ec4fe1bc](https://github.com/angular/angular.js/commit/ec4fe1bcab6f981103a10f860a3a00122aa78607),
|
||||
[#9180](https://github.com/angular/angular.js/issues/9180), [#9321](https://github.com/angular/angular.js/issues/9321))
|
||||
- **$location:**
|
||||
- revert erroneous logic and backport refactorings from master
|
||||
([1ee9b4ef](https://github.com/angular/angular.js/commit/1ee9b4ef5e4a795061d3aa19adefdeb7e0209eeb),
|
||||
[#8492](https://github.com/angular/angular.js/issues/8492))
|
||||
- allow 0 in path() and hash()
|
||||
([f807d7ab](https://github.com/angular/angular.js/commit/f807d7ab4ebd18899154528ea9ed50d5bc25c57a))
|
||||
- **$parse:** add quick check for Function constructor in fast path
|
||||
([756640f5](https://github.com/angular/angular.js/commit/756640f5aa8f3fd0084bff50534e23976a6fff00))
|
||||
- **$parse, events:** prevent accidental misuse of properties on $event
|
||||
([4d0614fd](https://github.com/angular/angular.js/commit/4d0614fd0da12c5783dfb4956c330edac87e62fe),
|
||||
[#9969](https://github.com/angular/angular.js/issues/9969))
|
||||
- **ngMock:** $httpBackend should match data containing Date objects correctly
|
||||
([1426b029](https://github.com/angular/angular.js/commit/1426b02980badfd322eb960d71bfb1a14d657847),
|
||||
[#5127](https://github.com/angular/angular.js/issues/5127))
|
||||
- **orderBy:** sort by identity if no predicate is given
|
||||
([45b896a1](https://github.com/angular/angular.js/commit/45b896a16abbcbfcdfb9a95c2d10c76a805b57cc),
|
||||
[#5847](https://github.com/angular/angular.js/issues/5847), [#4579](https://github.com/angular/angular.js/issues/4579), [#9403](https://github.com/angular/angular.js/issues/9403))
|
||||
- **select:** ensure the label attribute is updated in Internet Explorer
|
||||
([16833d0f](https://github.com/angular/angular.js/commit/16833d0fb6585117e9978d1accc3ade83e22e797),
|
||||
[#9621](https://github.com/angular/angular.js/issues/9621), [#10042](https://github.com/angular/angular.js/issues/10042))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **orderBy:** copy array with slice instead of for loop
|
||||
([409bcb38](https://github.com/angular/angular.js/commit/409bcb3810a1622178268f7ff7f4130887a1a3dc),
|
||||
[#9942](https://github.com/angular/angular.js/issues/9942))
|
||||
|
||||
|
||||
<a name="1.3.3"></a>
|
||||
# 1.3.3 undersea-arithmetic (2014-11-17)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$http:** don't parse single space responses as JSON
|
||||
([6f19a6fd](https://github.com/angular/angular.js/commit/6f19a6fd33ab72d3908e3418fba47ee8e1598fa6),
|
||||
[#9907](https://github.com/angular/angular.js/issues/9907))
|
||||
- **minErr:** stringify non-JSON compatible objects in error messages
|
||||
([cf43ccdf](https://github.com/angular/angular.js/commit/cf43ccdf9b8665a2fd5d6aa52f80cb2d7c9bb7e2),
|
||||
[#10085](https://github.com/angular/angular.js/issues/10085))
|
||||
- **$rootScope:** handle cyclic references in scopes when creating error messages
|
||||
([e80053d9](https://github.com/angular/angular.js/commit/e80053d91fd7c722e092a23d326384de2e552eb6),
|
||||
[#10085](https://github.com/angular/angular.js/issues/10085))
|
||||
- **ngRepeat:** support cyclic object references in error messages
|
||||
([fa12c3c8](https://github.com/angular/angular.js/commit/fa12c3c86af7965d1b9d9a5dd3434755e9e04635),
|
||||
[#9838](https://github.com/angular/angular.js/issues/9838), [#10065](https://github.com/angular/angular.js/issues/10065), [#10085](https://github.com/angular/angular.js/issues/10085))
|
||||
- **ngMock:** call $interval callbacks even when invokeApply is false
|
||||
([d81ff888](https://github.com/angular/angular.js/commit/d81ff8885b77f70c6417d7be3124d86d07447375),
|
||||
[#10032](https://github.com/angular/angular.js/issues/10032))
|
||||
- **ngPattern:** match behaviour of native HTML pattern attribute
|
||||
([85eb9660](https://github.com/angular/angular.js/commit/85eb9660ef67c24d5104a6a1921bedad0bd1b57e),
|
||||
[#9881](https://github.com/angular/angular.js/issues/9881), [#9888](https://github.com/angular/angular.js/issues/9888))
|
||||
- **select:** ensure the label attribute is updated in Internet Explorer
|
||||
([6604c236](https://github.com/angular/angular.js/commit/6604c2361427fba8c43a39dc2e92197390dfbdbe),
|
||||
[#9621](https://github.com/angular/angular.js/issues/9621), [#10042](https://github.com/angular/angular.js/issues/10042))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$location:** allow to location to be changed during $locationChangeStart
|
||||
([a9352c19](https://github.com/angular/angular.js/commit/a9352c19ce33f0393d6581547c7ea8dfc2a8b78f),
|
||||
[#9607](https://github.com/angular/angular.js/issues/9607), [#9678](https://github.com/angular/angular.js/issues/9678))
|
||||
- **$routeProvider:** allow setting caseInsensitiveMatch on the provider
|
||||
([0db573b7](https://github.com/angular/angular.js/commit/0db573b7493f76abd94ff65ce660017d617e865b),
|
||||
[#6477](https://github.com/angular/angular.js/issues/6477), [#9873](https://github.com/angular/angular.js/issues/9873))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **orderBy:** copy array with slice instead of for loop
|
||||
([8eabc546](https://github.com/angular/angular.js/commit/8eabc5463c795d87f37e5a9eacbbb14435024061),
|
||||
[#9942](https://github.com/angular/angular.js/issues/9942))
|
||||
|
||||
|
||||
|
||||
<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)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$browser:**
|
||||
- account for IE deserializing history.state on each read
|
||||
([1efaf3dc](https://github.com/angular/angular.js/commit/1efaf3dc136f822703a9cda55afac7895a923ccb),
|
||||
[#9587](https://github.com/angular/angular.js/issues/9587), [#9545](https://github.com/angular/angular.js/issues/9545))
|
||||
- do not decode cookies that do not appear encoded
|
||||
([9c995905](https://github.com/angular/angular.js/commit/9c9959059eb84f0f1d748b70b50ec47b7d23d065),
|
||||
[#9211](https://github.com/angular/angular.js/issues/9211), [#9225](https://github.com/angular/angular.js/issues/9225))
|
||||
- **$http:**
|
||||
- allow empty json response
|
||||
([9ba24c54](https://github.com/angular/angular.js/commit/9ba24c54d60e643b1450cc5cfa8f990bd524c130),
|
||||
[#9532](https://github.com/angular/angular.js/issues/9532), [#9562](https://github.com/angular/angular.js/issues/9562))
|
||||
- don't run transformData on HEAD methods
|
||||
([6e4955a3](https://github.com/angular/angular.js/commit/6e4955a3086555d8ca30c29955faa213b39c6f27),
|
||||
[#9528](https://github.com/angular/angular.js/issues/9528), [#9529](https://github.com/angular/angular.js/issues/9529))
|
||||
- **$injector:** ensure $get method invoked with provider context
|
||||
([372fa699](https://github.com/angular/angular.js/commit/372fa6993b2b1b4848aa4be3c3e11f69244fca6f),
|
||||
[#9511](https://github.com/angular/angular.js/issues/9511), [#9512](https://github.com/angular/angular.js/issues/9512))
|
||||
- **$location:** use clone of passed search() object
|
||||
([c7a9009e](https://github.com/angular/angular.js/commit/c7a9009e143299f0e45a85d715ff22fc676d3f93),
|
||||
[#9445](https://github.com/angular/angular.js/issues/9445))
|
||||
- **$parse:** stabilize one-time literal expressions correctly
|
||||
([874cac82](https://github.com/angular/angular.js/commit/874cac825bf29a936cb1b35f9af239687bc5e036))
|
||||
- **formController:** remove scope reference when form is destroyed
|
||||
([01f50e1a](https://github.com/angular/angular.js/commit/01f50e1a7b2bff7070616494774ec493f8133204),
|
||||
[#9315](https://github.com/angular/angular.js/issues/9315))
|
||||
- **jqLite:** remove native listener when all jqLite listeners were deregistered
|
||||
([d71fb6f2](https://github.com/angular/angular.js/commit/d71fb6f2713f1a636f6e9c25479870ee9941ad18),
|
||||
[#9509](https://github.com/angular/angular.js/issues/9509))
|
||||
- **select:**
|
||||
- add basic track by and select as support
|
||||
([addfff3c](https://github.com/angular/angular.js/commit/addfff3c46311f59bdcd100351260006d457316f),
|
||||
[#6564](https://github.com/angular/angular.js/issues/6564))
|
||||
- manage select controller options correctly
|
||||
([2435e2b8](https://github.com/angular/angular.js/commit/2435e2b8f84fde9495b8e9440a2b4f865b1ff541),
|
||||
[#9418](https://github.com/angular/angular.js/issues/9418))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$anchorScroll:** support a configurable vertical scroll offset
|
||||
([09c39d2c](https://github.com/angular/angular.js/commit/09c39d2ce687cdf0ac35dbb34a91f0d198c9d83a),
|
||||
[#9368](https://github.com/angular/angular.js/issues/9368), [#2070](https://github.com/angular/angular.js/issues/2070), [#9360](https://github.com/angular/angular.js/issues/9360))
|
||||
- **$animate:**
|
||||
- introduce the $animate.animate() method
|
||||
([02be700b](https://github.com/angular/angular.js/commit/02be700bda191b454de393f2805916f374a1d764))
|
||||
- allow $animate to pass custom styles into animations
|
||||
([e5f4d7b1](https://github.com/angular/angular.js/commit/e5f4d7b10ae5e6a17ab349995451c33b7d294245))
|
||||
- **currencyFilter:** add fractionSize as optional parameter
|
||||
([20685ffe](https://github.com/angular/angular.js/commit/20685ffe11036d4d604d13f0d792ca46497af4a1),
|
||||
[#3642](https://github.com/angular/angular.js/issues/3642), [#3461](https://github.com/angular/angular.js/issues/3461), [#3642](https://github.com/angular/angular.js/issues/3642), [#7922](https://github.com/angular/angular.js/issues/7922))
|
||||
- **jqLite:** add private jqLiteDocumentLoaded function
|
||||
([0dd316ef](https://github.com/angular/angular.js/commit/0dd316efea209e5e5de3e456b4e6562f011a1294))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$animate:** due to [e5f4d7b1](https://github.com/angular/angular.js/commit/e5f4d7b10ae5e6a17ab349995451c33b7d294245),
|
||||
staggering animations that use transitions will now
|
||||
always block the transition from starting (via `transition: 0s none`)
|
||||
up until the stagger step kicks in. The former behaviour was that the
|
||||
block was removed as soon as the pending class was added. This fix
|
||||
allows for styles to be applied in the pending class without causing
|
||||
an animation to trigger prematurely.
|
||||
|
||||
|
||||
<a name="1.3.0-rc.5"></a>
|
||||
# 1.3.0-rc.5 impossible-choreography (2014-10-08)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$anchorScroll:** don't scroll to top when initializing and location hash is empty
|
||||
([d5445c60](https://github.com/angular/angular.js/commit/d5445c601fafd6ecd38befeaa4c9ec7bb044127c),
|
||||
[#8848](https://github.com/angular/angular.js/issues/8848), [#9393](https://github.com/angular/angular.js/issues/9393))
|
||||
- **$animate:**
|
||||
- ensure hidden elements with ngShow/ngHide stay hidden during animations
|
||||
([39d0b368](https://github.com/angular/angular.js/commit/39d0b36826a077f7549a70d0cf3edebe90a10aaa),
|
||||
[#9103](https://github.com/angular/angular.js/issues/9103), [#9493](https://github.com/angular/angular.js/issues/9493))
|
||||
- permit class-based animations for leave operations if ngAnimateChildren is enabled
|
||||
([df1a00b1](https://github.com/angular/angular.js/commit/df1a00b11ac2722f4da441837795985f12682030),
|
||||
[#8092](https://github.com/angular/angular.js/issues/8092), [#9491](https://github.com/angular/angular.js/issues/9491))
|
||||
- ensure that class-based animations only consider the most recent DOM operations
|
||||
([c93924ed](https://github.com/angular/angular.js/commit/c93924ed275a62683b85c82f1c6c2e19d5662c9a),
|
||||
[#8946](https://github.com/angular/angular.js/issues/8946), [#9458](https://github.com/angular/angular.js/issues/9458))
|
||||
- abort class-based animations if the element is removed during digest
|
||||
([613d0a32](https://github.com/angular/angular.js/commit/613d0a3212de8dc01c817ca8526e09c57978a621),
|
||||
[#8796](https://github.com/angular/angular.js/issues/8796))
|
||||
- clear the GCS cache even when no animation is detected
|
||||
([cb85cbce](https://github.com/angular/angular.js/commit/cb85cbcec1c876db6062a0dc0bad80f842782194),
|
||||
[#8813](https://github.com/angular/angular.js/issues/8813))
|
||||
- **$browser:**
|
||||
- Cache `location.href` only during page reload phase
|
||||
([8ee1ba4b](https://github.com/angular/angular.js/commit/8ee1ba4b94d6fccff06d8781f7ed256c6ce664ff),
|
||||
[#9235](https://github.com/angular/angular.js/issues/9235), [#9455](https://github.com/angular/angular.js/issues/9455))
|
||||
- don’t use the history API when only the hash changes
|
||||
([7cb01a80](https://github.com/angular/angular.js/commit/7cb01a80beec669d8f6aae1dc211d2f0b7d4eac4),
|
||||
[#9423](https://github.com/angular/angular.js/issues/9423), [#9424](https://github.com/angular/angular.js/issues/9424),
|
||||
[858360b6](https://github.com/angular/angular.js/commit/858360b680a2bb5c19429c1be1c9506700cda476),
|
||||
[0656484d](https://github.com/angular/angular.js/commit/0656484d3e709c5162570b0dd6473b0b6140e5b2),
|
||||
[#9143](https://github.com/angular/angular.js/issues/9143), [#9406](https://github.com/angular/angular.js/issues/9406))
|
||||
- handle async href on url change in <=IE9
|
||||
([404b95fe](https://github.com/angular/angular.js/commit/404b95fe30a1bcd1313adafbd0018578d5b21d3d),
|
||||
[#9235](https://github.com/angular/angular.js/issues/9235))
|
||||
- **$compile:**
|
||||
- handle the removal of an interpolated attribute
|
||||
([a75546af](https://github.com/angular/angular.js/commit/a75546afdf41adab786eda30c258190cd4c5f1ae),
|
||||
[#9236](https://github.com/angular/angular.js/issues/9236), [#9240](https://github.com/angular/angular.js/issues/9240))
|
||||
- remove comment nodes from templates before asserting single root node
|
||||
([feba0174](https://github.com/angular/angular.js/commit/feba0174db0f8f929273beb8b90691734a9292e2),
|
||||
[#9212](https://github.com/angular/angular.js/issues/9212), [#9215](https://github.com/angular/angular.js/issues/9215))
|
||||
- use the correct namespace for transcluded svg elements
|
||||
([f3539f3c](https://github.com/angular/angular.js/commit/f3539f3cb5d9477f50f065c6a0ac7d6ca0a31092),
|
||||
[#9344](https://github.com/angular/angular.js/issues/9344), [#9415](https://github.com/angular/angular.js/issues/9415))
|
||||
- **$http:** honor application/json response header and parse json primitives
|
||||
([7b6c1d08](https://github.com/angular/angular.js/commit/7b6c1d08aceba6704a40302f373400aed9ed0e0b),
|
||||
[#2973](https://github.com/angular/angular.js/issues/2973))
|
||||
- **$injector:** throw when factory $get method does not return a value
|
||||
([0d3b69a5](https://github.com/angular/angular.js/commit/0d3b69a5f27b41745b504c7ffd8d72653bac1f85),
|
||||
[#4575](https://github.com/angular/angular.js/issues/4575), [#9210](https://github.com/angular/angular.js/issues/9210))
|
||||
- **$location:** allow `0` in `path()` and `hash()`
|
||||
([b8c5b871](https://github.com/angular/angular.js/commit/b8c5b87119a06edb8e8d1cefad81ee8d1f64f070))
|
||||
- **form:** fix submit prevention
|
||||
([86c7d122](https://github.com/angular/angular.js/commit/86c7d1221c706993044583d51a0c61423fee5bcf),
|
||||
[#3370](https://github.com/angular/angular.js/issues/3370), [#3776](https://github.com/angular/angular.js/issues/3776))
|
||||
- **ngAnimate:** defer DOM operations for changing classes to postDigest
|
||||
([667183a8](https://github.com/angular/angular.js/commit/667183a8c79d6ffce571a2be78c05dc76503b222),
|
||||
[#8234](https://github.com/angular/angular.js/issues/8234), [#9263](https://github.com/angular/angular.js/issues/9263))
|
||||
- **orderBy:** sort by identity if no predicate is given
|
||||
([607f016a](https://github.com/angular/angular.js/commit/607f016a0ba705ce40df0164360fb96a9d7f5912),
|
||||
[#5847](https://github.com/angular/angular.js/issues/5847), [#4579](https://github.com/angular/angular.js/issues/4579), [#9403](https://github.com/angular/angular.js/issues/9403))
|
||||
- **select:**
|
||||
- throw for `selectAs` and `trackBy`
|
||||
([30996f82](https://github.com/angular/angular.js/commit/30996f82afa03cd11771b3267e9367ecf9af6e6d))
|
||||
- use `$viewValue` instead of `$modelValue`
|
||||
([f7174169](https://github.com/angular/angular.js/commit/f7174169f4f710d605f6a67f39f90a67a07d4cab),
|
||||
[#8929](https://github.com/angular/angular.js/issues/8929))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$location:**
|
||||
- add support for History API state handling ([6fd36dee](https://github.com/angular/angular.js/commit/6fd36deed954b338e48390862971d465148dc1f2),
|
||||
[#9027](https://github.com/angular/angular.js/issues/9027))
|
||||
- allow automatic rewriting of links to be disabled
|
||||
([b3e09be5](https://github.com/angular/angular.js/commit/b3e09be58960b913fee3869bf36e7de3305bbe00),
|
||||
[#5487](https://github.com/angular/angular.js/issues/5487))
|
||||
- **$route:** ability to cancel $routeChangeStart event
|
||||
([f4ff11b0](https://github.com/angular/angular.js/commit/f4ff11b01e6a5f9a9eb25a38d327dfaadbd7c80c),
|
||||
[#5581](https://github.com/angular/angular.js/issues/5581), [#5714](https://github.com/angular/angular.js/issues/5714), [#9502](https://github.com/angular/angular.js/issues/9502))
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$animate:**
|
||||
- access DOM less in resolveElementClasses
|
||||
([22358cf9](https://github.com/angular/angular.js/commit/22358cf9c703d67f3cf9eb4899404b09578a5fad))
|
||||
- don't join classes before it's necessary in resolveElementClasses
|
||||
([003c44ec](https://github.com/angular/angular.js/commit/003c44eceee54c3398b0d2971fd97a512d7f7cec))
|
||||
- **ngBind:** set textContent rather than using element.text()
|
||||
([074a146d](https://github.com/angular/angular.js/commit/074a146d8b1ee7c93bf6d5892448a5c2a0143a28),
|
||||
[#9369](https://github.com/angular/angular.js/issues/9369), [#9396](https://github.com/angular/angular.js/issues/9396))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:** due to [feba0174](https://github.com/angular/angular.js/commit/feba0174db0f8f929273beb8b90691734a9292e2),
|
||||
|
||||
|
||||
If a template contains directives within comment nodes, and there is more than a single node in the
|
||||
template, those comment nodes are removed. The impact of this breaking change is expected to be
|
||||
quite low.
|
||||
|
||||
Closes #9212
|
||||
Closes #9215
|
||||
|
||||
- **ngAnimate:** due to [667183a8](https://github.com/angular/angular.js/commit/667183a8c79d6ffce571a2be78c05dc76503b222),
|
||||
|
||||
|
||||
The `$animate` CSS class API will always defer changes until the end of the next digest. This allows ngAnimate
|
||||
to coalesce class changes which occur over a short period of time into 1 or 2 DOM writes, rather than
|
||||
many. This prevents jank in browsers such as IE, and is generally a good thing.
|
||||
|
||||
If you find that your classes are not being immediately applied, be sure to invoke `$digest()`.
|
||||
|
||||
Closes #8234
|
||||
Closes #9263
|
||||
|
||||
- **$select:** due to [30996f8](https://github.com/angular/angular.js/commit/30996f82afa03cd11771b3267e9367ecf9af6e6d)
|
||||
|
||||
`ngOptions` will now throw an error when the comprehension expressions contains both a `select as`
|
||||
and `track by` expression.
|
||||
|
||||
These expressions are fundamentally incompatible because it is not possible to reliably and
|
||||
consistently determine the parent object of a model, since `select as` can assign any child of a
|
||||
`value` as the model value.
|
||||
|
||||
Prior to refactorings in this release, neither of these expressions worked correctly independently,
|
||||
and did not work at all when combined.
|
||||
|
||||
See #6564
|
||||
|
||||
- **$route:** due to [f4ff11b0](https://github.com/angular/angular.js/commit/f4ff11b01e6a5f9a9eb25a38d327dfaadbd7c80c),
|
||||
|
||||
Order of events has changed.
|
||||
Previously: `$locationChangeStart` -> `$locationChangeSuccess`
|
||||
-> `$routeChangeStart` -> `$routeChangeSuccess`
|
||||
|
||||
Now: `$locationChangeStart` -> `$routeChangeStart`
|
||||
-> `$locationChangeSuccess` -> -> `$routeChangeSuccess`
|
||||
|
||||
Fixes #5581
|
||||
Closes #5714
|
||||
Closes #9502- **ngAnimate:** due to [667183a8](https://github.com/angular/angular.js/commit/667183a8c79d6ffce571a2be78c05dc76503b222),
|
||||
|
||||
|
||||
The $animate class API will always defer changes until the end of the next digest. This allows ngAnimate
|
||||
to coalesce class changes which occur over a short period of time into 1 or 2 DOM writes, rather than
|
||||
many. This prevents jank in browsers such as IE, and is generally a good thing.
|
||||
|
||||
If you're finding that your classes are not being immediately applied, be sure to invoke $digest().
|
||||
|
||||
Closes #8234
|
||||
Closes #9263
|
||||
|
||||
|
||||
<a name="1.3.0-rc.4"></a>
|
||||
# 1.3.0-rc.4 unicorn-hydrafication (2014-10-01)
|
||||
|
||||
@@ -1190,6 +1620,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>
|
||||
|
||||
+1
-1
@@ -172,7 +172,7 @@ To ensure consistency throughout the source code, keep these rules in mind as yo
|
||||
* **Do not use namespaces**: Instead, wrap the entire angular code base in an anonymous closure and
|
||||
export our API explicitly rather than implicitly.
|
||||
* Wrap all code at **100 characters**.
|
||||
* Instead of complex inheritance hierarchies, we **prefer simple objects**. We use prototypical
|
||||
* Instead of complex inheritance hierarchies, we **prefer simple objects**. We use prototypal
|
||||
inheritance only when absolutely necessary.
|
||||
* We **love functions and closures** and, whenever possible, prefer them over objects.
|
||||
* To write concise code that can be better minified, we **use aliases internally** that map to the
|
||||
|
||||
+3
-1
@@ -112,7 +112,7 @@ var printSection = function(stream, title, section, printCommitLinks) {
|
||||
}
|
||||
stream.write(')\n');
|
||||
} else {
|
||||
stream.write(util.format('%s %s', prefix, commit.subject));
|
||||
stream.write(util.format('%s %s\n', prefix, commit.subject));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -188,6 +188,7 @@ var getPreviousTag = function() {
|
||||
|
||||
|
||||
var generate = function(version, file) {
|
||||
|
||||
getPreviousTag().then(function(tag) {
|
||||
console.log('Reading git log since', tag);
|
||||
readGitLog('^fix|^feat|^perf|BREAKING', tag).then(function(commits) {
|
||||
@@ -201,6 +202,7 @@ var generate = function(version, file) {
|
||||
|
||||
// publish for testing
|
||||
exports.parseRawCommit = parseRawCommit;
|
||||
exports.printSection = printSection;
|
||||
|
||||
// hacky start if not run by jasmine :-D
|
||||
if (process.argv.join('').indexOf('jasmine-node') === -1) {
|
||||
|
||||
+62
-1
@@ -1,4 +1,4 @@
|
||||
/* global describe: false, it: false, expect: false */
|
||||
/* global describe: false, beforeEach: false, afterEach: false, it: false, expect: false */
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -44,4 +44,65 @@ describe('changelog.js', function() {
|
||||
expect(msg.breaking).toEqual(' first breaking change\nsomething else\nanother line with more info\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('printSection', function() {
|
||||
var output;
|
||||
var streamMock = {
|
||||
write: function(str) {
|
||||
output += str;
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
output = '';
|
||||
});
|
||||
|
||||
it('should add a new line at the end of each breaking change list item ' +
|
||||
'when there is 1 item per component', function() {
|
||||
var title = 'test';
|
||||
var printCommitLinks = false;
|
||||
|
||||
var section = {
|
||||
module1: [{subject: 'breaking change 1'}],
|
||||
module2: [{subject: 'breaking change 2'}]
|
||||
};
|
||||
var expectedOutput =
|
||||
'\n' + '## test\n\n' +
|
||||
'- **module1:** breaking change 1\n' +
|
||||
'- **module2:** breaking change 2\n' +
|
||||
'\n';
|
||||
|
||||
ch.printSection(streamMock, title, section, printCommitLinks);
|
||||
expect(output).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should add a new line at the end of each breaking change list item ' +
|
||||
'when there are multiple items per component', function() {
|
||||
var title = 'test';
|
||||
var printCommitLinks = false;
|
||||
|
||||
var section = {
|
||||
module1: [
|
||||
{subject: 'breaking change 1.1'},
|
||||
{subject: 'breaking change 1.2'}
|
||||
],
|
||||
module2: [
|
||||
{subject: 'breaking change 2.1'},
|
||||
{subject: 'breaking change 2.2'}
|
||||
]
|
||||
};
|
||||
var expectedOutput =
|
||||
'\n' + '## test\n\n' +
|
||||
'- **module1:**\n' +
|
||||
' - breaking change 1.1\n' +
|
||||
' - breaking change 1.2\n' +
|
||||
'- **module2:**\n' +
|
||||
' - breaking change 2.1\n' +
|
||||
' - breaking change 2.2\n' +
|
||||
'\n';
|
||||
|
||||
ch.printSection(streamMock, title, section, printCommitLinks);
|
||||
expect(output).toBe(expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
angular.module('versions', [])
|
||||
|
||||
.controller('DocsVersionsCtrl', ['$scope', '$location', '$window', 'NG_VERSIONS', function($scope, $location, $window, NG_VERSIONS) {
|
||||
$scope.docs_version = NG_VERSIONS[0];
|
||||
$scope.docs_versions = NG_VERSIONS;
|
||||
|
||||
for(var i=0, minor = NaN; i < NG_VERSIONS.length; i++) {
|
||||
var version = NG_VERSIONS[i];
|
||||
@@ -13,13 +16,12 @@ angular.module('versions', [])
|
||||
minor = version.minor;
|
||||
}
|
||||
|
||||
$scope.docs_versions = NG_VERSIONS;
|
||||
$scope.getGroupName = function(v) {
|
||||
return v.isLatest ? 'Latest' : (v.isStable ? 'Stable' : 'Unstable');
|
||||
return v.isLatest ? 'Latest' : ('v' + v.major + '.' + v.minor + '.x');
|
||||
};
|
||||
|
||||
$scope.jumpToDocsVersion = function(version) {
|
||||
var currentPagePath = $location.path();
|
||||
var currentPagePath = $location.path().replace(/\/$/, '');
|
||||
|
||||
// TODO: We need to do some munging of the path for different versions of the API...
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ var packagePath = __dirname;
|
||||
|
||||
var Package = require('dgeni').Package;
|
||||
|
||||
// Create and export a new Dgeni package called dgeni-example. This package depends upon
|
||||
// the jsdoc and nunjucks packages defined in the dgeni-packages npm module.
|
||||
// Create and export a new Dgeni package called angularjs. This package depends upon
|
||||
// the ngdoc,nunjucks and examples packages defined in the dgeni-packages npm module.
|
||||
module.exports = new Package('angularjs', [
|
||||
require('dgeni-packages/ngdoc'),
|
||||
require('dgeni-packages/nunjucks'),
|
||||
@@ -103,7 +103,7 @@ module.exports = new Package('angularjs', [
|
||||
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
docTypes: ['indexPage'],
|
||||
getPath: function() {},
|
||||
pathTemplate: '.',
|
||||
outputPathTemplate: '${id}.html'
|
||||
});
|
||||
|
||||
@@ -131,6 +131,12 @@ module.exports = new Package('angularjs', [
|
||||
});
|
||||
})
|
||||
|
||||
.config(function(checkAnchorLinksProcessor) {
|
||||
checkAnchorLinksProcessor.base = '/';
|
||||
// We are only interested in docs that have an area (i.e. they are pages)
|
||||
checkAnchorLinksProcessor.checkDoc = function(doc) { return doc.area; };
|
||||
})
|
||||
|
||||
|
||||
.config(function(
|
||||
generateIndexPagesProcessor,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var gruntUtils = require('../../../lib/grunt/utils');
|
||||
var versionInfo = require('../../../lib/versions/version-info');
|
||||
|
||||
/**
|
||||
|
||||
@@ -148,7 +148,7 @@ or JavaScript callbacks.
|
||||
{@link ngAnimate CSS-based animations}
|
||||
</td>
|
||||
<td>
|
||||
Follow ngAnimate’s CSS naming structure to reference CSS transitions / keyframe animations in AngularJS. Once defined the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
Follow ngAnimate’s CSS naming structure to reference CSS transitions / keyframe animations in AngularJS. Once defined, the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -156,7 +156,7 @@ or JavaScript callbacks.
|
||||
{@link ngAnimate JS-based animations}
|
||||
</td>
|
||||
<td>
|
||||
Use {@link angular.Module#animation module.animation()} to register a JavaScript animation. Once registered the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
Use {@link angular.Module#animation module.animation()} to register a JavaScript animation. Once registered, the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -15,7 +15,7 @@ For example the issue can be triggered by this *invalid* code:
|
||||
|
||||
To resolve this error either ensure that the items in the collection have unique identity or use the `track by` syntax to specify how to track the association between models and DOM.
|
||||
|
||||
To resolve the example above can be resolved by using `track by $index`, which will cause the items to be keyed by their position in the array instead of their value:
|
||||
The example above can be resolved by using `track by $index`, which will cause the items to be keyed by their position in the array instead of their value:
|
||||
|
||||
```
|
||||
<div ng-repeat="value in [4, 4] track by $index"></div>
|
||||
|
||||
@@ -211,6 +211,10 @@ facilitate the browser URL change and history management.
|
||||
## Hashbang mode (default mode)
|
||||
|
||||
In this mode, `$location` uses Hashbang URLs in all browsers.
|
||||
Angular also does not intercept and rewrite links in this mode. I.e. links work
|
||||
as expected and also perform full page reloads when other parts of the url
|
||||
than the hash fragment was changed.
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
@@ -250,6 +254,10 @@ having to worry about whether the browser displaying your app supports the histo
|
||||
- Opening a regular URL in a legacy browser -> redirects to a hashbang URL
|
||||
- Opening hashbang URL in a modern browser -> rewrites to a regular URL
|
||||
|
||||
Note that in this mode, Angular intercepts all links (subject to the "Html link rewriting" rules below)
|
||||
and updates the url in a way that never performs a full page reload.
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
@@ -298,8 +306,8 @@ history API or not; the `$location` service makes this transparent to you.
|
||||
|
||||
### Html link rewriting
|
||||
|
||||
When you use HTML5 history API mode, you will need different links in different browsers, but all you
|
||||
have to do is specify regular URL links, such as: `<a href="/some?foo=bar">link</a>`
|
||||
When you use HTML5 history API mode, you will not need special hashbang links. All you have to do
|
||||
is specify regular URL links, such as: `<a href="/some?foo=bar">link</a>`
|
||||
|
||||
When a user clicks on this link,
|
||||
|
||||
@@ -314,17 +322,9 @@ reload to the original link.
|
||||
Example: `<a href="/ext/link?a=b" target="_self">link</a>`
|
||||
- Absolute links that go to a different domain<br>
|
||||
Example: `<a href="http://angularjs.org/">link</a>`
|
||||
- Links starting with '/' that lead to a different base path when base is defined<br>
|
||||
- Links starting with '/' that lead to a different base path<br>
|
||||
Example: `<a href="/not-my-base/link">link</a>`
|
||||
|
||||
When running Angular in the root of a domain, along side perhaps a normal application in the same
|
||||
directory, the "otherwise" route handler will try to handle all the URLs, including ones that map
|
||||
to static files.
|
||||
|
||||
To prevent this, you can set your base href for the app to `<base href=".">` and then prefix links
|
||||
to URLs that should be handled with `.`. Now, links to locations, which are not to be routed by Angular,
|
||||
are not prefixed with `.` and will not be intercepted by the `otherwise` rule in your `$routeProvider`.
|
||||
|
||||
|
||||
### Server side
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ initialization.
|
||||
<html xmlns:ng="http://angularjs.org" ng-app>
|
||||
<body>
|
||||
...
|
||||
<script src="angular.js">
|
||||
<script src="angular.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
@sortOrder 330
|
||||
@description
|
||||
|
||||
# HTML Compiler
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Note:** this guide is targeted towards developers who are already familiar with AngularJS basics.
|
||||
|
||||
@@ -12,7 +14,7 @@ If you want a deeper look into Angular's compilation process, you're in the righ
|
||||
</div>
|
||||
|
||||
|
||||
# Overview
|
||||
## Overview
|
||||
|
||||
Angular's {@link ng.$compile HTML compiler} allows the developer to teach the
|
||||
browser new HTML syntax. The compiler allows you to attach behavior to any HTML element or attribute
|
||||
@@ -330,7 +332,7 @@ The first issue we have to solve is that the dialog box template expects `title`
|
||||
But we would like the template's scope property `title` to be the result of interpolating the
|
||||
`<dialog>` element's `title` attribute (i.e. `"Hello {{username}}"`). Furthermore, the buttons expect
|
||||
the `onOk` and `onCancel` functions to be present in the scope. This limits the usefulness of the
|
||||
widget. To solve the mapping issue we use the `locals` to create local variables which the template
|
||||
widget. To solve the mapping issue we use the `scope` to create local variables which the template
|
||||
expects as follows:
|
||||
|
||||
```js
|
||||
|
||||
@@ -56,10 +56,10 @@ Try out the Live Preview above, and then let's walk through the example and desc
|
||||
|
||||
This looks like normal HTML, with some new markup. In Angular, a file like this is called a
|
||||
<a name="template">"{@link templates template}"</a>. When Angular starts your application, it parses and
|
||||
processes this new markup from the template using the so called <a name="compiler">"{@link compiler compiler}"</a>.
|
||||
processes this new markup from the template using the so-called <a name="compiler">"{@link compiler compiler}"</a>.
|
||||
The loaded, transformed and rendered DOM is then called the <a name="view">"view"</a>.
|
||||
|
||||
The first kind of new markup are the so called <a name="directive">"{@link directive directives}"</a>.
|
||||
The first kind of new markup are the so-called <a name="directive">"{@link directive directives}"</a>.
|
||||
They apply special behavior to attributes or elements in the HTML. In the example above we use the
|
||||
{@link ng.directive:ngApp `ng-app`} attribute, which is linked to a directive that automatically
|
||||
initializes our application. Angular also defines a directive for the {@link ng.directive:input `input`}
|
||||
@@ -89,7 +89,7 @@ A filter formats the value of an expression for display to the user.
|
||||
In the example above, the filter {@link ng.filter:currency `currency`} formats a number
|
||||
into an output that looks like money.
|
||||
|
||||
The important thing in the example is that angular provides _live_ bindings:
|
||||
The important thing in the example is that Angular provides _live_ bindings:
|
||||
Whenever the input values change, the value of the expressions are automatically
|
||||
recalculated and the DOM is updated with their values.
|
||||
The concept behind this is <a name="databinding">"{@link databinding two-way data binding}"</a>.
|
||||
@@ -150,13 +150,13 @@ different currencies and also pay the invoice.
|
||||
|
||||
What changed?
|
||||
|
||||
First, there is a new JavaScript file that contains a so called <a name="controller">"{@link controller controller}"</a>.
|
||||
First, there is a new JavaScript file that contains a so-called <a name="controller">"{@link controller controller}"</a>.
|
||||
More exactly, the file contains a constructor function that creates the actual controller instance.
|
||||
The purpose of controllers is to expose variables and functionality to expressions and directives.
|
||||
|
||||
Besides the new file that contains the controller code we also added a
|
||||
{@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.
|
||||
|
||||
|
||||
@@ -326,7 +326,7 @@ describe('state', function() {
|
||||
expect(childScope.timeOfDay).toBe('morning');
|
||||
expect(childScope.name).toBe('Mattie');
|
||||
expect(grandChildScope.timeOfDay).toBe('evening');
|
||||
expect(grandChildScope.name).toBe('Gingerbreak Baby');
|
||||
expect(grandChildScope.name).toBe('Gingerbread Baby');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -41,7 +41,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
### Other AngularJS Features
|
||||
|
||||
* **Animation:** {@link guide/animations Core concepts}, {@link ngAnimate ngAnimate API}, and [Animation in AngularJS 1.2](http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html)
|
||||
* **Security:** {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
|
||||
* **Security:** {@link guide/security Security Docs}, {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
|
||||
* **Internationalization and Localization:** {@link guide/i18n Angular Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/)
|
||||
* **Mobile:** {@link ngTouch Touch events}
|
||||
|
||||
@@ -89,11 +89,12 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
|
||||
* **Django:** [Tutorial](http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework), [Integrating AngularJS with Django](http://django-angular.readthedocs.org/en/latest/integration.html), [Getting Started with Django Rest Framework and AngularJS](http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html)
|
||||
* **FireBase:** [AngularFire](http://angularfire.com/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU)
|
||||
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/resources/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
|
||||
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/developers/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications/), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
|
||||
* **Hood.ie:** [60 Minutes to Awesome](http://www.roberthorvick.com/2013/06/30/todomvc-angularjs-hood-ie-60-minutes-to-awesome/)
|
||||
* **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610)
|
||||
* **Rails: **[Tutorial](http://coderberry.me/blog/2013/04/22/angularjs-on-rails-4-part-1/), [AngularJS with Rails4](https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4), [angularjs-rails](https://github.com/hiravgandhi/angularjs-rails)
|
||||
* **PHP: **[Building a RESTful web service](http://blog.brunoscopelliti.com/building-a-restful-web-service-with-angularjs-and-php-more-power-with-resource), [End to End with Laravel 4 (video)](http://www.youtube.com/watch?v=hqAyiqUs93c)
|
||||
* **Meteor: **[angular-meteor package](https://github.com/Urigo/angular-meteor)
|
||||
|
||||
## Learning Resources
|
||||
|
||||
@@ -105,6 +106,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/)
|
||||
@@ -113,12 +115,12 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
### Courses
|
||||
* **Free online:**
|
||||
[thinkster.io](http://thinkster.io),
|
||||
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1)
|
||||
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1),
|
||||
[CodeSchool](https://www.codeschool.com/courses/shaping-up-with-angular-js)
|
||||
* **Paid online:**
|
||||
[Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs),
|
||||
[Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/),
|
||||
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html)
|
||||
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html),
|
||||
[WintellectNOW (4 lessons)](http://www.wintellectnow.com/Course/Detail/mastering-angularjs)
|
||||
* **Paid onsite:**
|
||||
[angularbootcamp.com](http://angularbootcamp.com/)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
AngularJS is a structural framework for dynamic web apps. It lets you use HTML as your template
|
||||
language and lets you extend HTML's syntax to express your application's components clearly and
|
||||
succinctly. Angular's data binding and dependency injection eliminate much of the code you
|
||||
currently have to write. And it all happens within the browser, making it
|
||||
would otherwise have to write. And it all happens within the browser, making it
|
||||
an ideal partner with any server technology.
|
||||
|
||||
Angular is what HTML would have been had it been designed for applications. HTML is a great
|
||||
@@ -33,7 +33,7 @@ browser new syntax through a construct we call directives. Examples include:
|
||||
* Data binding, as in `{{}}`.
|
||||
* DOM control structures for repeating/hiding DOM fragments.
|
||||
* Support for forms and form validation.
|
||||
* Attaching code-behind to DOM elements.
|
||||
* Attaching new behavior to DOM elements, such as DOM event handling.
|
||||
* Grouping of HTML into reusable components.
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ Angular frees you from the following pains:
|
||||
* **Writing tons of initialization code just to get started:** Typically you need to write a lot
|
||||
of plumbing just to get a basic "Hello World" AJAX app working. With Angular you can bootstrap
|
||||
your app easily using services, which are auto-injected into your application in a
|
||||
[Guice](http://code.google.com/p/google-guice/)-like dependency-injection style. This allows you
|
||||
[Guice](https://github.com/google/guice)-like dependency-injection style. This allows you
|
||||
to get started developing features quickly. As a bonus, you get full control over the
|
||||
initialization process in automated tests.
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ linking} phase the {@link ng.$compileProvider#directive directives} set up
|
||||
render the updated value to the DOM.
|
||||
|
||||
Both controllers and directives have reference to the scope, but not to each other. This
|
||||
arrangement isolates the controller from the directive as well as from DOM. This is an important
|
||||
arrangement isolates the controller from the directive as well as from the DOM. This is an important
|
||||
point since it makes the controllers view agnostic, which greatly improves the testing story of
|
||||
the applications.
|
||||
|
||||
@@ -339,6 +339,18 @@ the dirty checking function must be efficient. Care should be taken that the dir
|
||||
function does not do any DOM access, as DOM access is orders of magnitude slower than property
|
||||
access on JavaScript object.
|
||||
|
||||
### Scope `$watch` Depths
|
||||
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-scope-watch-strategies.png">
|
||||
|
||||
Dirty checking can be done with three strategies: By reference, by collection contents, and by value. The strategies differ in the kinds of changes they detect, and in their performance characteristics.
|
||||
|
||||
- Watching *by reference* ({@link
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener)`) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object, changes inside it are not detected. This is the most efficient stategy.
|
||||
- Watching *collection contents* ({@link
|
||||
ng.$rootScope.Scope#$watchCollection scope.$watchCollection} `(watchExpression, listener)`) detects changes that occur inside an array or an object: When items are added, removed, or reordered. The detection is shallow - it does not reach into nested collections. Watching collection contents is more expensive than watching by reference, because copies of the collection contents need to be maintained. However, the strategy attempts to minimize the amount of copying required.
|
||||
- Watching *by value* ({@link
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener, true)`) detects any change in an arbitrarily nested data structure. It is the most powerful change detection strategy, but also the most expensive. A full traversal of the nested data structure is needed on each digest, and a full copy of it needs to be held in memory.
|
||||
|
||||
## Integration with the browser event loop
|
||||
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-runtime.png">
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
@ngdoc overview
|
||||
@name Security
|
||||
@sortOrder 525
|
||||
@description
|
||||
|
||||
# Security
|
||||
|
||||
This document explains some of AngularJS's security features and best practices that you should
|
||||
keep in mind as you build your application.
|
||||
|
||||
|
||||
## Expression Sandboxing
|
||||
|
||||
AngularJS's expressions are sandboxed not for security reasons, but instead to maintain a proper
|
||||
separation of application responsibilities. For example, access to `window` is disallowed
|
||||
because it makes it easy to introduce brittle global state into your application.
|
||||
|
||||
However, this sandbox is not intended to stop attackers who can edit the template before it's
|
||||
processed by Angular. It may be possible to run arbitrary JavaScript inside double-curly bindings
|
||||
if an attacker can modify them.
|
||||
|
||||
But if an attacker can change arbitrary HTML templates, there's nothing stopping them from doing:
|
||||
|
||||
```html
|
||||
<script>somethingEvil();</script>
|
||||
```
|
||||
|
||||
It's better to design your application in such a way that users cannot change client-side templates.
|
||||
For instance:
|
||||
|
||||
* Do not mix client and server templates
|
||||
* Do not use user input to generate templates dynamically
|
||||
* Do not run user input through `$scope.$eval`
|
||||
* Consider using {@link ng.directive:ngCsp CSP} (but don't rely only on CSP)
|
||||
|
||||
## Mixing client-side and server-side templates
|
||||
|
||||
In general, we recommend against this because it can create unintended XSS vectors.
|
||||
|
||||
However, it's ok to mix server-side templating in the bootstrap template (`index.html`) as long
|
||||
as user input cannot be used on the server to output html that would then be processed by Angular
|
||||
in a way that would cause allow for arbitrary code execution.
|
||||
|
||||
For instance, you can use server-side templating to dynamically generate CSS, URLs, etc, but not
|
||||
for generating templates that are bootstrapped/compiled by Angular.
|
||||
|
||||
|
||||
## Reporting a security issue
|
||||
|
||||
Email us at [security@angularjs.org](mailto:security@angularjs.org) to report any potential
|
||||
security issues in AngularJS.
|
||||
|
||||
Please keep in mind the above points about Angular's expression language.
|
||||
|
||||
|
||||
## See also
|
||||
|
||||
* {@link ng.directive:ngCsp Content Security Policy}
|
||||
* {@link ng.$sce Strict Contextual Escaping}
|
||||
* {@link ngSanitize.$sanitize $sanitize}
|
||||
@@ -268,8 +268,8 @@ logic, further simplifying the application logic.
|
||||
|
||||
```js
|
||||
myModule.filter('length', function() {
|
||||
return function(text){
|
||||
return (''+(text||'')).length;
|
||||
return function(text) {
|
||||
return ('' + (text || '')).length;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ development web server, run tests, and generate distributable files. Depending o
|
||||
pre-packaged bundle.
|
||||
|
||||
* [Java](http://www.java.com): We minify JavaScript using our
|
||||
[Closure Tools](https://developers.google.com/closure/) jar. Make sure you have Java (version 6 or higher) installed
|
||||
[Closure Tools](https://developers.google.com/closure/) jar. Make sure you have Java (version 7 or higher) installed
|
||||
and included in your [PATH](http://docs.oracle.com/javase/tutorial/essential/environment/paths.html) variable.
|
||||
|
||||
* [Grunt](http://gruntjs.com): We use Grunt as our build system. Install the grunt command-line tool globally with:
|
||||
|
||||
@@ -22,7 +22,7 @@ So it's definitely not a plugin or some other native browser extension.
|
||||
|
||||
### Is AngularJS a templating system?
|
||||
|
||||
At the highest level, Angular does look like a just another templating system. But there is one
|
||||
At the highest level, Angular does look like just another templating system. But there is one
|
||||
important reason why the Angular templating system is different, that makes it very good fit for
|
||||
application development: bidirectional data binding. The template is compiled in the browser and
|
||||
the compilation step produces a live view. This means you, the developers, don't need to write
|
||||
@@ -39,7 +39,7 @@ for server-side communication.
|
||||
|
||||
AngularJS was designed to be compatible with other security measures like Content Security Policy
|
||||
(CSP), HTTPS (SSL/TLS) and server-side authentication and authorization that greatly reduce the
|
||||
possible attack vectors and we highly recommended their use.
|
||||
possible attack vectors and we highly recommend their use.
|
||||
|
||||
|
||||
### Can I download the source, build, and host the AngularJS environment locally?
|
||||
|
||||
@@ -110,8 +110,9 @@ suggested solution is to also install the `nodejs-legacy` apt package, which ren
|
||||
`nodejs`.
|
||||
|
||||
```
|
||||
apt-get install nodejs-legacy
|
||||
apt-get install nodejs-legacy npm
|
||||
nodejs --version
|
||||
npm --version
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ really is that easy to set up any functional, readable, end-to-end test.
|
||||
### Running End to End Tests with Protractor
|
||||
Even though the syntax of this test looks very much like our controller unit test written with
|
||||
Jasmine, the end-to-end test uses APIs of [Protractor](https://github.com/angular/protractor). Read
|
||||
about the Protractor APIs at https://github.com/angular/protractor/blob/master/docs/api.md.
|
||||
about the Protractor APIs at http://angular.github.io/protractor/#/api.
|
||||
|
||||
Much like Karma is the test runner for unit tests, we use Protractor to run end-to-end tests.
|
||||
Try it with `npm run protractor`. End-to-end tests are slow, so unlike with unit tests, Protractor
|
||||
|
||||
@@ -182,7 +182,7 @@ You can now rerun `npm run protractor` to see the tests run.
|
||||
# Experiments
|
||||
|
||||
* In the `PhoneListCtrl` controller, remove the statement that sets the `orderProp` value and
|
||||
you'll see that Angular will temporarily add a new "unknown" option to the drop-down list and the
|
||||
you'll see that Angular will temporarily add a new blank ("unknown") option to the drop-down list and the
|
||||
ordering will default to unordered/natural order.
|
||||
|
||||
* Add an `{{orderProp}}` binding into the `index.html` template to display its current value as
|
||||
|
||||
@@ -236,7 +236,9 @@ the response is received:
|
||||
```
|
||||
|
||||
* We flush the request queue in the browser by calling `$httpBackend.flush()`. This causes the
|
||||
promise returned by the `$http` service to be resolved with the trained response.
|
||||
promise returned by the `$http` service to be resolved with the trained response. See
|
||||
'Flushing HTTP requests' in the {@link ngMock.$httpBackend mock $httpBackend} documentation for
|
||||
a full explanation of why this is necessary.
|
||||
|
||||
* We make the assertions, verifying that the phone model now exists on the scope.
|
||||
|
||||
@@ -256,8 +258,8 @@ You should now see the following output in the Karma tab:
|
||||
|
||||
# Experiments
|
||||
|
||||
* At the bottom of `index.html`, add a `<pre>{{phones | json}}</pre>` binding to see the list of phones
|
||||
displayed in json format.
|
||||
* At the bottom of `index.html`, add a `<pre>{{phones | filter:query | orderBy:orderProp | json}}</pre>`
|
||||
binding to see the list of phones displayed in json format.
|
||||
|
||||
* In the `PhoneListCtrl` controller, pre-process the http response by limiting the number of phones
|
||||
to the first 5 in the list. Use the following code in the `$http` callback:
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
In this step, you will learn how to create a layout template and how to build an app that has
|
||||
multiple views by adding routing, using an Angular module called 'ngRoute'.
|
||||
|
||||
* When you now navigate to `app/index.html`, you are redirected to `app/index.html#/phones`
|
||||
* When you now navigate to `app/index.html`, you are redirected to `app/index.html/#/phones`
|
||||
and the phone list appears in the browser.
|
||||
* When you click on a phone link the url changes to one specific to that phone and the stub of a
|
||||
phone detail page is displayed.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ we require, so in these cases, we can add a callback to process the server respo
|
||||
|
||||
## Test
|
||||
|
||||
Because we're now using the {@link ngResource ngResource} module, it's necessary to also need to
|
||||
Because we're now using the {@link ngResource ngResource} module, it's necessary to
|
||||
update the Karma config file with angular-resource so the new tests will pass.
|
||||
|
||||
__`test/karma.conf.js`:__
|
||||
|
||||
@@ -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 api/ng.directive:ngRepeat `ngRepeat`} or adding and removing classes on
|
||||
@@ -368,7 +368,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 |
@@ -12,7 +12,7 @@ set -e
|
||||
# before_script:
|
||||
# - curl https://gist.github.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash
|
||||
|
||||
CONNECT_URL="https://d2nkw87yt5k0to.cloudfront.net/downloads/sc-4.3-linux.tar.gz"
|
||||
CONNECT_URL="https://saucelabs.com/downloads/sc-4.3-linux.tar.gz"
|
||||
CONNECT_DIR="/tmp/sauce-connect-$RANDOM"
|
||||
CONNECT_DOWNLOAD="sc-4.3-linux.tar.gz"
|
||||
|
||||
|
||||
@@ -91,15 +91,6 @@ var getTaggedVersion = function() {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stable versions have an even minor version and have no prerelease
|
||||
* @param {SemVer} version The version to test
|
||||
* @return {Boolean} True if the version is stable
|
||||
*/
|
||||
var isStable = function(version) {
|
||||
return semver.satisfies(version, '1.0 || 1.2') && version.prerelease.length === 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a collection of all the previous versions sorted by semantic version
|
||||
* @return {Array.<SemVer>} The collection of previous versions
|
||||
@@ -119,8 +110,6 @@ var getPreviousVersions = function() {
|
||||
})
|
||||
.filter()
|
||||
.map(function(version) {
|
||||
version.isStable = isStable(version);
|
||||
|
||||
version.docsUrl = 'http://code.angularjs.org/' + version.version + '/docs';
|
||||
// Versions before 1.0.2 had a different docs folder name
|
||||
if ( version.major < 1 || (version.major === 1 && version.minor === 0 && version.dot < 2 ) ) {
|
||||
|
||||
Generated
+43
-9
@@ -1133,7 +1133,7 @@
|
||||
"version": "0.0.2"
|
||||
},
|
||||
"dgeni": {
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"dependencies": {
|
||||
"dependency-graph": {
|
||||
"version": "0.1.0",
|
||||
@@ -1254,7 +1254,7 @@
|
||||
}
|
||||
},
|
||||
"dgeni-packages": {
|
||||
"version": "0.10.0",
|
||||
"version": "0.10.1",
|
||||
"dependencies": {
|
||||
"catharsis": {
|
||||
"version": "0.7.1"
|
||||
@@ -1323,6 +1323,40 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "3.7.3",
|
||||
"dependencies": {
|
||||
"domhandler": {
|
||||
"version": "2.2.0"
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.5.0"
|
||||
},
|
||||
"domelementtype": {
|
||||
"version": "1.1.1"
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.1.13",
|
||||
"dependencies": {
|
||||
"core-util-is": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1"
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "2.4.1"
|
||||
},
|
||||
@@ -1886,7 +1920,7 @@
|
||||
},
|
||||
"grunt-jasmine-node": {
|
||||
"version": "0.1.0",
|
||||
"from": "grunt-jasmine-node@git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
|
||||
"from": "grunt-jasmine-node@git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7",
|
||||
"resolved": "git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7"
|
||||
},
|
||||
"grunt-jscs-checker": {
|
||||
@@ -4278,7 +4312,7 @@
|
||||
}
|
||||
},
|
||||
"protractor": {
|
||||
"version": "1.3.1",
|
||||
"version": "1.4.0",
|
||||
"dependencies": {
|
||||
"request": {
|
||||
"version": "2.36.0",
|
||||
@@ -4302,7 +4336,7 @@
|
||||
"version": "0.12.1",
|
||||
"dependencies": {
|
||||
"punycode": {
|
||||
"version": "1.3.1"
|
||||
"version": "1.3.2"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4310,7 +4344,7 @@
|
||||
"version": "0.1.4",
|
||||
"dependencies": {
|
||||
"combined-stream": {
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.7",
|
||||
"dependencies": {
|
||||
"delayed-stream": {
|
||||
"version": "0.0.5"
|
||||
@@ -4365,7 +4399,7 @@
|
||||
}
|
||||
},
|
||||
"selenium-webdriver": {
|
||||
"version": "2.43.5",
|
||||
"version": "2.44.0",
|
||||
"dependencies": {
|
||||
"tmp": {
|
||||
"version": "0.0.24"
|
||||
@@ -4374,7 +4408,7 @@
|
||||
"version": "0.4.4",
|
||||
"dependencies": {
|
||||
"sax": {
|
||||
"version": "0.6.0"
|
||||
"version": "0.6.1"
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "2.4.4",
|
||||
@@ -4437,7 +4471,7 @@
|
||||
"version": "2.4.1"
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.8",
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.1.32",
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@
|
||||
"marked": "~0.3.0",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"promises-aplus-tests": "~1.3.2",
|
||||
"protractor": "1.3.1",
|
||||
"protractor": "1.4.0",
|
||||
"q": "~1.0.0",
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
|
||||
@@ -71,6 +71,8 @@ function prepare {
|
||||
cd $TMP_DIR/bower-$repo
|
||||
replaceJsonProp "bower.json" "version" ".*" "$NEW_VERSION"
|
||||
replaceJsonProp "bower.json" "angular.*" ".*" "$NEW_VERSION"
|
||||
replaceJsonProp "package.json" "version" ".*" "$NEW_VERSION"
|
||||
replaceJsonProp "package.json" "angular.*" ".*" "$NEW_VERSION"
|
||||
|
||||
git add -A
|
||||
|
||||
@@ -88,6 +90,24 @@ function publish {
|
||||
cd $TMP_DIR/bower-$repo
|
||||
git push origin master
|
||||
git push origin v$NEW_VERSION
|
||||
|
||||
# don't publish every build to npm
|
||||
if [ "${NEW_VERSION/+sha}" = "$NEW_VERSION" ] ; then
|
||||
if [ "${NEW_VERSION/-}" = "$NEW_VERSION" ] ; then
|
||||
if [[ $NEW_VERSION =~ ^1\.2\.[0-9]+$ ]] ; then
|
||||
# publish 1.2.x releases with the appropriate tag
|
||||
# this ensures that `npm install` by default will not grab `1.2.x` releases
|
||||
npm publish --tag=old
|
||||
else
|
||||
# publish releases as "latest"
|
||||
npm publish
|
||||
fi
|
||||
else
|
||||
# publish prerelease builds with the beta tag
|
||||
npm publish --tag=beta
|
||||
fi
|
||||
fi
|
||||
|
||||
cd $SCRIPT_DIR
|
||||
done
|
||||
}
|
||||
|
||||
+2
-2
@@ -155,8 +155,8 @@ if ('i' !== 'I'.toLowerCase()) {
|
||||
}
|
||||
|
||||
|
||||
var /** holds major version number for IE or NaN for real browsers */
|
||||
msie,
|
||||
var
|
||||
msie, // holds major version number for IE, or NaN if UA is not IE.
|
||||
jqLite, // delay binding since jQuery could be loaded after us.
|
||||
jQuery, // delay binding
|
||||
slice = [].slice,
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Creates an injector function that can be used for retrieving services as well as for
|
||||
* Creates an injector object that can be used for retrieving services as well as for
|
||||
* dependency injection (see {@link guide/di dependency injection}).
|
||||
*
|
||||
|
||||
* @param {Array.<string|Function>} modules A list of module functions or their aliases. See
|
||||
* {@link angular.module}. The `ng` module must be explicitly added.
|
||||
* @returns {function()} Injector function. See {@link auto.$injector $injector}.
|
||||
* @returns {injector} Injector object. See {@link auto.$injector $injector}.
|
||||
*
|
||||
* @example
|
||||
* Typical usage
|
||||
@@ -101,7 +101,6 @@ function annotate(fn) {
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $injector
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
*
|
||||
@@ -116,7 +115,7 @@ function annotate(fn) {
|
||||
* expect($injector.get('$injector')).toBe($injector);
|
||||
* expect($injector.invoke(function($injector){
|
||||
* return $injector;
|
||||
* }).toBe($injector);
|
||||
* })).toBe($injector);
|
||||
* ```
|
||||
*
|
||||
* # Injection Function Annotation
|
||||
@@ -183,8 +182,8 @@ function annotate(fn) {
|
||||
* @description
|
||||
* Allows the user to query if the particular service exists.
|
||||
*
|
||||
* @param {string} Name of the service to query.
|
||||
* @returns {boolean} returns true if injector has given service.
|
||||
* @param {string} name Name of the service to query.
|
||||
* @returns {boolean} `true` if injector has given service.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@
|
||||
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
|
||||
* - [`clone()`](http://api.jquery.com/clone/)
|
||||
* - [`contents()`](http://api.jquery.com/contents/)
|
||||
* - [`css()`](http://api.jquery.com/css/)
|
||||
* - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()`
|
||||
* - [`data()`](http://api.jquery.com/data/)
|
||||
* - [`empty()`](http://api.jquery.com/empty/)
|
||||
* - [`eq()`](http://api.jquery.com/eq/)
|
||||
|
||||
@@ -53,6 +53,19 @@ function $AnchorScrollProvider() {
|
||||
|
||||
var autoScrollingEnabled = true;
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $anchorScrollProvider#disableAutoScrolling
|
||||
*
|
||||
* @description
|
||||
* 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.
|
||||
*
|
||||
* If automatic scrolling is disabled, one must explicitly call
|
||||
* {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the
|
||||
* current hash.
|
||||
*/
|
||||
this.disableAutoScrolling = function() {
|
||||
autoScrollingEnabled = false;
|
||||
};
|
||||
|
||||
+14
-7
@@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
/* global stripHash: true */
|
||||
|
||||
/**
|
||||
* ! This is a private undocumented service !
|
||||
@@ -124,7 +125,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
|
||||
var lastBrowserUrl = location.href,
|
||||
baseElement = document.find('base'),
|
||||
newLocation = null;
|
||||
reloadLocation = null;
|
||||
|
||||
/**
|
||||
* @name $browser#url
|
||||
@@ -153,8 +154,13 @@ function Browser(window, document, $log, $sniffer) {
|
||||
// setter
|
||||
if (url) {
|
||||
if (lastBrowserUrl == url) return;
|
||||
var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
|
||||
lastBrowserUrl = url;
|
||||
if ($sniffer.history) {
|
||||
// Don't use history API if only the hash changed
|
||||
// due to a bug in IE10/IE11 which leads
|
||||
// to not firing a `hashchange` nor `popstate` event
|
||||
// in some cases (see #9143).
|
||||
if (!sameBase && $sniffer.history) {
|
||||
if (replace) history.replaceState(null, '', url);
|
||||
else {
|
||||
history.pushState(null, '', url);
|
||||
@@ -162,7 +168,9 @@ function Browser(window, document, $log, $sniffer) {
|
||||
baseElement.attr('href', baseElement.attr('href'));
|
||||
}
|
||||
} else {
|
||||
newLocation = url;
|
||||
if (!sameBase) {
|
||||
reloadLocation = url;
|
||||
}
|
||||
if (replace) {
|
||||
location.replace(url);
|
||||
} else {
|
||||
@@ -172,10 +180,10 @@ function Browser(window, document, $log, $sniffer) {
|
||||
return self;
|
||||
// getter
|
||||
} else {
|
||||
// - newLocation is a workaround for an IE7-9 issue with location.replace and location.href
|
||||
// methods not updating location.href synchronously.
|
||||
// - reloadLocation is needed as browsers don't allow to read out
|
||||
// the new location.href if a reload happened.
|
||||
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
|
||||
return newLocation || location.href.replace(/%27/g,"'");
|
||||
return reloadLocation || location.href.replace(/%27/g,"'");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -183,7 +191,6 @@ function Browser(window, document, $log, $sniffer) {
|
||||
urlChangeInit = false;
|
||||
|
||||
function fireUrlChange() {
|
||||
newLocation = null;
|
||||
if (lastBrowserUrl == self.url()) return;
|
||||
|
||||
lastBrowserUrl = self.url();
|
||||
|
||||
@@ -369,7 +369,8 @@ function $CacheFactoryProvider() {
|
||||
* ```
|
||||
*
|
||||
* **Note:** the `script` tag containing the template does not need to be included in the `head` of
|
||||
* the document, but it must be below the `ng-app` definition.
|
||||
* the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
|
||||
* element with ng-app attribute), otherwise the template will be ignored.
|
||||
*
|
||||
* Adding via the $templateCache service:
|
||||
*
|
||||
|
||||
+22
-9
@@ -255,8 +255,13 @@
|
||||
* scope. This makes it possible for the widget to have private state, and the transclusion to
|
||||
* be bound to the parent (pre-`isolate`) scope.
|
||||
*
|
||||
* * `true` - transclude the content of the directive.
|
||||
* * `'element'` - transclude the whole element including any directives defined at lower priority.
|
||||
* There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
|
||||
* directive's element or the entire element:
|
||||
*
|
||||
* * `true` - transclude the content (i.e. the child nodes) of the directive's element.
|
||||
* * `'element'` - transclude the whole of the directive's element including any directives on this
|
||||
* element that defined at a lower priority than this directive. When used, the `template`
|
||||
* property is ignored.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** When testing an element transclude directive you must not place the directive at the root of the
|
||||
@@ -648,6 +653,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
};
|
||||
|
||||
Attributes.prototype = {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compile.directive.Attributes#$normalize
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
|
||||
* `data-`) to its normalized, camelCase form.
|
||||
*
|
||||
* Also there is special case for Moz prefix starting with upper case letter.
|
||||
*
|
||||
* For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
|
||||
*
|
||||
* @param {string} name Name to normalize
|
||||
*/
|
||||
$normalize: directiveNormalize,
|
||||
|
||||
|
||||
@@ -1981,13 +2001,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
|
||||
/**
|
||||
* Converts all accepted directives format into proper directive name.
|
||||
* All of these will become 'myDirective':
|
||||
* my:Directive
|
||||
* my-directive
|
||||
* x-my-directive
|
||||
* data-my:directive
|
||||
*
|
||||
* Also there is special case for Moz prefix starting with upper case letter.
|
||||
* @param name Name to normalize
|
||||
*/
|
||||
function directiveNormalize(name) {
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
* make the link go to the wrong URL if the user clicks it before
|
||||
* Angular has a chance to replace the `{{hash}}` markup with its
|
||||
* value. Until Angular replaces the markup the link will be broken
|
||||
* and will most likely return a 404 error.
|
||||
*
|
||||
* The `ngHref` directive solves this problem.
|
||||
* and will most likely return a 404 error. The `ngHref` directive
|
||||
* solves this problem.
|
||||
*
|
||||
* The wrong way to write it:
|
||||
* ```html
|
||||
|
||||
@@ -969,7 +969,7 @@ var VALID_CLASS = 'ng-valid',
|
||||
*
|
||||
* We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
|
||||
* module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
|
||||
* However, as we are using `$sce` the model can still decide to to provide unsafe content if it marks
|
||||
* However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
|
||||
* that content using the `$sce` service.
|
||||
*
|
||||
* <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
|
||||
@@ -1001,7 +1001,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
|
||||
|
||||
@@ -1284,7 +1284,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
*
|
||||
* For best practices on using `ngModel`, see:
|
||||
*
|
||||
* - [https://github.com/angular/angular.js/wiki/Understanding-Scopes]
|
||||
* - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
|
||||
*
|
||||
* For basic examples, how to use `ngModel`, see:
|
||||
*
|
||||
|
||||
@@ -139,7 +139,10 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
|
||||
* element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link
|
||||
* ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize`
|
||||
* is available, for example, by including {@link ngSanitize} in your module's dependencies (not in
|
||||
* core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to
|
||||
* core Angular). In order to use {@link ngSanitize} in your module's dependencies, you need to
|
||||
* include "angular-sanitize.js" in your application.
|
||||
*
|
||||
* You may also bypass sanitization for values you know are safe. To do so, bind to
|
||||
* an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
|
||||
* under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}.
|
||||
*
|
||||
|
||||
@@ -33,10 +33,8 @@
|
||||
</example>
|
||||
*/
|
||||
/*
|
||||
* A directive that allows creation of custom onclick handlers that are defined as angular
|
||||
* expressions and are compiled and executed within the current scope.
|
||||
*
|
||||
* Events that are handled via these handler are always configured not to propagate further.
|
||||
* A collection of directives that allows creation of custom event handlers that are defined as
|
||||
* angular expressions and are compiled and executed within the current scope.
|
||||
*/
|
||||
var ngEventDirectives = {};
|
||||
|
||||
@@ -54,7 +52,11 @@ forEach(
|
||||
ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
|
||||
return {
|
||||
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], /* expensiveChecks */ true);
|
||||
return function ngEventHandler(scope, element) {
|
||||
element.on(eventName, function(event) {
|
||||
var callback = function() {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
|
||||
* is created when the element is restored. The scope created within `ngIf` inherits from
|
||||
* its parent scope using
|
||||
* [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance).
|
||||
* [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
|
||||
* An important implication of this is if `ngModel` is used within `ngIf` to bind to
|
||||
* a javascript primitive defined in the parent scope. In this case any modifications made to the
|
||||
* variable within the child scope will override (hide) the value in the parent scope.
|
||||
@@ -33,8 +33,8 @@
|
||||
* and `leave` effects.
|
||||
*
|
||||
* @animations
|
||||
* enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container
|
||||
* leave - happens just before the ngIf contents are removed from the DOM
|
||||
* enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
|
||||
* leave - happens just before the `ngIf` contents are removed from the DOM
|
||||
*
|
||||
* @element ANY
|
||||
* @scope
|
||||
|
||||
@@ -40,7 +40,6 @@ var scriptDirective = ['$templateCache', function($templateCache) {
|
||||
compile: function(element, attr) {
|
||||
if (attr.type == 'text/ng-template') {
|
||||
var templateUrl = attr.id,
|
||||
// IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent
|
||||
text = element[0].text;
|
||||
|
||||
$templateCache.put(templateUrl, text);
|
||||
|
||||
@@ -551,6 +551,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
lastElement = existingOption.element;
|
||||
if (existingOption.label !== option.label) {
|
||||
lastElement.text(existingOption.label = option.label);
|
||||
lastElement.prop('label', existingOption.label);
|
||||
}
|
||||
if (existingOption.id !== option.id) {
|
||||
lastElement.val(existingOption.id = option.id);
|
||||
@@ -580,6 +581,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);
|
||||
}
|
||||
|
||||
|
||||
+11
-11
@@ -154,17 +154,17 @@ function filterFilter() {
|
||||
}
|
||||
|
||||
var search = function(obj, text){
|
||||
if (typeof text == 'string' && text.charAt(0) === '!') {
|
||||
if (typeof text === 'string' && text.charAt(0) === '!') {
|
||||
return !search(obj, text.substr(1));
|
||||
}
|
||||
switch (typeof obj) {
|
||||
case "boolean":
|
||||
case "number":
|
||||
case "string":
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'string':
|
||||
return comparator(obj, text);
|
||||
case "object":
|
||||
case 'object':
|
||||
switch (typeof text) {
|
||||
case "object":
|
||||
case 'object':
|
||||
return comparator(obj, text);
|
||||
default:
|
||||
for ( var objKey in obj) {
|
||||
@@ -175,7 +175,7 @@ function filterFilter() {
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
case "array":
|
||||
case 'array':
|
||||
for ( var i = 0; i < obj.length; i++) {
|
||||
if (search(obj[i], text)) {
|
||||
return true;
|
||||
@@ -187,13 +187,13 @@ function filterFilter() {
|
||||
}
|
||||
};
|
||||
switch (typeof expression) {
|
||||
case "boolean":
|
||||
case "number":
|
||||
case "string":
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'string':
|
||||
// Set up expression object and fall through
|
||||
expression = {$:expression};
|
||||
// jshint -W086
|
||||
case "object":
|
||||
case 'object':
|
||||
// jshint +W086
|
||||
for (var key in expression) {
|
||||
(function(path) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* correctly, make sure they are actually being saved as numbers and not strings.
|
||||
*
|
||||
* @param {Array} array The array to sort.
|
||||
* @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be
|
||||
* @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
|
||||
* used by the comparator to determine the order of elements.
|
||||
*
|
||||
* Can be one of:
|
||||
@@ -24,10 +24,13 @@
|
||||
* is interpreted as a property name to be used in comparisons (for example `"special name"`
|
||||
* to sort object by the value of their `special name` property). An expression can be
|
||||
* optionally prefixed with `+` or `-` to control ascending or descending sort order
|
||||
* (for example, `+name` or `-name`).
|
||||
* (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
|
||||
* element itself is used to compare where sorting.
|
||||
* - `Array`: An array of function or string predicates. The first predicate in the array
|
||||
* is used for sorting, but when two items are equivalent, the next predicate is used.
|
||||
*
|
||||
* If the predicate is missing or empty then it defaults to `'+'`.
|
||||
*
|
||||
* @param {boolean=} reverse Reverse the order of the array.
|
||||
* @returns {Array} Sorted copy of the source array.
|
||||
*
|
||||
@@ -116,8 +119,8 @@ orderByFilter.$inject = ['$parse'];
|
||||
function orderByFilter($parse){
|
||||
return function(array, sortPredicate, reverseOrder) {
|
||||
if (!(isArrayLike(array))) return array;
|
||||
if (!sortPredicate) return array;
|
||||
sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
|
||||
if (sortPredicate.length === 0) { sortPredicate = ['+']; }
|
||||
sortPredicate = map(sortPredicate, function(predicate){
|
||||
var descending = false, get = predicate || identity;
|
||||
if (isString(predicate)) {
|
||||
@@ -125,6 +128,12 @@ function orderByFilter($parse){
|
||||
descending = predicate.charAt(0) == '-';
|
||||
predicate = predicate.substring(1);
|
||||
}
|
||||
if ( predicate === '' ) {
|
||||
// Effectively no predicate was passed so we compare identity
|
||||
return reverseComparator(function(a,b) {
|
||||
return compare(a, b);
|
||||
}, descending);
|
||||
}
|
||||
get = $parse(predicate);
|
||||
if (get.constant) {
|
||||
var key = get();
|
||||
@@ -137,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++) {
|
||||
|
||||
+50
-13
@@ -144,9 +144,18 @@ function $HttpProvider() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Are ordered by request, i.e. they are applied in the same order as the
|
||||
* @ngdoc property
|
||||
* @name $httpProvider#interceptors
|
||||
* @description
|
||||
*
|
||||
* Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
|
||||
* pre-processing of request or postprocessing of responses.
|
||||
*
|
||||
* These service factories are ordered by request, i.e. they are applied in the same order as the
|
||||
* array, on request, but reverse order, on response.
|
||||
*/
|
||||
*
|
||||
* {@link ng.$http#interceptors Interceptors detailed info}
|
||||
**/
|
||||
var interceptorFactories = this.interceptors = [];
|
||||
|
||||
/**
|
||||
@@ -308,6 +317,21 @@ function $HttpProvider() {
|
||||
* In addition, you can supply a `headers` property in the config object passed when
|
||||
* calling `$http(config)`, which overrides the defaults without changing them globally.
|
||||
*
|
||||
* To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
|
||||
* Use the `headers` property, setting the desired header to `undefined`. For example:
|
||||
*
|
||||
* ```js
|
||||
* var req = {
|
||||
* method: 'POST',
|
||||
* url: 'http://example.com',
|
||||
* headers: {
|
||||
* 'Content-Type': undefined
|
||||
* },
|
||||
* data: { test: 'test' },
|
||||
* }
|
||||
*
|
||||
* $http(req).success(function(){...}).error(function(){...});
|
||||
* ```
|
||||
*
|
||||
* # Transforming Requests and Responses
|
||||
*
|
||||
@@ -887,18 +911,31 @@ function $HttpProvider() {
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {HttpPromise} Future object
|
||||
*/
|
||||
createShortMethodsWithData('post', 'put');
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @name $http#defaults
|
||||
*
|
||||
* @description
|
||||
* Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
|
||||
* default headers, withCredentials as well as request and response transformations.
|
||||
*
|
||||
* See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
|
||||
*/
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $http#patch
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `PATCH` request.
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {*} data Request content
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {HttpPromise} Future object
|
||||
*/
|
||||
createShortMethodsWithData('post', 'put', 'patch');
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @name $http#defaults
|
||||
*
|
||||
* @description
|
||||
* Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
|
||||
* default headers, withCredentials as well as request and response transformations.
|
||||
*
|
||||
* See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
|
||||
*/
|
||||
$http.defaults = defaults;
|
||||
|
||||
|
||||
|
||||
+24
-24
@@ -57,33 +57,33 @@ function $IntervalProvider() {
|
||||
* // Don't start a new fight if we are already fighting
|
||||
* if ( angular.isDefined(stop) ) return;
|
||||
*
|
||||
* stop = $interval(function() {
|
||||
* if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
|
||||
* $scope.blood_1 = $scope.blood_1 - 3;
|
||||
* $scope.blood_2 = $scope.blood_2 - 4;
|
||||
* } else {
|
||||
* $scope.stopFight();
|
||||
* stop = $interval(function() {
|
||||
* if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
|
||||
* $scope.blood_1 = $scope.blood_1 - 3;
|
||||
* $scope.blood_2 = $scope.blood_2 - 4;
|
||||
* } else {
|
||||
* $scope.stopFight();
|
||||
* }
|
||||
* }, 100);
|
||||
* };
|
||||
*
|
||||
* $scope.stopFight = function() {
|
||||
* if (angular.isDefined(stop)) {
|
||||
* $interval.cancel(stop);
|
||||
* stop = undefined;
|
||||
* }
|
||||
* }, 100);
|
||||
* };
|
||||
* };
|
||||
*
|
||||
* $scope.stopFight = function() {
|
||||
* if (angular.isDefined(stop)) {
|
||||
* $interval.cancel(stop);
|
||||
* stop = undefined;
|
||||
* }
|
||||
* };
|
||||
* $scope.resetFight = function() {
|
||||
* $scope.blood_1 = 100;
|
||||
* $scope.blood_2 = 120;
|
||||
* };
|
||||
*
|
||||
* $scope.resetFight = function() {
|
||||
* $scope.blood_1 = 100;
|
||||
* $scope.blood_2 = 120;
|
||||
* };
|
||||
*
|
||||
* $scope.$on('$destroy', function() {
|
||||
* // Make sure that the interval is destroyed too
|
||||
* $scope.stopFight();
|
||||
* });
|
||||
* }])
|
||||
* $scope.$on('$destroy', function() {
|
||||
* // Make sure that the interval is destroyed too
|
||||
* $scope.stopFight();
|
||||
* });
|
||||
* }])
|
||||
* // Register the 'myCurrentTime' directive factory method.
|
||||
* // We inject $interval and dateFilter service since the factory method is DI.
|
||||
* .directive('myCurrentTime', ['$interval', 'dateFilter',
|
||||
|
||||
+40
-57
@@ -127,21 +127,26 @@ function LocationHtml5Url(appBase, basePrefix) {
|
||||
this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
|
||||
};
|
||||
|
||||
this.$$rewrite = function(url) {
|
||||
this.$$parseLinkUrl = function(url, relHref) {
|
||||
var appUrl, prevAppUrl;
|
||||
var rewrittenUrl;
|
||||
|
||||
if ( (appUrl = beginsWith(appBase, url)) !== undefined ) {
|
||||
prevAppUrl = appUrl;
|
||||
if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) {
|
||||
return appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
|
||||
rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
|
||||
} else {
|
||||
return appBase + prevAppUrl;
|
||||
rewrittenUrl = appBase + prevAppUrl;
|
||||
}
|
||||
} else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) {
|
||||
return appBaseNoFile + appUrl;
|
||||
rewrittenUrl = appBaseNoFile + appUrl;
|
||||
} else if (appBaseNoFile == url + '/') {
|
||||
return appBaseNoFile;
|
||||
rewrittenUrl = appBaseNoFile;
|
||||
}
|
||||
if (rewrittenUrl) {
|
||||
this.$$parse(rewrittenUrl);
|
||||
}
|
||||
return !!rewrittenUrl;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -231,10 +236,12 @@ function LocationHashbangUrl(appBase, hashPrefix) {
|
||||
this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
|
||||
};
|
||||
|
||||
this.$$rewrite = function(url) {
|
||||
this.$$parseLinkUrl = function(url, relHref) {
|
||||
if(stripHash(appBase) == stripHash(url)) {
|
||||
return url;
|
||||
this.$$parse(url);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -254,16 +261,21 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) {
|
||||
|
||||
var appBaseNoFile = stripFile(appBase);
|
||||
|
||||
this.$$rewrite = function(url) {
|
||||
this.$$parseLinkUrl = function(url, relHref) {
|
||||
var rewrittenUrl;
|
||||
var appUrl;
|
||||
|
||||
if ( appBase == stripHash(url) ) {
|
||||
return url;
|
||||
rewrittenUrl = url;
|
||||
} else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) {
|
||||
return appBase + hashPrefix + appUrl;
|
||||
rewrittenUrl = appBase + hashPrefix + appUrl;
|
||||
} else if ( appBaseNoFile === url + '/') {
|
||||
return appBaseNoFile;
|
||||
rewrittenUrl = appBaseNoFile;
|
||||
}
|
||||
if (rewrittenUrl) {
|
||||
this.$$parse(rewrittenUrl);
|
||||
}
|
||||
return !!rewrittenUrl;
|
||||
};
|
||||
|
||||
this.$$compose = function() {
|
||||
@@ -391,7 +403,7 @@ LocationHashbangInHtml5Url.prototype =
|
||||
* @return {string} path
|
||||
*/
|
||||
path: locationGetterSetter('$$path', function(path) {
|
||||
path = path ? path.toString() : '';
|
||||
path = path !== null ? path.toString() : '';
|
||||
return path.charAt(0) == '/' ? path : '/' + path;
|
||||
}),
|
||||
|
||||
@@ -488,7 +500,7 @@ LocationHashbangInHtml5Url.prototype =
|
||||
* @return {string} hash
|
||||
*/
|
||||
hash: locationGetterSetter('$$hash', function(hash) {
|
||||
return hash ? hash.toString() : '';
|
||||
return hash !== null ? hash.toString() : '';
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -636,7 +648,7 @@ function $LocationProvider(){
|
||||
LocationMode = LocationHashbangUrl;
|
||||
}
|
||||
$location = new LocationMode(appBase, '#' + hashPrefix);
|
||||
$location.$$parse($location.$$rewrite(initialUrl));
|
||||
$location.$$parseLinkUrl(initialUrl, initialUrl);
|
||||
|
||||
var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
|
||||
|
||||
@@ -655,6 +667,9 @@ function $LocationProvider(){
|
||||
}
|
||||
|
||||
var absHref = elm.prop('href');
|
||||
// get the actual href attribute - see
|
||||
// http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
|
||||
var relHref = elm.attr('href') || elm.attr('xlink:href');
|
||||
|
||||
if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
|
||||
// SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
|
||||
@@ -665,50 +680,18 @@ function $LocationProvider(){
|
||||
// Ignore when url is started with javascript: or mailto:
|
||||
if (IGNORE_URI_REGEXP.test(absHref)) return;
|
||||
|
||||
// Make relative links work in HTML5 mode for legacy browsers (or at least IE8 & 9)
|
||||
// The href should be a regular url e.g. /link/somewhere or link/somewhere or ../somewhere or
|
||||
// somewhere#anchor or http://example.com/somewhere
|
||||
if (LocationMode === LocationHashbangInHtml5Url) {
|
||||
// get the actual href attribute - see
|
||||
// http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
|
||||
var href = elm.attr('href') || elm.attr('xlink:href');
|
||||
|
||||
if (href && href.indexOf('://') < 0) { // Ignore absolute URLs
|
||||
var prefix = '#' + hashPrefix;
|
||||
if (href[0] == '/') {
|
||||
// absolute path - replace old path
|
||||
absHref = appBase + prefix + href;
|
||||
} else if (href[0] == '#') {
|
||||
// local anchor
|
||||
absHref = appBase + prefix + ($location.path() || '/') + href;
|
||||
} else {
|
||||
// relative path - join with current path
|
||||
var stack = $location.path().split("/"),
|
||||
parts = href.split("/");
|
||||
if (stack.length === 2 && !stack[1]) stack.length = 1;
|
||||
for (var i=0; i<parts.length; i++) {
|
||||
if (parts[i] == ".")
|
||||
continue;
|
||||
else if (parts[i] == "..")
|
||||
stack.pop();
|
||||
else if (parts[i].length)
|
||||
stack.push(parts[i]);
|
||||
}
|
||||
absHref = appBase + prefix + stack.join('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rewrittenUrl = $location.$$rewrite(absHref);
|
||||
|
||||
if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) {
|
||||
event.preventDefault();
|
||||
if (rewrittenUrl != $browser.url()) {
|
||||
if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
|
||||
if ($location.$$parseLinkUrl(absHref, relHref)) {
|
||||
// We do a preventDefault for all urls that are part of the angular application,
|
||||
// in html5mode and also without, so that we are able to abort navigation without
|
||||
// getting double entries in the location history.
|
||||
event.preventDefault();
|
||||
// update location manually
|
||||
$location.$$parse(rewrittenUrl);
|
||||
$rootScope.$apply();
|
||||
// hack to work around FF6 bug 684208 when scenario runner clicks on links
|
||||
window.angular['ff-684208-preventDefault'] = true;
|
||||
if ($location.absUrl() != $browser.url()) {
|
||||
$rootScope.$apply();
|
||||
// hack to work around FF6 bug 684208 when scenario runner clicks on links
|
||||
window.angular['ff-684208-preventDefault'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+89
-42
@@ -7,7 +7,7 @@ var promiseWarning;
|
||||
// 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:
|
||||
@@ -16,7 +16,7 @@ var promiseWarning;
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
@@ -24,6 +24,8 @@ var promiseWarning;
|
||||
// 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) {
|
||||
@@ -760,7 +762,7 @@ Parser.prototype = {
|
||||
ensureSafeObject(context, parser.text);
|
||||
ensureSafeFunction(fnPtr, parser.text);
|
||||
|
||||
// IE stupidity! (IE doesn't have apply for some native functions)
|
||||
// IE doesn't have apply for some native functions
|
||||
var v = fnPtr.apply
|
||||
? fnPtr.apply(context, args)
|
||||
: fnPtr(args[0], args[1], args[2], args[3], args[4]);
|
||||
@@ -874,7 +876,12 @@ function setter(obj, path, setValue, fullExp, options) {
|
||||
return setValue;
|
||||
}
|
||||
|
||||
var getterFnCache = {};
|
||||
var getterFnCacheDefault = {};
|
||||
var getterFnCacheExpensive = {};
|
||||
|
||||
function isPossiblyDangerousMemberName(name) {
|
||||
return name == 'constructor';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the "Black Hole" variant from:
|
||||
@@ -887,29 +894,38 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
|
||||
ensureSafeMemberName(key2, fullExp);
|
||||
ensureSafeMemberName(key3, fullExp);
|
||||
ensureSafeMemberName(key4, fullExp);
|
||||
var eso = function(o) {
|
||||
return ensureSafeObject(o, fullExp);
|
||||
};
|
||||
var expensiveChecks = options.expensiveChecks;
|
||||
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 !options.unwrapPromises
|
||||
? 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;
|
||||
}
|
||||
@@ -919,73 +935,81 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
|
||||
|
||||
if (pathVal == null) return pathVal;
|
||||
|
||||
pathVal = pathVal[key0];
|
||||
pathVal = eso0(pathVal[key0]);
|
||||
if (pathVal && pathVal.then) {
|
||||
promiseWarning(fullExp);
|
||||
if (!("$$v" in pathVal)) {
|
||||
promise = pathVal;
|
||||
promise.$$v = undefined;
|
||||
promise.then(function(val) { promise.$$v = val; });
|
||||
promise.then(function(val) { promise.$$v = eso0(val); });
|
||||
}
|
||||
pathVal = pathVal.$$v;
|
||||
pathVal = eso0(pathVal.$$v);
|
||||
}
|
||||
|
||||
if (!key1) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key1];
|
||||
pathVal = eso1(pathVal[key1]);
|
||||
if (pathVal && pathVal.then) {
|
||||
promiseWarning(fullExp);
|
||||
if (!("$$v" in pathVal)) {
|
||||
promise = pathVal;
|
||||
promise.$$v = undefined;
|
||||
promise.then(function(val) { promise.$$v = val; });
|
||||
promise.then(function(val) { promise.$$v = eso1(val); });
|
||||
}
|
||||
pathVal = pathVal.$$v;
|
||||
pathVal = eso1(pathVal.$$v);
|
||||
}
|
||||
|
||||
if (!key2) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key2];
|
||||
pathVal = eso2(pathVal[key2]);
|
||||
if (pathVal && pathVal.then) {
|
||||
promiseWarning(fullExp);
|
||||
if (!("$$v" in pathVal)) {
|
||||
promise = pathVal;
|
||||
promise.$$v = undefined;
|
||||
promise.then(function(val) { promise.$$v = val; });
|
||||
promise.then(function(val) { promise.$$v = eso2(val); });
|
||||
}
|
||||
pathVal = pathVal.$$v;
|
||||
pathVal = eso2(pathVal.$$v);
|
||||
}
|
||||
|
||||
if (!key3) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key3];
|
||||
pathVal = eso3(pathVal[key3]);
|
||||
if (pathVal && pathVal.then) {
|
||||
promiseWarning(fullExp);
|
||||
if (!("$$v" in pathVal)) {
|
||||
promise = pathVal;
|
||||
promise.$$v = undefined;
|
||||
promise.then(function(val) { promise.$$v = val; });
|
||||
promise.then(function(val) { promise.$$v = eso3(val); });
|
||||
}
|
||||
pathVal = pathVal.$$v;
|
||||
pathVal = eso3(pathVal.$$v);
|
||||
}
|
||||
|
||||
if (!key4) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key4];
|
||||
pathVal = eso4(pathVal[key4]);
|
||||
if (pathVal && pathVal.then) {
|
||||
promiseWarning(fullExp);
|
||||
if (!("$$v" in pathVal)) {
|
||||
promise = pathVal;
|
||||
promise.$$v = undefined;
|
||||
promise.then(function(val) { promise.$$v = val; });
|
||||
promise.then(function(val) { promise.$$v = eso4(val); });
|
||||
}
|
||||
pathVal = pathVal.$$v;
|
||||
pathVal = eso4(pathVal.$$v);
|
||||
}
|
||||
return pathVal;
|
||||
};
|
||||
}
|
||||
|
||||
function getterFnWithExtraArgs(fn, fullExpression) {
|
||||
return function(s, l) {
|
||||
return fn(s, l, promiseWarning, ensureSafeObject, fullExpression);
|
||||
};
|
||||
}
|
||||
|
||||
function getterFn(path, options, fullExp) {
|
||||
var expensiveChecks = options.expensiveChecks;
|
||||
var getterFnCache = (expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault);
|
||||
// Check whether the cache has this getter already.
|
||||
// We can use hasOwnProperty directly on the cache because we ensure,
|
||||
// see below, that the cache never stores a path called 'hasOwnProperty'
|
||||
@@ -1017,35 +1041,48 @@ function getterFn(path, options, fullExp) {
|
||||
}
|
||||
} else {
|
||||
var code = 'var p;\n';
|
||||
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
|
||||
: '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
|
||||
(options.unwrapPromises
|
||||
? 'if (s && s.then) {\n' +
|
||||
: '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '["' + key + '"]';
|
||||
var wrapWithEso = expensiveChecks || isPossiblyDangerousMemberName(key);
|
||||
if (wrapWithEso) {
|
||||
lookupJs = 'eso(' + lookupJs + ', fe)';
|
||||
needsEnsureSafeObject = true;
|
||||
}
|
||||
code += 'if(s == null) return undefined;\n' +
|
||||
's=' + lookupJs + ';\n';
|
||||
if (options.unwrapPromises) {
|
||||
code += 'if (s && s.then) {\n' +
|
||||
' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' +
|
||||
' if (!("$$v" in s)) {\n' +
|
||||
' p=s;\n' +
|
||||
' p.$$v = undefined;\n' +
|
||||
' p.then(function(v) {p.$$v=v;});\n' +
|
||||
' p.then(function(v) {p.$$v=' + (wrapWithEso ? 'eso(v)' : 'v') + ';});\n' +
|
||||
'}\n' +
|
||||
' s=s.$$v\n' +
|
||||
'}\n'
|
||||
: '');
|
||||
' s=' + (wrapWithEso ? 'eso(s.$$v)' : 's.$$v') + '\n' +
|
||||
'}\n';
|
||||
|
||||
}
|
||||
});
|
||||
code += 'return s;';
|
||||
|
||||
/* jshint -W054 */
|
||||
var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning
|
||||
// s=scope, l=locals, pw=promiseWarning, eso=ensureSafeObject, fe=fullExpression
|
||||
var evaledFnGetter = new Function('s', 'l', 'pw', 'eso', 'fe', code);
|
||||
/* jshint +W054 */
|
||||
evaledFnGetter.toString = valueFn(code);
|
||||
fn = options.unwrapPromises ? function(scope, locals) {
|
||||
return evaledFnGetter(scope, locals, promiseWarning);
|
||||
} : evaledFnGetter;
|
||||
if (needsEnsureSafeObject || options.unwrapPromises) {
|
||||
evaledFnGetter = getterFnWithExtraArgs(evaledFnGetter, fullExp);
|
||||
}
|
||||
fn = evaledFnGetter;
|
||||
}
|
||||
|
||||
// Only cache the value if it's not going to mess up the cache object
|
||||
@@ -1109,12 +1146,14 @@ function getterFn(path, options, fullExp) {
|
||||
* service.
|
||||
*/
|
||||
function $ParseProvider() {
|
||||
var cache = {};
|
||||
var cacheDefault = {};
|
||||
var cacheExpensive = {};
|
||||
|
||||
var $parseOptions = {
|
||||
csp: false,
|
||||
unwrapPromises: false,
|
||||
logPromiseWarnings: true
|
||||
logPromiseWarnings: true,
|
||||
expensiveChecks: false
|
||||
};
|
||||
|
||||
|
||||
@@ -1201,6 +1240,12 @@ function $ParseProvider() {
|
||||
|
||||
this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) {
|
||||
$parseOptions.csp = $sniffer.csp;
|
||||
var $parseOptionsExpensive = {
|
||||
csp: $parseOptions.csp,
|
||||
unwrapPromises: $parseOptions.unwrapPromises,
|
||||
logPromiseWarnings: $parseOptions.logPromiseWarnings,
|
||||
expensiveChecks: true
|
||||
};
|
||||
|
||||
promiseWarning = function promiseWarningFn(fullExp) {
|
||||
if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return;
|
||||
@@ -1209,18 +1254,20 @@ function $ParseProvider() {
|
||||
'Automatic unwrapping of promises in Angular expressions is deprecated.');
|
||||
};
|
||||
|
||||
return function(exp) {
|
||||
return function(exp, expensiveChecks) {
|
||||
var parsedExpression;
|
||||
|
||||
switch (typeof exp) {
|
||||
case 'string':
|
||||
|
||||
var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
|
||||
if (cache.hasOwnProperty(exp)) {
|
||||
return cache[exp];
|
||||
}
|
||||
|
||||
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 (exp !== 'hasOwnProperty') {
|
||||
|
||||
+9
-1
@@ -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).
|
||||
*
|
||||
* [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
|
||||
* interface for interacting with an object that represents the result of an action that is
|
||||
@@ -100,6 +104,10 @@
|
||||
*
|
||||
* - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)`
|
||||
*
|
||||
* Because `catch` 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['catch'](callback)` or
|
||||
* `promise.then(null, errorCallback)` to make your code IE8 and Android 2.x compatible.
|
||||
*
|
||||
* - `finally(callback)` – 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
|
||||
|
||||
+5
-2
@@ -979,8 +979,11 @@ function $RootScopeProvider(){
|
||||
|
||||
var self = this;
|
||||
return function() {
|
||||
namedListeners[indexOf(namedListeners, listener)] = null;
|
||||
decrementListenerCount(self, 1, name);
|
||||
var indexOfListener = indexOf(namedListeners, listener);
|
||||
if (indexOfListener !== -1) {
|
||||
namedListeners[indexOfListener] = null;
|
||||
decrementListenerCount(self, 1, name);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -1156,6 +1156,16 @@ angular.module('ngAnimate', ['ng'])
|
||||
var parentCounter = 0;
|
||||
var animationReflowQueue = [];
|
||||
var cancelAnimationReflow;
|
||||
function clearCacheAfterReflow() {
|
||||
if (!cancelAnimationReflow) {
|
||||
cancelAnimationReflow = $$animateReflow(function() {
|
||||
animationReflowQueue = [];
|
||||
cancelAnimationReflow = null;
|
||||
lookupCache = {};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function afterReflow(element, callback) {
|
||||
if(cancelAnimationReflow) {
|
||||
cancelAnimationReflow();
|
||||
@@ -1524,7 +1534,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
//cancellation function then it means that there is no animation
|
||||
//to perform at all
|
||||
var preReflowCancellation = animateBefore(animationEvent, element, className);
|
||||
if(!preReflowCancellation) {
|
||||
if (!preReflowCancellation) {
|
||||
clearCacheAfterReflow();
|
||||
animationComplete();
|
||||
return;
|
||||
}
|
||||
@@ -1599,6 +1610,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
});
|
||||
return cancellationMethod;
|
||||
}
|
||||
clearCacheAfterReflow();
|
||||
animationCompleted();
|
||||
},
|
||||
|
||||
@@ -1623,6 +1635,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
});
|
||||
return cancellationMethod;
|
||||
}
|
||||
clearCacheAfterReflow();
|
||||
animationCompleted();
|
||||
},
|
||||
|
||||
|
||||
Vendored
+33
-17
@@ -1186,7 +1186,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*
|
||||
* - respond –
|
||||
@@ -1222,7 +1222,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1234,7 +1234,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1246,7 +1246,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1260,7 +1260,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1274,7 +1274,21 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $httpBackend#whenPATCH
|
||||
* @description
|
||||
* Creates a new backend definition for PATCH requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1285,7 +1299,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* Creates a new backend definition for JSONP requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
createShortMethods('when');
|
||||
@@ -1304,7 +1318,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* is in JSON format.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*
|
||||
* - respond –
|
||||
@@ -1333,7 +1347,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled. See #expect for more info.
|
||||
*/
|
||||
|
||||
@@ -1345,7 +1359,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1357,7 +1371,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1372,7 +1386,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1387,7 +1401,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1402,7 +1416,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
|
||||
@@ -1413,7 +1427,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* Creates a new request expectation for JSONP requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp} url HTTP url.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
|
||||
* request is handled.
|
||||
*/
|
||||
createShortMethods('expect');
|
||||
@@ -1506,7 +1520,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
|
||||
|
||||
function createShortMethods(prefix) {
|
||||
angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) {
|
||||
angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) {
|
||||
$httpBackend[prefix + method] = function(url, headers) {
|
||||
return $httpBackend[prefix](method, url, undefined, headers);
|
||||
};
|
||||
@@ -1549,7 +1563,9 @@ function MockHttpExpectation(method, url, data, headers) {
|
||||
if (angular.isUndefined(data)) return true;
|
||||
if (data && angular.isFunction(data.test)) return data.test(d);
|
||||
if (data && angular.isFunction(data)) return data(d);
|
||||
if (data && !angular.isString(data)) return angular.equals(data, angular.fromJson(d));
|
||||
if (data && !angular.isString(data)) {
|
||||
return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d));
|
||||
}
|
||||
return data == d;
|
||||
};
|
||||
|
||||
|
||||
@@ -577,7 +577,7 @@ function $RouteProvider(){
|
||||
if (i === 0) {
|
||||
result.push(segment);
|
||||
} else {
|
||||
var segmentMatch = segment.match(/(\w+)(.*)/);
|
||||
var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/);
|
||||
var key = segmentMatch[1];
|
||||
result.push(params[key]);
|
||||
result.push(segmentMatch[2] || '');
|
||||
|
||||
@@ -141,9 +141,9 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
||||
html.push(target);
|
||||
html.push('" ');
|
||||
}
|
||||
html.push('href="');
|
||||
html.push(url);
|
||||
html.push('">');
|
||||
html.push('href="',
|
||||
url.replace('"', '"'),
|
||||
'">');
|
||||
addText(text);
|
||||
html.push('</a>');
|
||||
}
|
||||
|
||||
+216
-18
@@ -440,6 +440,17 @@ describe('browser', function() {
|
||||
expect(locationReplace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set location.href and not use pushState when the url only changed in the hash fragment to please IE10/11', function() {
|
||||
sniffer.history = true;
|
||||
browser.url('http://server/#123');
|
||||
|
||||
expect(fakeWindow.location.href).toEqual('http://server/#123');
|
||||
|
||||
expect(pushState).not.toHaveBeenCalled();
|
||||
expect(replaceState).not.toHaveBeenCalled();
|
||||
expect(locationReplace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use location.replace when history.replaceState not available', function() {
|
||||
sniffer.history = false;
|
||||
browser.url('http://new.org', true);
|
||||
@@ -451,6 +462,17 @@ describe('browser', function() {
|
||||
expect(fakeWindow.location.href).toEqual('http://server/');
|
||||
});
|
||||
|
||||
it('should use location.replace and not use replaceState when the url only changed in the hash fragment to please IE10/11', function() {
|
||||
sniffer.history = true;
|
||||
browser.url('http://server/#123', true);
|
||||
|
||||
expect(locationReplace).toHaveBeenCalledWith('http://server/#123');
|
||||
|
||||
expect(pushState).not.toHaveBeenCalled();
|
||||
expect(replaceState).not.toHaveBeenCalled();
|
||||
expect(fakeWindow.location.href).toEqual('http://server/');
|
||||
});
|
||||
|
||||
it('should return $browser to allow chaining', function() {
|
||||
expect(browser.url('http://any.com')).toBe(browser);
|
||||
});
|
||||
@@ -468,6 +490,55 @@ describe('browser', function() {
|
||||
browser.url(current);
|
||||
expect(fakeWindow.location.href).toBe('dontchange');
|
||||
});
|
||||
|
||||
it('should not read out location.href if a reload was triggered but still allow to change the url', function() {
|
||||
sniffer.history = false;
|
||||
browser.url('http://server/someOtherUrlThatCausesReload');
|
||||
expect(fakeWindow.location.href).toBe('http://server/someOtherUrlThatCausesReload');
|
||||
|
||||
fakeWindow.location.href = 'http://someNewUrl';
|
||||
expect(browser.url()).toBe('http://server/someOtherUrlThatCausesReload');
|
||||
|
||||
browser.url('http://server/someOtherUrl');
|
||||
expect(browser.url()).toBe('http://server/someOtherUrl');
|
||||
expect(fakeWindow.location.href).toBe('http://server/someOtherUrl');
|
||||
});
|
||||
|
||||
it('assumes that changes to location.hash occur in sync', function() {
|
||||
// This is an asynchronous integration test that changes the
|
||||
// hash in all possible ways and checks
|
||||
// - whether the change to the hash can be read out in sync
|
||||
// - whether the change to the hash can be read out in the hashchange event
|
||||
var realWin = window,
|
||||
$realWin = jqLite(realWin),
|
||||
hashInHashChangeEvent = [];
|
||||
|
||||
runs(function() {
|
||||
$realWin.on('hashchange', hashListener);
|
||||
|
||||
realWin.location.hash = '1';
|
||||
realWin.location.href += '2';
|
||||
realWin.location.replace(realWin.location.href + '3');
|
||||
realWin.location.assign(realWin.location.href + '4');
|
||||
|
||||
expect(realWin.location.hash).toBe('#1234');
|
||||
});
|
||||
waitsFor(function() {
|
||||
return hashInHashChangeEvent.length > 3;
|
||||
});
|
||||
runs(function() {
|
||||
$realWin.off('hashchange', hashListener);
|
||||
|
||||
forEach(hashInHashChangeEvent, function(hash) {
|
||||
expect(hash).toBe('#1234');
|
||||
});
|
||||
});
|
||||
|
||||
function hashListener() {
|
||||
hashInHashChangeEvent.push(realWin.location.hash);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('urlChange', function() {
|
||||
@@ -546,15 +617,15 @@ describe('browser', function() {
|
||||
beforeEach(function() {
|
||||
sniffer.history = false;
|
||||
sniffer.hashchange = false;
|
||||
browser.url("http://server.current");
|
||||
browser.url("http://server/#current");
|
||||
});
|
||||
|
||||
it('should fire callback with the correct URL on location change outside of angular', function() {
|
||||
browser.onUrlChange(callback);
|
||||
|
||||
fakeWindow.location.href = 'http://server.new';
|
||||
fakeWindow.location.href = 'http://server/#new';
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(callback).toHaveBeenCalledWith('http://server.new');
|
||||
expect(callback).toHaveBeenCalledWith('http://server/#new');
|
||||
|
||||
fakeWindow.fire('popstate');
|
||||
fakeWindow.fire('hashchange');
|
||||
@@ -618,29 +689,156 @@ describe('browser', function() {
|
||||
|
||||
describe('integration tests with $location', function() {
|
||||
|
||||
beforeEach(module(function($provide, $locationProvider) {
|
||||
spyOn(fakeWindow.history, 'pushState').andCallFake(function(stateObj, title, newUrl) {
|
||||
fakeWindow.location.href = newUrl;
|
||||
function setup(options) {
|
||||
module(function($provide, $locationProvider) {
|
||||
spyOn(fakeWindow.history, 'pushState').andCallFake(function(stateObj, title, newUrl) {
|
||||
fakeWindow.location.href = newUrl;
|
||||
});
|
||||
spyOn(fakeWindow.location, 'replace').andCallFake(function(newUrl) {
|
||||
fakeWindow.location.href = newUrl;
|
||||
});
|
||||
$provide.value('$browser', browser);
|
||||
browser.pollFns = [];
|
||||
|
||||
sniffer.history = options.history;
|
||||
$provide.value('$sniffer', sniffer);
|
||||
|
||||
$locationProvider.html5Mode(options.html5Mode);
|
||||
});
|
||||
$provide.value('$browser', browser);
|
||||
browser.pollFns = [];
|
||||
}
|
||||
|
||||
$locationProvider.html5Mode(true);
|
||||
}));
|
||||
|
||||
it('should 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() {
|
||||
inject(function($rootScope, $location) {
|
||||
fakeWindow.history.pushState(null, '', 'http://server/someTestHash');
|
||||
|
||||
// Verify that infinite digest reported in #6976 no longer occurs
|
||||
expect(function() {
|
||||
it('should work with no history support, no html5Mode', function() {
|
||||
setup({
|
||||
history: false,
|
||||
html5Mode: false
|
||||
});
|
||||
inject(function($rootScope, $location) {
|
||||
$rootScope.$apply(function() {
|
||||
$location.path('/initialPath');
|
||||
});
|
||||
expect(fakeWindow.location.href).toBe('http://server/#/initialPath');
|
||||
|
||||
fakeWindow.location.href = 'http://server/#/someTestHash';
|
||||
|
||||
$rootScope.$digest();
|
||||
}).not.toThrow();
|
||||
|
||||
expect($location.path()).toBe('/someTestHash');
|
||||
expect($location.path()).toBe('/someTestHash');
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with history support, no html5Mode', function() {
|
||||
setup({
|
||||
history: true,
|
||||
html5Mode: false
|
||||
});
|
||||
inject(function($rootScope, $location) {
|
||||
$rootScope.$apply(function() {
|
||||
$location.path('/initialPath');
|
||||
});
|
||||
expect(fakeWindow.location.href).toBe('http://server/#/initialPath');
|
||||
|
||||
fakeWindow.location.href = 'http://server/#/someTestHash';
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toBe('/someTestHash');
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with no history support, with html5Mode', function() {
|
||||
setup({
|
||||
history: false,
|
||||
html5Mode: true
|
||||
});
|
||||
inject(function($rootScope, $location) {
|
||||
$rootScope.$apply(function() {
|
||||
$location.path('/initialPath');
|
||||
});
|
||||
expect(fakeWindow.location.href).toBe('http://server/#/initialPath');
|
||||
|
||||
fakeWindow.location.href = 'http://server/#/someTestHash';
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toBe('/someTestHash');
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with history support, with html5Mode', function() {
|
||||
setup({
|
||||
history: true,
|
||||
html5Mode: true
|
||||
});
|
||||
inject(function($rootScope, $location) {
|
||||
$rootScope.$apply(function() {
|
||||
$location.path('/initialPath');
|
||||
});
|
||||
expect(fakeWindow.location.href).toBe('http://server/initialPath');
|
||||
|
||||
fakeWindow.location.href = 'http://server/someTestHash';
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toBe('/someTestHash');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should not reload the page on every $digest when the page will be reloaded due to url rewrite on load', function() {
|
||||
setup({
|
||||
history: false,
|
||||
html5Mode: true
|
||||
});
|
||||
fakeWindow.location.href = 'http://server/some/deep/path';
|
||||
var changeUrlCount = 0;
|
||||
var _url = browser.url;
|
||||
browser.url = function(newUrl, replace) {
|
||||
if (newUrl) {
|
||||
changeUrlCount++;
|
||||
}
|
||||
return _url.call(this, newUrl, replace);
|
||||
};
|
||||
spyOn(browser, 'url').andCallThrough();
|
||||
inject(function($rootScope, $location) {
|
||||
$rootScope.$digest();
|
||||
$rootScope.$digest();
|
||||
$rootScope.$digest();
|
||||
$rootScope.$digest();
|
||||
|
||||
// from $location for rewriting the initial url into a hash url
|
||||
expect(browser.url).toHaveBeenCalledWith('http://server/#/some/deep/path', true);
|
||||
// from the initial call to the watch in $location for watching $location
|
||||
expect(browser.url).toHaveBeenCalledWith('http://server/#/some/deep/path', false);
|
||||
expect(changeUrlCount).toBe(2);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration test with $rootScope', function() {
|
||||
|
||||
beforeEach(module(function($provide, $locationProvider) {
|
||||
$provide.value('$browser', browser);
|
||||
browser.pollFns = [];
|
||||
}));
|
||||
|
||||
it('should not interfere with legacy browser url replace behavior', function() {
|
||||
inject(function($rootScope) {
|
||||
var current = fakeWindow.location.href;
|
||||
var newUrl = 'notyet';
|
||||
sniffer.history = false;
|
||||
browser.url(newUrl, true);
|
||||
expect(browser.url()).toBe(newUrl);
|
||||
$rootScope.$digest();
|
||||
expect(browser.url()).toBe(newUrl);
|
||||
expect(fakeWindow.location.href).toBe(current);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -82,6 +82,25 @@ describe('event directives', function() {
|
||||
|
||||
});
|
||||
|
||||
describe('security', function() {
|
||||
it('should allow access to the $event object', inject(function($rootScope, $compile) {
|
||||
var scope = $rootScope.$new();
|
||||
element = $compile('<button ng-click="e = $event">BTN</button>')(scope);
|
||||
element.triggerHandler('click');
|
||||
expect(scope.e.target).toBe(element[0]);
|
||||
}));
|
||||
|
||||
it('should block access to DOM nodes (e.g. exposed via $event)', inject(function($rootScope, $compile) {
|
||||
var scope = $rootScope.$new();
|
||||
element = $compile('<button ng-click="e = $event.target">BTN</button>')(scope);
|
||||
expect(function() {
|
||||
element.triggerHandler('click');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is disallowed! ' +
|
||||
'Expression: e = $event.target');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('blur', function() {
|
||||
|
||||
describe('call the listener asynchronously during $apply', function() {
|
||||
|
||||
@@ -38,8 +38,28 @@ describe('select', function() {
|
||||
};
|
||||
|
||||
return equals(expectedValues, actualValues);
|
||||
},
|
||||
|
||||
toEqualOption: function(value, text, label) {
|
||||
var errors = [];
|
||||
if (this.actual.attr('value') !== value) {
|
||||
errors.push('Expected option value "' + this.actual.attr('value') + '" to equal "' + value + '"');
|
||||
}
|
||||
if (text && this.actual.text() !== text) {
|
||||
errors.push('Expected option value "' + this.actual.attr('value') + '" to equal "' + value + '"');
|
||||
}
|
||||
if (label && this.actual.attr('label') !== label) {
|
||||
errors.push('Expected option value "' + this.actual.attr('value') + '" to equal "' + value + '"');
|
||||
}
|
||||
|
||||
this.message = function() {
|
||||
return errors.join('\n');
|
||||
};
|
||||
|
||||
return errors.length === 0;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -574,9 +594,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() {
|
||||
@@ -589,9 +609,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');
|
||||
});
|
||||
|
||||
|
||||
@@ -608,9 +628,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() {
|
||||
@@ -630,7 +650,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'});
|
||||
@@ -638,15 +658,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');
|
||||
});
|
||||
|
||||
|
||||
@@ -665,15 +685,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();
|
||||
@@ -725,9 +745,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');
|
||||
});
|
||||
|
||||
|
||||
@@ -771,7 +791,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() {
|
||||
@@ -795,8 +815,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',
|
||||
@@ -827,6 +847,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() {
|
||||
@@ -953,8 +990,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');
|
||||
});
|
||||
|
||||
|
||||
|
||||
+191
-73
@@ -1,92 +1,210 @@
|
||||
'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 same array if predicate is falsy', function() {
|
||||
var array = [1, 2, 3];
|
||||
expect(orderBy(array)).toBe(array);
|
||||
});
|
||||
|
||||
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}]);
|
||||
});
|
||||
describe('(Arrays)', function() {
|
||||
it('should return sorted array if predicate is not provided', function() {
|
||||
expect(orderBy([2, 1, 3])).toEqual([1, 2, 3]);
|
||||
|
||||
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(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(child, 'a', true)).toEqualData([{a:15}, {a:2}]);
|
||||
});
|
||||
expect(orderBy([2, 1, 3], '+')).toEqual([1, 2, 3]);
|
||||
expect(orderBy([2, 1, 3], ['+'])).toEqual([1, 2, 3]);
|
||||
|
||||
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}]);
|
||||
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 inherited from array', function() {
|
||||
function BaseCollection(){}
|
||||
BaseCollection.prototype = Array.prototype;
|
||||
var child = new BaseCollection();
|
||||
child.push({a:2});
|
||||
child.push({a:15});
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -985,6 +985,18 @@ describe('$http', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should have patch()', function() {
|
||||
$httpBackend.expect('PATCH', '/url', 'some-data').respond('');
|
||||
$http.patch('/url', 'some-data');
|
||||
});
|
||||
|
||||
|
||||
it('patch() should allow config param', function() {
|
||||
$httpBackend.expect('PATCH', '/url', 'some-data', checkHeader('Custom', 'Header')).respond('');
|
||||
$http.patch('/url', 'some-data', {headers: {'Custom': 'Header'}});
|
||||
});
|
||||
|
||||
|
||||
it('should have jsonp()', function() {
|
||||
$httpBackend.expect('JSONP', '/url').respond('');
|
||||
$http.jsonp('/url');
|
||||
@@ -1041,6 +1053,12 @@ describe('$http', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should transform object with date into json', function() {
|
||||
$httpBackend.expect('POST', '/url', {"date": new Date(Date.UTC(2013, 11, 25))}).respond('');
|
||||
$http({method: 'POST', url: '/url', data: {date: new Date(Date.UTC(2013, 11, 25))}});
|
||||
});
|
||||
|
||||
|
||||
it('should ignore strings', function() {
|
||||
$httpBackend.expect('POST', '/url', 'string-data').respond('');
|
||||
$http({method: 'POST', url: '/url', data: 'string-data'});
|
||||
|
||||
+127
-121
@@ -93,6 +93,11 @@ describe('$location', function() {
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/1?search=a&b=c&d#hash');
|
||||
});
|
||||
|
||||
it('path() should allow using 0 as path', function() {
|
||||
url.path(0);
|
||||
expect(url.path()).toBe('/0');
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/0?search=a&b=c&d#hash');
|
||||
});
|
||||
|
||||
it('path() should set to empty path on null value', function () {
|
||||
url.path('/foo');
|
||||
@@ -191,6 +196,11 @@ describe('$location', function() {
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#5');
|
||||
});
|
||||
|
||||
it('hash() should allow using 0', function() {
|
||||
url.hash(0);
|
||||
expect(url.hash()).toBe('0');
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#0');
|
||||
});
|
||||
|
||||
it('hash() should accept null parameter', function() {
|
||||
url.hash(null);
|
||||
@@ -291,9 +301,9 @@ describe('$location', function() {
|
||||
});
|
||||
|
||||
it('should not rewrite when hashbang url is not given', function() {
|
||||
initService(true, '!', true);
|
||||
initService({html5Mode:true,hashPrefix: '!',supportHistory: true});
|
||||
inject(
|
||||
initBrowser('http://domain.com/base/a/b', '/base'),
|
||||
initBrowser({url:'http://domain.com/base/a/b',basePath: '/base'}),
|
||||
function($rootScope, $location, $browser) {
|
||||
expect($browser.url()).toBe('http://domain.com/base/a/b');
|
||||
}
|
||||
@@ -526,24 +536,24 @@ describe('$location', function() {
|
||||
});
|
||||
|
||||
|
||||
function initService(html5Mode, hashPrefix, supportHistory) {
|
||||
function initService(options) {
|
||||
return module(function($provide, $locationProvider){
|
||||
$locationProvider.html5Mode(html5Mode);
|
||||
$locationProvider.hashPrefix(hashPrefix);
|
||||
$provide.value('$sniffer', {history: supportHistory});
|
||||
$locationProvider.html5Mode(options.html5Mode);
|
||||
$locationProvider.hashPrefix(options.hashPrefix);
|
||||
$provide.value('$sniffer', {history: options.supportHistory});
|
||||
});
|
||||
}
|
||||
function initBrowser(url, basePath) {
|
||||
function initBrowser(options) {
|
||||
return function($browser){
|
||||
$browser.url(url);
|
||||
$browser.$$baseHref = basePath;
|
||||
$browser.url(options.url);
|
||||
$browser.$$baseHref = options.basePath;
|
||||
};
|
||||
}
|
||||
|
||||
describe('wiring', function() {
|
||||
|
||||
beforeEach(initService(false, '!', true));
|
||||
beforeEach(inject(initBrowser('http://new.com/a/b#!', 'http://new.com/a/b')));
|
||||
beforeEach(initService({html5Mode:false,hashPrefix: '!',supportHistory: true}));
|
||||
beforeEach(inject(initBrowser({url:'http://new.com/a/b#!',basePath: 'http://new.com/a/b'})));
|
||||
|
||||
|
||||
it('should update $location when browser url changes', inject(function($browser, $location) {
|
||||
@@ -667,9 +677,9 @@ describe('$location', function() {
|
||||
describe('disabled history', function() {
|
||||
|
||||
it('should use hashbang url with hash prefix', function() {
|
||||
initService(false, '!');
|
||||
initService({html5Mode:false,hashPrefix: '!'});
|
||||
inject(
|
||||
initBrowser('http://domain.com/base/index.html#!/a/b', '/base/index.html'),
|
||||
initBrowser({url:'http://domain.com/base/index.html#!/a/b',basePath: '/base/index.html'}),
|
||||
function($rootScope, $location, $browser) {
|
||||
expect($browser.url()).toBe('http://domain.com/base/index.html#!/a/b');
|
||||
$location.path('/new');
|
||||
@@ -682,9 +692,9 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should use hashbang url without hash prefix', function() {
|
||||
initService(false, '');
|
||||
initService({html5Mode:false,hashPrefix: ''});
|
||||
inject(
|
||||
initBrowser('http://domain.com/base/index.html#/a/b', '/base/index.html'),
|
||||
initBrowser({url:'http://domain.com/base/index.html#/a/b',basePath: '/base/index.html'}),
|
||||
function($rootScope, $location, $browser) {
|
||||
expect($browser.url()).toBe('http://domain.com/base/index.html#/a/b');
|
||||
$location.path('/new');
|
||||
@@ -705,9 +715,9 @@ describe('$location', function() {
|
||||
}));
|
||||
|
||||
it('should use hashbang url with hash prefix', function() {
|
||||
initService(true, '!!', false);
|
||||
initService({html5Mode:true,hashPrefix: '!!',supportHistory: false});
|
||||
inject(
|
||||
initBrowser('http://domain.com/base/index.html#!!/a/b', '/base/index.html'),
|
||||
initBrowser({url:'http://domain.com/base/index.html#!!/a/b',basePath: '/base/index.html'}),
|
||||
function($rootScope, $location, $browser) {
|
||||
expect($browser.url()).toBe('http://domain.com/base/index.html#!!/a/b');
|
||||
$location.path('/new');
|
||||
@@ -720,9 +730,9 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should redirect to hashbang url when new url given', function() {
|
||||
initService(true, '!');
|
||||
initService({html5Mode:true,hashPrefix: '!'});
|
||||
inject(
|
||||
initBrowser('http://domain.com/base/new-path/index.html', '/base/index.html'),
|
||||
initBrowser({url:'http://domain.com/base/new-path/index.html',basePath: '/base/index.html'}),
|
||||
function($browser, $location) {
|
||||
expect($browser.url()).toBe('http://domain.com/base/index.html#!/new-path/index.html');
|
||||
}
|
||||
@@ -730,9 +740,9 @@ describe('$location', function() {
|
||||
});
|
||||
|
||||
it('should correctly convert html5 url with path matching basepath to hashbang url', function () {
|
||||
initService(true, '!', false);
|
||||
initService({html5Mode:true,hashPrefix: '!',supportHistory: false});
|
||||
inject(
|
||||
initBrowser('http://domain.com/base/index.html', '/base/index.html'),
|
||||
initBrowser({url:'http://domain.com/base/index.html',basePath: '/base/index.html'}),
|
||||
function($browser, $location) {
|
||||
expect($browser.url()).toBe('http://domain.com/base/index.html#!/index.html');
|
||||
}
|
||||
@@ -749,9 +759,9 @@ describe('$location', function() {
|
||||
}));
|
||||
|
||||
it('should use new url', function() {
|
||||
initService(true, '', true);
|
||||
initService({html5Mode:true,hashPrefix: '',supportHistory: true});
|
||||
inject(
|
||||
initBrowser('http://domain.com/base/old/index.html#a', '/base/index.html'),
|
||||
initBrowser({url:'http://domain.com/base/old/index.html#a',basePath: '/base/index.html'}),
|
||||
function($rootScope, $location, $browser) {
|
||||
expect($browser.url()).toBe('http://domain.com/base/old/index.html#a');
|
||||
$location.path('/new');
|
||||
@@ -764,9 +774,9 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should rewrite when hashbang url given', function() {
|
||||
initService(true, '!', true);
|
||||
initService({html5Mode:true,hashPrefix: '!',supportHistory: true});
|
||||
inject(
|
||||
initBrowser('http://domain.com/base/index.html#!/a/b', '/base/index.html'),
|
||||
initBrowser({url:'http://domain.com/base/index.html#!/a/b',basePath: '/base/index.html'}),
|
||||
function($rootScope, $location, $browser) {
|
||||
expect($browser.url()).toBe('http://domain.com/base/a/b');
|
||||
$location.path('/new');
|
||||
@@ -780,9 +790,9 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should rewrite when hashbang url given (without hash prefix)', function() {
|
||||
initService(true, '', true);
|
||||
initService({html5Mode:true,hashPrefix: '',supportHistory: true});
|
||||
inject(
|
||||
initBrowser('http://domain.com/base/index.html#/a/b', '/base/index.html'),
|
||||
initBrowser({url:'http://domain.com/base/index.html#/a/b',basePath: '/base/index.html'}),
|
||||
function($rootScope, $location, $browser) {
|
||||
expect($browser.url()).toBe('http://domain.com/base/a/b');
|
||||
expect($location.path()).toBe('/a/b');
|
||||
@@ -792,9 +802,9 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should set appBase to serverBase if base[href] is missing', function() {
|
||||
initService(true, '!', true);
|
||||
initService({html5Mode:true,hashPrefix: '!',supportHistory: true});
|
||||
inject(
|
||||
initBrowser('http://domain.com/my/view1#anchor1', ''),
|
||||
initBrowser({url:'http://domain.com/my/view1#anchor1',basePath: ''}),
|
||||
function($rootScope, $location, $browser) {
|
||||
expect($browser.url()).toBe('http://domain.com/my/view1#anchor1');
|
||||
expect($location.path()).toBe('/my/view1');
|
||||
@@ -837,21 +847,23 @@ describe('$location', function() {
|
||||
|
||||
var root, link, originalBrowser, lastEventPreventDefault;
|
||||
|
||||
function configureService(linkHref, html5Mode, supportHist, relLink, attrs, content) {
|
||||
if (typeof relLink !== "boolean") {
|
||||
content = attrs;
|
||||
attrs = relLink;
|
||||
relLink = false;
|
||||
}
|
||||
function configureService(options) {
|
||||
var linkHref = options.linkHref,
|
||||
html5Mode = options.html5Mode,
|
||||
supportHist = options.supportHist,
|
||||
relLink = options.relLink,
|
||||
attrs = options.attrs,
|
||||
content = options.content;
|
||||
|
||||
module(function($provide, $locationProvider) {
|
||||
attrs = attrs ? ' ' + attrs + ' ' : '';
|
||||
|
||||
// fake the base behavior
|
||||
if (typeof linkHref === 'string') {
|
||||
if (!relLink) {
|
||||
if (linkHref[0] == '/') {
|
||||
linkHref = 'http://host.com' + linkHref;
|
||||
} else if(!linkHref.match(/:\/\//)) {
|
||||
// fake the behavior of <base> tag
|
||||
linkHref = 'http://host.com/base/' + linkHref;
|
||||
}
|
||||
}
|
||||
@@ -876,8 +888,8 @@ describe('$location', function() {
|
||||
}
|
||||
|
||||
function initBrowser() {
|
||||
return function($browser){
|
||||
$browser.url('http://host.com/base');
|
||||
return function($browser, $document){
|
||||
$browser.url('http://host.com/base/index.html');
|
||||
$browser.$$baseHref = '/base/index.html';
|
||||
};
|
||||
}
|
||||
@@ -911,7 +923,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should rewrite rel link to new url when history enabled on new browser', function() {
|
||||
configureService('link?a#b', true, true);
|
||||
configureService({linkHref: 'link?a#b', html5Mode: true, supportHist: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -924,7 +936,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should do nothing if already on the same URL', function() {
|
||||
configureService('/base/', true, true);
|
||||
configureService({linkHref: '/base/', html5Mode: true, supportHist: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -951,7 +963,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should rewrite abs link to new url when history enabled on new browser', function() {
|
||||
configureService('/base/link?a#b', true, true);
|
||||
configureService({linkHref: '/base/link?a#b', html5Mode: true, supportHist: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -964,7 +976,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should rewrite rel link to hashbang url when history enabled on old browser', function() {
|
||||
configureService('link?a#b', true, false);
|
||||
configureService({linkHref: 'link?a#b', html5Mode: true, supportHist: false});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -978,7 +990,7 @@ describe('$location', function() {
|
||||
|
||||
// Regression (gh-7721)
|
||||
it('should not throw when clicking anchor with no href attribute when history enabled on old browser', function() {
|
||||
configureService(null, true, false);
|
||||
configureService({linkHref: null, html5Mode: true, supportHist: false});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -991,12 +1003,14 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should produce relative paths correctly when $location.path() is "/" when history enabled on old browser', function() {
|
||||
configureService('partial1', true, false, true);
|
||||
configureService({linkHref: 'partial1', html5Mode: true, supportHist: false});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
function($browser, $location) {
|
||||
$location.path('/');
|
||||
function($browser, $location, $rootScope) {
|
||||
$rootScope.$apply(function() {
|
||||
$location.path('/');
|
||||
});
|
||||
browserTrigger(link, 'click');
|
||||
expectRewriteTo($browser, 'http://host.com/base/index.html#!/partial1');
|
||||
}
|
||||
@@ -1005,7 +1019,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should rewrite abs link to hashbang url when history enabled on old browser', function() {
|
||||
configureService('/base/link?a#b', true, false);
|
||||
configureService({linkHref: '/base/link?a#b', html5Mode: true, supportHist: false});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1018,7 +1032,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should not rewrite full url links do different domain', function() {
|
||||
configureService('http://www.dot.abc/a?b=c', true);
|
||||
configureService({linkHref: 'http://www.dot.abc/a?b=c', html5Mode: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1031,7 +1045,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should not rewrite links with target="_blank"', function() {
|
||||
configureService('/a?b=c', true, true, 'target="_blank"');
|
||||
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="_blank"'});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1044,7 +1058,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should not rewrite links with target specified', function() {
|
||||
configureService('/a?b=c', true, true, 'target="some-frame"');
|
||||
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="some-frame"'});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1057,7 +1071,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should not rewrite links with `javascript:` URI', function() {
|
||||
configureService(' jAvAsCrIpT:throw new Error("Boom!")', true, true, true);
|
||||
configureService({linkHref: ' jAvAsCrIpT:throw new Error("Boom!")', html5Mode: true, supportHist: true, relLink: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1070,7 +1084,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should not rewrite links with `mailto:` URI', function() {
|
||||
configureService(' mAiLtO:foo@bar.com', true, true, true);
|
||||
configureService({linkHref: ' mAiLtO:foo@bar.com', html5Mode: true, supportHist: true, relLink: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1083,7 +1097,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should rewrite full url links to same domain and base path', function() {
|
||||
configureService('http://host.com/base/new', true);
|
||||
configureService({linkHref: 'http://host.com/base/new', html5Mode: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1096,7 +1110,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should rewrite when clicked span inside link', function() {
|
||||
configureService('some/link', true, true, '', '<span>link</span>');
|
||||
configureService({linkHref: 'some/link', html5Mode: true, supportHist: true, attrs: '', content: '<span>link</span>'});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1112,7 +1126,7 @@ describe('$location', function() {
|
||||
|
||||
it('should not rewrite when link to different base path when history enabled on new browser',
|
||||
function() {
|
||||
configureService('/other_base/link', true, true);
|
||||
configureService({linkHref: '/other_base/link', html5Mode: true, supportHist: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1126,7 +1140,7 @@ describe('$location', function() {
|
||||
|
||||
it('should not rewrite when link to different base path when history enabled on old browser',
|
||||
function() {
|
||||
configureService('/other_base/link', true, false);
|
||||
configureService({linkHref: '/other_base/link', html5Mode: true, supportHist: false});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1139,7 +1153,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should not rewrite when link to different base path when history disabled', function() {
|
||||
configureService('/other_base/link', false);
|
||||
configureService({linkHref: '/other_base/link', html5Mode: false});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1153,7 +1167,7 @@ describe('$location', function() {
|
||||
|
||||
it('should not rewrite when full link to different base path when history enabled on new browser',
|
||||
function() {
|
||||
configureService('http://host.com/other_base/link', true, true);
|
||||
configureService({linkHref: 'http://host.com/other_base/link', html5Mode: true, supportHist: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1167,7 +1181,7 @@ describe('$location', function() {
|
||||
|
||||
it('should not rewrite when full link to different base path when history enabled on old browser',
|
||||
function() {
|
||||
configureService('http://host.com/other_base/link', true, false);
|
||||
configureService({linkHref: 'http://host.com/other_base/link', html5Mode: true, supportHist: false});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1180,7 +1194,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should not rewrite when full link to different base path when history disabled', function() {
|
||||
configureService('http://host.com/other_base/link', false);
|
||||
configureService({linkHref: 'http://host.com/other_base/link', html5Mode: false});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1191,62 +1205,32 @@ describe('$location', function() {
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should rewrite relative links relative to current path when history disabled', function() {
|
||||
configureService('link', true, false, true);
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
function($browser, $location) {
|
||||
$location.path('/some');
|
||||
browserTrigger(link, 'click');
|
||||
expectRewriteTo($browser, 'http://host.com/base/index.html#!/some/link');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should replace current path when link begins with "/" and history disabled', function() {
|
||||
configureService('/link', true, false, true);
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
function($browser, $location) {
|
||||
$location.path('/some');
|
||||
browserTrigger(link, 'click');
|
||||
expectRewriteTo($browser, 'http://host.com/base/index.html#!/link');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should replace current hash fragment when link begins with "#" history disabled', function() {
|
||||
configureService('#link', true, false, true);
|
||||
configureService({linkHref: '#link', html5Mode: true, supportHist: false, relLink: false});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
function($browser, $location) {
|
||||
// Initialize browser URL
|
||||
$location.path('/some');
|
||||
$location.hash('foo');
|
||||
function($browser, $location, $rootScope) {
|
||||
$rootScope.$apply(function() {
|
||||
$location.hash('foo');
|
||||
});
|
||||
browserTrigger(link, 'click');
|
||||
expect($location.hash()).toBe('link');
|
||||
expectRewriteTo($browser, 'http://host.com/base/index.html#!/some#link');
|
||||
expectRewriteTo($browser, 'http://host.com/base/index.html#!#link');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
// don't run next tests on IE<9, as browserTrigger does not simulate pressed keys
|
||||
if (!msie || msie >= 9) {
|
||||
|
||||
it('should not rewrite when clicked with ctrl pressed', function() {
|
||||
configureService('/a?b=c', true, true);
|
||||
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
function($browser) {
|
||||
browserTrigger(link, 'click', ['ctrl']);
|
||||
browserTrigger(link, 'click', { keys: ['ctrl'] });
|
||||
expectNoRewrite($browser);
|
||||
}
|
||||
);
|
||||
@@ -1254,12 +1238,12 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should not rewrite when clicked with meta pressed', function() {
|
||||
configureService('/a?b=c', true, true);
|
||||
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
function($browser) {
|
||||
browserTrigger(link, 'click', ['meta']);
|
||||
browserTrigger(link, 'click', { keys: ['meta'] });
|
||||
expectNoRewrite($browser);
|
||||
}
|
||||
);
|
||||
@@ -1348,7 +1332,8 @@ describe('$location', function() {
|
||||
|
||||
var event = {
|
||||
target: jqLite(window.document.body).find('a')[0],
|
||||
preventDefault: jasmine.createSpy('preventDefault')
|
||||
preventDefault: jasmine.createSpy('preventDefault'),
|
||||
isDefaultPrevented: jasmine.createSpy().andReturn(false)
|
||||
};
|
||||
|
||||
|
||||
@@ -1378,7 +1363,8 @@ describe('$location', function() {
|
||||
|
||||
var event = {
|
||||
target: jqLite(window.document.body).find('a')[0],
|
||||
preventDefault: jasmine.createSpy('preventDefault')
|
||||
preventDefault: jasmine.createSpy('preventDefault'),
|
||||
isDefaultPrevented: jasmine.createSpy().andReturn(false)
|
||||
};
|
||||
|
||||
|
||||
@@ -1543,8 +1529,12 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should listen on click events on href and prevent browser default in html5 mode', function() {
|
||||
module(function($locationProvider) {
|
||||
module(function($locationProvider, $provide) {
|
||||
$locationProvider.html5Mode(true);
|
||||
$provide.decorator('$browser', function($delegate) {
|
||||
$delegate.$$baseHref = '/';
|
||||
return $delegate;
|
||||
});
|
||||
return function($rootElement, $compile, $rootScope) {
|
||||
$rootElement.html('<a href="http://server/somePath">link</a>');
|
||||
$compile($rootElement)($rootScope);
|
||||
@@ -1601,6 +1591,13 @@ describe('$location', function() {
|
||||
);
|
||||
});
|
||||
|
||||
function parseLinkAndReturn(location, url, relHref) {
|
||||
if (location.$$parseLinkUrl(url, relHref)) {
|
||||
return location.absUrl();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
describe('LocationHtml5Url', function() {
|
||||
var location, locationIndex;
|
||||
|
||||
@@ -1610,13 +1607,14 @@ describe('$location', function() {
|
||||
});
|
||||
|
||||
it('should rewrite URL', function() {
|
||||
expect(location.$$rewrite('http://other')).toEqual(undefined);
|
||||
expect(location.$$rewrite('http://server/pre')).toEqual('http://server/pre/');
|
||||
expect(location.$$rewrite('http://server/pre/')).toEqual('http://server/pre/');
|
||||
expect(location.$$rewrite('http://server/pre/otherPath')).toEqual('http://server/pre/otherPath');
|
||||
expect(locationIndex.$$rewrite('http://server/pre')).toEqual('http://server/pre/');
|
||||
expect(locationIndex.$$rewrite('http://server/pre/')).toEqual('http://server/pre/');
|
||||
expect(locationIndex.$$rewrite('http://server/pre/otherPath')).toEqual('http://server/pre/otherPath');
|
||||
expect(parseLinkAndReturn(location, 'http://other')).toEqual(undefined);
|
||||
expect(parseLinkAndReturn(location, 'http://server/pre')).toEqual('http://server/pre/');
|
||||
expect(parseLinkAndReturn(location, 'http://server/pre/')).toEqual('http://server/pre/');
|
||||
expect(parseLinkAndReturn(location, 'http://server/pre/otherPath')).toEqual('http://server/pre/otherPath');
|
||||
|
||||
expect(parseLinkAndReturn(locationIndex, 'http://server/pre')).toEqual('http://server/pre/');
|
||||
expect(parseLinkAndReturn(locationIndex, 'http://server/pre/')).toEqual('http://server/pre/');
|
||||
expect(parseLinkAndReturn(locationIndex, 'http://server/pre/otherPath')).toEqual('http://server/pre/otherPath');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1624,14 +1622,21 @@ describe('$location', function() {
|
||||
describe('LocationHashbangUrl', function() {
|
||||
var location;
|
||||
|
||||
function parseLinkAndReturn(location, url, relHref) {
|
||||
if (location.$$parseLinkUrl(url, relHref)) {
|
||||
return location.absUrl();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
it('should rewrite URL', function() {
|
||||
/* jshint scripturl: true */
|
||||
location = new LocationHashbangUrl('http://server/pre/', '#');
|
||||
|
||||
expect(location.$$rewrite('http://other')).toEqual(undefined);
|
||||
expect(location.$$rewrite('http://server/pre/')).toEqual('http://server/pre/');
|
||||
expect(location.$$rewrite('http://server/pre/#otherPath')).toEqual('http://server/pre/#otherPath');
|
||||
expect(location.$$rewrite('javascript:void(0)')).toEqual(undefined);
|
||||
expect(parseLinkAndReturn(location, 'http://other')).toEqual(undefined);
|
||||
expect(parseLinkAndReturn(location, 'http://server/pre/')).toEqual('http://server/pre/');
|
||||
expect(parseLinkAndReturn(location, 'http://server/pre/#otherPath')).toEqual('http://server/pre/#/otherPath');
|
||||
expect(parseLinkAndReturn(location, 'javascript:void(0)')).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("should not set hash if one was not originally specified", function() {
|
||||
@@ -1681,13 +1686,14 @@ describe('$location', function() {
|
||||
});
|
||||
|
||||
it('should rewrite URL', function() {
|
||||
expect(location.$$rewrite('http://other')).toEqual(undefined);
|
||||
expect(location.$$rewrite('http://server/pre')).toEqual('http://server/pre/');
|
||||
expect(location.$$rewrite('http://server/pre/')).toEqual('http://server/pre/');
|
||||
expect(location.$$rewrite('http://server/pre/otherPath')).toEqual('http://server/pre/#!otherPath');
|
||||
expect(locationIndex.$$rewrite('http://server/pre')).toEqual('http://server/pre/');
|
||||
expect(locationIndex.$$rewrite('http://server/pre/')).toEqual(undefined);
|
||||
expect(locationIndex.$$rewrite('http://server/pre/otherPath')).toEqual('http://server/pre/index.html#!otherPath');
|
||||
expect(parseLinkAndReturn(location, 'http://other')).toEqual(undefined);
|
||||
expect(parseLinkAndReturn(location, 'http://server/pre')).toEqual('http://server/pre/#!');
|
||||
expect(parseLinkAndReturn(location, 'http://server/pre/')).toEqual('http://server/pre/#!');
|
||||
expect(parseLinkAndReturn(location, 'http://server/pre/otherPath')).toEqual('http://server/pre/#!/otherPath');
|
||||
|
||||
expect(parseLinkAndReturn(locationIndex, 'http://server/pre')).toEqual('http://server/pre/index.html#!');
|
||||
expect(parseLinkAndReturn(locationIndex, 'http://server/pre/')).toEqual(undefined);
|
||||
expect(parseLinkAndReturn(locationIndex, 'http://server/pre/otherPath')).toEqual('http://server/pre/index.html#!/otherPath');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+35
-4
@@ -3,9 +3,10 @@
|
||||
describe('parser', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
/* global getterFnCache: true, promiseWarningCache: true */
|
||||
/* global getterFnCacheDefault: true, getterFnCacheExpensive: true, promiseWarningCache: true */
|
||||
// clear caches
|
||||
getterFnCache = {};
|
||||
getterFnCacheDefault = {};
|
||||
getterFnCacheExpensive = {};
|
||||
promiseWarningCache = {};
|
||||
});
|
||||
|
||||
@@ -649,9 +650,26 @@ describe('parser', function() {
|
||||
expect(scope.$eval('a + \n b.c + \r "\td" + \t \r\n\r "\r\n\n"')).toEqual("abc\td\r\n\n");
|
||||
});
|
||||
|
||||
|
||||
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() {
|
||||
@@ -1050,7 +1068,6 @@ describe('parser', function() {
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
|
||||
it('should call the function once when it is not part of the context', function() {
|
||||
var count = 0;
|
||||
scope.fn = function() {
|
||||
@@ -1061,6 +1078,20 @@ describe('parser', function() {
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
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', true));
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.w');
|
||||
}));
|
||||
});
|
||||
|
||||
it('should call the function once when it is part of the context on assignments', function() {
|
||||
var count = 0;
|
||||
|
||||
@@ -1361,6 +1361,31 @@ describe('Scope', function() {
|
||||
expect(child1.$$listenerCount).toEqual({event1: 1});
|
||||
expect(child2.$$listenerCount).toEqual({});
|
||||
}));
|
||||
|
||||
|
||||
it('should not decrement $$listenerCount when called second time', inject(function($rootScope) {
|
||||
var child = $rootScope.$new(),
|
||||
listener1Spy = jasmine.createSpy(),
|
||||
listener2Spy = jasmine.createSpy();
|
||||
|
||||
child.$on('abc', listener1Spy);
|
||||
expect($rootScope.$$listenerCount).toEqual({abc: 1});
|
||||
expect(child.$$listenerCount).toEqual({abc: 1});
|
||||
|
||||
var deregisterEventListener = child.$on('abc', listener2Spy);
|
||||
expect($rootScope.$$listenerCount).toEqual({abc: 2});
|
||||
expect(child.$$listenerCount).toEqual({abc: 2});
|
||||
|
||||
deregisterEventListener();
|
||||
|
||||
expect($rootScope.$$listenerCount).toEqual({abc: 1});
|
||||
expect(child.$$listenerCount).toEqual({abc: 1});
|
||||
|
||||
deregisterEventListener();
|
||||
|
||||
expect($rootScope.$$listenerCount).toEqual({abc: 1});
|
||||
expect(child.$$listenerCount).toEqual({abc: 1});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3159,6 +3159,64 @@ describe("ngAnimate", function() {
|
||||
});
|
||||
|
||||
|
||||
it("should reset the getComputedStyle lookup cache even when no animation is found",
|
||||
inject(function($compile, $rootScope, $animate, $sniffer, $document) {
|
||||
|
||||
if (!$sniffer.transitions) return;
|
||||
|
||||
$animate.enabled();
|
||||
|
||||
var html = '<div>' +
|
||||
' <div class="toggle" ng-if="onOff">On or Off</div>' +
|
||||
'</div>';
|
||||
|
||||
ss.addRule('.activated .toggle', '-webkit-transition:1s linear all;' +
|
||||
'transition:1s linear all;');
|
||||
|
||||
var child, element = $compile(html)($rootScope);
|
||||
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
$rootScope.onOff = true;
|
||||
$rootScope.$digest();
|
||||
|
||||
child = element.find('div');
|
||||
expect(child).not.toHaveClass('ng-enter');
|
||||
expect(child.parent()[0]).toEqual(element[0]);
|
||||
$animate.triggerReflow();
|
||||
|
||||
$rootScope.onOff = false;
|
||||
$rootScope.$digest();
|
||||
|
||||
child = element.find('div');
|
||||
expect(child.parent().length).toBe(0);
|
||||
$animate.triggerReflow();
|
||||
|
||||
element.addClass('activated');
|
||||
$rootScope.$digest();
|
||||
$animate.triggerReflow();
|
||||
|
||||
$rootScope.onOff = true;
|
||||
$rootScope.$digest();
|
||||
|
||||
child = element.find('div');
|
||||
expect(child).toHaveClass('ng-enter');
|
||||
$animate.triggerReflow();
|
||||
expect(child).toHaveClass('ng-enter-active');
|
||||
|
||||
browserTrigger(child, 'transitionend',
|
||||
{ timeStamp: Date.now() + 1000, elapsedTime: 2000 });
|
||||
|
||||
$animate.triggerCallbacks();
|
||||
|
||||
$rootScope.onOff = false;
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(child).toHaveClass('ng-leave');
|
||||
$animate.triggerReflow();
|
||||
expect(child).toHaveClass('ng-leave-active');
|
||||
}));
|
||||
|
||||
it("should cancel and perform the dom operation only after the reflow has run",
|
||||
inject(function($compile, $rootScope, $animate, $sniffer, $timeout) {
|
||||
|
||||
Vendored
+20
@@ -947,6 +947,26 @@ describe('ngMock', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should provide "expect" methods for each HTTP verb', function() {
|
||||
expect(typeof hb.expectGET).toBe("function");
|
||||
expect(typeof hb.expectPOST).toBe("function");
|
||||
expect(typeof hb.expectPUT).toBe("function");
|
||||
expect(typeof hb.expectPATCH).toBe("function");
|
||||
expect(typeof hb.expectDELETE).toBe("function");
|
||||
expect(typeof hb.expectHEAD).toBe("function");
|
||||
});
|
||||
|
||||
|
||||
it('should provide "when" methods for each HTTP verb', function() {
|
||||
expect(typeof hb.whenGET).toBe("function");
|
||||
expect(typeof hb.whenPOST).toBe("function");
|
||||
expect(typeof hb.whenPUT).toBe("function");
|
||||
expect(typeof hb.whenPATCH).toBe("function");
|
||||
expect(typeof hb.whenDELETE).toBe("function");
|
||||
expect(typeof hb.whenHEAD).toBe("function");
|
||||
});
|
||||
|
||||
|
||||
it('should respond with first matched definition', function() {
|
||||
hb.when('GET', '/url1').respond(200, 'content', {});
|
||||
hb.when('GET', '/url1').respond(201, 'another', {});
|
||||
|
||||
@@ -219,13 +219,13 @@ describe('$route', function() {
|
||||
expect($route.current).toBeUndefined();
|
||||
}));
|
||||
|
||||
it('matches literal *', inject(function($route, $location, $rootScope) {
|
||||
it('matches literal .', inject(function($route, $location, $rootScope) {
|
||||
$location.path('/$testX23/foo*(bar)/222');
|
||||
$rootScope.$digest();
|
||||
expect($route.current).toBeUndefined();
|
||||
}));
|
||||
|
||||
it('matches literal .', inject(function($route, $location, $rootScope) {
|
||||
it('matches literal *', inject(function($route, $location, $rootScope) {
|
||||
$location.path('/$test.23/foooo(bar)/222');
|
||||
$rootScope.$digest();
|
||||
expect($route.current).toBeUndefined();
|
||||
@@ -830,6 +830,29 @@ describe('$route', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should properly interpolate optional and eager route vars ' +
|
||||
'when redirecting from path with trailing slash', function() {
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/foo/:id?/:subid?', {templateUrl: 'foo.html'});
|
||||
$routeProvider.when('/bar/:id*/:subid', {templateUrl: 'bar.html'});
|
||||
});
|
||||
|
||||
inject(function($location, $rootScope, $route) {
|
||||
$location.path('/foo/id1/subid2/');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toEqual('/foo/id1/subid2');
|
||||
expect($route.current.templateUrl).toEqual('foo.html');
|
||||
|
||||
$location.path('/bar/id1/extra/subid2/');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toEqual('/bar/id1/extra/subid2');
|
||||
expect($route.current.templateUrl).toEqual('bar.html');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should allow custom redirectTo function to be used', function() {
|
||||
function customRedirectFn(routePathParams, path, search) {
|
||||
expect(routePathParams).toEqual({id: 'id3'});
|
||||
|
||||
@@ -29,6 +29,10 @@ describe('linky', function() {
|
||||
toEqual('my email is "<a href="mailto:me@example.com">me@example.com</a>"');
|
||||
});
|
||||
|
||||
it('should handle quotes in the email', function() {
|
||||
expect(linky('foo@"bar.com')).toEqual('<a href="mailto:foo@"bar.com">foo@"bar.com</a>');
|
||||
});
|
||||
|
||||
it('should handle target:', function() {
|
||||
expect(linky("http://example.com", "_blank")).
|
||||
toEqual('<a target="_blank" href="http://example.com">http://example.com</a>');
|
||||
|
||||
Reference in New Issue
Block a user