Compare commits
368 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| de6d4ca1da | |||
| 776dfc678b | |||
| 9532234bf1 | |||
| 5f5d4feadb | |||
| 791804bdbf | |||
| 7eafbb98c6 | |||
| bb8448c011 | |||
| 2ed53087d7 | |||
| 0af172040e | |||
| e19b04c9ec | |||
| 37e8b12265 | |||
| 1ace5eb396 | |||
| 3c2aee01b0 | |||
| 37bdcc984a | |||
| 027f20be1f | |||
| 9b7c1d0f7c | |||
| 5548328b67 | |||
| 7c6b1e06e8 | |||
| 288b69a314 | |||
| 2a2123441c | |||
| 1d7a95df56 | |||
| 1ed638582d | |||
| 3b14092135 | |||
| ef268196b9 | |||
| 12ba6cec4f | |||
| b7e1fb0515 | |||
| 755beb2b66 | |||
| 6d70ff5c8d | |||
| ace54ff08c | |||
| f5835963d5 | |||
| f3231b9447 | |||
| a8a3efb5f0 | |||
| e47f8d2b96 | |||
| dba6bc73e8 | |||
| c0a0781425 | |||
| 035e0130f3 | |||
| 9e57ce0c7a | |||
| 42a5033c56 | |||
| 6b19e7d527 | |||
| 28273b7f1e | |||
| ec54712ff3 | |||
| 23dd78f8a4 | |||
| d46fe3c23f | |||
| 92ca7efaa4 | |||
| 7090924515 | |||
| df744f3af4 | |||
| 8155c3a29e | |||
| b001c8ece5 | |||
| bec4435945 | |||
| 6fb1054ce6 | |||
| a83eced974 | |||
| 7cc4063303 | |||
| d8e242418d | |||
| 6518369f25 | |||
| 649b892205 | |||
| e0295cfec4 | |||
| 17fc6a70fe | |||
| 5d0f9ce4c7 | |||
| 250aec71f3 | |||
| 3b317c5dcb | |||
| e4cfb9d938 | |||
| 87ba8221ec | |||
| e34519e93b | |||
| 69be39fccf | |||
| deac80a6e8 | |||
| 0539611bac | |||
| d2177ae312 | |||
| f3bff27460 | |||
| 4df45b20d4 | |||
| 5745734991 | |||
| 0551aa95f0 | |||
| b62327ec2d | |||
| 07a58dd766 | |||
| ffe5e01584 | |||
| 3c2e1c5e4d | |||
| cdf7781878 | |||
| 7e8d3c1736 | |||
| d30845a757 | |||
| 06ed8ef012 | |||
| 2f437e8978 | |||
| faf02f0c4d | |||
| b8bd4d5460 | |||
| b2f46251ac | |||
| a26234f718 | |||
| 61315211da | |||
| af89daf464 | |||
| 224d7d6e90 | |||
| a179a9a96e | |||
| 610a5a0c14 | |||
| 4f5583465a | |||
| 75487ec933 | |||
| dddb1221fa | |||
| 69e4d40a76 | |||
| d521619c58 | |||
| 5ae63fd385 | |||
| 8b44324814 | |||
| 9e991ddb1d | |||
| 93070f1488 | |||
| 3c8583e5dd | |||
| 78699c62ea | |||
| 49f9e4cef1 | |||
| 7e746015ea | |||
| 8c269883fd | |||
| a69674b36d | |||
| 8b9e6c3501 | |||
| c97c53dbd4 | |||
| c6392616ea | |||
| 74dd2f7980 | |||
| c0cb9f8c14 | |||
| 7dff7bb696 | |||
| cdf6fb19c8 | |||
| c909f49112 | |||
| cc821502bc | |||
| 037aefae47 | |||
| febb4c1c35 | |||
| 76a6047af6 | |||
| b6b7c5a1d6 | |||
| 5b5f35d5e4 | |||
| 14948cf5d9 | |||
| 2b0978b07c | |||
| 1122dc7a5b | |||
| a3a9d4af05 | |||
| 36089931a5 | |||
| 74db36ee94 | |||
| 8d42ce8563 | |||
| f0c6ebc076 | |||
| 59d9b89852 | |||
| 6aac69039e | |||
| 9e96d98345 | |||
| 1e13544da8 | |||
| e03182f018 | |||
| f2d526190a | |||
| 039b138042 | |||
| 30a9da5dc1 | |||
| 25e1ad9a94 | |||
| 37cced6296 | |||
| a66c968df2 | |||
| c398d7d370 | |||
| d93533812b | |||
| ff11061a8f | |||
| c52bfd37ee | |||
| f8b755982a | |||
| bcc7089b3c | |||
| d43cc3f893 | |||
| c9d937082c | |||
| 26adeb119b | |||
| 15183f3e1f | |||
| 2c405f4171 | |||
| 8991680d8a | |||
| ec801ac137 | |||
| d6da505f4e | |||
| 5f7054bf5d | |||
| cf4ed8a145 | |||
| d1e48fcbf3 | |||
| cc42c99bec | |||
| 3c7bfa77aa | |||
| 2ee0f56c54 | |||
| 9b18644f30 | |||
| fce100a46c | |||
| 3a75b1124d | |||
| a32bc40fd7 | |||
| cfe13b5dac | |||
| d859dcecea | |||
| d3b32a7c94 | |||
| 1b17dfa693 | |||
| e6d9bea4f3 | |||
| c8e9105fe6 | |||
| d644dcfa52 | |||
| e7d37ee45a | |||
| 733a97adf8 | |||
| 96ed9ff59a | |||
| b9a9f91fbf | |||
| 650fd933df | |||
| e249502880 | |||
| ca3e0e7374 | |||
| e6966e05f5 | |||
| 682418f029 | |||
| c8fd7fd0e2 | |||
| 168db33985 | |||
| 79af2badcb | |||
| 610927d77b | |||
| 55d15806fb | |||
| 94e1c0391c | |||
| b21f4a376d | |||
| f28f283fcf | |||
| e362a510e3 | |||
| 8a7f752a80 | |||
| af7e0bd0a7 | |||
| bd524fc4e5 | |||
| 19a324ce11 | |||
| cd8b78ebfd | |||
| 8891757891 | |||
| 5c5193946d | |||
| ffa6c5195f | |||
| a758799c7f | |||
| e9253a88b9 | |||
| f3e053cb6f | |||
| 04450c48df | |||
| 8650843603 | |||
| e034fa08a8 | |||
| c6b4ab3548 | |||
| b429f538a3 | |||
| b3cae4f457 | |||
| 7b52a976e1 | |||
| 3a624a7ff5 | |||
| b32adb7dea | |||
| 271d2bed3a | |||
| 249a1d84e7 | |||
| fdf85bfd86 | |||
| 090e5426ac | |||
| 7c67b2fb6a | |||
| 3c9b39ff52 | |||
| 54b3875ba5 | |||
| 008a782bc8 | |||
| 524c5c8b5d | |||
| b936236fbc | |||
| fc115bfd0d | |||
| bca1604c12 | |||
| f4517b500c | |||
| f54edbbdd4 | |||
| be50e0769a | |||
| cf78fb5661 | |||
| 5c9eb75867 | |||
| f43cf3b816 | |||
| 175e727f05 | |||
| d938983c06 | |||
| 8d69f4b93a | |||
| ca96ec32f9 | |||
| 4f59022582 | |||
| 3bd95dbb1a | |||
| c959fa4fe8 | |||
| a5d434d857 | |||
| 0ae0591f42 | |||
| 43ac783d35 | |||
| c96dc60594 | |||
| b440ad36f3 | |||
| 8b2532cec7 | |||
| 8db47ca7d4 | |||
| bcaa3bb373 | |||
| 6fc4fdb438 | |||
| 6a5f8c0483 | |||
| 20c116d9d5 | |||
| 4a04c2ec0c | |||
| 89dd566277 | |||
| 9d168f058f | |||
| 5418564f04 | |||
| b0a05a7531 | |||
| 209b67df6a | |||
| 2e1539356a | |||
| 331cd5a8cb | |||
| f2ebfa16b0 | |||
| 95276a7e10 | |||
| 5dbd942bac | |||
| 84c13d96ff | |||
| 79941d2527 | |||
| 03ebecd5eb | |||
| 62bb728d07 | |||
| b8eb843b25 | |||
| 053247e412 | |||
| 7fa391c979 | |||
| b01c28c900 | |||
| c0b9e94dec | |||
| 83fbdd1097 | |||
| 03042c52b9 | |||
| 2a4a8226d1 | |||
| c81d8176cc | |||
| 04329151d2 | |||
| a9be003fce | |||
| ca30fce28c | |||
| b6e4a71166 | |||
| d9eff86ef7 | |||
| 8cb9c99ec0 | |||
| 9473780e77 | |||
| eb5fd400d3 | |||
| 0472c5f07e | |||
| 92558fe411 | |||
| d519953a4b | |||
| 4909d1d39d | |||
| 7079ff5eb6 | |||
| 10e1c759f4 | |||
| 6c67719dfa | |||
| cebd015f78 | |||
| fbdab513dd | |||
| f2b7fffdc0 | |||
| 42c38b29f7 | |||
| f299fd5122 | |||
| 05c88b866b | |||
| 9b08bfa251 | |||
| 99a000bac2 | |||
| f353fea042 | |||
| b1f50307b3 | |||
| d0c0eadedd | |||
| b8fac353f0 | |||
| b22308152f | |||
| 5e9041818b | |||
| db861db1f2 | |||
| b12d1b6813 | |||
| acb499f820 | |||
| 9a710c788d | |||
| 1b34c6d558 | |||
| a62c7b8b4e | |||
| 62cfedbe0c | |||
| 5cb7297a08 | |||
| 0f05516d14 | |||
| f5f1200f25 | |||
| c023c850c3 | |||
| 5318588d6e | |||
| 14c8f6a7ca | |||
| 351deb555f | |||
| 847d2da0f8 | |||
| dbefd671e4 | |||
| aff68a9ddf | |||
| 0a71753ce3 | |||
| 1a8642aac2 | |||
| 8114d55a15 | |||
| 9398040a41 | |||
| d804bbcd51 | |||
| d3fa7a2e9e | |||
| 8693eac417 | |||
| e0184d4aef | |||
| 1702e49548 | |||
| d6706efe7f | |||
| b08d4b22d2 | |||
| e8ded01cf5 | |||
| 7a5f25f667 | |||
| 96697f464f | |||
| 7e18724dfa | |||
| c269eb3d26 | |||
| fa62ea810f | |||
| bf8ed8a532 | |||
| d05a2809a1 | |||
| fa6c8c3131 | |||
| f7ac8ef97a | |||
| 4a4b28dbf3 | |||
| 3e12bc481d | |||
| 32137cab82 | |||
| f7b4296c38 | |||
| cab5e1d9b3 | |||
| dfe99836cd | |||
| 0f37194fb7 | |||
| e85774f709 | |||
| 44345c74de | |||
| 58f121a5c2 | |||
| cf6023ef22 | |||
| 2034871764 | |||
| 15d283b114 | |||
| 9be169365c | |||
| 00683a8bbb | |||
| f00b6cca02 | |||
| e05a97c6f5 | |||
| 2e3651686c | |||
| 536de14821 | |||
| e0a54f6b20 | |||
| 9767f7bdd3 | |||
| 167aa0c29c | |||
| 4ccd9eb883 | |||
| c0d638a94b | |||
| 054d40f338 | |||
| 5cec32492c | |||
| c25cb7d488 | |||
| 54e4a6ffbf | |||
| eee9a51fad | |||
| 77e6d833f6 | |||
| 33ad2b4126 | |||
| b84eaffd39 | |||
| a1107e81eb | |||
| e3e8813e3c | |||
| 6e2d9711e8 |
+122
-2
@@ -1,3 +1,123 @@
|
||||
<a name="1.1.3"></a>
|
||||
# 1.1.3 radioactive-gargle (2013-02-20)
|
||||
|
||||
_Note: 1.1.x releases are [considered unstable](http://blog.angularjs.org/2012/07/angularjs-10-12-roadmap.html).
|
||||
They pass all tests but we reserve the right to change new features/apis in between minor releases. Check them
|
||||
out and please give us feedback._
|
||||
|
||||
_Note: This release also contains all bug fixes available in [1.0.5](#1.0.5)._
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- initialize interpolated attributes before directive linking
|
||||
([bb8448c0](https://github.com/angular/angular.js/commit/bb8448c011127306df08c7479b66e5afe7a0fa94))
|
||||
- interpolate @ locals before the link function runs
|
||||
([2ed53087](https://github.com/angular/angular.js/commit/2ed53087d7dd06d728e333a449265f7685275548))
|
||||
- **$http:**
|
||||
- do not encode special characters `@$:,` in params
|
||||
([288b69a3](https://github.com/angular/angular.js/commit/288b69a314e9bd14458b6647532eb62aad5c5cdf))
|
||||
- **$resource:**
|
||||
- params should expand array values properly
|
||||
([2a212344](https://github.com/angular/angular.js/commit/2a2123441c2b749b8f316a24c3ca3f77a9132a01))
|
||||
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$http:** allow overriding the XSRF header and cookie name
|
||||
([8155c3a2](https://github.com/angular/angular.js/commit/8155c3a29ea0eb14806913b8ac08ba7727e1969c))
|
||||
- **$parse:** added `constant` and `literal` properties
|
||||
([1ed63858](https://github.com/angular/angular.js/commit/1ed638582d2f2c7f89384d9712f4cfac52cc5b70))
|
||||
- **$resource:** expose promise based api via $then and $resolved
|
||||
([dba6bc73](https://github.com/angular/angular.js/commit/dba6bc73e802fdae685a9f351d3e23c7efa8568a))
|
||||
- **$routeProvider:** add support to catch-all parameters in routes
|
||||
([7eafbb98](https://github.com/angular/angular.js/commit/7eafbb98c64c0dc079d7d3ec589f1270b7f6fea5))
|
||||
- **Scope:**
|
||||
- expose transcluded and isolate scope info for batarang
|
||||
([649b8922](https://github.com/angular/angular.js/commit/649b892205615a144dafff9984c0e6ab10ed341d))
|
||||
- only evaluate constant $watch expressions once
|
||||
([1d7a95df](https://github.com/angular/angular.js/commit/1d7a95df565192fc02a18b0b297b39dd615eaeb5))
|
||||
- **angular.noConflict:** added api to restore previous angular namespace reference
|
||||
([12ba6cec](https://github.com/angular/angular.js/commit/12ba6cec4fb79521101744e02a7e09f9fbb591c4))
|
||||
- **Directives:**
|
||||
- **ngSwitch:** support multiple matches on ngSwitchWhen and ngSwitchDefault
|
||||
([0af17204](https://github.com/angular/angular.js/commit/0af172040e03811c59d01682968241e3df226774),
|
||||
[#1074](https://github.com/angular/angular.js/issues/1074))
|
||||
- **Filters:**
|
||||
- **date:** add `[.,]sss` formatter for milliseconds
|
||||
([df744f3a](https://github.com/angular/angular.js/commit/df744f3af46fc227a934f16cb63c7a6038e7133b))
|
||||
- **filter:** add comparison function to filter
|
||||
([ace54ff0](https://github.com/angular/angular.js/commit/ace54ff08c4593195b49eadb04d258e6409d969e))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$http:** due to [288b69a3](https://github.com/angular/angular.js/commit/288b69a314e9bd14458b6647532eb62aad5c5cdf),
|
||||
$http now follows RFC3986 and does not encode special characters like `$@,:` in params.
|
||||
If your application needs to encode these characters, encode them manually, before sending the request.
|
||||
- **$resource:** due to [2a212344](https://github.com/angular/angular.js/commit/2a2123441c2b749b8f316a24c3ca3f77a9132a01),
|
||||
if the server relied on the buggy behavior of serializing arrays as http query arguments then
|
||||
either the backend should be fixed or a simple serialization of the array should be done
|
||||
on the client before calling the resource service.
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.0.5"></a>
|
||||
# 1.0.5 flatulent-propulsion (2013-02-20)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- sanitize values bound to a[href]
|
||||
([9532234b](https://github.com/angular/angular.js/commit/9532234bf1c408af9a6fd2c4743fdb585b920531))
|
||||
- rename $compileNote to compileNode
|
||||
([92ca7efa](https://github.com/angular/angular.js/commit/92ca7efaa4bc4f37da3008b234e19343a1fa4207),
|
||||
[#1941](https://github.com/angular/angular.js/issues/1941))
|
||||
- should not leak memory when there are top level empty text nodes
|
||||
([791804bd](https://github.com/angular/angular.js/commit/791804bdbfa6da7a39283623bd05628a01cd8720))
|
||||
- allow startingTag method to handle text / comment nodes
|
||||
([755beb2b](https://github.com/angular/angular.js/commit/755beb2b66ce9f9f9a218f2355bbaf96d94fbc15))
|
||||
- **$cookies:** set cookies on Safari&IE when base[href] is undefined
|
||||
([70909245](https://github.com/angular/angular.js/commit/7090924515214752b919b0c5630b3ea5e7c77223),
|
||||
[#1190](https://github.com/angular/angular.js/issues/1190))
|
||||
- **$http:**
|
||||
- patch for Firefox bug w/ CORS and response headers
|
||||
([e19b04c9](https://github.com/angular/angular.js/commit/e19b04c9ec985821edf1269c628cfa261f81d631),
|
||||
[#1468](https://github.com/angular/angular.js/issues/1468))
|
||||
- **$resource:**
|
||||
- update RegExp to allow urlParams with out leading slash
|
||||
([b7e1fb05](https://github.com/angular/angular.js/commit/b7e1fb0515798e1b4f3f2426f6b050951bee2617))
|
||||
- **Directives:**
|
||||
- **a:** workaround IE bug affecting mailto urls
|
||||
([37e8b122](https://github.com/angular/angular.js/commit/37e8b12265291918396bfee65d444a8f63697b73),
|
||||
[#1949](https://github.com/angular/angular.js/issues/1949))
|
||||
- **ngClass:** keep track of old ngClass value manually
|
||||
([5f5d4fea](https://github.com/angular/angular.js/commit/5f5d4feadbfa9d8ecc8150041dfd2bca2b2e9fea),
|
||||
[#1637](https://github.com/angular/angular.js/issues/1637))
|
||||
- **ngSwitch:** make ngSwitch compatible with controller backwards-compatiblity module
|
||||
([9b7c1d0f](https://github.com/angular/angular.js/commit/9b7c1d0f7ce442d4ad2ec587e66d2d335e64fa4e))
|
||||
- **Filters:**
|
||||
- **date:** invert timezone sign and always display sign
|
||||
([b001c8ec](https://github.com/angular/angular.js/commit/b001c8ece5472626bf49cf82753e8ac1aafd2513),
|
||||
[#1261](https://github.com/angular/angular.js/issues/1261))
|
||||
- **number:** fix formatting when "0" passed as fractionSize
|
||||
([f5835963](https://github.com/angular/angular.js/commit/f5835963d5982003a713dd354eefd376ed39ac02))
|
||||
- **scenario runner:** include error messages in XML output
|
||||
([d46fe3c2](https://github.com/angular/angular.js/commit/d46fe3c23fa269dcc10249148f2af14f3db6b066))
|
||||
- **Misc:**
|
||||
- don't use instanceof to detect arrays
|
||||
([3c2aee01](https://github.com/angular/angular.js/commit/3c2aee01b0b299995eb92f4255159585b0f53c10),
|
||||
[#1966](https://github.com/angular/angular.js/issues/1966))
|
||||
- angular.forEach should correctly iterate over objects with length prop
|
||||
([ec54712f](https://github.com/angular/angular.js/commit/ec54712ff3dab1ade44f94fa82d67edeffa79a1d),
|
||||
[#1840](https://github.com/angular/angular.js/issues/1840))
|
||||
|
||||
|
||||
|
||||
<a name="1.1.2"></a>
|
||||
# 1.1.2 tofu-animation (2013-01-22)
|
||||
|
||||
@@ -73,6 +193,8 @@ _Note: This release also contains all bug fixes available in [1.0.4](#1.0.4)._
|
||||
- HTTP method should be case-insensitive
|
||||
([8991680d](https://github.com/angular/angular.js/commit/8991680d8ab632dda60cd70c780868c803c74509),
|
||||
[#1403](https://github.com/angular/angular.js/issues/1403))
|
||||
- correct leading slash removal in resource URLs
|
||||
([b2f46251](https://github.com/angular/angular.js/commit/b2f46251aca76c8568ee7d4bab54edbc9d7a186a))
|
||||
- **$route:**
|
||||
- support route params not separated with slashes.
|
||||
([c6392616](https://github.com/angular/angular.js/commit/c6392616ea5245bd0d2f77dded0b948d9e2637c8))
|
||||
@@ -100,8 +222,6 @@ _Note: This release also contains all bug fixes available in [1.0.4](#1.0.4)._
|
||||
- **ngRepeat:** correctly apply $last if repeating over object
|
||||
([7e746015](https://github.com/angular/angular.js/commit/7e746015ea7dec3e9eb81bc4678fa9b6a83bc47c),
|
||||
[#1789](https://github.com/angular/angular.js/issues/1789))
|
||||
- **ngResource:** correct leading slash removal.
|
||||
([b2f46251](https://github.com/angular/angular.js/commit/b2f46251aca76c8568ee7d4bab54edbc9d7a186a))
|
||||
- **ngSwitch:** don't leak when destroyed while not attached
|
||||
([a26234f7](https://github.com/angular/angular.js/commit/a26234f7183013e2fcc9b35377e181ad96dc9917),
|
||||
[#1621](https://github.com/angular/angular.js/issues/1621))
|
||||
|
||||
@@ -26,10 +26,6 @@ Building AngularJS
|
||||
|
||||
Running Tests
|
||||
-------------
|
||||
Running tests requires installation of [Testacular](http://vojtajina.github.com/testacular):
|
||||
|
||||
sudo npm install -g testacular
|
||||
|
||||
To execute all unit tests, use:
|
||||
|
||||
rake test:unit
|
||||
|
||||
@@ -21,6 +21,8 @@ task :default => [:package]
|
||||
|
||||
desc 'Init the build workspace'
|
||||
task :init do
|
||||
%x(npm install)
|
||||
|
||||
FileUtils.mkdir(BUILD_DIR) unless File.directory?(BUILD_DIR)
|
||||
|
||||
v = YAML::load( File.open( 'version.yaml' ) )
|
||||
@@ -57,25 +59,6 @@ task :concat_scenario => :init do
|
||||
end
|
||||
|
||||
|
||||
desc 'Concat JSTD Scenario Adapter'
|
||||
task :concat_jstd_scenario_adapter => :init do
|
||||
|
||||
concat_file('jstd-scenario-adapter.js', [
|
||||
'src/ngScenario/jstd-scenario-adapter/angular.prefix',
|
||||
'src/ngScenario/jstd-scenario-adapter/Adapter.js',
|
||||
'src/ngScenario/jstd-scenario-adapter/angular.suffix',
|
||||
])
|
||||
|
||||
# TODO(vojta) use jstd configuration when implemented
|
||||
# (instead of including jstd-adapter-config.js)
|
||||
File.open(path_to('jstd-scenario-adapter-config.js'), 'w') do |f|
|
||||
f.write("/**\r\n" +
|
||||
" * Configuration for jstd scenario adapter \n */\n" +
|
||||
"var jstdScenarioAdapter = {\n relativeUrlPrefix: '/build/docs/'\n};\n")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
desc 'Concat AngularJS files'
|
||||
task :concat => :init do
|
||||
@@ -115,7 +98,7 @@ end
|
||||
|
||||
|
||||
desc 'Minify JavaScript'
|
||||
task :minify => [:init, :concat, :concat_scenario, :concat_jstd_scenario_adapter] do
|
||||
task :minify => [:init, :concat, :concat_scenario] do
|
||||
[ 'angular.js',
|
||||
'angular-cookies.js',
|
||||
'angular-loader.js',
|
||||
@@ -124,11 +107,7 @@ task :minify => [:init, :concat, :concat_scenario, :concat_jstd_scenario_adapter
|
||||
'angular-bootstrap.js',
|
||||
'angular-bootstrap-prettify.js'
|
||||
].each do |file|
|
||||
unless ENV['TRAVIS']
|
||||
fork { closure_compile(file) }
|
||||
else
|
||||
closure_compile(file)
|
||||
end
|
||||
fork { closure_compile(file) }
|
||||
end
|
||||
|
||||
Process.waitall
|
||||
@@ -366,6 +345,8 @@ end
|
||||
|
||||
|
||||
def start_testacular(config, singleRun, browsers, misc_options)
|
||||
Rake::Task[:init].invoke
|
||||
|
||||
sh "./node_modules/testacular/bin/testacular start " +
|
||||
"#{config} " +
|
||||
"#{'--single-run=true' if singleRun} " +
|
||||
|
||||
@@ -234,7 +234,7 @@ The separation of the controller and the view is important because:
|
||||
|
||||
The model is the data which is used merged with the template to produce the view. To be able to
|
||||
render the model into the view, the model has to be able to be referenced from the scope. Unlike many
|
||||
other frameworks Angular makes no restrictions or requirements an the model. There are no classes
|
||||
other frameworks Angular makes no restrictions or requirements on the model. There are no classes
|
||||
to inherit from or special accessor methods for accessing or changing the model. The model can be
|
||||
primitive, object hash, or a full object Type. In short the model is a plain JavaScript object.
|
||||
|
||||
@@ -248,9 +248,9 @@ primitive, object hash, or a full object Type. In short the model is a plain Jav
|
||||
|
||||
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-view.png">
|
||||
|
||||
The view is what the users sees. The view begins its life as a template, it is merged with the
|
||||
The view is what the user sees. The view begins its life as a template, is merged with the
|
||||
model and finally rendered into the browser DOM. Angular takes a very different approach to
|
||||
rendering the view, compared to most other templating systems.
|
||||
rendering the view compared to most other templating systems.
|
||||
|
||||
* **Others** - Most templating systems begin as an HTML string with special templating markup.
|
||||
Often the template markup breaks the HTML syntax which means that the template can not be
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
@name Developer Guide: Templates: Understanding Angular Filters
|
||||
@description
|
||||
|
||||
Angular filters format data for display to the user. In addition to formatting data, filters can
|
||||
also modify the DOM. This allows filters to handle tasks such as conditionally applying CSS styles
|
||||
to filtered output.
|
||||
Angular filters format data for display to the user.
|
||||
|
||||
For example, you might have a data object that needs to be formatted according to the locale before
|
||||
displaying it to the user. You can pass expressions through a chain of filters like this:
|
||||
|
||||
@@ -95,7 +95,7 @@ Compilation of HTML happens in three phases:
|
||||
var $compile = ...; // injected into your code
|
||||
var scope = ...;
|
||||
|
||||
var html = '<div ng-bind='exp'></div>';
|
||||
var html = '<div ng-bind="exp"></div>';
|
||||
|
||||
// Step 1: parse HTML into DOM element
|
||||
var template = angular.element(html);
|
||||
|
||||
@@ -158,9 +158,9 @@ angular.module('myModule', []).
|
||||
|
||||
angular.module('myModule', []).
|
||||
config(function($provide, $compileProvider, $filterProvider) {
|
||||
$provide.value('a', 123)
|
||||
$provide.factory('a', function() { return 123; })
|
||||
$compileProvider.directive('directiveName', ...).
|
||||
$provide.value('a', 123);
|
||||
$provide.factory('a', function() { return 123; });
|
||||
$compileProvider.directive('directiveName', ...);
|
||||
$filterProvider.register('filterName', ...);
|
||||
});
|
||||
</pre>
|
||||
|
||||
@@ -222,7 +222,8 @@ To run the E2E test suite:
|
||||
|
||||
To create and submit a change:
|
||||
|
||||
1. Please sign our Contributor License Agreement (CLA) before sending pull requests. For any code changes to be
|
||||
1. <a name="CLA"></a>
|
||||
Please sign our Contributor License Agreement (CLA) before sending pull requests. For any code changes to be
|
||||
accepted, the CLA must be signed. It's a quick process, we promise!
|
||||
|
||||
For individuals we have a [simple click-through form](http://code.google.com/legal/individual-cla-v1.0.html). For
|
||||
|
||||
@@ -109,7 +109,7 @@ __`app/index.html`:__
|
||||
|
||||
<html ng-app>
|
||||
|
||||
The `ng-app` attribute is represents an Angular directive (named `ngApp`; Angular uses
|
||||
The `ng-app` attribute represents an Angular directive (named `ngApp`; Angular uses
|
||||
`name-with-dashes` for attribute names and `camelCase` for the corresponding directive name)
|
||||
used to flag an element which Angular should consider to be the root element of our application.
|
||||
This gives application developers the freedom to tell Angular if the entire html page or only a
|
||||
@@ -127,7 +127,7 @@ being the element on which the `ngApp` directive was defined.
|
||||
|
||||
* Double-curly binding with an expression:
|
||||
|
||||
Nothing here {{'yet' + '!'}}`
|
||||
Nothing here {{'yet' + '!'}}
|
||||
|
||||
This line demonstrates the core feature of Angular's templating capabilities – a binding, denoted
|
||||
by double-curlies `{{ }}` as well as a simple expression `'yet' + '!'` used in this binding.
|
||||
|
||||
+23
-5
@@ -55,12 +55,15 @@ describe('ngdoc', function() {
|
||||
'@name a\n' +
|
||||
'@param {*} a short\n' +
|
||||
'@param {Type} b med\n' +
|
||||
'@param {Class=} [c=2] long\nline');
|
||||
'@param {Class=} [c=2] long\nline\n' +
|
||||
'@param {function(number, string=)} d fn with optional arguments');
|
||||
doc.parse();
|
||||
expect(doc.param).toEqual([
|
||||
{name:'a', description:'<p>short</p>', type:'*', optional:false, 'default':undefined},
|
||||
{name:'b', description:'<p>med</p>', type:'Type', optional:false, 'default':undefined},
|
||||
{name:'c', description:'<p>long\nline</p>', type:'Class', optional:true, 'default':'2'}
|
||||
{name:'c', description:'<p>long\nline</p>', type:'Class', optional:true, 'default':'2'},
|
||||
{name:'d', description:'<p>fn with optional arguments</p>',
|
||||
type: 'function(number, string=)', optional: false, 'default':undefined}
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -318,9 +321,9 @@ describe('ngdoc', function() {
|
||||
});
|
||||
|
||||
it('should not parse @property without a type', function() {
|
||||
var doc = new Doc("@property fake");
|
||||
var doc = new Doc("@property fake", 'test.js', '44');
|
||||
expect(function() { doc.parse(); }).
|
||||
toThrow(new Error("Not a valid 'property' format: fake"));
|
||||
toThrow(new Error("Not a valid 'property' format: fake (found in: test.js:44)"));
|
||||
});
|
||||
|
||||
it('should parse @property with type', function() {
|
||||
@@ -350,15 +353,30 @@ describe('ngdoc', function() {
|
||||
describe('@returns', function() {
|
||||
it('should not parse @returns without type', function() {
|
||||
var doc = new Doc("@returns lala");
|
||||
expect(doc.parse).toThrow();
|
||||
expect(function() { doc.parse(); }).
|
||||
toThrow();
|
||||
});
|
||||
|
||||
|
||||
it('should not parse @returns with invalid type', function() {
|
||||
var doc = new Doc("@returns {xx}x} lala", 'test.js', 34);
|
||||
expect(function() { doc.parse(); }).
|
||||
toThrow(new Error("Not a valid 'returns' format: {xx}x} lala (found in: test.js:34)"));
|
||||
});
|
||||
|
||||
|
||||
it('should parse @returns with type and description', function() {
|
||||
var doc = new Doc("@name a\n@returns {string} descrip tion");
|
||||
doc.parse();
|
||||
expect(doc.returns).toEqual({type: 'string', description: '<p>descrip tion</p>'});
|
||||
});
|
||||
|
||||
it('should parse @returns with complex type and description', function() {
|
||||
var doc = new Doc("@name a\n@returns {function(string, number=)} description");
|
||||
doc.parse();
|
||||
expect(doc.returns).toEqual({type: 'function(string, number=)', description: '<p>description</p>'});
|
||||
});
|
||||
|
||||
it('should transform description of @returns with markdown', function() {
|
||||
var doc = new Doc("@name a\n@returns {string} descrip *tion*");
|
||||
doc.parse();
|
||||
|
||||
+56
-41
@@ -214,23 +214,25 @@ Doc.prototype = {
|
||||
if (atName) {
|
||||
var text = trim(atText.join('\n')), match;
|
||||
if (atName == 'param') {
|
||||
match = text.match(/^\{([^}=]+)(=)?\}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
|
||||
// 1 12 2 34 4 5 5 6 6 3 7 7
|
||||
match = text.match(/^\{([^}]+)\}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
|
||||
// 1 1 23 3 4 4 5 5 2 6 6
|
||||
if (!match) {
|
||||
throw new Error("Not a valid 'param' format: " + text);
|
||||
throw new Error("Not a valid 'param' format: " + text + ' (found in: ' + self.file + ':' + self.line + ')');
|
||||
}
|
||||
|
||||
var optional = (match[1].slice(-1) === '=');
|
||||
var param = {
|
||||
name: match[5] || match[4],
|
||||
description:self.markdown(text.replace(match[0], match[7])),
|
||||
type: match[1],
|
||||
optional: !!match[2],
|
||||
'default':match[6]
|
||||
name: match[4] || match[3],
|
||||
description:self.markdown(text.replace(match[0], match[6])),
|
||||
type: optional ? match[1].substring(0, match[1].length-1) : match[1],
|
||||
optional: optional,
|
||||
'default':match[5]
|
||||
};
|
||||
self.param.push(param);
|
||||
} else if (atName == 'returns' || atName == 'return') {
|
||||
match = text.match(/^\{([^}=]+)\}\s+(.*)/);
|
||||
match = text.match(/^\{([^}]+)\}\s+(.*)/);
|
||||
if (!match) {
|
||||
throw new Error("Not a valid 'returns' format: " + text + ' in ' + self.file + ':' + self.line);
|
||||
throw new Error("Not a valid 'returns' format: " + text + ' (found in: ' + self.file + ':' + self.line + ')');
|
||||
}
|
||||
self.returns = {
|
||||
type: match[1],
|
||||
@@ -245,7 +247,7 @@ Doc.prototype = {
|
||||
} else if(atName == 'property') {
|
||||
match = text.match(/^\{(\S+)\}\s+(\S+)(\s+(.*))?/);
|
||||
if (!match) {
|
||||
throw new Error("Not a valid 'property' format: " + text);
|
||||
throw new Error("Not a valid 'property' format: " + text + ' (found in: ' + self.file + ':' + self.line + ')');
|
||||
}
|
||||
var property = new Doc({
|
||||
type: match[1],
|
||||
@@ -383,40 +385,53 @@ Doc.prototype = {
|
||||
var self = this;
|
||||
dom.h('Usage', function() {
|
||||
var restrict = self.restrict || 'AC';
|
||||
|
||||
if (restrict.match(/E/)) {
|
||||
dom.text('as element (see ');
|
||||
dom.text('This directive can be used as custom element, but we aware of ');
|
||||
dom.tag('a', {href:'guide/ie'}, 'IE restrictions');
|
||||
dom.text(')');
|
||||
dom.code(function() {
|
||||
dom.text('<');
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams('\n ', '="', '"');
|
||||
dom.text('>\n</');
|
||||
dom.text(dashCase(self.shortName));
|
||||
dom.text('>');
|
||||
});
|
||||
dom.text('.');
|
||||
}
|
||||
if (restrict.match(/A/)) {
|
||||
var element = self.element || 'ANY';
|
||||
dom.text('as attribute');
|
||||
dom.code(function() {
|
||||
dom.text('<' + element + ' ');
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams('\n ', '="', '"', true);
|
||||
dom.text('>\n ...\n');
|
||||
dom.text('</' + element + '>');
|
||||
});
|
||||
}
|
||||
if (restrict.match(/C/)) {
|
||||
dom.text('as class');
|
||||
var element = self.element || 'ANY';
|
||||
dom.code(function() {
|
||||
dom.text('<' + element + ' class="');
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams(' ', ': ', ';', true);
|
||||
dom.text('">\n ...\n');
|
||||
dom.text('</' + element + '>');
|
||||
|
||||
if (self.usage) {
|
||||
dom.tag('pre', function() {
|
||||
dom.tag('code', function() {
|
||||
dom.text(self.usage);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
if (restrict.match(/E/)) {
|
||||
dom.text('as element:');
|
||||
dom.code(function() {
|
||||
dom.text('<');
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams('\n ', '="', '"');
|
||||
dom.text('>\n</');
|
||||
dom.text(dashCase(self.shortName));
|
||||
dom.text('>');
|
||||
});
|
||||
}
|
||||
if (restrict.match(/A/)) {
|
||||
var element = self.element || 'ANY';
|
||||
dom.text('as attribute');
|
||||
dom.code(function() {
|
||||
dom.text('<' + element + ' ');
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams('\n ', '="', '"', true);
|
||||
dom.text('>\n ...\n');
|
||||
dom.text('</' + element + '>');
|
||||
});
|
||||
}
|
||||
if (restrict.match(/C/)) {
|
||||
dom.text('as class');
|
||||
var element = self.element || 'ANY';
|
||||
dom.code(function() {
|
||||
dom.text('<' + element + ' class="');
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams(' ', ': ', ';', true);
|
||||
dom.text('">\n ...\n');
|
||||
dom.text('</' + element + '>');
|
||||
});
|
||||
}
|
||||
}
|
||||
self.html_usage_directiveInfo(dom);
|
||||
self.html_usage_parameters(dom);
|
||||
|
||||
Vendored
+3704
-3668
File diff suppressed because it is too large
Load Diff
Vendored
+2
-4
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
1.7.2
|
||||
1.8.2
|
||||
|
||||
+4
-4
@@ -2,9 +2,9 @@
|
||||
"name": "AngularJS",
|
||||
"version": "0.0.0",
|
||||
"dependencies" : {
|
||||
"testacular" : "canary",
|
||||
"jasmine-node" : "*",
|
||||
"q-fs" : "*",
|
||||
"qq" : "*"
|
||||
"testacular" : "0.5.9",
|
||||
"jasmine-node" : "1.2.3",
|
||||
"q-fs" : "0.1.36",
|
||||
"qq" : "0.3.5"
|
||||
}
|
||||
}
|
||||
|
||||
+61
-11
@@ -49,8 +49,7 @@ if ('i' !== 'I'.toLowerCase()) {
|
||||
function fromCharCode(code) {return String.fromCharCode(code);}
|
||||
|
||||
|
||||
var Error = window.Error,
|
||||
/** holds major version number for IE or NaN for real browsers */
|
||||
var /** holds major version number for IE or NaN for real browsers */
|
||||
msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]),
|
||||
jqLite, // delay binding since jQuery could be loaded after us.
|
||||
jQuery, // delay binding
|
||||
@@ -58,12 +57,32 @@ var Error = window.Error,
|
||||
push = [].push,
|
||||
toString = Object.prototype.toString,
|
||||
|
||||
|
||||
_angular = window.angular,
|
||||
/** @name angular */
|
||||
angular = window.angular || (window.angular = {}),
|
||||
angularModule,
|
||||
nodeName_,
|
||||
uid = ['0', '0', '0'];
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.noConflict
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Restores the previous global value of angular and returns the current instance. Other libraries may already use the
|
||||
* angular namespace. Or a previous version of angular is already loaded on the page. In these cases you may want to
|
||||
* restore the previous namespace and keep a reference to angular.
|
||||
*
|
||||
* @return {Object} The current angular namespace
|
||||
*/
|
||||
function noConflict() {
|
||||
var a = window.angular;
|
||||
window.angular = _angular;
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.forEach
|
||||
@@ -91,6 +110,30 @@ var Error = window.Error,
|
||||
* @param {Object=} context Object to become context (`this`) for the iterator function.
|
||||
* @returns {Object|Array} Reference to `obj`.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {*} obj
|
||||
* @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...)
|
||||
*/
|
||||
function isArrayLike(obj) {
|
||||
if (!obj || (typeof obj.length !== 'number')) return false;
|
||||
|
||||
// We have on object which has length property. Should we treat it as array?
|
||||
if (typeof obj.hasOwnProperty != 'function' &&
|
||||
typeof obj.constructor != 'function') {
|
||||
// This is here for IE8: it is a bogus object treat it as array;
|
||||
return true;
|
||||
} else {
|
||||
return obj instanceof JQLite || // JQLite
|
||||
(jQuery && obj instanceof jQuery) || // jQuery
|
||||
toString.call(obj) !== '[object Object]' || // some browser native object
|
||||
typeof obj.callee === 'function'; // arguments (on IE8 looks like regular obj)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function forEach(obj, iterator, context) {
|
||||
var key;
|
||||
if (obj) {
|
||||
@@ -102,7 +145,7 @@ function forEach(obj, iterator, context) {
|
||||
}
|
||||
} else if (obj.forEach && obj.forEach !== forEach) {
|
||||
obj.forEach(iterator, context);
|
||||
} else if (isObject(obj) && isNumber(obj.length)) {
|
||||
} else if (isArrayLike(obj)) {
|
||||
for (key = 0; key < obj.length; key++)
|
||||
iterator.call(context, obj[key], key);
|
||||
} else {
|
||||
@@ -147,7 +190,7 @@ function reverseParams(iteratorFn) {
|
||||
/**
|
||||
* A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric
|
||||
* characters such as '012ABC'. The reason why we are not using simply a number counter is that
|
||||
* the number string gets longer over time, and it can also overflow, where as the the nextId
|
||||
* the number string gets longer over time, and it can also overflow, where as the nextId
|
||||
* will grow much slower, it is a string, and it will never overflow.
|
||||
*
|
||||
* @returns an unique alpha-numeric string
|
||||
@@ -543,9 +586,7 @@ function copy(source, destination){
|
||||
} else {
|
||||
if (source === destination) throw Error("Can't copy equivalent objects or arrays");
|
||||
if (isArray(source)) {
|
||||
while(destination.length) {
|
||||
destination.pop();
|
||||
}
|
||||
destination.length = 0;
|
||||
for ( var i = 0; i < source.length; i++) {
|
||||
destination.push(copy(source[i]));
|
||||
}
|
||||
@@ -756,9 +797,18 @@ function startingTag(element) {
|
||||
// are not allowed to have children. So we just ignore it.
|
||||
element.html('');
|
||||
} catch(e) {}
|
||||
return jqLite('<div>').append(element).html().
|
||||
match(/^(<[^>]+>)/)[1].
|
||||
replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
|
||||
// As Per DOM Standards
|
||||
var TEXT_NODE = 3;
|
||||
var elemHtml = jqLite('<div>').append(element).html();
|
||||
try {
|
||||
return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
|
||||
elemHtml.
|
||||
match(/^(<[^>]+>)/)[1].
|
||||
replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
|
||||
} catch(e) {
|
||||
return lowercase(elemHtml);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -842,7 +892,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
|
||||
* Use this directive to auto-bootstrap on application. Only
|
||||
* one directive can be used per HTML document. The directive
|
||||
* designates the root of the application and is typically placed
|
||||
* ot the root of the page.
|
||||
* at the root of the page.
|
||||
*
|
||||
* In the example below if the `ngApp` directive would not be placed
|
||||
* on the `html` element then the document would not be compiled
|
||||
|
||||
@@ -48,7 +48,8 @@ function publishExternalAPI(angular){
|
||||
'isDate': isDate,
|
||||
'lowercase': lowercase,
|
||||
'uppercase': uppercase,
|
||||
'callbacks': {counter: 0}
|
||||
'callbacks': {counter: 0},
|
||||
'noConflict': noConflict
|
||||
});
|
||||
|
||||
angularModule = setupModuleLoader(window);
|
||||
|
||||
@@ -192,7 +192,7 @@ function annotate(fn) {
|
||||
* This method does not work with code minfication / obfuscation. For this reason the following annotation strategies
|
||||
* are supported.
|
||||
*
|
||||
* # The `$injector` property
|
||||
* # The `$inject` property
|
||||
*
|
||||
* If a function has an `$inject` property and its value is an array of strings, then the strings represent names of
|
||||
* services to be injected into the function.
|
||||
@@ -410,9 +410,10 @@ function createInjector(modulesToLoad) {
|
||||
decorator: decorator
|
||||
}
|
||||
},
|
||||
providerInjector = createInternalInjector(providerCache, function() {
|
||||
throw Error("Unknown provider: " + path.join(' <- '));
|
||||
}),
|
||||
providerInjector = (providerCache.$injector =
|
||||
createInternalInjector(providerCache, function() {
|
||||
throw Error("Unknown provider: " + path.join(' <- '));
|
||||
})),
|
||||
instanceCache = {},
|
||||
instanceInjector = (instanceCache.$injector =
|
||||
createInternalInjector(instanceCache, function(servicename) {
|
||||
@@ -489,9 +490,7 @@ function createInjector(modulesToLoad) {
|
||||
try {
|
||||
for(var invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
|
||||
var invokeArgs = invokeQueue[i],
|
||||
provider = invokeArgs[0] == '$injector'
|
||||
? providerInjector
|
||||
: providerInjector.get(invokeArgs[0]);
|
||||
provider = providerInjector.get(invokeArgs[0]);
|
||||
|
||||
provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
|
||||
}
|
||||
|
||||
Vendored
+4
-4
@@ -4,10 +4,10 @@ var directive = {};
|
||||
var service = { value: {} };
|
||||
|
||||
var DEPENDENCIES = {
|
||||
'angular.js': 'http://code.angularjs.org/' + angular.version.full + 'angular.min.js',
|
||||
'angular-resource.js': 'http://code.angularjs.org/' + angular.version.full + 'angular-resource.min.js',
|
||||
'angular-sanitize.js': 'http://code.angularjs.org/' + angular.version.full + 'angular-sanitize.min.js',
|
||||
'angular-cookies.js': 'http://code.angularjs.org/' + angular.version.full + 'angular-cookies.min.js'
|
||||
'angular.js': 'http://code.angularjs.org/' + angular.version.full + '/angular.min.js',
|
||||
'angular-resource.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-resource.min.js',
|
||||
'angular-sanitize.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-sanitize.min.js',
|
||||
'angular-cookies.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-cookies.min.js'
|
||||
};
|
||||
|
||||
|
||||
|
||||
+4
-3
@@ -353,11 +353,11 @@ var JQLitePrototype = JQLite.prototype = {
|
||||
// value on get.
|
||||
//////////////////////////////////////////
|
||||
var BOOLEAN_ATTR = {};
|
||||
forEach('multiple,selected,checked,disabled,readOnly,required'.split(','), function(value) {
|
||||
forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
|
||||
BOOLEAN_ATTR[lowercase(value)] = value;
|
||||
});
|
||||
var BOOLEAN_ELEMENTS = {};
|
||||
forEach('input,select,option,textarea,button,form'.split(','), function(value) {
|
||||
forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
|
||||
BOOLEAN_ELEMENTS[uppercase(value)] = true;
|
||||
});
|
||||
|
||||
@@ -659,8 +659,9 @@ forEach({
|
||||
|
||||
append: function(element, node) {
|
||||
forEach(new JQLite(node), function(child){
|
||||
if (element.nodeType === 1)
|
||||
if (element.nodeType === 1 || element.nodeType === 11) {
|
||||
element.appendChild(child);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
+1
-1
@@ -237,7 +237,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
*/
|
||||
self.baseHref = function() {
|
||||
var href = baseElement.attr('href');
|
||||
return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href;
|
||||
return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : '';
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* @returns {object} Newly created cache object with the following set of methods:
|
||||
*
|
||||
* - `{object}` `info()` — Returns id, size, and options of cache.
|
||||
* - `{void}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache.
|
||||
* - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns it.
|
||||
* - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
|
||||
* - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
|
||||
* - `{void}` `removeAll()` — Removes all cached values.
|
||||
@@ -53,6 +53,8 @@ function $CacheFactoryProvider() {
|
||||
if (size > capacity) {
|
||||
this.remove(staleEnd.key);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
|
||||
|
||||
+69
-9
@@ -155,7 +155,8 @@ function $CompileProvider($provide) {
|
||||
Suffix = 'Directive',
|
||||
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
|
||||
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
|
||||
MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ';
|
||||
MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ',
|
||||
urlSanitizationWhitelist = /^\s*(https?|ftp|mailto):/;
|
||||
|
||||
|
||||
/**
|
||||
@@ -209,11 +210,41 @@ function $CompileProvider($provide) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name ng.$compileProvider#urlSanitizationWhitelist
|
||||
* @methodOf ng.$compileProvider
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
|
||||
* urls during a[href] sanitization.
|
||||
*
|
||||
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
|
||||
*
|
||||
* Any url about to be assigned to a[href] via data-binding is first normalized and turned into an
|
||||
* absolute url. Afterwards the url is matched against the `urlSanitizationWhitelist` regular
|
||||
* expression. If a match is found the original url is written into the dom. Otherwise the
|
||||
* absolute url is prefixed with `'unsafe:'` string and only then it is written into the DOM.
|
||||
*
|
||||
* @param {RegExp=} regexp New regexp to whitelist urls with.
|
||||
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
|
||||
* chaining otherwise.
|
||||
*/
|
||||
this.urlSanitizationWhitelist = function(regexp) {
|
||||
if (isDefined(regexp)) {
|
||||
urlSanitizationWhitelist = regexp;
|
||||
return this;
|
||||
}
|
||||
return urlSanitizationWhitelist;
|
||||
};
|
||||
|
||||
|
||||
this.$get = [
|
||||
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
|
||||
'$controller', '$rootScope',
|
||||
'$controller', '$rootScope', '$document',
|
||||
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
|
||||
$controller, $rootScope) {
|
||||
$controller, $rootScope, $document) {
|
||||
|
||||
var Attributes = function(element, attr) {
|
||||
this.$$element = element;
|
||||
@@ -235,7 +266,8 @@ function $CompileProvider($provide) {
|
||||
*/
|
||||
$set: function(key, value, writeAttr, attrName) {
|
||||
var booleanKey = getBooleanAttrName(this.$$element[0], key),
|
||||
$$observers = this.$$observers;
|
||||
$$observers = this.$$observers,
|
||||
normalizedVal;
|
||||
|
||||
if (booleanKey) {
|
||||
this.$$element.prop(key, value);
|
||||
@@ -254,6 +286,19 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sanitize a[href] values
|
||||
if (nodeName_(this.$$element[0]) === 'A' && key === 'href') {
|
||||
urlSanitizationNode.setAttribute('href', value);
|
||||
|
||||
// href property always returns normalized absolute url, so we can match against that
|
||||
normalizedVal = urlSanitizationNode.href;
|
||||
if (!normalizedVal.match(urlSanitizationWhitelist)) {
|
||||
this[key] = value = 'unsafe:' + normalizedVal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (writeAttr !== false) {
|
||||
if (value === null || value === undefined) {
|
||||
this.$$element.removeAttr(attrName);
|
||||
@@ -297,7 +342,8 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
};
|
||||
|
||||
var startSymbol = $interpolate.startSymbol(),
|
||||
var urlSanitizationNode = $document[0].createElement('a'),
|
||||
startSymbol = $interpolate.startSymbol(),
|
||||
endSymbol = $interpolate.endSymbol(),
|
||||
denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
|
||||
? identity
|
||||
@@ -330,7 +376,14 @@ function $CompileProvider($provide) {
|
||||
var $linkNode = cloneConnectFn
|
||||
? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
|
||||
: $compileNodes;
|
||||
$linkNode.data('$scope', scope);
|
||||
|
||||
// Attach scope only to non-text nodes.
|
||||
for(var i = 0, ii = $linkNode.length; i<ii; i++) {
|
||||
var node = $linkNode[i];
|
||||
if (node.nodeType == 1 /* element */ || node.nodeType == 9 /* document */) {
|
||||
$linkNode.eq(i).data('$scope', scope);
|
||||
}
|
||||
}
|
||||
safeAddClass($linkNode, 'ng-scope');
|
||||
if (cloneConnectFn) cloneConnectFn($linkNode, scope);
|
||||
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
|
||||
@@ -420,6 +473,7 @@ function $CompileProvider($provide) {
|
||||
(function(transcludeFn) {
|
||||
return function(cloneFn) {
|
||||
var transcludeScope = scope.$new();
|
||||
transcludeScope.$$transcluded = true;
|
||||
|
||||
return transcludeFn(transcludeScope, cloneFn).
|
||||
bind('$destroy', bind(transcludeScope, transcludeScope.$destroy));
|
||||
@@ -725,6 +779,8 @@ function $CompileProvider($provide) {
|
||||
lastValue,
|
||||
parentGet, parentSet;
|
||||
|
||||
scope.$$isolateBindings[scopeName] = mode + attrName;
|
||||
|
||||
switch (mode) {
|
||||
|
||||
case '@': {
|
||||
@@ -732,6 +788,10 @@ function $CompileProvider($provide) {
|
||||
scope[scopeName] = value;
|
||||
});
|
||||
attrs.$$observers[attrName].$$scope = parentScope;
|
||||
if( attrs[attrName] ) {
|
||||
// If the attribute has been provided then we trigger an interpolation to ensure the value is there for use in the link fn
|
||||
scope[scopeName] = $interpolate(attrs[attrName])(parentScope);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -935,7 +995,7 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
|
||||
directives.unshift(derivedSyncDirective);
|
||||
afterTemplateNodeLinkFn = applyDirectivesToNode(directives, $compileNode, tAttrs, childTranscludeFn);
|
||||
afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn);
|
||||
afterTemplateChildLinkFn = compileNodes($compileNode.contents(), childTranscludeFn);
|
||||
|
||||
|
||||
@@ -1015,10 +1075,10 @@ function $CompileProvider($provide) {
|
||||
function addAttrInterpolateDirective(node, directives, value, name) {
|
||||
var interpolateFn = $interpolate(value, true);
|
||||
|
||||
|
||||
// no interpolation found -> ignore
|
||||
if (!interpolateFn) return;
|
||||
|
||||
|
||||
directives.push({
|
||||
priority: 100,
|
||||
compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
|
||||
@@ -1030,7 +1090,7 @@ function $CompileProvider($provide) {
|
||||
interpolateFn = $interpolate(attr[name], true);
|
||||
}
|
||||
|
||||
attr[name] = undefined;
|
||||
attr[name] = interpolateFn(scope);
|
||||
($$observers[name] || ($$observers[name] = [])).$$inter = true;
|
||||
(attr.$$observers && attr.$$observers[name].$$scope || scope).
|
||||
$watch(interpolateFn, function interpolateFnWatchAction(value) {
|
||||
|
||||
+15
-5
@@ -11,15 +11,25 @@
|
||||
*
|
||||
* The reasoning for this change is to allow easy creation of action links with `ngClick` directive
|
||||
* without changing the location or causing page reloads, e.g.:
|
||||
* <a href="" ng-click="model.$save()">Save</a>
|
||||
* `<a href="" ng-click="model.$save()">Save</a>`
|
||||
*/
|
||||
var htmlAnchorDirective = valueFn({
|
||||
restrict: 'E',
|
||||
compile: function(element, attr) {
|
||||
// turn <a href ng-click="..">link</a> into a link in IE
|
||||
// but only if it doesn't have name attribute, in which case it's an anchor
|
||||
if (!attr.href) {
|
||||
attr.$set('href', '');
|
||||
|
||||
if (msie <= 8) {
|
||||
|
||||
// turn <a href ng-click="..">link</a> into a stylable link in IE
|
||||
// but only if it doesn't have name attribute, in which case it's an anchor
|
||||
if (!attr.href && !attr.name) {
|
||||
attr.$set('href', '');
|
||||
}
|
||||
|
||||
// add a comment node to anchors to workaround IE bug that causes element content to be reset
|
||||
// to new attribute content if attribute is updated with value containing @ and element also
|
||||
// contains value with @
|
||||
// see issue #1949
|
||||
element.append(document.createComment('IE fix'));
|
||||
}
|
||||
|
||||
return function(scope, element) {
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
it('should execute ng-click but not reload when no href but name specified', function() {
|
||||
element('#link-5').click();
|
||||
expect(input('value').val()).toEqual('5');
|
||||
expect(element('#link-5').attr('href')).toBe('');
|
||||
expect(element('#link-5').attr('href')).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should only change url when only ng-href', function() {
|
||||
@@ -272,6 +272,37 @@
|
||||
* @param {string} expression Angular expression that will be evaluated.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ng.directive:ngOpen
|
||||
* @restrict A
|
||||
*
|
||||
* @description
|
||||
* The HTML specs do not require browsers to preserve the special attributes such as open.
|
||||
* (The presence of them means true and absence means false)
|
||||
* This prevents the angular compiler from correctly retrieving the binding expression.
|
||||
* To solve this problem, we introduce the `ngOpen` directive.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Check me check multiple: <input type="checkbox" ng-model="open"><br/>
|
||||
<details id="details" ng-open="open">
|
||||
<summary>Show/Hide me</summary>
|
||||
</details>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should toggle open', function() {
|
||||
expect(element('#details').prop('open')).toBeFalsy();
|
||||
input('open').check();
|
||||
expect(element('#details').prop('open')).toBeTruthy();
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*
|
||||
* @element DETAILS
|
||||
* @param {string} expression Angular expression that will be evaluated.
|
||||
*/
|
||||
|
||||
var ngAttributeAliasDirectives = {};
|
||||
|
||||
@@ -309,8 +340,9 @@ forEach(['src', 'href'], function(attrName) {
|
||||
|
||||
// on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
|
||||
// then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
|
||||
// to set the property as well to achieve the desired effect
|
||||
if (msie) element.prop(attrName, value);
|
||||
// to set the property as well to achieve the desired effect.
|
||||
// we use attr[attrName] value since $set can sanitize the url.
|
||||
if (msie) element.prop(attrName, attr[attrName]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,7 +5,8 @@ var nullFormCtrl = {
|
||||
$addControl: noop,
|
||||
$removeControl: noop,
|
||||
$setValidity: noop,
|
||||
$setDirty: noop
|
||||
$setDirty: noop,
|
||||
$setPristine: noop
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,8 @@ function FormController(element, attrs) {
|
||||
var form = this,
|
||||
parentForm = element.parent().controller('form') || nullFormCtrl,
|
||||
invalidCount = 0, // used to easily determine if we are valid
|
||||
errors = form.$error = {};
|
||||
errors = form.$error = {},
|
||||
controls = [];
|
||||
|
||||
// init state
|
||||
form.$name = attrs.name;
|
||||
@@ -61,6 +63,8 @@ function FormController(element, attrs) {
|
||||
}
|
||||
|
||||
form.$addControl = function(control) {
|
||||
controls.push(control);
|
||||
|
||||
if (control.$name && !form.hasOwnProperty(control.$name)) {
|
||||
form[control.$name] = control;
|
||||
}
|
||||
@@ -73,6 +77,8 @@ function FormController(element, attrs) {
|
||||
forEach(errors, function(queue, validationToken) {
|
||||
form.$setValidity(validationToken, true, control);
|
||||
});
|
||||
|
||||
arrayRemove(controls, control);
|
||||
};
|
||||
|
||||
form.$setValidity = function(validationToken, isValid, control) {
|
||||
@@ -120,6 +126,29 @@ function FormController(element, attrs) {
|
||||
parentForm.$setDirty();
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name ng.directive:form.FormController#$setPristine
|
||||
* @methodOf ng.directive:form.FormController
|
||||
*
|
||||
* @description
|
||||
* Sets the form to its pristine state.
|
||||
*
|
||||
* This method can be called to remove the 'ng-dirty' class and set the form to its pristine
|
||||
* state (ng-pristine class). This method will also propagate to all the controls contained
|
||||
* in this form.
|
||||
*
|
||||
* Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
|
||||
* saving or resetting it.
|
||||
*/
|
||||
form.$setPristine = function () {
|
||||
element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
|
||||
form.$dirty = false;
|
||||
form.$pristine = true;
|
||||
forEach(controls, function(control) {
|
||||
control.$setPristine();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ var inputType = {
|
||||
* patterns defined as scope expressions.
|
||||
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
||||
* interaction with the input element.
|
||||
* @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trimming the
|
||||
* input.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
@@ -35,12 +37,12 @@ var inputType = {
|
||||
<script>
|
||||
function Ctrl($scope) {
|
||||
$scope.text = 'guest';
|
||||
$scope.word = /^\w*$/;
|
||||
$scope.word = /^\s*\w*\s*$/;
|
||||
}
|
||||
</script>
|
||||
<form name="myForm" ng-controller="Ctrl">
|
||||
Single word: <input type="text" name="input" ng-model="text"
|
||||
ng-pattern="word" required>
|
||||
ng-pattern="word" required ng-trim="false">
|
||||
<span class="error" ng-show="myForm.input.$error.required">
|
||||
Required!</span>
|
||||
<span class="error" ng-show="myForm.input.$error.pattern">
|
||||
@@ -69,6 +71,12 @@ var inputType = {
|
||||
input('text').enter('hello world');
|
||||
expect(binding('myForm.input.$valid')).toEqual('false');
|
||||
});
|
||||
|
||||
it('should not be trimmed', function() {
|
||||
input('text').enter('untrimmed ');
|
||||
expect(binding('text')).toEqual('untrimmed ');
|
||||
expect(binding('myForm.input.$valid')).toEqual('true');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
@@ -382,7 +390,14 @@ function isEmpty(value) {
|
||||
function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
|
||||
var listener = function() {
|
||||
var value = trim(element.val());
|
||||
var value = element.val();
|
||||
|
||||
// By default we will trim the value
|
||||
// If the attribute ng-trim exists we will avoid trimming
|
||||
// e.g. <input ng-model="foo" ng-trim="false">
|
||||
if (toBoolean(attr.ngTrim || 'T')) {
|
||||
value = trim(value);
|
||||
}
|
||||
|
||||
if (ctrl.$viewValue !== value) {
|
||||
scope.$apply(function() {
|
||||
@@ -963,6 +978,22 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
parentForm.$setValidity(validationErrorKey, isValid, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name ng.directive:ngModel.NgModelController#$setPristine
|
||||
* @methodOf ng.directive:ngModel.NgModelController
|
||||
*
|
||||
* @description
|
||||
* Sets the control to its pristine state.
|
||||
*
|
||||
* This method can be called to remove the 'ng-dirty' class and set the control to its pristine
|
||||
* state (ng-pristine class).
|
||||
*/
|
||||
this.$setPristine = function () {
|
||||
this.$dirty = false;
|
||||
this.$pristine = true;
|
||||
$element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
function classDirective(name, selector) {
|
||||
name = 'ngClass' + name;
|
||||
return ngDirective(function(scope, element, attr) {
|
||||
var oldVal = undefined;
|
||||
|
||||
scope.$watch(attr[name], ngClassWatchAction, true);
|
||||
|
||||
@@ -26,13 +27,14 @@ function classDirective(name, selector) {
|
||||
}
|
||||
|
||||
|
||||
function ngClassWatchAction(newVal, oldVal) {
|
||||
function ngClassWatchAction(newVal) {
|
||||
if (selector === true || scope.$index % 2 === selector) {
|
||||
if (oldVal && (newVal !== oldVal)) {
|
||||
removeClass(oldVal);
|
||||
}
|
||||
addClass(newVal);
|
||||
}
|
||||
oldVal = newVal;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +67,7 @@ function classDirective(name, selector) {
|
||||
*
|
||||
* The directive won't add duplicate classes if a particular class was already set.
|
||||
*
|
||||
* When the expression changes, the previously added classes are removed and only then the classes
|
||||
* When the expression changes, the previously added classes are removed and only then the
|
||||
* new classes are added.
|
||||
*
|
||||
* @element ANY
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
*/
|
||||
var ngEventDirectives = {};
|
||||
forEach(
|
||||
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave'.split(' '),
|
||||
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup'.split(' '),
|
||||
function(name) {
|
||||
var directiveName = directiveNormalize('ng-' + name);
|
||||
ngEventDirectives[directiveName] = ['$parse', function($parse) {
|
||||
@@ -164,6 +164,38 @@ forEach(
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ng.directive:ngKeydown
|
||||
*
|
||||
* @description
|
||||
* Specify custom behavior on keydown event.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
|
||||
* keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
|
||||
*
|
||||
* @example
|
||||
* See {@link ng.directive:ngClick ngClick}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ng.directive:ngKeyup
|
||||
*
|
||||
* @description
|
||||
* Specify custom behavior on keyup event.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
|
||||
* keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
|
||||
*
|
||||
* @example
|
||||
* See {@link ng.directive:ngClick ngClick}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ng.directive:ngSubmit
|
||||
|
||||
@@ -20,8 +20,11 @@
|
||||
* On child elments add:
|
||||
*
|
||||
* * `ngSwitchWhen`: the case statement to match against. If match then this
|
||||
* case will be displayed.
|
||||
* * `ngSwitchDefault`: the default case when no other casses match.
|
||||
* case will be displayed. If the same match appears multiple times, all the
|
||||
* elements will be displayed.
|
||||
* * `ngSwitchDefault`: the default case when no other case match. If there
|
||||
* are multiple default cases, all of them will be displayed when no other
|
||||
* case match.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
@@ -63,27 +66,34 @@ var NG_SWITCH = 'ng-switch';
|
||||
var ngSwitchDirective = valueFn({
|
||||
restrict: 'EA',
|
||||
require: 'ngSwitch',
|
||||
controller: function ngSwitchController() {
|
||||
// asks for $scope to fool the BC controller module
|
||||
controller: ['$scope', function ngSwitchController() {
|
||||
this.cases = {};
|
||||
},
|
||||
}],
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
var watchExpr = attr.ngSwitch || attr.on,
|
||||
selectedTransclude,
|
||||
selectedElement,
|
||||
selectedScope;
|
||||
selectedTranscludes,
|
||||
selectedElements,
|
||||
selectedScopes = [];
|
||||
|
||||
scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
|
||||
if (selectedElement) {
|
||||
selectedScope.$destroy();
|
||||
selectedElement.remove();
|
||||
selectedElement = selectedScope = null;
|
||||
for (var i= 0, ii=selectedScopes.length; i<ii; i++) {
|
||||
selectedScopes[i].$destroy();
|
||||
selectedElements[i].remove();
|
||||
}
|
||||
if ((selectedTransclude = ctrl.cases['!' + value] || ctrl.cases['?'])) {
|
||||
|
||||
selectedElements = [];
|
||||
selectedScopes = [];
|
||||
|
||||
if ((selectedTranscludes = ctrl.cases['!' + value] || ctrl.cases['?'])) {
|
||||
scope.$eval(attr.change);
|
||||
selectedScope = scope.$new();
|
||||
selectedTransclude(selectedScope, function(caseElement) {
|
||||
selectedElement = caseElement;
|
||||
element.append(caseElement);
|
||||
forEach(selectedTranscludes, function(selectedTransclude) {
|
||||
var selectedScope = scope.$new();
|
||||
selectedScopes.push(selectedScope);
|
||||
selectedTransclude(selectedScope, function(caseElement) {
|
||||
selectedElements.push(caseElement);
|
||||
element.append(caseElement);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -96,7 +106,8 @@ var ngSwitchWhenDirective = ngDirective({
|
||||
require: '^ngSwitch',
|
||||
compile: function(element, attrs, transclude) {
|
||||
return function(scope, element, attr, ctrl) {
|
||||
ctrl.cases['!' + attrs.ngSwitchWhen] = transclude;
|
||||
ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
|
||||
ctrl.cases['!' + attrs.ngSwitchWhen].push(transclude);
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -107,7 +118,8 @@ var ngSwitchDefaultDirective = ngDirective({
|
||||
require: '^ngSwitch',
|
||||
compile: function(element, attrs, transclude) {
|
||||
return function(scope, element, attr, ctrl) {
|
||||
ctrl.cases['?'] = transclude;
|
||||
ctrl.cases['?'] = (ctrl.cases['?'] || []);
|
||||
ctrl.cases['?'].push(transclude);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -147,7 +147,7 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
|
||||
if (current.controller) {
|
||||
locals.$scope = lastScope;
|
||||
controller = $controller(current.controller, locals);
|
||||
element.contents().data('$ngControllerController', controller);
|
||||
element.children().data('$ngControllerController', controller);
|
||||
}
|
||||
|
||||
link(lastScope);
|
||||
|
||||
+63
-17
@@ -32,6 +32,22 @@
|
||||
* called for each element of `array`. The final result is an array of those elements that
|
||||
* the predicate returned true for.
|
||||
*
|
||||
* @param {function(expected, actual)|true|undefined} comparator Comparator which is used in
|
||||
* determining if the expected value (from the filter expression) and actual value (from
|
||||
* the object in the array) should be considered a match.
|
||||
*
|
||||
* Can be one of:
|
||||
*
|
||||
* - `function(expected, actual)`:
|
||||
* The function will be given the object value and the predicate value to compare and
|
||||
* should return true if the item should be included in filtered result.
|
||||
*
|
||||
* - `true`: A shorthand for `function(expected, actual) { return angular.equals(expected, actual)}`.
|
||||
* this is essentially strict comparison of expected and actual.
|
||||
*
|
||||
* - `false|undefined`: A short hand for a function which will look for a substring match in case
|
||||
* insensitive way.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
@@ -39,7 +55,8 @@
|
||||
{name:'Mary', phone:'800-BIG-MARY'},
|
||||
{name:'Mike', phone:'555-4321'},
|
||||
{name:'Adam', phone:'555-5678'},
|
||||
{name:'Julie', phone:'555-8765'}]"></div>
|
||||
{name:'Julie', phone:'555-8765'},
|
||||
{name:'Juliette', phone:'555-5678'}]"></div>
|
||||
|
||||
Search: <input ng-model="searchText">
|
||||
<table id="searchTextResults">
|
||||
@@ -53,9 +70,10 @@
|
||||
Any: <input ng-model="search.$"> <br>
|
||||
Name only <input ng-model="search.name"><br>
|
||||
Phone only <input ng-model="search.phone"å><br>
|
||||
Equality <input type="checkbox" ng-model="strict"><br>
|
||||
<table id="searchObjResults">
|
||||
<tr><th>Name</th><th>Phone</th><tr>
|
||||
<tr ng-repeat="friend in friends | filter:search">
|
||||
<tr ng-repeat="friend in friends | filter:search:strict">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
<tr>
|
||||
@@ -75,14 +93,20 @@
|
||||
it('should search in specific fields when filtering with a predicate object', function() {
|
||||
input('search.$').enter('i');
|
||||
expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
|
||||
toEqual(['Mary', 'Mike', 'Julie']);
|
||||
toEqual(['Mary', 'Mike', 'Julie', 'Juliette']);
|
||||
});
|
||||
it('should use a equal comparison when comparator is true', function() {
|
||||
input('search.name').enter('Julie');
|
||||
input('strict').check();
|
||||
expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
|
||||
toEqual(['Julie']);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
function filterFilter() {
|
||||
return function(array, expression) {
|
||||
if (!(array instanceof Array)) return array;
|
||||
return function(array, expression, comperator) {
|
||||
if (!isArray(array)) return array;
|
||||
var predicates = [];
|
||||
predicates.check = function(value) {
|
||||
for (var j = 0; j < predicates.length; j++) {
|
||||
@@ -92,20 +116,43 @@ function filterFilter() {
|
||||
}
|
||||
return true;
|
||||
};
|
||||
switch(typeof comperator) {
|
||||
case "function":
|
||||
break;
|
||||
case "boolean":
|
||||
if(comperator == true) {
|
||||
comperator = function(obj, text) {
|
||||
return angular.equals(obj, text);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
comperator = function(obj, text) {
|
||||
text = (''+text).toLowerCase();
|
||||
return (''+obj).toLowerCase().indexOf(text) > -1
|
||||
};
|
||||
}
|
||||
var search = function(obj, text){
|
||||
if (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":
|
||||
return ('' + obj).toLowerCase().indexOf(text) > -1;
|
||||
return comperator(obj, text);
|
||||
case "object":
|
||||
for ( var objKey in obj) {
|
||||
if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
|
||||
return true;
|
||||
}
|
||||
switch (typeof text) {
|
||||
case "object":
|
||||
return comperator(obj, text);
|
||||
break;
|
||||
default:
|
||||
for ( var objKey in obj) {
|
||||
if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
case "array":
|
||||
@@ -128,19 +175,18 @@ function filterFilter() {
|
||||
for (var key in expression) {
|
||||
if (key == '$') {
|
||||
(function() {
|
||||
var text = (''+expression[key]).toLowerCase();
|
||||
if (!text) return;
|
||||
if (!expression[key]) return;
|
||||
var path = key
|
||||
predicates.push(function(value) {
|
||||
return search(value, text);
|
||||
return search(value, expression[path]);
|
||||
});
|
||||
})();
|
||||
} else {
|
||||
(function() {
|
||||
if (!expression[key]) return;
|
||||
var path = key;
|
||||
var text = (''+expression[key]).toLowerCase();
|
||||
if (!text) return;
|
||||
predicates.push(function(value) {
|
||||
return search(getter(value, path), text);
|
||||
return search(getter(value,path), expression[path]);
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
fraction += '0';
|
||||
}
|
||||
|
||||
if (fractionSize) formatedText += decimalSep + fraction.substr(0, fractionSize);
|
||||
if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
|
||||
}
|
||||
|
||||
parts.push(isNegative ? pattern.negPre : pattern.posPre);
|
||||
@@ -211,8 +211,12 @@ function dateStrGetter(name, shortForm) {
|
||||
}
|
||||
|
||||
function timeZoneGetter(date) {
|
||||
var offset = date.getTimezoneOffset();
|
||||
return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2);
|
||||
var zone = -1 * date.getTimezoneOffset();
|
||||
var paddedZone = (zone >= 0) ? "+" : "";
|
||||
|
||||
paddedZone += padNumber(zone / 60, 2) + padNumber(Math.abs(zone % 60), 2);
|
||||
|
||||
return paddedZone;
|
||||
}
|
||||
|
||||
function ampmGetter(date, formats) {
|
||||
@@ -237,6 +241,9 @@ var DATE_FORMATS = {
|
||||
m: dateGetter('Minutes', 1),
|
||||
ss: dateGetter('Seconds', 2),
|
||||
s: dateGetter('Seconds', 1),
|
||||
// while ISO 8601 requires fractions to be prefixed with `.` or `,`
|
||||
// we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
|
||||
sss: dateGetter('Milliseconds', 3),
|
||||
EEEE: dateStrGetter('Day'),
|
||||
EEE: dateStrGetter('Day', true),
|
||||
a: ampmGetter,
|
||||
@@ -275,6 +282,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
|
||||
* * `'m'`: Minute in hour (0-59)
|
||||
* * `'ss'`: Second in minute, padded (00-59)
|
||||
* * `'s'`: Second in minute (0-59)
|
||||
* * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
|
||||
* * `'a'`: am/pm marker
|
||||
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-1200)
|
||||
*
|
||||
@@ -298,7 +306,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
|
||||
*
|
||||
* @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
|
||||
* number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and it's
|
||||
* shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ).
|
||||
* shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
|
||||
* specified in the string input, the time is considered to be in the local timezone.
|
||||
* @param {string=} format Formatting rules (see Description). If not specified,
|
||||
* `mediumDate` is used.
|
||||
* @returns {string} Formatted string or the input if input is not recognized as date/millis.
|
||||
@@ -318,7 +327,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
|
||||
expect(binding("1288323623006 | date:'medium'")).
|
||||
toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
|
||||
expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
|
||||
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
|
||||
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
|
||||
expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
|
||||
toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
|
||||
});
|
||||
@@ -330,18 +339,22 @@ function dateFilter($locale) {
|
||||
|
||||
|
||||
var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
|
||||
function jsonStringToDate(string){
|
||||
// 1 2 3 4 5 6 7 8 9 10 11
|
||||
function jsonStringToDate(string) {
|
||||
var match;
|
||||
if (match = string.match(R_ISO8601_STR)) {
|
||||
var date = new Date(0),
|
||||
tzHour = 0,
|
||||
tzMin = 0;
|
||||
tzMin = 0,
|
||||
dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
|
||||
timeSetter = match[8] ? date.setUTCHours : date.setHours;
|
||||
|
||||
if (match[9]) {
|
||||
tzHour = int(match[9] + match[10]);
|
||||
tzMin = int(match[9] + match[11]);
|
||||
}
|
||||
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
|
||||
date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
|
||||
dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3]));
|
||||
timeSetter.call(date, int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
|
||||
return date;
|
||||
}
|
||||
return string;
|
||||
|
||||
+50
-33
@@ -6,20 +6,20 @@
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Creates a new array containing only a specified number of elements in an array. The elements
|
||||
* are taken from either the beginning or the end of the source array, as specified by the
|
||||
* value and sign (positive or negative) of `limit`.
|
||||
* Creates a new array or string containing only a specified number of elements. The elements
|
||||
* are taken from either the beginning or the end of the source array or string, as specified by
|
||||
* the value and sign (positive or negative) of `limit`.
|
||||
*
|
||||
* Note: This function is used to augment the `Array` type in Angular expressions. See
|
||||
* {@link ng.$filter} for more information about Angular arrays.
|
||||
*
|
||||
* @param {Array} array Source array to be limited.
|
||||
* @param {string|Number} limit The length of the returned array. If the `limit` number is
|
||||
* positive, `limit` number of items from the beginning of the source array are copied.
|
||||
* If the number is negative, `limit` number of items from the end of the source array are
|
||||
* copied. The `limit` will be trimmed if it exceeds `array.length`
|
||||
* @returns {Array} A new sub-array of length `limit` or less if input array had less than `limit`
|
||||
* elements.
|
||||
* @param {Array|string} input Source array or string to be limited.
|
||||
* @param {string|number} limit The length of the returned array or string. If the `limit` number
|
||||
* is positive, `limit` number of items from the beginning of the source array/string are copied.
|
||||
* If the number is negative, `limit` number of items from the end of the source array/string
|
||||
* are copied. The `limit` will be trimmed if it exceeds `array.length`
|
||||
* @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
|
||||
* had less than `limit` elements.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
@@ -27,59 +27,76 @@
|
||||
<script>
|
||||
function Ctrl($scope) {
|
||||
$scope.numbers = [1,2,3,4,5,6,7,8,9];
|
||||
$scope.limit = 3;
|
||||
$scope.letters = "abcdefghi";
|
||||
$scope.numLimit = 3;
|
||||
$scope.letterLimit = 3;
|
||||
}
|
||||
</script>
|
||||
<div ng-controller="Ctrl">
|
||||
Limit {{numbers}} to: <input type="integer" ng-model="limit">
|
||||
<p>Output: {{ numbers | limitTo:limit }}</p>
|
||||
Limit {{numbers}} to: <input type="integer" ng-model="numLimit">
|
||||
<p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
|
||||
Limit {{letters}} to: <input type="integer" ng-model="letterLimit">
|
||||
<p>Output letters: {{ letters | limitTo:letterLimit }}</p>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should limit the numer array to first three items', function() {
|
||||
expect(element('.doc-example-live input[ng-model=limit]').val()).toBe('3');
|
||||
expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3]');
|
||||
it('should limit the number array to first three items', function() {
|
||||
expect(element('.doc-example-live input[ng-model=numLimit]').val()).toBe('3');
|
||||
expect(element('.doc-example-live input[ng-model=letterLimit]').val()).toBe('3');
|
||||
expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3]');
|
||||
expect(binding('letters | limitTo:letterLimit')).toEqual('abc');
|
||||
});
|
||||
|
||||
it('should update the output when -3 is entered', function() {
|
||||
input('limit').enter(-3);
|
||||
expect(binding('numbers | limitTo:limit')).toEqual('[7,8,9]');
|
||||
input('numLimit').enter(-3);
|
||||
input('letterLimit').enter(-3);
|
||||
expect(binding('numbers | limitTo:numLimit')).toEqual('[7,8,9]');
|
||||
expect(binding('letters | limitTo:letterLimit')).toEqual('ghi');
|
||||
});
|
||||
|
||||
it('should not exceed the maximum size of input array', function() {
|
||||
input('limit').enter(100);
|
||||
expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3,4,5,6,7,8,9]');
|
||||
input('numLimit').enter(100);
|
||||
input('letterLimit').enter(100);
|
||||
expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3,4,5,6,7,8,9]');
|
||||
expect(binding('letters | limitTo:letterLimit')).toEqual('abcdefghi');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
function limitToFilter(){
|
||||
return function(array, limit) {
|
||||
if (!(array instanceof Array)) return array;
|
||||
return function(input, limit) {
|
||||
if (!isArray(input) && !isString(input)) return input;
|
||||
|
||||
limit = int(limit);
|
||||
|
||||
if (isString(input)) {
|
||||
//NaN check on limit
|
||||
if (limit) {
|
||||
return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
var out = [],
|
||||
i, n;
|
||||
|
||||
// check that array is iterable
|
||||
if (!array || !(array instanceof Array))
|
||||
return out;
|
||||
|
||||
// if abs(limit) exceeds maximum length, trim it
|
||||
if (limit > array.length)
|
||||
limit = array.length;
|
||||
else if (limit < -array.length)
|
||||
limit = -array.length;
|
||||
if (limit > input.length)
|
||||
limit = input.length;
|
||||
else if (limit < -input.length)
|
||||
limit = -input.length;
|
||||
|
||||
if (limit > 0) {
|
||||
i = 0;
|
||||
n = limit;
|
||||
} else {
|
||||
i = array.length + limit;
|
||||
n = array.length;
|
||||
i = input.length + limit;
|
||||
n = input.length;
|
||||
}
|
||||
|
||||
for (; i<n; i++) {
|
||||
out.push(array[i]);
|
||||
out.push(input[i]);
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
orderByFilter.$inject = ['$parse'];
|
||||
function orderByFilter($parse){
|
||||
return function(array, sortPredicate, reverseOrder) {
|
||||
if (!(array instanceof Array)) return array;
|
||||
if (!isArray(array)) return array;
|
||||
if (!sortPredicate) return array;
|
||||
sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
|
||||
sortPredicate = map(sortPredicate, function(predicate){
|
||||
|
||||
+82
-20
@@ -29,6 +29,43 @@ function parseHeaders(headers) {
|
||||
}
|
||||
|
||||
|
||||
var IS_SAME_DOMAIN_URL_MATCH = /^(([^:]+):)?\/\/(\w+:{0,1}\w*@)?([\w\.-]*)?(:([0-9]+))?(.*)$/;
|
||||
|
||||
|
||||
/**
|
||||
* Parse a request and location URL and determine whether this is a same-domain request.
|
||||
*
|
||||
* @param {string} requestUrl The url of the request.
|
||||
* @param {string} locationUrl The current browser location url.
|
||||
* @returns {boolean} Whether the request is for the same domain.
|
||||
*/
|
||||
function isSameDomain(requestUrl, locationUrl) {
|
||||
var match = IS_SAME_DOMAIN_URL_MATCH.exec(requestUrl);
|
||||
// if requestUrl is relative, the regex does not match.
|
||||
if (match == null) return true;
|
||||
|
||||
var domain1 = {
|
||||
protocol: match[2],
|
||||
host: match[4],
|
||||
port: int(match[6]) || DEFAULT_PORTS[match[2]] || null,
|
||||
// IE8 sets unmatched groups to '' instead of undefined.
|
||||
relativeProtocol: match[2] === undefined || match[2] === ''
|
||||
};
|
||||
|
||||
match = URL_MATCH.exec(locationUrl);
|
||||
var domain2 = {
|
||||
protocol: match[1],
|
||||
host: match[3],
|
||||
port: int(match[5]) || DEFAULT_PORTS[match[1]] || null
|
||||
};
|
||||
|
||||
return (domain1.protocol == domain2.protocol || domain1.relativeProtocol) &&
|
||||
domain1.host == domain2.host &&
|
||||
(domain1.port == domain2.port || (domain1.relativeProtocol &&
|
||||
domain2.port == DEFAULT_PORTS[domain2.protocol]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a function that provides access to parsed headers.
|
||||
*
|
||||
@@ -88,7 +125,7 @@ function $HttpProvider() {
|
||||
JSON_END = /[\}\]]\s*$/,
|
||||
PROTECTION_PREFIX = /^\)\]\}',?\n/;
|
||||
|
||||
var $config = this.defaults = {
|
||||
var defaults = this.defaults = {
|
||||
// transform incoming response data
|
||||
transformResponse: [function(data) {
|
||||
if (isString(data)) {
|
||||
@@ -108,12 +145,14 @@ function $HttpProvider() {
|
||||
// default headers
|
||||
headers: {
|
||||
common: {
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
'Accept': 'application/json, text/plain, */*'
|
||||
},
|
||||
post: {'Content-Type': 'application/json;charset=utf-8'},
|
||||
put: {'Content-Type': 'application/json;charset=utf-8'}
|
||||
}
|
||||
},
|
||||
|
||||
xsrfCookieName: 'XSRF-TOKEN',
|
||||
xsrfHeaderName: 'X-XSRF-TOKEN'
|
||||
};
|
||||
|
||||
var providerResponseInterceptors = this.responseInterceptors = [];
|
||||
@@ -215,7 +254,6 @@ function $HttpProvider() {
|
||||
*
|
||||
* - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
|
||||
* - `Accept: application/json, text/plain, * / *`
|
||||
* - `X-Requested-With: XMLHttpRequest`
|
||||
* - `$httpProvider.defaults.headers.post`: (header defaults for HTTP POST requests)
|
||||
* - `Content-Type: application/json`
|
||||
* - `$httpProvider.defaults.headers.put` (header defaults for HTTP PUT requests)
|
||||
@@ -348,9 +386,10 @@ function $HttpProvider() {
|
||||
* {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which
|
||||
* an unauthorized site can gain your user's private data. Angular provides following mechanism
|
||||
* to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
|
||||
* called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that
|
||||
* runs on your domain could read the cookie, your server can be assured that the XHR came from
|
||||
* JavaScript running on your domain.
|
||||
* (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only
|
||||
* JavaScript that runs on your domain could read the cookie, your server can be assured that
|
||||
* the XHR came from JavaScript running on your domain. The header will not be set for
|
||||
* cross-domain requests.
|
||||
*
|
||||
* To take advantage of this, your server needs to set a token in a JavaScript readable session
|
||||
* cookie called `XSRF-TOKEN` on first HTTP GET request. On subsequent non-GET requests the
|
||||
@@ -360,6 +399,9 @@ function $HttpProvider() {
|
||||
* up its own tokens). We recommend that the token is a digest of your site's authentication
|
||||
* cookie with {@link http://en.wikipedia.org/wiki/Rainbow_table salt for added security}.
|
||||
*
|
||||
* The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
|
||||
* properties of either $httpProvider.defaults, or the per-request config object.
|
||||
*
|
||||
*
|
||||
* @param {object} config Object describing the request to be made and how it should be
|
||||
* processed. The object has following properties:
|
||||
@@ -370,6 +412,8 @@ function $HttpProvider() {
|
||||
* `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified.
|
||||
* - **data** – `{string|Object}` – Data to be sent as the request message data.
|
||||
* - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server.
|
||||
* - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
|
||||
* - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
|
||||
* - **transformRequest** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
||||
* transform function or an array of such functions. The transform function takes the http
|
||||
* request body and headers and returns its transformed (typically serialized) version.
|
||||
@@ -384,6 +428,8 @@ function $HttpProvider() {
|
||||
* - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the
|
||||
* XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
|
||||
* requests with credentials} for more information.
|
||||
* - **responseType** - `{string}` - see {@link
|
||||
* https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
|
||||
*
|
||||
* @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
|
||||
* standard `then` method and two http specific methods: `success` and `error`. The `then`
|
||||
@@ -476,10 +522,17 @@ function $HttpProvider() {
|
||||
function $http(config) {
|
||||
config.method = uppercase(config.method);
|
||||
|
||||
var reqTransformFn = config.transformRequest || $config.transformRequest,
|
||||
respTransformFn = config.transformResponse || $config.transformResponse,
|
||||
defHeaders = $config.headers,
|
||||
reqHeaders = extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},
|
||||
var xsrfHeader = {},
|
||||
xsrfCookieName = config.xsrfCookieName || defaults.xsrfCookieName,
|
||||
xsrfHeaderName = config.xsrfHeaderName || defaults.xsrfHeaderName,
|
||||
xsrfToken = isSameDomain(config.url, $browser.url()) ?
|
||||
$browser.cookies()[xsrfCookieName] : undefined;
|
||||
xsrfHeader[xsrfHeaderName] = xsrfToken;
|
||||
|
||||
var reqTransformFn = config.transformRequest || defaults.transformRequest,
|
||||
respTransformFn = config.transformResponse || defaults.transformResponse,
|
||||
defHeaders = defaults.headers,
|
||||
reqHeaders = extend(xsrfHeader,
|
||||
defHeaders.common, defHeaders[lowercase(config.method)], config.headers),
|
||||
reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn),
|
||||
promise;
|
||||
@@ -489,6 +542,10 @@ function $HttpProvider() {
|
||||
delete reqHeaders['Content-Type'];
|
||||
}
|
||||
|
||||
if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
|
||||
config.withCredentials = defaults.withCredentials;
|
||||
}
|
||||
|
||||
// send request
|
||||
promise = sendReq(config, reqData, reqHeaders);
|
||||
|
||||
@@ -620,11 +677,11 @@ function $HttpProvider() {
|
||||
*
|
||||
* @description
|
||||
* Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
|
||||
* default headers as well as request and response transformations.
|
||||
* default headers, withCredentials as well as request and response transformations.
|
||||
*
|
||||
* See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
|
||||
*/
|
||||
$http.defaults = $config;
|
||||
$http.defaults = defaults;
|
||||
|
||||
|
||||
return $http;
|
||||
@@ -659,7 +716,7 @@ function $HttpProvider() {
|
||||
* Makes the request
|
||||
*
|
||||
* !!! ACCESSES CLOSURE VARS:
|
||||
* $httpBackend, $config, $log, $rootScope, defaultCache, $http.pendingRequests
|
||||
* $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
|
||||
*/
|
||||
function sendReq(config, reqData, reqHeaders) {
|
||||
var deferred = $q.defer(),
|
||||
@@ -700,7 +757,7 @@ function $HttpProvider() {
|
||||
// if we won't have the response in cache, send the request to the backend
|
||||
if (!cachedResp) {
|
||||
$httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
|
||||
config.withCredentials);
|
||||
config.withCredentials, config.responseType);
|
||||
}
|
||||
|
||||
return promise;
|
||||
@@ -755,10 +812,15 @@ function $HttpProvider() {
|
||||
var parts = [];
|
||||
forEachSorted(params, function(value, key) {
|
||||
if (value == null || value == undefined) return;
|
||||
if (isObject(value)) {
|
||||
value = toJson(value);
|
||||
}
|
||||
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
|
||||
if (!isArray(value)) value = [value];
|
||||
|
||||
forEach(value, function(v) {
|
||||
if (isObject(v)) {
|
||||
v = toJson(v);
|
||||
}
|
||||
parts.push(encodeUriQuery(key) + '=' +
|
||||
encodeUriQuery(v));
|
||||
});
|
||||
});
|
||||
return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
|
||||
}
|
||||
|
||||
+29
-3
@@ -32,7 +32,7 @@ function $HttpBackendProvider() {
|
||||
|
||||
function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) {
|
||||
// TODO(vojta): fix the signature
|
||||
return function(method, url, post, callback, headers, timeout, withCredentials) {
|
||||
return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
|
||||
$browser.$$incOutstandingRequestCount();
|
||||
url = url || $browser.url();
|
||||
|
||||
@@ -65,8 +65,30 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
|
||||
// always async
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
completeRequest(
|
||||
callback, status || xhr.status, xhr.responseText, xhr.getAllResponseHeaders());
|
||||
var responseHeaders = xhr.getAllResponseHeaders();
|
||||
|
||||
// TODO(vojta): remove once Firefox 21 gets released.
|
||||
// begin: workaround to overcome Firefox CORS http response headers bug
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=608735
|
||||
// Firefox already patched in nightly. Should land in Firefox 21.
|
||||
|
||||
// CORS "simple response headers" http://www.w3.org/TR/cors/
|
||||
var value,
|
||||
simpleHeaders = ["Cache-Control", "Content-Language", "Content-Type",
|
||||
"Expires", "Last-Modified", "Pragma"];
|
||||
if (!responseHeaders) {
|
||||
responseHeaders = "";
|
||||
forEach(simpleHeaders, function (header) {
|
||||
var value = xhr.getResponseHeader(header);
|
||||
if (value) {
|
||||
responseHeaders += header + ": " + value + "\n";
|
||||
}
|
||||
});
|
||||
}
|
||||
// end of the workaround.
|
||||
|
||||
completeRequest(callback, status || xhr.status, xhr.response || xhr.responseText,
|
||||
responseHeaders);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,6 +96,10 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
|
||||
xhr.withCredentials = true;
|
||||
}
|
||||
|
||||
if (responseType) {
|
||||
xhr.responseType = responseType;
|
||||
}
|
||||
|
||||
xhr.send(post || '');
|
||||
|
||||
if (timeout > 0) {
|
||||
|
||||
+16
-10
@@ -52,7 +52,7 @@ function $InterpolateProvider() {
|
||||
};
|
||||
|
||||
|
||||
this.$get = ['$parse', function($parse) {
|
||||
this.$get = ['$parse', '$exceptionHandler', function($parse, $exceptionHandler) {
|
||||
var startSymbolLength = startSymbol.length,
|
||||
endSymbolLength = endSymbol.length;
|
||||
|
||||
@@ -124,18 +124,24 @@ function $InterpolateProvider() {
|
||||
if (!mustHaveExpression || hasInterpolation) {
|
||||
concat.length = length;
|
||||
fn = function(context) {
|
||||
for(var i = 0, ii = length, part; i<ii; i++) {
|
||||
if (typeof (part = parts[i]) == 'function') {
|
||||
part = part(context);
|
||||
if (part == null || part == undefined) {
|
||||
part = '';
|
||||
} else if (typeof part != 'string') {
|
||||
part = toJson(part);
|
||||
try {
|
||||
for(var i = 0, ii = length, part; i<ii; i++) {
|
||||
if (typeof (part = parts[i]) == 'function') {
|
||||
part = part(context);
|
||||
if (part == null || part == undefined) {
|
||||
part = '';
|
||||
} else if (typeof part != 'string') {
|
||||
part = toJson(part);
|
||||
}
|
||||
}
|
||||
concat[i] = part;
|
||||
}
|
||||
concat[i] = part;
|
||||
return concat.join('');
|
||||
}
|
||||
catch(err) {
|
||||
var newErr = new Error('Error while interpolating: ' + text + '\n' + err.toString());
|
||||
$exceptionHandler(newErr);
|
||||
}
|
||||
return concat.join('');
|
||||
};
|
||||
fn.exp = text;
|
||||
fn.parts = parts;
|
||||
|
||||
+45
-1
@@ -33,7 +33,33 @@
|
||||
</example>
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc object
|
||||
* @name ng.$logProvider
|
||||
* @description
|
||||
* Use the `$logProvider` to configure how the application logs messages
|
||||
*/
|
||||
function $LogProvider(){
|
||||
var debug = true,
|
||||
self = this;
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @name ng.$logProvider#debugEnabled
|
||||
* @methodOf ng.$logProvider
|
||||
* @description
|
||||
* @param {string=} flag enable or disable debug level messages
|
||||
* @returns {*} current value if used as getter or itself (chaining) if used as setter
|
||||
*/
|
||||
this.debugEnabled = function(flag) {
|
||||
if (isDefined(flag)) {
|
||||
debug = flag;
|
||||
return this;
|
||||
} else {
|
||||
return debug;
|
||||
}
|
||||
};
|
||||
|
||||
this.$get = ['$window', function($window){
|
||||
return {
|
||||
/**
|
||||
@@ -74,7 +100,25 @@ function $LogProvider(){
|
||||
* @description
|
||||
* Write an error message
|
||||
*/
|
||||
error: consoleLog('error')
|
||||
error: consoleLog('error'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ng.$log#debug
|
||||
* @methodOf ng.$log
|
||||
*
|
||||
* @description
|
||||
* Write a debug message
|
||||
*/
|
||||
debug: (function () {
|
||||
var fn = consoleLog('debug');
|
||||
|
||||
return function() {
|
||||
if (debug) {
|
||||
fn.apply(self, arguments);
|
||||
}
|
||||
}
|
||||
}())
|
||||
};
|
||||
|
||||
function formatError(arg) {
|
||||
|
||||
+57
-19
@@ -20,6 +20,8 @@ var OPERATORS = {
|
||||
'%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
|
||||
'^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
|
||||
'=':noop,
|
||||
'===':function(self, locals, a, b){return a(self, locals)===b(self, locals);},
|
||||
'!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);},
|
||||
'==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
|
||||
'!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
|
||||
'<':function(self, locals, a,b){return a(self, locals)<b(self, locals);},
|
||||
@@ -70,9 +72,14 @@ function lex(text, csp){
|
||||
continue;
|
||||
} else {
|
||||
var ch2 = ch + peek(),
|
||||
ch3 = ch2 + peek(2),
|
||||
fn = OPERATORS[ch],
|
||||
fn2 = OPERATORS[ch2];
|
||||
if (fn2) {
|
||||
fn2 = OPERATORS[ch2],
|
||||
fn3 = OPERATORS[ch3];
|
||||
if (fn3) {
|
||||
tokens.push({index:index, text:ch3, fn:fn3});
|
||||
index += 3;
|
||||
} else if (fn2) {
|
||||
tokens.push({index:index, text:ch2, fn:fn2});
|
||||
index += 2;
|
||||
} else if (fn) {
|
||||
@@ -94,8 +101,9 @@ function lex(text, csp){
|
||||
return chars.indexOf(lastCh) != -1;
|
||||
}
|
||||
|
||||
function peek() {
|
||||
return index + 1 < text.length ? text.charAt(index + 1) : false;
|
||||
function peek(i) {
|
||||
var num = i || 1;
|
||||
return index + num < text.length ? text.charAt(index + num) : false;
|
||||
}
|
||||
function isNumber(ch) {
|
||||
return '0' <= ch && ch <= '9';
|
||||
@@ -295,6 +303,8 @@ function parser(text, json, $filter, csp){
|
||||
if (tokens.length !== 0) {
|
||||
throwError("is an unexpected token", tokens[0]);
|
||||
}
|
||||
value.literal = !!value.literal;
|
||||
value.constant = !!value.constant;
|
||||
return value;
|
||||
|
||||
///////////////////////////////////
|
||||
@@ -342,15 +352,19 @@ function parser(text, json, $filter, csp){
|
||||
}
|
||||
|
||||
function unaryFn(fn, right) {
|
||||
return function(self, locals) {
|
||||
return extend(function(self, locals) {
|
||||
return fn(self, locals, right);
|
||||
};
|
||||
}, {
|
||||
constant:right.constant
|
||||
});
|
||||
}
|
||||
|
||||
function binaryFn(left, fn, right) {
|
||||
return function(self, locals) {
|
||||
return extend(function(self, locals) {
|
||||
return fn(self, locals, left, right);
|
||||
};
|
||||
}, {
|
||||
constant:left.constant && right.constant
|
||||
});
|
||||
}
|
||||
|
||||
function statements() {
|
||||
@@ -456,7 +470,7 @@ function parser(text, json, $filter, csp){
|
||||
function equality() {
|
||||
var left = relational();
|
||||
var token;
|
||||
if ((token = expect('==','!='))) {
|
||||
if ((token = expect('==','!=','===','!=='))) {
|
||||
left = binaryFn(left, token.fn, equality());
|
||||
}
|
||||
return left;
|
||||
@@ -518,6 +532,9 @@ function parser(text, json, $filter, csp){
|
||||
if (!primary) {
|
||||
throwError("not a primary expression", token);
|
||||
}
|
||||
if (token.json) {
|
||||
primary.constant = primary.literal = true;
|
||||
}
|
||||
}
|
||||
|
||||
var next, context;
|
||||
@@ -606,23 +623,32 @@ function parser(text, json, $filter, csp){
|
||||
// This is used with json array declaration
|
||||
function arrayDeclaration () {
|
||||
var elementFns = [];
|
||||
var allConstant = true;
|
||||
if (peekToken().text != ']') {
|
||||
do {
|
||||
elementFns.push(expression());
|
||||
var elementFn = expression();
|
||||
elementFns.push(elementFn);
|
||||
if (!elementFn.constant) {
|
||||
allConstant = false;
|
||||
}
|
||||
} while (expect(','));
|
||||
}
|
||||
consume(']');
|
||||
return function(self, locals){
|
||||
return extend(function(self, locals){
|
||||
var array = [];
|
||||
for ( var i = 0; i < elementFns.length; i++) {
|
||||
array.push(elementFns[i](self, locals));
|
||||
}
|
||||
return array;
|
||||
};
|
||||
}, {
|
||||
literal:true,
|
||||
constant:allConstant
|
||||
});
|
||||
}
|
||||
|
||||
function object () {
|
||||
var keyValues = [];
|
||||
var allConstant = true;
|
||||
if (peekToken().text != '}') {
|
||||
do {
|
||||
var token = expect(),
|
||||
@@ -630,10 +656,13 @@ function parser(text, json, $filter, csp){
|
||||
consume(":");
|
||||
var value = expression();
|
||||
keyValues.push({key:key, value:value});
|
||||
if (!value.constant) {
|
||||
allConstant = false;
|
||||
}
|
||||
} while (expect(','));
|
||||
}
|
||||
consume('}');
|
||||
return function(self, locals){
|
||||
return extend(function(self, locals){
|
||||
var object = {};
|
||||
for ( var i = 0; i < keyValues.length; i++) {
|
||||
var keyValue = keyValues[i];
|
||||
@@ -641,7 +670,10 @@ function parser(text, json, $filter, csp){
|
||||
object[keyValue.key] = value;
|
||||
}
|
||||
return object;
|
||||
};
|
||||
}, {
|
||||
literal:true,
|
||||
constant:allConstant
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -840,12 +872,18 @@ function getterFn(path, csp) {
|
||||
* @param {string} expression String expression to compile.
|
||||
* @returns {function(context, locals)} a function which represents the compiled expression:
|
||||
*
|
||||
* * `context`: an object against which any expressions embedded in the strings are evaluated
|
||||
* against (Topically a scope object).
|
||||
* * `locals`: local variables context object, useful for overriding values in `context`.
|
||||
* * `context` – `{object}` – an object against which any expressions embedded in the strings
|
||||
* are evaluated against (tipically a scope object).
|
||||
* * `locals` – `{object=}` – local variables context object, useful for overriding values in
|
||||
* `context`.
|
||||
*
|
||||
* The return function also has an `assign` property, if the expression is assignable, which
|
||||
* allows one to set values to expressions.
|
||||
* The returned function also has the following properties:
|
||||
* * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
|
||||
* literal.
|
||||
* * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
|
||||
* constant literals.
|
||||
* * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
|
||||
* set to a function to change its value on the given context.
|
||||
*
|
||||
*/
|
||||
function $ParseProvider() {
|
||||
|
||||
+9
-9
@@ -12,7 +12,7 @@
|
||||
* interface for interacting with an object that represents the result of an action that is
|
||||
* performed asynchronously, and may or may not be finished at any given point in time.
|
||||
*
|
||||
* From the perspective of dealing with error handling, deferred and promise apis are to
|
||||
* From the perspective of dealing with error handling, deferred and promise APIs are to
|
||||
* asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
|
||||
*
|
||||
* <pre>
|
||||
@@ -47,7 +47,7 @@
|
||||
*
|
||||
* At first it might not be obvious why this extra complexity is worth the trouble. The payoff
|
||||
* comes in the way of
|
||||
* [guarantees that promise and deferred apis make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
|
||||
* [guarantees that promise and deferred APIs make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
|
||||
*
|
||||
* Additionally the promise api allows for composition that is very hard to do with the
|
||||
* traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
|
||||
@@ -59,7 +59,7 @@
|
||||
*
|
||||
* A new instance of deferred is constructed by calling `$q.defer()`.
|
||||
*
|
||||
* The purpose of the deferred object is to expose the associated Promise instance as well as apis
|
||||
* The purpose of the deferred object is to expose the associated Promise instance as well as APIs
|
||||
* that can be used for signaling the successful or unsuccessful completion of the task.
|
||||
*
|
||||
* **Methods**
|
||||
@@ -102,7 +102,7 @@
|
||||
* return result + 1;
|
||||
* });
|
||||
*
|
||||
* // promiseB will be resolved immediately after promiseA is resolved and it's value will be
|
||||
* // promiseB will be resolved immediately after promiseA is resolved and its value will be
|
||||
* // the result of promiseA incremented by 1
|
||||
* </pre>
|
||||
*
|
||||
@@ -127,7 +127,7 @@
|
||||
* # Testing
|
||||
*
|
||||
* <pre>
|
||||
* it('should simulate promise', inject(function($q, $rootSCope) {
|
||||
* it('should simulate promise', inject(function($q, $rootScope) {
|
||||
* var deferred = $q.defer();
|
||||
* var promise = deferred.promise;
|
||||
* var resolvedValue;
|
||||
@@ -136,7 +136,7 @@
|
||||
* expect(resolvedValue).toBeUndefined();
|
||||
*
|
||||
* // Simulate resolving of promise
|
||||
* defered.resolve(123);
|
||||
* deferred.resolve(123);
|
||||
* // Note that the 'then' function does not get called synchronously.
|
||||
* // This is because we want the promise API to always be async, whether or not
|
||||
* // it got called synchronously or asynchronously.
|
||||
@@ -312,12 +312,12 @@ function qFactory(nextTick, exceptionHandler) {
|
||||
* @methodOf ng.$q
|
||||
* @description
|
||||
* Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
|
||||
* This is useful when you are dealing with on object that might or might not be a promise, or if
|
||||
* This is useful when you are dealing with an object that might or might not be a promise, or if
|
||||
* the promise comes from a source that can't be trusted.
|
||||
*
|
||||
* @param {*} value Value or a promise
|
||||
* @returns {Promise} Returns a single promise that will be resolved with an array of values,
|
||||
* each value coresponding to the promise at the same index in the `promises` array. If any of
|
||||
* each value corresponding to the promise at the same index in the `promises` array. If any of
|
||||
* the promises is resolved with a rejection, this resulting promise will be resolved with the
|
||||
* same rejection.
|
||||
*/
|
||||
@@ -379,7 +379,7 @@ function qFactory(nextTick, exceptionHandler) {
|
||||
*
|
||||
* @param {Array.<Promise>} promises An array of promises.
|
||||
* @returns {Promise} Returns a single promise that will be resolved with an array of values,
|
||||
* each value coresponding to the promise at the same index in the `promises` array. If any of
|
||||
* each value corresponding to the promise at the same index in the `promises` array. If any of
|
||||
* the promises is resolved with a rejection, this resulting promise will be resolved with the
|
||||
* same rejection.
|
||||
*/
|
||||
|
||||
+25
-16
@@ -137,6 +137,7 @@ function $RootScopeProvider(){
|
||||
this.$$destroyed = false;
|
||||
this.$$asyncQueue = [];
|
||||
this.$$listeners = {};
|
||||
this.$$isolateBindings = {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,7 +197,6 @@ function $RootScopeProvider(){
|
||||
child['this'] = child;
|
||||
child.$$listeners = {};
|
||||
child.$parent = this;
|
||||
child.$$asyncQueue = [];
|
||||
child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
|
||||
child.$$prevSibling = this.$$childTail;
|
||||
if (this.$$childHead) {
|
||||
@@ -300,6 +300,14 @@ function $RootScopeProvider(){
|
||||
watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
|
||||
}
|
||||
|
||||
if (typeof watchExp == 'string' && get.constant) {
|
||||
var originalFn = watcher.fn;
|
||||
watcher.fn = function(newVal, oldVal, scope) {
|
||||
originalFn.call(this, newVal, oldVal, scope);
|
||||
arrayRemove(array, watcher);
|
||||
};
|
||||
}
|
||||
|
||||
if (!array) {
|
||||
array = scope.$$watchers = [];
|
||||
}
|
||||
@@ -363,7 +371,7 @@ function $RootScopeProvider(){
|
||||
$digest: function() {
|
||||
var watch, value, last,
|
||||
watchers,
|
||||
asyncQueue,
|
||||
asyncQueue = this.$$asyncQueue,
|
||||
length,
|
||||
dirty, ttl = TTL,
|
||||
next, current, target = this,
|
||||
@@ -372,18 +380,19 @@ function $RootScopeProvider(){
|
||||
|
||||
beginPhase('$digest');
|
||||
|
||||
do {
|
||||
do { // "while dirty" loop
|
||||
dirty = false;
|
||||
current = target;
|
||||
do {
|
||||
asyncQueue = current.$$asyncQueue;
|
||||
while(asyncQueue.length) {
|
||||
try {
|
||||
current.$eval(asyncQueue.shift());
|
||||
} catch (e) {
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
|
||||
while(asyncQueue.length) {
|
||||
try {
|
||||
current.$eval(asyncQueue.shift());
|
||||
} catch (e) {
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
}
|
||||
|
||||
do { // "traverse the scopes" loop
|
||||
if ((watchers = current.$$watchers)) {
|
||||
// process our watches
|
||||
length = watchers.length;
|
||||
@@ -618,10 +627,6 @@ function $RootScopeProvider(){
|
||||
* Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
|
||||
* event life cycle.
|
||||
*
|
||||
* @param {string} name Event name to listen on.
|
||||
* @param {function(event)} listener Function to call when the event is emitted.
|
||||
* @returns {function()} Returns a deregistration function for this listener.
|
||||
*
|
||||
* The event listener function format is: `function(event, args...)`. The `event` object
|
||||
* passed into the listener has the following attributes:
|
||||
*
|
||||
@@ -632,6 +637,10 @@ function $RootScopeProvider(){
|
||||
* propagation (available only for events that were `$emit`-ed).
|
||||
* - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag to true.
|
||||
* - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
|
||||
*
|
||||
* @param {string} name Event name to listen on.
|
||||
* @param {function(event, args...)} listener Function to call when the event is emitted.
|
||||
* @returns {function()} Returns a deregistration function for this listener.
|
||||
*/
|
||||
$on: function(name, listener) {
|
||||
var namedListeners = this.$$listeners[name];
|
||||
@@ -809,7 +818,7 @@ function $RootScopeProvider(){
|
||||
|
||||
/**
|
||||
* function used as an initial value for watchers.
|
||||
* because it's uniqueue we can easily tell it apart from other values
|
||||
* because it's unique we can easily tell it apart from other values
|
||||
*/
|
||||
function initWatchVal() {}
|
||||
}];
|
||||
|
||||
+51
-14
@@ -23,9 +23,18 @@ function $RouteProvider(){
|
||||
* `$location.path` will be updated to add or drop the trailing slash to exactly match the
|
||||
* route definition.
|
||||
*
|
||||
* `path` can contain named groups starting with a colon (`:name`). All characters up to the
|
||||
* next slash are matched and stored in `$routeParams` under the given `name` when the route
|
||||
* matches.
|
||||
* * `path` can contain named groups starting with a colon (`:name`). All characters up
|
||||
* to the next slash are matched and stored in `$routeParams` under the given `name`
|
||||
* when the route matches.
|
||||
* * `path` can contain named groups starting with a star (`*name`). All characters are
|
||||
* eagerly stored in `$routeParams` under the given `name` when the route matches.
|
||||
*
|
||||
* For example, routes like `/color/:color/largecode/*largecode/edit` will match
|
||||
* `/color/brown/largecode/code/with/slashs/edit` and extract:
|
||||
*
|
||||
* * `color: brown`
|
||||
* * `largecode: code/with/slashs`.
|
||||
*
|
||||
*
|
||||
* @param {Object} route Mapping information to be assigned to `$route.current` on route
|
||||
* match.
|
||||
@@ -35,12 +44,24 @@ function $RouteProvider(){
|
||||
* - `controller` – `{(string|function()=}` – Controller fn that should be associated with newly
|
||||
* created scope or the name of a {@link angular.Module#controller registered controller}
|
||||
* if passed as a string.
|
||||
* - `template` – `{string=}` – html template as a string that should be used by
|
||||
* {@link ng.directive:ngView ngView} or
|
||||
* - `template` – `{string=|function()=}` – html template as a string or function that returns
|
||||
* an html template as a string which should be used by {@link ng.directive:ngView ngView} or
|
||||
* {@link ng.directive:ngInclude ngInclude} directives.
|
||||
* this property takes precedence over `templateUrl`.
|
||||
* - `templateUrl` – `{string=}` – path to an html template that should be used by
|
||||
* {@link ng.directive:ngView ngView}.
|
||||
* This property takes precedence over `templateUrl`.
|
||||
*
|
||||
* If `template` is a function, it will be called with the following parameters:
|
||||
*
|
||||
* - `{Array.<Object>}` - route parameters extracted from the current
|
||||
* `$location.path()` by applying the current route
|
||||
*
|
||||
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
|
||||
* template that should be used by {@link ng.directive:ngView ngView}.
|
||||
*
|
||||
* If `templateUrl` is a function, it will be called with the following parameters:
|
||||
*
|
||||
* - `{Array.<Object>}` - route parameters extracted from the current
|
||||
* `$location.path()` by applying the current route
|
||||
*
|
||||
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
|
||||
* be injected into the controller. If any of these dependencies are promises, they will be
|
||||
* resolved and converted to a value before the controller is instantiated and the
|
||||
@@ -329,12 +350,12 @@ function $RouteProvider(){
|
||||
// regex only once and then reuse it
|
||||
|
||||
// Escape regexp special characters.
|
||||
when = '^' + when.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") + '$';
|
||||
when = '^' + when.replace(/[-\/\\^$:*+?.()|[\]{}]/g, "\\$&") + '$';
|
||||
var regex = '',
|
||||
params = [],
|
||||
dst = {};
|
||||
|
||||
var re = /:(\w+)/g,
|
||||
var re = /\\([:*])(\w+)/g,
|
||||
paramMatch,
|
||||
lastMatchedIndex = 0;
|
||||
|
||||
@@ -342,8 +363,15 @@ function $RouteProvider(){
|
||||
// Find each :param in `when` and replace it with a capturing group.
|
||||
// Append all other sections of when unchanged.
|
||||
regex += when.slice(lastMatchedIndex, paramMatch.index);
|
||||
regex += '([^\\/]*)';
|
||||
params.push(paramMatch[1]);
|
||||
switch(paramMatch[1]) {
|
||||
case ':':
|
||||
regex += '([^\\/]*)';
|
||||
break;
|
||||
case '*':
|
||||
regex += '(.*)';
|
||||
break;
|
||||
}
|
||||
params.push(paramMatch[2]);
|
||||
lastMatchedIndex = re.lastIndex;
|
||||
}
|
||||
// Append trailing path part.
|
||||
@@ -395,9 +423,18 @@ function $RouteProvider(){
|
||||
values.push(isString(value) ? $injector.get(value) : $injector.invoke(value));
|
||||
});
|
||||
if (isDefined(template = next.template)) {
|
||||
if (isFunction(template)) {
|
||||
template = template(next.params);
|
||||
}
|
||||
} else if (isDefined(template = next.templateUrl)) {
|
||||
template = $http.get(template, {cache: $templateCache}).
|
||||
then(function(response) { return response.data; });
|
||||
if (isFunction(template)) {
|
||||
template = template(next.params);
|
||||
}
|
||||
if (isDefined(template)) {
|
||||
next.loadedTemplateUrl = template;
|
||||
template = $http.get(template, {cache: $templateCache}).
|
||||
then(function(response) { return response.data; });
|
||||
}
|
||||
}
|
||||
if (isDefined(template)) {
|
||||
keys.push('$template');
|
||||
|
||||
+7
-6
@@ -5,6 +5,7 @@
|
||||
*
|
||||
* @name ng.$sniffer
|
||||
* @requires $window
|
||||
* @requires $document
|
||||
*
|
||||
* @property {boolean} history Does the browser support html5 history api ?
|
||||
* @property {boolean} hashchange Does the browser support hashchange event ?
|
||||
@@ -13,9 +14,10 @@
|
||||
* This is very simple implementation of testing browser's features.
|
||||
*/
|
||||
function $SnifferProvider() {
|
||||
this.$get = ['$window', function($window) {
|
||||
this.$get = ['$window', '$document', function($window, $document) {
|
||||
var eventSupport = {},
|
||||
android = int((/android (\d+)/.exec(lowercase($window.navigator.userAgent)) || [])[1]);
|
||||
android = int((/android (\d+)/.exec(lowercase($window.navigator.userAgent)) || [])[1]),
|
||||
document = $document[0];
|
||||
|
||||
return {
|
||||
// Android has history.pushState, but it does not update location correctly
|
||||
@@ -25,7 +27,7 @@ function $SnifferProvider() {
|
||||
history: !!($window.history && $window.history.pushState && !(android < 4)),
|
||||
hashchange: 'onhashchange' in $window &&
|
||||
// IE8 compatible mode lies
|
||||
(!$window.document.documentMode || $window.document.documentMode > 7),
|
||||
(!document.documentMode || document.documentMode > 7),
|
||||
hasEvent: function(event) {
|
||||
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
|
||||
// it. In particular the event is not fired when backspace or delete key are pressed or
|
||||
@@ -33,14 +35,13 @@ function $SnifferProvider() {
|
||||
if (event == 'input' && msie == 9) return false;
|
||||
|
||||
if (isUndefined(eventSupport[event])) {
|
||||
var divElm = $window.document.createElement('div');
|
||||
var divElm = document.createElement('div');
|
||||
eventSupport[event] = 'on' + event in divElm;
|
||||
}
|
||||
|
||||
return eventSupport[event];
|
||||
},
|
||||
// TODO(i): currently there is no way to feature detect CSP without triggering alerts
|
||||
csp: false
|
||||
csp: document.securityPolicy ? document.securityPolicy.isActive : false
|
||||
};
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -19,6 +19,18 @@ angular.module('ngCookies', ['ng']).
|
||||
* this object, new cookies are created/deleted at the end of current $eval.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function ExampleController($cookies) {
|
||||
// Retrieving a cookie
|
||||
var favoriteCookie = $cookies.myFavorite;
|
||||
// Setting a cookie
|
||||
$cookies.myFavorite = 'oatmeal';
|
||||
}
|
||||
</script>
|
||||
</doc:source>
|
||||
</doc:example>
|
||||
*/
|
||||
factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) {
|
||||
var cookies = {},
|
||||
|
||||
Vendored
+67
-36
@@ -456,6 +456,7 @@ angular.mock.$LogProvider = function() {
|
||||
* newYearInBratislava.getDate() => 1;
|
||||
* newYearInBratislava.getHours() => 0;
|
||||
* newYearInBratislava.getMinutes() => 0;
|
||||
* newYearInBratislava.getSeconds() => 0;
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
@@ -512,6 +513,10 @@ angular.mock.$LogProvider = function() {
|
||||
return self.date.getSeconds();
|
||||
};
|
||||
|
||||
self.getMilliseconds = function() {
|
||||
return self.date.getMilliseconds();
|
||||
};
|
||||
|
||||
self.getTimezoneOffset = function() {
|
||||
return offset * 60;
|
||||
};
|
||||
@@ -562,7 +567,7 @@ angular.mock.$LogProvider = function() {
|
||||
}
|
||||
|
||||
//hide all methods not implemented in this mock that the Date prototype exposes
|
||||
var unimplementedMethods = ['getMilliseconds', 'getUTCDay',
|
||||
var unimplementedMethods = ['getUTCDay',
|
||||
'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
|
||||
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
|
||||
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
|
||||
@@ -1352,17 +1357,49 @@ function MockXhr() {
|
||||
* @description
|
||||
*
|
||||
* This service is just a simple decorator for {@link ng.$timeout $timeout} service
|
||||
* that adds a "flush" method.
|
||||
*/
|
||||
* that adds a "flush" and "verifyNoPendingTasks" methods.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngMock.$timeout#flush
|
||||
* @methodOf ngMock.$timeout
|
||||
* @description
|
||||
*
|
||||
* Flushes the queue of pending tasks.
|
||||
*/
|
||||
angular.mock.$TimeoutDecorator = function($delegate, $browser) {
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngMock.$timeout#flush
|
||||
* @methodOf ngMock.$timeout
|
||||
* @description
|
||||
*
|
||||
* Flushes the queue of pending tasks.
|
||||
*/
|
||||
$delegate.flush = function() {
|
||||
$browser.defer.flush();
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngMock.$timeout#verifyNoPendingTasks
|
||||
* @methodOf ngMock.$timeout
|
||||
* @description
|
||||
*
|
||||
* Verifies that there are no pending tasks that need to be flushed.
|
||||
*/
|
||||
$delegate.verifyNoPendingTasks = function() {
|
||||
if ($browser.deferredFns.length) {
|
||||
throw Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' +
|
||||
formatPendingTasksAsString($browser.deferredFns));
|
||||
}
|
||||
};
|
||||
|
||||
function formatPendingTasksAsString(tasks) {
|
||||
var result = [];
|
||||
angular.forEach(tasks, function(task) {
|
||||
result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}');
|
||||
});
|
||||
|
||||
return result.join(', ');
|
||||
}
|
||||
|
||||
return $delegate;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -1388,15 +1425,9 @@ angular.module('ngMock', ['ng']).provider({
|
||||
$httpBackend: angular.mock.$HttpBackendProvider,
|
||||
$rootElement: angular.mock.$RootElementProvider
|
||||
}).config(function($provide) {
|
||||
$provide.decorator('$timeout', function($delegate, $browser) {
|
||||
$delegate.flush = function() {
|
||||
$browser.defer.flush();
|
||||
};
|
||||
return $delegate;
|
||||
});
|
||||
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name ngMockE2E
|
||||
@@ -1610,14 +1641,20 @@ window.jstestdriver && (function(window) {
|
||||
})(window);
|
||||
|
||||
|
||||
window.jasmine && (function(window) {
|
||||
(window.jasmine || window.mocha) && (function(window) {
|
||||
|
||||
var currentSpec = null;
|
||||
|
||||
beforeEach(function() {
|
||||
currentSpec = this;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
var spec = getCurrentSpec();
|
||||
var injector = spec.$injector;
|
||||
var injector = currentSpec.$injector;
|
||||
|
||||
spec.$injector = null;
|
||||
spec.$modules = null;
|
||||
currentSpec.$injector = null;
|
||||
currentSpec.$modules = null;
|
||||
currentSpec = null;
|
||||
|
||||
if (injector) {
|
||||
injector.get('$rootElement').unbind();
|
||||
@@ -1639,13 +1676,8 @@ window.jasmine && (function(window) {
|
||||
angular.callbacks.counter = 0;
|
||||
});
|
||||
|
||||
function getCurrentSpec() {
|
||||
return jasmine.getEnv().currentSpec;
|
||||
}
|
||||
|
||||
function isSpecRunning() {
|
||||
var spec = getCurrentSpec();
|
||||
return spec && spec.queue.running;
|
||||
return currentSpec && currentSpec.queue.running;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1670,11 +1702,10 @@ window.jasmine && (function(window) {
|
||||
return isSpecRunning() ? workFn() : workFn;
|
||||
/////////////////////
|
||||
function workFn() {
|
||||
var spec = getCurrentSpec();
|
||||
if (spec.$injector) {
|
||||
if (currentSpec.$injector) {
|
||||
throw Error('Injector already created, can not register a module!');
|
||||
} else {
|
||||
var modules = spec.$modules || (spec.$modules = []);
|
||||
var modules = currentSpec.$modules || (currentSpec.$modules = []);
|
||||
angular.forEach(moduleFns, function(module) {
|
||||
modules.push(module);
|
||||
});
|
||||
@@ -1741,13 +1772,13 @@ window.jasmine && (function(window) {
|
||||
return isSpecRunning() ? workFn() : workFn;
|
||||
/////////////////////
|
||||
function workFn() {
|
||||
var spec = getCurrentSpec();
|
||||
var modules = spec.$modules || [];
|
||||
var modules = currentSpec.$modules || [];
|
||||
|
||||
modules.unshift('ngMock');
|
||||
modules.unshift('ng');
|
||||
var injector = spec.$injector;
|
||||
var injector = currentSpec.$injector;
|
||||
if (!injector) {
|
||||
injector = spec.$injector = angular.injector(modules);
|
||||
injector = currentSpec.$injector = angular.injector(modules);
|
||||
}
|
||||
for(var i = 0, ii = blockFns.length; i < ii; i++) {
|
||||
try {
|
||||
|
||||
+104
-42
@@ -19,12 +19,13 @@
|
||||
* the need to interact with the low level {@link ng.$http $http} service.
|
||||
*
|
||||
* @param {string} url A parameterized URL template with parameters prefixed by `:` as in
|
||||
* `/user/:username`. If you are using a URL with a port number (e.g.
|
||||
* `/user/:username`. If you are using a URL with a port number (e.g.
|
||||
* `http://example.com:8080/api`), you'll need to escape the colon character before the port
|
||||
* number, like this: `$resource('http://example.com\\:8080/api')`.
|
||||
*
|
||||
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
|
||||
* `actions` methods.
|
||||
* `actions` methods. If any of the parameter value is a function, it will be executed every time
|
||||
* when a param value needs to be obtained for a request (unless the param was overriden).
|
||||
*
|
||||
* Each key value in the parameter object is first bound to url template if present and then any
|
||||
* excess keys are appended to the url search query after the `?`.
|
||||
@@ -36,21 +37,40 @@
|
||||
* the data object (useful for non-GET operations).
|
||||
*
|
||||
* @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
|
||||
* default set of resource actions. The declaration should be created in the following format:
|
||||
* default set of resource actions. The declaration should be created in the format of {@link
|
||||
* ng.$http#Parameters $http.config}:
|
||||
*
|
||||
* {action1: {method:?, params:?, isArray:?},
|
||||
* action2: {method:?, params:?, isArray:?},
|
||||
* {action1: {method:?, params:?, isArray:?, headers:?, ...},
|
||||
* action2: {method:?, params:?, isArray:?, headers:?, ...},
|
||||
* ...}
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* - `action` – {string} – The name of action. This name becomes the name of the method on your
|
||||
* - **`action`** – {string} – The name of action. This name becomes the name of the method on your
|
||||
* resource object.
|
||||
* - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
|
||||
* and `JSONP`
|
||||
* - `params` – {object=} – Optional set of pre-bound parameters for this action.
|
||||
* - isArray – {boolean=} – If true then the returned object for this action is an array, see
|
||||
* - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
|
||||
* and `JSONP`.
|
||||
* - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of the
|
||||
* parameter value is a function, it will be executed every time when a param value needs to be
|
||||
* obtained for a request (unless the param was overriden).
|
||||
* - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, see
|
||||
* `returns` section.
|
||||
* - **`transformRequest`** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
||||
* transform function or an array of such functions. The transform function takes the http
|
||||
* request body and headers and returns its transformed (typically serialized) version.
|
||||
* - **`transformResponse`** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
||||
* transform function or an array of such functions. The transform function takes the http
|
||||
* response body and headers and returns its transformed (typically deserialized) version.
|
||||
* - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
|
||||
* GET request, otherwise if a cache instance built with
|
||||
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
|
||||
* caching.
|
||||
* - **`timeout`** – `{number}` – timeout in milliseconds.
|
||||
* - **`withCredentials`** - `{boolean}` - whether to to set the `withCredentials` flag on the
|
||||
* XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
|
||||
* requests with credentials} for more information.
|
||||
* - **`responseType`** - `{string}` - see {@link
|
||||
* https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
|
||||
*
|
||||
* @returns {Object} A resource "class" object with methods for the default set of resource actions
|
||||
* optionally extended with custom `actions`. The default set contains these actions:
|
||||
@@ -63,9 +83,9 @@
|
||||
*
|
||||
* Calling these methods invoke an {@link ng.$http} with the specified http method,
|
||||
* destination and parameters. When the data is returned from the server then the object is an
|
||||
* instance of the resource class `save`, `remove` and `delete` actions are available on it as
|
||||
* methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read,
|
||||
* update, delete) on server-side data like this:
|
||||
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it
|
||||
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
|
||||
* read, update, delete) on server-side data like this:
|
||||
* <pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
var user = User.get({userId:123}, function() {
|
||||
@@ -90,6 +110,24 @@
|
||||
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
|
||||
*
|
||||
*
|
||||
* The Resource instances and collection have these additional properties:
|
||||
*
|
||||
* - `$then`: the `then` method of a {@link ng.$q promise} derived from the underlying
|
||||
* {@link ng.$http $http} call.
|
||||
*
|
||||
* The success callback for the `$then` method will be resolved if the underlying `$http` requests
|
||||
* succeeds.
|
||||
*
|
||||
* The success callback is called with a single object which is the {@link ng.$http http response}
|
||||
* object extended with a new property `resource`. This `resource` property is a reference to the
|
||||
* result of the resource action — resource object or array of resources.
|
||||
*
|
||||
* The error callback is called with the {@link ng.$http http response} object when an http
|
||||
* error occurs.
|
||||
*
|
||||
* - `$resolved`: true if the promise has been resolved (either with success or rejection);
|
||||
* Knowing if the Resource has been resolved is useful in data-binding.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* # Credit card resource
|
||||
@@ -132,7 +170,7 @@
|
||||
* The object returned from this function execution is a resource "class" which has "static" method
|
||||
* for each action in the definition.
|
||||
*
|
||||
* Calling these methods invoke `$http` on the `url` template with the given `method` and `params`.
|
||||
* Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and `headers`.
|
||||
* When the data is returned from the server then the object is an instance of the resource type and
|
||||
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
|
||||
* operations (create, read, update, delete) on server-side data.
|
||||
@@ -145,9 +183,9 @@
|
||||
});
|
||||
</pre>
|
||||
*
|
||||
* It's worth noting that the success callback for `get`, `query` and other method gets passed
|
||||
* in the response that came from the server as well as $http header getter function, so one
|
||||
* could rewrite the above example and get access to http headers as:
|
||||
* It's worth noting that the success callback for `get`, `query` and other method gets passed
|
||||
* in the response that came from the server as well as $http header getter function, so one
|
||||
* could rewrite the above example and get access to http headers as:
|
||||
*
|
||||
<pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
@@ -270,7 +308,7 @@ angular.module('ngResource', ['ng']).
|
||||
this.defaults = defaults || {};
|
||||
var urlParams = this.urlParams = {};
|
||||
forEach(template.split(/\W/), function(param){
|
||||
if (param && template.match(new RegExp("[^\\\\]:" + param + "\\W"))) {
|
||||
if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(template))) {
|
||||
urlParams[param] = true;
|
||||
}
|
||||
});
|
||||
@@ -278,7 +316,7 @@ angular.module('ngResource', ['ng']).
|
||||
}
|
||||
|
||||
Route.prototype = {
|
||||
url: function(params) {
|
||||
setUrlParams: function(config, params) {
|
||||
var self = this,
|
||||
url = this.template,
|
||||
val,
|
||||
@@ -301,16 +339,17 @@ angular.module('ngResource', ['ng']).
|
||||
});
|
||||
}
|
||||
});
|
||||
url = url.replace(/\/?#$/, '');
|
||||
var query = [];
|
||||
|
||||
// set the url
|
||||
config.url = url.replace(/\/?#$/, '').replace(/\/*$/, '');
|
||||
|
||||
// set params - delegate param encoding to $http
|
||||
forEach(params, function(value, key){
|
||||
if (!self.urlParams[key]) {
|
||||
query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value));
|
||||
config.params = config.params || {};
|
||||
config.params[key] = value;
|
||||
}
|
||||
});
|
||||
query.sort();
|
||||
url = url.replace(/\/*$/, '');
|
||||
return url + (query.length ? '?' + query.join('&') : '');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -324,6 +363,7 @@ angular.module('ngResource', ['ng']).
|
||||
var ids = {};
|
||||
actionParams = extend({}, paramDefaults, actionParams);
|
||||
forEach(actionParams, function(value, key){
|
||||
if (isFunction(value)) { value = value(); }
|
||||
ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
|
||||
});
|
||||
return ids;
|
||||
@@ -341,6 +381,8 @@ angular.module('ngResource', ['ng']).
|
||||
var data;
|
||||
var success = noop;
|
||||
var error = null;
|
||||
var promise;
|
||||
|
||||
switch(arguments.length) {
|
||||
case 4:
|
||||
error = a4;
|
||||
@@ -376,25 +418,45 @@ angular.module('ngResource', ['ng']).
|
||||
}
|
||||
|
||||
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
|
||||
$http({
|
||||
method: action.method,
|
||||
url: route.url(extend({}, extractParams(data, action.params || {}), params)),
|
||||
data: data
|
||||
}).then(function(response) {
|
||||
var data = response.data;
|
||||
var httpConfig = {},
|
||||
promise;
|
||||
|
||||
if (data) {
|
||||
if (action.isArray) {
|
||||
value.length = 0;
|
||||
forEach(data, function(item) {
|
||||
value.push(new Resource(item));
|
||||
});
|
||||
} else {
|
||||
copy(data, value);
|
||||
}
|
||||
forEach(action, function(value, key) {
|
||||
if (key != 'params' && key != 'isArray' ) {
|
||||
httpConfig[key] = copy(value);
|
||||
}
|
||||
});
|
||||
httpConfig.data = data;
|
||||
route.setUrlParams(httpConfig, extend({}, extractParams(data, action.params || {}), params));
|
||||
|
||||
function markResolved() { value.$resolved = true; }
|
||||
|
||||
promise = $http(httpConfig);
|
||||
value.$resolved = false;
|
||||
|
||||
promise.then(markResolved, markResolved);
|
||||
value.$then = promise.then(function(response) {
|
||||
var data = response.data;
|
||||
var then = value.$then, resolved = value.$resolved;
|
||||
|
||||
if (data) {
|
||||
if (action.isArray) {
|
||||
value.length = 0;
|
||||
forEach(data, function(item) {
|
||||
value.push(new Resource(item));
|
||||
});
|
||||
} else {
|
||||
copy(data, value);
|
||||
value.$then = then;
|
||||
value.$resolved = resolved;
|
||||
}
|
||||
(success||noop)(value, response.headers);
|
||||
}, error);
|
||||
}
|
||||
|
||||
(success||noop)(value, response.headers);
|
||||
|
||||
response.resource = value;
|
||||
return response;
|
||||
}, error).then;
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* plain email address links.
|
||||
*
|
||||
* @param {string} text Input text.
|
||||
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
|
||||
* @returns {string} Html-linkified text.
|
||||
*
|
||||
* @usage
|
||||
@@ -24,6 +25,7 @@
|
||||
'mailto:us@somewhere.org,\n'+
|
||||
'another@somewhere.org,\n'+
|
||||
'and one more: ftp://127.0.0.1/.';
|
||||
$scope.snippetWithTarget = 'http://angularjs.org/';
|
||||
}
|
||||
</script>
|
||||
<div ng-controller="Ctrl">
|
||||
@@ -43,6 +45,15 @@
|
||||
<div ng-bind-html="snippet | linky"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="linky-target">
|
||||
<td>linky target</td>
|
||||
<td>
|
||||
<pre><div ng-bind-html="snippetWithTarget | linky:'_blank'"><br></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="escaped-html">
|
||||
<td>no filter</td>
|
||||
<td><pre><div ng-bind="snippet"><br></div></pre></td>
|
||||
@@ -75,6 +86,11 @@
|
||||
toBe('new <a href="http://link">http://link</a>.');
|
||||
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
|
||||
});
|
||||
|
||||
it('should work with the target property', function() {
|
||||
expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")).
|
||||
toBe('<a target="_blank" href="http://angularjs.org/">http://angularjs.org/</a>');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
@@ -82,7 +98,7 @@ angular.module('ngSanitize').filter('linky', function() {
|
||||
var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
|
||||
MAILTO_REGEXP = /^mailto:/;
|
||||
|
||||
return function(text) {
|
||||
return function(text, target) {
|
||||
if (!text) return text;
|
||||
var match;
|
||||
var raw = text;
|
||||
@@ -91,6 +107,10 @@ angular.module('ngSanitize').filter('linky', function() {
|
||||
var writer = htmlSanitizeWriter(html);
|
||||
var url;
|
||||
var i;
|
||||
var properties = {};
|
||||
if (angular.isDefined(target)) {
|
||||
properties.target = target;
|
||||
}
|
||||
while ((match = raw.match(LINKY_URL_REGEXP))) {
|
||||
// We can not end in these as they are sometimes found at the end of the sentence
|
||||
url = match[0];
|
||||
@@ -98,7 +118,8 @@ angular.module('ngSanitize').filter('linky', function() {
|
||||
if (match[2] == match[3]) url = 'mailto:' + url;
|
||||
i = match.index;
|
||||
writer.chars(raw.substr(0, i));
|
||||
writer.start('a', {href:url});
|
||||
properties.href = url;
|
||||
writer.start('a', properties);
|
||||
writer.chars(match[0].replace(MAILTO_REGEXP, ''));
|
||||
writer.end('a');
|
||||
raw = raw.substring(i + match[0].length);
|
||||
|
||||
@@ -123,7 +123,7 @@ var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:
|
||||
BEGING_END_TAGE_REGEXP = /^<\s*\//,
|
||||
COMMENT_REGEXP = /<!--(.*?)-->/g,
|
||||
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
|
||||
URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/,
|
||||
URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/,
|
||||
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character)
|
||||
|
||||
|
||||
|
||||
@@ -298,6 +298,8 @@ angular.scenario.dsl('select', function() {
|
||||
option = select.find('option:contains("' + value + '")');
|
||||
if (option.length) {
|
||||
select.val(option.val());
|
||||
} else {
|
||||
return done("option '" + value + "' not found");
|
||||
}
|
||||
}
|
||||
select.trigger('change');
|
||||
@@ -325,6 +327,7 @@ angular.scenario.dsl('select', function() {
|
||||
* Usage:
|
||||
* element(selector, label).count() get the number of elements that match selector
|
||||
* element(selector, label).click() clicks an element
|
||||
* element(selector, label).mouseover() mouseover an element
|
||||
* element(selector, label).query(fn) executes fn(selectedElements, done)
|
||||
* element(selector, label).{method}() gets the value (as defined by jQuery, ex. val)
|
||||
* element(selector, label).{method}(value) sets the value (as defined by jQuery, ex. val)
|
||||
@@ -365,6 +368,30 @@ angular.scenario.dsl('element', function() {
|
||||
});
|
||||
};
|
||||
|
||||
chain.dblclick = function() {
|
||||
return this.addFutureAction("element '" + this.label + "' dblclick", function($window, $document, done) {
|
||||
var elements = $document.elements();
|
||||
var href = elements.attr('href');
|
||||
var eventProcessDefault = elements.trigger('dblclick')[0];
|
||||
|
||||
if (href && elements[0].nodeName.toUpperCase() === 'A' && eventProcessDefault) {
|
||||
this.application.navigateTo(href, function() {
|
||||
done();
|
||||
}, done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
chain.mouseover = function() {
|
||||
return this.addFutureAction("element '" + this.label + "' mouseover", function($window, $document, done) {
|
||||
var elements = $document.elements();
|
||||
elements.trigger('mouseover');
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
chain.query = function(fn) {
|
||||
return this.addFutureAction('element ' + this.label + ' custom query', function($window, $document, done) {
|
||||
fn.call(this, $document.elements(), done);
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* JSTestDriver adapter for angular scenario tests
|
||||
*
|
||||
* Example of jsTestDriver.conf for running scenario tests with JSTD:
|
||||
<pre>
|
||||
server: http://localhost:9877
|
||||
|
||||
load:
|
||||
- lib/angular-scenario.js
|
||||
- lib/jstd-scenario-adapter-config.js
|
||||
- lib/jstd-scenario-adapter.js
|
||||
# your test files go here #
|
||||
|
||||
proxy:
|
||||
- {matcher: "/your-prefix/*", server: "http://localhost:8000/"}
|
||||
</pre>
|
||||
*
|
||||
* For more information on how to configure jstd proxy, see {@link http://code.google.com/p/js-test-driver/wiki/Proxy}
|
||||
* Note the order of files - it's important !
|
||||
*
|
||||
* Example of jstd-scenario-adapter-config.js
|
||||
<pre>
|
||||
var jstdScenarioAdapter = {
|
||||
relativeUrlPrefix: '/your-prefix/'
|
||||
};
|
||||
</pre>
|
||||
*
|
||||
* Whenever you use <code>browser().navigateTo('relativeUrl')</code> in your scenario test, the relativeUrlPrefix will be prepended.
|
||||
* You have to configure this to work together with JSTD proxy.
|
||||
*
|
||||
* Let's assume you are using the above configuration (jsTestDriver.conf and jstd-scenario-adapter-config.js):
|
||||
* Now, when you call <code>browser().navigateTo('index.html')</code> in your scenario test, the browser will open /your-prefix/index.html.
|
||||
* That matches the proxy, so JSTD will proxy this request to http://localhost:8000/index.html.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Custom type of test case
|
||||
*
|
||||
* @const
|
||||
* @see jstestdriver.TestCaseInfo
|
||||
*/
|
||||
var SCENARIO_TYPE = 'scenario';
|
||||
|
||||
/**
|
||||
* Plugin for JSTestDriver
|
||||
* Connection point between scenario's jstd output and jstestdriver.
|
||||
*
|
||||
* @see jstestdriver.PluginRegistrar
|
||||
*/
|
||||
function JstdPlugin() {
|
||||
var nop = function() {};
|
||||
|
||||
this.reportResult = nop;
|
||||
this.reportEnd = nop;
|
||||
this.runScenario = nop;
|
||||
|
||||
this.name = 'Angular Scenario Adapter';
|
||||
|
||||
/**
|
||||
* Called for each JSTD TestCase
|
||||
*
|
||||
* Handles only SCENARIO_TYPE test cases. There should be only one fake TestCase.
|
||||
* Runs all scenario tests (under one fake TestCase) and report all results to JSTD.
|
||||
*
|
||||
* @param {jstestdriver.TestRunConfiguration} configuration
|
||||
* @param {Function} onTestDone
|
||||
* @param {Function} onAllTestsComplete
|
||||
* @returns {boolean} True if this type of test is handled by this plugin, false otherwise
|
||||
*/
|
||||
this.runTestConfiguration = function(configuration, onTestDone, onAllTestsComplete) {
|
||||
if (configuration.getTestCaseInfo().getType() != SCENARIO_TYPE) return false;
|
||||
|
||||
this.reportResult = onTestDone;
|
||||
this.reportEnd = onAllTestsComplete;
|
||||
this.runScenario();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
this.getTestRunsConfigurationFor = function(testCaseInfos, expressions, testRunsConfiguration) {
|
||||
testRunsConfiguration.push(
|
||||
new jstestdriver.TestRunConfiguration(
|
||||
new jstestdriver.TestCaseInfo(
|
||||
'Angular Scenario Tests', function() {}, SCENARIO_TYPE), []));
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton instance of the plugin
|
||||
* Accessed using closure by:
|
||||
* - jstd output (reports to this plugin)
|
||||
* - initScenarioAdapter (register the plugin to jstd)
|
||||
*/
|
||||
var plugin = new JstdPlugin();
|
||||
|
||||
/**
|
||||
* Initialise scenario jstd-adapter
|
||||
* (only if jstestdriver is defined)
|
||||
*
|
||||
* @param {Object} jstestdriver Undefined when run from browser (without jstd)
|
||||
* @param {Function} initScenarioAndRun Function that inits scenario and runs all the tests
|
||||
* @param {Object=} config Configuration object, supported properties:
|
||||
* - relativeUrlPrefix: prefix for all relative links when navigateTo()
|
||||
*/
|
||||
function initScenarioAdapter(jstestdriver, initScenarioAndRun, config) {
|
||||
if (jstestdriver) {
|
||||
// create and register ScenarioPlugin
|
||||
jstestdriver.pluginRegistrar.register(plugin);
|
||||
plugin.runScenario = initScenarioAndRun;
|
||||
|
||||
/**
|
||||
* HACK (angular.scenario.Application.navigateTo)
|
||||
*
|
||||
* We need to navigate to relative urls when running from browser (without JSTD),
|
||||
* because we want to allow running scenario tests without creating its own virtual host.
|
||||
* For example: http://angular.local/build/docs/docs-scenario.html
|
||||
*
|
||||
* On the other hand, when running with JSTD, we need to navigate to absolute urls,
|
||||
* because of JSTD proxy. (proxy, because of same domain policy)
|
||||
*
|
||||
* So this hack is applied only if running with JSTD and change all relative urls to absolute.
|
||||
*/
|
||||
var appProto = angular.scenario.Application.prototype,
|
||||
navigateTo = appProto.navigateTo,
|
||||
relativeUrlPrefix = config && config.relativeUrlPrefix || '/';
|
||||
|
||||
appProto.navigateTo = function(url, loadFn, errorFn) {
|
||||
if (url.charAt(0) != '/' && url.charAt(0) != '#' &&
|
||||
url != 'about:blank' && !url.match(/^https?/)) {
|
||||
url = relativeUrlPrefix + url;
|
||||
}
|
||||
|
||||
return navigateTo.call(this, url, loadFn, errorFn);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds proper TestResult object from given model spec
|
||||
*
|
||||
* TODO(vojta) report error details
|
||||
*
|
||||
* @param {angular.scenario.ObjectModel.Spec} spec
|
||||
* @returns {jstestdriver.TestResult}
|
||||
*/
|
||||
function createTestResultFromSpec(spec) {
|
||||
var map = {
|
||||
success: 'PASSED',
|
||||
error: 'ERROR',
|
||||
failure: 'FAILED'
|
||||
};
|
||||
|
||||
return new jstestdriver.TestResult(
|
||||
spec.fullDefinitionName,
|
||||
spec.name,
|
||||
jstestdriver.TestResult.RESULT[map[spec.status]],
|
||||
spec.error || '',
|
||||
spec.line || '',
|
||||
spec.duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates JSTD output (jstestdriver.TestResult)
|
||||
*/
|
||||
angular.scenario.output('jstd', function(context, runner, model) {
|
||||
model.on('SpecEnd', function(spec) {
|
||||
plugin.reportResult(createTestResultFromSpec(spec));
|
||||
});
|
||||
|
||||
model.on('RunnerEnd', function() {
|
||||
plugin.reportEnd();
|
||||
});
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @license AngularJS v"NG_VERSION_FULL"
|
||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window) {
|
||||
@@ -1,2 +0,0 @@
|
||||
initScenarioAdapter(window.jstestdriver, angular.scenario.setUpAndRun, window.jstdScenarioAdapter);
|
||||
})(window);
|
||||
@@ -43,7 +43,7 @@ angular.scenario.output('xml', function(context, runner, model) {
|
||||
if (step.error) {
|
||||
var error = $('<error></error>');
|
||||
stepContext.append(error);
|
||||
error.text(formatException(stepContext.error));
|
||||
error.text(formatException(step.error));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+123
-3
@@ -31,7 +31,7 @@ describe('angular', function() {
|
||||
expect(copy(date) === date).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should copy array", function() {
|
||||
it("should deeply copy an array into an existing array", function() {
|
||||
var src = [1, {name:"value"}];
|
||||
var dst = [{key:"v"}];
|
||||
expect(copy(src, dst)).toBe(dst);
|
||||
@@ -40,6 +40,15 @@ describe('angular', function() {
|
||||
expect(dst[1]).not.toBe(src[1]);
|
||||
});
|
||||
|
||||
it("should deeply copy an array into a new array", function() {
|
||||
var src = [1, {name:"value"}];
|
||||
var dst = copy(src);
|
||||
expect(src).toEqual([1, {name:"value"}]);
|
||||
expect(dst).toEqual(src);
|
||||
expect(dst).not.toBe(src);
|
||||
expect(dst[1]).not.toBe(src[1]);
|
||||
});
|
||||
|
||||
it('should copy empty array', function() {
|
||||
var src = [];
|
||||
var dst = [{key: "v"}];
|
||||
@@ -47,7 +56,7 @@ describe('angular', function() {
|
||||
expect(dst).toEqual([]);
|
||||
});
|
||||
|
||||
it("should copy object", function() {
|
||||
it("should deeply copy an object into an existing object", function() {
|
||||
var src = {a:{name:"value"}};
|
||||
var dst = {b:{key:"v"}};
|
||||
expect(copy(src, dst)).toBe(dst);
|
||||
@@ -56,6 +65,16 @@ describe('angular', function() {
|
||||
expect(dst.a).not.toBe(src.a);
|
||||
});
|
||||
|
||||
it("should deeply copy an object into an existing object", function() {
|
||||
var src = {a:{name:"value"}};
|
||||
var dst = copy(src, dst);
|
||||
expect(src).toEqual({a:{name:"value"}});
|
||||
expect(dst).toEqual(src);
|
||||
expect(dst).not.toBe(src);
|
||||
expect(dst.a).toEqual(src.a);
|
||||
expect(dst.a).not.toBe(src.a);
|
||||
});
|
||||
|
||||
it("should copy primitives", function() {
|
||||
expect(copy(null)).toEqual(null);
|
||||
expect(copy('')).toBe('');
|
||||
@@ -257,7 +276,7 @@ describe('angular', function() {
|
||||
function MyObj() {
|
||||
this.bar = 'barVal';
|
||||
this.baz = 'bazVal';
|
||||
};
|
||||
}
|
||||
MyObj.prototype.foo = 'fooVal';
|
||||
|
||||
var obj = new MyObj(),
|
||||
@@ -267,6 +286,77 @@ describe('angular', function() {
|
||||
|
||||
expect(log).toEqual(['bar:barVal', 'baz:bazVal']);
|
||||
});
|
||||
|
||||
|
||||
it('should handle JQLite and jQuery objects like arrays', function() {
|
||||
var jqObject = jqLite("<p><span>s1</span><span>s2</span></p>").find("span"),
|
||||
log = [];
|
||||
|
||||
forEach(jqObject, function(value, key) { log.push(key + ':' + value.innerHTML)});
|
||||
expect(log).toEqual(['0:s1', '1:s2']);
|
||||
});
|
||||
|
||||
|
||||
it('should handle NodeList objects like arrays', function() {
|
||||
var nodeList = jqLite("<p><span>a</span><span>b</span><span>c</span></p>")[0].childNodes,
|
||||
log = [];
|
||||
|
||||
|
||||
forEach(nodeList, function(value, key) { log.push(key + ':' + value.innerHTML)});
|
||||
expect(log).toEqual(['0:a', '1:b', '2:c']);
|
||||
});
|
||||
|
||||
|
||||
it('should handle HTMLCollection objects like arrays', function() {
|
||||
document.body.innerHTML = "<p>" +
|
||||
"<a name='x'>a</a>" +
|
||||
"<a name='y'>b</a>" +
|
||||
"<a name='x'>c</a>" +
|
||||
"</p>";
|
||||
|
||||
var htmlCollection = document.getElementsByName('x'),
|
||||
log = [];
|
||||
|
||||
forEach(htmlCollection, function(value, key) { log.push(key + ':' + value.innerHTML)});
|
||||
expect(log).toEqual(['0:a', '1:c']);
|
||||
});
|
||||
|
||||
|
||||
it('should handle arguments objects like arrays', function() {
|
||||
var args,
|
||||
log = [];
|
||||
|
||||
(function(){ args = arguments}('a', 'b', 'c'));
|
||||
|
||||
forEach(args, function(value, key) { log.push(key + ':' + value)});
|
||||
expect(log).toEqual(['0:a', '1:b', '2:c']);
|
||||
});
|
||||
|
||||
|
||||
it('should handle objects with length property as objects', function() {
|
||||
var obj = {
|
||||
'foo' : 'bar',
|
||||
'length': 2
|
||||
},
|
||||
log = [];
|
||||
|
||||
forEach(obj, function(value, key) { log.push(key + ':' + value)});
|
||||
expect(log).toEqual(['foo:bar', 'length:2']);
|
||||
});
|
||||
|
||||
|
||||
it('should handle objects of custom types with length property as objects', function() {
|
||||
function CustomType() {
|
||||
this.length = 2;
|
||||
this.foo = 'bar'
|
||||
}
|
||||
|
||||
var obj = new CustomType(),
|
||||
log = [];
|
||||
|
||||
forEach(obj, function(value, key) { log.push(key + ':' + value)});
|
||||
expect(log).toEqual(['length:2', 'foo:bar']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -583,6 +673,13 @@ describe('angular', function() {
|
||||
toBe('<ng-abc x="2A">');
|
||||
});
|
||||
});
|
||||
|
||||
describe('startingTag', function() {
|
||||
it('should allow passing in Nodes instead of Elements', function() {
|
||||
var txtNode = document.createTextNode('some text');
|
||||
expect(startingTag(txtNode)).toBe('some text');
|
||||
});
|
||||
});
|
||||
|
||||
describe('snake_case', function(){
|
||||
it('should convert to snake_case', function() {
|
||||
@@ -640,4 +737,27 @@ describe('angular', function() {
|
||||
expect(toJson({key: $rootScope})).toEqual('{"key":"$SCOPE"}');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('noConflict', function() {
|
||||
var globalAngular;
|
||||
beforeEach(function() {
|
||||
globalAngular = angular;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
angular = globalAngular;
|
||||
});
|
||||
|
||||
it('should return angular', function() {
|
||||
var a = angular.noConflict();
|
||||
expect(a).toBe(globalAngular);
|
||||
});
|
||||
|
||||
it('should restore original angular', function() {
|
||||
var a = angular.noConflict();
|
||||
expect(angular).toBeUndefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+1
-1
@@ -175,7 +175,7 @@ describe('Binder', function() {
|
||||
$rootScope.error['throw'] = function() {throw 'MyError';};
|
||||
errorLogs.length = 0;
|
||||
$rootScope.$apply();
|
||||
expect(errorLogs.shift()).toBe('MyError');
|
||||
expect(errorLogs.shift().message).toBe('Error while interpolating: {{error.throw()}}\nMyError');
|
||||
|
||||
$rootScope.error['throw'] = function() {return 'ok';};
|
||||
$rootScope.$apply();
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
describe('injector', function() {
|
||||
var providers;
|
||||
var injector;
|
||||
var providerInjector;
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
beforeEach(module(function($provide, $injector) {
|
||||
providers = function(name, factory, annotations) {
|
||||
$provide.factory(name, extend(factory, annotations||{}));
|
||||
};
|
||||
providerInjector = $injector;
|
||||
}));
|
||||
beforeEach(inject(function($injector){
|
||||
injector = $injector;
|
||||
@@ -72,6 +74,11 @@ describe('injector', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should create a new $injector for the run phase', inject(function($injector) {
|
||||
expect($injector).not.toBe(providerInjector);
|
||||
}));
|
||||
|
||||
|
||||
describe('invoke', function() {
|
||||
var args;
|
||||
|
||||
@@ -549,26 +556,26 @@ describe('injector', function() {
|
||||
|
||||
|
||||
it('should decorate the missing service error with module name', function() {
|
||||
angular.module('TestModule', [], function($injector) {});
|
||||
angular.module('TestModule', [], function(xyzzy) {});
|
||||
expect(function() {
|
||||
createInjector(['TestModule']);
|
||||
}).toThrow('Unknown provider: $injector from TestModule');
|
||||
}).toThrow('Unknown provider: xyzzy from TestModule');
|
||||
});
|
||||
|
||||
|
||||
it('should decorate the missing service error with module function', function() {
|
||||
function myModule($injector){}
|
||||
function myModule(xyzzy){}
|
||||
expect(function() {
|
||||
createInjector([myModule]);
|
||||
}).toThrow('Unknown provider: $injector from ' + myModule);
|
||||
}).toThrow('Unknown provider: xyzzy from ' + myModule);
|
||||
});
|
||||
|
||||
|
||||
it('should decorate the missing service error with module array function', function() {
|
||||
function myModule($injector){}
|
||||
function myModule(xyzzy){}
|
||||
expect(function() {
|
||||
createInjector([['$injector', myModule]]);
|
||||
}).toThrow('Unknown provider: $injector from ' + myModule);
|
||||
createInjector([['xyzzy', myModule]]);
|
||||
}).toThrow('Unknown provider: xyzzy from ' + myModule);
|
||||
});
|
||||
|
||||
|
||||
|
||||
+6
-1
@@ -955,9 +955,14 @@ describe('jqLite', function() {
|
||||
expect(root.append('text')).toEqual(root);
|
||||
expect(root.html()).toEqual('text');
|
||||
});
|
||||
it('should not append anything if parent node is not of type element', function() {
|
||||
it('should append to document fragment', function() {
|
||||
var root = jqLite(document.createDocumentFragment());
|
||||
expect(root.append('<p>foo</p>')).toBe(root);
|
||||
expect(root.children().length).toBe(1);
|
||||
});
|
||||
it('should not append anything if parent node is not of type element or docfrag', function() {
|
||||
var root = jqLite('<p>some text node</p>').contents();
|
||||
expect(root.append('<p>foo</p>')).toBe(root);
|
||||
expect(root.children().length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
+6
-6
@@ -109,7 +109,7 @@ beforeEach(function() {
|
||||
if (this.actual.callCount != 1) {
|
||||
if (this.actual.callCount == 0) {
|
||||
return [
|
||||
'Expected spy ' + this.actual.identity + ' to have been called with ' +
|
||||
'Expected spy ' + this.actual.identity + ' to have been called once with ' +
|
||||
jasmine.pp(expectedArgs) + ' but it was never called.',
|
||||
'Expected spy ' + this.actual.identity + ' not to have been called with ' +
|
||||
jasmine.pp(expectedArgs) + ' but it was.'
|
||||
@@ -117,16 +117,16 @@ beforeEach(function() {
|
||||
}
|
||||
|
||||
return [
|
||||
'Expected spy ' + this.actual.identity + ' to have been called with ' +
|
||||
jasmine.pp(expectedArgs) + ' but it was never called.',
|
||||
'Expected spy ' + this.actual.identity + ' not to have been called with ' +
|
||||
'Expected spy ' + this.actual.identity + ' to have been called once with ' +
|
||||
jasmine.pp(expectedArgs) + ' but it was called ' + this.actual.callCount + ' times.',
|
||||
'Expected spy ' + this.actual.identity + ' not to have been called once with ' +
|
||||
jasmine.pp(expectedArgs) + ' but it was.'
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'Expected spy ' + this.actual.identity + ' to have been called with ' +
|
||||
'Expected spy ' + this.actual.identity + ' to have been called once with ' +
|
||||
jasmine.pp(expectedArgs) + ' but was called with ' + jasmine.pp(this.actual.argsForCall),
|
||||
'Expected spy ' + this.actual.identity + ' not to have been called with ' +
|
||||
'Expected spy ' + this.actual.identity + ' not to have been called once with ' +
|
||||
jasmine.pp(expectedArgs) + ' but was called with ' + jasmine.pp(this.actual.argsForCall)
|
||||
];
|
||||
}
|
||||
|
||||
+14
-2
@@ -279,6 +279,18 @@ describe('browser', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('put via cookies(cookieName, string), if no <base href> ', function () {
|
||||
beforeEach(function () {
|
||||
fakeDocument.basePath = undefined;
|
||||
});
|
||||
|
||||
it('should default path in cookie to "" (empty string)', function () {
|
||||
browser.cookies('cookie', 'bender');
|
||||
// This only fails in Safari and IE when cookiePath returns undefined
|
||||
// Where it now succeeds since baseHref return '' instead of undefined
|
||||
expect(document.cookie).toEqual('cookie=bender');
|
||||
});
|
||||
});
|
||||
|
||||
describe('get via cookies()[cookieName]', function() {
|
||||
|
||||
@@ -555,9 +567,9 @@ describe('browser', function() {
|
||||
expect(browser.baseHref()).toEqual('/base/path/');
|
||||
});
|
||||
|
||||
it('should return undefined if no <base href>', function() {
|
||||
it('should return \'\' (empty string) if no <base href>', function() {
|
||||
fakeDocument.basePath = undefined;
|
||||
expect(browser.baseHref()).toBeUndefined();
|
||||
expect(browser.baseHref()).toEqual('');
|
||||
});
|
||||
|
||||
it('should remove domain from <base href>', function() {
|
||||
|
||||
@@ -104,6 +104,12 @@ describe('$cacheFactory', function() {
|
||||
cache.remove(123);
|
||||
expect(cache.info().size).toBe(0);
|
||||
}));
|
||||
|
||||
|
||||
it("should return value from put", inject(function($cacheFactory) {
|
||||
var obj = {};
|
||||
expect(cache.put('k1', obj)).toBe(obj);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
||||
+248
-15
@@ -116,12 +116,17 @@ describe('$compile', function() {
|
||||
|
||||
describe('compile phase', function() {
|
||||
|
||||
it('should attach scope to the document node when it is compiled explicitly', inject(function($document){
|
||||
$compile($document)($rootScope);
|
||||
expect($document.scope()).toBe($rootScope);
|
||||
}));
|
||||
|
||||
it('should wrap root text nodes in spans', inject(function($compile, $rootScope) {
|
||||
element = jqLite('<div>A<a>B</a>C</div>');
|
||||
var text = element.contents();
|
||||
expect(text[0].nodeName).toEqual('#text');
|
||||
text = $compile(text)($rootScope);
|
||||
expect(lowercase(text[0].nodeName)).toEqual('span');
|
||||
expect(text[0].nodeName).toEqual('SPAN');
|
||||
expect(element.find('span').text()).toEqual('A<a>B</a>C');
|
||||
}));
|
||||
|
||||
@@ -137,6 +142,41 @@ describe('$compile', function() {
|
||||
expect(spans.text().indexOf('C')).toEqual(0);
|
||||
});
|
||||
|
||||
it('should not leak memory when there are top level empty text nodes', function() {
|
||||
var calcCacheSize = function() {
|
||||
var size = 0;
|
||||
forEach(jqLite.cache, function(item, key) { size++; });
|
||||
return size;
|
||||
};
|
||||
|
||||
// We compile the contents of element (i.e. not element itself)
|
||||
// Then delete these contents and check the cache has been reset to zero
|
||||
|
||||
// First with only elements at the top level
|
||||
element = jqLite('<div><div></div></div>');
|
||||
$compile(element.contents())($rootScope);
|
||||
element.html('');
|
||||
expect(calcCacheSize()).toEqual(0);
|
||||
|
||||
// Next with non-empty text nodes at the top level
|
||||
// (in this case the compiler will wrap them in a <span>)
|
||||
element = jqLite('<div>xxx</div>');
|
||||
$compile(element.contents())($rootScope);
|
||||
element.html('');
|
||||
expect(calcCacheSize()).toEqual(0);
|
||||
|
||||
// Next with comment nodes at the top level
|
||||
element = jqLite('<div><!-- comment --></div>');
|
||||
$compile(element.contents())($rootScope);
|
||||
element.html('');
|
||||
expect(calcCacheSize()).toEqual(0);
|
||||
|
||||
// Finally with empty text nodes at the top level
|
||||
element = jqLite('<div> \n<div></div> </div>');
|
||||
$compile(element.contents())($rootScope);
|
||||
element.html('');
|
||||
expect(calcCacheSize()).toEqual(0);
|
||||
});
|
||||
|
||||
describe('multiple directives per element', function() {
|
||||
it('should allow multiple directives per element', inject(function($compile, $rootScope, log){
|
||||
@@ -1381,9 +1421,10 @@ describe('$compile', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should set interpolated attrs to undefined', inject(function($rootScope, $compile) {
|
||||
it('should set interpolated attrs to initial interpolation value', inject(function($rootScope, $compile) {
|
||||
$rootScope.whatever = 'test value';
|
||||
$compile('<div some-attr="{{whatever}}" observer></div>')($rootScope);
|
||||
expect(directiveAttrs.someAttr).toBeUndefined();
|
||||
expect(directiveAttrs.someAttr).toBe($rootScope.whatever);
|
||||
}));
|
||||
|
||||
|
||||
@@ -1538,6 +1579,25 @@ describe('$compile', function() {
|
||||
expect(element.text()).toEqual('WORKS');
|
||||
});
|
||||
});
|
||||
|
||||
it('should support $observe inside link function on directive object', function() {
|
||||
module(function() {
|
||||
directive('testLink', valueFn({
|
||||
templateUrl: 'test-link.html',
|
||||
link: function(scope, element, attrs) {
|
||||
attrs.$observe( 'testLink', function ( val ) {
|
||||
scope.testAttr = val;
|
||||
});
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile, $rootScope, $templateCache) {
|
||||
$templateCache.put('test-link.html', '{{testAttr}}' );
|
||||
element = $compile('<div test-link="{{1+2}}"></div>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('3');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1793,27 +1853,21 @@ describe('$compile', function() {
|
||||
describe('attribute', function() {
|
||||
it('should copy simple attribute', inject(function() {
|
||||
compile('<div><span my-component attr="some text">');
|
||||
expect(componentScope.attr).toEqual(undefined);
|
||||
expect(componentScope.attrAlias).toEqual(undefined);
|
||||
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(componentScope.attr).toEqual('some text');
|
||||
expect(componentScope.attrAlias).toEqual('some text');
|
||||
expect(componentScope.attrAlias).toEqual(componentScope.attr);
|
||||
}));
|
||||
|
||||
it('should set up the interpolation before it reaches the link function', inject(function() {
|
||||
$rootScope.name = 'misko';
|
||||
compile('<div><span my-component attr="hello {{name}}">');
|
||||
expect(componentScope.attr).toEqual('hello misko');
|
||||
expect(componentScope.attrAlias).toEqual('hello misko');
|
||||
}));
|
||||
|
||||
it('should update when interpolated attribute updates', inject(function() {
|
||||
compile('<div><span my-component attr="hello {{name}}">');
|
||||
expect(componentScope.attr).toEqual(undefined);
|
||||
expect(componentScope.attrAlias).toEqual(undefined);
|
||||
|
||||
$rootScope.name = 'misko';
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(componentScope.attr).toEqual('hello misko');
|
||||
expect(componentScope.attrAlias).toEqual('hello misko');
|
||||
|
||||
$rootScope.name = 'igor';
|
||||
$rootScope.$apply();
|
||||
@@ -1938,6 +1992,21 @@ describe('$compile', function() {
|
||||
compile('<div><span bad-declaration>');
|
||||
}).toThrow('Invalid isolate scope definition for directive badDeclaration: xxx');
|
||||
}));
|
||||
|
||||
it('should expose a $$isolateBindings property onto the scope', inject(function() {
|
||||
compile('<div><span my-component>');
|
||||
|
||||
expect(typeof componentScope.$$isolateBindings).toBe('object');
|
||||
|
||||
expect(componentScope.$$isolateBindings.attr).toBe('@attr');
|
||||
expect(componentScope.$$isolateBindings.attrAlias).toBe('@attr');
|
||||
expect(componentScope.$$isolateBindings.ref).toBe('=ref');
|
||||
expect(componentScope.$$isolateBindings.refAlias).toBe('=ref');
|
||||
expect(componentScope.$$isolateBindings.reference).toBe('=reference');
|
||||
expect(componentScope.$$isolateBindings.expr).toBe('&expr');
|
||||
expect(componentScope.$$isolateBindings.exprAlias).toBe('&expr');
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -2264,5 +2333,169 @@ describe('$compile', function() {
|
||||
|
||||
expect(element.text()).toBe('-->|x|');
|
||||
}));
|
||||
|
||||
|
||||
it('should add a $$transcluded property onto the transcluded scope', function() {
|
||||
module(function() {
|
||||
directive('trans', function() {
|
||||
return {
|
||||
transclude: true,
|
||||
replace: true,
|
||||
scope: true,
|
||||
template: '<div><span>I:{{$$transcluded}}</span><div ng-transclude></div></div>'
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
element = $compile('<div><div trans>T:{{$$transcluded}}</div></div>')
|
||||
($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(jqLite(element.find('span')[0]).text()).toEqual('I:');
|
||||
expect(jqLite(element.find('span')[1]).text()).toEqual('T:true');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('href sanitization', function() {
|
||||
|
||||
it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.testUrl = "javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(element.attr('href')).toBe('unsafe:javascript:doEvilStuff()');
|
||||
}));
|
||||
|
||||
|
||||
it('should sanitize data: urls', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.testUrl = "data:evilPayload";
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(element.attr('href')).toBe('unsafe:data:evilPayload');
|
||||
}));
|
||||
|
||||
|
||||
it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
|
||||
// case-sensitive
|
||||
$rootScope.testUrl = "JaVaScRiPt:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
|
||||
|
||||
// tab in protocol
|
||||
$rootScope.testUrl = "java\u0009script:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
expect(element[0].href).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
|
||||
|
||||
// space before
|
||||
$rootScope.testUrl = " javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
|
||||
|
||||
// ws chars before
|
||||
$rootScope.testUrl = " \u000e javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
expect(element[0].href).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
|
||||
|
||||
// post-fixed with proper url
|
||||
$rootScope.testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
|
||||
$rootScope.$apply();
|
||||
expect(element[0].href).toBeOneOf(
|
||||
'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
|
||||
'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
|
||||
);
|
||||
}));
|
||||
|
||||
|
||||
it('should sanitize ngHref bindings as well', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a ng-href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.testUrl = "javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
|
||||
}));
|
||||
|
||||
|
||||
it('should not sanitize valid urls', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
|
||||
$rootScope.testUrl = "foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('foo/bar');
|
||||
|
||||
$rootScope.testUrl = "/foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('/foo/bar');
|
||||
|
||||
$rootScope.testUrl = "../foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('../foo/bar');
|
||||
|
||||
$rootScope.testUrl = "#foo";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('#foo');
|
||||
|
||||
$rootScope.testUrl = "http://foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('http://foo/bar');
|
||||
|
||||
$rootScope.testUrl = " http://foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe(' http://foo/bar');
|
||||
|
||||
$rootScope.testUrl = "https://foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('https://foo/bar');
|
||||
|
||||
$rootScope.testUrl = "ftp://foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('ftp://foo/bar');
|
||||
|
||||
$rootScope.testUrl = "mailto:foo@bar.com";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('mailto:foo@bar.com');
|
||||
}));
|
||||
|
||||
|
||||
it('should not sanitize href on elements other than anchor', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div href="{{testUrl}}"></div>')($rootScope);
|
||||
$rootScope.testUrl = "javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(element.attr('href')).toBe('javascript:doEvilStuff()');
|
||||
}));
|
||||
|
||||
|
||||
it('should not sanitize attributes other than href', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a title="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.testUrl = "javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(element.attr('title')).toBe('javascript:doEvilStuff()');
|
||||
}));
|
||||
|
||||
|
||||
it('should allow reconfiguration of the href whitelist', function() {
|
||||
module(function($compileProvider) {
|
||||
expect($compileProvider.urlSanitizationWhitelist() instanceof RegExp).toBe(true);
|
||||
var returnVal = $compileProvider.urlSanitizationWhitelist(/javascript:/);
|
||||
expect(returnVal).toBe($compileProvider);
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
|
||||
$rootScope.testUrl = "javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('javascript:doEvilStuff()');
|
||||
|
||||
$rootScope.testUrl = "http://recon/figured";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('unsafe:http://recon/figured');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
describe('a', function() {
|
||||
var element;
|
||||
var element, $compile, $rootScope;
|
||||
|
||||
|
||||
beforeEach(inject(function(_$compile_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
|
||||
afterEach(function(){
|
||||
@@ -9,8 +15,7 @@ describe('a', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should prevent default action to be executed when href is empty',
|
||||
inject(function($rootScope, $compile) {
|
||||
it('should prevent default action to be executed when href is empty', function() {
|
||||
var orgLocation = document.location.href,
|
||||
preventDefaultCalled = false,
|
||||
event;
|
||||
@@ -42,5 +47,15 @@ describe('a', function() {
|
||||
}
|
||||
|
||||
expect(document.location.href).toEqual(orgLocation);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
it('should prevent IE for changing text content when setting attribute', function() {
|
||||
// see issue #1949
|
||||
element = jqLite('<a href="">hello@you</a>');
|
||||
$compile(element);
|
||||
element.attr('href', 'bye@me');
|
||||
|
||||
expect(element.text()).toBe('hello@you');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,6 +74,16 @@ describe('boolean attr directives', function() {
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('multiple')).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should bind open', inject(function($rootScope, $compile) {
|
||||
element = $compile('<details ng-open="isOpen"></details>')($rootScope)
|
||||
$rootScope.isOpen=false;
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('open')).toBeFalsy();
|
||||
$rootScope.isOpen=true;
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('open')).toBeTruthy();
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -430,4 +430,110 @@ describe('form', function() {
|
||||
expect(doc).toBeDirty();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('$setPristine', function() {
|
||||
|
||||
it('should reset pristine state of form and controls', function() {
|
||||
|
||||
doc = $compile(
|
||||
'<form name="testForm">' +
|
||||
'<input ng-model="named1" name="foo">' +
|
||||
'<input ng-model="named2" name="bar">' +
|
||||
'</form>')(scope);
|
||||
|
||||
scope.$digest();
|
||||
|
||||
var form = doc,
|
||||
formCtrl = scope.testForm,
|
||||
input1 = form.find('input').eq(0),
|
||||
input1Ctrl = input1.controller('ngModel'),
|
||||
input2 = form.find('input').eq(1),
|
||||
input2Ctrl = input2.controller('ngModel');
|
||||
|
||||
input1Ctrl.$setViewValue('xx');
|
||||
input2Ctrl.$setViewValue('yy');
|
||||
scope.$apply();
|
||||
expect(form).toBeDirty();
|
||||
expect(input1).toBeDirty();
|
||||
expect(input2).toBeDirty();
|
||||
|
||||
formCtrl.$setPristine();
|
||||
expect(form).toBePristine();
|
||||
expect(formCtrl.$pristine).toBe(true);
|
||||
expect(formCtrl.$dirty).toBe(false);
|
||||
expect(input1).toBePristine();
|
||||
expect(input1Ctrl.$pristine).toBe(true);
|
||||
expect(input1Ctrl.$dirty).toBe(false);
|
||||
expect(input2).toBePristine();
|
||||
expect(input2Ctrl.$pristine).toBe(true);
|
||||
expect(input2Ctrl.$dirty).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('should reset pristine state of anonymous form controls', function() {
|
||||
|
||||
doc = $compile(
|
||||
'<form name="testForm">' +
|
||||
'<input ng-model="anonymous">' +
|
||||
'</form>')(scope);
|
||||
|
||||
scope.$digest();
|
||||
|
||||
var form = doc,
|
||||
formCtrl = scope.testForm,
|
||||
input = form.find('input').eq(0),
|
||||
inputCtrl = input.controller('ngModel');
|
||||
|
||||
inputCtrl.$setViewValue('xx');
|
||||
scope.$apply();
|
||||
expect(form).toBeDirty();
|
||||
expect(input).toBeDirty();
|
||||
|
||||
formCtrl.$setPristine();
|
||||
expect(form).toBePristine();
|
||||
expect(formCtrl.$pristine).toBe(true);
|
||||
expect(formCtrl.$dirty).toBe(false);
|
||||
expect(input).toBePristine();
|
||||
expect(inputCtrl.$pristine).toBe(true);
|
||||
expect(inputCtrl.$dirty).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('should reset pristine state of nested forms', function() {
|
||||
|
||||
doc = $compile(
|
||||
'<form name="testForm">' +
|
||||
'<div ng-form>' +
|
||||
'<input ng-model="named" name="foo">' +
|
||||
'</div>' +
|
||||
'</form>')(scope);
|
||||
|
||||
scope.$digest();
|
||||
|
||||
var form = doc,
|
||||
formCtrl = scope.testForm,
|
||||
nestedForm = form.find('div'),
|
||||
nestedFormCtrl = nestedForm.controller('form'),
|
||||
nestedInput = form.find('input').eq(0),
|
||||
nestedInputCtrl = nestedInput.controller('ngModel');
|
||||
|
||||
nestedInputCtrl.$setViewValue('xx');
|
||||
scope.$apply();
|
||||
expect(form).toBeDirty();
|
||||
expect(nestedForm).toBeDirty();
|
||||
expect(nestedInput).toBeDirty();
|
||||
|
||||
formCtrl.$setPristine();
|
||||
expect(form).toBePristine();
|
||||
expect(formCtrl.$pristine).toBe(true);
|
||||
expect(formCtrl.$dirty).toBe(false);
|
||||
expect(nestedForm).toBePristine();
|
||||
expect(nestedFormCtrl.$pristine).toBe(true);
|
||||
expect(nestedFormCtrl.$dirty).toBe(false);
|
||||
expect(nestedInput).toBePristine();
|
||||
expect(nestedInputCtrl.$pristine).toBe(true);
|
||||
expect(nestedInputCtrl.$dirty).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -117,6 +117,18 @@ describe('NgModelController', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setPristine', function() {
|
||||
|
||||
it('should set control to its pristine state', function() {
|
||||
ctrl.$setViewValue('edit');
|
||||
expect(ctrl.$dirty).toBe(true);
|
||||
expect(ctrl.$pristine).toBe(false);
|
||||
|
||||
ctrl.$setPristine();
|
||||
expect(ctrl.$dirty).toBe(false);
|
||||
expect(ctrl.$pristine).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('view -> model', function() {
|
||||
|
||||
@@ -382,6 +394,14 @@ describe('input', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should update the model and not trim the value', function() {
|
||||
compileInput('<input type="text" ng-model="name" name="alias" ng-trim="false" />');
|
||||
|
||||
changeInputValueTo(' a ');
|
||||
expect(scope.name).toEqual(' a ');
|
||||
});
|
||||
|
||||
|
||||
it('should allow complex reference binding', function() {
|
||||
compileInput('<input type="text" ng-model="obj[\'abc\'].name"/>');
|
||||
|
||||
|
||||
@@ -236,6 +236,17 @@ describe('ngClass', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should not mess up class value due to observing an interpolated class attribute', inject(function($rootScope, $compile) {
|
||||
$rootScope.foo = true;
|
||||
$rootScope.$watch("anything", function() {
|
||||
$rootScope.foo = false;
|
||||
});
|
||||
element = $compile('<div ng-class="{foo:foo}"></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('foo')).toBe(false);
|
||||
}));
|
||||
|
||||
|
||||
it('should update ngClassOdd/Even when model is changed by filtering', inject(function($rootScope, $compile) {
|
||||
element = $compile('<ul>' +
|
||||
'<li ng-repeat="i in items" ' +
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
describe('ngKeyup and ngKeydown directives', function() {
|
||||
var element;
|
||||
|
||||
afterEach(function() {
|
||||
dealoc(element);
|
||||
});
|
||||
|
||||
it('should get called on a keyup', inject(function($rootScope, $compile) {
|
||||
element = $compile('<input ng-keyup="touched = true">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.touched).toBeFalsy();
|
||||
|
||||
browserTrigger(element, 'keyup');
|
||||
expect($rootScope.touched).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should get called on a keydown', inject(function($rootScope, $compile) {
|
||||
element = $compile('<input ng-keydown="touched = true">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.touched).toBeFalsy();
|
||||
|
||||
browserTrigger(element, 'keydown');
|
||||
expect($rootScope.touched).toEqual(true);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
@@ -36,6 +36,36 @@ describe('ngSwitch', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should show all switch-whens that match the current value', inject(function($rootScope, $compile) {
|
||||
element = $compile(
|
||||
'<ul ng-switch="select">' +
|
||||
'<li ng-switch-when="1">first:{{name}}</li>' +
|
||||
'<li ng-switch-when="1">, first too:{{name}}</li>' +
|
||||
'<li ng-switch-when="2">second:{{name}}</li>' +
|
||||
'<li ng-switch-when="true">true:{{name}}</li>' +
|
||||
'</ul>')($rootScope);
|
||||
expect(element.html()).toEqual('<!-- ngSwitchWhen: 1 -->' +
|
||||
'<!-- ngSwitchWhen: 1 -->' +
|
||||
'<!-- ngSwitchWhen: 2 -->' +
|
||||
'<!-- ngSwitchWhen: true -->');
|
||||
$rootScope.select = 1;
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('first:, first too:');
|
||||
$rootScope.name="shyam";
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('first:shyam, first too:shyam');
|
||||
$rootScope.select = 2;
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('second:shyam');
|
||||
$rootScope.name = 'misko';
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('second:misko');
|
||||
$rootScope.select = true;
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('true:misko');
|
||||
}));
|
||||
|
||||
|
||||
it('should switch on switch-when-default', inject(function($rootScope, $compile) {
|
||||
element = $compile(
|
||||
'<ng:switch on="select">' +
|
||||
@@ -50,6 +80,21 @@ describe('ngSwitch', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should show all switch-when-default', inject(function($rootScope, $compile) {
|
||||
element = $compile(
|
||||
'<ul ng-switch="select">' +
|
||||
'<li ng-switch-when="1">one</li>' +
|
||||
'<li ng-switch-default>other</li>' +
|
||||
'<li ng-switch-default>, other too</li>' +
|
||||
'</ul>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('other, other too');
|
||||
$rootScope.select = 1;
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('one');
|
||||
}));
|
||||
|
||||
|
||||
it('should call change on switch', inject(function($rootScope, $compile) {
|
||||
element = $compile(
|
||||
'<ng:switch on="url" change="name=\'works\'">' +
|
||||
|
||||
@@ -429,7 +429,7 @@ describe('ngView', function() {
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.load).toHaveBeenCalledOnce();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should set $scope and $controllerController on the view', function() {
|
||||
@@ -459,4 +459,28 @@ describe('ngView', function() {
|
||||
expect(div.controller()).toBe($route.current.scope.ctrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not set $scope or $controllerController on top level text elements in the view', function() {
|
||||
function MyCtrl($scope) {}
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/foo', {templateUrl: 'tpl.html', controller: MyCtrl});
|
||||
});
|
||||
|
||||
inject(function($templateCache, $location, $rootScope, $route) {
|
||||
$templateCache.put('tpl.html', '<div></div> ');
|
||||
$location.url('/foo');
|
||||
$rootScope.$digest();
|
||||
|
||||
forEach(element.contents(), function(node) {
|
||||
if ( node.nodeType == 3 ) {
|
||||
expect(jqLite(node).scope()).not.toBe($route.current.scope);
|
||||
expect(jqLite(node).controller()).not.toBeDefined();
|
||||
} else {
|
||||
expect(jqLite(node).scope()).toBe($route.current.scope);
|
||||
expect(jqLite(node).controller()).toBeDefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,4 +66,57 @@ describe('Filter: filter', function() {
|
||||
expect(filter(items, '!isk').length).toBe(1);
|
||||
expect(filter(items, '!isk')[0]).toEqual(items[1]);
|
||||
});
|
||||
|
||||
describe('should support comparator', function() {
|
||||
|
||||
it('as equality when true', function() {
|
||||
var items = ['misko', 'adam', 'adamson'];
|
||||
var expr = 'adam';
|
||||
expect(filter(items, expr, true)).toEqual([items[1]]);
|
||||
expect(filter(items, expr, false)).toEqual([items[1], items[2]]);
|
||||
|
||||
var items = [
|
||||
{key: 'value1', nonkey: 1},
|
||||
{key: 'value2', nonkey: 2},
|
||||
{key: 'value12', nonkey: 3},
|
||||
{key: 'value1', nonkey:4},
|
||||
{key: 'Value1', nonkey:5}
|
||||
];
|
||||
var expr = {key: 'value1'};
|
||||
expect(filter(items, expr, true)).toEqual([items[0], items[3]]);
|
||||
|
||||
var items = [
|
||||
{key: 1, nonkey: 1},
|
||||
{key: 2, nonkey: 2},
|
||||
{key: 12, nonkey: 3},
|
||||
{key: 1, nonkey:4}
|
||||
];
|
||||
var expr = { key: 1 };
|
||||
expect(filter(items, expr, true)).toEqual([items[0], items[3]]);
|
||||
|
||||
var expr = 12;
|
||||
expect(filter(items, expr, true)).toEqual([items[2]]);
|
||||
});
|
||||
|
||||
it('and use the function given to compare values', function() {
|
||||
var items = [
|
||||
{key: 1, nonkey: 1},
|
||||
{key: 2, nonkey: 2},
|
||||
{key: 12, nonkey: 3},
|
||||
{key: 1, nonkey:14}
|
||||
];
|
||||
var expr = {key: 10};
|
||||
var comparator = function (obj,value) {
|
||||
return obj > value;
|
||||
}
|
||||
expect(filter(items, expr, comparator)).toEqual([items[2]]);
|
||||
|
||||
expr = 10;
|
||||
expect(filter(items, expr, comparator)).toEqual([items[2], items[3]]);
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -71,6 +71,17 @@ describe('filters', function() {
|
||||
var num = formatNumber(123.1116, pattern, ',', '.');
|
||||
expect(num).toBe('123.112');
|
||||
});
|
||||
|
||||
it('should format the same with string as well as numeric fractionSize', function(){
|
||||
var num = formatNumber(123.1, pattern, ',', '.', "0");
|
||||
expect(num).toBe('123');
|
||||
var num = formatNumber(123.1, pattern, ',', '.', 0);
|
||||
expect(num).toBe('123');
|
||||
var num = formatNumber(123.1, pattern, ',', '.', "3");
|
||||
expect(num).toBe('123.100');
|
||||
var num = formatNumber(123.1, pattern, ',', '.', 3);
|
||||
expect(num).toBe('123.100');
|
||||
});
|
||||
});
|
||||
|
||||
describe('currency', function() {
|
||||
@@ -162,9 +173,9 @@ describe('filters', function() {
|
||||
|
||||
describe('date', function() {
|
||||
|
||||
var morning = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.000Z'); //7am
|
||||
var noon = new angular.mock.TzDate(+5, '2010-09-03T17:05:08.000Z'); //12pm
|
||||
var midnight = new angular.mock.TzDate(+5, '2010-09-03T05:05:08.000Z'); //12am
|
||||
var morning = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.001Z'); //7am
|
||||
var noon = new angular.mock.TzDate(+5, '2010-09-03T17:05:08.012Z'); //12pm
|
||||
var midnight = new angular.mock.TzDate(+5, '2010-09-03T05:05:08.123Z'); //12am
|
||||
var earlyDate = new angular.mock.TzDate(+5, '0001-09-03T05:05:08.000Z');
|
||||
|
||||
var date;
|
||||
@@ -192,14 +203,23 @@ describe('filters', function() {
|
||||
expect(date(morning, "yy-MM-dd HH:mm:ss")).
|
||||
toEqual('10-09-03 07:05:08');
|
||||
|
||||
expect(date(morning, "yy-MM-dd HH:mm:ss.sss")).
|
||||
toEqual('10-09-03 07:05:08.001');
|
||||
|
||||
expect(date(midnight, "yyyy-M-d h=H:m:saZ")).
|
||||
toEqual('2010-9-3 12=0:5:8AM0500');
|
||||
toEqual('2010-9-3 12=0:5:8AM-0500');
|
||||
|
||||
expect(date(midnight, "yyyy-MM-dd hh=HH:mm:ssaZ")).
|
||||
toEqual('2010-09-03 12=00:05:08AM0500');
|
||||
toEqual('2010-09-03 12=00:05:08AM-0500');
|
||||
|
||||
expect(date(midnight, "yyyy-MM-dd hh=HH:mm:ss.sssaZ")).
|
||||
toEqual('2010-09-03 12=00:05:08.123AM-0500');
|
||||
|
||||
expect(date(noon, "yyyy-MM-dd hh=HH:mm:ssaZ")).
|
||||
toEqual('2010-09-03 12=12:05:08PM0500');
|
||||
toEqual('2010-09-03 12=12:05:08PM-0500');
|
||||
|
||||
expect(date(noon, "yyyy-MM-dd hh=HH:mm:ss.sssaZ")).
|
||||
toEqual('2010-09-03 12=12:05:08.012PM-0500');
|
||||
|
||||
expect(date(noon, "EEE, MMM d, yyyy")).
|
||||
toEqual('Fri, Sep 3, 2010');
|
||||
@@ -211,14 +231,30 @@ describe('filters', function() {
|
||||
toEqual('September 03, 1');
|
||||
});
|
||||
|
||||
it('should format timezones correctly (as per ISO_8601)', function() {
|
||||
//Note: TzDate's first argument is offset, _not_ timezone.
|
||||
var utc = new angular.mock.TzDate( 0, '2010-09-03T12:05:08.000Z');
|
||||
var eastOfUTC = new angular.mock.TzDate(-5, '2010-09-03T12:05:08.000Z');
|
||||
var westOfUTC = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.000Z');
|
||||
|
||||
expect(date(utc, "yyyy-MM-ddTHH:mm:ssZ")).
|
||||
toEqual('2010-09-03T12:05:08+0000')
|
||||
|
||||
expect(date(eastOfUTC, "yyyy-MM-ddTHH:mm:ssZ")).
|
||||
toEqual('2010-09-03T17:05:08+0500')
|
||||
|
||||
expect(date(westOfUTC, "yyyy-MM-ddTHH:mm:ssZ")).
|
||||
toEqual('2010-09-03T07:05:08-0500')
|
||||
});
|
||||
|
||||
it('should treat single quoted strings as string literals', function() {
|
||||
expect(date(midnight, "yyyy'de' 'a'x'dd' 'adZ' h=H:m:saZ")).
|
||||
toEqual('2010de axdd adZ 12=0:5:8AM0500');
|
||||
toEqual('2010de axdd adZ 12=0:5:8AM-0500');
|
||||
});
|
||||
|
||||
it('should treat a sequence of two single quotes as a literal single quote', function() {
|
||||
expect(date(midnight, "yyyy'de' 'a''dd' 'adZ' h=H:m:saZ")).
|
||||
toEqual("2010de a'dd adZ 12=0:5:8AM0500");
|
||||
toEqual("2010de a'dd adZ 12=0:5:8AM-0500");
|
||||
});
|
||||
|
||||
it('should accept default formats', function() {
|
||||
@@ -253,32 +289,37 @@ describe('filters', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should support various iso8061 date strings as input', function() {
|
||||
var format = 'yyyy-MM ss';
|
||||
it('should support various iso8061 date strings with timezone as input', function() {
|
||||
var format = 'yyyy-MM-dd ss';
|
||||
|
||||
//full ISO8061
|
||||
expect(date('2003-09-10T13:02:03.000Z', format)).toEqual('2003-09 03');
|
||||
expect(date('2003-09-10T13:02:03.000Z', format)).toEqual('2003-09-10 03');
|
||||
|
||||
expect(date('2003-09-10T13:02:03.000+00:00', format)).toEqual('2003-09 03');
|
||||
expect(date('2003-09-10T13:02:03.000+00:00', format)).toEqual('2003-09-10 03');
|
||||
|
||||
expect(date('2003-09-10T13:02:03-08:00', format)).toEqual('2003-09 03');
|
||||
|
||||
expect(date('20030910T033203-0930', format)).toEqual('2003-09 03');
|
||||
|
||||
//no timezone
|
||||
expect(date('2003-09-10T13:02:03.000', format)).toEqual('2003-09 03');
|
||||
expect(date('20030910T033203-0930', format)).toEqual('2003-09-10 03');
|
||||
|
||||
//no millis
|
||||
expect(date('2003-09-10T13:02:03Z', format)).toEqual('2003-09 03');
|
||||
expect(date('2003-09-10T13:02:03Z', format)).toEqual('2003-09-10 03');
|
||||
|
||||
//no seconds
|
||||
expect(date('2003-09-10T13:02Z', format)).toEqual('2003-09 00');
|
||||
expect(date('2003-09-10T13:02Z', format)).toEqual('2003-09-10 00');
|
||||
|
||||
//no minutes
|
||||
expect(date('2003-09-10T13Z', format)).toEqual('2003-09 00');
|
||||
expect(date('2003-09-10T13Z', format)).toEqual('2003-09-10 00');
|
||||
});
|
||||
|
||||
|
||||
it('should parse iso8061 date strings without timezone as local time', function() {
|
||||
var format = 'yyyy-MM-dd HH-mm-ss';
|
||||
|
||||
//full ISO8061 without timezone
|
||||
expect(date('2003-09-10T03:02:04.000', format)).toEqual('2003-09-10 03-02-04');
|
||||
|
||||
expect(date('20030910T030204', format)).toEqual('2003-09-10 03-02-04');
|
||||
|
||||
//no time
|
||||
expect(date('2003-09-10', format)).toEqual('2003-09 00');
|
||||
expect(date('2003-09-10', format)).toEqual('2003-09-10 00-00-00');
|
||||
});
|
||||
|
||||
it('should support different degrees of subsecond precision', function () {
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
describe('Filter: limitTo', function() {
|
||||
var items;
|
||||
var str
|
||||
var limitTo;
|
||||
|
||||
beforeEach(inject(function($filter) {
|
||||
items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
|
||||
str = "tuvwxyz";
|
||||
limitTo = $filter('limitTo');
|
||||
}));
|
||||
|
||||
@@ -13,12 +15,16 @@ describe('Filter: limitTo', function() {
|
||||
it('should return the first X items when X is positive', function() {
|
||||
expect(limitTo(items, 3)).toEqual(['a', 'b', 'c']);
|
||||
expect(limitTo(items, '3')).toEqual(['a', 'b', 'c']);
|
||||
expect(limitTo(str, 3)).toEqual("tuv");
|
||||
expect(limitTo(str, '3')).toEqual("tuv");
|
||||
});
|
||||
|
||||
|
||||
it('should return the last X items when X is negative', function() {
|
||||
expect(limitTo(items, -3)).toEqual(['f', 'g', 'h']);
|
||||
expect(limitTo(items, '-3')).toEqual(['f', 'g', 'h']);
|
||||
expect(limitTo(str, -3)).toEqual("xyz");
|
||||
expect(limitTo(str, '-3')).toEqual("xyz");
|
||||
});
|
||||
|
||||
|
||||
@@ -30,11 +36,17 @@ describe('Filter: limitTo', function() {
|
||||
expect(limitTo(items, undefined)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return an empty string when X cannot be parsed', function() {
|
||||
expect(limitTo(str, 'bogus')).toEqual("");
|
||||
expect(limitTo(str, 'null')).toEqual("");
|
||||
expect(limitTo(str, 'undefined')).toEqual("");
|
||||
expect(limitTo(str, null)).toEqual("");
|
||||
expect(limitTo(str, undefined)).toEqual("");
|
||||
});
|
||||
|
||||
it('should return an empty array when input is not Array type', function() {
|
||||
expect(limitTo('bogus', 1)).toEqual('bogus');
|
||||
expect(limitTo(null, 1)).toEqual(null);
|
||||
expect(limitTo(undefined, 1)).toEqual(undefined);
|
||||
|
||||
it('should return input if not String or Array', function() {
|
||||
expect(limitTo(1,1)).toEqual(1);
|
||||
expect(limitTo(null, 1)).toEqual(null);
|
||||
expect(limitTo(undefined, 1)).toEqual(undefined);
|
||||
expect(limitTo({}, 1)).toEqual({});
|
||||
@@ -42,11 +54,18 @@ describe('Filter: limitTo', function() {
|
||||
|
||||
|
||||
it('should return a copy of input array if X is exceeds array length', function () {
|
||||
expect(limitTo(items, 19)).toEqual(items);
|
||||
expect(limitTo(items, 9)).toEqual(items);
|
||||
expect(limitTo(items, '9')).toEqual(items);
|
||||
expect(limitTo(items, -9)).toEqual(items);
|
||||
expect(limitTo(items, '-9')).toEqual(items);
|
||||
|
||||
expect(limitTo(items, 9)).not.toBe(items);
|
||||
});
|
||||
|
||||
it('should return the entire string if X exceeds input length', function() {
|
||||
expect(limitTo(str, 9)).toEqual(str);
|
||||
expect(limitTo(str, '9')).toEqual(str);
|
||||
expect(limitTo(str, -9)).toEqual(str);
|
||||
expect(limitTo(str, '-9')).toEqual(str);
|
||||
})
|
||||
});
|
||||
|
||||
@@ -116,6 +116,9 @@ describe('$httpBackend', function() {
|
||||
};
|
||||
|
||||
this.getAllResponseHeaders = valueFn('');
|
||||
// for temporary Firefox CORS workaround
|
||||
// see https://github.com/angular/angular.js/issues/1468
|
||||
this.getResponseHeader = valueFn('');
|
||||
}
|
||||
|
||||
callback.andCallFake(function(status, response) {
|
||||
@@ -135,6 +138,24 @@ describe('$httpBackend', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should set responseType and return xhr.response', function() {
|
||||
$backend('GET', '/whatever', null, callback, {}, null, null, 'blob');
|
||||
|
||||
var xhrInstance = MockXhr.$$lastInstance;
|
||||
expect(xhrInstance.responseType).toBe('blob');
|
||||
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(response).toBe(xhrInstance.response);
|
||||
});
|
||||
|
||||
xhrInstance.response = {some: 'object'};
|
||||
xhrInstance.readyState = 4;
|
||||
xhrInstance.onreadystatechange();
|
||||
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
describe('JSONP', function() {
|
||||
|
||||
var SCRIPT_URL = /([^\?]*)\?cb=angular\.callbacks\.(.*)/;
|
||||
|
||||
+88
-13
@@ -144,9 +144,26 @@ describe('$http', function() {
|
||||
|
||||
|
||||
it('should jsonify objects in params map', inject(function($httpBackend, $http) {
|
||||
$httpBackend.expect('GET', '/url?a=1&b=%7B%22c%22%3A3%7D').respond('');
|
||||
$httpBackend.expect('GET', '/url?a=1&b=%7B%22c%22:3%7D').respond('');
|
||||
$http({url: '/url', params: {a:1, b:{c:3}}, method: 'GET'});
|
||||
}));
|
||||
|
||||
|
||||
it('should expand arrays in params map', inject(function($httpBackend, $http) {
|
||||
$httpBackend.expect('GET', '/url?a=1&a=2&a=3').respond('');
|
||||
$http({url: '/url', params: {a: [1,2,3]}, method: 'GET'});
|
||||
}));
|
||||
|
||||
|
||||
it('should not encode @ in url params', function() {
|
||||
//encodeURIComponent is too agressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt
|
||||
//with regards to the character set (pchar) allowed in path segments
|
||||
//so we need this test to make sure that we don't over-encode the params and break stuff
|
||||
//like buzz api which uses @self
|
||||
|
||||
$httpBackend.expect('GET', '/Path?!do%26h=g%3Da+h&:bar=$baz@1').respond('');
|
||||
$http({url: '/Path', params: {':bar': '$baz@1', '!do&h': 'g=a h'}, method: 'GET'});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -371,8 +388,7 @@ describe('$http', function() {
|
||||
|
||||
it('should set default headers for GET request', function() {
|
||||
$httpBackend.expect('GET', '/url', undefined, function(headers) {
|
||||
return headers['Accept'] == 'application/json, text/plain, */*' &&
|
||||
headers['X-Requested-With'] == 'XMLHttpRequest';
|
||||
return headers['Accept'] == 'application/json, text/plain, */*';
|
||||
}).respond('');
|
||||
|
||||
$http({url: '/url', method: 'GET', headers: {}});
|
||||
@@ -383,7 +399,6 @@ describe('$http', function() {
|
||||
it('should set default headers for POST request', function() {
|
||||
$httpBackend.expect('POST', '/url', 'messageBody', function(headers) {
|
||||
return headers['Accept'] == 'application/json, text/plain, */*' &&
|
||||
headers['X-Requested-With'] == 'XMLHttpRequest' &&
|
||||
headers['Content-Type'] == 'application/json;charset=utf-8';
|
||||
}).respond('');
|
||||
|
||||
@@ -395,7 +410,6 @@ describe('$http', function() {
|
||||
it('should set default headers for PUT request', function() {
|
||||
$httpBackend.expect('PUT', '/url', 'messageBody', function(headers) {
|
||||
return headers['Accept'] == 'application/json, text/plain, */*' &&
|
||||
headers['X-Requested-With'] == 'XMLHttpRequest' &&
|
||||
headers['Content-Type'] == 'application/json;charset=utf-8';
|
||||
}).respond('');
|
||||
|
||||
@@ -406,8 +420,7 @@ describe('$http', function() {
|
||||
|
||||
it('should set default headers for custom HTTP method', function() {
|
||||
$httpBackend.expect('FOO', '/url', undefined, function(headers) {
|
||||
return headers['Accept'] == 'application/json, text/plain, */*' &&
|
||||
headers['X-Requested-With'] == 'XMLHttpRequest';
|
||||
return headers['Accept'] == 'application/json, text/plain, */*';
|
||||
}).respond('');
|
||||
|
||||
$http({url: '/url', method: 'FOO', headers: {}});
|
||||
@@ -418,7 +431,6 @@ describe('$http', function() {
|
||||
it('should override default headers with custom', function() {
|
||||
$httpBackend.expect('POST', '/url', 'messageBody', function(headers) {
|
||||
return headers['Accept'] == 'Rewritten' &&
|
||||
headers['X-Requested-With'] == 'XMLHttpRequest' &&
|
||||
headers['Content-Type'] == 'Rewritten';
|
||||
}).respond('');
|
||||
|
||||
@@ -429,6 +441,17 @@ describe('$http', function() {
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('should not set XSRF cookie for cross-domain requests', inject(function($browser) {
|
||||
$browser.cookies('XSRF-TOKEN', 'secret');
|
||||
$browser.url('http://host.com/base');
|
||||
$httpBackend.expect('GET', 'http://www.test.com/url', undefined, function(headers) {
|
||||
return headers['X-XSRF-TOKEN'] === undefined;
|
||||
}).respond('');
|
||||
|
||||
$http({url: 'http://www.test.com/url', method: 'GET', headers: {}});
|
||||
$httpBackend.flush();
|
||||
}));
|
||||
|
||||
|
||||
it('should not send Content-Type header if request data/body is undefined', function() {
|
||||
$httpBackend.expect('POST', '/url', undefined, function(headers) {
|
||||
@@ -441,22 +464,27 @@ describe('$http', function() {
|
||||
|
||||
|
||||
it('should set the XSRF cookie into a XSRF header', inject(function($browser) {
|
||||
function checkXSRF(secret) {
|
||||
function checkXSRF(secret, header) {
|
||||
return function(headers) {
|
||||
return headers['X-XSRF-TOKEN'] == secret;
|
||||
return headers[header || 'X-XSRF-TOKEN'] == secret;
|
||||
};
|
||||
}
|
||||
|
||||
$browser.cookies('XSRF-TOKEN', 'secret');
|
||||
$browser.cookies('aCookie', 'secret2');
|
||||
$httpBackend.expect('GET', '/url', undefined, checkXSRF('secret')).respond('');
|
||||
$httpBackend.expect('POST', '/url', undefined, checkXSRF('secret')).respond('');
|
||||
$httpBackend.expect('PUT', '/url', undefined, checkXSRF('secret')).respond('');
|
||||
$httpBackend.expect('DELETE', '/url', undefined, checkXSRF('secret')).respond('');
|
||||
$httpBackend.expect('GET', '/url', undefined, checkXSRF('secret', 'aHeader')).respond('');
|
||||
$httpBackend.expect('GET', '/url', undefined, checkXSRF('secret2')).respond('');
|
||||
|
||||
$http({url: '/url', method: 'GET'});
|
||||
$http({url: '/url', method: 'POST', headers: {'S-ome': 'Header'}});
|
||||
$http({url: '/url', method: 'PUT', headers: {'Another': 'Header'}});
|
||||
$http({url: '/url', method: 'DELETE', headers: {}});
|
||||
$http({url: '/url', method: 'GET', xsrfHeaderName: 'aHeader'})
|
||||
$http({url: '/url', method: 'GET', xsrfCookieName: 'aCookie'})
|
||||
|
||||
$httpBackend.flush();
|
||||
}));
|
||||
@@ -959,12 +987,35 @@ describe('$http', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should pass timeout and withCredentials', function() {
|
||||
it('should pass timeout, withCredentials and responseType', function() {
|
||||
var $httpBackend = jasmine.createSpy('$httpBackend');
|
||||
|
||||
$httpBackend.andCallFake(function(m, u, d, c, h, timeout, withCredentials) {
|
||||
$httpBackend.andCallFake(function(m, u, d, c, h, timeout, withCredentials, responseType) {
|
||||
expect(timeout).toBe(12345);
|
||||
expect(withCredentials).toBe(true);
|
||||
expect(responseType).toBe('json');
|
||||
});
|
||||
|
||||
module(function($provide) {
|
||||
$provide.value('$httpBackend', $httpBackend);
|
||||
});
|
||||
|
||||
inject(function($http) {
|
||||
$http({
|
||||
method: 'GET', url: 'some.html', timeout: 12345, withCredentials: true, responseType: 'json'
|
||||
});
|
||||
expect($httpBackend).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
$httpBackend.verifyNoOutstandingExpectation = noop;
|
||||
});
|
||||
|
||||
|
||||
it('should use withCredentials from default', function() {
|
||||
var $httpBackend = jasmine.createSpy('$httpBackend');
|
||||
|
||||
$httpBackend.andCallFake(function(m, u, d, c, h, timeout, withCredentials, responseType) {
|
||||
expect(withCredentials).toBe(true);
|
||||
});
|
||||
|
||||
module(function($provide) {
|
||||
@@ -972,10 +1023,34 @@ describe('$http', function() {
|
||||
});
|
||||
|
||||
inject(function($http) {
|
||||
$http({method: 'GET', url: 'some.html', timeout: 12345, withCredentials: true});
|
||||
$http.defaults.withCredentials = true;
|
||||
$http({
|
||||
method: 'GET', url: 'some.html', timeout: 12345, responseType: 'json'
|
||||
});
|
||||
expect($httpBackend).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
$httpBackend.verifyNoOutstandingExpectation = noop;
|
||||
});
|
||||
|
||||
describe('isSameDomain', function() {
|
||||
it('should support various combinations of urls', function() {
|
||||
expect(isSameDomain('path/morepath',
|
||||
'http://www.adomain.com')).toBe(true);
|
||||
expect(isSameDomain('http://www.adomain.com/path',
|
||||
'http://www.adomain.com')).toBe(true);
|
||||
expect(isSameDomain('//www.adomain.com/path',
|
||||
'http://www.adomain.com')).toBe(true);
|
||||
expect(isSameDomain('//www.adomain.com/path',
|
||||
'https://www.adomain.com')).toBe(true);
|
||||
expect(isSameDomain('//www.adomain.com/path',
|
||||
'http://www.adomain.com:1234')).toBe(false);
|
||||
expect(isSameDomain('https://www.adomain.com/path',
|
||||
'http://www.adomain.com')).toBe(false);
|
||||
expect(isSameDomain('http://www.adomain.com:1234/path',
|
||||
'http://www.adomain.com')).toBe(false);
|
||||
expect(isSameDomain('http://www.anotherdomain.com/path',
|
||||
'http://www.adomain.com')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,29 @@ describe('$interpolate', function() {
|
||||
expect($interpolate('{{ false }}')()).toEqual('false');
|
||||
}));
|
||||
|
||||
it('should rethrow exceptions', inject(function($interpolate, $rootScope) {
|
||||
$rootScope.err = function () {
|
||||
throw new Error('oops');
|
||||
};
|
||||
expect(function () {
|
||||
$interpolate('{{err()}}')($rootScope);
|
||||
}).toThrow('Error while interpolating: {{err()}}\nError: oops');
|
||||
}));
|
||||
|
||||
it('should stop interpolation when encountering an exception', inject(function($interpolate, $compile, $rootScope) {
|
||||
$rootScope.err = function () {
|
||||
throw new Error('oops');
|
||||
};
|
||||
var dom = jqLite('<div>{{1 + 1}}</div><div>{{err()}}</div><div>{{1 + 2}}</div>');
|
||||
$compile(dom)($rootScope);
|
||||
expect(function () {
|
||||
$rootScope.$apply();
|
||||
}).toThrow('Error while interpolating: {{err()}}\nError: oops');
|
||||
expect(dom[0].innerHTML).toEqual('2');
|
||||
expect(dom[1].innerHTML).toEqual('{{err()}}');
|
||||
expect(dom[2].innerHTML).toEqual('{{1 + 2}}');
|
||||
}));
|
||||
|
||||
|
||||
it('should return interpolation function', inject(function($interpolate, $rootScope) {
|
||||
$rootScope.name = 'Misko';
|
||||
|
||||
+45
-8
@@ -1,17 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
function initService(debugEnabled) {
|
||||
return module(function($logProvider){
|
||||
$logProvider.debugEnabled(debugEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
describe('$log', function() {
|
||||
var $window, logger, log, warn, info, error;
|
||||
var $window, logger, log, warn, info, error, debug;
|
||||
|
||||
|
||||
|
||||
beforeEach(module(function($provide){
|
||||
$window = {navigator: {}};
|
||||
$window = {navigator: {}, document: {}};
|
||||
logger = '';
|
||||
log = function() { logger+= 'log;'; };
|
||||
warn = function() { logger+= 'warn;'; };
|
||||
info = function() { logger+= 'info;'; };
|
||||
error = function() { logger+= 'error;'; };
|
||||
debug = function() { logger+= 'debug;'; };
|
||||
|
||||
$provide.provider('$log', $LogProvider);
|
||||
$provide.value('$exceptionHandler', angular.mock.rethrow);
|
||||
@@ -23,14 +30,16 @@ describe('$log', function() {
|
||||
$window.console = {log: log,
|
||||
warn: warn,
|
||||
info: info,
|
||||
error: error};
|
||||
error: error,
|
||||
debug: debug};
|
||||
},
|
||||
function($log) {
|
||||
$log.log();
|
||||
$log.warn();
|
||||
$log.info();
|
||||
$log.error();
|
||||
expect(logger).toEqual('log;warn;info;error;');
|
||||
$log.debug();
|
||||
expect(logger).toEqual('log;warn;info;error;debug;');
|
||||
}
|
||||
));
|
||||
|
||||
@@ -44,7 +53,8 @@ describe('$log', function() {
|
||||
$log.warn();
|
||||
$log.info();
|
||||
$log.error();
|
||||
expect(logger).toEqual('log;log;log;log;');
|
||||
$log.debug();
|
||||
expect(logger).toEqual('log;log;log;log;log;');
|
||||
}
|
||||
));
|
||||
|
||||
@@ -55,6 +65,7 @@ describe('$log', function() {
|
||||
$log.warn();
|
||||
$log.info();
|
||||
$log.error();
|
||||
$log.debug();
|
||||
}
|
||||
));
|
||||
|
||||
@@ -64,22 +75,48 @@ describe('$log', function() {
|
||||
log.apply = log.call =
|
||||
warn.apply = warn.call =
|
||||
info.apply = info.call =
|
||||
error.apply = error.call = null;
|
||||
error.apply = error.call =
|
||||
debug.apply = debug.call = null;
|
||||
|
||||
$window.console = {log: log,
|
||||
warn: warn,
|
||||
info: info,
|
||||
error: error};
|
||||
error: error,
|
||||
debug: debug};
|
||||
},
|
||||
function($log) {
|
||||
$log.log.apply($log);
|
||||
$log.warn.apply($log);
|
||||
$log.info.apply($log);
|
||||
$log.error.apply($log);
|
||||
expect(logger).toEqual('log;warn;info;error;');
|
||||
$log.debug.apply($log);
|
||||
expect(logger).toEqual('log;warn;info;error;debug;');
|
||||
})
|
||||
);
|
||||
|
||||
describe("$log.debug", function () {
|
||||
|
||||
beforeEach(initService(false));
|
||||
|
||||
it("should skip debugging output if disabled", inject(
|
||||
function(){
|
||||
$window.console = {log: log,
|
||||
warn: warn,
|
||||
info: info,
|
||||
error: error,
|
||||
debug: debug};
|
||||
},
|
||||
function($log) {
|
||||
$log.log();
|
||||
$log.warn();
|
||||
$log.info();
|
||||
$log.error();
|
||||
$log.debug();
|
||||
expect(logger).toEqual('log;warn;info;error;');
|
||||
}
|
||||
));
|
||||
|
||||
});
|
||||
|
||||
describe('$log.error', function() {
|
||||
var e, $log, errorArgs;
|
||||
|
||||
+81
-3
@@ -91,8 +91,8 @@ describe('parser', function() {
|
||||
expect(tokens[1].text).toEqual('b');
|
||||
});
|
||||
|
||||
it('should tokenize relation', function() {
|
||||
var tokens = lex("! == != < > <= >=");
|
||||
it('should tokenize relation and equality', function() {
|
||||
var tokens = lex("! == != < > <= >= === !==");
|
||||
expect(tokens[0].text).toEqual('!');
|
||||
expect(tokens[1].text).toEqual('==');
|
||||
expect(tokens[2].text).toEqual('!=');
|
||||
@@ -100,6 +100,8 @@ describe('parser', function() {
|
||||
expect(tokens[4].text).toEqual('>');
|
||||
expect(tokens[5].text).toEqual('<=');
|
||||
expect(tokens[6].text).toEqual('>=');
|
||||
expect(tokens[7].text).toEqual('===');
|
||||
expect(tokens[8].text).toEqual('!==');
|
||||
});
|
||||
|
||||
it('should tokenize statements', function() {
|
||||
@@ -197,12 +199,20 @@ describe('parser', function() {
|
||||
expect(scope.$eval("false")).toBeFalsy();
|
||||
expect(scope.$eval("!true")).toBeFalsy();
|
||||
expect(scope.$eval("1==1")).toBeTruthy();
|
||||
expect(scope.$eval("1==true")).toBeTruthy();
|
||||
expect(scope.$eval("1===1")).toBeTruthy();
|
||||
expect(scope.$eval("1==='1'")).toBeFalsy();
|
||||
expect(scope.$eval("1===true")).toBeFalsy();
|
||||
expect(scope.$eval("'true'===true")).toBeFalsy();
|
||||
expect(scope.$eval("1!==2")).toBeTruthy();
|
||||
expect(scope.$eval("1!=='1'")).toBeTruthy();
|
||||
expect(scope.$eval("1!=2")).toBeTruthy();
|
||||
expect(scope.$eval("1<2")).toBeTruthy();
|
||||
expect(scope.$eval("1<=1")).toBeTruthy();
|
||||
expect(scope.$eval("1>2")).toEqual(1>2);
|
||||
expect(scope.$eval("2>=1")).toEqual(2>=1);
|
||||
expect(scope.$eval("true==2<3")).toEqual(true === 2<3);
|
||||
expect(scope.$eval("true==2<3")).toEqual(true == 2<3);
|
||||
expect(scope.$eval("true===2<3")).toEqual(true === 2<3);
|
||||
});
|
||||
|
||||
it('should parse logical', function() {
|
||||
@@ -661,6 +671,74 @@ describe('parser', function() {
|
||||
expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('literal', function() {
|
||||
it('should mark scalar value expressions as literal', inject(function($parse) {
|
||||
expect($parse('0').literal).toBe(true);
|
||||
expect($parse('"hello"').literal).toBe(true);
|
||||
expect($parse('true').literal).toBe(true);
|
||||
expect($parse('false').literal).toBe(true);
|
||||
expect($parse('null').literal).toBe(true);
|
||||
expect($parse('undefined').literal).toBe(true);
|
||||
}));
|
||||
|
||||
it('should mark array expressions as literal', inject(function($parse) {
|
||||
expect($parse('[]').literal).toBe(true);
|
||||
expect($parse('[1, 2, 3]').literal).toBe(true);
|
||||
expect($parse('[1, identifier]').literal).toBe(true);
|
||||
}));
|
||||
|
||||
it('should mark object expressions as literal', inject(function($parse) {
|
||||
expect($parse('{}').literal).toBe(true);
|
||||
expect($parse('{x: 1}').literal).toBe(true);
|
||||
expect($parse('{foo: bar}').literal).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not mark function calls or operator expressions as literal', inject(function($parse) {
|
||||
expect($parse('1 + 1').literal).toBe(false);
|
||||
expect($parse('call()').literal).toBe(false);
|
||||
expect($parse('[].length').literal).toBe(false);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('constant', function() {
|
||||
it('should mark scalar value expressions as constant', inject(function($parse) {
|
||||
expect($parse('12.3').constant).toBe(true);
|
||||
expect($parse('"string"').constant).toBe(true);
|
||||
expect($parse('true').constant).toBe(true);
|
||||
expect($parse('false').constant).toBe(true);
|
||||
expect($parse('null').constant).toBe(true);
|
||||
expect($parse('undefined').constant).toBe(true);
|
||||
}));
|
||||
|
||||
it('should mark arrays as constant if they only contain constant elements', inject(function($parse) {
|
||||
expect($parse('[]').constant).toBe(true);
|
||||
expect($parse('[1, 2, 3]').constant).toBe(true);
|
||||
expect($parse('["string", null]').constant).toBe(true);
|
||||
expect($parse('[[]]').constant).toBe(true);
|
||||
expect($parse('[1, [2, 3], {4: 5}]').constant).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not mark arrays as constant if they contain any non-constant elements', inject(function($parse) {
|
||||
expect($parse('[foo]').constant).toBe(false);
|
||||
expect($parse('[x + 1]').constant).toBe(false);
|
||||
expect($parse('[bar[0]]').constant).toBe(false);
|
||||
}));
|
||||
|
||||
it('should mark complex expressions involving constant values as constant', inject(function($parse) {
|
||||
expect($parse('!true').constant).toBe(true);
|
||||
expect($parse('1 - 1').constant).toBe(true);
|
||||
expect($parse('"foo" + "bar"').constant).toBe(true);
|
||||
expect($parse('5 != null').constant).toBe(true);
|
||||
expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not mark any expression involving variables or function calls as constant', inject(function($parse) {
|
||||
expect($parse('true.toString()').constant).toBe(false);
|
||||
expect($parse('foo(1, 2, 3)').constant).toBe(false);
|
||||
expect($parse('"name" + id').constant).toBe(false);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -99,6 +99,14 @@ describe('Scope', function() {
|
||||
expect(spy).wasCalled();
|
||||
}));
|
||||
|
||||
it('should not keep constant expressions on watch queue', inject(function($rootScope) {
|
||||
$rootScope.$watch('1 + 1', function() {});
|
||||
expect($rootScope.$$watchers.length).toEqual(1);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.$$watchers.length).toEqual(0);
|
||||
}));
|
||||
|
||||
|
||||
it('should delegate exceptions', function() {
|
||||
module(function($exceptionHandlerProvider) {
|
||||
@@ -119,10 +127,14 @@ describe('Scope', function() {
|
||||
var log = '';
|
||||
$rootScope.$watch('a', function() { log += 'a'; });
|
||||
$rootScope.$watch('b', function() { log += 'b'; });
|
||||
// constant expressions have slightly different handling,
|
||||
// let's ensure they are kept in the same list as others
|
||||
$rootScope.$watch('1', function() { log += '1'; });
|
||||
$rootScope.$watch('c', function() { log += 'c'; });
|
||||
$rootScope.$watch('2', function() { log += '2'; });
|
||||
$rootScope.a = $rootScope.b = $rootScope.c = 1;
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual('abc');
|
||||
expect(log).toEqual('ab1c2');
|
||||
}));
|
||||
|
||||
|
||||
@@ -216,7 +228,7 @@ describe('Scope', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should prevent infinite recursion and print print watcher function name or body',
|
||||
it('should prevent infinite recursion and print watcher function name or body',
|
||||
inject(function($rootScope) {
|
||||
$rootScope.$watch(function watcherA() {return $rootScope.a;}, function() {$rootScope.b++;});
|
||||
$rootScope.$watch(function() {return $rootScope.b;}, function() {$rootScope.a++;});
|
||||
@@ -328,7 +340,7 @@ describe('Scope', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should always call the watchr with newVal and oldVal equal on the first run',
|
||||
it('should always call the watcher with newVal and oldVal equal on the first run',
|
||||
inject(function($rootScope) {
|
||||
var log = [];
|
||||
function logger(scope, newVal, oldVal) {
|
||||
@@ -457,7 +469,7 @@ describe('Scope', function() {
|
||||
child.$evalAsync(function(scope) { log += 'child.async;'; });
|
||||
child.$watch('value', function() { log += 'child.$digest;'; });
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual('parent.async;parent.$digest;child.async;child.$digest;');
|
||||
expect(log).toEqual('parent.async;child.async;parent.$digest;child.$digest;');
|
||||
}));
|
||||
|
||||
it('should cause a $digest rerun', inject(function($rootScope) {
|
||||
|
||||
@@ -59,6 +59,63 @@ describe('$route', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should route and fire change event when catch-all params are used', function() {
|
||||
var log = '',
|
||||
lastRoute,
|
||||
nextRoute;
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/Book1/:book/Chapter/:chapter/*highlight/edit',
|
||||
{controller: noop, templateUrl: 'Chapter.html'});
|
||||
$routeProvider.when('/Book2/:book/*highlight/Chapter/:chapter',
|
||||
{controller: noop, templateUrl: 'Chapter.html'});
|
||||
$routeProvider.when('/Blank', {});
|
||||
});
|
||||
inject(function($route, $location, $rootScope) {
|
||||
$rootScope.$on('$routeChangeStart', function(event, next, current) {
|
||||
log += 'before();';
|
||||
expect(current).toBe($route.current);
|
||||
lastRoute = current;
|
||||
nextRoute = next;
|
||||
});
|
||||
$rootScope.$on('$routeChangeSuccess', function(event, current, last) {
|
||||
log += 'after();';
|
||||
expect(current).toBe($route.current);
|
||||
expect(lastRoute).toBe(last);
|
||||
expect(nextRoute).toBe(current);
|
||||
});
|
||||
|
||||
$location.path('/Book1/Moby/Chapter/Intro/one/edit').search('p=123');
|
||||
$rootScope.$digest();
|
||||
$httpBackend.flush();
|
||||
expect(log).toEqual('before();after();');
|
||||
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one', p:'123'});
|
||||
|
||||
log = '';
|
||||
$location.path('/Blank').search('ignore');
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual('before();after();');
|
||||
expect($route.current.params).toEqual({ignore:true});
|
||||
|
||||
log = '';
|
||||
$location.path('/Book1/Moby/Chapter/Intro/one/two/edit').search('p=123');
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual('before();after();');
|
||||
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one/two', p:'123'});
|
||||
|
||||
log = '';
|
||||
$location.path('/Book2/Moby/one/two/Chapter/Intro').search('p=123');
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual('before();after();');
|
||||
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one/two', p:'123'});
|
||||
|
||||
log = '';
|
||||
$location.path('/NONE');
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual('before();after();');
|
||||
expect($route.current).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not change route when location is canceled', function() {
|
||||
module(function($routeProvider) {
|
||||
@@ -715,6 +772,53 @@ describe('$route', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should allow using a function as a template', function() {
|
||||
var customTemplateWatcher = jasmine.createSpy('customTemplateWatcher');
|
||||
|
||||
function customTemplateFn(routePathParams) {
|
||||
customTemplateWatcher(routePathParams);
|
||||
expect(routePathParams).toEqual({id: 'id3'});
|
||||
return '<h1>' + routePathParams.id + '</h1>';
|
||||
}
|
||||
|
||||
module(function($routeProvider){
|
||||
$routeProvider.when('/bar/:id/:subid/:subsubid', {templateUrl: 'bar.html'});
|
||||
$routeProvider.when('/foo/:id', {template: customTemplateFn});
|
||||
});
|
||||
|
||||
inject(function($route, $location, $rootScope) {
|
||||
$location.path('/foo/id3');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(customTemplateWatcher).toHaveBeenCalledWith({id: 'id3'});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should allow using a function as a templateUrl', function() {
|
||||
var customTemplateUrlWatcher = jasmine.createSpy('customTemplateUrlWatcher');
|
||||
|
||||
function customTemplateUrlFn(routePathParams) {
|
||||
customTemplateUrlWatcher(routePathParams);
|
||||
expect(routePathParams).toEqual({id: 'id3'});
|
||||
return 'foo.html';
|
||||
}
|
||||
|
||||
module(function($routeProvider){
|
||||
$routeProvider.when('/bar/:id/:subid/:subsubid', {templateUrl: 'bar.html'});
|
||||
$routeProvider.when('/foo/:id', {templateUrl: customTemplateUrlFn});
|
||||
});
|
||||
|
||||
inject(function($route, $location, $rootScope) {
|
||||
$location.path('/foo/id3');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(customTemplateUrlWatcher).toHaveBeenCalledWith({id: 'id3'});
|
||||
expect($route.current.loadedTemplateUrl).toEqual('foo.html');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('reload', function() {
|
||||
|
||||
it('should reload even if reloadOnSearch is false', function() {
|
||||
|
||||
+24
-6
@@ -2,9 +2,10 @@
|
||||
|
||||
describe('$sniffer', function() {
|
||||
|
||||
function sniffer($window) {
|
||||
function sniffer($window, $document) {
|
||||
$window.navigator = {};
|
||||
return new $SnifferProvider().$get[1]($window);
|
||||
$document = jqLite($document || {});
|
||||
return new $SnifferProvider().$get[2]($window, $document);
|
||||
}
|
||||
|
||||
describe('history', function() {
|
||||
@@ -20,15 +21,15 @@ describe('$sniffer', function() {
|
||||
|
||||
describe('hashchange', function() {
|
||||
it('should be true if onhashchange property defined', function() {
|
||||
expect(sniffer({onhashchange: true, document: {}}).hashchange).toBe(true);
|
||||
expect(sniffer({onhashchange: true}, {}).hashchange).toBe(true);
|
||||
});
|
||||
|
||||
it('should be false if onhashchange property not defined', function() {
|
||||
expect(sniffer({document: {}}).hashchange).toBe(false);
|
||||
expect(sniffer({}, {}).hashchange).toBe(false);
|
||||
});
|
||||
|
||||
it('should be false if documentMode is 7 (IE8 comp mode)', function() {
|
||||
expect(sniffer({onhashchange: true, document: {documentMode: 7}}).hashchange).toBe(false);
|
||||
expect(sniffer({onhashchange: true}, {documentMode: 7}).hashchange).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,7 +43,7 @@ describe('$sniffer', function() {
|
||||
if (elm === 'div') return mockDivElement;
|
||||
});
|
||||
|
||||
$sniffer = sniffer({document: mockDocument});
|
||||
$sniffer = sniffer({}, mockDocument);
|
||||
});
|
||||
|
||||
|
||||
@@ -78,4 +79,21 @@ describe('$sniffer', function() {
|
||||
expect($sniffer.hasEvent('input')).toBe((msie == 9) ? false : true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('csp', function() {
|
||||
it('should be false if document.securityPolicy.isActive not available', function() {
|
||||
expect(sniffer({}, {}).csp).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('should use document.securityPolicy.isActive if available', function() {
|
||||
var createDocumentWithCSP = function(csp) {
|
||||
return {securityPolicy: {isActive: csp}};
|
||||
};
|
||||
|
||||
expect(sniffer({}, createDocumentWithCSP(false)).csp).toBe(false);
|
||||
expect(sniffer({}, createDocumentWithCSP(true)).csp).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+24
@@ -109,6 +109,13 @@ describe('ngMock', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should fake getMilliseconds method', function() {
|
||||
expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.003Z').getMilliseconds()).toBe(3);
|
||||
expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.023Z').getMilliseconds()).toBe(23);
|
||||
expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.123Z').getMilliseconds()).toBe(123);
|
||||
});
|
||||
|
||||
|
||||
it('should create a date representing new year in Bratislava', function() {
|
||||
var newYearInBratislava = new angular.mock.TzDate(-1, '2009-12-31T23:00:00.000Z');
|
||||
expect(newYearInBratislava.getTimezoneOffset()).toBe(-60);
|
||||
@@ -117,6 +124,7 @@ describe('ngMock', function() {
|
||||
expect(newYearInBratislava.getDate()).toBe(1);
|
||||
expect(newYearInBratislava.getHours()).toBe(0);
|
||||
expect(newYearInBratislava.getMinutes()).toBe(0);
|
||||
expect(newYearInBratislava.getSeconds()).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -327,6 +335,22 @@ describe('ngMock', function() {
|
||||
$timeout.flush();
|
||||
expect(logger).toEqual(['t1', 't3', 't2']);
|
||||
}));
|
||||
|
||||
|
||||
it('should throw an exception when not flushed', inject(function($timeout){
|
||||
$timeout(noop);
|
||||
|
||||
var expectedError = 'Deferred tasks to flush (1): {id: 0, time: 0}';
|
||||
expect(function() {$timeout.verifyNoPendingTasks();}).toThrow(expectedError);
|
||||
}));
|
||||
|
||||
|
||||
it('should do nothing when all tasks have been flushed', inject(function($timeout) {
|
||||
$timeout(noop);
|
||||
|
||||
$timeout.flush();
|
||||
expect(function() {$timeout.verifyNoPendingTasks();}).not.toThrow();
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,14 @@ describe("resource", function() {
|
||||
},
|
||||
patch: {
|
||||
method: 'PATCH'
|
||||
},
|
||||
conditionalPut: {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'If-None-Match': '*'
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
callback = jasmine.createSpy();
|
||||
}));
|
||||
@@ -112,14 +119,33 @@ describe("resource", function() {
|
||||
|
||||
|
||||
it('should not encode @ in url params', function() {
|
||||
//encodeURIComponent is too agressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt
|
||||
//with regards to the character set (pchar) allowed in path segments
|
||||
//so we need this test to make sure that we don't over-encode the params and break stuff like
|
||||
//buzz api which uses @self
|
||||
//encodeURIComponent is too agressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt
|
||||
//with regards to the character set (pchar) allowed in path segments
|
||||
//so we need this test to make sure that we don't over-encode the params and break stuff like
|
||||
//buzz api which uses @self
|
||||
|
||||
var R = $resource('/Path/:a');
|
||||
$httpBackend.expect('GET', '/Path/doh@fo%20o?!do%26h=g%3Da+h&:bar=$baz@1').respond('{}');
|
||||
R.get({a: 'doh@fo o', ':bar': '$baz@1', '!do&h': 'g=a h'});
|
||||
});
|
||||
|
||||
|
||||
it('should encode array params', function() {
|
||||
var R = $resource('/Path/:a');
|
||||
$httpBackend.expect('GET', '/Path/doh@fo%20o?!do%26h=g%3Da+h&:bar=$baz@1').respond('{}');
|
||||
R.get({a: 'doh@fo o', ':bar': '$baz@1', '!do&h': 'g=a h'});
|
||||
$httpBackend.expect('GET', '/Path/doh&foo?bar=baz1&bar=baz2').respond('{}');
|
||||
R.get({a: 'doh&foo', bar: ['baz1', 'baz2']});
|
||||
});
|
||||
|
||||
it('should allow relative paths in resource url', function () {
|
||||
var R = $resource(':relativePath');
|
||||
$httpBackend.expect('GET', 'data.json').respond('{}');
|
||||
R.get({ relativePath: 'data.json' });
|
||||
});
|
||||
|
||||
it('should handle + in url params', function () {
|
||||
var R = $resource('/api/myapp/:myresource?from=:from&to=:to&histlen=:histlen');
|
||||
$httpBackend.expect('GET', '/api/myapp/pear+apple?from=2012-04-01&to=2012-04-29&histlen=3').respond('{}');
|
||||
R.get({ myresource: 'pear+apple', from : '2012-04-01', to : '2012-04-29', histlen : 3 });
|
||||
});
|
||||
|
||||
|
||||
@@ -213,6 +239,15 @@ describe("resource", function() {
|
||||
});
|
||||
|
||||
|
||||
it('should send correct headers', function() {
|
||||
$httpBackend.expectPUT('/CreditCard/123', undefined, function(headers) {
|
||||
return headers['If-None-Match'] == "*";
|
||||
}).respond({id:123});
|
||||
|
||||
CreditCard.conditionalPut({id: {key:123}});
|
||||
});
|
||||
|
||||
|
||||
it("should read partial resource", function() {
|
||||
$httpBackend.expect('GET', '/CreditCard').respond([{id:{key:123}}]);
|
||||
var ccs = CreditCard.query();
|
||||
@@ -248,7 +283,7 @@ describe("resource", function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
|
||||
|
||||
var ccs = CreditCard.query({key: 'value'}, callback);
|
||||
expect(ccs).toEqual([]);
|
||||
expect(ccs).toEqualData([]);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
$httpBackend.flush();
|
||||
@@ -365,6 +400,35 @@ describe("resource", function() {
|
||||
});
|
||||
|
||||
|
||||
it('should support dynamic default parameters (global)', function() {
|
||||
var currentGroup = 'students',
|
||||
Person = $resource('/Person/:group/:id', { group: function() { return currentGroup; }});
|
||||
|
||||
|
||||
$httpBackend.expect('GET', '/Person/students/fedor').respond({id: 'fedor', email: 'f@f.com'});
|
||||
|
||||
var fedor = Person.get({id: 'fedor'});
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(fedor).toEqualData({id: 'fedor', email: 'f@f.com'});
|
||||
});
|
||||
|
||||
|
||||
it('should support dynamic default parameters (action specific)', function() {
|
||||
var currentGroup = 'students',
|
||||
Person = $resource('/Person/:group/:id', {}, {
|
||||
fetch: {method: 'GET', params: {group: function() { return currentGroup; }}}
|
||||
});
|
||||
|
||||
$httpBackend.expect('GET', '/Person/students/fedor').respond({id: 'fedor', email: 'f@f.com'});
|
||||
|
||||
var fedor = Person.fetch({id: 'fedor'});
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(fedor).toEqualData({id: 'fedor', email: 'f@f.com'});
|
||||
});
|
||||
|
||||
|
||||
it('should exercise full stack', function() {
|
||||
var Person = $resource('/Person/:id');
|
||||
|
||||
@@ -375,6 +439,210 @@ describe("resource", function() {
|
||||
});
|
||||
|
||||
|
||||
describe('promise api', function() {
|
||||
|
||||
var $rootScope;
|
||||
|
||||
|
||||
beforeEach(inject(function(_$rootScope_) {
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
|
||||
describe('single resource', function() {
|
||||
|
||||
it('should add promise $then method to the result object', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
|
||||
var cc = CreditCard.get({id: 123});
|
||||
|
||||
cc.$then(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
var response = callback.mostRecentCall.args[0];
|
||||
|
||||
expect(response).toEqualData({
|
||||
data: {id: 123, number: '9876'},
|
||||
status: 200,
|
||||
config: {method: 'GET', data: undefined, url: '/CreditCard/123'},
|
||||
resource: {id: 123, number: '9876', $resolved: true}
|
||||
});
|
||||
expect(typeof response.resource.$save).toBe('function');
|
||||
});
|
||||
|
||||
|
||||
it('should keep $then around after promise resolution', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
|
||||
var cc = CreditCard.get({id: 123});
|
||||
|
||||
cc.$then(callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
var response = callback.mostRecentCall.args[0];
|
||||
|
||||
callback.reset();
|
||||
|
||||
cc.$then(callback);
|
||||
$rootScope.$apply(); //flush async queue
|
||||
|
||||
expect(callback).toHaveBeenCalledOnceWith(response);
|
||||
});
|
||||
|
||||
|
||||
it('should allow promise chaining via $then method', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
|
||||
var cc = CreditCard.get({id: 123});
|
||||
|
||||
cc.$then(function(response) { return 'new value'; }).then(callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(callback).toHaveBeenCalledOnceWith('new value');
|
||||
});
|
||||
|
||||
|
||||
it('should allow error callback registration via $then method', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard/123').respond(404, 'resource not found');
|
||||
var cc = CreditCard.get({id: 123});
|
||||
|
||||
cc.$then(null, callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
var response = callback.mostRecentCall.args[0];
|
||||
|
||||
expect(response).toEqualData({
|
||||
data : 'resource not found',
|
||||
status : 404,
|
||||
config : { method : 'GET', data : undefined, url : '/CreditCard/123' }
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should add $resolved boolean field to the result object', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
|
||||
var cc = CreditCard.get({id: 123});
|
||||
|
||||
expect(cc.$resolved).toBe(false);
|
||||
|
||||
cc.$then(callback);
|
||||
expect(cc.$resolved).toBe(false);
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(cc.$resolved).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should set $resolved field to true when an error occurs', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard/123').respond(404, 'resource not found');
|
||||
var cc = CreditCard.get({id: 123});
|
||||
|
||||
cc.$then(null, callback);
|
||||
$httpBackend.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
expect(cc.$resolved).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('resource collection', function() {
|
||||
|
||||
it('should add promise $then method to the result object', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
|
||||
var ccs = CreditCard.query({key: 'value'});
|
||||
|
||||
ccs.$then(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
var response = callback.mostRecentCall.args[0];
|
||||
|
||||
expect(response).toEqualData({
|
||||
data: [{id: 1}, {id :2}],
|
||||
status: 200,
|
||||
config: {method: 'GET', data: undefined, url: '/CreditCard', params: {key: 'value'}},
|
||||
resource: [ { id : 1 }, { id : 2 } ]
|
||||
});
|
||||
expect(typeof response.resource[0].$save).toBe('function');
|
||||
expect(typeof response.resource[1].$save).toBe('function');
|
||||
});
|
||||
|
||||
|
||||
it('should keep $then around after promise resolution', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
|
||||
var ccs = CreditCard.query({key: 'value'});
|
||||
|
||||
ccs.$then(callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
var response = callback.mostRecentCall.args[0];
|
||||
|
||||
callback.reset();
|
||||
|
||||
ccs.$then(callback);
|
||||
$rootScope.$apply(); //flush async queue
|
||||
|
||||
expect(callback).toHaveBeenCalledOnceWith(response);
|
||||
});
|
||||
|
||||
|
||||
it('should allow promise chaining via $then method', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
|
||||
var ccs = CreditCard.query({key: 'value'});
|
||||
|
||||
ccs.$then(function(response) { return 'new value'; }).then(callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(callback).toHaveBeenCalledOnceWith('new value');
|
||||
});
|
||||
|
||||
|
||||
it('should allow error callback registration via $then method', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond(404, 'resource not found');
|
||||
var ccs = CreditCard.query({key: 'value'});
|
||||
|
||||
ccs.$then(null, callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
var response = callback.mostRecentCall.args[0];
|
||||
|
||||
expect(response).toEqualData({
|
||||
data : 'resource not found',
|
||||
status : 404,
|
||||
config : { method : 'GET', data : undefined, url : '/CreditCard', params: {key: 'value'}}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should add $resolved boolean field to the result object', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
|
||||
var ccs = CreditCard.query({key: 'value'}, callback);
|
||||
|
||||
expect(ccs.$resolved).toBe(false);
|
||||
|
||||
ccs.$then(callback);
|
||||
expect(ccs.$resolved).toBe(false);
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(ccs.$resolved).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should set $resolved field to true when an error occurs', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond(404, 'resource not found');
|
||||
var ccs = CreditCard.query({key: 'value'});
|
||||
|
||||
ccs.$then(null, callback);
|
||||
$httpBackend.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
expect(ccs.$resolved).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('failure mode', function() {
|
||||
var ERROR_CODE = 500,
|
||||
ERROR_RESPONSE = 'Server Error',
|
||||
@@ -407,4 +675,26 @@ describe("resource", function() {
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should transform request/response', function() {
|
||||
var Person = $resource('/Person/:id', {}, {
|
||||
save: {
|
||||
method: 'POST',
|
||||
params: {id: '@id'},
|
||||
transformRequest: function(data) {
|
||||
return angular.toJson({ __id: data.id });
|
||||
},
|
||||
transformResponse: function(data) {
|
||||
return { id: data.__id };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$httpBackend.expect('POST', '/Person/123', { __id: 123 }).respond({ __id: 456 });
|
||||
var person = new Person({id:123});
|
||||
person.$save();
|
||||
$httpBackend.flush();
|
||||
expect(person.id).toEqual(456);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
describe('ngBindHtml', function() {
|
||||
beforeEach(module('ngSanitize'));
|
||||
|
||||
it('should set html', inject(function($rootScope, $compile) {
|
||||
element = $compile('<div ng-bind-html="html"></div>')($rootScope);
|
||||
var element = $compile('<div ng-bind-html="html"></div>')($rootScope);
|
||||
$rootScope.html = '<div unknown>hello</div>';
|
||||
$rootScope.$digest();
|
||||
expect(angular.lowercase(element.html())).toEqual('<div>hello</div>');
|
||||
@@ -10,7 +13,7 @@ describe('ngBindHtml', function() {
|
||||
|
||||
|
||||
it('should reset html when value is null or undefined', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div ng-bind-html="html"></div>')($rootScope);
|
||||
var element = $compile('<div ng-bind-html="html"></div>')($rootScope);
|
||||
|
||||
angular.forEach([null, undefined, ''], function(val) {
|
||||
$rootScope.html = 'some val';
|
||||
|
||||
@@ -24,4 +24,11 @@ describe('linky', function() {
|
||||
expect(linky("send email to me@example.com, but")).
|
||||
toEqual('send email to <a href="mailto:me@example.com">me@example.com</a>, but');
|
||||
});
|
||||
|
||||
it('should handle target:', function() {
|
||||
expect(linky("http://example.com", "_blank")).
|
||||
toEqual('<a target="_blank" href="http://example.com">http://example.com</a>')
|
||||
expect(linky("http://example.com", "someNamedIFrame")).
|
||||
toEqual('<a target="someNamedIFrame" href="http://example.com">http://example.com</a>')
|
||||
});
|
||||
});
|
||||
|
||||
@@ -188,7 +188,7 @@ describe('HTML', function() {
|
||||
expect(html).toEqual('<div>');
|
||||
});
|
||||
|
||||
describe('explicitly dissallow', function() {
|
||||
describe('explicitly disallow', function() {
|
||||
it('should not allow attributes', function() {
|
||||
writer.start('div', {id:'a', name:'a', style:'a'});
|
||||
expect(html).toEqual('<div>');
|
||||
@@ -230,10 +230,11 @@ describe('HTML', function() {
|
||||
expect(isUri('https://abc')).toBeTruthy();
|
||||
expect(isUri('ftp://abc')).toBeTruthy();
|
||||
expect(isUri('mailto:me@example.com')).toBeTruthy();
|
||||
expect(isUri('tel:123-123-1234')).toBeTruthy();
|
||||
expect(isUri('#anchor')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not be UIR', function() {
|
||||
it('should not be URI', function() {
|
||||
expect(isUri('')).toBeFalsy();
|
||||
expect(isUri('javascript:alert')).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -270,6 +270,16 @@ describe("angular.scenario.dsl", function() {
|
||||
expect($root.futureError).toMatch(/did not match/);
|
||||
});
|
||||
|
||||
it('should fail to select an option that does not exist', function(){
|
||||
doc.append(
|
||||
'<select ng-model="test">' +
|
||||
' <option value=A>one</option>' +
|
||||
' <option value=B selected>two</option>' +
|
||||
'</select>'
|
||||
);
|
||||
$root.dsl.select('test').option('three');
|
||||
expect($root.futureError).toMatch(/not found/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Element', function() {
|
||||
@@ -305,6 +315,58 @@ describe("angular.scenario.dsl", function() {
|
||||
dealoc(elm);
|
||||
});
|
||||
|
||||
it('should execute dblclick', function() {
|
||||
var clicked;
|
||||
// Hash is important, otherwise we actually
|
||||
// go to a different page and break the runner
|
||||
doc.append('<a href="#"></a>');
|
||||
doc.find('a').dblclick(function() {
|
||||
clicked = true;
|
||||
});
|
||||
$root.dsl.element('a').dblclick();
|
||||
});
|
||||
|
||||
it('should navigate page if dblclick on anchor', function() {
|
||||
expect($window.location).not.toEqual('#foo');
|
||||
doc.append('<a href="#foo"></a>');
|
||||
$root.dsl.element('a').dblclick();
|
||||
expect($window.location).toMatch(/#foo$/);
|
||||
});
|
||||
|
||||
it('should not navigate if dblclick event was cancelled', function() {
|
||||
var initLocation = $window.location,
|
||||
elm = jqLite('<a href="#foo"></a>');
|
||||
|
||||
doc.append(elm);
|
||||
elm.bind('dblclick', function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
$root.dsl.element('a').dblclick();
|
||||
expect($window.location).toBe(initLocation);
|
||||
dealoc(elm);
|
||||
});
|
||||
|
||||
it('should execute mouseover', function() {
|
||||
var mousedOver;
|
||||
doc.append('<div></div>');
|
||||
doc.find('div').mouseover(function() {
|
||||
mousedOver = true;
|
||||
});
|
||||
$root.dsl.element('div').mouseover();
|
||||
expect(mousedOver).toBe(true);
|
||||
});
|
||||
|
||||
it('should bubble up the mouseover event', function() {
|
||||
var mousedOver;
|
||||
doc.append('<div id="outer"><div id="inner"></div></div>');
|
||||
doc.find('#outer').mouseover(function() {
|
||||
mousedOver = true;
|
||||
});
|
||||
$root.dsl.element('#inner').mouseover();
|
||||
expect(mousedOver).toBe(true);
|
||||
});
|
||||
|
||||
it('should count matching elements', function() {
|
||||
doc.append('<span></span><span></span>');
|
||||
$root.dsl.element('span').count();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
describe('angular.scenario.output.json', function() {
|
||||
describe('angular.scenario.output.xml', function() {
|
||||
var output, context;
|
||||
var runner, model, $window;
|
||||
var spec, step;
|
||||
@@ -33,4 +33,17 @@ describe('angular.scenario.output.json', function() {
|
||||
expect(context.find('it').attr('status')).toEqual('success');
|
||||
expect(context.find('it step').attr('status')).toEqual('success');
|
||||
});
|
||||
|
||||
it('should output errors to the XML', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepFailure', spec, step, 'error reason');
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
runner.emit('RunnerEnd');
|
||||
|
||||
expect(context.find('it').attr('status')).toEqual('failure');
|
||||
expect(context.find('it step').attr('status')).toEqual('failure');
|
||||
expect(context.find('it step').text()).toEqual('error reason');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ var util = require('util');
|
||||
|
||||
|
||||
var MAX_LENGTH = 70;
|
||||
var PATTERN = /^(\w*)(\(([\w\$\.\-\*/]*)\))?\: (.*)$/;
|
||||
var PATTERN = /^(?:fixup!\s*)?(\w*)(\(([\w\$\.\-\*/]*)\))?\: (.*)$/;
|
||||
var IGNORED = /^WIP\:/;
|
||||
var TYPES = {
|
||||
feat: true,
|
||||
@@ -51,7 +51,7 @@ var validateMessage = function(message) {
|
||||
var match = PATTERN.exec(message);
|
||||
|
||||
if (!match) {
|
||||
error('does not match "<type>(<scope>): <subject>" !');
|
||||
error('does not match "<type>(<scope>): <subject>" ! was: ' + message);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ describe('validate-commit-msg.js', function() {
|
||||
describe('validateMessage', function() {
|
||||
|
||||
it('should be valid', function() {
|
||||
expect(m.validateMessage('fixup! fix($compile): something')).toBe(VALID);
|
||||
expect(m.validateMessage('fix($compile): something')).toBe(VALID);
|
||||
expect(m.validateMessage('feat($location): something')).toBe(VALID);
|
||||
expect(m.validateMessage('docs($filter): something')).toBe(VALID);
|
||||
@@ -50,7 +51,7 @@ describe('validate-commit-msg.js', function() {
|
||||
var msg = 'not correct format';
|
||||
|
||||
expect(m.validateMessage(msg)).toBe(INVALID);
|
||||
expect(errors).toEqual(['INVALID COMMIT MSG: does not match "<type>(<scope>): <subject>" !']);
|
||||
expect(errors).toEqual(['INVALID COMMIT MSG: does not match "<type>(<scope>): <subject>" ! was: not correct format']);
|
||||
});
|
||||
|
||||
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
# AngularJS build config file
|
||||
---
|
||||
version: 1.0.4
|
||||
codename: bewildering-hair
|
||||
stable: 1.0.3
|
||||
version: 1.1.3
|
||||
codename: radioactive-gargle
|
||||
stable: 1.0.4
|
||||
|
||||
Reference in New Issue
Block a user