Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e4adebd07a | |||
| ac94f6125f | |||
| c5686c5271 | |||
| 7fdb54d12b | |||
| 869008140a | |||
| 26ee32ec79 | |||
| a06193f97b | |||
| ba9dee170c | |||
| d4b60ada1e | |||
| b839f73ad0 | |||
| aee32931fd | |||
| 7ee5f46bbc | |||
| 2b97854bf4 | |||
| 316ee8f7ca | |||
| 2e18f44fcd | |||
| c85d064ecf | |||
| 7b9b82281a | |||
| aab632b330 | |||
| 4c8d8ad508 | |||
| 3a8f3dc9ea | |||
| c139e68d99 | |||
| 2caec44632 | |||
| eae848a712 | |||
| 47a55ca767 | |||
| 3d78bf3fa8 | |||
| 6e80d0ad54 | |||
| ae637acdfb | |||
| 661f6d9ecf | |||
| 56a7abd38f | |||
| 7d70dcdab1 | |||
| 9e6161e579 | |||
| bd28c74c1d | |||
| cd77c089ba | |||
| 83f88c1818 | |||
| fb2c585897 | |||
| bdbe4fd34a | |||
| 0b4e150aa5 | |||
| e091bb7dbb | |||
| b62c858499 | |||
| 4a18274670 | |||
| e3bf1ed217 | |||
| a5f037d033 | |||
| 25152bb218 | |||
| 24eb528b05 | |||
| d161cc6b25 | |||
| d604f941e8 | |||
| 85758ce3af | |||
| 924e68c7d5 | |||
| f297aa5d0a | |||
| 337ce67612 | |||
| 8b56c08327 | |||
| 32eec67023 | |||
| fd1528a6c8 | |||
| 6cb5fbf5ef | |||
| f6644c720e | |||
| 0d9aafba3b |
@@ -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
|
||||
|
||||
+279
-2
@@ -1,3 +1,252 @@
|
||||
<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)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
- **filterFilter:**
|
||||
- make `$` match properties on deeper levels as well
|
||||
([bd28c74c](https://github.com/angular/angular.js/commit/bd28c74c1d91c477a86f10fe36576cba0249e6ef),
|
||||
[#10401](https://github.com/angular/angular.js/issues/10401))
|
||||
- let expression object `{$: '...'}` also match primitive items
|
||||
([fb2c5858](https://github.com/angular/angular.js/commit/fb2c58589758744c0eef8c2ead3dbcf27a5cf200),
|
||||
[#10428](https://github.com/angular/angular.js/issues/10428))
|
||||
- **ngAria:** trigger digest on `ng-click` via keypress, pass `$event` to expression
|
||||
([924e68c7](https://github.com/angular/angular.js/commit/924e68c7d522a1086969f3583d0ce87e59110bc5),
|
||||
[#10442](https://github.com/angular/angular.js/issues/10442), [#10443](https://github.com/angular/angular.js/issues/10443), [#10447](https://github.com/angular/angular.js/issues/10447))
|
||||
- **orderBy:** compare timestamps when sorting date objects
|
||||
([661f6d9e](https://github.com/angular/angular.js/commit/661f6d9ecf1459ce3b2794c3cde373e17ae83972),
|
||||
[#10512](https://github.com/angular/angular.js/issues/10512), [#10516](https://github.com/angular/angular.js/issues/10516))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **limitTo:** replace for loop with slice
|
||||
([cd77c089](https://github.com/angular/angular.js/commit/cd77c089ba2f4b94ccc74f32f0ffa9fb70851c02))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.3.7"></a>
|
||||
# 1.3.7 leaky-obstruction (2014-12-15)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** use `createMap()` for `$$observe` listeners when initialized from attr interpolation
|
||||
([8e28bb4c](https://github.com/angular/angular.js/commit/8e28bb4c2f6d015dfe1cec7755f1ca9b0ecef1f8))
|
||||
- **$http:**
|
||||
- only parse as JSON when opening/closing brackets match
|
||||
([b9bdbe61](https://github.com/angular/angular.js/commit/b9bdbe615cc4070d2233ff06830a4c6fb1217cda),
|
||||
[#10349](https://github.com/angular/angular.js/issues/10349), [#10357](https://github.com/angular/angular.js/issues/10357))
|
||||
- don't convert FormData objects to JSON
|
||||
([40258838](https://github.com/angular/angular.js/commit/40258838031604feecb862afdc6f1f503d80ce4a),
|
||||
[#10373](https://github.com/angular/angular.js/issues/10373))
|
||||
- **$parse:** a chain of field accessors should use a single `getterFn`
|
||||
([c90ad968](https://github.com/angular/angular.js/commit/c90ad96808be350526516626205c3a7d1da79024))
|
||||
- **ngRepeat:** allow extra whitespaces in `(key,value)` part of micro-syntax
|
||||
([ef640cbc](https://github.com/angular/angular.js/commit/ef640cbc2af5794c987e75472c12e63a59590044),
|
||||
[#6827](https://github.com/angular/angular.js/issues/6827), [#6833](https://github.com/angular/angular.js/issues/6833))
|
||||
- **orderBy:** do not try to call `valueOf`/`toString` on `null`
|
||||
([a097aa95](https://github.com/angular/angular.js/commit/a097aa95b7c78beab6d1b7d521c25f7d9d7843d9),
|
||||
[#10385](https://github.com/angular/angular.js/issues/10385), [#10386](https://github.com/angular/angular.js/issues/10386))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** add support for `ng-attr` with camelCased attributes
|
||||
([d8e37078](https://github.com/angular/angular.js/commit/d8e37078600089839f82f0e84022f1087e1fd3f2),
|
||||
[#9845](https://github.com/angular/angular.js/issues/9845), [#10194](https://github.com/angular/angular.js/issues/10194))
|
||||
- **$http:** pass response status code to data transform functions
|
||||
([1b740974](https://github.com/angular/angular.js/commit/1b740974f5eb373bed04071d51f908ced7c5a8e5),
|
||||
[#10324](https://github.com/angular/angular.js/issues/10324), [#6734](https://github.com/angular/angular.js/issues/6734), [#10440](https://github.com/angular/angular.js/issues/10440))
|
||||
- **$rootScope:** allow passing `locals` argument to `$evalAsync`
|
||||
([9b96cea4](https://github.com/angular/angular.js/commit/9b96cea462676d123e1b2dd852aedbe3da8fa4a0),
|
||||
[#10390](https://github.com/angular/angular.js/issues/10390))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** only re-`$interpolate` attribute values at link time if changed since compile
|
||||
([9ae0c01c](https://github.com/angular/angular.js/commit/9ae0c01c2bcaff2f3906eec574f9c6ed8abde14a))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **orderBy:** due to [a097aa95](https://github.com/angular/angular.js/commit/a097aa95b7c78beab6d1b7d521c25f7d9d7843d9),
|
||||
|
||||
Previously, if either value being compared in the orderBy comparator was null or undefined, the
|
||||
order would, incorrectly, not change. Now, this order behaves more like Array.prototype.sort, which
|
||||
by default pushes `null` behind objects, due to `n` occurring after `[` (the first characters of their
|
||||
stringified forms) in ASCII / Unicode. If `toString` is customized, or does not exist, the
|
||||
behaviour is undefined.
|
||||
|
||||
|
||||
|
||||
<a name="1.2.28"></a>
|
||||
# 1.2.28 finnish-disembarkation (2014-12-15)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$route:** fix redirection with optional/eager params
|
||||
([1b9e408d](https://github.com/angular/angular.js/commit/1b9e408ddbe48a6d3db27f501515d6efad01f42d),
|
||||
[#9742](https://github.com/angular/angular.js/issues/9742), [#10202](https://github.com/angular/angular.js/issues/10202))
|
||||
- **linky:** encode double quotes when serializing email addresses
|
||||
([929dd15b](https://github.com/angular/angular.js/commit/929dd15b9b65034350f18abe6c56a8d956f4b978),
|
||||
[#8945](https://github.com/angular/angular.js/issues/8945), [#8964](https://github.com/angular/angular.js/issues/8964), [#5946](https://github.com/angular/angular.js/issues/5946), [#10090](https://github.com/angular/angular.js/issues/10090), [#9256](https://github.com/angular/angular.js/issues/9256))
|
||||
|
||||
|
||||
|
||||
<a name="1.3.6"></a>
|
||||
# 1.3.6 robofunky-danceblaster (2014-12-08)
|
||||
|
||||
@@ -17,7 +266,7 @@
|
||||
- strip off empty hash segments when comparing
|
||||
([e93710fe](https://github.com/angular/angular.js/commit/e93710fe0e4fb05ceee59a04f290692a5bec5d20),
|
||||
[#9635](https://github.com/angular/angular.js/issues/9635))
|
||||
- **$parse:**
|
||||
- **$parse:**
|
||||
- fix operators associativity
|
||||
([ed1243ff](https://github.com/angular/angular.js/commit/ed1243ffc7c2cb4bd5b4dece597597db8eb08e34))
|
||||
- follow JavaScript context for unbound functions
|
||||
@@ -81,7 +330,7 @@
|
||||
## Breaking Changes
|
||||
|
||||
- **$location:** due to [2dc34a96](https://github.com/angular/angular.js/commit/2dc34a969956eea680be4c8d9f800556d110996a),
|
||||
|
||||
|
||||
|
||||
We no longer throw an `ihshprfx` error if the URL after the base path
|
||||
contains only a hash fragment. Previously, if the base URL was `http://abc.com/base/`
|
||||
@@ -96,6 +345,34 @@ and hashPrfix are set up as above, then `http://abc.com/base/other/path` does no
|
||||
throw an error but just ignores the extra path: `http://abc.com/base`.
|
||||
|
||||
|
||||
- **filterFilter:** due to [a75537d4](https://github.com/angular/angular.js/commit/a75537d461c92e3455e372ff5005bf0cad2d2e95),
|
||||
|
||||
Named properties in the expression object will only match against properties on the **same level**.
|
||||
Previously, named string properties would match against properties on the same level **or deeper**.
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
arr = filterFilter([{level1: {level2: 'test'}}], {level1: 'test'}); // arr.length -> 1
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
arr = filterFilter([{level1: {level2: 'test'}}], {level1: 'test'}); // arr.length -> 0
|
||||
```
|
||||
|
||||
In order to match deeper nested properties, you have to either match the depth level of the
|
||||
property or use the special `$` key (which still matches properties on the same level
|
||||
**or deeper**). E.g.:
|
||||
|
||||
```js
|
||||
// Both examples below have `arr.length === 1`
|
||||
arr = filterFilter([{level1: {level2: 'test'}}], {level1: {level2: 'test'}});
|
||||
arr = filterFilter([{level1: {level2: 'test'}}], {$: 'test'});
|
||||
```
|
||||
|
||||
|
||||
<a name="1.3.5"></a>
|
||||
# 1.3.5 cybernetic-mercantilism (2014-12-01)
|
||||
|
||||
|
||||
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': [
|
||||
|
||||
@@ -128,7 +128,7 @@ Use ngRoute to enable URL routing to your application. The ngRoute module suppor
|
||||
|
||||
## {@link ngAnimate ngAnimate}
|
||||
|
||||
Use ngAnimate to enable animation features into your application. Various core ng directives will provide
|
||||
Use ngAnimate to enable animation features within your application. Various core ng directives will provide
|
||||
animation hooks into your application when ngAnimate is included. Animations are defined by using CSS transitions/animations
|
||||
or JavaScript callbacks.
|
||||
|
||||
@@ -273,7 +273,7 @@ Use ngSanitize to securely parse and manipulate HTML data in your application.
|
||||
|
||||
## {@link ngMock ngMock}
|
||||
|
||||
Use ngMock to inject and mock modules, factories, services and providers within your unit tests
|
||||
Use ngMock to inject and mock modules, factories, services and providers within your unit tests.
|
||||
|
||||
<div class="alert alert-info">Include the **angular-mocks.js** file into your test runner for this to work.</div>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
@fullName Response does not match configured parameter
|
||||
@description
|
||||
|
||||
This error occurs when the {@link ngResource.$resource `$resource`} service expects a response that can be deserialized as an array, receives an object, or vice versa.
|
||||
This error occurs when the {@link ngResource.$resource `$resource`} service expects a response that can be deserialized as an array but receives an object, or vice versa.
|
||||
By default, all resource actions expect objects, except `query` which expects arrays.
|
||||
|
||||
To resolve this error, make sure your `$resource` configuration matches the actual format of the data returned from the server.
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
@fullName Bad `hasOwnProperty` Name
|
||||
@description
|
||||
|
||||
Occurs when you try to use the name `hasOwnProperty` in a context where it is not allow.
|
||||
Occurs when you try to use the name `hasOwnProperty` in a context where it is not allowed.
|
||||
Generally, a name cannot be `hasOwnProperty` because it is used, internally, on a object
|
||||
and allowing such a name would break lookups on this object.
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
# What does it do?
|
||||
|
||||
The `$location` service parses the URL in the browser address bar (based on the [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL available to
|
||||
your application. Changes to the URL in the address bar are reflected into $location service and
|
||||
changes to $location are reflected into the browser address bar.
|
||||
The `$location` service parses the URL in the browser address bar (based on [`window.location`](https://developer.mozilla.org/en/window.location)) and makes the URL available to
|
||||
your application. Changes to the URL in the address bar are reflected into the `$location` service and
|
||||
changes to `$location` are reflected into the browser address bar.
|
||||
|
||||
**The $location service:**
|
||||
|
||||
@@ -21,7 +21,7 @@ changes to $location are reflected into the browser address bar.
|
||||
- Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
|
||||
|
||||
|
||||
## Comparing $location to window.location
|
||||
## Comparing `$location` to `window.location`
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
@@ -68,7 +68,7 @@ changes to $location are reflected into the browser address bar.
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## When should I use $location?
|
||||
## When should I use `$location`?
|
||||
Any time your application needs to react to a change in the current URL or if you want to change
|
||||
the current URL in the browser.
|
||||
|
||||
@@ -85,7 +85,7 @@ others customizing the configuration can enable new features.
|
||||
Once the `$location` service is instantiated, you can interact with it via jQuery-style getter and
|
||||
setter methods that allow you to get or change the current URL in the browser.
|
||||
|
||||
## $location service configuration
|
||||
## `$location` service configuration
|
||||
|
||||
To configure the `$location` service, retrieve the
|
||||
{@link ng.$locationProvider $locationProvider} and set the parameters as follows:
|
||||
@@ -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
|
||||
@@ -835,7 +835,7 @@ angular.module('locationExample', [])
|
||||
|
||||
# Related API
|
||||
|
||||
* {@link ng.$location $location API}
|
||||
* {@link ng.$location `$location` API}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -6,7 +6,7 @@
|
||||
# E2E Testing
|
||||
|
||||
<div class="alert alert-danger">
|
||||
**Note:** In the past, end to end testing could be done with a deprecated tool called
|
||||
**Note:** In the past, end-to-end testing could be done with a deprecated tool called
|
||||
[Angular Scenario Runner](http://code.angularjs.org/1.2.16/docs/guide/e2e-testing). That tool
|
||||
is now in maintenance mode.
|
||||
</div>
|
||||
@@ -14,7 +14,7 @@ is now in maintenance mode.
|
||||
As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to
|
||||
verify the correctness of new features, catch bugs and notice regressions. Unit tests
|
||||
are the first line of defense for catching bugs, but sometimes issues come up with integration
|
||||
between components which can't be captured in a unit test. End to end tests are made to find
|
||||
between components which can't be captured in a unit test. End-to-end tests are made to find
|
||||
these problems.
|
||||
|
||||
We have built [Protractor](https://github.com/angular/protractor), an end
|
||||
@@ -23,7 +23,7 @@ Angular application.
|
||||
|
||||
## Using Protractor
|
||||
|
||||
Protractor is a [Node.js](http://nodejs.org) program, and runs end to end tests that are also
|
||||
Protractor is a [Node.js](http://nodejs.org) program, and runs end-to-end tests that are also
|
||||
written in JavaScript and run with node. Protractor uses [WebDriver](https://code.google.com/p/selenium/wiki/GettingStarted)
|
||||
to control browsers and simulate user actions.
|
||||
|
||||
@@ -77,7 +77,7 @@ filter the list of items.
|
||||
## Example
|
||||
See the [angular-seed](https://github.com/angular/angular-seed) project for more examples, or look
|
||||
at the embedded examples in the Angular documentation (For example, {@link $http $http}
|
||||
has an end to end test in the example under the `protractor.js` tag).
|
||||
has an end-to-end test in the example under the `protractor.js` tag).
|
||||
|
||||
## Caveats
|
||||
|
||||
|
||||
@@ -26,8 +26,16 @@ Angular expressions are like JavaScript expressions with the following differenc
|
||||
* **Forgiving:** In JavaScript, trying to evaluate undefined properties generates `ReferenceError`
|
||||
or `TypeError`. In Angular, expression evaluation is forgiving to `undefined` and `null`.
|
||||
|
||||
* **No Control Flow Statements:** you cannot use the following in an Angular expression:
|
||||
* **No Control Flow Statements:** You cannot use the following in an Angular expression:
|
||||
conditionals, loops, or exceptions.
|
||||
|
||||
* **No Function Declarations:** You cannot declare functions in an Angular expression,
|
||||
even inside `ng-init` directive.
|
||||
|
||||
* **No RegExp Creation With Literal Notation:** You cannot create regular expressions
|
||||
in an Angular expression.
|
||||
|
||||
* **No Comma And Void Operators:** You cannot use `,` or `void` in an Angular expression.
|
||||
|
||||
* **Filters:** You can use {@link guide/filter filters} within expressions to format data before
|
||||
displaying it.
|
||||
@@ -164,11 +172,11 @@ expression. The reason behind this is core to the Angular philosophy that applic
|
||||
be in controllers, not the views. If you need a real conditional, loop, or to throw from a view
|
||||
expression, delegate to a JavaScript method instead.
|
||||
|
||||
## No RegExp creation with literal notation
|
||||
## No function declarations or RegExp creation with literal notation
|
||||
|
||||
You can't create regular expressions from within AngularJS expressions. This is to avoid complex
|
||||
model transformation logic inside templates. Such logic is better placed in a controller or in a dedicated
|
||||
filter where it can be tested properly.
|
||||
You can't declare functions or create regular expressions from within AngularJS expressions. This is
|
||||
to avoid complex model transformation logic inside templates. Such logic is better placed in a
|
||||
controller or in a dedicated filter where it can be tested properly.
|
||||
|
||||
## `$event`
|
||||
|
||||
|
||||
@@ -131,7 +131,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 +339,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)
|
||||
@@ -109,6 +110,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner
|
||||
* [AngularJS : Novice to Ninja](http://www.amazon.in/AngularJS-Novice-Ninja-Sandeep-Panda/dp/0992279453) by Sandeep Panda
|
||||
* [AngularJS UI Development](http://www.amazon.com/AngularJS-UI-Development-Amit-Ghart-ebook/dp/B00OXVAK7A) by Amit Gharat and Matthias Nehlsen
|
||||
* [Responsive Web Design with AngularJS](http://www.amazon.com/Responsive-Design-AngularJS-Sandeep-Kumar/dp/178439842X) by Sandeep Kumar Patel
|
||||
|
||||
###Videos:
|
||||
* [egghead.io](http://egghead.io/)
|
||||
|
||||
@@ -21,7 +21,7 @@ which drives many of these changes.
|
||||
|
||||
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 +877,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 +1134,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
|
||||
|
||||
@@ -177,7 +177,7 @@ for example, only a portion of the view needs to be controlled by Angular.
|
||||
|
||||
To examine the scope in the debugger:
|
||||
|
||||
1. right click on the element of interest in your browser and select 'inspect element'. You
|
||||
1. Right click on the element of interest in your browser and select 'inspect element'. You
|
||||
should see the browser debugger with the element you clicked on highlighted.
|
||||
|
||||
2. The debugger allows you to access the currently selected element in the console as `$0`
|
||||
|
||||
@@ -16,8 +16,8 @@ about the phones in the catalog.
|
||||
|
||||
## Data
|
||||
|
||||
Note that the `phones.json` file contains unique ids and image urls for each of the phones. The
|
||||
urls point to the `app/img/phones/` directory.
|
||||
Note that the `phones.json` file contains unique IDs and image URLs for each of the phones. The
|
||||
URLs point to the `app/img/phones/` directory.
|
||||
|
||||
__`app/phones/phones.json`__ (sample snippet):
|
||||
|
||||
@@ -59,7 +59,7 @@ the element attribute.
|
||||
We also added phone images next to each record using an image tag with the {@link
|
||||
ng.directive:ngSrc ngSrc} directive. That directive prevents the
|
||||
browser from treating the Angular `{{ expression }}` markup literally, and initiating a request to
|
||||
invalid url `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only
|
||||
invalid URL `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only
|
||||
specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`).
|
||||
Using the `ngSrc` directive prevents the browser from making an http request to an invalid location.
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ fleshed out the `phone-detail.html` view template.
|
||||
|
||||
## Data
|
||||
|
||||
In addition to `phones.json`, the `app/phones/` directory also contains one json file for each
|
||||
In addition to `phones.json`, the `app/phones/` directory also contains one JSON file for each
|
||||
phone:
|
||||
|
||||
__`app/phones/nexus-s.json`:__ (sample snippet)
|
||||
@@ -53,7 +53,7 @@ show this data in the phone detail view.
|
||||
|
||||
## Controller
|
||||
|
||||
We'll expand the `PhoneDetailCtrl` by using the `$http` service to fetch the json files. This works
|
||||
We'll expand the `PhoneDetailCtrl` by using the `$http` service to fetch the JSON files. This works
|
||||
the same way as the phone list controller.
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
|
||||
@@ -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>
|
||||
|
||||
...
|
||||
|
||||
|
||||
Generated
+18
-18
@@ -2453,17 +2453,6 @@
|
||||
"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 +4799,7 @@
|
||||
}
|
||||
},
|
||||
"protractor": {
|
||||
"version": "1.4.0",
|
||||
"version": "1.6.0",
|
||||
"dependencies": {
|
||||
"request": {
|
||||
"version": "2.36.0",
|
||||
@@ -4828,7 +4817,7 @@
|
||||
"version": "0.5.2"
|
||||
},
|
||||
"node-uuid": {
|
||||
"version": "1.4.1"
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "0.12.1",
|
||||
@@ -4858,16 +4847,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 +4898,7 @@
|
||||
"version": "0.6.1"
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.5",
|
||||
"dependencies": {
|
||||
"lodash-node": {
|
||||
"version": "2.4.1"
|
||||
@@ -4926,6 +4915,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 +4966,7 @@
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.2.8",
|
||||
"version": "0.2.9",
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.1.32",
|
||||
|
||||
+3
-3
@@ -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",
|
||||
@@ -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
|
||||
|
||||
+6
-4
@@ -401,6 +401,8 @@ noop.$inject = [];
|
||||
return (transformationFn || angular.identity)(value);
|
||||
};
|
||||
```
|
||||
* @param {*} value to be returned.
|
||||
* @returns {*} the value passed in.
|
||||
*/
|
||||
function identity($) {return $;}
|
||||
identity.$inject = [];
|
||||
@@ -655,7 +657,7 @@ function arrayRemove(array, value) {
|
||||
* Creates a deep copy of `source`, which should be an object or an array.
|
||||
*
|
||||
* * If no destination is supplied, a copy of the object or array is created.
|
||||
* * If a destination is provided, all of its elements (for array) or properties (for objects)
|
||||
* * If a destination is provided, all of its elements (for arrays) or properties (for objects)
|
||||
* are deleted and then all elements/properties from the source are copied to it.
|
||||
* * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
|
||||
* * If `source` is identical to 'destination' an exception will be thrown.
|
||||
@@ -993,7 +995,7 @@ function toJson(obj, pretty) {
|
||||
* Deserializes a JSON string.
|
||||
*
|
||||
* @param {string} json JSON string to deserialize.
|
||||
* @returns {Object|Array|string|number} Deserialized thingy.
|
||||
* @returns {Object|Array|string|number} Deserialized JSON string.
|
||||
*/
|
||||
function fromJson(json) {
|
||||
return isString(json)
|
||||
@@ -1166,7 +1168,7 @@ function getNgAttribute(element, ngAttr) {
|
||||
* {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
|
||||
*
|
||||
* You can specify an **AngularJS module** to be used as the root module for the application. This
|
||||
* module will be loaded into the {@link auto.$injector} when the application is bootstrapped and
|
||||
* module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
|
||||
* should contain the application code needed or have dependencies on other modules that will
|
||||
* contain the code. See {@link angular.module} for more information.
|
||||
*
|
||||
@@ -1174,7 +1176,7 @@ function getNgAttribute(element, ngAttr) {
|
||||
* document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
|
||||
* would not be resolved to `3`.
|
||||
*
|
||||
* `ngApp` is the easiest, and most common, way to bootstrap an application.
|
||||
* `ngApp` is the easiest, and most common way to bootstrap an application.
|
||||
*
|
||||
<example module="ngAppDemo">
|
||||
<file name="index.html">
|
||||
|
||||
@@ -489,19 +489,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
|
||||
});
|
||||
|
||||
+7
-1619
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);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
+45
-21
@@ -13,14 +13,15 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
*
|
||||
* The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
|
||||
* elements for the `<select>` element using the array or object obtained by evaluating the
|
||||
* `ngOptions` comprehension_expression.
|
||||
* `ngOptions` comprehension expression.
|
||||
*
|
||||
* In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
|
||||
* similar result. However, the `ngOptions` provides some benefits such as reducing memory and
|
||||
* similar result. However, `ngOptions` provides some benefits such as reducing memory and
|
||||
* increasing speed by not creating a new scope for each repeated instance, as well as providing
|
||||
* more flexibility in how the `select`'s model is assigned via `select as`. `ngOptions` should be
|
||||
* used when the `select` model needs to be bound to a non-string value. This is because an option
|
||||
* element can only be bound to string values at present.
|
||||
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
|
||||
* comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
|
||||
* to a non-string value. This is because an option element can only be bound to string values at
|
||||
* present.
|
||||
*
|
||||
* When an item in the `<select>` menu is selected, the array element or object property
|
||||
* represented by the selected option will be bound to the model identified by the `ngModel`
|
||||
@@ -35,28 +36,51 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
|
||||
* </div>
|
||||
*
|
||||
* ## `select as`
|
||||
* ## `select` **`as`**
|
||||
*
|
||||
* Using `select as` will bind the result of the `select as` expression to the model, but
|
||||
* Using `select` **`as`** will bind the result of the `select` expression to the model, but
|
||||
* the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
|
||||
* or property name (for object data sources) of the value within the collection. If a `track by` expression
|
||||
* or property name (for object data sources) of the value within the collection. If a **`track by`** expression
|
||||
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
|
||||
*
|
||||
* ### `select as` with `track by`
|
||||
*
|
||||
* Using `select as` together with `track by` is not recommended. Reasoning:
|
||||
* ### `select` **`as`** and **`track by`**
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together.
|
||||
* </div>
|
||||
*
|
||||
* Consider the following example:
|
||||
*
|
||||
* ```html
|
||||
* <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
|
||||
* ```
|
||||
*
|
||||
* ```js
|
||||
* $scope.values = [{
|
||||
* id: 1,
|
||||
* label: 'aLabel',
|
||||
* subItem: { name: 'aSubItem' }
|
||||
* }, {
|
||||
* id: 2,
|
||||
* label: 'bLabel',
|
||||
* subItem: { name: 'bSubItem' }
|
||||
* }];
|
||||
*
|
||||
* $scope.selected = { name: 'aSubItem' };
|
||||
* ```
|
||||
*
|
||||
* With the purpose of preserving the selection, the **`track by`** expression is always applied to the element
|
||||
* of the data source (to `item` in this example). To calculate whether an element is selected, we do the
|
||||
* following:
|
||||
*
|
||||
* 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
|
||||
* 2. Apply **`track by`** to the already selected value in `ngModel`.
|
||||
* In the example: this is not possible as **`track by`** refers to `item.id`, but the selected
|
||||
* value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to
|
||||
* a wrong object, the selected element can't be found, `<select>` is always reset to the "not
|
||||
* selected" option.
|
||||
*
|
||||
* - Example: <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
|
||||
* values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItem'}}],
|
||||
* $scope.selected = {name: 'aSubItem'};
|
||||
* - track by is always applied to `value`, with the purpose of preserving the selection,
|
||||
* (to `item` in this case)
|
||||
* - to calculate whether an item is selected we do the following:
|
||||
* 1. apply `track by` to the values in the array, e.g.
|
||||
* In the example: [1,2]
|
||||
* 2. apply `track by` to the already selected value in `ngModel`:
|
||||
* In the example: this is not possible, as `track by` refers to `item.id`, but the selected
|
||||
* value from `ngModel` is `{name: aSubItem}`.
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
|
||||
@@ -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(modelValue) || (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;
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
+27
-16
@@ -14,19 +14,26 @@
|
||||
*
|
||||
* Can be one of:
|
||||
*
|
||||
* - `string`: The string is evaluated as an expression and the resulting value is used for substring match against
|
||||
* the contents of the `array`. All strings or objects with string properties in `array` that contain this string
|
||||
* will be returned. The predicate can be negated by prefixing the string with `!`.
|
||||
* - `string`: The string is used for matching against the contents of the `array`. All strings or
|
||||
* objects with string properties in `array` that match this string will be returned. This also
|
||||
* applies to nested object properties.
|
||||
* The predicate can be negated by prefixing the string with `!`.
|
||||
*
|
||||
* - `Object`: A pattern object can be used to filter specific properties on objects contained
|
||||
* by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
|
||||
* which have property `name` containing "M" and property `phone` containing "1". A special
|
||||
* property name `$` can be used (as in `{$:"text"}`) to accept a match against any
|
||||
* property of the object. That's equivalent to the simple substring match with a `string`
|
||||
* as described above. The predicate can be negated by prefixing the string with `!`.
|
||||
* For Example `{name: "!M"}` predicate will return an array of items which have property `name`
|
||||
* property of the object or its nested object properties. That's equivalent to the simple
|
||||
* substring match with a `string` as described above. The predicate can be negated by prefixing
|
||||
* the string with `!`.
|
||||
* For example `{name: "!M"}` predicate will return an array of items which have property `name`
|
||||
* not containing "M".
|
||||
*
|
||||
* Note that a named property will match properties on the same level only, while the special
|
||||
* `$` property will match properties on the same level or deeper. E.g. an array item like
|
||||
* `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
|
||||
* **will** be matched by `{$: 'John'}`.
|
||||
*
|
||||
* - `function(value, index)`: A predicate function can be used to write arbitrary filters. The
|
||||
* function is called for each element of `array`. The final result is an array of those
|
||||
* elements that the predicate returned true for.
|
||||
@@ -39,10 +46,10 @@
|
||||
*
|
||||
* - `function(actual, expected)`:
|
||||
* The function will be given the object value and the predicate value to compare and
|
||||
* should return true if the item should be included in filtered result.
|
||||
* should return true if both values should be considered equal.
|
||||
*
|
||||
* - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`.
|
||||
* this is essentially strict comparison of expected and actual.
|
||||
* - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
|
||||
* This is essentially strict comparison of expected and actual.
|
||||
*
|
||||
* - `false|undefined`: A short hand for a function which will look for a substring match in case
|
||||
* insensitive way.
|
||||
@@ -145,6 +152,7 @@ function filterFilter() {
|
||||
|
||||
// Helper functions for `filterFilter`
|
||||
function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
|
||||
var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
|
||||
var predicateFn;
|
||||
|
||||
if (comparator === true) {
|
||||
@@ -163,19 +171,22 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
|
||||
}
|
||||
|
||||
predicateFn = function(item) {
|
||||
if (shouldMatchPrimitives && !isObject(item)) {
|
||||
return deepCompare(item, expression.$, comparator, false);
|
||||
}
|
||||
return deepCompare(item, expression, comparator, matchAgainstAnyProp);
|
||||
};
|
||||
|
||||
return predicateFn;
|
||||
}
|
||||
|
||||
function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
|
||||
function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
|
||||
var actualType = typeof actual;
|
||||
var expectedType = typeof expected;
|
||||
|
||||
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) {
|
||||
@@ -188,11 +199,11 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
|
||||
var key;
|
||||
if (matchAgainstAnyProp) {
|
||||
for (key in actual) {
|
||||
if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) {
|
||||
if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
|
||||
} else if (expectedType === 'object') {
|
||||
for (key in expected) {
|
||||
var expectedVal = expected[key];
|
||||
@@ -200,9 +211,9 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var keyIsDollar = key === '$';
|
||||
var actualVal = keyIsDollar ? actual : actual[key];
|
||||
if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) {
|
||||
var matchAnyProperty = key === '$';
|
||||
var actualVal = matchAnyProperty ? actual : actual[key];
|
||||
if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,36 +97,11 @@ function limitToFilter() {
|
||||
limit = int(limit);
|
||||
}
|
||||
|
||||
if (isString(input)) {
|
||||
//NaN check on limit
|
||||
if (limit) {
|
||||
return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
var out = [],
|
||||
i, n;
|
||||
|
||||
// if abs(limit) exceeds maximum length, trim it
|
||||
if (limit > input.length)
|
||||
limit = input.length;
|
||||
else if (limit < -input.length)
|
||||
limit = -input.length;
|
||||
|
||||
if (limit > 0) {
|
||||
i = 0;
|
||||
n = limit;
|
||||
//NaN check on limit
|
||||
if (limit) {
|
||||
return limit > 0 ? input.slice(0, limit) : input.slice(limit);
|
||||
} else {
|
||||
i = input.length + limit;
|
||||
n = input.length;
|
||||
return isString(input) ? "" : [];
|
||||
}
|
||||
|
||||
for (; i < n; i++) {
|
||||
out.push(input[i]);
|
||||
}
|
||||
|
||||
return out;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -130,9 +130,7 @@ function orderByFilter($parse) {
|
||||
}
|
||||
if (predicate === '') {
|
||||
// Effectively no predicate was passed so we compare identity
|
||||
return reverseComparator(function(a, b) {
|
||||
return compare(a, b);
|
||||
}, descending);
|
||||
return reverseComparator(compare, descending);
|
||||
}
|
||||
get = $parse(predicate);
|
||||
if (get.constant) {
|
||||
@@ -174,14 +172,14 @@ function orderByFilter($parse) {
|
||||
|
||||
function objectToString(value) {
|
||||
if (value === null) return 'null';
|
||||
if (typeof value.toString === 'function') {
|
||||
value = value.toString();
|
||||
if (isPrimitive(value)) return value;
|
||||
}
|
||||
if (typeof value.valueOf === 'function') {
|
||||
value = value.valueOf();
|
||||
if (isPrimitive(value)) return value;
|
||||
}
|
||||
if (typeof value.toString === 'function') {
|
||||
value = value.toString();
|
||||
if (isPrimitive(value)) return value;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -381,7 +381,7 @@ function $HttpProvider() {
|
||||
*
|
||||
* Both requests and responses can be transformed using transformation functions: `transformRequest`
|
||||
* and `transformResponse`. These properties can be a single function that returns
|
||||
* the transformed value (`{function(data, headersGetter, status)`) or an array of such transformation functions,
|
||||
* the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
|
||||
* which allows you to `push` or `unshift` a new transformation function into the transformation chain.
|
||||
*
|
||||
* ### Default Transformations
|
||||
|
||||
+8
-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;
|
||||
}
|
||||
});
|
||||
@@ -800,18 +800,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 +939,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;
|
||||
|
||||
@@ -9,7 +9,7 @@ var $compileMinErr = minErr('$compile');
|
||||
* @description
|
||||
* The `$templateRequest` service downloads the provided template using `$http` and, upon success,
|
||||
* stores the contents inside of `$templateCache`. If the HTTP request fails or the response data
|
||||
* of the HTTP request is empty then a `$compile` error will be thrown (the exception can be thwarted
|
||||
* of the HTTP request is empty, a `$compile` error will be thrown (the exception can be thwarted
|
||||
* by setting the 2nd parameter of the function to true).
|
||||
*
|
||||
* @param {string} tpl The HTTP request template URL
|
||||
|
||||
@@ -833,7 +833,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
* promise that was returned when the animation was started.
|
||||
*
|
||||
* ```js
|
||||
* var promise = $animate.addClass(element, 'super-long-animation').then(function() {
|
||||
* var promise = $animate.addClass(element, 'super-long-animation');
|
||||
* promise.then(function() {
|
||||
* //this will still be called even if cancelled
|
||||
* });
|
||||
*
|
||||
|
||||
+19
-12
@@ -297,21 +297,28 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
}
|
||||
};
|
||||
})
|
||||
.directive('ngClick',['$aria', function($aria) {
|
||||
.directive('ngClick',['$aria', '$parse', function($aria, $parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem, attr) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
compile: function(elem, attr) {
|
||||
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
|
||||
return function(scope, elem, attr) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
if ($aria.config('bindKeypress') && !elem.attr('ng-keypress')) {
|
||||
elem.on('keypress', function(event) {
|
||||
if (event.keyCode === 32 || event.keyCode === 13) {
|
||||
scope.$eval(attr.ngClick);
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($aria.config('bindKeypress') && !attr.ngKeypress) {
|
||||
elem.on('keypress', function(event) {
|
||||
if (event.keyCode === 32 || event.keyCode === 13) {
|
||||
scope.$apply(callback);
|
||||
}
|
||||
|
||||
function callback() {
|
||||
fn(scope, { $event: event });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
Vendored
+25
-25
@@ -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:
|
||||
@@ -1276,7 +1276,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1290,7 +1290,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1304,7 +1304,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1320,7 +1320,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1336,7 +1336,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1349,7 +1349,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1370,7 +1370,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* is in JSON format.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*
|
||||
@@ -1405,7 +1405,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled. See #expect for more info.
|
||||
*/
|
||||
@@ -1419,7 +1419,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1433,7 +1433,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1450,7 +1450,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1467,7 +1467,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1484,7 +1484,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1497,7 +1497,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+989
-3586
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');
|
||||
});
|
||||
});
|
||||
@@ -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
@@ -0,0 +1,507 @@
|
||||
'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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -65,6 +65,66 @@ describe('Filter: filter', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should match primitive array values against top-level `$` property in object expression',
|
||||
function() {
|
||||
var items, expr;
|
||||
|
||||
items = ['something', 'something else', 'another thing'];
|
||||
expr = {$: 'some'};
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[1]]);
|
||||
|
||||
items = [{val: 'something'}, {val: 'something else'}, {val: 'another thing'}];
|
||||
expr = {$: 'some'};
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[1]]);
|
||||
|
||||
items = [123, 456, 789];
|
||||
expr = {$: 1};
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)).toEqual([items[0]]);
|
||||
|
||||
items = [true, false, 'true'];
|
||||
expr = {$: true, ignored: 'false'};
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[2]]);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
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'}];
|
||||
@@ -136,14 +196,30 @@ describe('Filter: filter', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should respect the depth level of a "$" property', function() {
|
||||
var items = [{person: {name: 'Annet', email: 'annet@example.com'}},
|
||||
{person: {name: 'Billy', email: 'me@billy.com'}},
|
||||
{person: {name: 'Joan', email: {home: 'me@joan.com', work: 'joan@example.net'}}}];
|
||||
var expr = {person: {$: 'net'}};
|
||||
it('should match named properties only against named properties on the same level', function() {
|
||||
var expr = {person: {name: 'John'}};
|
||||
var items = [{person: 'John'}, // No match (1 level higher)
|
||||
{person: {name: 'John'}}, // Match (same level)
|
||||
{person: {name: {first: 'John', last: 'Doe'}}}]; // No match (1 level deeper)
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)).toEqual([items[0]]);
|
||||
expect(filter(items, expr)).toEqual([items[1]]);
|
||||
});
|
||||
|
||||
|
||||
it('should match any properties on same or deeper level for given "$" property', function() {
|
||||
var items = [{level1: 'test', foo1: 'bar1'},
|
||||
{level1: {level2: 'test', foo2:'bar2'}, foo1: 'bar1'},
|
||||
{level1: {level2: {level3: 'test', foo3: 'bar3'}, foo2: 'bar2'}, foo1: 'bar1'}];
|
||||
|
||||
expect(filter(items, {$: 'ES'}).length).toBe(3);
|
||||
expect(filter(items, {$: 'ES'})).toEqual([items[0], items[1], items[2]]);
|
||||
|
||||
expect(filter(items, {level1: {$: 'ES'}}).length).toBe(2);
|
||||
expect(filter(items, {level1: {$: 'ES'}})).toEqual([items[1], items[2]]);
|
||||
|
||||
expect(filter(items, {level1: {level2: {$: 'ES'}}}).length).toBe(1);
|
||||
expect(filter(items, {level1: {level2: {$: 'ES'}}})).toEqual([items[2]]);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -79,6 +79,16 @@ describe('Filter: orderBy', function() {
|
||||
{ a:new Date('01/01/2014'), b:3 }]);
|
||||
});
|
||||
|
||||
it('should compare timestamps when sorting dates', function() {
|
||||
expect(orderBy([
|
||||
new Date('01/01/2015'),
|
||||
new Date('01/01/2014')
|
||||
])).toEqualData([
|
||||
new Date('01/01/2014'),
|
||||
new Date('01/01/2015')
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should use function', function() {
|
||||
expect(
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -509,6 +509,23 @@ describe('$aria', function() {
|
||||
expect(clickFn).not.toHaveBeenCalled();
|
||||
expect(keypressFn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update bindings when keypress handled', function() {
|
||||
compileInput('<div ng-click="text = \'clicked!\'">{{text}}</div>');
|
||||
expect(element.text()).toBe('');
|
||||
spyOn(scope.$root, '$digest').andCallThrough();
|
||||
element.triggerHandler({ type: 'keypress', keyCode: 13 });
|
||||
expect(element.text()).toBe('clicked!');
|
||||
expect(scope.$root.$digest).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should pass $event to ng-click handler as local', function() {
|
||||
compileInput('<div ng-click="event = $event">{{event.type}}' +
|
||||
'{{event.keyCode}}</div>');
|
||||
expect(element.text()).toBe('');
|
||||
element.triggerHandler({ type: 'keypress', keyCode: 13 });
|
||||
expect(element.text()).toBe('keypress13');
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions when bindKeypress set to false', function() {
|
||||
|
||||
Vendored
+33
-13
@@ -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!");
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user