Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ce49d4d61b | |||
| 69ee593fd2 | |||
| 39ddef6829 | |||
| 11aedbd741 | |||
| e4d1e12f7f | |||
| 9c2b32d6f3 | |||
| 4b3a590b00 | |||
| 7f50e97628 | |||
| e5ee6123fc | |||
| 923b6aba0d | |||
| 955e20eb61 | |||
| 286a40751c | |||
| eca0535457 | |||
| 7ace77a5d7 | |||
| 7f362af153 | |||
| 35dee2abac | |||
| 29c926201d | |||
| 9b6852a8c9 | |||
| 53efc8d5d0 | |||
| 0b8461c9cb | |||
| abd8e2a9eb | |||
| bc4dadc894 | |||
| ec53089bb1 | |||
| 7bb50e2823 | |||
| 632b2ddd34 | |||
| 7caad2205a | |||
| 9bf5f89659 | |||
| f41ca4a53e | |||
| 0bcd0872d8 | |||
| 6ec5946094 | |||
| 784ea8e160 | |||
| 6ec53bdfd3 | |||
| d1b6480dcf | |||
| ef6fed3ef8 | |||
| 473dee5786 | |||
| 779e3f6b5f | |||
| 71bca00651 | |||
| 9a9fce0abc | |||
| 939ca37cfe | |||
| 4ae8a2a4b6 | |||
| 113d3954b9 | |||
| 7cb5983750 | |||
| 2b149ca6d4 | |||
| e77866c18c | |||
| 0dc6418d20 | |||
| 837a077578 | |||
| adf91fe6ee | |||
| dea1c0d34c | |||
| f533acc9aa | |||
| d01cae2a0d | |||
| d015c8a80b | |||
| 8d2717146b | |||
| 3d598dae64 | |||
| 0a58986f52 | |||
| 6e69b85f9a | |||
| 7a9e336028 | |||
| 9b8df52aa9 | |||
| 7fab29fbe1 | |||
| 1a47fcbb8b | |||
| ffd4dab611 | |||
| 13edaa95c7 | |||
| 47b1f54bba | |||
| cdc7280dd3 | |||
| 0bb282bc6d | |||
| fdb09ef858 | |||
| 5e69cb2f9f | |||
| 5c2da38e3f | |||
| 3a799df0f1 | |||
| 511c765a44 | |||
| bf55d76d27 | |||
| c9efc80cd0 | |||
| fa15f2a6df | |||
| c023a0bfbb | |||
| b470e005e8 | |||
| c959191882 | |||
| e4adebd07a | |||
| ac94f6125f | |||
| c5686c5271 | |||
| 7fdb54d12b | |||
| 869008140a | |||
| 26ee32ec79 | |||
| a06193f97b | |||
| ba9dee170c | |||
| d4b60ada1e | |||
| b839f73ad0 | |||
| aee32931fd | |||
| 7ee5f46bbc | |||
| 2b97854bf4 | |||
| 316ee8f7ca | |||
| 2e18f44fcd | |||
| c85d064ecf | |||
| 7b9b82281a | |||
| aab632b330 | |||
| 4c8d8ad508 | |||
| 3a8f3dc9ea | |||
| c139e68d99 |
+9
-2
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"excludeFiles": ["src/ngLocale/**"],
|
||||
"disallowKeywords": ["with"],
|
||||
"disallowKeywordsOnNewLine": ["else"],
|
||||
"disallowMixedSpacesAndTabs": true,
|
||||
"disallowMultipleLineStrings": true,
|
||||
"disallowNewlineBeforeBlockStatements": true,
|
||||
@@ -11,6 +12,7 @@
|
||||
"disallowSpacesInAnonymousFunctionExpression": {
|
||||
"beforeOpeningRoundBrace": true
|
||||
},
|
||||
"disallowSpacesInCallExpression": true,
|
||||
"disallowSpacesInFunctionDeclaration": {
|
||||
"beforeOpeningRoundBrace": true
|
||||
},
|
||||
@@ -18,6 +20,11 @@
|
||||
"beforeOpeningRoundBrace": true
|
||||
},
|
||||
"disallowSpacesInsideArrayBrackets": true,
|
||||
"requireSpaceBeforeKeywords": [
|
||||
"else",
|
||||
"while",
|
||||
"catch"
|
||||
],
|
||||
"disallowSpacesInsideParentheses": true,
|
||||
"disallowTrailingComma": true,
|
||||
"disallowTrailingWhitespace": true,
|
||||
@@ -33,9 +40,9 @@
|
||||
"afterConsequent": true,
|
||||
"beforeAlternate": true
|
||||
},
|
||||
"requireSpacesInForStatement": true,
|
||||
"requireSpacesInFunction": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
},
|
||||
"validateLineBreaks": "LF",
|
||||
"validateParameterSeparator": ", "
|
||||
"validateLineBreaks": "LF"
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,10 +4,10 @@
|
||||
// that correct the existing code base issues and make the new check pass.
|
||||
|
||||
{
|
||||
"validateParameterSeparator": ", ", // Re-assert this rule when JSCS allows multiple spaces
|
||||
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
|
||||
"disallowImplicitTypeConversion": ["string"],
|
||||
"disallowMultipleLineBreaks": true,
|
||||
"disallowKeywordsOnNewLine": ["else"],
|
||||
"validateJSDoc": {
|
||||
"checkParamNames": true,
|
||||
"requireParamTypes": true
|
||||
|
||||
@@ -9,9 +9,11 @@ branches:
|
||||
env:
|
||||
matrix:
|
||||
- JOB=unit BROWSER_PROVIDER=saucelabs
|
||||
- JOB=docs-e2e BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
|
||||
- JOB=unit BROWSER_PROVIDER=browserstack
|
||||
- JOB=docs-e2e BROWSER_PROVIDER=browserstack
|
||||
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack
|
||||
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack
|
||||
global:
|
||||
@@ -22,6 +24,10 @@ env:
|
||||
- LOGS_DIR=/tmp/angular-build/logs
|
||||
- BROWSER_PROVIDER_READY_FILE=/tmp/browsersprovider-tunnel-ready
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: "JOB=unit BROWSER_PROVIDER=browserstack"
|
||||
|
||||
install:
|
||||
# - npm config set registry http://23.251.144.68
|
||||
# Disable the spinner, it looks bad on Travis
|
||||
|
||||
+381
@@ -1,3 +1,384 @@
|
||||
<a name="1.4.0-beta.3"></a>
|
||||
# 1.4.0-beta.3 substance-mimicry (2015-02-02)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- do not initialize optional '&' binding if attribute not specified
|
||||
([6a38dbfd](https://github.com/angular/angular.js/commit/6a38dbfd3c34c8f9efff503d17eb3cbeb666d422),
|
||||
[#6404](https://github.com/angular/angular.js/issues/6404), [#9216](https://github.com/angular/angular.js/issues/9216))
|
||||
- respect return value from controller constructor
|
||||
([62d514b0](https://github.com/angular/angular.js/commit/62d514b06937cc7dd86e973ea11165c88343b42d))
|
||||
- **$controller:** throw better error when controller expression is bad
|
||||
([dda65e99](https://github.com/angular/angular.js/commit/dda65e992b72044c0fa0c8f5f33184028c0e3ad7),
|
||||
[#10875](https://github.com/angular/angular.js/issues/10875), [#10910](https://github.com/angular/angular.js/issues/10910))
|
||||
- **$parse:**
|
||||
- handle null targets at assign
|
||||
([2e5a7e52](https://github.com/angular/angular.js/commit/2e5a7e52a0385575bbb55a801471b009afafeca3))
|
||||
- remove references to last arguments to a fn call
|
||||
([e61eae1b](https://github.com/angular/angular.js/commit/e61eae1b1f2351c51bcfe4142749a4e68a2806ff),
|
||||
[#10894](https://github.com/angular/angular.js/issues/10894))
|
||||
- **a:** don't reload if there is only a name attribute
|
||||
([d729fcf0](https://github.com/angular/angular.js/commit/d729fcf030be1d3ef37196d36ea3bf3249ee3318),
|
||||
[#6273](https://github.com/angular/angular.js/issues/6273), [#10880](https://github.com/angular/angular.js/issues/10880))
|
||||
- **angular.copy:** support copying `TypedArray`s
|
||||
([aa0f6449](https://github.com/angular/angular.js/commit/aa0f64496a66d2a5d1a4d033f2eb075a8b084a78),
|
||||
[#10745](https://github.com/angular/angular.js/issues/10745))
|
||||
- **filter:** format timezone correctly in the case that UTC timezone is used
|
||||
([8c469191](https://github.com/angular/angular.js/commit/8c46919199090a05634789774124b38983430c76),
|
||||
[#9359](https://github.com/angular/angular.js/issues/9359))
|
||||
- **ngRoute:** dont duplicate optional params into query
|
||||
([27bf2ce4](https://github.com/angular/angular.js/commit/27bf2ce40c5adfb1494d69c9d0ac9cf433834a12),
|
||||
[#10689](https://github.com/angular/angular.js/issues/10689))
|
||||
- **ngScenario:** allow ngScenario to handle lazy-loaded and manually bootstrapped applications
|
||||
([c69caa7b](https://github.com/angular/angular.js/commit/c69caa7beee4e920f8f587eb3e943be99864a14f),
|
||||
[#10723](https://github.com/angular/angular.js/issues/10723))
|
||||
- **validators:** maxlength should use viewValue for $isEmpty
|
||||
([bfcf9946](https://github.com/angular/angular.js/commit/bfcf9946e16d21b55dde50d4d21c71c898b10215),
|
||||
[#10898](https://github.com/angular/angular.js/issues/10898))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** allow using bindToController as object, support both new/isolate scopes
|
||||
([35498d70](https://github.com/angular/angular.js/commit/35498d7045ba9138016464a344e2c145ce5264c1),
|
||||
[#10420](https://github.com/angular/angular.js/issues/10420), [#10467](https://github.com/angular/angular.js/issues/10467))
|
||||
- **filter:** support conversion to timezone other than UTC
|
||||
([c6d8512a](https://github.com/angular/angular.js/commit/c6d8512a1d7345516d1bd9a039d81821b9518bff),
|
||||
[#10858](https://github.com/angular/angular.js/issues/10858))
|
||||
- **ngMocks:** cleanup $inject annotations after each test
|
||||
([0baa17a3](https://github.com/angular/angular.js/commit/0baa17a3b7ad2b242df2b277b81cebdf75b04287))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$scope:** Add a property $$watchersCount to scope
|
||||
([c1500ea7](https://github.com/angular/angular.js/commit/c1500ea775c4cb130088b7d5bb5fb872bda50bae))
|
||||
- **$parse** new and more performant parser
|
||||
([0d42426](https://github.com/angular/angular.js/commit/0d424263ead16635afb582affab2b147f8e71626))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:** due to [6a38dbfd](https://github.com/angular/angular.js/commit/6a38dbfd3c34c8f9efff503d17eb3cbeb666d422),
|
||||
Previously, '&' expressions would always set up a function in the isolate scope. Now, if the binding
|
||||
is marked as optional and the attribute is not specified, no function will be added to the isolate scope.
|
||||
|
||||
|
||||
<a name="1.3.12"></a>
|
||||
# 1.3.12 outlandish-knitting (2015-02-02)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$controller:** throw better error when controller expression is bad
|
||||
([632b2ddd](https://github.com/angular/angular.js/commit/632b2ddd34c07b3b5a207bd83ca3a5e6e613e63b),
|
||||
[#10875](https://github.com/angular/angular.js/issues/10875), [#10910](https://github.com/angular/angular.js/issues/10910))
|
||||
- **$parse:** remove references to last arguments to a fn call
|
||||
([7caad220](https://github.com/angular/angular.js/commit/7caad2205a6e9927890192a3638f55532bdaaf75),
|
||||
[#10894](https://github.com/angular/angular.js/issues/10894))
|
||||
- **ngRoute:** dont duplicate optional params into query
|
||||
([f41ca4a5](https://github.com/angular/angular.js/commit/f41ca4a53ed53f172fb334911be56e42aad58794),
|
||||
[#10689](https://github.com/angular/angular.js/issues/10689))
|
||||
- **ngScenario:** Allow ngScenario to handle lazy-loaded and manually bootstrapped applications
|
||||
([0bcd0872](https://github.com/angular/angular.js/commit/0bcd0872d8d2e37e6cb7aa5bc5cb0c742b4294f9),
|
||||
[#10723](https://github.com/angular/angular.js/issues/10723))
|
||||
- **validators:** maxlength should use viewValue for $isEmpty
|
||||
([abd8e2a9](https://github.com/angular/angular.js/commit/abd8e2a9eb2d21ac67989c2f7b64c4c6547a1585),
|
||||
[#10898](https://github.com/angular/angular.js/issues/10898))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngMocks:** cleanup $inject annotations after each test
|
||||
([6ec59460](https://github.com/angular/angular.js/commit/6ec5946094ee92b820bbacc886fa2367715e60b4))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.4.0-beta.2"></a>
|
||||
# 1.4.0-beta.2 holographic-rooster (2015-01-26)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$location:** don't rewrite when link is shift-clicked
|
||||
([8b33de6f](https://github.com/angular/angular.js/commit/8b33de6fd0ec0eb785fed697f062763b5c1d8d23),
|
||||
[#9904](https://github.com/angular/angular.js/issues/9904), [#9906](https://github.com/angular/angular.js/issues/9906))
|
||||
- **$templateRequest:** cache downloaded templates as strings
|
||||
([b3a9bd3a](https://github.com/angular/angular.js/commit/b3a9bd3ae043e3042ea7ccfe08e3b36a84feb35e),
|
||||
[#10630](https://github.com/angular/angular.js/issues/10630), [#10646](https://github.com/angular/angular.js/issues/10646))
|
||||
- **filterFilter:** throw error if input is not an array
|
||||
([cea8e751](https://github.com/angular/angular.js/commit/cea8e75144e6910b806b63a6ec2a6d118316fddd),
|
||||
[#9992](https://github.com/angular/angular.js/issues/9992), [#10352](https://github.com/angular/angular.js/issues/10352))
|
||||
- **htmlAnchorDirective:**
|
||||
- remove "element !== target element" check
|
||||
([2958cd30](https://github.com/angular/angular.js/commit/2958cd308b5ebaf223a3e5df3fb5bf0f23408447),
|
||||
[#10866](https://github.com/angular/angular.js/issues/10866))
|
||||
- don't add event listener if replaced, ignore event if target is different element
|
||||
([b146af11](https://github.com/angular/angular.js/commit/b146af11271de8fa4c51c6db87df104269f41a33),
|
||||
[#4262](https://github.com/angular/angular.js/issues/4262), [#10849](https://github.com/angular/angular.js/issues/10849))
|
||||
- **ngPluralize:** fix wrong text content when count is null/undefined
|
||||
([3228d3b4](https://github.com/angular/angular.js/commit/3228d3b4991af681e57de5ab079c1e1c11cf35cb),
|
||||
[#10836](https://github.com/angular/angular.js/issues/10836), [#10841](https://github.com/angular/angular.js/issues/10841))
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **filterFilter:** due to [cea8e751](https://github.com/angular/angular.js/commit/cea8e75144e6910b806b63a6ec2a6d118316fddd),
|
||||
Previously, the filter was not applied if used with a non array.
|
||||
Now, it throws an error. This can be worked around by converting an object to an array, using
|
||||
a filter such as https://github.com/petebacondarwin/angular-toArrayFilter
|
||||
|
||||
Closes #9992
|
||||
Closes #10352
|
||||
|
||||
|
||||
<a name="1.3.11"></a>
|
||||
# 1.3.11 spiffy-manatee (2015-01-26)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$location:** don't rewrite when link is shift-clicked
|
||||
([939ca37c](https://github.com/angular/angular.js/commit/939ca37cfe5f6fc35b09b6705caabd1fcc3cf9d3),
|
||||
[#9904](https://github.com/angular/angular.js/issues/9904), [#9906](https://github.com/angular/angular.js/issues/9906))
|
||||
- **htmlAnchorDirective:**
|
||||
- remove "element !== target element" check
|
||||
([779e3f6b](https://github.com/angular/angular.js/commit/779e3f6b5f8d2550e758cb0c5f64187ba8e00e29),
|
||||
[#10866](https://github.com/angular/angular.js/issues/10866))
|
||||
- don't add event listener if replaced, ignore event if target is different element
|
||||
([837a0775](https://github.com/angular/angular.js/commit/837a077578081bbd07863bef85241537d19fa652),
|
||||
[#4262](https://github.com/angular/angular.js/issues/4262), [#10849](https://github.com/angular/angular.js/issues/10849))
|
||||
|
||||
|
||||
<a name="1.4.0-beta.1"></a>
|
||||
# 1.4.0-beta.1 trepidatious-salamander (2015-01-20)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** ensure no transitions are applied when an empty inline style object is provided
|
||||
([0db5b21b](https://github.com/angular/angular.js/commit/0db5b21b1d09431535e0c0bf8ac63d4b5b24d349),
|
||||
[#10613](https://github.com/angular/angular.js/issues/10613), [#10770](https://github.com/angular/angular.js/issues/10770))
|
||||
- **$compile:** support class directives on SVG elements
|
||||
([23c8a90d](https://github.com/angular/angular.js/commit/23c8a90d22f7c7b41b5a756b89498ffac828980a),
|
||||
[#10736](https://github.com/angular/angular.js/issues/10736), [#10756](https://github.com/angular/angular.js/issues/10756))
|
||||
- **form:** clean up success state of controls when they are removed
|
||||
([2408f2de](https://github.com/angular/angular.js/commit/2408f2ded5ead6e678c241e38ef474c1fadff92b),
|
||||
[#10509](https://github.com/angular/angular.js/issues/10509))
|
||||
- **ngController:** allow bound constructor fns as controllers
|
||||
([d17fbc38](https://github.com/angular/angular.js/commit/d17fbc3862e0a2e646db1222f184dbe663da4a1f),
|
||||
[#10784](https://github.com/angular/angular.js/issues/10784), [#10790](https://github.com/angular/angular.js/issues/10790))
|
||||
- **ngRepeat:** do not sort object keys alphabetically
|
||||
([c260e738](https://github.com/angular/angular.js/commit/c260e7386391877625eda086480de73e8a0ba921),
|
||||
[#6210](https://github.com/angular/angular.js/issues/6210), [#10538](https://github.com/angular/angular.js/issues/10538))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$http:** provide a config object as an argument to header functions
|
||||
([d435464c](https://github.com/angular/angular.js/commit/d435464c51d3912f56cfc830d86bfc64a1578327),
|
||||
[#7235](https://github.com/angular/angular.js/issues/7235), [#10622](https://github.com/angular/angular.js/issues/10622))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **ngRepeat:** due to [c260e738](https://github.com/angular/angular.js/commit/c260e7386391877625eda086480de73e8a0ba921),
|
||||
|
||||
|
||||
Previously, the order of items when using ngRepeat to iterate
|
||||
over object properties was guaranteed to be consistent by sorting the
|
||||
keys into alphabetic order.
|
||||
|
||||
Now, the order of the items is browser dependent based on the order returned
|
||||
from iterating over the object using the `for key in obj` syntax.
|
||||
|
||||
It seems that browsers generally follow the strategy of providing
|
||||
keys in the order in which they were defined, although there are exceptions
|
||||
when keys are deleted and reinstated. See
|
||||
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues
|
||||
|
||||
The best approach is to convert Objects into Arrays by a filter such as
|
||||
https://github.com/petebacondarwin/angular-toArrayFilter
|
||||
or some other mechanism, and then sort them manually in the order you need.
|
||||
|
||||
Closes #6210
|
||||
Closes #10538
|
||||
|
||||
|
||||
|
||||
<a name="1.3.10"></a>
|
||||
# 1.3.10 heliotropic-sundial (2015-01-20)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** ensure no transitions are applied when an empty inline style object is provided
|
||||
([9b8df52a](https://github.com/angular/angular.js/commit/9b8df52aa960b9b6288fc150d55ea2e35f56555e),
|
||||
[#10613](https://github.com/angular/angular.js/issues/10613), [#10770](https://github.com/angular/angular.js/issues/10770))
|
||||
- **$compile:** support class directives on SVG elements
|
||||
([7a9e3360](https://github.com/angular/angular.js/commit/7a9e3360284d58197a1fe34de57f5e0f6d1f4a76),
|
||||
[#10736](https://github.com/angular/angular.js/issues/10736), [#10756](https://github.com/angular/angular.js/issues/10756))
|
||||
- **form:** clean up success state of controls when they are removed
|
||||
([cdc7280d](https://github.com/angular/angular.js/commit/cdc7280dd3d5a2ded784c06dd55fe36c2053fb6f),
|
||||
[#10509](https://github.com/angular/angular.js/issues/10509))
|
||||
- **ngController:** allow bound constructor fns as controllers
|
||||
([d015c8a8](https://github.com/angular/angular.js/commit/d015c8a80b28754633c846fc50d11c9437519486),
|
||||
[#10784](https://github.com/angular/angular.js/issues/10784), [#10790](https://github.com/angular/angular.js/issues/10790))
|
||||
|
||||
|
||||
|
||||
<a name="1.4.0-beta.0"></a>
|
||||
# 1.4.0-beta.0 photonic-umbrakinesis (2015-01-13)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$location:** support right button click on anchors in firefox
|
||||
([aa798f12](https://github.com/angular/angular.js/commit/aa798f123658cb78b5581513d26577016195cafe),
|
||||
[#7984](https://github.com/angular/angular.js/issues/7984))
|
||||
- **$templateRequest:** propagate HTTP status on failed requests
|
||||
([e24f22bd](https://github.com/angular/angular.js/commit/e24f22bdb1740388938d58778aa24d307a79a796),
|
||||
[#10514](https://github.com/angular/angular.js/issues/10514), [#10628](https://github.com/angular/angular.js/issues/10628))
|
||||
- **dateFilter:** ignore invalid dates
|
||||
([1334b8c8](https://github.com/angular/angular.js/commit/1334b8c8326b93e0ca016c85516627900c7a9fd3),
|
||||
[#10640](https://github.com/angular/angular.js/issues/10640))
|
||||
- **filterFilter:** use isArray() to determine array type
|
||||
([a01ce6b8](https://github.com/angular/angular.js/commit/a01ce6b81c197b0a4a1057981e8e9c1b74f37587),
|
||||
[#10621](https://github.com/angular/angular.js/issues/10621))
|
||||
- **ngChecked:** ensure that ngChecked doesn't interfere with ngModel
|
||||
([e079111b](https://github.com/angular/angular.js/commit/e079111b33bf36be21c0941718b41cc9ca67bea0),
|
||||
[#10662](https://github.com/angular/angular.js/issues/10662), [#10664](https://github.com/angular/angular.js/issues/10664))
|
||||
- **ngClass:** handle multi-class definitions as an element of an array
|
||||
([e1132f53](https://github.com/angular/angular.js/commit/e1132f53b03a5a71aa9b6eded24d64e3bc83929b),
|
||||
[#8578](https://github.com/angular/angular.js/issues/8578), [#10651](https://github.com/angular/angular.js/issues/10651))
|
||||
- **ngModelOptions:** allow sharing options between multiple inputs
|
||||
([9c9c6b3f](https://github.com/angular/angular.js/commit/9c9c6b3fe4edfe78ae275c413ee3eefb81f1ebf6),
|
||||
[#10667](https://github.com/angular/angular.js/issues/10667))
|
||||
- **ngOptions:**
|
||||
- support one-time binding on the option values
|
||||
([ba90261b](https://github.com/angular/angular.js/commit/ba90261b7586b519483883800ea876510faf5c21),
|
||||
[#10687](https://github.com/angular/angular.js/issues/10687), [#10694](https://github.com/angular/angular.js/issues/10694))
|
||||
- prevent infinite digest if track by expression is stable
|
||||
([fc21db8a](https://github.com/angular/angular.js/commit/fc21db8a15545fad53124fc941b3c911a8d57067),
|
||||
[#9464](https://github.com/angular/angular.js/issues/9464))
|
||||
- update model if selected option is removed
|
||||
([933591d6](https://github.com/angular/angular.js/commit/933591d69cee2c5580da1d8522ba90a7d924da0e),
|
||||
[#7736](https://github.com/angular/angular.js/issues/7736))
|
||||
- ensure that the correct option is selected when options are loaded async
|
||||
([7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
[#8019](https://github.com/angular/angular.js/issues/8019), [#9714](https://github.com/angular/angular.js/issues/9714), [#10639](https://github.com/angular/angular.js/issues/10639))
|
||||
- **ngPluralize:** generate a warning when using a not defined rule
|
||||
([c66b4b6a](https://github.com/angular/angular.js/commit/c66b4b6a133f7215d50c23db516986cfc1f0a985))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$filter:** display Infinity symbol when number is Infinity
|
||||
([51d67742](https://github.com/angular/angular.js/commit/51d6774286202b55ade402ca097e417e70fd546b),
|
||||
[#10421](https://github.com/angular/angular.js/issues/10421))
|
||||
- **$timeout:** allow `fn` to be an optional parameter
|
||||
([5a603023](https://github.com/angular/angular.js/commit/5a60302389162c6ef45f311c1aaa65a00d538c66),
|
||||
[#9176](https://github.com/angular/angular.js/issues/9176))
|
||||
- **limitTo:** ignore limit when invalid
|
||||
([a3c3bf33](https://github.com/angular/angular.js/commit/a3c3bf3332e5685dc319c46faef882cb6ac246e1),
|
||||
[#10510](https://github.com/angular/angular.js/issues/10510))
|
||||
- **ngMock/$exceptionHandler:** log errors when rethrowing
|
||||
([deb3cb4d](https://github.com/angular/angular.js/commit/deb3cb4daef0054457bd9fb8995829fff0e8f1e4),
|
||||
[#10540](https://github.com/angular/angular.js/issues/10540), [#10564](https://github.com/angular/angular.js/issues/10564))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **ngStyleDirective:** use $watchCollection
|
||||
([8928d023](https://github.com/angular/angular.js/commit/8928d0234551a272992d0eccef73b3ad6cb8bfd1),
|
||||
[#10535](https://github.com/angular/angular.js/issues/10535))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **limitTo:** due to [a3c3bf33](https://github.com/angular/angular.js/commit/a3c3bf3332e5685dc319c46faef882cb6ac246e1),
|
||||
limitTo changed behavior when limit value is invalid.
|
||||
Instead of returning empty object/array it returns unchanged input.
|
||||
|
||||
|
||||
- **ngOptions:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
|
||||
|
||||
When using `ngOptions`: the directive applies a surrogate key as the value of the `<option>` element.
|
||||
This commit changes the actual string used as the surrogate key. We now store a string that is computed
|
||||
by calling `hashKey` on the item in the options collection; previously it was the index or key of the
|
||||
item in the collection.
|
||||
|
||||
(This is in keeping with the way that the unknown option value is represented in the select directive.)
|
||||
|
||||
Before you might have seen:
|
||||
|
||||
```
|
||||
<select ng-model="x" ng-option="i in items">
|
||||
<option value="1">a</option>
|
||||
<option value="2">b</option>
|
||||
<option value="3">c</option>
|
||||
<option value="4">d</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
Now it will be something like:
|
||||
|
||||
```
|
||||
<select ng-model="x" ng-option="i in items">
|
||||
<option value="string:a">a</option>
|
||||
<option value="string:b">b</option>
|
||||
<option value="string:c">c</option>
|
||||
<option value="string:d">d</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
If your application code relied on this value, which it shouldn't, then you will need to modify your
|
||||
application to accommodate this. You may find that you can use the `track by` feaure of `ngOptions`
|
||||
as this provides the ability to specify the key that is stored.
|
||||
|
||||
- **ngOptions:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
|
||||
When iterating over an object's properties using the `(key, value) in obj` syntax
|
||||
the order of the elements used to be sorted alphabetically. This was an artificial
|
||||
attempt to create a deterministic ordering since browsers don't guarantee the order.
|
||||
But in practice this is not what people want and so this change iterates over properties
|
||||
in the order they are returned by Object.keys(obj), which is almost always the order
|
||||
in which the properties were defined.
|
||||
|
||||
|
||||
|
||||
<a name="1.3.9"></a>
|
||||
# 1.3.9 multidimensional-awareness (2015-01-13)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$parse:** allow use of locals in assignments
|
||||
([86900814](https://github.com/angular/angular.js/commit/869008140a96e0e9e0d9774cc2e5fdd66ada7ba9))
|
||||
- **filterFilter:** use isArray() to determine array type
|
||||
([d4b60ada](https://github.com/angular/angular.js/commit/d4b60ada1ecff5afdb3210caa44e149e9f3d4c1b),
|
||||
[#10621](https://github.com/angular/angular.js/issues/10621))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngMock/$exceptionHandler:** log errors when rethrowing
|
||||
([2b97854b](https://github.com/angular/angular.js/commit/2b97854bf4786fe8579974e2b9d6b4adee8a3dc3),
|
||||
[#10540](https://github.com/angular/angular.js/issues/10540), [#10564](https://github.com/angular/angular.js/issues/10564))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **ngStyleDirective:** use $watchCollection
|
||||
([4c8d8ad5](https://github.com/angular/angular.js/commit/4c8d8ad5083d9dd17c0b8480339d5f95943f1b71),
|
||||
[#10535](https://github.com/angular/angular.js/issues/10535))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.3.8"></a>
|
||||
# 1.3.8 prophetic-narwhal (2014-12-19)
|
||||
|
||||
|
||||
+3
-3
@@ -87,7 +87,7 @@ Before you submit your pull request consider the following guidelines:
|
||||
that relates to your submission. You don't want to duplicate effort.
|
||||
* Please sign our [Contributor License Agreement (CLA)](#cla) before sending pull
|
||||
requests. We cannot accept code without this.
|
||||
* Make your changes in a new git branch
|
||||
* Make your changes in a new git branch:
|
||||
|
||||
```shell
|
||||
git checkout -b my-fix-branch master
|
||||
@@ -107,7 +107,7 @@ Before you submit your pull request consider the following guidelines:
|
||||
```
|
||||
Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
|
||||
|
||||
* Build your changes locally to ensure all the tests pass
|
||||
* Build your changes locally to ensure all the tests pass:
|
||||
|
||||
```shell
|
||||
grunt test
|
||||
@@ -120,7 +120,7 @@ Before you submit your pull request consider the following guidelines:
|
||||
```
|
||||
|
||||
* In GitHub, send a pull request to `angular:master`.
|
||||
* If we suggest changes then
|
||||
* If we suggest changes then:
|
||||
* Make the required updates.
|
||||
* Re-run the Angular test suite to ensure tests are still passing.
|
||||
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
|
||||
|
||||
+2
-2
@@ -316,9 +316,9 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'jscs', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:protractor']);
|
||||
grunt.registerTask('test:jqlite', 'Run the unit tests with Karma' , ['tests:jqlite']);
|
||||
grunt.registerTask('test:jquery', 'Run the jQuery unit tests with Karma', ['tests:jquery']);
|
||||
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['tests:modules']);
|
||||
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['build', 'tests:modules']);
|
||||
grunt.registerTask('test:docs', 'Run the doc-page tests with Karma', ['package', 'tests:docs']);
|
||||
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['tests:jqlite', 'tests:jquery', 'tests:modules']);
|
||||
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['test:jqlite', 'test:jquery', 'test:modules']);
|
||||
grunt.registerTask('test:protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'protractor:normal']);
|
||||
grunt.registerTask('test:travis-protractor', 'Run the end to end tests with Protractor for Travis CI builds', ['connect:testserver', 'protractor:travis']);
|
||||
grunt.registerTask('test:ci-protractor', 'Run the end to end tests with Protractor for Jenkins CI builds', ['webdriver', 'connect:testserver', 'protractor:jenkins']);
|
||||
|
||||
Vendored
+5
-1
@@ -53,6 +53,7 @@ var angularFiles = {
|
||||
'src/ng/directive/form.js',
|
||||
'src/ng/directive/input.js',
|
||||
'src/ng/directive/ngBind.js',
|
||||
'src/ng/directive/ngChange.js',
|
||||
'src/ng/directive/ngClass.js',
|
||||
'src/ng/directive/ngCloak.js',
|
||||
'src/ng/directive/ngController.js',
|
||||
@@ -61,6 +62,8 @@ var angularFiles = {
|
||||
'src/ng/directive/ngIf.js',
|
||||
'src/ng/directive/ngInclude.js',
|
||||
'src/ng/directive/ngInit.js',
|
||||
'src/ng/directive/ngList.js',
|
||||
'src/ng/directive/ngModel.js',
|
||||
'src/ng/directive/ngNonBindable.js',
|
||||
'src/ng/directive/ngPluralize.js',
|
||||
'src/ng/directive/ngRepeat.js',
|
||||
@@ -70,7 +73,8 @@ var angularFiles = {
|
||||
'src/ng/directive/ngTransclude.js',
|
||||
'src/ng/directive/script.js',
|
||||
'src/ng/directive/select.js',
|
||||
'src/ng/directive/style.js'
|
||||
'src/ng/directive/style.js',
|
||||
'src/ng/directive/validators.js'
|
||||
],
|
||||
|
||||
'angularLoader': [
|
||||
|
||||
@@ -546,10 +546,10 @@ h4 {
|
||||
margin-left:10px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
color:black!important;
|
||||
.btn:hover, .btn:focus {
|
||||
color: black!important;
|
||||
border: 1px solid #ddd!important;
|
||||
background:white!important;
|
||||
background: white!important;
|
||||
}
|
||||
|
||||
.view-source, .improve-docs {
|
||||
@@ -631,6 +631,7 @@ ul.events > li {
|
||||
}
|
||||
.main-body-grid .side-navigation {
|
||||
display:block!important;
|
||||
padding-bottom:50px;
|
||||
}
|
||||
.main-body-grid .side-navigation.ng-hide {
|
||||
display:none!important;
|
||||
@@ -656,14 +657,14 @@ ul.events > li {
|
||||
}
|
||||
.toc-close {
|
||||
position: absolute;
|
||||
bottom: -50px;
|
||||
bottom: 5px;
|
||||
left: 50%;
|
||||
margin-left: -50%;
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
background: #eee;
|
||||
border-radius: 5px;
|
||||
width: 90%;
|
||||
width: 100%;
|
||||
border:1px solid #ddd;
|
||||
box-shadow:0 0 10px #bbb;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
angular.module('examples', [])
|
||||
|
||||
.factory('formPostData', ['$document', function($document) {
|
||||
return function(url, fields) {
|
||||
return function(url, newWindow, fields) {
|
||||
/**
|
||||
* Form previously posted to target="_blank", but pop-up blockers were causing this to not work.
|
||||
* If a user chose to bypass pop-up blocker one time and click the link, they would arrive at
|
||||
* a new default plnkr, not a plnkr with the desired template.
|
||||
* If the form posts to target="_blank", pop-up blockers can cause it not to work.
|
||||
* If a user choses to bypass pop-up blocker one time and click the link, they will arrive at
|
||||
* a new default plnkr, not a plnkr with the desired template. Given this undesired behavior,
|
||||
* some may still want to open the plnk in a new window by opting-in via ctrl+click. The
|
||||
* newWindow param allows for this possibility.
|
||||
*/
|
||||
var form = angular.element('<form style="display: none;" method="post" action="' + url + '"></form>');
|
||||
var target = newWindow ? '_blank' : '_self';
|
||||
var form = angular.element('<form style="display: none;" method="post" action="' + url + '" target="' + target + '"></form>');
|
||||
angular.forEach(fields, function(value, name) {
|
||||
var input = angular.element('<input type="hidden" name="' + name + '">');
|
||||
input.attr('value', value);
|
||||
@@ -21,9 +24,10 @@ angular.module('examples', [])
|
||||
|
||||
|
||||
.factory('openPlunkr', ['formPostData', '$http', '$q', function(formPostData, $http, $q) {
|
||||
return function(exampleFolder) {
|
||||
return function(exampleFolder, clickEvent) {
|
||||
|
||||
var exampleName = 'AngularJS Example';
|
||||
var newWindow = clickEvent.ctrlKey || clickEvent.metaKey;
|
||||
|
||||
// Load the manifest for the example
|
||||
$http.get(exampleFolder + '/manifest.json')
|
||||
@@ -71,7 +75,7 @@ angular.module('examples', [])
|
||||
postData.private = true;
|
||||
postData.description = exampleName;
|
||||
|
||||
formPostData('http://plnkr.co/edit/?p=preview', postData);
|
||||
formPostData('http://plnkr.co/edit/?p=preview', newWindow, postData);
|
||||
});
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
is HTML and wrap each line in a <p> - thus breaking the HTML #}
|
||||
|
||||
<div>
|
||||
<a ng-click="openPlunkr('{$ doc.path $}')" class="btn pull-right">
|
||||
<a ng-click="openPlunkr('{$ doc.path $}', $event)" class="btn pull-right">
|
||||
<i class="glyphicon glyphicon-edit"> </i>
|
||||
Edit in Plunker</a>
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
@ngdoc error
|
||||
@name $controller:ctrlfmt
|
||||
@fullName Badly formed controller string
|
||||
@description
|
||||
|
||||
This error occurs when {@link ng.$controller $controller} service is called
|
||||
with a string that does not match the supported controller string formats.
|
||||
|
||||
Supported formats:
|
||||
|
||||
1. `__name__`
|
||||
2. `__name__ as __identifier__`
|
||||
|
||||
N'either `__name__` or `__identifier__` may contain spaces.
|
||||
|
||||
Example of incorrect usage that leads to this error:
|
||||
```html
|
||||
<!-- unclosed ng-controller attribute messes up the format -->
|
||||
<div ng-controller="myController>
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
// does not match `__name__` or `__name__ as __identifier__`
|
||||
var myCtrl = $controller("mY contRoller", { $scope: newScope });
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
directive("myDirective", function() {
|
||||
return {
|
||||
// does not match `__name__` or `__name__ as __identifier__`
|
||||
controller: "mY contRoller",
|
||||
link: function() {}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
To fix the examples above, ensure that the controller string matches the supported
|
||||
formats, and that any html attributes which are used as controller expressions are
|
||||
closed.
|
||||
|
||||
|
||||
Please consult the {@link ng.$controller $controller} service api docs to learn more.
|
||||
@@ -35,7 +35,7 @@ var users = [ { name: 'Hank' }, { name: 'Francisco' } ];
|
||||
|
||||
$scope.getUsers = function() {
|
||||
return users;
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
The maximum number of allowed iterations of the `$digest` cycle is controlled via TTL setting which can be configured via {@link ng.$rootScopeProvider $rootScopeProvider}.
|
||||
|
||||
@@ -16,6 +16,8 @@ Reserved names include:
|
||||
- `this`
|
||||
- `undefined`
|
||||
- `$parent`
|
||||
- `$id`
|
||||
- `$root`
|
||||
- `$even`
|
||||
- `$odd`
|
||||
- `$first`
|
||||
|
||||
@@ -358,7 +358,7 @@ legacy browsers and hashbang links in modern browser:
|
||||
|
||||
Here you can see two `$location` instances, both in **Html5 mode**, but on different browsers, so
|
||||
that you can see the differences. These `$location` services are connected to a fake browsers. Each
|
||||
input represents address bar of the browser.
|
||||
input represents the address bar of the browser.
|
||||
|
||||
Note that when you type hashbang url into first browser (or vice versa) it doesn't rewrite /
|
||||
redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL
|
||||
|
||||
@@ -37,6 +37,7 @@ Currently, ngAria interfaces with the following directives:
|
||||
* {@link guide/accessibility#nghide ngHide}
|
||||
* {@link guide/accessibility#ngclick ngClick}
|
||||
* {@link guide/accessibility#ngdblclick ngDblClick}
|
||||
* {@link guide/accessibility#ngmessages ngMessages}
|
||||
|
||||
<h2 id="ngmodel">ngModel</h2>
|
||||
|
||||
@@ -209,10 +210,14 @@ The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redun
|
||||
`display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.
|
||||
|
||||
<h2><span id="ngclick">ngClick</span> and <span id="ngdblclick">ngDblclick</span></h2>
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex` if it isn't there already.
|
||||
Even with this, you must currently still add `ng-keypress` to non-interactive elements such as `div`
|
||||
or `taco-button` to enable keyboard access. Conversation is currently ongoing about whether ngAria
|
||||
should also bind `ng-keypress`.
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` if it isn't there
|
||||
already.
|
||||
|
||||
For `ng-click`, keypress will also be bound to `div` and `li` elements. You can turn this
|
||||
functionality on or off with the `bindKeypress` configuration option.
|
||||
|
||||
For `ng-dblclick`, you must manually add `ng-keypress` to non-interactive elements such as `div`
|
||||
or `taco-button` to enable keyboard access.
|
||||
|
||||
<h3>Example</h3>
|
||||
```html
|
||||
@@ -223,7 +228,6 @@ Becomes:
|
||||
```html
|
||||
<div ng-click="toggleMenu()" tabindex="0"></div>
|
||||
```
|
||||
*Note: ngAria still requires `ng-keypress` to be added manually to non-native controls like divs.*
|
||||
|
||||
<h2 id="ngmessages">ngMessages</h2>
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ As a best practice, consider adding an `ng-strict-di` directive on the same elem
|
||||
```
|
||||
|
||||
This will ensure that all services in your application are properly annotated.
|
||||
See the {@link guide/di#using-strict-dependency-injection dependancy injection strict mode} docs
|
||||
See the {@link guide/di#using-strict-dependency-injection dependency injection strict mode} docs
|
||||
for more.
|
||||
|
||||
|
||||
@@ -156,4 +156,4 @@ until `angular.resumeBootstrap()` is called.
|
||||
|
||||
`angular.resumeBootstrap()` takes an optional array of modules that
|
||||
should be added to the original list of modules that the app was
|
||||
about to be bootstrapped with.
|
||||
about to be bootstrapped with.
|
||||
@@ -751,9 +751,12 @@ own behavior to it.
|
||||
angular.module('docsIsoFnBindExample', [])
|
||||
.controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {
|
||||
$scope.name = 'Tobias';
|
||||
$scope.hideDialog = function () {
|
||||
$scope.message = '';
|
||||
$scope.hideDialog = function (message) {
|
||||
$scope.message = message;
|
||||
$scope.dialogIsHidden = true;
|
||||
$timeout(function () {
|
||||
$scope.message = '';
|
||||
$scope.dialogIsHidden = false;
|
||||
}, 2000);
|
||||
};
|
||||
@@ -771,14 +774,15 @@ own behavior to it.
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<div ng-controller="Controller">
|
||||
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
|
||||
{{message}}
|
||||
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog(message)">
|
||||
Check out the contents, {{name}}!
|
||||
</my-dialog>
|
||||
</div>
|
||||
</file>
|
||||
<file name="my-dialog-close.html">
|
||||
<div class="alert">
|
||||
<a href class="close" ng-click="close()">×</a>
|
||||
<a href class="close" ng-click="close({message: 'closing for now'})">×</a>
|
||||
<div ng-transclude></div>
|
||||
</div>
|
||||
</file>
|
||||
@@ -795,9 +799,15 @@ callback functions to directive behaviors.
|
||||
|
||||
When the user clicks the `x` in the dialog, the directive's `close` function is called, thanks to
|
||||
`ng-click.` This call to `close` on the isolated scope actually evaluates the expression
|
||||
`hideDialog()` in the context of the original scope, thus running `Controller`'s `hideDialog`
|
||||
`hideDialog(message)` in the context of the original scope, thus running `Controller`'s `hideDialog`
|
||||
function.
|
||||
|
||||
Often it's desirable to pass data from the isolate scope via an expression to the
|
||||
parent scope, this can be done by passing a map of local variable names and values into the expression
|
||||
wrapper fn. For example, the hideDialog function takes a message to display when the dialog is hidden.
|
||||
This is specified in the directive by calling `close({message: 'closing for now'})`. Then the local
|
||||
variable `message` will be available within the `on-close` expression.
|
||||
|
||||
<div class="alert alert-success">
|
||||
**Best Practice:** use `&attr` in the `scope` option when you want your directive
|
||||
to expose an API for binding to behaviors.
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
Controls (`input`, `select`, `textarea`) are ways for a user to enter data.
|
||||
A Form is a collection of controls for the purpose of grouping related controls together.
|
||||
|
||||
Form and controls provide validation services, so that the user can be notified of invalid input.
|
||||
This provides a better user experience, because the user gets instant feedback on how to
|
||||
correct the error. Keep in mind that while client-side validation plays an important role
|
||||
in providing good user experience, it can easily be circumvented and thus can not be trusted.
|
||||
Server-side validation is still necessary for a secure application.
|
||||
Form and controls provide validation services, so that the user can be notified of invalid input
|
||||
before submitting a form. This provides a better user experience than server-side validation alone
|
||||
because the user gets instant feedback on how to correct the error. Keep in mind that while
|
||||
client-side validation plays an important role in providing good user experience, it can easily
|
||||
be circumvented and thus can not be trusted. Server-side validation is still necessary for a
|
||||
secure application.
|
||||
|
||||
|
||||
# Simple form
|
||||
@@ -131,7 +132,7 @@ A form is an instance of {@link form.FormController FormController}.
|
||||
The form instance can optionally be published into the scope using the `name` attribute.
|
||||
|
||||
Similarly, an input control that has the {@link ng.directive:ngModel ngModel} directive holds an
|
||||
instance of {@link ngModel.NgModelController NgModelController}.Such a control instance
|
||||
instance of {@link ngModel.NgModelController NgModelController}. Such a control instance
|
||||
can be published as a property of the form instance using the `name` attribute on the input control.
|
||||
The name attribute specifies the name of the property on the form instance.
|
||||
|
||||
@@ -339,7 +340,7 @@ In the following example we create two directives:
|
||||
<div>
|
||||
Username:
|
||||
<input type="text" ng-model="name" name="name" username />{{name}}<br />
|
||||
<span ng-show="form.name.$pending.username">Checking if this name is available ...</span>
|
||||
<span ng-show="form.name.$pending.username">Checking if this name is available...</span>
|
||||
<span ng-show="form.name.$error.username">This username is already taken!</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
|
||||
## Specific Topics
|
||||
|
||||
* **Login: **[Google example](https://developers.google.com/+/photohunt/python), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
|
||||
* **Login: **[Google example](https://developers.google.com/+/photohunt/python), [AngularJS Faceb0ok library](https://github.com/pc035860/angular-easyfb), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
|
||||
* **Mobile:** [Angular on Mobile Guide](http://www.ng-newsletter.com/posts/angular-on-mobile.html), [PhoneGap](http://devgirl.org/2013/06/10/quick-start-guide-phonegap-and-angularjs/)
|
||||
* **Other Languages:** [CoffeeScript](http://www.coffeescriptlove.com/2013/08/angularjs-and-coffeescript-tutorials.html), [Dart](https://github.com/angular/angular.dart.tutorial/wiki)
|
||||
* **Realtime: **[Socket.io](http://www.creativebloq.com/javascript/angularjs-collaboration-board-socketio-2132885), [OmniBinder](https://github.com/jeffbcross/omnibinder)
|
||||
@@ -62,6 +62,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
|
||||
## Tools
|
||||
|
||||
* **Getting Started:** [Comparison of the options for starting a new project](http://www.dancancro.com/comparison-of-angularjs-application-starters/)
|
||||
* **Debugging:** [Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en)
|
||||
* **Testing:** [Karma](http://karma-runner.github.io), [Protractor](https://github.com/angular/protractor)
|
||||
* **Editor support:** [Webstorm](http://plugins.jetbrains.com/plugin/6971) (and [video](http://www.youtube.com/watch?v=LJOyrSh1kDU)), [Sublime Text](https://github.com/angular-ui/AngularJS-sublime-package), [Visual Studio](http://madskristensen.net/post/angularjs-intellisense-in-visual-studio-2012)
|
||||
@@ -101,7 +102,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
## Learning Resources
|
||||
|
||||
###Books
|
||||
* [AngularJS](http://www.amazon.com/AngularJS-Brad-Green/dp/1449344852) by Brad Green and Shyam Seshadri
|
||||
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
|
||||
* [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin
|
||||
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
|
||||
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
|
||||
|
||||
@@ -12,7 +12,7 @@ succinctly. Angular's data binding and dependency injection eliminate much of th
|
||||
would otherwise have to write. And it all happens within the browser, making it
|
||||
an ideal partner with any server technology.
|
||||
|
||||
Angular is what HTML would have been had it been designed for applications. HTML is a great
|
||||
Angular is what HTML would have been, had it been designed for applications. HTML is a great
|
||||
declarative language for static documents. It does not contain much in the way of creating
|
||||
applications, and as a result building web applications is an exercise in *what do I have to do
|
||||
to trick the browser into doing what I want?*
|
||||
@@ -28,10 +28,10 @@ The impedance mismatch between dynamic applications and static documents is ofte
|
||||
|
||||
Angular takes another approach. It attempts to minimize the impedance mismatch between document
|
||||
centric HTML and what an application needs by creating new HTML constructs. Angular teaches the
|
||||
browser new syntax through a construct we call directives. Examples include:
|
||||
browser new syntax through a construct we call *directives*. Examples include:
|
||||
|
||||
* Data binding, as in `{{}}`.
|
||||
* DOM control structures for repeating/hiding DOM fragments.
|
||||
* DOM control structures for repeating, showing and hiding DOM fragments.
|
||||
* Support for forms and form validation.
|
||||
* Attaching new behavior to DOM elements, such as DOM event handling.
|
||||
* Grouping of HTML into reusable components.
|
||||
@@ -42,20 +42,20 @@ browser new syntax through a construct we call directives. Examples include:
|
||||
|
||||
Angular is not a single piece in the overall puzzle of building the client-side of a web
|
||||
application. It handles all of the DOM and AJAX glue code you once wrote by hand and puts it in a
|
||||
well-defined structure. This makes Angular opinionated about how a CRUD application should be
|
||||
built. But while it is opinionated, it also tries to make sure that its opinion is just a
|
||||
starting point you can easily change. Angular comes with the following out-of-the-box:
|
||||
well-defined structure. This makes Angular opinionated about how a CRUD (Create, Read, Update, Delete)
|
||||
application should be built. But while it is opinionated, it also tries to make sure that its opinion
|
||||
is just a starting point you can easily change. Angular comes with the following out-of-the-box:
|
||||
|
||||
* Everything you need to build a CRUD app in a cohesive set: data-binding, basic templating
|
||||
directives, form validation, routing, deep-linking, reusable components, dependency injection.
|
||||
* Testability story: unit-testing, end-to-end testing, mocks, test harnesses.
|
||||
* Everything you need to build a CRUD app in a cohesive set: Data-binding, basic templating
|
||||
directives, form validation, routing, deep-linking, reusable components and dependency injection.
|
||||
* Testability story: Unit-testing, end-to-end testing, mocks and test harnesses.
|
||||
* Seed application with directory layout and test scripts as a starting point.
|
||||
|
||||
|
||||
## Angular Sweet Spot
|
||||
## Angular's sweet spot
|
||||
|
||||
Angular simplifies application development by presenting a higher level of abstraction to the
|
||||
developer. Like any abstraction, it comes at a cost of flexibility. In other words not every app
|
||||
developer. Like any abstraction, it comes at a cost of flexibility. In other words, not every app
|
||||
is a good fit for Angular. Angular was built with the CRUD application in mind. Luckily CRUD
|
||||
applications represent the majority of web applications. To understand what Angular is
|
||||
good at, though, it helps to understand when an app is not a good fit for Angular.
|
||||
@@ -78,7 +78,7 @@ expressing business logic.
|
||||
* It is an excellent idea to decouple the client side of an app from the server side. This
|
||||
allows development work to progress in parallel, and allows for reuse of both sides.
|
||||
* It is very helpful indeed if the framework guides developers through the entire journey of
|
||||
building an app: from designing the UI, through writing the business logic, to testing.
|
||||
building an app: From designing the UI, through writing the business logic, to testing.
|
||||
* It is always good to make common tasks trivial and difficult tasks possible.
|
||||
|
||||
|
||||
|
||||
@@ -15,13 +15,51 @@ which drives many of these changes.
|
||||
|
||||
# Migrating from 1.2 to 1.3
|
||||
|
||||
## Controllers
|
||||
|
||||
Due to [3f2232b5](https://github.com/angular/angular.js/commit/3f2232b5a181512fac23775b1df4a6ebda67d018),
|
||||
`$controller` will no longer look for controllers on `window`.
|
||||
The old behavior of looking on `window` for controllers was originally intended
|
||||
for use in examples, demos, and toy apps. We found that allowing global controller
|
||||
functions encouraged poor practices, so we resolved to disable this behavior by
|
||||
default.
|
||||
|
||||
To migrate, register your controllers with modules rather than exposing them
|
||||
as globals:
|
||||
|
||||
Before:
|
||||
|
||||
```javascript
|
||||
function MyController() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```javascript
|
||||
angular.module('myApp', []).controller('MyController', [function() {
|
||||
// ...
|
||||
}]);
|
||||
```
|
||||
|
||||
Although it's not recommended, you can re-enable the old behavior like this:
|
||||
|
||||
```javascript
|
||||
angular.module('myModule').config(['$controllerProvider', function($controllerProvider) {
|
||||
// this option might be handy for migrating old apps, but please don't use it
|
||||
// in new ones!
|
||||
$controllerProvider.allowGlobals();
|
||||
}]);
|
||||
```
|
||||
|
||||
## Angular Expression Parsing (`$parse` + `$interpolate`)
|
||||
|
||||
- due to [77ada4c8](https://github.com/angular/angular.js/commit/77ada4c82d6b8fc6d977c26f3cdb48c2f5fbe5a5),
|
||||
|
||||
You can no longer invoke .bind, .call or .apply on a function in angular expressions.
|
||||
This is to disallow changing the behaviour of existing functions
|
||||
in an unforseen fashion.
|
||||
in an unforeseen fashion.
|
||||
|
||||
- due to [6081f207](https://github.com/angular/angular.js/commit/6081f20769e64a800ee8075c168412b21f026d99),
|
||||
|
||||
@@ -877,7 +915,7 @@ of `$sce.trustAsHtml(string)`. When bound to a plain string, the string is sanit
|
||||
module is not loaded) and the bound expression evaluates to a value that is not trusted an
|
||||
exception is thrown.
|
||||
|
||||
When using this directive you can either include `ngSanitize` in your module's dependencis (See the
|
||||
When using this directive you can either include `ngSanitize` in your module's dependencies (See the
|
||||
example at the {@link ngBindHtml} reference) or use the {@link $sce} service to set the value as
|
||||
trusted.
|
||||
|
||||
@@ -1134,10 +1172,10 @@ freely available to JavaScript code (as before).
|
||||
|
||||
Angular expressions execute in a limited context. They do not have
|
||||
direct access to the global scope, `window`, `document` or the Function
|
||||
constructor. However, they have direct access to names/properties on
|
||||
the scope chain. It has been a long standing best practice to keep
|
||||
constructor. However, they have direct access to names/properties on
|
||||
the scope chain. It has been a long standing best practice to keep
|
||||
sensitive APIs outside of the scope chain (in a closure or your
|
||||
controller.) That's easier said that done for two reasons:
|
||||
controller.) That's easier said than done for two reasons:
|
||||
|
||||
1. JavaScript does not have a notion of private properties so if you need
|
||||
someone on the scope chain for JavaScript use, you also expose it to
|
||||
|
||||
@@ -10,13 +10,13 @@ There are a few things you might consider when running your AngularJS applicatio
|
||||
|
||||
## Disabling Debug Data
|
||||
|
||||
By default AngularJS attaches information about scopes to DOM nodes, and adds CSS classes
|
||||
to data-bound elements. The information that is not included is:
|
||||
By default AngularJS attaches information about binding and scopes to DOM nodes,
|
||||
and adds CSS classes to data-bound elements:
|
||||
|
||||
As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
|
||||
`ng-class` is attached to the corresponding element.
|
||||
- As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
|
||||
`ng-binding` are attached to the corresponding element.
|
||||
|
||||
Where the compiler has created a new scope, the scope and either `ng-scope` or `ng-isolated-scope`
|
||||
- Where the compiler has created a new scope, the scope and either `ng-scope` or `ng-isolated-scope`
|
||||
CSS class are attached to the corresponding element. These scope references can then be accessed via
|
||||
`element.scope()` and `element.isolateScope()`.
|
||||
|
||||
|
||||
@@ -33,6 +33,9 @@ To see the app running in a browser, open a *separate* terminal/command line tab
|
||||
run `npm start` to start the web server. Now, open a browser window for the app and navigate to
|
||||
<a href="http://localhost:8000/app/" target="_blank">`http://localhost:8000/app/`</a>
|
||||
|
||||
Note that if you already ran the master branch app prior to checking out step-0, you may see the cached
|
||||
master version of the app in your browser window at this point. Just hit refresh to re-load the page.
|
||||
|
||||
You can now see the page in your browser. It's not very exciting, but that's OK.
|
||||
|
||||
The HTML page that displays "Nothing here yet!" was constructed with the HTML code shown below.
|
||||
|
||||
@@ -21,7 +21,7 @@ multiple views by adding routing, using an Angular module called 'ngRoute'.
|
||||
The routing functionality added by this step is provided by angular in the `ngRoute` module, which
|
||||
is distributed separately from the core Angular framework.
|
||||
|
||||
We are using [Bower][bower] to install client side dependencies. This step updates the
|
||||
We are using [Bower][bower] to install client-side dependencies. This step updates the
|
||||
`bower.json` configuration file to include the new dependency:
|
||||
|
||||
```json
|
||||
@@ -46,7 +46,7 @@ The new dependency `"angular-route": "~1.3.0"` tells bower to install a version
|
||||
angular-route component that is compatible with version 1.3.x. We must tell bower to download
|
||||
and install this dependency.
|
||||
|
||||
If you have bower installed globally then you can run `bower install` but for this project we have
|
||||
If you have bower installed globally, then you can run `bower install` but for this project, we have
|
||||
preconfigured npm to run bower install for us:
|
||||
|
||||
```
|
||||
@@ -70,7 +70,7 @@ the current "route" — the view that is currently displayed to the user.
|
||||
Application routes in Angular are declared via the {@link ngRoute.$routeProvider $routeProvider},
|
||||
which is the provider of the {@link ngRoute.$route $route service}. This service makes it easy to
|
||||
wire together controllers, view templates, and the current URL location in the browser. Using this
|
||||
feature we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us
|
||||
feature, we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us
|
||||
utilize the browser's history (back and forward navigation) and bookmarks.
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ AngularJS, so it's important for you to understand a thing or two about how it w
|
||||
|
||||
When the application bootstraps, Angular creates an injector that will be used to find and inject all
|
||||
of the services that are required by your app. The injector itself doesn't know anything about what
|
||||
`$http` or `$route` services do, in fact it doesn't even know about the existence of these services
|
||||
`$http` or `$route` services do. In fact, the injector doesn't even know about the existence of these services
|
||||
unless it is configured with proper module definitions.
|
||||
|
||||
The injector only carries out the following steps :
|
||||
@@ -295,7 +295,7 @@ phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
|
||||
|
||||
Again, note that we created a new module called `phonecatControllers`. For small AngularJS
|
||||
applications, it's common to create just one module for all of your controllers if there are just a
|
||||
few. As your application grows it is quite common to refactor your code into additional modules.
|
||||
few. As your application grows, it is quite common to refactor your code into additional modules.
|
||||
For larger apps, you will probably want to create separate modules for each major feature of
|
||||
your app.
|
||||
|
||||
@@ -349,7 +349,7 @@ the same binding into the `phone-list.html` template, the binding will work as e
|
||||
|
||||
<div style="display: none">
|
||||
* In `PhoneCatCtrl`, create a new model called "`hero`" with `this.hero = 'Zoro'`. In
|
||||
`PhoneListCtrl` let's shadow it with `this.hero = 'Batman'`, and in `PhoneDetailCtrl` we'll use
|
||||
`PhoneListCtrl`, let's shadow it with `this.hero = 'Batman'`. In `PhoneDetailCtrl`, we'll use
|
||||
`this.hero = "Captain Proton"`. Then add the `<p>hero = {{hero}}</p>` to all three of our templates
|
||||
(`index.html`, `phone-list.html`, and `phone-detail.html`). Open the app and you'll see scope
|
||||
inheritance and model property shadowing do some wonders.
|
||||
|
||||
@@ -31,7 +31,7 @@ phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$h
|
||||
|
||||
$scope.setImage = function(imageUrl) {
|
||||
$scope.mainImageUrl = imageUrl;
|
||||
}
|
||||
};
|
||||
}]);
|
||||
```
|
||||
|
||||
|
||||
@@ -288,5 +288,5 @@ learn how to improve this application with animations.
|
||||
<ul doc-tutorial-nav="11"></ul>
|
||||
|
||||
[restful]: http://en.wikipedia.org/wiki/Representational_State_Transfer
|
||||
[jasmine-matchers]: https://github.com/pivotal/jasmine/wiki/Matchers
|
||||
[jasmine-matchers]: http://jasmine.github.io/1.3/introduction.html#section-Matchers
|
||||
[bower]: http://bower.io/
|
||||
|
||||
@@ -97,7 +97,7 @@ __`app/index.html`.__
|
||||
...
|
||||
|
||||
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
|
||||
<script src="bower_components/jquery/jquery.js"></script>
|
||||
<script src="bower_components/jquery/dist/jquery.js"></script>
|
||||
|
||||
...
|
||||
|
||||
|
||||
@@ -2912,16 +2912,12 @@ goog.i18n.DateTimeSymbols_my = {
|
||||
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
|
||||
'စက်တင်ဘာ', 'အောက်တိုဘာ',
|
||||
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
|
||||
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
|
||||
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
|
||||
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
|
||||
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
|
||||
'ဒီဇင်ဘာ'],
|
||||
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
|
||||
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
|
||||
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
|
||||
'စက်တင်ဘာ', 'အောက်တိုဘာ',
|
||||
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
|
||||
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
|
||||
'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
|
||||
'နို', 'ဒီ'],
|
||||
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
|
||||
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
|
||||
'နို', 'ဒီ'],
|
||||
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
|
||||
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
|
||||
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
|
||||
@@ -2945,7 +2941,7 @@ goog.i18n.DateTimeSymbols_my = {
|
||||
'တတိယ သုံးလပတ်',
|
||||
'စတုတ္ထ သုံးလပတ်'],
|
||||
AMPMS: ['နံနက်', 'ညနေ'],
|
||||
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
|
||||
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
|
||||
TIMEFORMATS: ['HH:mm:ss zzzz', 'HH:mm:ss z', 'HH:mm:ss', 'HH:mm'],
|
||||
DATETIMEFORMATS: ['{1}မှာ {0}', '{1} {0}', '{1} {0}', '{1} {0}'],
|
||||
FIRSTDAYOFWEEK: 6,
|
||||
|
||||
@@ -14493,16 +14493,12 @@ goog.i18n.DateTimeSymbols_my_MM = {
|
||||
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
|
||||
'စက်တင်ဘာ', 'အောက်တိုဘာ',
|
||||
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
|
||||
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
|
||||
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
|
||||
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
|
||||
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
|
||||
'ဒီဇင်ဘာ'],
|
||||
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
|
||||
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
|
||||
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
|
||||
'စက်တင်ဘာ', 'အောက်တိုဘာ',
|
||||
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
|
||||
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
|
||||
'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
|
||||
'နို', 'ဒီ'],
|
||||
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
|
||||
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
|
||||
'နို', 'ဒီ'],
|
||||
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
|
||||
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
|
||||
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
|
||||
@@ -14526,7 +14522,7 @@ goog.i18n.DateTimeSymbols_my_MM = {
|
||||
'တတိယ သုံးလပတ်',
|
||||
'စတုတ္ထ သုံးလပတ်'],
|
||||
AMPMS: ['နံနက်', 'ညနေ'],
|
||||
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
|
||||
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
|
||||
TIMEFORMATS: ['HH:mm:ss zzzz', 'HH:mm:ss z', 'HH:mm:ss', 'HH:mm'],
|
||||
DATETIMEFORMATS: ['{1}မှာ {0}', '{1} {0}', '{1} {0}', '{1} {0}'],
|
||||
FIRSTDAYOFWEEK: 6,
|
||||
|
||||
@@ -2112,7 +2112,7 @@ goog.i18n.NumberFormatSymbols_lt = {
|
||||
SCIENTIFIC_PATTERN: '#E0',
|
||||
PERCENT_PATTERN: '#,##0\u00A0%',
|
||||
CURRENCY_PATTERN: '#,##0.00\u00A0\u00A4',
|
||||
DEF_CURRENCY_CODE: 'LTL'
|
||||
DEF_CURRENCY_CODE: 'EUR'
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -35,18 +35,18 @@ module.exports = function(config, specificOptions) {
|
||||
'SL_Chrome': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'chrome',
|
||||
version: '34'
|
||||
version: '39'
|
||||
},
|
||||
'SL_Firefox': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'firefox',
|
||||
version: '26'
|
||||
version: '31'
|
||||
},
|
||||
'SL_Safari': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.9',
|
||||
version: '7'
|
||||
platform: 'OS X 10.10',
|
||||
version: '8'
|
||||
},
|
||||
'SL_IE_9': {
|
||||
base: 'SauceLabs',
|
||||
@@ -71,13 +71,13 @@ module.exports = function(config, specificOptions) {
|
||||
base: 'BrowserStack',
|
||||
browser: 'chrome',
|
||||
os: 'OS X',
|
||||
os_version: 'Mountain Lion'
|
||||
os_version: 'Yosemite'
|
||||
},
|
||||
'BS_Safari': {
|
||||
base: 'BrowserStack',
|
||||
browser: 'safari',
|
||||
os: 'OS X',
|
||||
os_version: 'Mountain Lion'
|
||||
os_version: 'Yosemite'
|
||||
},
|
||||
'BS_Firefox': {
|
||||
base: 'BrowserStack',
|
||||
|
||||
Generated
+108
-84
@@ -1395,66 +1395,66 @@
|
||||
}
|
||||
},
|
||||
"dgeni-packages": {
|
||||
"version": "0.10.7",
|
||||
"version": "0.10.8",
|
||||
"dependencies": {
|
||||
"catharsis": {
|
||||
"version": "0.7.1"
|
||||
},
|
||||
"change-case": {
|
||||
"version": "2.1.5",
|
||||
"version": "2.2.0",
|
||||
"dependencies": {
|
||||
"camel-case": {
|
||||
"version": "1.0.2"
|
||||
"version": "1.1.1"
|
||||
},
|
||||
"constant-case": {
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"dot-case": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"is-lower-case": {
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"is-upper-case": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"lower-case": {
|
||||
"version": "1.0.2"
|
||||
},
|
||||
"param-case": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"pascal-case": {
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"path-case": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"sentence-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"dot-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"is-lower-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"is-upper-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"lower-case": {
|
||||
"version": "1.1.1"
|
||||
},
|
||||
"param-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"pascal-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"path-case": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"sentence-case": {
|
||||
"version": "1.1.1"
|
||||
},
|
||||
"snake-case": {
|
||||
"version": "1.0.1"
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"swap-case": {
|
||||
"version": "1.0.2"
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"title-case": {
|
||||
"version": "1.0.1"
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"upper-case": {
|
||||
"version": "1.0.3"
|
||||
"version": "1.1.1"
|
||||
},
|
||||
"upper-case-first": {
|
||||
"version": "1.0.1"
|
||||
"version": "1.1.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"esprima": {
|
||||
"version": "1.2.2"
|
||||
"version": "1.2.3"
|
||||
},
|
||||
"estraverse": {
|
||||
"version": "1.7.1"
|
||||
"version": "1.9.1"
|
||||
},
|
||||
"glob": {
|
||||
"version": "3.2.11",
|
||||
@@ -1626,7 +1626,7 @@
|
||||
}
|
||||
},
|
||||
"node-uuid": {
|
||||
"version": "1.4.1"
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"cookie-jar": {
|
||||
"version": "0.2.0"
|
||||
@@ -2342,26 +2342,32 @@
|
||||
},
|
||||
"grunt-jasmine-node": {
|
||||
"version": "0.1.0",
|
||||
"from": "grunt-jasmine-node@git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7",
|
||||
"from": "grunt-jasmine-node@git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
|
||||
"resolved": "git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7"
|
||||
},
|
||||
"grunt-jscs": {
|
||||
"version": "0.7.1",
|
||||
"version": "1.2.0",
|
||||
"dependencies": {
|
||||
"hooker": {
|
||||
"version": "0.2.3"
|
||||
},
|
||||
"jscs": {
|
||||
"version": "1.6.2",
|
||||
"version": "1.10.0",
|
||||
"dependencies": {
|
||||
"colors": {
|
||||
"version": "0.6.2"
|
||||
"version": "1.0.3"
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.3.0"
|
||||
"version": "2.5.1"
|
||||
},
|
||||
"esprima": {
|
||||
"version": "1.2.2"
|
||||
"version": "1.2.3"
|
||||
},
|
||||
"esprima-harmony-jscs": {
|
||||
"version": "1.1.0-regex-token-fix"
|
||||
},
|
||||
"estraverse": {
|
||||
"version": "1.9.1"
|
||||
},
|
||||
"exit": {
|
||||
"version": "0.1.2"
|
||||
@@ -2370,11 +2376,22 @@
|
||||
"version": "4.0.6",
|
||||
"dependencies": {
|
||||
"graceful-fs": {
|
||||
"version": "3.0.4"
|
||||
"version": "3.0.5"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1"
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "2.5.0"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.3.1",
|
||||
"dependencies": {
|
||||
@@ -2386,54 +2403,61 @@
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.1",
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "2.5.0"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.0"
|
||||
"brace-expansion": {
|
||||
"version": "1.1.0",
|
||||
"dependencies": {
|
||||
"balanced-match": {
|
||||
"version": "0.2.0"
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "1.0.1"
|
||||
"version": "1.0.2"
|
||||
},
|
||||
"vow-fs": {
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.4",
|
||||
"dependencies": {
|
||||
"node-uuid": {
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"vow": {
|
||||
"version": "0.4.4"
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"vow-queue": {
|
||||
"version": "0.3.1"
|
||||
"version": "0.4.1"
|
||||
},
|
||||
"glob": {
|
||||
"version": "3.2.8",
|
||||
"version": "4.3.5",
|
||||
"dependencies": {
|
||||
"minimatch": {
|
||||
"version": "0.2.14",
|
||||
"inflight": {
|
||||
"version": "1.0.4",
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "2.5.0"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.0"
|
||||
"wrappy": {
|
||||
"version": "1.0.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1"
|
||||
},
|
||||
"once": {
|
||||
"version": "1.3.1",
|
||||
"dependencies": {
|
||||
"wrappy": {
|
||||
"version": "1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.6",
|
||||
"dependencies": {
|
||||
"lodash-node": {
|
||||
"version": "2.4.1"
|
||||
@@ -2441,29 +2465,18 @@
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "1.1.0"
|
||||
"version": "1.2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vow": {
|
||||
"version": "0.4.5"
|
||||
"version": "0.4.8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"grunt-merge-conflict": {
|
||||
"version": "0.0.2"
|
||||
},
|
||||
"grunt-parallel": {
|
||||
"version": "0.3.1",
|
||||
"dependencies": {
|
||||
"q": {
|
||||
"version": "0.8.12"
|
||||
},
|
||||
"lpad": {
|
||||
"version": "0.1.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"grunt-shell": {
|
||||
"version": "1.1.1",
|
||||
"dependencies": {
|
||||
@@ -4810,7 +4823,7 @@
|
||||
}
|
||||
},
|
||||
"protractor": {
|
||||
"version": "1.4.0",
|
||||
"version": "1.6.0",
|
||||
"dependencies": {
|
||||
"request": {
|
||||
"version": "2.36.0",
|
||||
@@ -4828,7 +4841,7 @@
|
||||
"version": "0.5.2"
|
||||
},
|
||||
"node-uuid": {
|
||||
"version": "1.4.1"
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "0.12.1",
|
||||
@@ -4858,16 +4871,16 @@
|
||||
"version": "0.4.0"
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "0.10.0",
|
||||
"version": "0.10.1",
|
||||
"dependencies": {
|
||||
"assert-plus": {
|
||||
"version": "0.1.2"
|
||||
"version": "0.1.5"
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.1.11"
|
||||
},
|
||||
"ctype": {
|
||||
"version": "0.5.2"
|
||||
"version": "0.5.3"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4909,7 +4922,7 @@
|
||||
"version": "0.6.1"
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.5",
|
||||
"dependencies": {
|
||||
"lodash-node": {
|
||||
"version": "2.4.1"
|
||||
@@ -4926,6 +4939,17 @@
|
||||
"jasminewd": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"jasminewd2": {
|
||||
"version": "0.0.2"
|
||||
},
|
||||
"jasmine": {
|
||||
"version": "2.1.1",
|
||||
"dependencies": {
|
||||
"jasmine-core": {
|
||||
"version": "2.1.3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"saucelabs": {
|
||||
"version": "0.1.1"
|
||||
},
|
||||
@@ -4966,7 +4990,7 @@
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.2.8",
|
||||
"version": "0.2.9",
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.1.32",
|
||||
|
||||
+4
-4
@@ -11,6 +11,7 @@
|
||||
"bower": "~1.3.9",
|
||||
"browserstacktunnel-wrapper": "~1.3.1",
|
||||
"canonical-path": "0.0.2",
|
||||
"cheerio": "^0.17.0",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.10.0",
|
||||
"event-stream": "~3.1.0",
|
||||
@@ -23,7 +24,7 @@
|
||||
"grunt-contrib-jshint": "~0.10.0",
|
||||
"grunt-ddescribe-iit": "~0.0.1",
|
||||
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
|
||||
"grunt-jscs": "~0.7.1",
|
||||
"grunt-jscs": "~1.2.0",
|
||||
"grunt-merge-conflict": "~0.0.1",
|
||||
"grunt-shell": "~1.1.1",
|
||||
"gulp": "~3.8.0",
|
||||
@@ -51,7 +52,7 @@
|
||||
"marked": "~0.3.0",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"promises-aplus-tests": "~2.1.0",
|
||||
"protractor": "1.4.0",
|
||||
"protractor": "^1.6.0",
|
||||
"q": "~1.0.0",
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
@@ -59,8 +60,7 @@
|
||||
"semver": "~4.0.3",
|
||||
"shelljs": "~0.3.0",
|
||||
"sorted-object": "^1.0.0",
|
||||
"stringmap": "^0.2.2",
|
||||
"cheerio": "^0.17.0"
|
||||
"stringmap": "^0.2.2"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ if [ $JOB = "unit" ]; then
|
||||
grunt test:unit --browsers $BROWSERS --reporters dots
|
||||
grunt ci-checks
|
||||
grunt tests:docs --browsers $BROWSERS --reporters dots
|
||||
elif [ $JOB = "docs-e2e" ]; then
|
||||
grunt test:travis-protractor --specs "docs/app/e2e/**/*.scenario.js"
|
||||
elif [ $JOB = "e2e" ]; then
|
||||
if [ $TEST_TARGET = "jquery" ]; then
|
||||
|
||||
+8
-5
@@ -317,8 +317,7 @@ function nextUid() {
|
||||
function setHashKey(obj, h) {
|
||||
if (h) {
|
||||
obj.$$hashKey = h;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
delete obj.$$hashKey;
|
||||
}
|
||||
}
|
||||
@@ -627,7 +626,7 @@ function isElement(node) {
|
||||
function makeMap(str) {
|
||||
var obj = {}, items = str.split(","), i;
|
||||
for (i = 0; i < items.length; i++)
|
||||
obj[ items[i] ] = true;
|
||||
obj[items[i]] = true;
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -1338,7 +1337,7 @@ function angularInit(element, bootstrap) {
|
||||
* @param {DOMElement} element DOM element which is the root of angular application.
|
||||
* @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
|
||||
* Each item in the array should be the name of a predefined module or a (DI annotated)
|
||||
* function that will be invoked by the injector as a run block.
|
||||
* function that will be invoked by the injector as a `config` block.
|
||||
* See: {@link angular.module modules}
|
||||
* @param {Object=} config an object for defining configuration options for the application. The
|
||||
* following keys are supported:
|
||||
@@ -1408,8 +1407,12 @@ function bootstrap(element, modules, config) {
|
||||
forEach(extraModules, function(module) {
|
||||
modules.push(module);
|
||||
});
|
||||
doBootstrap();
|
||||
return doBootstrap();
|
||||
};
|
||||
|
||||
if (isFunction(angular.resumeDeferredBootstrap)) {
|
||||
angular.resumeDeferredBootstrap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -800,7 +800,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
}
|
||||
|
||||
var args = [],
|
||||
$inject = annotate(fn, strictDi, serviceName),
|
||||
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
|
||||
length, i,
|
||||
key;
|
||||
|
||||
@@ -829,7 +829,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
// Check if Type is annotated and use just the given function at n-1 as parameter
|
||||
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
|
||||
// Object creation: http://jsperf.com/create-constructor/2
|
||||
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
|
||||
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
|
||||
var returnedValue = invoke(Type, instance, locals, serviceName);
|
||||
|
||||
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
|
||||
@@ -839,7 +839,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
invoke: invoke,
|
||||
instantiate: instantiate,
|
||||
get: getService,
|
||||
annotate: annotate,
|
||||
annotate: createInjector.$$annotate,
|
||||
has: function(name) {
|
||||
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
+5
-2
@@ -1450,6 +1450,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
// use class as directive
|
||||
className = node.className;
|
||||
if (isObject(className)) {
|
||||
// Maybe SVGAnimatedString
|
||||
className = className.animVal;
|
||||
}
|
||||
if (isString(className) && className !== '') {
|
||||
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
|
||||
nName = directiveNormalize(match[2]);
|
||||
@@ -2151,8 +2155,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
afterTemplateChildLinkFn,
|
||||
beforeTemplateCompileNode = $compileNode[0],
|
||||
origAsyncDirective = directives.shift(),
|
||||
// The fact that we have to copy and patch the directive seems wrong!
|
||||
derivedSyncDirective = extend({}, origAsyncDirective, {
|
||||
derivedSyncDirective = inherit(origAsyncDirective, {
|
||||
templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
|
||||
}),
|
||||
templateUrl = (isFunction(origAsyncDirective.templateUrl))
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var $controllerMinErr = minErr('$controller');
|
||||
|
||||
/**
|
||||
* @ngdoc provider
|
||||
* @name $controllerProvider
|
||||
@@ -87,7 +89,12 @@ function $ControllerProvider() {
|
||||
}
|
||||
|
||||
if (isString(expression)) {
|
||||
match = expression.match(CNTRL_REG),
|
||||
match = expression.match(CNTRL_REG);
|
||||
if (!match) {
|
||||
throw $controllerMinErr('ctrlfmt',
|
||||
"Badly formed controller string '{0}'. " +
|
||||
"Must match `__name__ as __id__` or `__name__`.", expression);
|
||||
}
|
||||
constructor = match[1],
|
||||
identifier = identifier || match[3];
|
||||
expression = controllers.hasOwnProperty(constructor)
|
||||
@@ -111,7 +118,7 @@ function $ControllerProvider() {
|
||||
// Object creation: http://jsperf.com/create-constructor/2
|
||||
var controllerPrototype = (isArray(expression) ?
|
||||
expression[expression.length - 1] : expression).prototype;
|
||||
instance = Object.create(controllerPrototype);
|
||||
instance = Object.create(controllerPrototype || null);
|
||||
|
||||
if (identifier) {
|
||||
addIdentifier(locals, identifier, instance, constructor || expression.name);
|
||||
|
||||
@@ -18,6 +18,9 @@ var htmlAnchorDirective = valueFn({
|
||||
compile: function(element, attr) {
|
||||
if (!attr.href && !attr.xlinkHref && !attr.name) {
|
||||
return function(scope, element) {
|
||||
// If the linked element is not an anchor tag anymore, do nothing
|
||||
if (element[0].nodeName.toLowerCase() !== 'a') return;
|
||||
|
||||
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
|
||||
var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
|
||||
'xlink:href' : 'href';
|
||||
|
||||
+13
-10
@@ -163,6 +163,9 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
forEach(form.$error, function(value, name) {
|
||||
form.$setValidity(name, null, control);
|
||||
});
|
||||
forEach(form.$$success, function(value, name) {
|
||||
form.$setValidity(name, null, control);
|
||||
});
|
||||
|
||||
arrayRemove(controls, control);
|
||||
};
|
||||
@@ -180,23 +183,23 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
addSetValidityMethod({
|
||||
ctrl: this,
|
||||
$element: element,
|
||||
set: function(object, property, control) {
|
||||
set: function(object, property, controller) {
|
||||
var list = object[property];
|
||||
if (!list) {
|
||||
object[property] = [control];
|
||||
object[property] = [controller];
|
||||
} else {
|
||||
var index = list.indexOf(control);
|
||||
var index = list.indexOf(controller);
|
||||
if (index === -1) {
|
||||
list.push(control);
|
||||
list.push(controller);
|
||||
}
|
||||
}
|
||||
},
|
||||
unset: function(object, property, control) {
|
||||
unset: function(object, property, controller) {
|
||||
var list = object[property];
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
arrayRemove(list, control);
|
||||
arrayRemove(list, controller);
|
||||
if (list.length === 0) {
|
||||
delete object[property];
|
||||
}
|
||||
@@ -489,19 +492,19 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
alias = controller.$name;
|
||||
|
||||
if (alias) {
|
||||
setter(scope, alias, controller, alias);
|
||||
setter(scope, null, alias, controller, alias);
|
||||
attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) {
|
||||
if (alias === newValue) return;
|
||||
setter(scope, alias, undefined, alias);
|
||||
setter(scope, null, alias, undefined, alias);
|
||||
alias = newValue;
|
||||
setter(scope, alias, controller, alias);
|
||||
setter(scope, null, alias, controller, alias);
|
||||
parentFormCtrl.$$renameControl(controller, alias);
|
||||
});
|
||||
}
|
||||
formElement.on('$destroy', function() {
|
||||
parentFormCtrl.$removeControl(controller);
|
||||
if (alias) {
|
||||
setter(scope, alias, undefined, alias);
|
||||
setter(scope, null, alias, undefined, alias);
|
||||
}
|
||||
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
|
||||
});
|
||||
|
||||
+94
-1684
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngChange
|
||||
*
|
||||
* @description
|
||||
* Evaluate the given expression when the user changes the input.
|
||||
* The expression is evaluated immediately, unlike the JavaScript onchange event
|
||||
* which only triggers at the end of a change (usually, when the user leaves the
|
||||
* form element or presses the return key).
|
||||
*
|
||||
* The `ngChange` expression is only evaluated when a change in the input value causes
|
||||
* a new value to be committed to the model.
|
||||
*
|
||||
* It will not be evaluated:
|
||||
* * if the value returned from the `$parsers` transformation pipeline has not changed
|
||||
* * if the input has continued to be invalid since the model will stay `null`
|
||||
* * if the model is changed programmatically and not by a change to the input value
|
||||
*
|
||||
*
|
||||
* Note, this directive requires `ngModel` to be present.
|
||||
*
|
||||
* @element input
|
||||
* @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
|
||||
* in input value.
|
||||
*
|
||||
* @example
|
||||
* <example name="ngChange-directive" module="changeExample">
|
||||
* <file name="index.html">
|
||||
* <script>
|
||||
* angular.module('changeExample', [])
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.counter = 0;
|
||||
* $scope.change = function() {
|
||||
* $scope.counter++;
|
||||
* };
|
||||
* }]);
|
||||
* </script>
|
||||
* <div ng-controller="ExampleController">
|
||||
* <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
|
||||
* <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
|
||||
* <label for="ng-change-example2">Confirmed</label><br />
|
||||
* <tt>debug = {{confirmed}}</tt><br/>
|
||||
* <tt>counter = {{counter}}</tt><br/>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* var counter = element(by.binding('counter'));
|
||||
* var debug = element(by.binding('confirmed'));
|
||||
*
|
||||
* it('should evaluate the expression if changing from view', function() {
|
||||
* expect(counter.getText()).toContain('0');
|
||||
*
|
||||
* element(by.id('ng-change-example1')).click();
|
||||
*
|
||||
* expect(counter.getText()).toContain('1');
|
||||
* expect(debug.getText()).toContain('true');
|
||||
* });
|
||||
*
|
||||
* it('should not evaluate the expression if changing from model', function() {
|
||||
* element(by.id('ng-change-example2')).click();
|
||||
|
||||
* expect(counter.getText()).toContain('0');
|
||||
* expect(debug.getText()).toContain('true');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var ngChangeDirective = valueFn({
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
ctrl.$viewChangeListeners.push(function() {
|
||||
scope.$eval(attr.ngChange);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -141,8 +141,9 @@ function classDirective(name, selector) {
|
||||
* new classes are added.
|
||||
*
|
||||
* @animations
|
||||
* add - happens just before the class is applied to the element
|
||||
* remove - happens just before the class is removed from the element
|
||||
* **add** - happens just before the class is applied to the elements
|
||||
*
|
||||
* **remove** - happens just before the class is removed from the element
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngClass {@link guide/expression Expression} to eval. The result
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<select ng-model="template" ng-options="t.name for t in templates">
|
||||
<option value="">(blank)</option>
|
||||
</select>
|
||||
url of the template: <tt>{{template.url}}</tt>
|
||||
url of the template: <code>{{template.url}}</code>
|
||||
<hr/>
|
||||
<div class="slide-animate-container">
|
||||
<div class="slide-animate" ng-include="template.url"></div>
|
||||
@@ -173,7 +173,7 @@
|
||||
* @name ngInclude#$includeContentError
|
||||
* @eventType emit on the scope ngInclude was declared in
|
||||
* @description
|
||||
* Emitted when a template HTTP request yields an erronous response (status < 200 || status > 299)
|
||||
* Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
|
||||
*
|
||||
* @param {Object} angularEvent Synthetic event object.
|
||||
* @param {String} src URL of content to load.
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
|
||||
* sure you have parenthesis for correct precedence:
|
||||
* <pre class="prettyprint">
|
||||
* <div ng-init="test1 = (data | orderBy:'name')"></div>
|
||||
* `<div ng-init="test1 = (data | orderBy:'name')"></div>`
|
||||
* </pre>
|
||||
* </div>
|
||||
*
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngList
|
||||
*
|
||||
* @description
|
||||
* Text input that converts between a delimited string and an array of strings. The default
|
||||
* delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
|
||||
* delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
|
||||
*
|
||||
* The behaviour of the directive is affected by the use of the `ngTrim` attribute.
|
||||
* * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
|
||||
* list item is respected. This implies that the user of the directive is responsible for
|
||||
* dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
|
||||
* tab or newline character.
|
||||
* * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
|
||||
* when joining the list items back together) and whitespace around each list item is stripped
|
||||
* before it is added to the model.
|
||||
*
|
||||
* ### Example with Validation
|
||||
*
|
||||
* <example name="ngList-directive" module="listExample">
|
||||
* <file name="app.js">
|
||||
* angular.module('listExample', [])
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.names = ['morpheus', 'neo', 'trinity'];
|
||||
* }]);
|
||||
* </file>
|
||||
* <file name="index.html">
|
||||
* <form name="myForm" ng-controller="ExampleController">
|
||||
* List: <input name="namesInput" ng-model="names" ng-list required>
|
||||
* <span class="error" ng-show="myForm.namesInput.$error.required">
|
||||
* Required!</span>
|
||||
* <br>
|
||||
* <tt>names = {{names}}</tt><br/>
|
||||
* <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
|
||||
* <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
|
||||
* <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
|
||||
* <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
|
||||
* </form>
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* var listInput = element(by.model('names'));
|
||||
* var names = element(by.exactBinding('names'));
|
||||
* var valid = element(by.binding('myForm.namesInput.$valid'));
|
||||
* var error = element(by.css('span.error'));
|
||||
*
|
||||
* it('should initialize to model', function() {
|
||||
* expect(names.getText()).toContain('["morpheus","neo","trinity"]');
|
||||
* expect(valid.getText()).toContain('true');
|
||||
* expect(error.getCssValue('display')).toBe('none');
|
||||
* });
|
||||
*
|
||||
* it('should be invalid if empty', function() {
|
||||
* listInput.clear();
|
||||
* listInput.sendKeys('');
|
||||
*
|
||||
* expect(names.getText()).toContain('');
|
||||
* expect(valid.getText()).toContain('false');
|
||||
* expect(error.getCssValue('display')).not.toBe('none');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* ### Example - splitting on whitespace
|
||||
* <example name="ngList-directive-newlines">
|
||||
* <file name="index.html">
|
||||
* <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea>
|
||||
* <pre>{{ list | json }}</pre>
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it("should split the text by newlines", function() {
|
||||
* var listInput = element(by.model('list'));
|
||||
* var output = element(by.binding('list | json'));
|
||||
* listInput.sendKeys('abc\ndef\nghi');
|
||||
* expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* @element input
|
||||
* @param {string=} ngList optional delimiter that should be used to split the value.
|
||||
*/
|
||||
var ngListDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority: 100,
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
// We want to control whitespace trimming so we use this convoluted approach
|
||||
// to access the ngList attribute, which doesn't pre-trim the attribute
|
||||
var ngList = element.attr(attr.$attr.ngList) || ', ';
|
||||
var trimValues = attr.ngTrim !== 'false';
|
||||
var separator = trimValues ? trim(ngList) : ngList;
|
||||
|
||||
var parse = function(viewValue) {
|
||||
// If the viewValue is invalid (say required but empty) it will be `undefined`
|
||||
if (isUndefined(viewValue)) return;
|
||||
|
||||
var list = [];
|
||||
|
||||
if (viewValue) {
|
||||
forEach(viewValue.split(separator), function(value) {
|
||||
if (value) list.push(trimValues ? trim(value) : value);
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
ctrl.$parsers.push(parse);
|
||||
ctrl.$formatters.push(function(value) {
|
||||
if (isArray(value)) {
|
||||
return value.join(ngList);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
// Override the standard $isEmpty because an empty array means the input is empty.
|
||||
ctrl.$isEmpty = function(value) {
|
||||
return !value || !value.length;
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,29 @@
|
||||
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
|
||||
* This may be useful when, for instance, nesting ngRepeats.
|
||||
*
|
||||
* # Iterating over object properties
|
||||
*
|
||||
* It is possible to get `ngRepeat` to iterate over the properties of an object using the following
|
||||
* syntax:
|
||||
*
|
||||
* ```js
|
||||
* <div ng-repeat="(key, value) in myObj"> ... </div>
|
||||
* ```
|
||||
*
|
||||
* You need to be aware that the JavaScript specification does not define what order
|
||||
* it will return the keys for an object. In order to have a guaranteed deterministic order
|
||||
* for the keys, Angular versions up to and including 1.3 **sort the keys alphabetically**.
|
||||
*
|
||||
* If this is not desired, the recommended workaround is to convert your object into an array
|
||||
* that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
|
||||
* do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
|
||||
* or implement a `$watch` on the object yourself.
|
||||
*
|
||||
* In version 1.4 we will remove the sorting, since it seems that browsers generally follow the
|
||||
* strategy of providing keys in the order in which they were defined, although there are exceptions
|
||||
* when keys are deleted and reinstated.
|
||||
*
|
||||
*
|
||||
* # Special repeat start and end points
|
||||
* To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
|
||||
* the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
|
||||
@@ -267,7 +290,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
var keyIdentifier = match[2];
|
||||
|
||||
if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
|
||||
/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent)$/.test(aliasAs))) {
|
||||
/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
|
||||
throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
|
||||
aliasAs);
|
||||
}
|
||||
|
||||
@@ -40,10 +40,11 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
*
|
||||
* By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
|
||||
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
|
||||
* class in CSS:
|
||||
* class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
|
||||
* with extra animation classes that can be added.
|
||||
*
|
||||
* ```css
|
||||
* .ng-hide {
|
||||
* .ng-hide:not(.ng-hide-animate) {
|
||||
* /* this is just another form of hiding an element */
|
||||
* display: block!important;
|
||||
* position: absolute;
|
||||
|
||||
@@ -47,10 +47,10 @@
|
||||
</example>
|
||||
*/
|
||||
var ngStyleDirective = ngDirective(function(scope, element, attr) {
|
||||
scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
|
||||
scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
|
||||
if (oldStyles && (newStyles !== oldStyles)) {
|
||||
forEach(oldStyles, function(val, style) { element.css(style, '');});
|
||||
}
|
||||
if (newStyles) element.css(newStyles);
|
||||
}, true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
'use strict';
|
||||
|
||||
var requiredDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
attr.required = true; // force truthy in case we are on non input element
|
||||
|
||||
ctrl.$validators.required = function(modelValue, viewValue) {
|
||||
return !attr.required || !ctrl.$isEmpty(viewValue);
|
||||
};
|
||||
|
||||
attr.$observe('required', function() {
|
||||
ctrl.$validate();
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
var patternDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var regexp, patternExp = attr.ngPattern || attr.pattern;
|
||||
attr.$observe('pattern', function(regex) {
|
||||
if (isString(regex) && regex.length > 0) {
|
||||
regex = new RegExp('^' + regex + '$');
|
||||
}
|
||||
|
||||
if (regex && !regex.test) {
|
||||
throw minErr('ngPattern')('noregexp',
|
||||
'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
|
||||
regex, startingTag(elm));
|
||||
}
|
||||
|
||||
regexp = regex || undefined;
|
||||
ctrl.$validate();
|
||||
});
|
||||
|
||||
ctrl.$validators.pattern = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
var maxlengthDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var maxlength = -1;
|
||||
attr.$observe('maxlength', function(value) {
|
||||
var intVal = int(value);
|
||||
maxlength = isNaN(intVal) ? -1 : intVal;
|
||||
ctrl.$validate();
|
||||
});
|
||||
ctrl.$validators.maxlength = function(modelValue, viewValue) {
|
||||
return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var minlengthDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var minlength = 0;
|
||||
attr.$observe('minlength', function(value) {
|
||||
minlength = int(value) || 0;
|
||||
ctrl.$validate();
|
||||
});
|
||||
ctrl.$validators.minlength = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -186,7 +186,7 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatc
|
||||
|
||||
if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
|
||||
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
|
||||
} else if (actualType === 'array') {
|
||||
} else if (isArray(actual)) {
|
||||
// In case `actual` is an array, consider it a match
|
||||
// if ANY of it's items matches `expected`
|
||||
return actual.some(function(item) {
|
||||
|
||||
@@ -353,7 +353,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?: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)
|
||||
* * `'sss'`: Millisecond in second, padded (000-999)
|
||||
* * `'a'`: AM/PM marker
|
||||
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
|
||||
* * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
|
||||
|
||||
+2
-2
@@ -837,7 +837,7 @@ function $LocationProvider() {
|
||||
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
|
||||
// currently we open nice url link and redirect then
|
||||
|
||||
if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.which == 2) return;
|
||||
if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
|
||||
|
||||
var elm = jqLite(event.target);
|
||||
|
||||
@@ -879,7 +879,7 @@ function $LocationProvider() {
|
||||
|
||||
|
||||
// rewrite hashbang url <> html5 url
|
||||
if ($location.absUrl() != initialUrl) {
|
||||
if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
|
||||
$browser.url($location.absUrl(), true);
|
||||
}
|
||||
|
||||
|
||||
+13
-7
@@ -664,7 +664,7 @@ Parser.prototype = {
|
||||
}, {
|
||||
assign: function(scope, value, locals) {
|
||||
var o = object(scope, locals);
|
||||
if (!o) object.assign(scope, o = {});
|
||||
if (!o) object.assign(scope, o = {}, locals);
|
||||
return getter.assign(o, value);
|
||||
}
|
||||
});
|
||||
@@ -690,7 +690,7 @@ Parser.prototype = {
|
||||
var key = ensureSafeMemberName(indexFn(self, locals), expression);
|
||||
// prevent overwriting of Function.constructor which would break ensureSafeObject check
|
||||
var o = ensureSafeObject(obj(self, locals), expression);
|
||||
if (!o) obj.assign(self, o = {});
|
||||
if (!o) obj.assign(self, o = {}, locals);
|
||||
return o[key] = value;
|
||||
}
|
||||
});
|
||||
@@ -728,6 +728,11 @@ Parser.prototype = {
|
||||
? fn.apply(context, args)
|
||||
: fn(args[0], args[1], args[2], args[3], args[4]);
|
||||
|
||||
if (args) {
|
||||
// Free-up the memory (arguments of the last function call).
|
||||
args.length = 0;
|
||||
}
|
||||
|
||||
return ensureSafeObject(v, expressionText);
|
||||
};
|
||||
},
|
||||
@@ -800,18 +805,19 @@ Parser.prototype = {
|
||||
// Parser helper functions
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
function setter(obj, path, setValue, fullExp) {
|
||||
function setter(obj, locals, path, setValue, fullExp) {
|
||||
ensureSafeObject(obj, fullExp);
|
||||
ensureSafeObject(locals, fullExp);
|
||||
|
||||
var element = path.split('.'), key;
|
||||
for (var i = 0; element.length > 1; i++) {
|
||||
key = ensureSafeMemberName(element.shift(), fullExp);
|
||||
var propertyObj = ensureSafeObject(obj[key], fullExp);
|
||||
var propertyObj = (i === 0 && locals && locals[key]) || obj[key];
|
||||
if (!propertyObj) {
|
||||
propertyObj = {};
|
||||
obj[key] = propertyObj;
|
||||
}
|
||||
obj = propertyObj;
|
||||
obj = ensureSafeObject(propertyObj, fullExp);
|
||||
}
|
||||
key = ensureSafeMemberName(element.shift(), fullExp);
|
||||
ensureSafeObject(obj[key], fullExp);
|
||||
@@ -938,8 +944,8 @@ function getterFn(path, options, fullExp) {
|
||||
}
|
||||
|
||||
fn.sharedGetter = true;
|
||||
fn.assign = function(self, value) {
|
||||
return setter(self, path, value, path);
|
||||
fn.assign = function(self, value, locals) {
|
||||
return setter(self, locals, path, value, path);
|
||||
};
|
||||
getterFnCache[path] = fn;
|
||||
return fn;
|
||||
|
||||
+1
-2
@@ -347,8 +347,7 @@ function qFactory(nextTick, exceptionHandler) {
|
||||
'qcycle',
|
||||
"Expected promise to be resolved with value other than itself '{0}'",
|
||||
val));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.$$resolve(val);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1045,7 +1045,7 @@ function $RootScopeProvider() {
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Schedule the invokation of $apply to occur at a later time. The actual time difference
|
||||
* Schedule the invocation of $apply to occur at a later time. The actual time difference
|
||||
* varies across browsers, but is typically around ~10 milliseconds.
|
||||
*
|
||||
* This can be used to queue up multiple expressions which need to be evaluated in the same
|
||||
|
||||
@@ -22,8 +22,7 @@ var $compileMinErr = minErr('$compile');
|
||||
function $TemplateRequestProvider() {
|
||||
this.$get = ['$templateCache', '$http', '$q', function($templateCache, $http, $q) {
|
||||
function handleRequestFn(tpl, ignoreRequestError) {
|
||||
var self = handleRequestFn;
|
||||
self.totalPendingRequests++;
|
||||
handleRequestFn.totalPendingRequests++;
|
||||
|
||||
var transformResponse = $http.defaults && $http.defaults.transformResponse;
|
||||
|
||||
@@ -41,13 +40,14 @@ function $TemplateRequestProvider() {
|
||||
};
|
||||
|
||||
return $http.get(tpl, httpOptions)
|
||||
.finally(function() {
|
||||
handleRequestFn.totalPendingRequests--;
|
||||
})
|
||||
.then(function(response) {
|
||||
self.totalPendingRequests--;
|
||||
return response.data;
|
||||
}, handleError);
|
||||
|
||||
function handleError(resp) {
|
||||
self.totalPendingRequests--;
|
||||
if (!ignoreRequestError) {
|
||||
throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl);
|
||||
}
|
||||
|
||||
@@ -1327,8 +1327,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
} else if (lastAnimation.event == 'setClass') {
|
||||
animationsToCancel.push(lastAnimation);
|
||||
cleanup(element, className);
|
||||
}
|
||||
else if (runningAnimations[className]) {
|
||||
} else if (runningAnimations[className]) {
|
||||
var current = runningAnimations[className];
|
||||
if (current.event == animationEvent) {
|
||||
skipAnimation = true;
|
||||
@@ -1869,7 +1868,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
return;
|
||||
}
|
||||
|
||||
if (!staggerTime && styles) {
|
||||
if (!staggerTime && styles && Object.keys(styles).length > 0) {
|
||||
if (!timings.transitionDuration) {
|
||||
element.css('transition', timings.animationDuration + 's linear all');
|
||||
appliedStyles.push('transition');
|
||||
|
||||
+10
-2
@@ -100,7 +100,8 @@ function $AriaProvider() {
|
||||
* - **ariaMultiline** – `{boolean}` – Enables/disables aria-multiline tags
|
||||
* - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags
|
||||
* - **tabindex** – `{boolean}` – Enables/disables tabindex tags
|
||||
* - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on ng-click
|
||||
* - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on `<div>` and
|
||||
* `<li>` elements with ng-click
|
||||
*
|
||||
* @description
|
||||
* Enables/disables various ARIA attributes
|
||||
@@ -303,11 +304,18 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
compile: function(elem, attr) {
|
||||
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
|
||||
return function(scope, elem, attr) {
|
||||
|
||||
function isNodeOneOf(elem, nodeTypeArray) {
|
||||
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
if ($aria.config('bindKeypress') && !attr.ngKeypress) {
|
||||
if ($aria.config('bindKeypress') && !attr.ngKeypress && isNodeOneOf(elem, ['DIV', 'LI'])) {
|
||||
elem.on('keypress', function(event) {
|
||||
if (event.keyCode === 32 || event.keyCode === 13) {
|
||||
scope.$apply(callback);
|
||||
|
||||
Vendored
+1
-1
@@ -81,7 +81,7 @@ $provide.value("$locale", {
|
||||
"shortTime": "HH:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
"CURRENCY_SYM": "Lt",
|
||||
"CURRENCY_SYM": "\u20ac",
|
||||
"DECIMAL_SEP": ",",
|
||||
"GROUP_SEP": "\u00a0",
|
||||
"PATTERNS": [
|
||||
|
||||
Vendored
+1
-1
@@ -81,7 +81,7 @@ $provide.value("$locale", {
|
||||
"shortTime": "HH:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
"CURRENCY_SYM": "Lt",
|
||||
"CURRENCY_SYM": "\u20ac",
|
||||
"DECIMAL_SEP": ",",
|
||||
"GROUP_SEP": "\u00a0",
|
||||
"PATTERNS": [
|
||||
|
||||
Vendored
+14
-14
@@ -40,26 +40,26 @@ $provide.value("$locale", {
|
||||
"\u1005\u1014\u1031"
|
||||
],
|
||||
"SHORTMONTH": [
|
||||
"\u1007\u1014\u103a\u1014\u101d\u102b\u101b\u102e",
|
||||
"\u1016\u1031\u1016\u1031\u102c\u103a\u101d\u102b\u101b\u102e",
|
||||
"\u1007\u1014\u103a",
|
||||
"\u1016\u1031",
|
||||
"\u1019\u1010\u103a",
|
||||
"\u1027\u1015\u103c\u102e",
|
||||
"\u1019\u1031",
|
||||
"\u1007\u103d\u1014\u103a",
|
||||
"\u1007\u1030\u101c\u102d\u102f\u1004\u103a",
|
||||
"\u1029\u1002\u102f\u1010\u103a",
|
||||
"\u1005\u1000\u103a\u1010\u1004\u103a\u1018\u102c",
|
||||
"\u1021\u1031\u102c\u1000\u103a\u1010\u102d\u102f\u1018\u102c",
|
||||
"\u1014\u102d\u102f\u101d\u1004\u103a\u1018\u102c",
|
||||
"\u1012\u102e\u1007\u1004\u103a\u1018\u102c"
|
||||
"\u1007\u1030",
|
||||
"\u1029",
|
||||
"\u1005\u1000\u103a",
|
||||
"\u1021\u1031\u102c\u1000\u103a",
|
||||
"\u1014\u102d\u102f",
|
||||
"\u1012\u102e"
|
||||
],
|
||||
"fullDate": "EEEE, y MMMM dd",
|
||||
"longDate": "y MMMM d",
|
||||
"medium": "y MMM d HH:mm:ss",
|
||||
"mediumDate": "y MMM d",
|
||||
"fullDate": "EEEE, dd MMMM y",
|
||||
"longDate": "d MMMM y",
|
||||
"medium": "d MMM y HH:mm:ss",
|
||||
"mediumDate": "d MMM y",
|
||||
"mediumTime": "HH:mm:ss",
|
||||
"short": "yy/MM/dd HH:mm",
|
||||
"shortDate": "yy/MM/dd",
|
||||
"short": "dd-MM-yy HH:mm",
|
||||
"shortDate": "dd-MM-yy",
|
||||
"shortTime": "HH:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
|
||||
Vendored
+14
-14
@@ -40,26 +40,26 @@ $provide.value("$locale", {
|
||||
"\u1005\u1014\u1031"
|
||||
],
|
||||
"SHORTMONTH": [
|
||||
"\u1007\u1014\u103a\u1014\u101d\u102b\u101b\u102e",
|
||||
"\u1016\u1031\u1016\u1031\u102c\u103a\u101d\u102b\u101b\u102e",
|
||||
"\u1007\u1014\u103a",
|
||||
"\u1016\u1031",
|
||||
"\u1019\u1010\u103a",
|
||||
"\u1027\u1015\u103c\u102e",
|
||||
"\u1019\u1031",
|
||||
"\u1007\u103d\u1014\u103a",
|
||||
"\u1007\u1030\u101c\u102d\u102f\u1004\u103a",
|
||||
"\u1029\u1002\u102f\u1010\u103a",
|
||||
"\u1005\u1000\u103a\u1010\u1004\u103a\u1018\u102c",
|
||||
"\u1021\u1031\u102c\u1000\u103a\u1010\u102d\u102f\u1018\u102c",
|
||||
"\u1014\u102d\u102f\u101d\u1004\u103a\u1018\u102c",
|
||||
"\u1012\u102e\u1007\u1004\u103a\u1018\u102c"
|
||||
"\u1007\u1030",
|
||||
"\u1029",
|
||||
"\u1005\u1000\u103a",
|
||||
"\u1021\u1031\u102c\u1000\u103a",
|
||||
"\u1014\u102d\u102f",
|
||||
"\u1012\u102e"
|
||||
],
|
||||
"fullDate": "EEEE, y MMMM dd",
|
||||
"longDate": "y MMMM d",
|
||||
"medium": "y MMM d HH:mm:ss",
|
||||
"mediumDate": "y MMM d",
|
||||
"fullDate": "EEEE, dd MMMM y",
|
||||
"longDate": "d MMMM y",
|
||||
"medium": "d MMM y HH:mm:ss",
|
||||
"mediumDate": "d MMM y",
|
||||
"mediumTime": "HH:mm:ss",
|
||||
"short": "yy/MM/dd HH:mm",
|
||||
"shortDate": "yy/MM/dd",
|
||||
"short": "dd-MM-yy HH:mm",
|
||||
"shortDate": "dd-MM-yy",
|
||||
"shortTime": "HH:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
|
||||
@@ -173,7 +173,7 @@ angular.module('ngMessages', [])
|
||||
* at a time and this depends on the prioritization of the messages within the template. (This can
|
||||
* be changed by using the ng-messages-multiple on the directive container.)
|
||||
*
|
||||
* A remote template can also be used to promote message reuseability and messages can also be
|
||||
* A remote template can also be used to promote message reusability and messages can also be
|
||||
* overridden.
|
||||
*
|
||||
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
|
||||
|
||||
Vendored
+25
-11
@@ -243,31 +243,31 @@ angular.mock.$ExceptionHandlerProvider = function() {
|
||||
*
|
||||
* @param {string} mode Mode of operation, defaults to `rethrow`.
|
||||
*
|
||||
* - `rethrow`: If any errors are passed to the handler in tests, it typically means that there
|
||||
* is a bug in the application or test, so this mock will make these tests fail.
|
||||
* - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log`
|
||||
* mode stores an array of errors in `$exceptionHandler.errors`, to allow later
|
||||
* assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and
|
||||
* {@link ngMock.$log#reset reset()}
|
||||
* - `rethrow`: If any errors are passed to the handler in tests, it typically means that there
|
||||
* is a bug in the application or test, so this mock will make these tests fail.
|
||||
* For any implementations that expect exceptions to be thrown, the `rethrow` mode
|
||||
* will also maintain a log of thrown errors.
|
||||
*/
|
||||
this.mode = function(mode) {
|
||||
switch (mode) {
|
||||
case 'rethrow':
|
||||
handler = function(e) {
|
||||
throw e;
|
||||
};
|
||||
break;
|
||||
case 'log':
|
||||
var errors = [];
|
||||
|
||||
switch (mode) {
|
||||
case 'log':
|
||||
case 'rethrow':
|
||||
var errors = [];
|
||||
handler = function(e) {
|
||||
if (arguments.length == 1) {
|
||||
errors.push(e);
|
||||
} else {
|
||||
errors.push([].slice.call(arguments, 0));
|
||||
}
|
||||
if (mode === "rethrow") {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
handler.errors = errors;
|
||||
break;
|
||||
default:
|
||||
@@ -2127,18 +2127,32 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
|
||||
if (window.jasmine || window.mocha) {
|
||||
|
||||
var currentSpec = null,
|
||||
annotatedFunctions = [],
|
||||
isSpecRunning = function() {
|
||||
return !!currentSpec;
|
||||
};
|
||||
|
||||
angular.mock.$$annotate = angular.injector.$$annotate;
|
||||
angular.injector.$$annotate = function(fn) {
|
||||
if (typeof fn === 'function' && !fn.$inject) {
|
||||
annotatedFunctions.push(fn);
|
||||
}
|
||||
return angular.mock.$$annotate.apply(this, arguments);
|
||||
};
|
||||
|
||||
|
||||
(window.beforeEach || window.setup)(function() {
|
||||
annotatedFunctions = [];
|
||||
currentSpec = this;
|
||||
});
|
||||
|
||||
(window.afterEach || window.teardown)(function() {
|
||||
var injector = currentSpec.$injector;
|
||||
|
||||
annotatedFunctions.forEach(function(fn) {
|
||||
delete fn.$inject;
|
||||
});
|
||||
|
||||
angular.forEach(currentSpec.$modules, function(module) {
|
||||
if (module && module.$$hashKey) {
|
||||
module.$$hashKey = undefined;
|
||||
|
||||
+4
-10
@@ -477,21 +477,15 @@ function $RouteProvider() {
|
||||
* definitions will be interpolated into the location's path, while
|
||||
* remaining properties will be treated as query params.
|
||||
*
|
||||
* @param {Object} newParams mapping of URL parameter names to values
|
||||
* @param {!Object<string, string>} newParams mapping of URL parameter names to values
|
||||
*/
|
||||
updateParams: function(newParams) {
|
||||
if (this.current && this.current.$$route) {
|
||||
var searchParams = {}, self=this;
|
||||
|
||||
angular.forEach(Object.keys(newParams), function(key) {
|
||||
if (!self.current.pathParams[key]) searchParams[key] = newParams[key];
|
||||
});
|
||||
|
||||
newParams = angular.extend({}, this.current.params, newParams);
|
||||
$location.path(interpolate(this.current.$$route.originalPath, newParams));
|
||||
$location.search(angular.extend({}, $location.search(), searchParams));
|
||||
}
|
||||
else {
|
||||
// interpolate modifies newParams, only query params are left
|
||||
$location.search(newParams);
|
||||
} else {
|
||||
throw $routeMinErr('norout', 'Tried updating route when with no current route');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,14 +271,14 @@ function htmlParser(html, handler) {
|
||||
}
|
||||
}
|
||||
var index, chars, match, stack = [], last = html, text;
|
||||
stack.last = function() { return stack[ stack.length - 1 ]; };
|
||||
stack.last = function() { return stack[stack.length - 1]; };
|
||||
|
||||
while (html) {
|
||||
text = '';
|
||||
chars = true;
|
||||
|
||||
// Make sure we're not in a script or style element
|
||||
if (!stack.last() || !specialElements[ stack.last() ]) {
|
||||
if (!stack.last() || !specialElements[stack.last()]) {
|
||||
|
||||
// Comment
|
||||
if (html.indexOf("<!--") === 0) {
|
||||
@@ -336,7 +336,8 @@ function htmlParser(html, handler) {
|
||||
}
|
||||
|
||||
} else {
|
||||
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
|
||||
// IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w].
|
||||
html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
|
||||
function(all, text) {
|
||||
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
|
||||
|
||||
@@ -360,17 +361,17 @@ function htmlParser(html, handler) {
|
||||
|
||||
function parseStartTag(tag, tagName, rest, unary) {
|
||||
tagName = angular.lowercase(tagName);
|
||||
if (blockElements[ tagName ]) {
|
||||
while (stack.last() && inlineElements[ stack.last() ]) {
|
||||
if (blockElements[tagName]) {
|
||||
while (stack.last() && inlineElements[stack.last()]) {
|
||||
parseEndTag("", stack.last());
|
||||
}
|
||||
}
|
||||
|
||||
if (optionalEndTagElements[ tagName ] && stack.last() == tagName) {
|
||||
if (optionalEndTagElements[tagName] && stack.last() == tagName) {
|
||||
parseEndTag("", tagName);
|
||||
}
|
||||
|
||||
unary = voidElements[ tagName ] || !!unary;
|
||||
unary = voidElements[tagName] || !!unary;
|
||||
|
||||
if (!unary)
|
||||
stack.push(tagName);
|
||||
@@ -395,13 +396,13 @@ function htmlParser(html, handler) {
|
||||
if (tagName)
|
||||
// Find the closest opened tag of the same type
|
||||
for (pos = stack.length - 1; pos >= 0; pos--)
|
||||
if (stack[ pos ] == tagName)
|
||||
if (stack[pos] == tagName)
|
||||
break;
|
||||
|
||||
if (pos >= 0) {
|
||||
// Close all the open elements, up the stack
|
||||
for (i = stack.length - 1; i >= pos; i--)
|
||||
if (handler.end) handler.end(stack[ i ]);
|
||||
if (handler.end) handler.end(stack[i]);
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos;
|
||||
|
||||
@@ -68,19 +68,31 @@ angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorF
|
||||
try {
|
||||
var $window = self.getWindow_();
|
||||
|
||||
if ($window.angular) {
|
||||
// Disable animations
|
||||
$window.angular.resumeBootstrap([['$provide', function($provide) {
|
||||
return ['$animate', function($animate) {
|
||||
$animate.enabled(false);
|
||||
}];
|
||||
}]]);
|
||||
if (!$window.angular) {
|
||||
self.executeAction(loadFn);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$window.angular.resumeBootstrap) {
|
||||
$window.angular.resumeDeferredBootstrap = resumeDeferredBootstrap;
|
||||
} else {
|
||||
resumeDeferredBootstrap();
|
||||
}
|
||||
|
||||
self.executeAction(loadFn);
|
||||
} catch (e) {
|
||||
errorFn(e);
|
||||
}
|
||||
|
||||
function resumeDeferredBootstrap() {
|
||||
// Disable animations
|
||||
var $injector = $window.angular.resumeBootstrap([['$provide', function($provide) {
|
||||
return ['$animate', function($animate) {
|
||||
$animate.enabled(false);
|
||||
}];
|
||||
}]]);
|
||||
self.rootElement = $injector.get('$rootElement')[0];
|
||||
self.executeAction(loadFn);
|
||||
}
|
||||
}).attr('src', url);
|
||||
|
||||
// for IE compatibility set the name *after* setting the frame url
|
||||
@@ -105,7 +117,14 @@ angular.scenario.Application.prototype.executeAction = function(action) {
|
||||
if (!$window.angular) {
|
||||
return action.call(this, $window, _jQuery($window.document));
|
||||
}
|
||||
angularInit($window.document, function(element) {
|
||||
|
||||
if (!!this.rootElement) {
|
||||
executeWithElement(this.rootElement);
|
||||
} else {
|
||||
angularInit($window.document, angular.bind(this, executeWithElement));
|
||||
}
|
||||
|
||||
function executeWithElement(element) {
|
||||
var $injector = $window.angular.element(element).injector();
|
||||
var $element = _jQuery(element);
|
||||
|
||||
@@ -118,5 +137,5 @@ angular.scenario.Application.prototype.executeAction = function(action) {
|
||||
action.call(self, $window, $element);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -304,7 +304,7 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
|
||||
var element = windowJquery(this),
|
||||
bindings;
|
||||
if (bindings = element.data('$binding')) {
|
||||
for (var expressions = [], binding, j=0, jj=bindings.length; j < jj; j++) {
|
||||
for (var expressions = [], binding, j=0, jj=bindings.length; j < jj; j++) {
|
||||
binding = bindings[j];
|
||||
|
||||
if (binding.expressions) {
|
||||
|
||||
@@ -55,8 +55,7 @@
|
||||
if (window.WebKitTransitionEvent) {
|
||||
evnt = new WebKitTransitionEvent(eventType, eventData);
|
||||
evnt.initEvent(eventType, false, true);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
try {
|
||||
evnt = new TransitionEvent(eventType, eventData);
|
||||
}
|
||||
@@ -65,13 +64,11 @@
|
||||
evnt.initTransitionEvent(eventType, null, null, null, eventData.elapsedTime || 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (/animationend/.test(eventType)) {
|
||||
} else if (/animationend/.test(eventType)) {
|
||||
if (window.WebKitAnimationEvent) {
|
||||
evnt = new WebKitAnimationEvent(eventType, eventData);
|
||||
evnt.initEvent(eventType, false, true);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
try {
|
||||
evnt = new AnimationEvent(eventType, eventData);
|
||||
}
|
||||
@@ -80,8 +77,7 @@
|
||||
evnt.initAnimationEvent(eventType, null, null, null, eventData.elapsedTime || 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
evnt = document.createEvent('MouseEvents');
|
||||
x = x || 0;
|
||||
y = y || 0;
|
||||
|
||||
@@ -1081,6 +1081,26 @@ describe('angular', function() {
|
||||
window.name = originalName;
|
||||
});
|
||||
|
||||
it('should provide injector for deferred bootstrap', function() {
|
||||
var injector;
|
||||
window.name = 'NG_DEFER_BOOTSTRAP!';
|
||||
|
||||
injector = angular.bootstrap(element);
|
||||
expect(injector).toBeUndefined();
|
||||
|
||||
injector = angular.resumeBootstrap();
|
||||
expect(injector).toBeDefined();
|
||||
});
|
||||
|
||||
it('should resume deferred bootstrap, if defined', function() {
|
||||
var injector;
|
||||
window.name = 'NG_DEFER_BOOTSTRAP!';
|
||||
|
||||
angular.resumeDeferredBootstrap = noop;
|
||||
var spy = spyOn(angular, "resumeDeferredBootstrap");
|
||||
injector = angular.bootstrap(element);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should wait for extra modules', function() {
|
||||
window.name = 'NG_DEFER_BOOTSTRAP!';
|
||||
|
||||
@@ -238,7 +238,11 @@ describe('injector', function() {
|
||||
|
||||
|
||||
it('should publish annotate API', function() {
|
||||
expect(injector.annotate).toBe(annotate);
|
||||
expect(angular.mock.$$annotate).toBe(annotate);
|
||||
spyOn(angular.mock, '$$annotate').andCallThrough();
|
||||
function fn() {}
|
||||
injector.annotate(fn);
|
||||
expect(angular.mock.$$annotate).toHaveBeenCalledWith(fn);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -972,7 +976,7 @@ describe('strict-di injector', function() {
|
||||
});
|
||||
});
|
||||
inject(function($injector) {
|
||||
expect (function() {
|
||||
expect(function() {
|
||||
$injector.invoke(function($test2) {});
|
||||
}).toThrowMinErr('$injector', 'strictdi');
|
||||
});
|
||||
@@ -986,7 +990,7 @@ describe('strict-di injector', function() {
|
||||
});
|
||||
});
|
||||
inject(function($injector) {
|
||||
expect (function() {
|
||||
expect(function() {
|
||||
$injector.invoke(['$test', function($test) {}]);
|
||||
}).toThrowMinErr('$injector', 'strictdi');
|
||||
});
|
||||
|
||||
@@ -9,22 +9,29 @@ describe('private mocks', function() {
|
||||
var doc = $document[0];
|
||||
var count = doc.styleSheets.length;
|
||||
var stylesheet = createMockStyleSheet($document, $window);
|
||||
expect(doc.styleSheets.length).toBe(count + 1);
|
||||
var elm;
|
||||
runs(function() {
|
||||
expect(doc.styleSheets.length).toBe(count + 1);
|
||||
|
||||
angular.element(doc.body).append($rootElement);
|
||||
angular.element(doc.body).append($rootElement);
|
||||
|
||||
var elm = $compile('<div class="padded">...</div>')($rootScope);
|
||||
$rootElement.append(elm);
|
||||
elm = $compile('<div class="padded">...</div>')($rootScope);
|
||||
$rootElement.append(elm);
|
||||
|
||||
expect(getStyle(elm, 'paddingTop')).toBe('0px');
|
||||
expect(getStyle(elm, 'paddingTop')).toBe('0px');
|
||||
|
||||
stylesheet.addRule('.padded', 'padding-top:2px');
|
||||
stylesheet.addRule('.padded', 'padding-top:2px');
|
||||
});
|
||||
|
||||
expect(getStyle(elm, 'paddingTop')).toBe('2px');
|
||||
waitsFor(function() {
|
||||
return getStyle(elm, 'paddingTop') === '2px';
|
||||
});
|
||||
|
||||
stylesheet.destroy();
|
||||
runs(function() {
|
||||
stylesheet.destroy();
|
||||
|
||||
expect(getStyle(elm, 'paddingTop')).toBe('0px');
|
||||
expect(getStyle(elm, 'paddingTop')).toBe('0px');
|
||||
});
|
||||
|
||||
function getStyle(element, key) {
|
||||
var node = element[0];
|
||||
|
||||
@@ -339,3 +339,67 @@ window.dump = function() {
|
||||
return angular.mock.dump(arg);
|
||||
}));
|
||||
};
|
||||
|
||||
function getInputCompileHelper(currentSpec) {
|
||||
|
||||
var helper = {};
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('attrCapture', function() {
|
||||
return function(scope, element, $attrs) {
|
||||
helper.attrs = $attrs;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope, $sniffer) {
|
||||
|
||||
helper.compileInput = function(inputHtml, mockValidity, scope) {
|
||||
|
||||
scope = helper.scope = scope || $rootScope;
|
||||
|
||||
// Create the input element and dealoc when done
|
||||
helper.inputElm = jqLite(inputHtml);
|
||||
|
||||
// Set up mock validation if necessary
|
||||
if (isObject(mockValidity)) {
|
||||
VALIDITY_STATE_PROPERTY = 'ngMockValidity';
|
||||
helper.inputElm.prop(VALIDITY_STATE_PROPERTY, mockValidity);
|
||||
currentSpec.after(function() {
|
||||
VALIDITY_STATE_PROPERTY = 'validity';
|
||||
});
|
||||
}
|
||||
|
||||
// Create the form element and dealoc when done
|
||||
helper.formElm = jqLite('<form name="form"></form>');
|
||||
helper.formElm.append(helper.inputElm);
|
||||
|
||||
// Compile the lot and return the input element
|
||||
$compile(helper.formElm)(scope);
|
||||
|
||||
spyOn(scope.form, '$addControl').andCallThrough();
|
||||
spyOn(scope.form, '$$renameControl').andCallThrough();
|
||||
|
||||
scope.$digest();
|
||||
|
||||
return helper.inputElm;
|
||||
};
|
||||
|
||||
helper.changeInputValueTo = function(value) {
|
||||
helper.inputElm.val(value);
|
||||
browserTrigger(helper.inputElm, $sniffer.hasEvent('input') ? 'input' : 'change');
|
||||
};
|
||||
|
||||
helper.changeGivenInputTo = function(inputElm, value) {
|
||||
inputElm.val(value);
|
||||
browserTrigger(inputElm, $sniffer.hasEvent('input') ? 'input' : 'change');
|
||||
};
|
||||
|
||||
helper.dealoc = function() {
|
||||
dealoc(helper.inputElm);
|
||||
dealoc(helper.formElm);
|
||||
};
|
||||
});
|
||||
|
||||
return helper;
|
||||
}
|
||||
|
||||
@@ -395,7 +395,7 @@ describe('browser', function() {
|
||||
});
|
||||
|
||||
|
||||
it ('should return a value for an existing cookie', function() {
|
||||
it('should return a value for an existing cookie', function() {
|
||||
document.cookie = "foo=bar=baz;path=/";
|
||||
expect(browser.cookies().foo).toEqual('bar=baz');
|
||||
});
|
||||
@@ -408,7 +408,7 @@ describe('browser', function() {
|
||||
expect(browser.cookies()['foo']).toBe('"first"');
|
||||
});
|
||||
|
||||
it ('should decode cookie values that were encoded by puts', function() {
|
||||
it('should decode cookie values that were encoded by puts', function() {
|
||||
document.cookie = "cookie2%3Dbar%3Bbaz=val%3Due;path=/";
|
||||
expect(browser.cookies()['cookie2=bar;baz']).toEqual('val=ue');
|
||||
});
|
||||
|
||||
+70
-19
@@ -423,8 +423,7 @@ describe('$compile', function() {
|
||||
describe('multiple directives per element', function() {
|
||||
it('should allow multiple directives per element', inject(function($compile, $rootScope, log) {
|
||||
element = $compile(
|
||||
'<span greet="angular" log="L" x-high-log="H" data-medium-log="M"></span>')
|
||||
($rootScope);
|
||||
'<span greet="angular" log="L" x-high-log="H" data-medium-log="M"></span>')($rootScope);
|
||||
expect(element.text()).toEqual('Hello angular');
|
||||
expect(log).toEqual('L; M; H');
|
||||
}));
|
||||
@@ -443,6 +442,17 @@ describe('$compile', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should allow directives in SVG element classes', inject(function($compile, $rootScope, log) {
|
||||
if (!window.SVGElement) return;
|
||||
element = $compile('<svg><text class="greet: angular; log:123;"></text></svg>')($rootScope);
|
||||
var text = element.children().eq(0);
|
||||
// In old Safari, SVG elements don't have innerHTML, so element.html() won't work
|
||||
// (https://bugs.webkit.org/show_bug.cgi?id=136903)
|
||||
expect(text.text()).toEqual('Hello angular');
|
||||
expect(log).toEqual('123');
|
||||
}));
|
||||
|
||||
|
||||
it('should ignore not set CSS classes on SVG elements', inject(function($compile, $rootScope, log) {
|
||||
if (!window.SVGElement) return;
|
||||
// According to spec SVG element className property is readonly, but only FF
|
||||
@@ -596,8 +606,7 @@ describe('$compile', function() {
|
||||
describe('priority', function() {
|
||||
it('should honor priority', inject(function($compile, $rootScope, log) {
|
||||
element = $compile(
|
||||
'<span log="L" x-high-log="H" data-medium-log="M"></span>')
|
||||
($rootScope);
|
||||
'<span log="L" x-high-log="H" data-medium-log="M"></span>')($rootScope);
|
||||
expect(log).toEqual('L; M; H');
|
||||
}));
|
||||
});
|
||||
@@ -798,8 +807,7 @@ describe('$compile', function() {
|
||||
|
||||
|
||||
it('should compile template when replacing', inject(function($compile, $rootScope, log) {
|
||||
element = $compile('<div><div replace medium-log>ignore</div><div>')
|
||||
($rootScope);
|
||||
element = $compile('<div><div replace medium-log>ignore</div><div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('Replace!');
|
||||
expect(log).toEqual('LOG; HIGH; MEDIUM');
|
||||
@@ -807,8 +815,7 @@ describe('$compile', function() {
|
||||
|
||||
|
||||
it('should compile template when appending', inject(function($compile, $rootScope, log) {
|
||||
element = $compile('<div><div append medium-log>ignore</div><div>')
|
||||
($rootScope);
|
||||
element = $compile('<div><div append medium-log>ignore</div><div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('Append!');
|
||||
expect(log).toEqual('LOG; HIGH; MEDIUM');
|
||||
@@ -817,8 +824,7 @@ describe('$compile', function() {
|
||||
|
||||
it('should merge attributes including style attr', inject(function($compile, $rootScope) {
|
||||
element = $compile(
|
||||
'<div><div replace class="medium-log" style="height: 20px" ></div><div>')
|
||||
($rootScope);
|
||||
'<div><div replace class="medium-log" style="height: 20px" ></div><div>')($rootScope);
|
||||
var div = element.find('div');
|
||||
expect(div.hasClass('medium-log')).toBe(true);
|
||||
expect(div.hasClass('log')).toBe(true);
|
||||
@@ -830,8 +836,7 @@ describe('$compile', function() {
|
||||
|
||||
it('should not merge attributes if they are the same', inject(function($compile, $rootScope) {
|
||||
element = $compile(
|
||||
'<div><div nomerge class="medium-log" id="myid"></div><div>')
|
||||
($rootScope);
|
||||
'<div><div nomerge class="medium-log" id="myid"></div><div>')($rootScope);
|
||||
var div = element.find('div');
|
||||
expect(div.hasClass('medium-log')).toBe(true);
|
||||
expect(div.hasClass('log')).toBe(true);
|
||||
@@ -1104,6 +1109,29 @@ describe('$compile', function() {
|
||||
expect(element.find('p').text()).toBe('Hello, world!');
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep prototype properties on directive', function() {
|
||||
module(function() {
|
||||
function DirectiveClass() {
|
||||
this.restrict = 'E';
|
||||
this.template = "<p>{{value}}</p>";
|
||||
}
|
||||
|
||||
DirectiveClass.prototype.compile = function() {
|
||||
return function(scope, element, attrs) {
|
||||
scope.value = "Test Value";
|
||||
};
|
||||
};
|
||||
|
||||
directive('templateUrlWithPrototype', valueFn(new DirectiveClass()));
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<template-url-with-prototype><template-url-with-prototype>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.find("p")[0].innerHTML).toEqual("Test Value");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2023,6 +2051,32 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep prototype properties on sync version of async directive', function() {
|
||||
module(function() {
|
||||
function DirectiveClass() {
|
||||
this.restrict = 'E';
|
||||
this.templateUrl = "test.html";
|
||||
}
|
||||
|
||||
DirectiveClass.prototype.compile = function() {
|
||||
return function(scope, element, attrs) {
|
||||
scope.value = "Test Value";
|
||||
};
|
||||
};
|
||||
|
||||
directive('templateUrlWithPrototype', valueFn(new DirectiveClass()));
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope, $httpBackend) {
|
||||
$httpBackend.whenGET('test.html').
|
||||
respond('<p>{{value}}</p>');
|
||||
element = $compile('<template-url-with-prototype><template-url-with-prototype>')($rootScope);
|
||||
$httpBackend.flush();
|
||||
$rootScope.$digest();
|
||||
expect(element.find("p")[0].innerHTML).toEqual("Test Value");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -4599,8 +4653,7 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
element = $compile('<div><div trans>T:{{x}}-{{$parent.$id}}-{{$id}}<span>;</span></div></div>')
|
||||
($rootScope);
|
||||
element = $compile('<div><div trans>T:{{x}}-{{$parent.$id}}-{{$id}}<span>;</span></div></div>')($rootScope);
|
||||
$rootScope.x = 'root';
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('W:iso-1-2;T:root-2-3;');
|
||||
@@ -4885,8 +4938,7 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
element = $compile('<div><div trans>T:{{$$transcluded}}</div></div>')
|
||||
($rootScope);
|
||||
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');
|
||||
@@ -5717,8 +5769,7 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
element = $compile('<div><div high-log trans="text" log>{{$parent.$id}}-{{$id}};</div></div>')
|
||||
($rootScope);
|
||||
element = $compile('<div><div high-log trans="text" log>{{$parent.$id}}-{{$id}};</div></div>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(log).toEqual('compile: <!-- trans: text -->; link; LOG; LOG; HIGH');
|
||||
expect(element.text()).toEqual('1-2;1-3;');
|
||||
@@ -6054,7 +6105,7 @@ describe('$compile', function() {
|
||||
link: function(scope, element, attrs, ctrl, transclude) {
|
||||
|
||||
// We use timeout here to simulate how ng-if works
|
||||
$timeout(function() {
|
||||
$timeout(function() {
|
||||
transclude(function(child) { element.append(child); });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,6 +27,18 @@ describe('$controller', function() {
|
||||
expect(ctrl instanceof FooCtrl).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow registration of bound controller functions', function() {
|
||||
var FooCtrl = function($scope) { $scope.foo = 'bar'; },
|
||||
scope = {},
|
||||
ctrl;
|
||||
|
||||
var BoundFooCtrl = FooCtrl.bind(null);
|
||||
|
||||
$controllerProvider.register('FooCtrl', ['$scope', BoundFooCtrl]);
|
||||
ctrl = $controller('FooCtrl', {$scope: scope});
|
||||
|
||||
expect(scope.foo).toBe('bar');
|
||||
});
|
||||
|
||||
it('should allow registration of map of controllers', function() {
|
||||
var FooCtrl = function($scope) { $scope.foo = 'foo'; },
|
||||
@@ -78,7 +90,16 @@ describe('$controller', function() {
|
||||
var foo = $controller('a.Foo', {$scope: scope});
|
||||
expect(foo).toBeDefined();
|
||||
expect(foo instanceof Foo).toBe(true);
|
||||
}));
|
||||
}));
|
||||
|
||||
|
||||
it('should throw ctrlfmt if name contains spaces', function() {
|
||||
expect(function() {
|
||||
$controller('ctrl doom');
|
||||
}).toThrowMinErr("$controller", "ctrlfmt",
|
||||
"Badly formed controller string 'ctrl doom'. " +
|
||||
"Must match `__name__ as __id__` or `__name__`.");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -156,5 +177,37 @@ describe('$controller', function() {
|
||||
}).toThrowMinErr("$controller", "noscp", "Cannot export controller 'a.b.FooCtrl' as 'foo'! No $scope object provided via `locals`.");
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should throw ctrlfmt if identifier contains non-ident characters', function() {
|
||||
expect(function() {
|
||||
$controller('ctrl as foo<bar');
|
||||
}).toThrowMinErr("$controller", "ctrlfmt",
|
||||
"Badly formed controller string 'ctrl as foo<bar'. " +
|
||||
"Must match `__name__ as __id__` or `__name__`.");
|
||||
});
|
||||
|
||||
|
||||
it('should throw ctrlfmt if identifier contains spaces', function() {
|
||||
expect(function() {
|
||||
$controller('ctrl as foo bar');
|
||||
}).toThrowMinErr("$controller", "ctrlfmt",
|
||||
"Badly formed controller string 'ctrl as foo bar'. " +
|
||||
"Must match `__name__ as __id__` or `__name__`.");
|
||||
});
|
||||
|
||||
|
||||
it('should throw ctrlfmt if identifier missing after " as "', function() {
|
||||
expect(function() {
|
||||
$controller('ctrl as ');
|
||||
}).toThrowMinErr("$controller", "ctrlfmt",
|
||||
"Badly formed controller string 'ctrl as '. " +
|
||||
"Must match `__name__ as __id__` or `__name__`.");
|
||||
expect(function() {
|
||||
$controller('ctrl as');
|
||||
}).toThrowMinErr("$controller", "ctrlfmt",
|
||||
"Badly formed controller string 'ctrl as'. " +
|
||||
"Must match `__name__ as __id__` or `__name__`.");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,25 @@
|
||||
describe('a', function() {
|
||||
var element, $compile, $rootScope;
|
||||
|
||||
beforeEach(module(function($compileProvider) {
|
||||
$compileProvider.
|
||||
directive('linkTo', valueFn({
|
||||
restrict: 'A',
|
||||
template: '<div class="my-link"><a href="{{destination}}">{{destination}}</a></div>',
|
||||
replace: true,
|
||||
scope: {
|
||||
destination: '@linkTo'
|
||||
}
|
||||
})).
|
||||
directive('linkNot', valueFn({
|
||||
restrict: 'A',
|
||||
template: '<div class="my-link"><a href>{{destination}}</a></div>',
|
||||
replace: true,
|
||||
scope: {
|
||||
destination: '@linkNot'
|
||||
}
|
||||
}));
|
||||
}));
|
||||
|
||||
beforeEach(inject(function(_$compile_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
@@ -76,6 +95,40 @@ describe('a', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should not preventDefault if anchor element is replaced with href-containing element', function() {
|
||||
spyOn(jqLite.prototype, 'on').andCallThrough();
|
||||
element = $compile('<a link-to="https://www.google.com">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
var child = element.children('a');
|
||||
var preventDefault = jasmine.createSpy('preventDefault');
|
||||
|
||||
child.triggerHandler({
|
||||
type: 'click',
|
||||
preventDefault: preventDefault
|
||||
});
|
||||
|
||||
expect(preventDefault).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should preventDefault if anchor element is replaced with element without href attribute', function() {
|
||||
spyOn(jqLite.prototype, 'on').andCallThrough();
|
||||
element = $compile('<a link-not="https://www.google.com">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
var child = element.children('a');
|
||||
var preventDefault = jasmine.createSpy('preventDefault');
|
||||
|
||||
child.triggerHandler({
|
||||
type: 'click',
|
||||
preventDefault: preventDefault
|
||||
});
|
||||
|
||||
expect(preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
if (isDefined(window.SVGElement)) {
|
||||
describe('SVGAElement', function() {
|
||||
it('should prevent default action to be executed when href is empty', function() {
|
||||
|
||||
@@ -463,7 +463,7 @@ describe('form', function() {
|
||||
doc = jqLite(
|
||||
'<form name="parent">' +
|
||||
'<div class="ng-form" name="child">' +
|
||||
'<input ng-if="inputPresent" ng-model="modelA" name="inputA" required>' +
|
||||
'<input ng-if="inputPresent" ng-model="modelA" name="inputA" required maxlength="10">' +
|
||||
'</div>' +
|
||||
'</form>');
|
||||
$compile(doc)(scope);
|
||||
@@ -476,23 +476,75 @@ describe('form', function() {
|
||||
|
||||
expect(parent).toBeDefined();
|
||||
expect(child).toBeDefined();
|
||||
|
||||
expect(parent.$error.required).toEqual([child]);
|
||||
expect(parent.$$success.maxlength).toEqual([child]);
|
||||
|
||||
expect(child.$error.required).toEqual([input]);
|
||||
expect(child.$$success.maxlength).toEqual([input]);
|
||||
|
||||
expect(doc.hasClass('ng-invalid')).toBe(true);
|
||||
expect(doc.hasClass('ng-invalid-required')).toBe(true);
|
||||
expect(doc.hasClass('ng-valid-maxlength')).toBe(true);
|
||||
expect(doc.find('div').hasClass('ng-invalid')).toBe(true);
|
||||
expect(doc.find('div').hasClass('ng-invalid-required')).toBe(true);
|
||||
expect(doc.find('div').hasClass('ng-valid-maxlength')).toBe(true);
|
||||
|
||||
//remove child input
|
||||
scope.inputPresent = false;
|
||||
scope.$apply();
|
||||
scope.$apply('inputPresent = false');
|
||||
|
||||
expect(parent.$error.required).toBeFalsy();
|
||||
expect(parent.$$success.maxlength).toBeFalsy();
|
||||
|
||||
expect(child.$error.required).toBeFalsy();
|
||||
expect(child.$$success.maxlength).toBeFalsy();
|
||||
|
||||
expect(doc.hasClass('ng-valid')).toBe(true);
|
||||
expect(doc.hasClass('ng-valid-required')).toBe(false);
|
||||
expect(doc.hasClass('ng-invalid-required')).toBe(false);
|
||||
expect(doc.hasClass('ng-valid-maxlength')).toBe(false);
|
||||
expect(doc.hasClass('ng-invalid-maxlength')).toBe(false);
|
||||
|
||||
expect(doc.find('div').hasClass('ng-valid')).toBe(true);
|
||||
expect(doc.find('div').hasClass('ng-valid-required')).toBe(false);
|
||||
expect(doc.find('div').hasClass('ng-invalid-required')).toBe(false);
|
||||
expect(doc.find('div').hasClass('ng-valid-maxlength')).toBe(false);
|
||||
expect(doc.find('div').hasClass('ng-invalid-maxlength')).toBe(false);
|
||||
});
|
||||
|
||||
it('should deregister a input that is $pending when it is removed from DOM', function() {
|
||||
doc = jqLite(
|
||||
'<form name="parent">' +
|
||||
'<div class="ng-form" name="child">' +
|
||||
'<input ng-if="inputPresent" ng-model="modelA" name="inputA">' +
|
||||
'</div>' +
|
||||
'</form>');
|
||||
$compile(doc)(scope);
|
||||
scope.$apply('inputPresent = true');
|
||||
|
||||
var parent = scope.parent;
|
||||
var child = scope.child;
|
||||
var input = child.inputA;
|
||||
|
||||
scope.$apply(child.inputA.$setValidity('fake', undefined));
|
||||
|
||||
expect(parent).toBeDefined();
|
||||
expect(child).toBeDefined();
|
||||
|
||||
expect(parent.$pending.fake).toEqual([child]);
|
||||
expect(child.$pending.fake).toEqual([input]);
|
||||
|
||||
expect(doc.hasClass('ng-pending')).toBe(true);
|
||||
expect(doc.find('div').hasClass('ng-pending')).toBe(true);
|
||||
|
||||
//remove child input
|
||||
scope.$apply('inputPresent = false');
|
||||
|
||||
expect(parent.$pending).toBeUndefined();
|
||||
expect(child.$pending).toBeUndefined();
|
||||
|
||||
expect(doc.hasClass('ng-pending')).toBe(false);
|
||||
expect(doc.find('div').hasClass('ng-pending')).toBe(false);
|
||||
});
|
||||
|
||||
it('should leave the parent form invalid when deregister a removed input', function() {
|
||||
|
||||
+991
-3588
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
/* globals getInputCompileHelper: false */
|
||||
|
||||
describe('ngChange', function() {
|
||||
|
||||
var helper, $rootScope;
|
||||
|
||||
beforeEach(function() {
|
||||
helper = getInputCompileHelper(this);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
helper.dealoc();
|
||||
});
|
||||
|
||||
|
||||
beforeEach(inject(function(_$rootScope_) {
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
it('should $eval expression after new value is set in the model', function() {
|
||||
helper.compileInput('<input type="text" ng-model="value" ng-change="change()" />');
|
||||
|
||||
$rootScope.change = jasmine.createSpy('change').andCallFake(function() {
|
||||
expect($rootScope.value).toBe('new value');
|
||||
});
|
||||
|
||||
helper.changeInputValueTo('new value');
|
||||
expect($rootScope.change).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
it('should not $eval the expression if changed from model', function() {
|
||||
helper.compileInput('<input type="text" ng-model="value" ng-change="change()" />');
|
||||
|
||||
$rootScope.change = jasmine.createSpy('change');
|
||||
$rootScope.$apply('value = true');
|
||||
|
||||
expect($rootScope.change).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should $eval ngChange expression on checkbox', function() {
|
||||
var inputElm = helper.compileInput('<input type="checkbox" ng-model="foo" ng-change="changeFn()">');
|
||||
|
||||
$rootScope.changeFn = jasmine.createSpy('changeFn');
|
||||
expect($rootScope.changeFn).not.toHaveBeenCalled();
|
||||
|
||||
browserTrigger(inputElm, 'click');
|
||||
expect($rootScope.changeFn).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
it('should be able to change the model and via that also update the view', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-change="value=\'b\'" />');
|
||||
|
||||
helper.changeInputValueTo('a');
|
||||
expect(inputElm.val()).toBe('b');
|
||||
});
|
||||
});
|
||||
@@ -37,6 +37,10 @@ describe('ngController', function() {
|
||||
this.mark = 'works';
|
||||
});
|
||||
|
||||
var Foo = function($scope) {
|
||||
$scope.mark = 'foo';
|
||||
};
|
||||
$controllerProvider.register('BoundFoo', ['$scope', Foo.bind(null)]);
|
||||
}));
|
||||
|
||||
afterEach(function() {
|
||||
@@ -50,6 +54,11 @@ describe('ngController', function() {
|
||||
expect(element.text()).toBe('Hello Misko!');
|
||||
}));
|
||||
|
||||
it('should instantiate bound constructor functions', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div ng-controller="BoundFoo">{{mark}}</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toBe('foo');
|
||||
}));
|
||||
|
||||
it('should publish controller into scope', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div ng-controller="Public as p">{{p.mark}}</div>')($rootScope);
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
'use strict';
|
||||
|
||||
/* globals getInputCompileHelper: false */
|
||||
|
||||
describe('ngList', function() {
|
||||
|
||||
var helper, $rootScope;
|
||||
|
||||
beforeEach(function() {
|
||||
helper = getInputCompileHelper(this);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
helper.dealoc();
|
||||
});
|
||||
|
||||
|
||||
beforeEach(inject(function(_$rootScope_) {
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
it('should parse text into an array', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="list" ng-list />');
|
||||
|
||||
// model -> view
|
||||
$rootScope.$apply("list = ['x', 'y', 'z']");
|
||||
expect(inputElm.val()).toBe('x, y, z');
|
||||
|
||||
// view -> model
|
||||
helper.changeInputValueTo('1, 2, 3');
|
||||
expect($rootScope.list).toEqual(['1', '2', '3']);
|
||||
});
|
||||
|
||||
|
||||
it("should not clobber text if model changes due to itself", function() {
|
||||
// When the user types 'a,b' the 'a,' stage parses to ['a'] but if the
|
||||
// $parseModel function runs it will change to 'a', in essence preventing
|
||||
// the user from ever typing ','.
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="list" ng-list />');
|
||||
|
||||
helper.changeInputValueTo('a ');
|
||||
expect(inputElm.val()).toEqual('a ');
|
||||
expect($rootScope.list).toEqual(['a']);
|
||||
|
||||
helper.changeInputValueTo('a ,');
|
||||
expect(inputElm.val()).toEqual('a ,');
|
||||
expect($rootScope.list).toEqual(['a']);
|
||||
|
||||
helper.changeInputValueTo('a , ');
|
||||
expect(inputElm.val()).toEqual('a , ');
|
||||
expect($rootScope.list).toEqual(['a']);
|
||||
|
||||
helper.changeInputValueTo('a , b');
|
||||
expect(inputElm.val()).toEqual('a , b');
|
||||
expect($rootScope.list).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
|
||||
it('should convert empty string to an empty array', function() {
|
||||
helper.compileInput('<input type="text" ng-model="list" ng-list />');
|
||||
|
||||
helper.changeInputValueTo('');
|
||||
expect($rootScope.list).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
it('should be invalid if required and empty', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-list ng-model="list" required>');
|
||||
helper.changeInputValueTo('');
|
||||
expect($rootScope.list).toBeUndefined();
|
||||
expect(inputElm).toBeInvalid();
|
||||
helper.changeInputValueTo('a,b');
|
||||
expect($rootScope.list).toEqual(['a','b']);
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
describe('with a custom separator', function() {
|
||||
it('should split on the custom separator', function() {
|
||||
helper.compileInput('<input type="text" ng-model="list" ng-list=":" />');
|
||||
|
||||
helper.changeInputValueTo('a,a');
|
||||
expect($rootScope.list).toEqual(['a,a']);
|
||||
|
||||
helper.changeInputValueTo('a:b');
|
||||
expect($rootScope.list).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
|
||||
it("should join the list back together with the custom separator", function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="list" ng-list=" : " />');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.list = ['x', 'y', 'z'];
|
||||
});
|
||||
expect(inputElm.val()).toBe('x : y : z');
|
||||
});
|
||||
});
|
||||
|
||||
describe('(with ngTrim undefined or true)', function() {
|
||||
|
||||
it('should ignore separator whitespace when splitting', function() {
|
||||
helper.compileInput('<input type="text" ng-model="list" ng-list=" | " />');
|
||||
|
||||
helper.changeInputValueTo('a|b');
|
||||
expect($rootScope.list).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('should trim whitespace from each list item', function() {
|
||||
helper.compileInput('<input type="text" ng-model="list" ng-list="|" />');
|
||||
|
||||
helper.changeInputValueTo('a | b');
|
||||
expect($rootScope.list).toEqual(['a', 'b']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('(with ngTrim set to false)', function() {
|
||||
|
||||
it('should use separator whitespace when splitting', function() {
|
||||
helper.compileInput('<input type="text" ng-model="list" ng-trim="false" ng-list=" | " />');
|
||||
|
||||
helper.changeInputValueTo('a|b');
|
||||
expect($rootScope.list).toEqual(['a|b']);
|
||||
|
||||
helper.changeInputValueTo('a | b');
|
||||
expect($rootScope.list).toEqual(['a','b']);
|
||||
|
||||
});
|
||||
|
||||
it("should not trim whitespace from each list item", function() {
|
||||
helper.compileInput('<input type="text" ng-model="list" ng-trim="false" ng-list="|" />');
|
||||
helper.changeInputValueTo('a | b');
|
||||
expect($rootScope.list).toEqual(['a ',' b']);
|
||||
});
|
||||
|
||||
it("should support splitting on newlines", function() {
|
||||
helper.compileInput('<textarea type="text" ng-model="list" ng-trim="false" ng-list=" "></textarea');
|
||||
helper.changeInputValueTo('a\nb');
|
||||
expect($rootScope.list).toEqual(['a','b']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -474,6 +474,8 @@ describe('ngRepeat', function() {
|
||||
'this',
|
||||
'undefined',
|
||||
'$parent',
|
||||
'$root',
|
||||
'$id',
|
||||
'$index',
|
||||
'$first',
|
||||
'$middle',
|
||||
|
||||
@@ -0,0 +1,549 @@
|
||||
'use strict';
|
||||
|
||||
/* globals getInputCompileHelper: false */
|
||||
|
||||
describe('validators', function() {
|
||||
|
||||
var helper, $rootScope;
|
||||
|
||||
beforeEach(function() {
|
||||
helper = getInputCompileHelper(this);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
helper.dealoc();
|
||||
});
|
||||
|
||||
|
||||
beforeEach(inject(function(_$rootScope_) {
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
|
||||
describe('pattern', function() {
|
||||
|
||||
it('should validate in-lined pattern', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="/^\\d\\d\\d-\\d\\d-\\d\\d\\d\\d$/" />');
|
||||
|
||||
helper.changeInputValueTo('x000-00-0000x');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
helper.changeInputValueTo('000-00-0000');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('000-00-0000x');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
helper.changeInputValueTo('123-45-6789');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('x');
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should listen on ng-pattern when pattern is observed', function() {
|
||||
var value, patternVal = /^\w+$/;
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="pat" attr-capture />');
|
||||
helper.attrs.$observe('pattern', function(v) {
|
||||
value = helper.attrs.pattern;
|
||||
});
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.pat = patternVal;
|
||||
});
|
||||
|
||||
expect(value).toBe(patternVal);
|
||||
});
|
||||
|
||||
|
||||
it('should validate in-lined pattern with modifiers', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="/^abc?$/i" />');
|
||||
|
||||
helper.changeInputValueTo('aB');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('xx');
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should validate pattern from scope', function() {
|
||||
$rootScope.regexp = /^\d\d\d-\d\d-\d\d\d\d$/;
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="regexp" />');
|
||||
|
||||
helper.changeInputValueTo('x000-00-0000x');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
helper.changeInputValueTo('000-00-0000');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('000-00-0000x');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
helper.changeInputValueTo('123-45-6789');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('x');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.regexp = /abc?/;
|
||||
});
|
||||
|
||||
helper.changeInputValueTo('ab');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('xx');
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should perform validations when the ngPattern scope value changes', function() {
|
||||
$rootScope.regexp = /^[a-z]+$/;
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="regexp" />');
|
||||
|
||||
helper.changeInputValueTo('abcdef');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('123');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.regexp = /^\d+$/;
|
||||
});
|
||||
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('abcdef');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.regexp = '';
|
||||
});
|
||||
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should register "pattern" with the model validations when the pattern attribute is used', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" pattern="^\\d+$" />');
|
||||
|
||||
helper.changeInputValueTo('abcd');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.input.$error.pattern).toBe(true);
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.form.input.$error.pattern).not.toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should not throw an error when scope pattern can\'t be found', function() {
|
||||
expect(function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="foo" ng-pattern="fooRegexp" />');
|
||||
$rootScope.$apply("foo = 'bar'");
|
||||
}).not.toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/);
|
||||
});
|
||||
|
||||
|
||||
it('should throw an error when the scope pattern is not a regular expression', function() {
|
||||
expect(function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="foo" ng-pattern="fooRegexp" />');
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.fooRegexp = {};
|
||||
$rootScope.foo = 'bar';
|
||||
});
|
||||
}).toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/);
|
||||
});
|
||||
|
||||
|
||||
it('should be invalid if entire string does not match pattern', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="\\d{4}">');
|
||||
helper.changeInputValueTo('1234');
|
||||
expect($rootScope.form.test.$error.pattern).not.toBe(true);
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('123');
|
||||
expect($rootScope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect($rootScope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should be cope with patterns that start with ^', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="^\\d{4}">');
|
||||
helper.changeInputValueTo('1234');
|
||||
expect($rootScope.form.test.$error.pattern).not.toBe(true);
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('123');
|
||||
expect($rootScope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect($rootScope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should be cope with patterns that end with $', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="\\d{4}$">');
|
||||
helper.changeInputValueTo('1234');
|
||||
expect($rootScope.form.test.$error.pattern).not.toBe(true);
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('123');
|
||||
expect($rootScope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect($rootScope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('minlength', function() {
|
||||
|
||||
it('should invalidate values that are shorter than the given minlength', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-minlength="3" />');
|
||||
|
||||
helper.changeInputValueTo('aa');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
helper.changeInputValueTo('aaa');
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should listen on ng-minlength when minlength is observed', function() {
|
||||
var value = 0;
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-minlength="min" attr-capture />');
|
||||
helper.attrs.$observe('minlength', function(v) {
|
||||
value = int(helper.attrs.minlength);
|
||||
});
|
||||
|
||||
$rootScope.$apply('min = 5');
|
||||
|
||||
expect(value).toBe(5);
|
||||
});
|
||||
|
||||
|
||||
it('should observe the standard minlength attribute and register it as a validator on the model', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="{{ min }}" />');
|
||||
$rootScope.$apply('min = 10');
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.input.$error.minlength).toBe(true);
|
||||
|
||||
$rootScope.$apply('min = 5');
|
||||
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.form.input.$error.minlength).not.toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should validate when the model is initalized as a number', function() {
|
||||
$rootScope.value = 12345;
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
|
||||
expect($rootScope.value).toBe(12345);
|
||||
expect($rootScope.form.input.$error.minlength).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should validate emptiness against the viewValue', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
|
||||
|
||||
var ctrl = inputElm.controller('ngModel');
|
||||
spyOn(ctrl, '$isEmpty').andCallThrough();
|
||||
|
||||
ctrl.$parsers.push(function(value) {
|
||||
return value + '678';
|
||||
});
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('maxlength', function() {
|
||||
|
||||
it('should invalidate values that are longer than the given maxlength', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="5" />');
|
||||
|
||||
helper.changeInputValueTo('aaaaaaaa');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
helper.changeInputValueTo('aaa');
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should only accept empty values when maxlength is 0', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="0" />');
|
||||
|
||||
helper.changeInputValueTo('');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('a');
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should accept values of any length when maxlength is negative', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="-1" />');
|
||||
|
||||
helper.changeInputValueTo('');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('aaaaaaaaaa');
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should accept values of any length when maxlength is non-numeric', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="{{maxlength}}" />');
|
||||
helper.changeInputValueTo('aaaaaaaaaa');
|
||||
|
||||
$rootScope.$apply('maxlength = "5"');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply('maxlength = "abc"');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.$apply('maxlength = ""');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.$apply('maxlength = null');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.someObj = {};
|
||||
$rootScope.$apply('maxlength = someObj');
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should listen on ng-maxlength when maxlength is observed', function() {
|
||||
var value = 0;
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="max" attr-capture />');
|
||||
helper.attrs.$observe('maxlength', function(v) {
|
||||
value = int(helper.attrs.maxlength);
|
||||
});
|
||||
|
||||
$rootScope.$apply('max = 10');
|
||||
|
||||
expect(value).toBe(10);
|
||||
});
|
||||
|
||||
|
||||
it('should observe the standard maxlength attribute and register it as a validator on the model', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
|
||||
$rootScope.$apply('max = 1');
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.input.$error.maxlength).toBe(true);
|
||||
|
||||
$rootScope.$apply('max = 6');
|
||||
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.form.input.$error.maxlength).not.toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should assign the correct model after an observed validator became valid', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
|
||||
|
||||
$rootScope.$apply('max = 1');
|
||||
helper.changeInputValueTo('12345');
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
|
||||
$rootScope.$apply('max = 6');
|
||||
expect($rootScope.value).toBe('12345');
|
||||
});
|
||||
|
||||
|
||||
it('should assign the correct model after an observed validator became invalid', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
|
||||
|
||||
$rootScope.$apply('max = 6');
|
||||
helper.changeInputValueTo('12345');
|
||||
expect($rootScope.value).toBe('12345');
|
||||
|
||||
$rootScope.$apply('max = 1');
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it('should leave the value as invalid if observed maxlength changed, but is still invalid', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
|
||||
$rootScope.$apply('max = 1');
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.input.$error.maxlength).toBe(true);
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
|
||||
$rootScope.$apply('max = 3');
|
||||
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.input.$error.maxlength).toBe(true);
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it('should not notify if observed maxlength changed, but is still invalid', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" ng-change="ngChangeSpy()" ' +
|
||||
'maxlength="{{ max }}" />');
|
||||
|
||||
$rootScope.$apply('max = 1');
|
||||
helper.changeInputValueTo('12345');
|
||||
|
||||
$rootScope.ngChangeSpy = jasmine.createSpy();
|
||||
$rootScope.$apply('max = 3');
|
||||
|
||||
expect($rootScope.ngChangeSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should leave the model untouched when validating before model initialization', function() {
|
||||
$rootScope.value = '12345';
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
|
||||
expect($rootScope.value).toBe('12345');
|
||||
});
|
||||
|
||||
|
||||
it('should validate when the model is initalized as a number', function() {
|
||||
$rootScope.value = 12345;
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="10" />');
|
||||
expect($rootScope.value).toBe(12345);
|
||||
expect($rootScope.form.input.$error.maxlength).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should validate emptiness against the viewValue', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="10" />');
|
||||
|
||||
var ctrl = inputElm.controller('ngModel');
|
||||
spyOn(ctrl, '$isEmpty').andCallThrough();
|
||||
|
||||
ctrl.$parsers.push(function(value) {
|
||||
return value + '678';
|
||||
});
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('required', function() {
|
||||
|
||||
it('should allow bindings via ngRequired', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-required="required" />');
|
||||
|
||||
$rootScope.$apply("required = false");
|
||||
|
||||
helper.changeInputValueTo('');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
|
||||
$rootScope.$apply("required = true");
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply("value = 'some'");
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply("required = false");
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should invalid initial value with bound required', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" required="{{required}}" />');
|
||||
|
||||
$rootScope.$apply('required = true');
|
||||
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should be $invalid but $pristine if not touched', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="name" name="alias" required />');
|
||||
|
||||
$rootScope.$apply("name = null");
|
||||
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
helper.changeInputValueTo('');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect(inputElm).toBeDirty();
|
||||
});
|
||||
|
||||
|
||||
it('should allow empty string if not required', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="foo" />');
|
||||
helper.changeInputValueTo('a');
|
||||
helper.changeInputValueTo('');
|
||||
expect($rootScope.foo).toBe('');
|
||||
});
|
||||
|
||||
|
||||
it('should set $invalid when model undefined', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="notDefined" required />');
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should consider bad input as an error before any other errors are considered', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" required />', { badInput: true });
|
||||
var ctrl = inputElm.controller('ngModel');
|
||||
ctrl.$parsers.push(function() {
|
||||
return undefined;
|
||||
});
|
||||
|
||||
helper.changeInputValueTo('abc123');
|
||||
|
||||
expect(ctrl.$error.parse).toBe(true);
|
||||
expect(inputElm).toHaveClass('ng-invalid-parse');
|
||||
expect(inputElm).toBeInvalid(); // invalid because of the number validator
|
||||
});
|
||||
|
||||
|
||||
it('should allow `false` as a valid value when the input type is not "checkbox"', function() {
|
||||
var inputElm = helper.compileInput('<input type="radio" ng-value="true" ng-model="answer" required />' +
|
||||
'<input type="radio" ng-value="false" ng-model="answer" required />');
|
||||
|
||||
$rootScope.$apply();
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply("answer = true");
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.$apply("answer = false");
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
it('should validate emptiness against the viewValue', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" required />');
|
||||
|
||||
var ctrl = inputElm.controller('ngModel');
|
||||
spyOn(ctrl, '$isEmpty').andCallThrough();
|
||||
|
||||
ctrl.$parsers.push(function(value) {
|
||||
return value + '678';
|
||||
});
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -92,6 +92,39 @@ describe('Filter: filter', function() {
|
||||
);
|
||||
|
||||
|
||||
it('should match items with array properties containing one or more matching items', function() {
|
||||
var items, expr;
|
||||
|
||||
items = [
|
||||
{tags: ['web', 'html', 'css', 'js']},
|
||||
{tags: ['hybrid', 'html', 'css', 'js', 'ios', 'android']},
|
||||
{tags: ['mobile', 'ios', 'android']}
|
||||
];
|
||||
expr = {tags: 'html'};
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[1]]);
|
||||
|
||||
items = [
|
||||
{nums: [1, 345, 12]},
|
||||
{nums: [0, 46, 78]},
|
||||
{nums: [123, 4, 67]}
|
||||
];
|
||||
expr = {nums: 12};
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[2]]);
|
||||
|
||||
items = [
|
||||
{customers: [{name: 'John'}, {name: 'Elena'}, {name: 'Bill'}]},
|
||||
{customers: [{name: 'Sam'}, {name: 'Klara'}, {name: 'Bill'}]},
|
||||
{customers: [{name: 'Molli'}, {name: 'Elena'}, {name: 'Lora'}]}
|
||||
];
|
||||
expr = {customers: {name: 'Bill'}};
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[1]]);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
it('should take object as predicate', function() {
|
||||
var items = [{first: 'misko', last: 'hevery'},
|
||||
{first: 'adam', last: 'abrons'}];
|
||||
|
||||
+63
-6
@@ -673,6 +673,16 @@ describe('$location', function() {
|
||||
}));
|
||||
});
|
||||
|
||||
describe('rewrite hashbang url <> html5 url', function() {
|
||||
beforeEach(initService({html5Mode: true, supportHistory: true}));
|
||||
beforeEach(inject(initBrowser({url:'http://new.com/#', basePath: '/'})));
|
||||
|
||||
it('should not replace browser url if only the empty hash fragment is cleared', inject(function($browser, $location) {
|
||||
expect($browser.url()).toBe('http://new.com/#');
|
||||
expect($location.absUrl()).toBe('http://new.com/');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('wiring', function() {
|
||||
|
||||
beforeEach(initService({html5Mode:false,hashPrefix: '!',supportHistory: true}));
|
||||
@@ -1272,7 +1282,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should not rewrite links with target="_blank"', function() {
|
||||
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="_blank"'});
|
||||
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="_blank"'});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1285,7 +1295,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should not rewrite links with target specified', function() {
|
||||
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="some-frame"'});
|
||||
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="some-frame"'});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1323,7 +1333,7 @@ describe('$location', function() {
|
||||
});
|
||||
|
||||
|
||||
it ('should not rewrite links when rewriting links is disabled', function() {
|
||||
it('should not rewrite links when rewriting links is disabled', function() {
|
||||
configureService({linkHref: 'link?a#b', html5Mode: {enabled: true, rewriteLinks:false}, supportHist: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
@@ -1480,7 +1490,7 @@ describe('$location', function() {
|
||||
});
|
||||
|
||||
it('should not rewrite when clicked with ctrl pressed', function() {
|
||||
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true});
|
||||
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1493,7 +1503,7 @@ describe('$location', function() {
|
||||
|
||||
|
||||
it('should not rewrite when clicked with meta pressed', function() {
|
||||
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true});
|
||||
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
@@ -1504,6 +1514,53 @@ describe('$location', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('should not rewrite when right click pressed', function() {
|
||||
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
function($browser) {
|
||||
var rightClick;
|
||||
if (document.createEvent) {
|
||||
rightClick = document.createEvent('MouseEvents');
|
||||
rightClick.initMouseEvent('click', true, true, window, 1, 10, 10, 10, 10, false,
|
||||
false, false, false, 2, null);
|
||||
|
||||
link.dispatchEvent(rightClick);
|
||||
} else if (document.createEventObject) { // for IE
|
||||
rightClick = document.createEventObject();
|
||||
rightClick.type = 'click';
|
||||
rightClick.cancelBubble = true;
|
||||
rightClick.detail = 1;
|
||||
rightClick.screenX = 10;
|
||||
rightClick.screenY = 10;
|
||||
rightClick.clientX = 10;
|
||||
rightClick.clientY = 10;
|
||||
rightClick.ctrlKey = false;
|
||||
rightClick.altKey = false;
|
||||
rightClick.shiftKey = false;
|
||||
rightClick.metaKey = false;
|
||||
rightClick.button = 2;
|
||||
link.fireEvent('onclick', rightClick);
|
||||
}
|
||||
expectNoRewrite($browser);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should not rewrite when clicked with shift pressed', function() {
|
||||
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true});
|
||||
inject(
|
||||
initBrowser(),
|
||||
initLocation(),
|
||||
function($browser) {
|
||||
browserTrigger(link, 'click', { keys: ['shift'] });
|
||||
expectNoRewrite($browser);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should not mess up hash urls when clicking on links in hashbang mode', function() {
|
||||
var base;
|
||||
@@ -1813,7 +1870,7 @@ describe('$location', function() {
|
||||
})
|
||||
);
|
||||
|
||||
it ('should fire $locationChangeSuccess event when change from browser location bar',
|
||||
it('should fire $locationChangeSuccess event when change from browser location bar',
|
||||
inject(function($log, $location, $browser, $rootScope) {
|
||||
$rootScope.$apply(); // clear initial $locationChangeStart
|
||||
|
||||
|
||||
@@ -488,6 +488,62 @@ describe('parser', function() {
|
||||
expect(scope.b).toEqual(234);
|
||||
});
|
||||
|
||||
it('should allow use of locals in the left side of an assignment', inject(function($rootScope) {
|
||||
$rootScope.a = {};
|
||||
$rootScope.key = "value";
|
||||
var localA = {};
|
||||
|
||||
//getterFn
|
||||
$rootScope.$eval('a.value = 1', {a: localA});
|
||||
expect(localA.value).toBe(1);
|
||||
|
||||
$rootScope.$eval('w.a.value = 2', {w: {a: localA}});
|
||||
expect(localA.value).toBe(2);
|
||||
|
||||
//field access
|
||||
$rootScope.$eval('(a).value = 3', {a: localA});
|
||||
expect(localA.value).toBe(3);
|
||||
|
||||
$rootScope.$eval('{c: {b: a}}.c.b.value = 4', {a: localA});
|
||||
expect(localA.value).toBe(4);
|
||||
|
||||
//object index
|
||||
$rootScope.$eval('a[key] = 5', {a: localA});
|
||||
expect(localA.value).toBe(5);
|
||||
|
||||
$rootScope.$eval('w.a[key] = 6', {w: {a: localA}});
|
||||
expect(localA.value).toBe(6);
|
||||
|
||||
$rootScope.$eval('{c: {b: a}}.c.b[key] = 7', {a: localA});
|
||||
expect(localA.value).toBe(7);
|
||||
|
||||
//Nothing should have touched the $rootScope.a
|
||||
expect($rootScope.a.value).toBeUndefined();
|
||||
}));
|
||||
|
||||
it('should allow use of locals in sub expressions of the left side of an assignment', inject(function($rootScope, $parse) {
|
||||
delete $rootScope.x;
|
||||
$rootScope.$eval('x[a][b] = true', {a: 'foo', b: 'bar'});
|
||||
expect($rootScope.x.foo.bar).toBe(true);
|
||||
|
||||
delete $rootScope.x;
|
||||
$rootScope.$eval('x.foo[b] = true', {b: 'bar'});
|
||||
expect($rootScope.x.foo.bar).toBe(true);
|
||||
|
||||
delete $rootScope.x;
|
||||
$rootScope.$eval('x[a].bar = true', {a: 'foo'});
|
||||
expect($rootScope.x.foo.bar).toBe(true);
|
||||
}));
|
||||
|
||||
it('should ignore locals beyond the root object of an assignment expression', inject(function($rootScope) {
|
||||
var a = {};
|
||||
var locals = {a: a};
|
||||
$rootScope.b = {a: {value: 123}};
|
||||
$rootScope.$eval('b.a.value = 1', locals);
|
||||
expect(a.value).toBeUndefined();
|
||||
expect($rootScope.b.a.value).toBe(1);
|
||||
}));
|
||||
|
||||
it('should evaluate assignments in ternary operator', function() {
|
||||
scope.$eval('a = 1 ? 2 : 3');
|
||||
expect(scope.a).toBe(2);
|
||||
@@ -799,6 +855,12 @@ describe('parser', function() {
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: a.toString.constructor');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval("c.a = 1", {c: Function.prototype.constructor});
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: c.a');
|
||||
});
|
||||
|
||||
it('should disallow traversing the Function object in a setter: E02', function() {
|
||||
@@ -933,6 +995,14 @@ describe('parser', function() {
|
||||
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
|
||||
'Expression: foo["bar"]["keys"](foo)');
|
||||
});
|
||||
|
||||
it('should NOT allow access to Object constructor in assignment locals', function() {
|
||||
expect(function() {
|
||||
scope.$eval("O.constructor.a = 1", {O: Object});
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
|
||||
'Expression: O.constructor.a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Window and $element/node', function() {
|
||||
|
||||
@@ -14,7 +14,7 @@ describe('Scope', function() {
|
||||
|
||||
it('should expose the constructor', inject(function($rootScope) {
|
||||
/* jshint -W103 */
|
||||
if (msie) return;
|
||||
if (msie < 11) return;
|
||||
expect($rootScope.__proto__).toBe($rootScope.constructor.prototype);
|
||||
}));
|
||||
|
||||
|
||||
@@ -87,11 +87,9 @@ describe('$sniffer', function() {
|
||||
var ua = $window.navigator.userAgent.toLowerCase();
|
||||
if (/chrome/i.test(ua) || /safari/i.test(ua) || /webkit/i.test(ua)) {
|
||||
expectedPrefix = 'Webkit';
|
||||
}
|
||||
else if (/firefox/i.test(ua)) {
|
||||
} else if (/firefox/i.test(ua)) {
|
||||
expectedPrefix = 'Moz';
|
||||
}
|
||||
else if (/ie/i.test(ua) || /trident/i.test(ua)) {
|
||||
} else if (/ie/i.test(ua) || /trident/i.test(ua)) {
|
||||
expectedPrefix = 'Ms';
|
||||
}
|
||||
expect($sniffer.vendorPrefix).toBe(expectedPrefix);
|
||||
|
||||
@@ -1381,6 +1381,25 @@ describe("ngAnimate", function() {
|
||||
expect(element.attr('style')).toContain('border-color: blue');
|
||||
}));
|
||||
|
||||
it("should not apply a piggy-back-transition if the styles object contains no styles",
|
||||
inject(function($compile, $animate, $rootScope, $sniffer) {
|
||||
|
||||
if (!$sniffer.animations) return;
|
||||
|
||||
$animate.enabled(true);
|
||||
ss.addRule('.on', '-webkit-animation: 1s super-animation; animation: 1s super-animation;');
|
||||
|
||||
element = $compile(html('<div>1</div>'))($rootScope);
|
||||
|
||||
$animate.addClass(element, 'on', {
|
||||
to: {}
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.triggerReflow();
|
||||
expect(element.attr('style')).not.toMatch(/transition/);
|
||||
}));
|
||||
|
||||
it("should pause the playstate when performing a stagger animation",
|
||||
inject(function($animate, $rootScope, $compile, $sniffer, $timeout) {
|
||||
|
||||
|
||||
+24
-5
@@ -488,12 +488,24 @@ describe('$aria', function() {
|
||||
|
||||
it('should a trigger click from the keyboard', function() {
|
||||
scope.someAction = function() {};
|
||||
compileInput('<div ng-click="someAction()" tabindex="0"></div>');
|
||||
|
||||
var elements = $compile('<section>' +
|
||||
'<div class="div-click" ng-click="someAction(\'div\')" tabindex="0"></div>' +
|
||||
'<ul><li ng-click="someAction( \'li\')" tabindex="0"></li></ul>' +
|
||||
'</section>')(scope);
|
||||
|
||||
scope.$digest();
|
||||
|
||||
clickFn = spyOn(scope, 'someAction');
|
||||
|
||||
element.triggerHandler({type: 'keypress', keyCode: 32});
|
||||
var divElement = elements.find('div');
|
||||
var liElement = elements.find('li');
|
||||
|
||||
expect(clickFn).toHaveBeenCalled();
|
||||
divElement.triggerHandler({type: 'keypress', keyCode: 32});
|
||||
liElement.triggerHandler({type: 'keypress', keyCode: 32});
|
||||
|
||||
expect(clickFn).toHaveBeenCalledWith('div');
|
||||
expect(clickFn).toHaveBeenCalledWith('li');
|
||||
});
|
||||
|
||||
it('should not override existing ng-keypress', function() {
|
||||
@@ -526,6 +538,13 @@ describe('$aria', function() {
|
||||
element.triggerHandler({ type: 'keypress', keyCode: 13 });
|
||||
expect(element.text()).toBe('keypress13');
|
||||
});
|
||||
|
||||
it('should not bind keypress to elements not in the default config', function() {
|
||||
compileInput('<button ng-click="event = $event">{{event.type}}{{event.keyCode}}</button>');
|
||||
expect(element.text()).toBe('');
|
||||
element.triggerHandler({ type: 'keypress', keyCode: 13 });
|
||||
expect(element.text()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions when bindKeypress set to false', function() {
|
||||
@@ -534,11 +553,11 @@ describe('$aria', function() {
|
||||
}));
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
|
||||
it('should not a trigger click from the keyboard', function() {
|
||||
it('should not a trigger click', function() {
|
||||
scope.someAction = function() {};
|
||||
var clickFn = spyOn(scope, 'someAction');
|
||||
|
||||
element = $compile('<div ng-click="someAction()" tabindex="0">></div>')(scope);
|
||||
element = $compile('<div ng-click="someAction()" tabindex="0"></div>')(scope);
|
||||
|
||||
element.triggerHandler({type: 'keypress', keyCode: 32});
|
||||
|
||||
|
||||
Vendored
+77
-17
@@ -592,22 +592,42 @@ describe('ngMock', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should log exceptions', module(function($exceptionHandlerProvider) {
|
||||
$exceptionHandlerProvider.mode('log');
|
||||
var $exceptionHandler = $exceptionHandlerProvider.$get();
|
||||
$exceptionHandler('MyError');
|
||||
expect($exceptionHandler.errors).toEqual(['MyError']);
|
||||
it('should log exceptions', function() {
|
||||
module(function($exceptionHandlerProvider) {
|
||||
$exceptionHandlerProvider.mode('log');
|
||||
});
|
||||
inject(function($exceptionHandler) {
|
||||
$exceptionHandler('MyError');
|
||||
expect($exceptionHandler.errors).toEqual(['MyError']);
|
||||
|
||||
$exceptionHandler('MyError', 'comment');
|
||||
expect($exceptionHandler.errors[1]).toEqual(['MyError', 'comment']);
|
||||
}));
|
||||
$exceptionHandler('MyError', 'comment');
|
||||
expect($exceptionHandler.errors[1]).toEqual(['MyError', 'comment']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should log and rethrow exceptions', function() {
|
||||
module(function($exceptionHandlerProvider) {
|
||||
$exceptionHandlerProvider.mode('rethrow');
|
||||
});
|
||||
inject(function($exceptionHandler) {
|
||||
expect(function() { $exceptionHandler('MyError'); }).toThrow('MyError');
|
||||
expect($exceptionHandler.errors).toEqual(['MyError']);
|
||||
|
||||
expect(function() { $exceptionHandler('MyError', 'comment'); }).toThrow('MyError');
|
||||
expect($exceptionHandler.errors[1]).toEqual(['MyError', 'comment']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw on wrong argument', function() {
|
||||
module(function($exceptionHandlerProvider) {
|
||||
expect(function() {
|
||||
$exceptionHandlerProvider.mode('XXX');
|
||||
}).toThrow("Unknown mode 'XXX', only 'log'/'rethrow' modes are allowed!");
|
||||
});
|
||||
|
||||
inject(); // Trigger the tests in `module`
|
||||
});
|
||||
|
||||
it('should throw on wrong argument', module(function($exceptionHandlerProvider) {
|
||||
expect(function() {
|
||||
$exceptionHandlerProvider.mode('XXX');
|
||||
}).toThrow("Unknown mode 'XXX', only 'log'/'rethrow' modes are allowed!");
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -767,6 +787,39 @@ describe('ngMock', function() {
|
||||
expect(testFn.$$hashKey).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('$inject cleanup', function() {
|
||||
function testFn() {
|
||||
|
||||
}
|
||||
|
||||
it('should add $inject when invoking test function', inject(function($injector) {
|
||||
$injector.invoke(testFn);
|
||||
expect(testFn.$inject).toBeDefined();
|
||||
}));
|
||||
|
||||
it('should cleanup $inject after previous test', function() {
|
||||
expect(testFn.$inject).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should add $inject when annotating test function', inject(function($injector) {
|
||||
$injector.annotate(testFn);
|
||||
expect(testFn.$inject).toBeDefined();
|
||||
}));
|
||||
|
||||
it('should cleanup $inject after previous test', function() {
|
||||
expect(testFn.$inject).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should invoke an already annotated function', inject(function($injector) {
|
||||
testFn.$inject = [];
|
||||
$injector.invoke(testFn);
|
||||
}));
|
||||
|
||||
it('should not cleanup $inject after previous test', function() {
|
||||
expect(testFn.$inject).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('in DSL', function() {
|
||||
@@ -1173,7 +1226,7 @@ describe('ngMock', function() {
|
||||
});
|
||||
|
||||
|
||||
it ('should throw exception when only headers differs from expectation', function() {
|
||||
it('should throw exception when only headers differs from expectation', function() {
|
||||
hb.when('GET').respond(200, '', {});
|
||||
hb.expect('GET', '/match', undefined, {'Content-Type': 'application/json'});
|
||||
|
||||
@@ -1184,7 +1237,7 @@ describe('ngMock', function() {
|
||||
});
|
||||
|
||||
|
||||
it ('should throw exception when only data differs from expectation', function() {
|
||||
it('should throw exception when only data differs from expectation', function() {
|
||||
hb.when('GET').respond(200, '', {});
|
||||
hb.expect('GET', '/match', 'some-data');
|
||||
|
||||
@@ -1195,7 +1248,7 @@ describe('ngMock', function() {
|
||||
});
|
||||
|
||||
|
||||
it ('should not throw an exception when parsed body is equal to expected body object', function() {
|
||||
it('should not throw an exception when parsed body is equal to expected body object', function() {
|
||||
hb.when('GET').respond(200, '', {});
|
||||
|
||||
hb.expect('GET', '/match', {a: 1, b: 2});
|
||||
@@ -1210,7 +1263,7 @@ describe('ngMock', function() {
|
||||
});
|
||||
|
||||
|
||||
it ('should throw exception when only parsed body differs from expected body object', function() {
|
||||
it('should throw exception when only parsed body differs from expected body object', function() {
|
||||
hb.when('GET').respond(200, '', {});
|
||||
hb.expect('GET', '/match', {a: 1, b: 2});
|
||||
|
||||
@@ -1760,3 +1813,10 @@ describe('ngMockE2E', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('make sure that we can create an injector outside of tests', function() {
|
||||
//since some libraries create custom injectors outside of tests,
|
||||
//we want to make sure that this is not breaking the internals of
|
||||
//how we manage annotated function cleanup during tests. See #10967
|
||||
angular.injector([function($injector) {}]);
|
||||
});
|
||||
|
||||
@@ -1341,6 +1341,30 @@ describe('$route', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not update query params when an optional property was previously not in path', function() {
|
||||
var routeChangeSpy = jasmine.createSpy('route change');
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/bar/:barId/:fooId/:spamId/:eggId?', {controller: angular.noop});
|
||||
});
|
||||
|
||||
inject(function($route, $routeParams, $location, $rootScope) {
|
||||
$rootScope.$on('$routeChangeSuccess', routeChangeSpy);
|
||||
|
||||
$location.path('/bar/1/2/3');
|
||||
$location.search({initial: 'true'});
|
||||
$rootScope.$digest();
|
||||
routeChangeSpy.reset();
|
||||
|
||||
$route.updateParams({barId: '5', fooId: '6', eggId: '4'});
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($routeParams).toEqual({barId: '5', fooId: '6', spamId: '3', eggId: '4', initial: 'true'});
|
||||
expect(routeChangeSpy).toHaveBeenCalledOnce();
|
||||
expect($location.path()).toEqual('/bar/5/6/3/4');
|
||||
expect($location.search()).toEqual({initial: 'true'});
|
||||
});
|
||||
});
|
||||
|
||||
it('should complain if called without an existing route', inject(function($route) {
|
||||
expect($route.updateParams).toThrowMinErr('ngRoute', 'norout');
|
||||
|
||||
@@ -140,6 +140,10 @@ describe('HTML', function() {
|
||||
expectHTML('a<SCRIPT>evil< / scrIpt >c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove script that has newline characters', function() {
|
||||
expectHTML('a<SCRIPT\n>\n\revil\n\r< / scrIpt\n >c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove DOCTYPE header', function() {
|
||||
expectHTML('<!DOCTYPE html>').toEqual('');
|
||||
expectHTML('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"\n"http://www.w3.org/TR/html4/strict.dtd">').toEqual('');
|
||||
@@ -160,6 +164,10 @@ describe('HTML', function() {
|
||||
expectHTML('a<STyle>evil</stYle>c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove style that has newline characters', function() {
|
||||
expectHTML('a<STyle \n>\n\revil\n\r</stYle\n>c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove script and style', function() {
|
||||
expectHTML('a<STyle>evil<script></script></stYle>c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
@@ -118,6 +118,75 @@ describe('angular.scenario.Application', function() {
|
||||
expect(called).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should set rootElement when navigateTo instigates bootstrap', inject(function($injector, $browser) {
|
||||
var called;
|
||||
var testWindow = {
|
||||
document: jqLite('<div class="test-foo"></div>')[0],
|
||||
angular: {
|
||||
element: jqLite,
|
||||
service: {},
|
||||
resumeBootstrap: noop
|
||||
}
|
||||
};
|
||||
jqLite(testWindow.document).data('$injector', $injector);
|
||||
var resumeBootstrapSpy = spyOn(testWindow.angular, 'resumeBootstrap').andReturn($injector);
|
||||
|
||||
var injectorGet = $injector.get;
|
||||
spyOn($injector, 'get').andCallFake(function(name) {
|
||||
switch (name) {
|
||||
case "$rootElement": return jqLite(testWindow.document);
|
||||
default: return injectorGet(name);
|
||||
}
|
||||
});
|
||||
|
||||
app.getWindow_ = function() {
|
||||
return testWindow;
|
||||
};
|
||||
app.navigateTo('http://localhost/', noop);
|
||||
callLoadHandlers(app);
|
||||
expect(app.rootElement).toBe(testWindow.document);
|
||||
expect(resumeBootstrapSpy).toHaveBeenCalled();
|
||||
dealoc(testWindow.document);
|
||||
}));
|
||||
|
||||
it('should set setup resumeDeferredBootstrap if resumeBootstrap is not yet defined', inject(function($injector, $browser) {
|
||||
var called;
|
||||
var testWindow = {
|
||||
document: jqLite('<div class="test-foo"></div>')[0],
|
||||
angular: {
|
||||
element: jqLite,
|
||||
service: {},
|
||||
resumeBootstrap: null
|
||||
}
|
||||
};
|
||||
jqLite(testWindow.document).data('$injector', $injector);
|
||||
|
||||
var injectorGet = $injector.get;
|
||||
var injectorSpy = spyOn($injector, 'get').andCallFake(function(name) {
|
||||
switch (name) {
|
||||
case "$rootElement": return jqLite(testWindow.document);
|
||||
default: return injectorGet(name);
|
||||
}
|
||||
});
|
||||
|
||||
app.getWindow_ = function() {
|
||||
return testWindow;
|
||||
};
|
||||
app.navigateTo('http://localhost/', noop);
|
||||
expect(testWindow.angular.resumeDeferredBootstrap).toBeUndefined();
|
||||
callLoadHandlers(app);
|
||||
expect(testWindow.angular.resumeDeferredBootstrap).toBeDefined();
|
||||
expect(app.rootElement).toBeUndefined;
|
||||
expect(injectorSpy).not.toHaveBeenCalled();
|
||||
|
||||
var resumeBootstrapSpy = spyOn(testWindow.angular, 'resumeBootstrap').andReturn($injector);
|
||||
testWindow.angular.resumeDeferredBootstrap();
|
||||
expect(app.rootElement).toBe(testWindow.document);
|
||||
expect(resumeBootstrapSpy).toHaveBeenCalled();
|
||||
expect(injectorSpy).toHaveBeenCalledWith("$rootElement");
|
||||
dealoc(testWindow.document);
|
||||
}));
|
||||
|
||||
it('should wait for pending requests in executeAction', inject(function($injector, $browser) {
|
||||
var called, polled;
|
||||
var handlers = [];
|
||||
@@ -144,4 +213,32 @@ describe('angular.scenario.Application', function() {
|
||||
handlers[0]();
|
||||
dealoc(testWindow.document);
|
||||
}));
|
||||
|
||||
it('should allow explicit rootElement', inject(function($injector, $browser) {
|
||||
var called, polled;
|
||||
var handlers = [];
|
||||
var testWindow = {
|
||||
document: jqLite('<div class="test-foo"></div>')[0],
|
||||
angular: {
|
||||
element: jqLite,
|
||||
service: {}
|
||||
}
|
||||
};
|
||||
$browser.notifyWhenNoOutstandingRequests = function(fn) {
|
||||
handlers.push(fn);
|
||||
};
|
||||
app.rootElement = testWindow.document;
|
||||
jqLite(testWindow.document).data('$injector', $injector);
|
||||
app.getWindow_ = function() {
|
||||
return testWindow;
|
||||
};
|
||||
app.executeAction(function($window, $document) {
|
||||
expect($window).toEqual(testWindow);
|
||||
expect($document).toBeDefined();
|
||||
expect($document[0].className).toEqual('test-foo');
|
||||
});
|
||||
expect(handlers.length).toEqual(1);
|
||||
handlers[0]();
|
||||
dealoc(testWindow.document);
|
||||
}));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user