Compare commits

...

57 Commits

Author SHA1 Message Date
Igor Minar 3efdeebcb7 chore(release): cut 1.2.2 consciousness-inertia release 2013-11-22 09:05:42 -08:00
Igor Minar 16febf8357 docs(CHANGELOG): add release notes for 1.2.2 consciousness-inertia 2013-11-22 09:03:40 -08:00
Caitlin Potter 0f7c4ca671 chore(style): fix missing indentation in httpBackend from a3172a2 2013-11-22 15:03:12 +00:00
Pete Bacon Darwin a3172a285f fix($httpBackend): only IE8 and below can't use script.onload for JSONP
IE8, IE9 and IE10 can use `script.onreadystate` so up till now we have been using this
if the sniffer says we are on IE.
But IE11 now does not support `script.onreadystate` and only supports the more standard
`script.onload` and `script.onerror`.
IE9 and IE10 do support `script.onload` and `script.onerror`. So now we only test whether
we are on IE8 or earlier before using `script.onreadystate`.
See http://pieisgood.org/test/script-link-events/

jQuery just uses all these handlers at once and hopes for the best, but since IE9 and IE10
support both sets of handlers, this could cause the handlers to be run more than once.

jQuery also notes that there is a potential memory leak in IE unless we remove the handlers
from the script object once they are run.  So we are doing this too, now.

Closes #4523
Closes #4527
Closes #4922
2013-11-22 13:45:55 +00:00
Igor Minar 84c408ce63 test($compile): correct the assertion to make test pass on IE11 2013-11-22 00:52:57 -08:00
rodyhaddad 40647b179c fix($parse): allow for new lines in expr when promise unwrapping is on
Previously, when unwrapping promises was set to `true`,
an error would occur if a parsed expression had a
new line in it.
This was because when generating the `evaledFnGetter` code,
a new line in an parsed expression would create a new line
in a JS string in that code, which is illegal. That is:
```js
pw("A+
B")
```

Closes #4718
2013-11-22 00:19:08 -08:00
Chirayu Krishnappa 0421cb4200 fix($compile): secure form[action] & iframe[srcdoc]
Require bindings to form[action] to be $sce.RESOURCE_URL and bindings to
iframe[srcdoc] to be $sce.HTML

Closes #4927
Closes #4933
2013-11-21 23:15:15 -08:00
Gonzalo Ruiz de Villa 6f1050df4f fix(httpBackend): should not read response data when request is aborted
When a request is aborted, it makes no sense to read the response headers or text.
Also in IE9, trying to read data (either response headers or text) from an aborted request
throws an Error c00c023f.

Fixes #4913
Closes #4940
2013-11-21 23:09:46 -08:00
Corey Burrows 4d16472b91 fix(ngMock): fixes httpBackend expectation with body object
Fixes an issue with httpBackend expectations where a given body object
may not match the actual request body if its keys are serialized in a
different order.

Closes #4956
2013-11-21 22:57:15 -08:00
Pete Bacon Darwin 9e89a31b12 fix(loader): expose $$minErr to modules such asngResource
This is highlighted in angular-phonecat when you try to use the index-async.html
which needs to load the ngResource module asynchronously but fails when it tries
to call `angular.$$minErr` to create the $resourceMinErr object.

Closes #5050
2013-11-21 22:51:26 -08:00
Tobias Bosch e6521e7491 fix(ngView): Don't throw when the ngView element contains content with directives.
Fixes #5069
2013-11-21 22:20:11 -08:00
Tobias Bosch 0a7cbb33b0 fix(ngInclude): Don't throw when the ngInclude element contains content with directives.
Related to #5069
2013-11-21 22:20:11 -08:00
Tobias Bosch 579242346c fix(tests): Correct tests for IE11
Some tests were wrong. However, src/* did not contain problems.

Fixes #5046
2013-11-21 21:53:09 -08:00
Peter Deak c42d0a0418 fix(ngAnimate): correctly retain and restore existing styles during and after animation
Closes #4869
2013-11-21 23:37:01 -05:00
Matias Niemelä 3fbb25e25c chore($animate): remove unnecessary reflective dereferencing 2013-11-21 20:48:21 -05:00
Matias Niemelä 6760d7a315 fix($animate): ensure keyframe animations are blocked around the reflow
Keyframe animations trigger on the first CSS class and not the second.
This may cause a slight flicker during a stagger animation since the
animation has already started before the stagger delay is considered.
This fix ensures that the animation is blocked until the active animation
starts which allows for staggering animations to take over properly.

Closes #5018
2013-11-21 20:48:15 -05:00
Matias Niemelä 062fbed8fc fix($animate): ensure transition animations are unblocked before the dom operation occurs
Transitions are blocked when the base CSS class is added at the start of the animation. This
causes an issue if the followup CSS class contains animatable-styles. Now, once the animation
active state is triggered (when the animation CSS dom operation occurs) the animation itself
will always trigger an animate without a quick jump.

Closes #5014
Closes #4265
2013-11-21 20:48:07 -05:00
Matias Niemelä 76e4db6f3d fix($animate): ensure addClass/removeClass animations do not snap during reflow
Closes #4892
2013-11-21 20:47:55 -05:00
Matias Niemelä 0cd7e8f227 fix($compile): ensure CSS classes are added and removed only when necessary
When $compile interpolates a CSS class attribute expression it will
do so by comparing the CSS class value already present on the element.
This may lead to unexpected results when dealing with ngClass values being
added and removed therefore it is best that both compile and ngClass delegate
addClass/removeClass operations to the same block of code.
2013-11-21 20:47:44 -05:00
Vojta Jina ba1b47f85b test(docs): fix the failing specs
Because Grunt was not failing the build, we didn't noticed these failing specs.
2013-11-21 12:41:57 -08:00
Vojta Jina 0a3481e23a chore: use temporary version of grunt-jasmine-node
This should be reverted once https://github.com/jasmine-contrib/grunt-jasmine-node/pull/33 gets merged in the upstream repo.

It fixes the problem where Grunt does not fail the build, even though there are failures.
See https://travis-ci.org/angular/angular.js/builds/14329011#L2366
2013-11-21 12:41:48 -08:00
jody tate e33c365144 docs(guide/unit-testing): minor style and grammar changes
Closes #5057
2013-11-21 20:37:57 +00:00
gdi2290 e3ceb50b73 docs(faq): update compressed and minified file size
Closes #5058
2013-11-21 20:27:27 +00:00
scottywakefield 6b5772bbbd docs(guide/ie): add info about what IE versions are supported
Added text from https://github.com/angular/angular.js/issues/4974

Closes #5070
2013-11-21 20:25:41 +00:00
Andrew Silluron-Gonzalez 6288cf5ca4 fix(ngController): fix issue with ngInclude on the same element
This changes the priority of ngController to 500 so that it takes precedence
over ngInclude.

Closes #4431, #4521
2013-11-21 09:52:34 -08:00
Tobias Bosch f6ecf9a3c9 fix($resource): Always return a resource instance when calling class methods on resources.
Previously, calling `MyResource.save(myResourceInstance)`returned
a promise, in contrast to the docs for `$resource`. However,
calling `MyResource.save({name: 'Tobias"})`already correctly
returned a resource instance.

Fixes #4545.
Closes #5061.
2013-11-21 09:51:02 -08:00
Chia-liang Kao a4e6d962d7 feat(input): hold listener during text composition
When composing text in CJKV, intermediate buffer for unfinished text should not
be updating the bound scope variables.

Closes #4684
2013-11-21 09:46:33 -08:00
Igor Minar 7874a4d007 docs(guide/migration): fix typo ngHtmlBind -> ngBindHtml 2013-11-21 07:29:18 -08:00
Matias Niemelä 1d50663b38 fix(ngAnimate): use a fallback CSS property that doesn't break existing styles
The clip property seems to remove the box-shadow property when an absolute
positioned animation is ongoing. This fix changes the property to be border-spacing
which is also very underused. The border-spacing CSS property is only visible
when border-collapse is set to separate.

Closes #4902
Closes #5030
2013-11-20 20:54:07 -05:00
Tobias Bosch ec3c4f94c7 refactor($sce): Use $sniffer instead of $document for feature detection.
Also adds `$sniffer.msieDocumentMode` property.

Closes #4931
Closes #5045
2013-11-20 23:12:39 +00:00
Matias Niemelä 6b8bbe4d90 fix(ngClass): ensure that ngClass only adds/removes the changed classes
ngClass works by removing all the former classes and then adding all the
new classes to the element during each watch change operation. This may
cause transition animations to never render. The ngClass directive will
now only add and remove the classes that change during each watch operation.

Closes #4960
Closes #4944
2013-11-20 17:15:56 -05:00
Matias Niemelä 7067a8fb0b fix($animate): ensure the DOM operation isn't run twice
Depending on the animations placed on ngClass, the DOM operation may
run twice causing a race condition between addClass and removeClass.
Depending on what classes are removed and added via $compile this may
cause all CSS classes to be removed accidentally from the element
being animated.

Closes #4949
2013-11-20 17:08:03 -05:00
Brian Ford c47abd0dd7 fix(ngInclude): allow ngInclude to load scripts when jQuery is included
In 1.2, the behavior of ngInclude was modified to use DOM APIs rather than jqLite. This means that
even when jQuery was loaded, ngInclude was not calling into it, and thus scripts were not eval'd
as they had been before. Although the use of ngInclude to eval scripts as a lazy-loading strategy
was never an intentional feature, this patch restores the ability to do so.

Closes #3756
2013-11-20 13:58:54 -08:00
Michel Salib 68d71bbc01 docs($log): the documented default log behavior was incorrect
Closes #4953
2013-11-20 21:02:02 +00:00
Jarrett Harris 334303e485 docs(ngAnimate): fixed two small typos
Line 162: 'defiend' should be 'defined'
Line 225: 'callback function be excuted' should be 'callback function will be executed'.

Closes #5048
2013-11-20 14:32:30 +00:00
gdennie b95fd53c6e docs(booleanAtts): explain the motivation for boolean attributes
It was not explicitly and consistently stated that the transient nature of boolean
attributes precludes them from hosting binding expressions.
This change make that more clear and reinforces the simplicity and elegance of the solution.

Closes #5031
2013-11-20 14:30:37 +00:00
Ashutosh Das c77dd040b4 docs(tutorial/step-2): correct the link to jasmine docs
Closes #5029
2013-11-20 14:21:49 +00:00
Stéphane Reynaud dc027f22e5 docs(ngRepeat): fix typo
Replace "ian" in "in"

Closes #5027
2013-11-20 14:10:23 +00:00
Stéphane Reynaud 043500fb27 docs(ngPluralize): Fix missing space before parentheses
There should be a space between "braces" and "(`{}`)"

Closes #5026
2013-11-20 11:38:29 +00:00
Maksim 3ceb6ab477 docs(guide/directive): use hideDialog handler in example
The handler is in the controller but was not being used in the template.

Closes #5020
2013-11-20 11:37:00 +00:00
gipsy86147 999fa44616 docs(guide/ie): fix typo
Closes #5006
2013-11-20 11:31:56 +00:00
Dave Gaeddert 5f9121ad56 docs(guide/providers): remove extra closing parenthesis in example
Closes #5005
2013-11-20 11:30:45 +00:00
Tyler Eich b4cf8483d7 docs(guide/migration): fix typo
Closes #5002
2013-11-20 11:26:57 +00:00
Yves Richard 8a9816e06b docs(guide/compiler): fix typo in isolate scope def
Closes #4999
2013-11-20 11:25:20 +00:00
jbnizet c0e10683a6 docs(api): example for $provide.value() uses $provide.value()
The example code for `$provide.value()` actually used `$provide.constant()`.
It now uses `$provide.value()`.

Closes #4983
Closes #4990
2013-11-20 11:21:09 +00:00
Pete Bacon Darwin cad5a367c3 docs(ngRoute): make it easier to find the example
Closes #4975
2013-11-20 11:18:21 +00:00
Stéphane Reynaud f2453eabb3 docs(tutorial): minimum node.js version is 0.10 (Windows too)
The doc has been modified by the following commit: bcc6e8d4f6
But the change was not made ​​for the part of Windows.

Closes #4967
2013-11-20 11:02:30 +00:00
Jayson Harshbarger aa0b11d794 docs(guide/migration): fix typo
Closes #4965
2013-11-20 11:00:01 +00:00
Matias Niemelä b9fa5c5a67 docs($animate): update the docs explaining enable/disable for specific elements 2013-11-19 17:50:24 -05:00
Vojta Jina 751f058f30 chore(travis): increase disconnect timeout
I still see some disconnection issues with IE9, hopefully this will help a bit.
2013-11-18 16:14:09 -08:00
Pete Bacon Darwin 29274e1d8d docs(ngApp): improve description and example 2013-11-18 16:21:59 +00:00
Jens Berthold 23ba287897 docs(guide/directive): clarify code example for isolated scopes bindings
Use different names for the attribute on the element (`info`) and the property (`customerInfo`)
on the isolate scope. Before `customer` was used for both which made it harder to understand.

Closes #4825
2013-11-18 15:03:35 +00:00
Pete Bacon Darwin 8f1e3606dd docs(guide/directive): add note about HTML case-insensitivity
Closes #4719
2013-11-18 12:58:53 +00:00
Chance ac56d1c9d9 docs(tutorial/step-4): controllers are no longer global functions
The docs did not line up with the codebase / previous steps of the tutorial.

Closes #4988
2013-11-18 12:49:19 +00:00
Pete Bacon Darwin de2919cb9a docs(guide/i18n): fix link to i18n files in the project
Closes #4998
2013-11-18 11:55:30 +00:00
Christoph Burgdorf 61943276f0 chore(*): remove accidentally created file
Closes #4963
2013-11-15 21:46:13 +00:00
Vojta Jina 88ce00a3cf chore(release): start a new release iteration 2013-11-15 00:03:03 -08:00
55 changed files with 1906 additions and 1092 deletions
+83
View File
@@ -1,3 +1,86 @@
<a name="1.2.2"></a>
# 1.2.2 consciousness-inertia (2013-11-22)
## Bug Fixes
- **$animate:**
- ensure keyframe animations are blocked around the reflow
([6760d7a3](https://github.com/angular/angular.js/commit/6760d7a315d7ea5cbd4f8ab74b200f754a2041f4),
[#5018](https://github.com/angular/angular.js/issues/5018))
- ensure transition animations are unblocked before the dom operation occurs
([062fbed8](https://github.com/angular/angular.js/commit/062fbed8fc3f7bc55433f8c6915c27520e6f63c5),
[#5014](https://github.com/angular/angular.js/issues/5014),
[#4265](https://github.com/angular/angular.js/issues/4265))
- ensure addClass/removeClass animations do not snap during reflow
([76e4db6f](https://github.com/angular/angular.js/commit/76e4db6f3d15199ac1fbe85f9cfa6079a1c4fa56),
[#4892](https://github.com/angular/angular.js/issues/4892))
- ensure the DOM operation isn't run twice
([7067a8fb](https://github.com/angular/angular.js/commit/7067a8fb0b18d5b5489006e1960cee721a88b4d2),
[#4949](https://github.com/angular/angular.js/issues/4949))
- **$compile:**
- secure form[action] & iframe[srcdoc]
([0421cb42](https://github.com/angular/angular.js/commit/0421cb4200e672818ed10996e92311404c150c3a),
[#4927](https://github.com/angular/angular.js/issues/4927),
[#4933](https://github.com/angular/angular.js/issues/4933))
- ensure CSS classes are added and removed only when necessary
([0cd7e8f2](https://github.com/angular/angular.js/commit/0cd7e8f22721f62b62440bb059ae764ebbe7b42a))
- **$httpBackend:** only IE8 and below can't use `script.onload` for JSONP
([a3172a28](https://github.com/angular/angular.js/commit/a3172a285fd74b5aa6c8d68a4988c767c06f549c),
[#4523](https://github.com/angular/angular.js/issues/4523),
[#4527](https://github.com/angular/angular.js/issues/4527),
[#4922](https://github.com/angular/angular.js/issues/4922))
- **$parse:** allow for new lines in expr when promise unwrapping is on
([40647b17](https://github.com/angular/angular.js/commit/40647b179c473f3f470bb1b3237d6f006269582f),
[#4718](https://github.com/angular/angular.js/issues/4718))
- **$resource:** Always return a resource instance when calling class methods on resources.
([f6ecf9a3](https://github.com/angular/angular.js/commit/f6ecf9a3c9090593faf5fa50586c99a56b51c776),
[#4545](https://github.com/angular/angular.js/issues/4545),
[#5061](https://github.com/angular/angular.js/issues/5061))
- **httpBackend:** should not read response data when request is aborted
([6f1050df](https://github.com/angular/angular.js/commit/6f1050df4fa885bd59ce85adbef7350ea93911a3),
[#4913](https://github.com/angular/angular.js/issues/4913),
[#4940](https://github.com/angular/angular.js/issues/4940))
- **loader:** expose `$$minErr` to modules such as`ngResource`
([9e89a31b](https://github.com/angular/angular.js/commit/9e89a31b129e40c805178535c244899ffafb77d8),
[#5050](https://github.com/angular/angular.js/issues/5050))
- **ngAnimate:**
- correctly retain and restore existing styles during and after animation
([c42d0a04](https://github.com/angular/angular.js/commit/c42d0a041890b39fc98afd357ec1307a3a36208d),
[#4869](https://github.com/angular/angular.js/issues/4869))
- use a fallback CSS property that doesn't break existing styles
([1d50663b](https://github.com/angular/angular.js/commit/1d50663b38ba042e8d748ffa6d48cfb5e93cfd7e),
[#4902](https://github.com/angular/angular.js/issues/4902),
[#5030](https://github.com/angular/angular.js/issues/5030))
- **ngClass:** ensure that ngClass only adds/removes the changed classes
([6b8bbe4d](https://github.com/angular/angular.js/commit/6b8bbe4d90640542eed5607a8c91f6b977b1d6c0),
[#4960](https://github.com/angular/angular.js/issues/4960),
[#4944](https://github.com/angular/angular.js/issues/4944))
- **ngController:** fix issue with ngInclude on the same element
([6288cf5c](https://github.com/angular/angular.js/commit/6288cf5ca471b0615a026fdb4db3ba242c9d8f88),
[#4431](https://github.com/angular/angular.js/issues/4431))
- **ngInclude:**
- Don't throw when the ngInclude element contains content with directives.
([0a7cbb33](https://github.com/angular/angular.js/commit/0a7cbb33b06778833a4d99b1868cc07690a827a7))
- allow ngInclude to load scripts when jQuery is included
([c47abd0d](https://github.com/angular/angular.js/commit/c47abd0dd7490576f4b84ee51ebaca385c1036da),
[#3756](https://github.com/angular/angular.js/issues/3756))
- **ngMock:** fixes httpBackend expectation with body object
([4d16472b](https://github.com/angular/angular.js/commit/4d16472b918a3482942d76f1e273a5aa01f65e83),
[#4956](https://github.com/angular/angular.js/issues/4956))
- **ngView:** Don't throw when the ngView element contains content with directives.
([e6521e74](https://github.com/angular/angular.js/commit/e6521e7491242504250b57dd0ee66af49e653c33),
[#5069](https://github.com/angular/angular.js/issues/5069))
- **tests:** Correct tests for IE11
([57924234](https://github.com/angular/angular.js/commit/579242346c4202ea58fc2cae6df232289cbea0bb),
[#5046](https://github.com/angular/angular.js/issues/5046))
- **input:** hold listener during text composition
([a4e6d962](https://github.com/angular/angular.js/commit/a4e6d962d78b26f5112d48c4f88c1e6234d0cae7),
[#4684](https://github.com/angular/angular.js/issues/4684))
<a name="1.2.1"></a>
# 1.2.1 underscore-empathy (2013-11-14)
+4 -4
View File
@@ -15,8 +15,8 @@ ng\:form {
* when the active class isn't set, or if the active class doesn't
* contain any styles to transition to, then, if ngAnimate is used,
* it will appear as if the webpage is broken due to the forever hanging
* animations. The clip (!ie) and zoom (ie) CSS properties are used
* since they trigger a transition without making the browser
* animations. The border-spacing (!ie) and zoom (ie) CSS properties are
* used below since they trigger a transition without making the browser
* animate anything and they're both highly underused CSS properties */
.ng-animate-start { clip:rect(0, auto, auto, 0); -ms-zoom:1.0001; }
.ng-animate-active { clip:rect(-1px, auto, auto, 0); -ms-zoom:1; }
.ng-animate-start { border-spacing:1px 1px; -ms-zoom:1.0001; }
.ng-animate-active { border-spacing:0px 0px; -ms-zoom:1; }
+1 -1
View File
@@ -350,7 +350,7 @@ Creating local properties on widget scope creates two problems:
To solve the issue of lack of isolation, the directive declares a new `isolated` scope. An
isolated scope does not prototypically inherit from the child scope, and therefore we don't have
isolated scope does not prototypically inherit from the parent scope, and therefore we don't have
to worry about accidentally clobbering any properties.
However `isolated` scope creates a new problem: if a transcluded DOM is a child of the widget
+49 -50
View File
@@ -13,45 +13,44 @@ Unit testing as the name implies is about testing individual units of code. Unit
answer questions such as "Did I think about the logic correctly?" or "Does the sort function order the list
in the right order?"
In order to answer such question it is very important that we can isolate the unit of code under test.
In order to answer such a question it is very important that we can isolate the unit of code under test.
That is because when we are testing the sort function we don't want to be forced into creating
related pieces such as the DOM elements, or making any XHR calls in getting the data to sort.
While
this may seem obvious it usually is very difficult to be able to call an individual function on a
typical project. The reason is that the developers often mix concerns, and they end up with a
piece of code which does everything. It reads the data from XHR, it sorts it and then it
While this may seem obvious it can be very difficult to call an individual function on a
typical project. The reason is that the developers often mix concerns resulting in a
piece of code which does everything. It makes an XHR request, it sorts the response data and then it
manipulates the DOM.
With Angular we try to make it easy for you to do the right thing, and so we
provide dependency injection for your XHR (which you can mock out) and we created abstraction which
provide dependency injection for your XHR (which you can mock out) and we created abstractions which
allow you to sort your model without having to resort to manipulating the DOM. So that in the end,
it is easy to write a sort function which sorts some data, so that your test can create a data set,
apply the function, and assert that the resulting model is in the correct order. The test does not
have to wait for XHR, or create the right kind of DOM, or assert that your function has mutated the
DOM in the right way.
have to wait for the XHR response to arrive, create the right kind of test DOM, nor assert that your
function has mutated the DOM in the right way.
## With great power comes great responsibility
Angular is written with testability in mind, but it still requires that you
do the right thing. We tried to make the right thing easy, but Angular is not magic, which means if
you don't follow these guidelines you may very well end up with an untestable application.
Angular is written with testability in mind, but it still requires that you do the right thing.
We tried to make the right thing easy, but Angular is not magic. If you don't follow these guidelines
you may very well end up with an untestable application.
## Dependency Injection
There are several ways in which you can get a hold of a dependency:
1. You could create it using the `new` operator.
2. You could look for it in a well known place, also known as global singleton.
3. You could ask a registry (also known as service registry) for it. (But how do you get a hold of
the registry? Most likely by looking it up in a well known place. See #2)
4. You could expect that it be handed to you.
There are several ways in which you can get a hold of a dependency. You can:
1. Create it using the `new` operator.
2. Look for it in a well-known place, also known as a global singleton.
3. Ask a registry (also known as service registry) for it. (But how do you get a hold of
the registry? Most likely by looking it up in a well known place. See #2.)
4. Expect it to be handed to you.
Out of the four options in the list above, only the last one is testable. Let's look at why:
### Using the `new` operator
While there is nothing wrong with the `new` operator fundamentally the issue is that calling a new
on a constructor permanently binds the call site to the type. For example lets say that we are
trying to instantiate an `XHR` so that we can get some data from the server.
While there is nothing wrong with the `new` operator fundamentally, a problem arises when calling `new`
on a constructor. This permanently binds the call site to the type. For example, lets say that we try to
instantiate an `XHR` that will retrieve data from the server.
<pre>
function MyClass() {
@@ -64,12 +63,12 @@ function MyClass() {
}
</pre>
The issue becomes that in tests, we would very much like to instantiate a `MockXHR` which would
A problem surfaces in tests when we would like to instantiate a `MockXHR` that would
allow us to return fake data and simulate network failures. By calling `new XHR()` we are
permanently bound to the actual XHR, and there is no good way to replace it. Yes there is monkey
patching. That is a bad idea for many reasons which are outside the scope of this document.
permanently bound to the actual XHR and there is no way to replace it. Yes, we could monkey
patch, but that is a bad idea for many reasons which are outside the scope of this document.
The class above is hard to test since we have to resort to monkey patching:
Here's an example of how the class above becomes hard to test when resorting to monkey patching:
<pre>
var oldXHR = XHR;
XHR = function MockXHR() {};
@@ -81,7 +80,7 @@ XHR = oldXHR; // if you forget this bad things will happen
### Global look-up:
Another way to approach the problem is to look for the service in a well known location.
Another way to approach the problem is to look for the service in a well-known location.
<pre>
function MyClass() {
@@ -95,14 +94,14 @@ function MyClass() {
}
</pre>
While no new instance of the dependency is being created, it is fundamentally the same as `new`, in
that there is no good way to intercept the call to `global.xhr` for testing purposes, other then
While no new dependency instance is created, it is fundamentally the same as `new` in
that no way exists to intercept the call to `global.xhr` for testing purposes, other then
through monkey patching. The basic issue for testing is that a global variable needs to be mutated in
order to replace it with call to a mock method. For further explanation why this is bad see: {@link
order to replace it with call to a mock method. For further explanation of why this is bad see: {@link
http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/ Brittle Global
State & Singletons}
The class above is hard to test since we have to change global state:
The class above is hard to test since we have to change the global state:
<pre>
var oldXHR = global.xhr;
global.xhr = function mockXHR() {};
@@ -115,7 +114,7 @@ global.xhr = oldXHR; // if you forget this bad things will happen
### Service Registry:
It may seem as that this can be solved by having a registry for all of the services, and then
It may seem that this can be solved by having a registry of all the services and then
having the tests replace the services as needed.
<pre>
@@ -131,12 +130,12 @@ function MyClass() {
}
</pre>
However, where does the serviceRegistry come from? if it is:
* `new`-ed up, the test has no chance to reset the services for testing
* global look-up, then the service returned is global as well (but resetting is easier, since
there is only one global variable to be reset).
However, where does the serviceRegistry come from? If it is:
* `new`-ed up, the test has no chance to reset the services for testing.
* a global look-up then the service returned is global as well (but resetting is easier, since
only one global variable exists to be reset).
The class above is hard to test since we have to change global state:
The class above is hard to test since we have to change the global state:
<pre>
var oldServiceLocator = global.serviceLocator;
global.serviceLocator.set('xhr', function mockXHR() {});
@@ -148,7 +147,7 @@ global.serviceLocator = oldServiceLocator; // if you forget this bad things will
### Passing in Dependencies:
Lastly the dependency can be passed in.
Last, the dependency can be passed in.
<pre>
function MyClass(xhr) {
@@ -161,12 +160,12 @@ function MyClass(xhr) {
}
</pre>
This is the preferred way since the code makes no assumptions as to where the `xhr` comes from,
rather that whoever created the class was responsible for passing it in. Since the creator of the
This is the preferred method since the code makes no assumptions about the origin of `xhr` and cares
instead about whoever created the class responsible for passing it in. Since the creator of the
class should be different code than the user of the class, it separates the responsibility of
creation from the logic, and that is what dependency-injection is in a nutshell.
creation from the logic. This is dependency-injection is in a nutshell.
The class above is very testable, since in the test we can write:
The class above is testable, since in the test we can write:
<pre>
function xhrMock(args) {...}
var myClass = new MyClass(xhrMock);
@@ -176,12 +175,12 @@ myClass.doWork();
Notice that no global variables were harmed in the writing of this test.
Angular comes with {@link di dependency injection} built in which makes the right thing
Angular comes with {@link di dependency injection} built-in, making the right thing
easy to do, but you still need to do it if you wish to take advantage of the testability story.
## Controllers
What makes each application unique is its logic, which is what we would like to test. If the logic
for your application is mixed in with DOM manipulation, it will be hard to test as in the example
What makes each application unique is its logic, and the logic is what we would like to test. If the logic
for your application contains DOM manipulation, it will be hard to test. See the example
below:
<pre>
@@ -209,7 +208,7 @@ function PasswordCtrl() {
}
</pre>
The code above is problematic from a testability point of view, since it requires your test to have the right kind
The code above is problematic from a testability point of view since it requires your test to have the right kind
of DOM present when the code executes. The test would look like this:
<pre>
@@ -226,8 +225,8 @@ expect(span.text()).toEqual('weak');
$('body').html('');
</pre>
In angular the controllers are strictly separated from the DOM manipulation logic which results in
a much easier testability story as can be seen in this example:
In angular the controllers are strictly separated from the DOM manipulation logic and this results in
a much easier testability story as the following example shows:
<pre>
function PasswordCtrl($scope) {
@@ -245,7 +244,7 @@ function PasswordCtrl($scope) {
}
</pre>
and the test is straight forward
and the test is straight forward:
<pre>
var $scope = {};
@@ -255,11 +254,11 @@ $scope.grade();
expect($scope.strength).toEqual('weak');
</pre>
Notice that the test is not only much shorter but it is easier to follow what is going on. We say
Notice that the test is not only much shorter, it is also easier to follow what is happening. We say
that such a test tells a story, rather then asserting random bits which don't seem to be related.
## Filters
{@link api/ng.$filterProvider Filters} are functions which transform the data into user readable
{@link api/ng.$filterProvider Filters} are functions which transform the data into a user readable
format. They are important because they remove the formatting responsibility from the application
logic, further simplifying the application logic.
@@ -282,7 +281,7 @@ you create with directives may be used throughout your application and in many d
### Simple HTML Element Directive
Lets start with an angular app with no dependencies.
Let's start with an angular app with no dependencies.
<pre>
var app = angular.module('myApp', []);
+25 -22
View File
@@ -56,9 +56,9 @@ The following also **matches** `ngModel`:
Angular **normalizes** an element's tag and attribute name to determine which elements match which
directives. We typically refer to directives by their case-sensitive
{@link http://en.wikipedia.org/wiki/CamelCase camelCase} **normalized** name (e.g. `ngModel`).
However, in the DOM, we refer to directives by case-insensitive forms, typically using
{@link http://en.wikipedia.org/wiki/Letter_case#Computers dash-delimited} attributes on DOM elements
(e.g. `ng-model`).
However, since HTML is case-insensitive, we refer to directives in the DOM by lower-case
forms, typically using {@link http://en.wikipedia.org/wiki/Letter_case#Computers dash-delimited}
attributes on DOM elements (e.g. `ng-model`).
The **normalization** process is as follows:
@@ -404,40 +404,43 @@ we call an **isolate scope**. To do this, we can use a directive's `scope` optio
return {
restrict: 'E',
scope: {
customer: '=customer'
customerInfo: '=info'
},
templateUrl: 'my-customer.html'
templateUrl: 'my-customer-iso.html'
};
});
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<my-customer customer="naomi"></my-customer>
<my-customer info="naomi"></my-customer>
<hr>
<my-customer customer="igor"></my-customer>
<my-customer info="igor"></my-customer>
</div>
</file>
<file name="my-customer.html">
Name: {{customer.name}} Address: {{customer.address}}
<file name="my-customer-iso.html">
Name: {{customerInfo.name}} Address: {{customerInfo.address}}
</file>
</example>
Looking at `index.html`, the first `<my-customer>` element binds the inner scope's `customer` to
`naomi`, which we have exposed on our controller's scope. The second binds `customer` to `igor`.
Looking at `index.html`, the first `<my-customer>` element binds the `info` attribute to `naomi`,
which we have exposed on our controller's scope. The second binds `info` to `igor`.
Let's take a closer look at the scope option:
```javascript
//...
scope: {
customer: '=customer'
customerInfo: '=info'
},
//...
```
The property name (`customer`) corresponds to the variable name of the `myCustomer` directive's
isolated scope. The value of the property (`=customer`) tells `$compile` to bind to the `customer`
attribute.
The **scope option** is an object that contains a property for each isolate scope binding. In this
case it has just one property:
- Its name (`customerInfo`) corresponds to the
directive's **isolate scope** property `customerInfo`.
- Its value (`=info`) tells `$compile` to bind to the `info` attribute.
<div class="alert alert-warning">
**Note:** These `=attr` attributes in the `scope` option of directives are normalized just like
@@ -449,12 +452,12 @@ For cases where the attribute name is the same as the value you want to bind to
directive's scope, you can use this shorthand syntax:
```javascript
//...
...
scope: {
// same as '=customer'
// same as '=customer'
customer: '='
},
//...
...
```
Besides making it possible to bind different data to the scope inside a directive, using an isolated
@@ -475,7 +478,7 @@ within our directive's template:
return {
restrict: 'E',
scope: {
customer: '=customer'
customerInfo: '=info'
},
templateUrl: 'my-customer-plus-vojta.html'
};
@@ -483,11 +486,11 @@ within our directive's template:
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<my-customer customer="naomi"></my-customer>
<my-customer info="naomi"></my-customer>
</div>
</file>
<file name="my-customer-plus-vojta.html">
Name: {{customer.name}} Address: {{customer.address}}
Name: {{customerInfo.name}} Address: {{customerInfo.address}}
<hr>
Name: {{vojta.name}} Address: {{vojta.address}}
</file>
@@ -717,7 +720,7 @@ own behavior to it.
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<my-dialog ng-hide="dialogIsHidden" on-close="dialogIsHidden = true">
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
Check out the contents, {{name}}!
</my-dialog>
</div>
+1 -1
View File
@@ -43,7 +43,7 @@ http://userguide.icu-project.org/locale ICU } website for more information about
**Supported locales in Angular**
Angular separates number and datetime format rule sets into different files, each file for a
particular locale. You can find a list of currently supported locales {@link
https://github.com/angular/angular.js/tree/master/i18n/locale here}
https://github.com/angular/angular.js/tree/master/src/ngLocale here}
# Providing locale rules to Angular
There are two approaches to providing locale rules to Angular:
+12 -1
View File
@@ -8,6 +8,17 @@ This document describes the Internet Explorer (IE) idiosyncrasies when dealing w
attributes and tags. Read this document if you are planning on deploying your Angular application
on IE v8.0 or earlier.
The project currently supports and will attempt to fix bugs for IE8 and above. The continuous
integration server runs all the tests against IE8. See http://ci.angularjs.org.
IE7 and below are not tested and the project makes no guarantee that Angular will work on it.
A subset of the AngularJS functionality may work. It is up to you to test and decide whether
it works for your particular app.
It is very unlikely that issues specific to IE7 or earlier will be given any time by the core team.
[GitHub](https://github.com/angular/angular.js/issues/4974)
# Short Version
To make your Angular application work on IE please make sure that:
@@ -80,7 +91,7 @@ The **important** parts are:
IE has issues with element tag names which are not standard HTML tag names. These fall into two
categories, and each category has its own fix.
* If the tag name starts with `my:` prefix than it is considered an XML namespace and must
* If the tag name starts with `my:` prefix then it is considered an XML namespace and must
have corresponding namespace declaration on `<html xmlns:my="ignored">`
* If the tag has no `:` but it is not a standard HTML tag, then it must be pre-created using
+7 -7
View File
@@ -3,7 +3,7 @@
@description
AngularJS version 1.2 introduces several breaking changes that will may require changes to your
AngularJS version 1.2 introduces several breaking changes that may require changes to your
application's source code.
Although we try to avoid breaking changes, there are some cases where it is unavoidable.
@@ -35,7 +35,7 @@ below should still apply, but you may want to consult the
<li>{@link guide/migration#resource-methods-return-the-promise Resource methods return the promise}</li>
<li>{@link guide/migration#resource-promises-are-resolved-with-the-resource-instance Resource promises are resolved with the resource instance}</li>
<li>{@link guide/migration#$locationsearch-supports-multiple-keys $location.search supports multiple keys}</li>
<li>{@link guide/migration#nghtmlbindunsafe-has-been-removed-and-replaced-by-nghtmlbind ngHtmlBindUnsafe has been removed and replaced by ngHtmlBind}</li>
<li>{@link guide/migration#ngbindhtmlunsafe-has-been-removed-and-replaced-by-ngbindhtml ngBindHtmlUnsafe has been removed and replaced by ngBindHtml}</li>
<li>{@link guide/migration#form-names-that-are-expressions-are-evaluated Form names that are expressions are evaluated}</li>
<li>{@link guide/migration#hasownproperty-disallowed-as-an-input-name hasOwnProperty disallowed as an input name}</li>
<li>{@link guide/migration#directives-order-of-postlink-functions-reversed Directives: Order of postLink functions reversed}</li>
@@ -45,7 +45,7 @@ below should still apply, but you may want to consult the
<li>{@link guide/migration#urls-are-now-sanitized-against-a-whitelist URLs are now sanitized against a whitelist}</li>
<li>{@link guide/migration#isolate-scope-only-exposed-to-directives-with-property Isolate scope only exposed to directives with <code>scope</code> property}</li>
<li>{@link guide/migration#change-to-interpolation-priority Change to interpolation priority}</li>
<li>{@link guide/migration#underscore-prefixed/suffixed-prorperties-are-non-bindable Underscore-prefixed/suffixed prorperties are non-bindable}</li>
<li>{@link guide/migration#underscore-prefixed/suffixed-properties-are-non-bindable Underscore-prefixed/suffixed properties are non-bindable}</li>
<li>{@link guide/migration#you-cannot-bind-to-select[multiple] You cannot bind to select[multiple]}</li>
<li>{@link guide/migration#uncommon-region-specific-local-files-were-removed-from-i18n Uncommon region-specific local files were removed from i18n}</li>
</ul>
@@ -376,11 +376,11 @@ passing it to `$location`.
See [80739409](https://github.com/angular/angular.js/commit/807394095b991357225a03d5fed81fea5c9a1abe).
## ngHtmlBindUnsafe has been removed and replaced by ngHtmlBind
## ngBindHtmlUnsafe has been removed and replaced by ngBindHtml
`ngHtmlBind` which has been moved from `ngSanitize` module to the core `ng` module.
`ngBindHtml` which has been moved from `ngSanitize` module to the core `ng` module.
`ngBindHtml` provides `ngHtmlBindUnsafe` like
`ngBindHtml` provides `ngBindHtmlUnsafe` like
behavior (evaluate an expression and innerHTML the result into the DOM) when bound to the result
of `$sce.trustAsHtml(string)`. When bound to a plain string, the string is sanitized via
`$sanitize` before being innerHTML'd. If the `$sanitize` service isn't available (`ngSanitize`
@@ -588,7 +588,7 @@ See [79223eae](https://github.com/angular/angular.js/commit/79223eae502283889334
[#4528](https://github.com/angular/angular.js/issues/4528), and
[#4649](https://github.com/angular/angular.js/issues/4649)
## Underscore-prefixed/suffixed prorperties are non-bindable
## Underscore-prefixed/suffixed properties are non-bindable
This change introduces the notion of "private" properties (properties
whose names begin and/or end with an underscore) on the scope chain.
+1 -1
View File
@@ -228,7 +228,7 @@ myApp.provider('unicornLauncher', function UnicornLauncherProvider() {
// let's assume that the UnicornLauncher constructor was also changed to
// accept and use the useTinfoilShielding argument
return new UnicornLauncher(apiToken, useTinfoilShielding);
}]);
}];
});
```
+1 -1
View File
@@ -67,7 +67,7 @@ illustration we typically build snappy apps with hundreds or thousands of active
### How big is the angular.js file that I need to include?
The size of the file is < 29KB compressed and minified.
The size of the file is < 36KB compressed and minified.
### Can I use the open-source Closure Library with Angular?
+1 -1
View File
@@ -85,7 +85,7 @@ to run <code>scripts/web-server.js</code>, a simple bundled http server.</p></l
<div class="tab-pane well" id="git-win" title="Git on Windows">
<ol>
<li><p>You will need Node.js and Karma to run unit tests, so please verify that you have
<a href="http://nodejs.org/">Node.js</a> v0.8 or better installed
<a href="http://nodejs.org/">Node.js</a> v0.10 or better installed
and that the <code>node</code> executable is on your <code>PATH</code> by running the following
command in a terminal window:</p>
<pre>node --version</pre>
+2 -2
View File
@@ -180,8 +180,8 @@ is available to be injected.
Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in
this tutorial in Jasmine. You can learn about Jasmine on the {@link
http://pivotal.github.com/jasmine/ Jasmine home page} and on the {@link
https://github.com/pivotal/jasmine/wiki Jasmine wiki}.
http://pivotal.github.com/jasmine/ Jasmine home page} and at the {@link
http://pivotal.github.io/jasmine/ Jasmine docs}.
The angular-seed project is pre-configured to run all unit tests using {@link
http://karma-runner.github.io/ Karma}. To run the test, do the following:
+6 -5
View File
@@ -112,11 +112,12 @@ describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
var scope, ctrl;
beforeEach(function() {
scope = {},
ctrl = new PhoneListCtrl(scope);
});
beforeEach(module('phonecatApp'));
beforeEach(inject(function($controller) {
scope = {};
ctrl = $controller('PhoneListCtrl', {$scope:scope});
}));
it('should create "phones" model with 3 phones', function() {
expect(scope.phones.length).toBe(3);
+2 -2
View File
@@ -34,8 +34,8 @@ describe('ngdoc', function() {
var d1 = new Doc('@name a.b.c').parse();
var d2 = new Doc('@name a.b.ng-c').parse();
var d3 = new Doc('@name some text: more text').parse();
expect(ngdoc.metadata([d1])[0].shortName).toEqual('c');
expect(ngdoc.metadata([d2])[0].shortName).toEqual('ng-c');
expect(ngdoc.metadata([d1])[0].shortName).toEqual('a.b.c');
expect(ngdoc.metadata([d2])[0].shortName).toEqual('a.b.ng-c');
expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text');
});
+1 -1
View File
@@ -20,7 +20,7 @@ describe('Docs Links', function() {
});
it('should have an "view source" button', function() {
spyOn(gruntUtil, 'getVersion').andReturn({cdn: '1.2.299'});
spyOn(gruntUtil, 'getVersion').andReturn({full: '1.2.299'});
expect(doc.html()).
toContain('<a href="http://github.com/angular/angular.js/tree/v1.2.299/test.js#L42" class="view-source btn btn-action"><i class="icon-zoom-in"> </i> View source</a>');
+1 -1
View File
@@ -5,7 +5,7 @@ module.exports = function(config, specificOptions) {
logLevel: config.LOG_INFO,
logColors: true,
browsers: ['Chrome'],
browserDisconnectTimeout: 5000,
browserDisconnectTimeout: 10000,
// config for Travis CI
+4 -4
View File
@@ -1,8 +1,8 @@
{
"name": "angularjs",
"version": "1.2.1",
"cdnVersion": "1.2.0",
"codename": "underscore-empathy",
"version": "1.2.2",
"cdnVersion": "1.2.1",
"codename": "consciousness-inertia",
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
@@ -30,7 +30,7 @@
"yaml-js": "~0.0.8",
"marked": "0.2.9",
"rewire": "1.1.3",
"grunt-jasmine-node": "~0.1.0",
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
"grunt-parallel": "~0.3.1",
"grunt-ddescribe-iit": "~0.0.1",
"grunt-merge-conflict": "~0.0.1",
+1 -1
View File
@@ -162,4 +162,4 @@
"nullFormCtrl": false
}
}
}
+29 -17
View File
@@ -80,7 +80,7 @@
-assertArgFn,
-assertNotHasOwnProperty,
-getter,
-getBlockElements
-getBlockElements,
*/
@@ -1102,26 +1102,38 @@ function encodeUriQuery(val, pctEncodeSpaces) {
*
* @description
*
* Use this directive to auto-bootstrap an application. Only
* one ngApp directive can be used per HTML document. The directive
* designates the root of the application and is typically placed
* at the root of the page.
* Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
* designates the **root element** of the application and is typically placed near the root element
* of the page - e.g. on the `<body>` or `<html>` tags.
*
* The first ngApp found in the document will be auto-bootstrapped. To use multiple applications in
* an HTML document you must manually bootstrap them using {@link angular.bootstrap}.
* Applications cannot be nested.
* Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
* found in the document will be used to define the root element to auto-bootstrap as an
* application. To run multiple applications in an HTML document you must manually bootstrap them using
* {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
*
* In the example below if the `ngApp` directive were not placed
* on the `html` element then the document would not be compiled
* and the `{{ 1+2 }}` would not be resolved to `3`.
* 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
* should contain the application code needed or have dependencies on other modules that will
* contain the code. See {@link angular.module} for more information.
*
* `ngApp` is the easiest way to bootstrap an application.
* In the example below if the `ngApp` directive were not placed on the `html` element then the
* document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
* would not be resolved to `3`.
*
<doc:example>
<doc:source>
I can add: 1 + 2 = {{ 1+2 }}
</doc:source>
</doc:example>
* `ngApp` is the easiest, and most common, way to bootstrap an application.
*
<example module="ngAppDemo">
<file name="index.html">
<div ng-controller="ngAppDemoController">
I can add: {{a}} + {{b}} = {{ a+b }}
</file>
<file name="script.js">
angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
$scope.a = 1;
$scope.b = 2;
});
</file>
</example>
*
*/
function angularInit(element, bootstrap) {
+3 -3
View File
@@ -501,11 +501,11 @@ function annotate(fn) {
* @example
* Here are some examples of creating value services.
* <pre>
* $provide.constant('ADMIN_USER', 'admin');
* $provide.value('ADMIN_USER', 'admin');
*
* $provide.constant('RoleLookup', { admin: 0, writer: 1, reader: 2 });
* $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
*
* $provide.constant('halfOf', function(value) {
* $provide.value('halfOf', function(value) {
* return value / 2;
* });
* </pre>
+6 -1
View File
@@ -17,7 +17,12 @@ function setupModuleLoader(window) {
return obj[name] || (obj[name] = factory());
}
return ensure(ensure(window, 'angular', Object), 'module', function() {
var angular = ensure(window, 'angular', Object);
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
angular.$$minErr = angular.$$minErr || minErr;
return ensure(angular, 'module', function() {
/** @type {Object.<string, angular.Module>} */
var modules = {};
+8 -7
View File
@@ -99,13 +99,14 @@ var $AnimateProvider = ['$provide', function($provide) {
* inserted into the DOM
*/
enter : function(element, parent, after, done) {
var afterNode = after && after[after.length - 1];
var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
// IE does not like undefined so we have to pass null.
var afterNextSibling = (afterNode && afterNode.nextSibling) || null;
forEach(element, function(node) {
parentNode.insertBefore(node, afterNextSibling);
});
if (after) {
after.after(element);
} else {
if (!parent || !parent[0]) {
parent = after.parent();
}
parent.append(element);
}
done && $timeout(done, 0, false);
},
+93 -66
View File
@@ -672,6 +672,24 @@ function $CompileProvider($provide) {
}
},
/**
* @ngdoc function
* @name ng.$compile.directive.Attributes#$updateClass
* @methodOf ng.$compile.directive.Attributes
* @function
*
* @description
* Adds and removes the appropriate CSS class values to the element based on the difference
* between the new and old CSS class values (specified as newClasses and oldClasses).
*
* @param {string} newClasses The current CSS className value
* @param {string} oldClasses The former CSS className value
*/
$updateClass : function(newClasses, oldClasses) {
this.$removeClass(tokenDifference(oldClasses, newClasses));
this.$addClass(tokenDifference(newClasses, oldClasses));
},
/**
* Set a normalized attribute on the element in a way such that all directives
* can share the attribute. This function properly handles boolean attributes.
@@ -682,59 +700,53 @@ function $CompileProvider($provide) {
* @param {string=} attrName Optional none normalized name. Defaults to key.
*/
$set: function(key, value, writeAttr, attrName) {
//special case for class attribute addition + removal
//so that class changes can tap into the animation
//hooks provided by the $animate service
if(key == 'class') {
value = value || '';
var current = this.$$element.attr('class') || '';
this.$removeClass(tokenDifference(current, value).join(' '));
this.$addClass(tokenDifference(value, current).join(' '));
// TODO: decide whether or not to throw an error if "class"
//is set through this function since it may cause $updateClass to
//become unstable.
var booleanKey = getBooleanAttrName(this.$$element[0], key),
normalizedVal,
nodeName;
if (booleanKey) {
this.$$element.prop(key, value);
attrName = booleanKey;
}
this[key] = value;
// translate normalized key to actual key
if (attrName) {
this.$attr[key] = attrName;
} else {
var booleanKey = getBooleanAttrName(this.$$element[0], key),
normalizedVal,
nodeName;
if (booleanKey) {
this.$$element.prop(key, value);
attrName = booleanKey;
attrName = this.$attr[key];
if (!attrName) {
this.$attr[key] = attrName = snake_case(key, '-');
}
}
this[key] = value;
nodeName = nodeName_(this.$$element);
// translate normalized key to actual key
if (attrName) {
this.$attr[key] = attrName;
} else {
attrName = this.$attr[key];
if (!attrName) {
this.$attr[key] = attrName = snake_case(key, '-');
}
}
nodeName = nodeName_(this.$$element);
// sanitize a[href] and img[src] values
if ((nodeName === 'A' && key === 'href') ||
(nodeName === 'IMG' && key === 'src')) {
// NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
if (!msie || msie >= 8 ) {
normalizedVal = urlResolve(value).href;
if (normalizedVal !== '') {
if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
(key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
this[key] = value = 'unsafe:' + normalizedVal;
}
// sanitize a[href] and img[src] values
if ((nodeName === 'A' && key === 'href') ||
(nodeName === 'IMG' && key === 'src')) {
// NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
if (!msie || msie >= 8 ) {
normalizedVal = urlResolve(value).href;
if (normalizedVal !== '') {
if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
(key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
this[key] = value = 'unsafe:' + normalizedVal;
}
}
}
}
if (writeAttr !== false) {
if (value === null || value === undefined) {
this.$$element.removeAttr(attrName);
} else {
this.$$element.attr(attrName, value);
}
if (writeAttr !== false) {
if (value === null || value === undefined) {
this.$$element.removeAttr(attrName);
} else {
this.$$element.attr(attrName, value);
}
}
@@ -747,22 +759,6 @@ function $CompileProvider($provide) {
$exceptionHandler(e);
}
});
function tokenDifference(str1, str2) {
var values = [],
tokens1 = str1.split(/\s+/),
tokens2 = str2.split(/\s+/);
outer:
for(var i=0;i<tokens1.length;i++) {
var token = tokens1[i];
for(var j=0;j<tokens2.length;j++) {
if(token == tokens2[j]) continue outer;
}
values.push(token);
}
return values;
}
},
@@ -1784,10 +1780,15 @@ function $CompileProvider($provide) {
function getTrustedContext(node, attrNormalizedName) {
if (attrNormalizedName == "srcdoc") {
return $sce.HTML;
}
var tag = nodeName_(node);
// maction[xlink:href] can source SVG. It's not limited to <maction>.
if (attrNormalizedName == "xlinkHref" ||
(nodeName_(node) != "IMG" && (attrNormalizedName == "src" ||
attrNormalizedName == "ngSrc"))) {
(tag == "FORM" && attrNormalizedName == "action") ||
(tag != "IMG" && (attrNormalizedName == "src" ||
attrNormalizedName == "ngSrc"))) {
return $sce.RESOURCE_URL;
}
}
@@ -1832,9 +1833,19 @@ function $CompileProvider($provide) {
attr[name] = interpolateFn(scope);
($$observers[name] || ($$observers[name] = [])).$$inter = true;
(attr.$$observers && attr.$$observers[name].$$scope || scope).
$watch(interpolateFn, function interpolateFnWatchAction(value) {
attr.$set(name, value);
});
$watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
//special case for class attribute addition + removal
//so that class changes can tap into the animation
//hooks provided by the $animate service. Be sure to
//skip animations when the first digest occurs (when
//both the new and the old values are the same) since
//the CSS classes are the non-interpolated values
if(name === 'class' && newValue != oldValue) {
attr.$updateClass(newValue, oldValue);
} else {
attr.$set(name, newValue);
}
});
}
};
}
@@ -1974,3 +1985,19 @@ function directiveLinkingFn(
/* Element */ rootElement,
/* function(Function) */ boundTranscludeFn
){}
function tokenDifference(str1, str2) {
var values = '',
tokens1 = str1.split(/\s+/),
tokens2 = str2.split(/\s+/);
outer:
for(var i = 0; i < tokens1.length; i++) {
var token = tokens1[i];
for(var j = 0; j < tokens2.length; j++) {
if(token == tokens2[j]) continue outer;
}
values += (values.length > 0 ? ' ' : '') + token;
}
return values;
}
+22 -5
View File
@@ -149,8 +149,11 @@
*
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as disabled. (Their presence means true and their absence means false.)
* This prevents the Angular compiler from retrieving the binding expression.
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngDisabled` directive solves this problem for the `disabled` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
*
* @example
<doc:example>
@@ -181,8 +184,11 @@
* @description
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as checked. (Their presence means true and their absence means false.)
* This prevents the Angular compiler from retrieving the binding expression.
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngChecked` directive solves this problem for the `checked` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
* @example
<doc:example>
<doc:source>
@@ -212,8 +218,12 @@
* @description
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as readonly. (Their presence means true and their absence means false.)
* This prevents the Angular compiler from retrieving the binding expression.
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngReadonly` directive solves this problem for the `readonly` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
* @example
<doc:example>
<doc:source>
@@ -243,8 +253,11 @@
* @description
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as selected. (Their presence means true and their absence means false.)
* This prevents the Angular compiler from retrieving the binding expression.
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngSelected` directive solves this problem for the `selected` atttribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
* @example
<doc:example>
<doc:source>
@@ -276,8 +289,12 @@
* @description
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as open. (Their presence means true and their absence means false.)
* This prevents the Angular compiler from retrieving the binding expression.
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngOpen` directive solves this problem for the `open` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
*
* @example
<doc:example>
+13
View File
@@ -392,8 +392,21 @@ var inputType = {
function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
// In composition mode, users are still inputing intermediate text buffer,
// hold the listener until composition is done.
// More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
var composing = false;
element.on('compositionstart', function() {
composing = true;
});
element.on('compositionend', function() {
composing = false;
});
var listener = function() {
if (composing) return;
var value = element.val();
// By default we will trim the value
+9 -17
View File
@@ -20,11 +20,10 @@ function classDirective(name, selector) {
// jshint bitwise: false
var mod = $index & 1;
if (mod !== old$index & 1) {
if (mod === selector) {
addClass(scope.$eval(attr[name]));
} else {
removeClass(scope.$eval(attr[name]));
}
var classes = flattenClasses(scope.$eval(attr[name]));
mod === selector ?
attr.$addClass(classes) :
attr.$removeClass(classes);
}
});
}
@@ -32,24 +31,17 @@ function classDirective(name, selector) {
function ngClassWatchAction(newVal) {
if (selector === true || scope.$index % 2 === selector) {
if (oldVal && !equals(newVal,oldVal)) {
removeClass(oldVal);
var newClasses = flattenClasses(newVal || '');
if(!oldVal) {
attr.$addClass(newClasses);
} else if(!equals(newVal,oldVal)) {
attr.$updateClass(newClasses, flattenClasses(oldVal));
}
addClass(newVal);
}
oldVal = copy(newVal);
}
function removeClass(classVal) {
attr.$removeClass(flattenClasses(classVal));
}
function addClass(classVal) {
attr.$addClass(flattenClasses(classVal));
}
function flattenClasses(classVal) {
if(isArray(classVal)) {
return classVal.join(' ');
+2 -1
View File
@@ -167,6 +167,7 @@
var ngControllerDirective = [function() {
return {
scope: true,
controller: '@'
controller: '@',
priority: 500
};
}];
+15 -10
View File
@@ -188,18 +188,23 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
if (thisChangeId !== changeCounter) return;
var newScope = scope.$new();
$transclude(newScope, function(clone) {
cleanupLastIncludeContent();
// Note: This will also link all children of ng-include that were contained in the original
// html. If that content contains controllers, ... they could pollute/change the scope.
// However, using ng-include on an element with additional content does not make sense...
// Note: We can't remove them in the cloneAttchFn of $transclude as that
// function is called before linking the content, which would apply child
// directives to non existing elements.
var clone = $transclude(newScope, noop);
cleanupLastIncludeContent();
currentScope = newScope;
currentElement = clone;
currentScope = newScope;
currentElement = clone;
currentElement.html(response);
$animate.enter(currentElement, null, $element, afterAnimation);
$compile(currentElement.contents())(currentScope);
currentScope.$emit('$includeContentLoaded');
scope.$eval(onloadExp);
});
currentElement.html(response);
$animate.enter(currentElement, null, $element, afterAnimation);
$compile(currentElement.contents())(currentScope);
currentScope.$emit('$includeContentLoaded');
scope.$eval(onloadExp);
}).error(function() {
if (thisChangeId === changeCounter) cleanupLastIncludeContent();
});
+1 -1
View File
@@ -50,7 +50,7 @@
* other numbers, for example 12, so that instead of showing "12 people are viewing", you can
* show "a dozen people are viewing".
*
* You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted
* You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
* into pluralized strings. In the previous example, Angular will replace `{}` with
* <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
* for <span ng-non-bindable>{{numberExpression}}</span>.
+1 -1
View File
@@ -99,7 +99,7 @@
* For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
* `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
* with the corresponding item in the array by identity. Moving the same object in array would move the DOM
* element in the same way ian the DOM.
* element in the same way in the DOM.
*
* For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
* case the object identity does not matter. Two objects are considered equivalent as long as their `id`
+19 -6
View File
@@ -34,6 +34,8 @@ function $HttpBackendProvider() {
}
function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) {
var ABORTED = -1;
// TODO(vojta): fix the signature
return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
var status;
@@ -69,13 +71,19 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
// always async
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
var responseHeaders = xhr.getAllResponseHeaders();
var responseHeaders = null,
response = null;
if(status !== ABORTED) {
responseHeaders = xhr.getAllResponseHeaders();
response = xhr.responseType ? xhr.response : xhr.responseText;
}
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
// response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
completeRequest(callback,
status || xhr.status,
(xhr.responseType ? xhr.response : xhr.responseText),
response,
responseHeaders);
}
};
@@ -99,7 +107,7 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
function timeoutRequest() {
status = -1;
status = ABORTED;
jsonpDone && jsonpDone();
xhr && xhr.abort();
}
@@ -128,6 +136,7 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
// - adds and immediately removes script elements from the document
var script = rawDocument.createElement('script'),
doneWrapper = function() {
script.onreadystatechange = script.onload = script.onerror = null;
rawDocument.body.removeChild(script);
if (done) done();
};
@@ -135,12 +144,16 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
script.type = 'text/javascript';
script.src = url;
if (msie) {
if (msie && msie <= 8) {
script.onreadystatechange = function() {
if (/loaded|complete/.test(script.readyState)) doneWrapper();
if (/loaded|complete/.test(script.readyState)) {
doneWrapper();
}
};
} else {
script.onload = script.onerror = doneWrapper;
script.onload = script.onerror = function() {
doneWrapper();
};
}
rawDocument.body.appendChild(script);
+1 -1
View File
@@ -11,7 +11,7 @@
*
* The main purpose of this service is to simplify debugging and troubleshooting.
*
* The default is not to log `debug` messages. You can use
* The default is to log `debug` messages. You can use
* {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
*
* @example
+1 -1
View File
@@ -1017,7 +1017,7 @@ function getterFn(path, options, fullExp) {
: '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
(options.unwrapPromises
? 'if (s && s.then) {\n' +
' pw("' + fullExp.replace(/\"/g, '\\"') + '");\n' +
' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' +
' if (!("$$v" in s)) {\n' +
' p=s;\n' +
' p.$$v = undefined;\n' +
+8 -12
View File
@@ -199,8 +199,7 @@ function $SceDelegateProvider() {
return resourceUrlBlacklist;
};
this.$get = ['$log', '$document', '$injector', function(
$log, $document, $injector) {
this.$get = ['$injector', function($injector) {
var htmlSanitizer = function htmlSanitizer(html) {
throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
@@ -731,18 +730,15 @@ function $SceProvider() {
* sce.js and sceSpecs.js would need to be aware of this detail.
*/
this.$get = ['$parse', '$document', '$sceDelegate', function(
$parse, $document, $sceDelegate) {
this.$get = ['$parse', '$sniffer', '$sceDelegate', function(
$parse, $sniffer, $sceDelegate) {
// Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows
// the "expression(javascript expression)" syntax which is insecure.
if (enabled && msie) {
var documentMode = $document[0].documentMode;
if (documentMode !== undefined && documentMode < 8) {
throw $sceMinErr('iequirks',
'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' +
'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
}
if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) {
throw $sceMinErr('iequirks',
'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' +
'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
}
var sce = copy(SCE_CONTEXTS);
+4 -2
View File
@@ -22,6 +22,7 @@ function $SnifferProvider() {
int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
document = $document[0] || {},
documentMode = document.documentMode,
vendorPrefix,
vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/,
bodyStyle = document.body && document.body.style,
@@ -66,7 +67,7 @@ function $SnifferProvider() {
// jshint +W018
hashchange: 'onhashchange' in $window &&
// IE8 compatible mode lies
(!document.documentMode || document.documentMode > 7),
(!documentMode || documentMode > 7),
hasEvent: function(event) {
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
// it. In particular the event is not fired when backspace or delete key are pressed or
@@ -84,7 +85,8 @@ function $SnifferProvider() {
vendorPrefix: vendorPrefix,
transitions : transitions,
animations : animations,
msie : msie
msie : msie,
msieDocumentMode: documentMode
};
}];
}
+90 -33
View File
@@ -159,7 +159,7 @@
* }
* </pre>
*
* Staggering animations work by default in ngRepeat (so long as the CSS class is defiend). Outside of ngRepeat, to use staggering animations
* Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
* on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
* are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
* will also be reset if more than 10ms has passed after the last animation has been fired.
@@ -222,7 +222,7 @@
* JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run
* a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits
* the element's CSS class attribute value and then run the matching animation event function (if found).
* In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function
* In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function will
* be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported).
*
* Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned.
@@ -510,6 +510,7 @@ angular.module('ngAnimate', ['ng'])
* @function
*
* @param {boolean=} value If provided then set the animation on or off.
* @param {jQuery/jqLite element=} element If provided then the element will be used to represent the enable/disable operation
* @return {boolean} Current animation state.
*
* @description
@@ -548,7 +549,8 @@ angular.module('ngAnimate', ['ng'])
and the onComplete callback will be fired once the animation is fully complete.
*/
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
var classes = (element.attr('class') || '') + ' ' + className;
var currentClassName = element.attr('class') || '';
var classes = currentClassName + ' ' + className;
var animationLookup = (' ' + classes).replace(/\s+/g,'.');
if (!parentElement) {
parentElement = afterElement ? afterElement.parent() : element.parent();
@@ -563,7 +565,7 @@ angular.module('ngAnimate', ['ng'])
//the animation if any matching animations are not found at all.
//NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found.
if (animationsDisabled(element, parentElement) || matches.length === 0) {
domOperation();
fireDOMOperation();
closeAnimation();
return;
}
@@ -596,27 +598,48 @@ angular.module('ngAnimate', ['ng'])
//this would mean that an animation was not allowed so let the existing
//animation do it's thing and close this one early
if(animations.length === 0) {
domOperation();
fireDOMOperation();
fireDoneCallbackAsync();
return;
}
//this value will be searched for class-based CSS className lookup. Therefore,
//we prefix and suffix the current className value with spaces to avoid substring
//lookups of className tokens
var futureClassName = ' ' + currentClassName + ' ';
if(ngAnimateState.running) {
//if an animation is currently running on the element then lets take the steps
//to cancel that animation and fire any required callbacks
$timeout.cancel(ngAnimateState.closeAnimationTimeout);
cleanup(element);
cancelAnimations(ngAnimateState.animations);
(ngAnimateState.done || noop)(true);
//if the class is removed during the reflow then it will revert the styles temporarily
//back to the base class CSS styling causing a jump-like effect to occur. This check
//here ensures that the domOperation is only performed after the reflow has commenced
if(ngAnimateState.beforeComplete) {
(ngAnimateState.done || noop)(true);
} else if(isClassBased && !ngAnimateState.structural) {
//class-based animations will compare element className values after cancelling the
//previous animation to see if the element properties already contain the final CSS
//class and if so then the animation will be skipped. Since the domOperation will
//be performed only after the reflow is complete then our element's className value
//will be invalid. Therefore the same string manipulation that would occur within the
//DOM operation will be performed below so that the class comparison is valid...
futureClassName = ngAnimateState.event == 'removeClass' ?
futureClassName.replace(ngAnimateState.className, '') :
futureClassName + ngAnimateState.className + ' ';
}
}
//There is no point in perform a class-based animation if the element already contains
//(on addClass) or doesn't contain (on removeClass) the className being animated.
//The reason why this is being called after the previous animations are cancelled
//is so that the CSS classes present on the element can be properly examined.
if((animationEvent == 'addClass' && element.hasClass(className)) ||
(animationEvent == 'removeClass' && !element.hasClass(className))) {
domOperation();
var classNameToken = ' ' + className + ' ';
if((animationEvent == 'addClass' && futureClassName.indexOf(classNameToken) >= 0) ||
(animationEvent == 'removeClass' && futureClassName.indexOf(classNameToken) == -1)) {
fireDOMOperation();
fireDoneCallbackAsync();
return;
}
@@ -627,6 +650,8 @@ angular.module('ngAnimate', ['ng'])
element.data(NG_ANIMATE_STATE, {
running:true,
event:animationEvent,
className:className,
structural:!isClassBased,
animations:animations,
done:onBeforeAnimationsComplete
@@ -637,7 +662,7 @@ angular.module('ngAnimate', ['ng'])
invokeRegisteredAnimationFns(animations, 'before', onBeforeAnimationsComplete);
function onBeforeAnimationsComplete(cancelled) {
domOperation();
fireDOMOperation();
if(cancelled === true) {
closeAnimation();
return;
@@ -695,6 +720,15 @@ angular.module('ngAnimate', ['ng'])
doneCallback && $timeout(doneCallback, 0, false);
}
//it is less complicated to use a flag than managing and cancelling
//timeouts containing multiple callbacks.
function fireDOMOperation() {
if(!fireDOMOperation.hasBeenRun) {
fireDOMOperation.hasBeenRun = true;
domOperation();
}
}
function closeAnimation() {
if(!closeAnimation.hasBeenRun) {
closeAnimation.hasBeenRun = true;
@@ -737,10 +771,10 @@ angular.module('ngAnimate', ['ng'])
function cancelAnimations(animations) {
var isCancelledFlag = true;
forEach(animations, function(animation) {
if(!animations['beforeComplete']) {
if(!animations.beforeComplete) {
(animation.beforeEnd || noop)(isCancelledFlag);
}
if(!animations['afterComplete']) {
if(!animations.afterComplete) {
(animation.afterEnd || noop)(isCancelledFlag);
}
});
@@ -842,13 +876,6 @@ angular.module('ngAnimate', ['ng'])
}, 10, false);
}
function applyStyle(node, style) {
var oldStyle = node.getAttribute('style') || '';
var newStyle = (oldStyle.length > 0 ? '; ' : '') + style;
node.setAttribute('style', newStyle);
return oldStyle;
}
function getElementAnimationDetails(element, cacheKey) {
var data = cacheKey ? lookupCache[cacheKey] : null;
if(!data) {
@@ -967,7 +994,9 @@ angular.module('ngAnimate', ['ng'])
if(timings.transitionDuration > 0) {
element.addClass(NG_ANIMATE_FALLBACK_CLASS_NAME);
activeClassName += NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME + ' ';
node.style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
blockTransitions(element);
} else {
blockKeyframeAnimations(element);
}
forEach(className.split(' '), function(klass, i) {
@@ -987,6 +1016,25 @@ angular.module('ngAnimate', ['ng'])
return true;
}
function blockTransitions(element) {
element[0].style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
}
function blockKeyframeAnimations(element) {
element[0].style[ANIMATION_PROP] = 'none 0s';
}
function unblockTransitions(element) {
var node = element[0], prop = TRANSITION_PROP + PROPERTY_KEY;
if(node.style[prop] && node.style[prop].length > 0) {
node.style[prop] = '';
}
}
function unblockKeyframeAnimations(element) {
element[0].style[ANIMATION_PROP] = '';
}
function animateRun(element, className, activeAnimationComplete) {
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
if(!element.hasClass(className) || !data) {
@@ -1002,20 +1050,21 @@ angular.module('ngAnimate', ['ng'])
var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * 1000;
var startTime = Date.now();
var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
var formerStyle;
var ii = data.ii;
var applyFallbackStyle, style = '';
var applyFallbackStyle, style = '', appliedStyles = [];
if(timings.transitionDuration > 0) {
node.style[TRANSITION_PROP + PROPERTY_KEY] = '';
var propertyStyle = timings.transitionPropertyStyle;
if(propertyStyle.indexOf('all') == -1) {
applyFallbackStyle = true;
var fallbackProperty = $sniffer.msie ? '-ms-zoom' : 'clip';
var fallbackProperty = $sniffer.msie ? '-ms-zoom' : 'border-spacing';
style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ', ' + fallbackProperty + '; ';
style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ', ' + timings.transitionDuration + 's; ';
appliedStyles.push(CSS_PREFIX + 'transition-property');
appliedStyles.push(CSS_PREFIX + 'transition-duration');
}
} else {
unblockKeyframeAnimations(element);
}
if(ii > 0) {
@@ -1027,16 +1076,19 @@ angular.module('ngAnimate', ['ng'])
style += CSS_PREFIX + 'transition-delay: ' +
prepareStaggerDelay(delayStyle, stagger.transitionDelay, ii) + '; ';
appliedStyles.push(CSS_PREFIX + 'transition-delay');
}
if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
style += CSS_PREFIX + 'animation-delay: ' +
prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, ii) + '; ';
appliedStyles.push(CSS_PREFIX + 'animation-delay');
}
}
if(style.length > 0) {
formerStyle = applyStyle(node, style);
if(appliedStyles.length > 0) {
var oldStyle = node.getAttribute('style') || '';
node.setAttribute('style', oldStyle + ' ' + style);
}
element.on(css3AnimationEvents, onAnimationProgress);
@@ -1049,10 +1101,8 @@ angular.module('ngAnimate', ['ng'])
element.off(css3AnimationEvents, onAnimationProgress);
element.removeClass(activeClassName);
animateClose(element, className);
if(formerStyle != null) {
formerStyle.length > 0 ?
node.setAttribute('style', formerStyle) :
node.removeAttribute('style');
for (var i in appliedStyles) {
node.style.removeProperty(appliedStyles[i]);
}
};
@@ -1116,6 +1166,7 @@ angular.module('ngAnimate', ['ng'])
//happen in the first place
var cancel = preReflowCancellation;
afterReflow(function() {
unblockTransitions(element);
//once the reflow is complete then we point cancel to
//the new cancellation function which will remove all of the
//animation properties from the active animation
@@ -1179,7 +1230,10 @@ angular.module('ngAnimate', ['ng'])
beforeAddClass : function(element, className, animationCompleted) {
var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'));
if(cancellationMethod) {
afterReflow(animationCompleted);
afterReflow(function() {
unblockTransitions(element);
animationCompleted();
});
return cancellationMethod;
}
animationCompleted();
@@ -1192,7 +1246,10 @@ angular.module('ngAnimate', ['ng'])
beforeRemoveClass : function(element, className, animationCompleted) {
var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'));
if(cancellationMethod) {
afterReflow(animationCompleted);
afterReflow(function() {
unblockTransitions(element);
animationCompleted();
});
return cancellationMethod;
}
animationCompleted();
+1 -1
View File
@@ -1572,7 +1572,7 @@ function MockHttpExpectation(method, url, data, headers) {
if (angular.isUndefined(data)) return true;
if (data && angular.isFunction(data.test)) return data.test(d);
if (data && angular.isFunction(data)) return data(d);
if (data && !angular.isString(data)) return angular.toJson(data) == d;
if (data && !angular.isString(data)) return angular.equals(data, angular.fromJson(d));
return data == d;
};
+2 -2
View File
@@ -439,7 +439,7 @@ angular.module('ngResource', ['ng']).
}
/* jshint +W086 */ /* (purposefully fall through case statements) */
var isInstanceCall = data instanceof Resource;
var isInstanceCall = this instanceof Resource;
var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
var httpConfig = {};
var responseInterceptor = action.interceptor && action.interceptor.response ||
@@ -522,7 +522,7 @@ angular.module('ngResource', ['ng']).
if (isFunction(params)) {
error = success; success = params; params = {};
}
var result = Resource[name](params, this, success, error);
var result = Resource[name].call(this, params, this, success, error);
return result.$promise || result;
};
});
+34 -28
View File
@@ -199,37 +199,43 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
if (template) {
var newScope = scope.$new();
$transclude(newScope, function(clone) {
clone.html(template);
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
if (angular.isDefined(autoScrollExp)
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}
});
cleanupLastView();
var link = $compile(clone.contents()),
current = $route.current;
currentScope = current.scope = newScope;
currentElement = clone;
if (current.controller) {
locals.$scope = currentScope;
var controller = $controller(current.controller, locals);
if (current.controllerAs) {
currentScope[current.controllerAs] = controller;
}
clone.data('$ngControllerController', controller);
clone.children().data('$ngControllerController', controller);
// Note: This will also link all children of ng-view that were contained in the original
// html. If that content contains controllers, ... they could pollute/change the scope.
// However, using ng-view on an element with additional content does not make sense...
// Note: We can't remove them in the cloneAttchFn of $transclude as that
// function is called before linking the content, which would apply child
// directives to non existing elements.
var clone = $transclude(newScope, angular.noop);
clone.html(template);
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
if (angular.isDefined(autoScrollExp)
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}
link(currentScope);
currentScope.$emit('$viewContentLoaded');
currentScope.$eval(onloadExp);
});
cleanupLastView();
var link = $compile(clone.contents()),
current = $route.current;
currentScope = current.scope = newScope;
currentElement = clone;
if (current.controller) {
locals.$scope = currentScope;
var controller = $controller(current.controller, locals);
if (current.controllerAs) {
currentScope[current.controllerAs] = controller;
}
clone.data('$ngControllerController', controller);
clone.children().data('$ngControllerController', controller);
}
link(currentScope);
currentScope.$emit('$viewContentLoaded');
currentScope.$eval(onloadExp);
} else {
cleanupLastView();
}
+8 -1
View File
@@ -9,6 +9,9 @@
*
* The `ngRoute` module provides routing and deeplinking services and directives for angular apps.
*
* ## Example
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
*
* {@installModule route}
*
* <div doc-module-components="ngRoute"></div>
@@ -24,8 +27,12 @@ var ngRouteModule = angular.module('ngRoute', ['ng']).
*
* @description
*
* Used for configuring routes. See {@link ngRoute.$route $route} for an example.
* Used for configuring routes.
*
* ## Example
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
*
* ## Dependencies
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*/
function $RouteProvider(){
+4
View File
@@ -78,4 +78,8 @@ describe('module loader', function() {
window.angular.module('hasOwnProperty', []);
}).toThrowMinErr('ng','badname', "hasOwnProperty is not a valid module name");
});
it('should expose `$$minErr` on the `angular` object', function() {
expect(window.angular.$$minErr).toEqual(jasmine.any(Function));
})
});
+72 -3
View File
@@ -596,8 +596,8 @@ describe('$compile', function() {
expect(element).toHaveClass('class_2');
}));
if (!msie || msie > 10) {
// style interpolation not working on IE<11.
if (!msie || msie > 11) {
// style interpolation not working on IE (including IE11).
it('should handle interpolated css style from replacing directive', inject(
function($compile, $rootScope) {
element = $compile('<div replace-with-interpolated-style></div>')($rootScope);
@@ -4232,6 +4232,76 @@ describe('$compile', function() {
}));
});
describe('form[action]', function() {
it('should pass through action attribute for the same domain', inject(function($compile, $rootScope, $sce) {
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
$rootScope.testUrl = "different_page";
$rootScope.$apply();
expect(element.attr('action')).toEqual('different_page');
}));
it('should clear out action attribute for a different domain', inject(function($compile, $rootScope, $sce) {
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
$rootScope.testUrl = "http://a.different.domain.example.com";
expect(function() { $rootScope.$apply() }).toThrowMinErr(
"$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " +
"loading resource from url not allowed by $sceDelegate policy. URL: " +
"http://a.different.domain.example.com");
}));
it('should clear out JS action attribute', inject(function($compile, $rootScope, $sce) {
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
$rootScope.testUrl = "javascript:alert(1);";
expect(function() { $rootScope.$apply() }).toThrowMinErr(
"$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " +
"loading resource from url not allowed by $sceDelegate policy. URL: " +
"javascript:alert(1);");
}));
it('should clear out non-resource_url action attribute', inject(function($compile, $rootScope, $sce) {
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
$rootScope.testUrl = $sce.trustAsUrl("javascript:doTrustedStuff()");
expect($rootScope.$apply).toThrowMinErr(
"$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " +
"loading resource from url not allowed by $sceDelegate policy. URL: javascript:doTrustedStuff()");
}));
it('should pass through $sce.trustAs() values in action attribute', inject(function($compile, $rootScope, $sce) {
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
$rootScope.testUrl = $sce.trustAsResourceUrl("javascript:doTrustedStuff()");
$rootScope.$apply();
expect(element.attr('action')).toEqual('javascript:doTrustedStuff()');
}));
});
if (!msie || msie >= 11) {
describe('iframe[srcdoc]', function() {
it('should NOT set iframe contents for untrusted values', inject(function($compile, $rootScope, $sce) {
element = $compile('<iframe srcdoc="{{html}}"></iframe>')($rootScope);
$rootScope.html = '<div onclick="">hello</div>';
expect(function() { $rootScope.$digest(); }).toThrowMinErr('$interpolate', 'interr', new RegExp(
/Can't interpolate: {{html}}\n/.source +
/[^[]*\[\$sce:unsafe\] Attempting to use an unsafe value in a safe context./.source));
}));
it('should NOT set html for wrongly typed values', inject(function($rootScope, $compile, $sce) {
element = $compile('<iframe srcdoc="{{html}}"></iframe>')($rootScope);
$rootScope.html = $sce.trustAsCss('<div onclick="">hello</div>');
expect(function() { $rootScope.$digest(); }).toThrowMinErr('$interpolate', 'interr', new RegExp(
/Can't interpolate: {{html}}\n/.source +
/[^[]*\[\$sce:unsafe\] Attempting to use an unsafe value in a safe context./.source));
}));
it('should set html for trusted values', inject(function($rootScope, $compile, $sce) {
element = $compile('<iframe srcdoc="{{html}}"></iframe>')($rootScope);
$rootScope.html = $sce.trustAsHtml('<div onclick="">hello</div>');
$rootScope.$digest();
expect(angular.lowercase(element.attr('srcdoc'))).toEqual('<div onclick="">hello</div>');
}));
});
}
describe('ngAttr* attribute binding', function() {
it('should bind after digest but not before', inject(function($compile, $rootScope) {
@@ -4479,7 +4549,6 @@ describe('$compile', function() {
$compile(element)($rootScope);
$rootScope.$digest();
data = $animate.flushNext('removeClass');
expect(element.hasClass('fire')).toBe(true);
+14
View File
@@ -454,6 +454,20 @@ describe('input', function() {
expect(scope.name).toEqual('adam');
});
it('should not update the model between "compositionstart" and "compositionend"', function() {
compileInput('<input type="text" ng-model="name" name="alias"" />');
changeInputValueTo('a');
expect(scope.name).toEqual('a');
if (!(msie < 9)) {
browserTrigger(inputElm, 'compositionstart');
changeInputValueTo('adam');
expect(scope.name).toEqual('a');
browserTrigger(inputElm, 'compositionend');
}
changeInputValueTo('adam');
expect(scope.name).toEqual('adam');
});
describe('"paste" and "cut" events', function() {
beforeEach(function() {
// Force browser to report a lack of an 'input' event
+42 -1
View File
@@ -321,7 +321,6 @@ describe('ngClass animations', function() {
$rootScope.val = 'one';
$rootScope.$digest();
$animate.flushNext('addClass');
$animate.flushNext('addClass');
expect($animate.queue.length).toBe(0);
$rootScope.val = '';
@@ -411,4 +410,46 @@ describe('ngClass animations', function() {
expect(enterComplete).toBe(true);
});
});
it("should not remove classes if they're going to be added back right after", function() {
module('mock.animate');
inject(function($rootScope, $compile, $animate) {
var className;
$rootScope.one = true;
$rootScope.two = true;
$rootScope.three = true;
var element = angular.element('<div ng-class="{one:one, two:two, three:three}"></div>');
$compile(element)($rootScope);
$rootScope.$digest();
//this fires twice due to the class observer firing
className = $animate.flushNext('addClass').params[1];
expect(className).toBe('one two three');
expect($animate.queue.length).toBe(0);
$rootScope.three = false;
$rootScope.$digest();
className = $animate.flushNext('removeClass').params[1];
expect(className).toBe('three');
expect($animate.queue.length).toBe(0);
$rootScope.two = false;
$rootScope.three = true;
$rootScope.$digest();
className = $animate.flushNext('removeClass').params[1];
expect(className).toBe('two');
className = $animate.flushNext('addClass').params[1];
expect(className).toBe('three');
expect($animate.queue.length).toBe(0);
});
});
});
+55
View File
@@ -85,4 +85,59 @@ describe('ngController', function() {
$rootScope.$digest();
expect(element.text()).toBe('Vojta');
}));
it('should work with ngInclude on the same element', inject(function($compile, $rootScope, $httpBackend) {
$rootScope.GreeterController = function($scope) {
$scope.name = 'Vojta';
};
element = $compile('<div><div ng-controller="GreeterController" ng-include="\'url\'"></div></div>')($rootScope);
$httpBackend.expect('GET', 'url').respond('{{name}}');
$rootScope.$digest();
$httpBackend.flush();
expect(element.text()).toEqual('Vojta');
}));
it('should only instantiate the controller once with ngInclude on the same element',
inject(function($compile, $rootScope, $httpBackend) {
var count = 0;
$rootScope.CountController = function($scope) {
count += 1;
};
element = $compile('<div><div ng-controller="CountController" ng-include="url"></div></div>')($rootScope);
$httpBackend.expect('GET', 'first').respond('first');
$rootScope.url = 'first';
$rootScope.$digest();
$httpBackend.flush();
$httpBackend.expect('GET', 'second').respond('second');
$rootScope.url = 'second';
$rootScope.$digest();
$httpBackend.flush();
expect(count).toBe(1);
}));
it('when ngInclude is on the same element, the content included content should get a child scope of the controller',
inject(function($compile, $rootScope, $httpBackend) {
var controllerScope;
$rootScope.ExposeScopeController = function($scope) {
controllerScope = $scope;
};
element = $compile('<div><div ng-controller="ExposeScopeController" ng-include="\'url\'"></div></div>')($rootScope);
$httpBackend.expect('GET', 'url').respond('<div ng-init="name=\'Vojta\'"></div>');
$rootScope.$digest();
$httpBackend.flush();
expect(controllerScope.name).toBeUndefined();
}));
});
+61 -4
View File
@@ -312,6 +312,31 @@ describe('ngInclude', function() {
}));
it('should exec scripts when jQuery is included', inject(function($compile, $rootScope, $httpBackend) {
if (!jQuery) {
return;
}
element = $compile('<div><span ng-include="includeUrl"></span></div>')($rootScope);
// the element needs to be appended for the script to run
element.appendTo(document.body);
window._ngIncludeCausesScriptToRun = false;
$httpBackend.expect('GET', 'url1').respond('<script>window._ngIncludeCausesScriptToRun = true;</script>');
$rootScope.includeUrl = 'url1';
$rootScope.$digest();
$httpBackend.flush();
expect(window._ngIncludeCausesScriptToRun).toBe(true);
// IE8 doesn't like deleting properties of window
window._ngIncludeCausesScriptToRun = undefined;
try {
delete window._ngIncludeCausesScriptToRun;
} catch (e) {}
}));
describe('autoscroll', function() {
var autoScrollSpy;
@@ -440,10 +465,22 @@ describe('ngInclude', function() {
});
describe('ngInclude and transcludes', function() {
var element, directive;
beforeEach(module(function($compileProvider) {
element = null;
directive = $compileProvider.directive;
}));
afterEach(function() {
if (element) {
dealoc(element);
}
});
it('should allow access to directive controller from children when used in a replace template', function() {
var controller;
module(function($compileProvider) {
var directive = $compileProvider.directive;
module(function() {
directive('template', valueFn({
template: '<div ng-include="\'include.html\'"></div>',
replace: true,
@@ -460,13 +497,33 @@ describe('ngInclude and transcludes', function() {
});
inject(function($compile, $rootScope, $httpBackend) {
$httpBackend.expectGET('include.html').respond('<div><div test></div></div>');
var element = $compile('<div><div template></div></div>')($rootScope);
element = $compile('<div><div template></div></div>')($rootScope);
$rootScope.$apply();
$httpBackend.flush();
expect(controller.flag).toBe(true);
dealoc(element);
});
});
it("should compile it's content correctly (although we remove it later)", function() {
var testElement;
module(function() {
directive('test', function() {
return {
link: function(scope, element) {
testElement = element;
}
};
});
});
inject(function($compile, $rootScope, $httpBackend) {
$httpBackend.expectGET('include.html').respond(' ');
element = $compile('<div><div ng-include="\'include.html\'"><div test></div></div></div>')($rootScope);
$rootScope.$apply();
$httpBackend.flush();
expect(testElement[0].nodeName).toBe('DIV');
});
});
});
describe('ngInclude animations', function() {
+54 -3
View File
@@ -118,6 +118,25 @@ describe('$httpBackend', function() {
});
});
it('should not try to read response data when request is aborted', function() {
callback.andCallFake(function(status, response, headers) {
expect(status).toBe(-1);
expect(response).toBe(null);
expect(headers).toBe(null);
});
$backend('GET', '/url', null, callback, {}, 2000);
xhr = MockXhr.$$lastInstance;
spyOn(xhr, 'abort');
fakeTimeout.flush();
expect(xhr.abort).toHaveBeenCalledOnce();
xhr.status = 0;
xhr.readyState = 4;
xhr.onreadystatechange();
expect(callback).toHaveBeenCalledOnce();
});
it('should abort request on timeout', function() {
callback.andCallFake(function(status, response) {
expect(status).toBe(-1);
@@ -273,7 +292,7 @@ describe('$httpBackend', function() {
script.readyState = 'complete';
script.onreadystatechange();
} else {
script.onload()
script.onload();
}
expect(callback).toHaveBeenCalledOnce();
@@ -294,7 +313,7 @@ describe('$httpBackend', function() {
script.readyState = 'complete';
script.onreadystatechange();
} else {
script.onload()
script.onload();
}
expect(callbacks[callbackId]).toBeUndefined();
@@ -302,6 +321,38 @@ describe('$httpBackend', function() {
});
if(msie<=8) {
it('should attach onreadystatechange handler to the script object', function() {
$backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, noop);
expect(fakeDocument.$$scripts[0].onreadystatechange).toEqual(jasmine.any(Function));
var script = fakeDocument.$$scripts[0];
script.readyState = 'complete';
script.onreadystatechange();
expect(script.onreadystatechange).toBe(null);
});
} else {
it('should attach onload and onerror handlers to the script object', function() {
$backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, noop);
expect(fakeDocument.$$scripts[0].onload).toEqual(jasmine.any(Function));
expect(fakeDocument.$$scripts[0].onerror).toEqual(jasmine.any(Function));
var script = fakeDocument.$$scripts[0];
script.onload();
expect(script.onload).toBe(null);
expect(script.onerror).toBe(null);
});
}
it('should call callback with status -2 when script fails to load', function() {
callback.andCallFake(function(status, response) {
expect(status).toBe(-2);
@@ -316,7 +367,7 @@ describe('$httpBackend', function() {
script.readyState = 'complete';
script.onreadystatechange();
} else {
script.onload()
script.onload();
}
expect(callback).toHaveBeenCalledOnce();
});
+736 -725
View File
File diff suppressed because it is too large Load Diff
+13 -20
View File
@@ -29,10 +29,10 @@ describe('SCE', function() {
describe('IE8 quirks mode', function() {
function runTest(enabled, documentMode, expectException) {
module(function($provide) {
$provide.value('$document', [{
documentMode: documentMode,
createElement: function() {}
}]);
$provide.value('$sniffer', {
msie: documentMode,
msieDocumentMode: documentMode
});
$provide.value('$sceDelegate', {trustAs: null, valueOf: null, getTrusted: null});
});
@@ -43,22 +43,15 @@ describe('SCE', function() {
return $injector.invoke(sceProvider.$get, sceProvider);
}
var origMsie = $window.msie;
try {
$window.msie = true;
if (expectException) {
expect(constructSce).toThrowMinErr(
'$sce', 'iequirks', 'Strict Contextual Escaping does not support Internet Explorer ' +
'version < 9 in quirks mode. You can fix this by adding the text <!doctype html> to ' +
'the top of your HTML document. See http://docs.angularjs.org/api/ng.$sce for more ' +
'information.');
} else {
// no exception.
constructSce();
}
}
finally {
$window.msie = origMsie;
if (expectException) {
expect(constructSce).toThrowMinErr(
'$sce', 'iequirks', 'Strict Contextual Escaping does not support Internet Explorer ' +
'version < 9 in quirks mode. You can fix this by adding the text <!doctype html> to ' +
'the top of your HTML document. See http://docs.angularjs.org/api/ng.$sce for more ' +
'information.');
} else {
// no exception.
constructSce();
}
});
}
+10 -2
View File
@@ -334,7 +334,15 @@ describe('$sniffer', function() {
});
});
it('should return true for msie when internet explorer is being used', inject(function($sniffer) {
expect($sniffer.msie > 0).toBe(window.navigator.appName == 'Microsoft Internet Explorer');
it('should return the internal msie flag', inject(function($sniffer) {
expect(isNaN($sniffer.msie)).toBe(isNaN(msie));
if (msie) {
expect($sniffer.msie).toBe(msie);
}
}));
it('should return document.documentMode as msieDocumentMode', function() {
var someDocumentMode = 123;
expect(sniffer({}, {documentMode: someDocumentMode}).msieDocumentMode).toBe(someDocumentMode);
});
});
+173 -1
View File
@@ -420,6 +420,27 @@ describe("ngAnimate", function() {
expect(element.children().length).toBe(0);
}));
it("should retain existing styles of the animated element",
inject(function($animate, $rootScope, $sniffer, $timeout) {
element.append(child);
child.attr('style', 'width: 20px');
$animate.addClass(child, 'ng-hide');
$animate.leave(child);
$rootScope.$digest();
if($sniffer.transitions) {
$timeout.flush();
//this is to verify that the existing style is appended with a semicolon automatically
expect(child.attr('style')).toMatch(/width: 20px;.+?/i);
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
}
expect(child.attr('style')).toMatch(/width: 20px/i);
}));
it("should call the cancel callback when another animation is called on the same element",
inject(function($animate, $rootScope, $sniffer, $timeout) {
@@ -823,7 +844,7 @@ describe("ngAnimate", function() {
$timeout.flush();
//IE removes the -ms- prefix when placed on the style
var fallbackProperty = $sniffer.msie ? 'zoom' : 'clip';
var fallbackProperty = $sniffer.msie ? 'zoom' : 'border-spacing';
var regExp = new RegExp("transition-property:\\s+color\\s*,\\s*" + fallbackProperty + "\\s*;");
expect(child2.attr('style') || '').toMatch(regExp);
expect(child2.hasClass('ng-animate')).toBe(true);
@@ -975,6 +996,35 @@ describe("ngAnimate", function() {
expect(element).toBeShown();
}));
it("should NOT overwrite styles with outdated values when animation completes",
inject(function($animate, $rootScope, $compile, $sniffer, $timeout) {
if(!$sniffer.transitions) return;
var style = '-webkit-transition-duration: 1s, 2000ms, 1s;' +
'-webkit-transition-property: height, left, opacity;' +
'transition-duration: 1s, 2000ms, 1s;' +
'transition-property: height, left, opacity;';
ss.addRule('.ng-hide-add', style);
ss.addRule('.ng-hide-remove', style);
element = $compile(html('<div style="width: 100px">foo</div>'))($rootScope);
element.addClass('ng-hide');
$animate.removeClass(element, 'ng-hide');
$timeout.flush();
var now = Date.now();
browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 });
browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 });
element.css('width', '200px');
browserTrigger(element,'transitionend', { timeStamp: now + 2000, elapsedTime: 2 });
expect(element.css('width')).toBe("200px");
}));
it("should animate for the highest duration",
inject(function($animate, $rootScope, $compile, $sniffer, $timeout) {
var style = '-webkit-transition:1s linear all 2s;' +
@@ -2515,6 +2565,36 @@ describe("ngAnimate", function() {
expect(element.hasClass('yellow-add')).toBe(true);
}));
it("should cancel and perform the dom operation only after the reflow has run",
inject(function($compile, $rootScope, $animate, $sniffer, $timeout) {
if (!$sniffer.transitions) return;
ss.addRule('.green-add', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');
ss.addRule('.red-add', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');
var element = $compile('<div></div>')($rootScope);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$animate.addClass(element, 'green');
expect(element.hasClass('green-add')).toBe(true);
$animate.addClass(element, 'red');
expect(element.hasClass('red-add')).toBe(true);
expect(element.hasClass('green')).toBe(false);
expect(element.hasClass('red')).toBe(false);
$timeout.flush();
expect(element.hasClass('green')).toBe(true);
expect(element.hasClass('red')).toBe(true);
}));
it('should enable and disable animations properly on the root element', function() {
var count = 0;
module(function($animateProvider) {
@@ -2599,4 +2679,96 @@ describe("ngAnimate", function() {
});
});
it('should only perform the DOM operation once',
inject(function($sniffer, $compile, $rootScope, $rootElement, $animate, $timeout) {
if (!$sniffer.transitions) return;
ss.addRule('.base-class', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');
$animate.enabled(true);
var element = $compile('<div class="base-class one two"></div>')($rootScope);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$animate.removeClass(element, 'base-class one two');
//still true since we're before the reflow
expect(element.hasClass('base-class')).toBe(true);
//this will cancel the remove animation
$animate.addClass(element, 'base-class one two');
//the cancellation was a success and the class was added right away
//since there was no successive animation for the after animation
expect(element.hasClass('base-class')).toBe(true);
//the reflow...
$timeout.flush();
//the reflow DOM operation was commenced but it ran before so it
//shouldn't run agaun
expect(element.hasClass('base-class')).toBe(true);
}));
it('should block and unblock transitions before the dom operation occurs',
inject(function($rootScope, $compile, $rootElement, $document, $animate, $sniffer, $timeout) {
if (!$sniffer.transitions) return;
$animate.enabled(true);
ss.addRule('.cross-animation', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');
var capturedProperty = 'none';
var element = $compile('<div class="cross-animation"></div>')($rootScope);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
var node = element[0];
node._setAttribute = node.setAttribute;
node.setAttribute = function(prop, val) {
if(prop == 'class' && val.indexOf('trigger-class') >= 0) {
var propertyKey = ($sniffer.vendorPrefix == 'Webkit' ? '-webkit-' : '') + 'transition-property';
capturedProperty = element.css(propertyKey);
}
node._setAttribute(prop, val);
};
$animate.addClass(element, 'trigger-class');
$timeout.flush();
expect(capturedProperty).not.toBe('none');
}));
it('should block and unblock keyframe animations around the reflow operation',
inject(function($rootScope, $compile, $rootElement, $document, $animate, $sniffer, $timeout) {
if (!$sniffer.animations) return;
$animate.enabled(true);
ss.addRule('.cross-animation', '-webkit-animation:1s my_animation;' +
'animation:1s my_animation;');
var element = $compile('<div class="cross-animation"></div>')($rootScope);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
var node = element[0];
var animationKey = $sniffer.vendorPrefix == 'Webkit' ? 'WebkitAnimation' : 'animation';
$animate.addClass(element, 'trigger-class');
expect(node.style[animationKey]).toContain('none');
$timeout.flush();
expect(node.style[animationKey]).not.toContain('none');
}));
});
+51 -5
View File
@@ -1,11 +1,8 @@
'use strict';
var msie = +((/msie (\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1]);
describe('ngMock', function() {
var noop = angular.noop;
describe('TzDate', function() {
function minutes(min) {
@@ -686,10 +683,10 @@ describe('ngMock', function() {
expect(d($rootScope)).toMatch(/{"abc":"123"}/);
}));
it('should serialize scope that has overridden "hasOwnProperty"', inject(function($rootScope){
it('should serialize scope that has overridden "hasOwnProperty"', inject(function($rootScope, $sniffer){
// MS IE8 just doesn't work for this kind of thing, since "for ... in" doesn't return
// things like hasOwnProperty even if it is explicitly defined on the actual object!
if (msie<=8) return;
if ($sniffer.msie<=8) return;
$rootScope.hasOwnProperty = 'X';
expect(d($rootScope)).toMatch(/Scope\(.*\): \{/);
expect(d($rootScope)).toMatch(/hasOwnProperty: "X"/);
@@ -944,6 +941,29 @@ describe('ngMock', function() {
});
it('should match data object if specified', function() {
hb.when('GET', '/a/b', {a: 1, b: 2}).respond(201, 'content1');
hb.when('GET', '/a/b').respond(202, 'content2');
hb('GET', '/a/b', '{"a":1,"b":2}', function(status, response) {
expect(status).toBe(201);
expect(response).toBe('content1');
});
hb('GET', '/a/b', '{"b":2,"a":1}', function(status, response) {
expect(status).toBe(201);
expect(response).toBe('content1');
});
hb('GET', '/a/b', null, function(status, response) {
expect(status).toBe(202);
expect(response).toBe('content2');
});
hb.flush();
});
it('should match only method', function() {
hb.when('GET').respond(202, 'c');
callback.andCallFake(function(status, response) {
@@ -1075,6 +1095,32 @@ describe('ngMock', function() {
});
it ('should not throw an exception when parsed body is equal to expected body object', function() {
hb.when('GET').respond(200, '', {});
hb.expect('GET', '/match', {a: 1, b: 2});
expect(function() {
hb('GET', '/match', '{"a":1,"b":2}', noop, {});
}).not.toThrow();
hb.expect('GET', '/match', {a: 1, b: 2});
expect(function() {
hb('GET', '/match', '{"b":2,"a":1}', noop, {});
}).not.toThrow();
});
it ('should throw exception when only parsed body differs from expected body object', function() {
hb.when('GET').respond(200, '', {});
hb.expect('GET', '/match', {a: 1, b: 2});
expect(function() {
hb('GET', '/match', '{"a":1,"b":3}', noop, {});
}).toThrow('Expected GET /match with different data\n' +
'EXPECTED: {"a":1,"b":2}\nGOT: {"a":1,"b":3}');
});
it("should use when's respond() when no expect() respond is defined", function() {
callback.andCallFake(function(status, response) {
expect(status).toBe(201);
+12
View File
@@ -533,6 +533,18 @@ describe("resource", function() {
expect(person.name).toEqual('misko');
});
it('should return a resource instance when calling a class method with a resource instance', function() {
$httpBackend.expect('GET', '/Person/123').respond('{"name":"misko"}');
var Person = $resource('/Person/:id');
var person = Person.get({id:123});
$httpBackend.flush();
$httpBackend.expect('POST', '/Person').respond('{"name":"misko2"}');
var person2 = Person.save(person);
$httpBackend.flush();
expect(person2).toEqual(jasmine.any(Person));
});
describe('promise api', function() {
+37 -7
View File
@@ -515,12 +515,23 @@ describe('ngView', function() {
});
describe('ngView and transcludes', function() {
var element, directive;
beforeEach(module('ngRoute', function($compileProvider) {
element = null;
directive = $compileProvider.directive;
}));
afterEach(function() {
if (element) {
dealoc(element);
}
});
it('should allow access to directive controller from children when used in a replace template', function() {
var controller;
module('ngRoute');
module(function($compileProvider, $routeProvider) {
module(function($routeProvider) {
$routeProvider.when('/view', {templateUrl: 'view.html'});
var directive = $compileProvider.directive;
directive('template', function() {
return {
template: '<div ng-view></div>',
@@ -542,14 +553,35 @@ describe('ngView and transcludes', function() {
});
inject(function($compile, $rootScope, $httpBackend, $location) {
$httpBackend.expectGET('view.html').respond('<div><div test></div></div>');
var element = $compile('<div><div template></div></div>')($rootScope);
element = $compile('<div><div template></div></div>')($rootScope);
$location.url('/view');
$rootScope.$apply();
$httpBackend.flush();
expect(controller.flag).toBe(true);
dealoc(element);
});
});
it("should compile it's content correctly (although we remove it later)", function() {
var testElement;
module(function($compileProvider, $routeProvider) {
$routeProvider.when('/view', {template: ' '});
var directive = $compileProvider.directive;
directive('test', function() {
return {
link: function(scope, element) {
testElement = element;
}
};
});
});
inject(function($compile, $rootScope, $location) {
element = $compile('<div><div ng-view><div test someAttr></div></div></div>')($rootScope);
$location.url('/view');
$rootScope.$apply();
expect(testElement[0].nodeName).toBe('DIV');
});
});
});
describe('ngView animations', function() {
@@ -656,7 +688,6 @@ describe('ngView animations', function() {
item = $animate.flushNext('enter').element;
$animate.flushNext('addClass').element;
$animate.flushNext('addClass').element;
expect(item.hasClass('classy')).toBe(true);
@@ -676,7 +707,6 @@ describe('ngView animations', function() {
$animate.flushNext('enter').element;
item = $animate.flushNext('leave').element;
$animate.flushNext('addClass').element;
$animate.flushNext('addClass').element;
expect(item.hasClass('boring')).toBe(true);
View File