Compare commits

...

72 Commits

Author SHA1 Message Date
Matias Niemelä 78ba429e6a chore(CHANGELOG): remove reverted commits from 1.2.4 2013-12-06 13:14:56 -05:00
Matias Niemelä dbf8c3c745 revert: chore(Angular.js): Use call and === instead of apply and == in type check functions 2013-12-06 13:14:08 -05:00
Matias Niemelä 3602c9785b revert: chore($resource): Use shallow copy instead of angular.copy 2013-12-06 13:13:48 -05:00
Matias Niemelä acaac21fd1 chore(release): cut 1.2.4 wormhole-blaster release 2013-12-06 12:46:25 -05:00
Matias Niemelä c98ef94706 docs(CHANGELOG): add v1.2.4 changes 2013-12-06 12:46:01 -05:00
Tobias Bosch b0972a2e75 fix($compile): update cloned elements if the template arrives after the cloning
If an element has a directive whose content is loaded using `templateUrl`,
and the element is cloned using a linking function before the template arrives,
the clone needs to be updated as well.

This also updates `ngIf` and `ngRepeat` to keep the connection to the clone
of a tranclude function, so that they know about the changes a directive with
`templateUrl` does to the element in the future.

Fixes to #4930.
2013-12-05 22:16:25 -08:00
Caitlin Potter 2dbb6f9a54 fix(isElement): return boolean value rather than truthy value.
angular.isElement currently returns a truthy object/function, or false. This
patch aims to correct this behaviour by casting the result of the isElement
expression to a boolean value via double-negation.

Closes #4519
Closes #4534
2013-12-05 17:14:05 -08:00
Karl Seamon 785a5fd7c1 chore(Angular.js): Use call and === instead of apply and == in type check functions
Updates isDate et al to use call instead of apply and === instead of ==.
The change to call brings minor performance improvement and === is just
better practice than ==.
http://jsperf.com/call-vs-apply-tostring

Closes #5295
2013-12-05 16:43:11 -08:00
Karl Seamon a55c1e79cf chore($resource): Use shallow copy instead of angular.copy
Replace calls to angular.copy with calls to a new function, shallowClearAndCopy.
Add calls to copy for cache access in $http in order to prevent modification of cached data.
Results in a measurable improvement to the startup time of complex apps within Google.

Closes #5300
2013-12-05 16:13:04 -08:00
Karl Seamon d070450cd2 chore(Scope): short-circuit after dirty-checking last dirty watcher
Stop dirty-checking during $digest after the last dirty watcher has been re-checked.

This prevents unneeded re-checking of the remaining watchers (They were already
checked in the previous iteration), bringing a substantial performance improvement
to the average case run time of $digest.

Closes #5272
Closes #5287
2013-12-05 15:37:37 -08:00
Andres Kalle 09648e4888 docs(tutorial/step-6): remove unused class="diagram"
Closes #5197
2013-12-05 22:17:07 +00:00
Pete Bacon Darwin 2adbcf189b docs(tutorial/step-3): remember to install karma plugins 2013-12-05 22:06:38 +00:00
Pete Bacon Darwin 39c5ffb2a6 docs(tutorial/step-2): remember to install karma plugins 2013-12-05 22:06:28 +00:00
Tobias Bosch 04a570d31c docs(TRIAGING): Initial doc about triaging issues in Angular 2013-12-05 09:36:14 -08:00
Matias Niemelä 958d3d56b1 fix($animate): ensure animations work with directives that share a transclusion
Closes #4716
Closes #4871
Closes #5021
Closes #5278
2013-12-05 10:54:19 -05:00
Caitlin Potter 0e50810c53 fix(ngInit): evaluate ngInit before ngInclude
The priority of ngInit is adjusted to occur before ngInclude, and after
ngController. This enables ngInit to initiallize values in a controller's
scope, and also to initiallize values before ngInclude executes.

Closes #5167
Closes #5208
2013-12-04 23:26:56 -08:00
Vojta Jina 21e48abbc1 chore(travis): move checks from before_scripts to scripts
If jshint (or any other ci-check) fails, Travis marks the build as "Errored" which I don't think is desider:
https://travis-ci.org/angular/angular.js/builds/14938896
2013-12-04 22:53:54 -08:00
Daniel Tabuenca b6d5439343 fix(input): ensure ngModelWatch() triggers second digest pass when appropriate
Due to an earlier change, ngModelWatch() no longer returns a value to the
caller. This means the digest loop has no way to tell if the watch actually
modified anything and so can not schedule another pass.

This means any watches that watch form or model controller changes
(e.g. watches on form.$valid) that are scheduled prior to an ngModelWatch()
will not be able to see any changes made therin.

This commit fixes this behavior by returning the latest evaluated ng-model
value.

Closes #5258
Closes #5282
2013-12-04 22:49:11 -08:00
Matias Niemelä 93901bdde4 fix($animate): ensure ms durations are properly rounded
Closes #5113
Closes #5162
2013-12-04 19:26:40 -05:00
Jeff Cross d802ed1b36 fix($rootScope): broadcast $destroy event on $rootScope
Fixes #5169
2013-12-04 15:29:19 -08:00
Sorin Gitlan e8f4305e9d docs($interpolate): demonstrate a filter in the interpolated expression
Closes #5186
2013-12-04 22:36:42 +00:00
Iwona Lalik b38a2287f2 docs(tutorial/step-3): add module to ng-app directive in code sample
Closes #5184
2013-12-04 22:36:42 +00:00
Julien Bouquillon 1e7675ad4c docs(input): remove deprecated isolated scope pitfall
The 1.2 release fixed the documented pitfall at 909cabd36d
by isolating only the isolated directive's scope.

Closes #5179
2013-12-04 22:36:41 +00:00
David Bennett 280b5ce3c0 chore(closure): add $routeProvider#redirectTo function parameters
Closes #5173
2013-12-04 22:36:41 +00:00
Elwin Arens fbc5cf514b docs(tutorial/step-12): fix refernce to incorrect jquery version
Closes #5156
2013-12-04 22:36:40 +00:00
Mateusz Jedruch f01087f802 fix(closure): closure compiler shouldn't rename .defaults.transformRequest 2013-12-04 14:01:34 -08:00
Yves Richard 4ac6424e87 docs(animate.js): fix copy/paste typo in leave docs
fix the copy/paste typo within $animate.leave method inside of the $animate docs.
2013-12-04 14:28:32 -05:00
Thomas Guillory d3c486dd6d fix($rootScope): clear phase if an exception is raised by a watcher
Add calls to clearPhase() when an exception is raised by a watcher
while a digest cycle, in order to not be stuck on `$digest` scope phase
2013-12-04 10:11:29 -08:00
Daniel Tabuenca 2d0f6ccba8 fix($compile): ensure isolated local watches' lastValue is always in sync
When using two-way binding with isolate scope, under some circumstances
the lastValue variable captured in the parentValueWatch function can get
out of sync.

Specifically, if both the value in the origin scope as well as the value
in the isolate scope get independently updated to the same value within
one digest cycle, the lastValue is never updated. This potentially causes
the watch to make the wrong decision as to which side to update on subsequent
passes.

This fixes things by ensuring lastValue is always set to the last seen
value even if the watch's logic was short circuited because there was no
difference between the values in the original and isolate scopes.

Closes #5182
2013-12-04 09:45:20 -08:00
Vojta Jina 9a81b8668a chore(travis): give browsers more time to respond 2013-12-03 19:32:32 -08:00
Vojta Jina 9481d69d1c chore(travis): add IE 11 (SL) to the build 2013-12-03 19:32:32 -08:00
Vojta Jina 7615723547 chore: make it simpler to run tests on SL/BS during local development 2013-12-03 16:07:13 -08:00
Vojta Jina 338f949259 chore(travis): set SauceLabs build id 2013-12-03 15:49:19 -08:00
Vojta Jina d0192b31a3 chore(travis): disable IE11
For some reason it's broken on SL.
2013-12-03 15:49:19 -08:00
Vojta Jina 6127528b50 chore(travis): switch back to SauceLabs
I think we are pretty close to be able to use both.

The xhr-polling seems to be pretty stable, but I'm having problems with multiple SSH tunnels (on BS), so let's try to switch back to SL.
2013-12-03 15:49:19 -08:00
Vojta Jina 0410572322 chore(travis): define a launcher for IE11 (SauceLabs) 2013-12-03 15:49:19 -08:00
Vojta Jina fd2371cfc2 chore(travis): report both build number and id to BS 2013-12-03 15:49:19 -08:00
Vojta Jina 267fcc999c chore(travis): log used ports 2013-12-03 15:49:18 -08:00
Vojta Jina 84187b6d94 chore(travis): use different port numbers per build
We can't establish multiple SSH tunnels for the same port (for BrowserStack).
This makes it possible to run multiple parallel builds using BrowserStack.
2013-12-03 15:49:18 -08:00
Vojta Jina 5d6482bb3b chore(karma): correct the 404 ignoring 2013-12-03 15:49:18 -08:00
Naomi Black 023765c593 docs: fixed a typo and made a minor edit to docs section of CONTRIBUTING.md 2013-12-03 15:44:56 -08:00
Igor Minar 4a401bbcf3 style(Scope): remove extra ws 2013-12-03 15:44:13 -08:00
Igor Minar 7401c70718 style(Scope): rename child scope type from Child to ChildScope
This change makes it easier to debug angular, especiall when dealing with heap snapshots
and hunting for memory leaks.
2013-12-03 15:44:13 -08:00
Igor Minar bb36bc7edf style(Angular.js): fix typo in comment 2013-12-03 15:44:13 -08:00
Michał Gołębiowski bf1972dc1e fix(ngSanitize): prefer textContent to innerText to avoid layout trashing
innerText depends on styling as it doesn't display hidden elements.
Therefore, it's better to use textContent not to cause unnecessary
reflows. However, IE<9 don't support textContent so the innerText
fallback is necessary.
2013-12-03 14:45:30 -08:00
Karl Seamon 689dfb1679 chore($parse): micro-optimization for ensureSafeObject function
This version matches the "alternate 2.2" version here: http://jsperf.com/ensuresafeobject/2

alternate 2.3 is a bit faster and simpler, but would break backwards compatibility.

Closes #5246
2013-12-03 11:10:48 -08:00
Hubert SABLONNIÈRE 1169b54456 fix(jqLite): ignore incompatible nodes on find()
When a jqLite collection contains text nodes, find() does not work :-(

This fix ignores all nodes than can't do getElementsByTagName()

It seems a little bit faster than testing nodeType : http://jsperf.com/nodetype-vs-duck-typing

Closes #4120
2013-12-03 09:45:06 -08:00
Pete Bacon Darwin 81b81856ee fix($sanitize): don't rely on YARR regex engine executing immediately
In Safari 7 (and other browsers potentially using the latest YARR JIT library)
regular expressions are not always executed immediately that they are called.
The regex is only evaluated (lazily) when you first access properties on the `matches`
result object returned from the regex call.

In the case of `decodeEntities()`, we were updating this returned object, `parts[0] = ''`,
before accessing it, `if (parts[2])', and so our change was overwritten by the result
of executing the regex.

The solution here is not to modify the match result object at all. We only need to make use
of the three match results directly in code.

Developers should be aware, in the future, when using regex, to read from the result object
before making modifications to it.

There is no additional test committed here, because when run against Safari 7, this
bug caused numerous specs to fail, which are all fixed by this commit.

Closes #5193
Closes #5192
2013-12-03 13:35:09 +00:00
Vojta Jina fd4b99936e chore(travis): increase BrowserStack timeout to 10min
Because IE is retarded.
2013-12-02 22:51:15 -08:00
Vojta Jina 09271a8ab9 chore(travis): ignore 404 warnings, debug log into file
This is a terrible hack/workaround, however I don't think there is any better way to achieve this
with log4js.
2013-12-02 22:51:15 -08:00
Naomi Black 5a8d9acacb docs: update CONTRIBUTING.MD with process for doc fixes 2013-12-02 16:23:59 -08:00
Vojta Jina 04d5a5072f chore(travis): fix the build id on browser stack
This just improves the way BrowserStack groups the sessions.
2013-12-02 14:43:44 -08:00
Vojta Jina 55c30e1be6 chore: use karma-browserstack-launcher from master 2013-12-02 14:20:14 -08:00
Vojta Jina 97fc84c151 chore(deps): use regular junit-reporter
We don't need the special branch anymore.
2013-12-02 14:20:06 -08:00
Vojta Jina 4ee0687f3f chore(travis): tolerate 2 disconnects to make the build more stable 2013-12-02 14:19:56 -08:00
Vojta Jina ddff347b91 chore(travis): use only websockets and xhr-polling
I wanna see more info about BrowserStack...
2013-12-02 14:19:46 -08:00
James Brewer 05ef1bd853 chore(grunt): update to latest jshint task
Upgrade JSHint task from ~0.6.4 to ~0.7.2. Two useful changes: ability
to set jshintrc option to use jshint's native ability for finding .jshintrc
files relative to the linted files and update jshint to 2.3.0.

Closes #5143
2013-12-02 21:20:07 +00:00
Blaise Kal d0f8bd30a6 docs($animate): require ngAnimate in example, syntax fixes
The example in the section "JavaScript-defined Animations" would not run without the ngAnimate dependency. Also added a missing comma and semicolons.
2013-12-02 14:55:34 -05:00
Peter Bacon Darwin 1a8d3c8b3a chore(docs): fix back-to-top anchor in angularjs.org doc pages
Closes https://github.com/angular/angularjs.org/issues/45
2013-11-28 12:16:07 +00:00
Levi Weiss 753687e5c2 docs(tutorial/step-10): fix typo
Closes #5171
2013-11-27 23:21:46 +00:00
Stéphane Reynaud 1a15c01b64 docs($compile): fix missing space 2013-11-27 23:20:09 +00:00
Ammar 7f33e1ca89 docs(tutorial/step-12): fix typo
Closes #5148
2013-11-27 23:19:02 +00:00
magoswiat 28d00945ba docs(tutorial/step-0): add target="_blank" to open app in new page
Closes #5145
2013-11-27 23:17:28 +00:00
Blaise Kal 6f40c88f47 docs(form): provide a list of Angular's built-in validation tokens
As requested by a top-rated Disqus comment: http://docs.angularjs.org/api/ng.directive:form.FormController#comment-655325797

Closes #5121
2013-11-27 23:06:19 +00:00
deepak-kapoor 68dd621082 docs(guide/concepts): fix incorrect module name in example
Closes #5116
2013-11-27 22:59:13 +00:00
wjtk 3abfb4ef51 docs($window): move use of $window to controller
Move use of `$window` from template to controller, because accessing `$window`
in expressions is now disallowed and doesn't work.

Closes #5110
2013-11-27 22:54:45 +00:00
Pete Bacon Darwin 1014e52349 docs($injector): use square bracket notation for $inject annotation
Closes #5104
2013-11-27 22:52:10 +00:00
Evan Winslow cda061f723 docs(guide/di): use square bracket notation for $inject annotation
Closes #5104
2013-11-27 22:48:02 +00:00
Pavel Pomerantsev 1497c6c1fb docs(guide/providers): fix typo
Closes #5102
2013-11-27 22:41:53 +00:00
Marc Lipovsky e41e445b51 docs(guide/compiler): add fourth step on appending the compiled template to the DOM
Closes #5087
2013-11-27 22:41:10 +00:00
rodyhaddad 7ab73190b7 docs(migration): add a note about "private" properties being reverted
Closes #5086
2013-11-27 22:35:09 +00:00
Peter Bacon Darwin 450b3a5460 chore(release): start 1.2.4 wormhole-baster iteration 2013-11-27 20:21:06 +00:00
50 changed files with 907 additions and 203 deletions
+2 -2
View File
@@ -13,15 +13,15 @@ env:
before_script:
- mkdir -p $LOGS_DIR
- ./lib/browser-stack/start-tunnel.sh
- ./lib/sauce/sauce_connect_setup.sh
- npm install -g grunt-cli
- grunt bower
- grunt bower
- grunt package-without-bower
- grunt ci-checks
- ./lib/sauce/sauce_connect_block.sh
script:
- grunt ci-checks
- ./travis_build.sh
after_script:
+57
View File
@@ -1,3 +1,60 @@
<a name="1.2.4"></a>
# 1.2.4 wormhole-blaster (2013-12-06)
## Bug Fixes
- **$animate:**
- ensure animations work with directives that share a transclusion
([958d3d56](https://github.com/angular/angular.js/commit/958d3d56b1899a2cfc7b18c0292e5a1d8c64d0a5),
[#4716](https://github.com/angular/angular.js/issues/4716), [#4871](https://github.com/angular/angular.js/issues/4871), [#5021](https://github.com/angular/angular.js/issues/5021), [#5278](https://github.com/angular/angular.js/issues/5278))
- ensure ms durations are properly rounded
([93901bdd](https://github.com/angular/angular.js/commit/93901bdde4bb9f0ba114ebb33b8885808e1823e1),
[#5113](https://github.com/angular/angular.js/issues/5113), [#5162](https://github.com/angular/angular.js/issues/5162))
- **$compile:**
- update cloned elements if the template arrives after the cloning
([b0972a2e](https://github.com/angular/angular.js/commit/b0972a2e75909e41dbac6e4413ada7df2d51df3a))
- ensure the isolated local watch `lastValue` is always in sync
([2d0f6ccb](https://github.com/angular/angular.js/commit/2d0f6ccba896fe34141d6d4f59eef6fba580c5c2),
[#5182](https://github.com/angular/angular.js/issues/5182))
- **$rootScope:**
- ensure that when the $destroy event is broadcast on $rootScope that it does something
([d802ed1b](https://github.com/angular/angular.js/commit/d802ed1b3680cfc1751777fac465b92ee29944dc),
[#5169](https://github.com/angular/angular.js/issues/5169))
- ensure the phase is cleared within a digest if an exception is raised by a watcher
([d3c486dd](https://github.com/angular/angular.js/commit/d3c486dd6dfa8d5dca32a3e28aa685fb7260c878))
- **$sanitize:** don't rely on YARR regex engine executing immediately in order to prevent object mutation
([81b81856](https://github.com/angular/angular.js/commit/81b81856ee43d2876927c4e1f774affa87e99707),
[#5193](https://github.com/angular/angular.js/issues/5193), [#5192](https://github.com/angular/angular.js/issues/5192))
- **closure:** closure compiler shouldn't rename .defaults.transformRequest
([f01087f8](https://github.com/angular/angular.js/commit/f01087f802839637843115cbcf99702e09d866f6))
- **input:** ensure ngModelWatch() triggers second digest pass when appropriate
([b6d54393](https://github.com/angular/angular.js/commit/b6d5439343b9801f7f2a009d0de09cba9aa21a1d),
[#5258](https://github.com/angular/angular.js/issues/5258), [#5282](https://github.com/angular/angular.js/issues/5282))
- **isElement:** return boolean value rather than `truthy` value.
([2dbb6f9a](https://github.com/angular/angular.js/commit/2dbb6f9a54eb5ff5847eed11c85ac4cf119eb41c),
[#4519](https://github.com/angular/angular.js/issues/4519), [#4534](https://github.com/angular/angular.js/issues/4534))
- **jqLite:** ignore incompatible nodes on find()
([1169b544](https://github.com/angular/angular.js/commit/1169b5445691e1495354d235a3badf05240e3904),
[#4120](https://github.com/angular/angular.js/issues/4120))
- **ngInit:** evaluate ngInit before ngInclude
([0e50810c](https://github.com/angular/angular.js/commit/0e50810c53428f4c1f5bfdba9599df54cb7a6c6e),
[#5167](https://github.com/angular/angular.js/issues/5167), [#5208](https://github.com/angular/angular.js/issues/5208))
- **ngSanitize:** prefer textContent to innerText to avoid layout trashing
([bf1972dc](https://github.com/angular/angular.js/commit/bf1972dc1e8ffbeaddfa53df1d49bc5a2177f09c))
## Performance Improvements
- **$parse:** micro-optimization for ensureSafeObject function
([689dfb16](https://github.com/angular/angular.js/commit/689dfb167924a61aef444ce7587fb987d8080990),
[#5246](https://github.com/angular/angular.js/issues/5246))
- **Scope:** short-circuit after dirty-checking last dirty watcher
([d070450c](https://github.com/angular/angular.js/commit/d070450cd2b3b3a3aa34b69d3fa1f4cc3be025dd),
[#5272](https://github.com/angular/angular.js/issues/5272), [#5287](https://github.com/angular/angular.js/issues/5287))
<a name="1.2.3"></a>
# 1.2.3 unicorn-zapper (2013-11-27)
+13
View File
@@ -29,6 +29,19 @@ duplication of work, and help you to craft the change so that it is successfully
project.
* **Small Changes** can be crafted and submitted to [GitHub Repository][github] as a Pull Request.
## Want a Doc Fix?
If you want to help improve the docs, it's a good idea to let others know what you're working on to
minimize duplication of effort. Before starting, check out the issue queue for [Milestone:Docs Only](https://github.com/angular/angular.js/issues?milestone=24&state=open).
Comment on an issue to let others know what you're working on, or create a new issue if your work
doesn't fit within the scope of any of the existing doc fix projects.
For large fixes, please build and test the documentation before submitting the PR to be sure you haven't
accidentally introduced any layout or formatting issues.You should also make sure that your commit message
is labeled "docs:" and follows the **Git Commit Guidelines** outlined below.
If you're just making a small change, don't worry about filing an issue first. Use the friendly blue "Improve this doc" button at the top right of the doc page to fork the repository in-place and make a quick change on the fly.
## Submission Guidelines
### Submitting an Issue
+3 -10
View File
@@ -106,45 +106,38 @@ module.exports = function(grunt) {
},
jshint: {
options: {
jshintrc: true,
},
ng: {
files: { src: files['angularSrc'] },
options: { jshintrc: 'src/.jshintrc' }
},
ngAnimate: {
files: { src: 'src/ngAnimate/**/*.js' },
options: { jshintrc: 'src/ngAnimate/.jshintrc' }
},
ngCookies: {
files: { src: 'src/ngCookies/**/*.js' },
options: { jshintrc: 'src/ngCookies/.jshintrc' }
},
ngLocale: {
files: { src: 'src/ngLocale/**/*.js' },
options: { jshintrc: 'src/ngLocale/.jshintrc' }
},
ngMock: {
files: { src: 'src/ngMock/**/*.js' },
options: { jshintrc: 'src/ngMock/.jshintrc' }
},
ngResource: {
files: { src: 'src/ngResource/**/*.js' },
options: { jshintrc: 'src/ngResource/.jshintrc' }
},
ngRoute: {
files: { src: 'src/ngRoute/**/*.js' },
options: { jshintrc: 'src/ngRoute/.jshintrc' }
},
ngSanitize: {
files: { src: 'src/ngSanitize/**/*.js' },
options: { jshintrc: 'src/ngSanitize/.jshintrc' }
},
ngScenario: {
files: { src: 'src/ngScenario/**/*.js' },
options: { jshintrc: 'src/ngScenario/.jshintrc' }
},
ngTouch: {
files: { src: 'src/ngTouch/**/*.js' },
options: { jshintrc: 'src/ngTouch/.jshintrc' }
}
},
+55
View File
@@ -0,0 +1,55 @@
# Triage new issues/PRs on github
This document shows the steps the Angular team is using to triage issues.
The labels are used later on for planning releases.
## Tips ##
* install [github pr helper extension](https://github.com/petebacondarwin/github-pr-helper) and become 356% more productive
* Label "resolution:*"
* these tags can be used for labeling a closed issue/PR with a reason why it was closed. (we can add reasons as we need them, right there are only a few rejection reasons. it doesn't make sense to label issues that were fixed or prs that were merged)
## Process ##
1. Open list of [non triaged issues](https://github.com/angular/angular.js/issues?direction=desc&milestone=none&page=1&sort=created&state=open)
1. Assign yourself: Pick an issue that is not assigned to anyone and assign it to you
1. Assign milestone:
* "Docs only" milestone - for documentation PR -> **Done**.
* Current/next milestone - regressions
* 1.2.x - everything else
1. Label "GH: *" (to be automated via Mary Poppins)
* PR - issue is a PR
* issue - otherwise
1. Bugs:
* Label "Type: Bug"
* Label "Type: Regression" - if the bug is a regression
* Duplicate? - Check if there are comments pointing out that this is a dupe, if they do exist verify that this is indeed a dupe and close it and go to the last step
* Reproducible? - Steps to reproduce the bug are clear, if not ask for clarification (ideally plunker or fiddle)
* Reproducible on master? - http://code.angularjs.org/snapshot/
1. Non bugs:
* Label "Type: Feature" or "Type: Chore"
* Label "needs: breaking change" - if needed
* Understandable? - verify if the description of the request is clear. if not ask for clarification
* Goals of angular core? - Often new features should be implemented as a third-party module rather than an addition to the core.
1. Label "component: *"
* In rare cases, it's ok to have multiple components.
1. Label "impact: *"
* small - obscure issue affecting one or handful of developers
* medium - impacts some usage patterns
* large - impacts most or all of angular apps
1. Label "complexity: *"
* small - trivial change
* medium - non-trivial but straightforward change
* large - changes to many components in angular or any changes to $compile, ngRepeat or other "fun" components
1. Label "PRs welcome" for "GH: issue"
* if complexity is small or medium and the problem as well as solution are well captured in the issue
1. Label "cla: yes" for "GH: PR":
* otherwise prompt the contributor to sign the CLA
1. Label "origin: google" for issues from Google
1. Label "high priority" for security issues, major performance regressions or memory leaks
1. Unassign yourself from the issue
+14 -2
View File
@@ -1053,6 +1053,10 @@ angular.$http;
*/
angular.$http.Config;
angular.$http.Config.transformRequest;
angular.$http.Config.transformResponse;
// /**
// * This extern is currently incomplete as delete is a reserved word.
// * To use delete, index $http.
@@ -1159,6 +1163,13 @@ angular.$http.HttpPromise.error = function(callback) {};
*/
angular.$http.Response;
angular.$HttpProvider;
/**
* @type {angular.$http.Config}
*/
angular.$HttpProvider.defaults;
/******************************************************************************
* $injector Service
*****************************************************************************/
@@ -1698,7 +1709,8 @@ angular.$routeProvider.when = function(path, route) {};
* resolve: (Object.<string, (
* string|Function|Array.<string|Function>|angular.$q.Promise
* )>|undefined),
* redirectTo: (string|function()|undefined),
* redirectTo: (
* string|function(Object.<string>, string, Object): string|undefined),
* reloadOnSearch: (boolean|undefined)
* }}
*/
@@ -1721,7 +1733,7 @@ angular.$routeProvider.Params.templateUrl;
*/
angular.$routeProvider.Params.resolve;
/** @type {string|function()} */
/** @type {string|function(Object.<string>, string, Object): string} */
angular.$routeProvider.Params.redirectTo;
/** @type {boolean} */
+5 -1
View File
@@ -190,6 +190,7 @@ This should help give you an idea of what Angular does internally.
<pre>
var $compile = ...; // injected into your code
var scope = ...;
var parent = ...; // DOM element where the compiled template can be appended
var html = '<div ng-bind="exp"></div>';
@@ -200,7 +201,10 @@ This should help give you an idea of what Angular does internally.
var linkFn = $compile(template);
// Step 3: link the compiled template with the scope.
linkFn(scope);
var element = linkFn(scope);
// Step 4: Append to DOM (optional)
parent.appendChild(element);
</pre>
### The difference between Compile and Link
+1 -1
View File
@@ -270,7 +270,7 @@ When Angular starts, it will use the configuration of the module with the name d
including the configuration of all modules that this module depends on.
In the example above:
The template contains the directive `ng-app="invoice"`. This tells Angular
The template contains the directive `ng-app="invoice2"`. This tells Angular
to use the `invoice` module as the main module for the application.
The code snippet `angular.module('invoice', ['finance'])` specifies that the `invoice` module depends on the
`finance` module. By this, Angular uses the `InvoiceController` as well as the `currencyConverter` service.
+1 -1
View File
@@ -146,7 +146,7 @@ of service names to inject.
var MyController = function(renamed$scope, renamedGreeter) {
...
}
MyController.$inject = ['$scope', 'greeter'];
MyController['$inject'] = ['$scope', 'greeter'];
</pre>
In this scenario the ordering of the values in the '$inject' array must match the ordering of the arguments to inject.
+4
View File
@@ -590,6 +590,10 @@ See [79223eae](https://github.com/angular/angular.js/commit/79223eae502283889334
## Underscore-prefixed/suffixed properties are non-bindable
<div class="alert alert-info">
<p>**Reverted**: This breaking change has been reverted in 1.2.1, and so can be ignored if you're using **version 1.2.1 or higher**</p>
</div>
This change introduces the notion of "private" properties (properties
whose names begin and/or end with an underscore) on the scope chain.
These properties will not be available to Angular expressions (i.e. {{
+1 -1
View File
@@ -110,7 +110,7 @@ myApp.factory('clientId', function clientIdFactory() {
But given that the token is just a string literal, sticking with the Value recipe is still more
appropriate as it makes the code easier to follow.
Let's say, however, that we would also like create a service that computes a token used for
Let's say, however, that we would also like to create a service that computes a token used for
authentication against a remote API. This token will be called 'apiToken' and will be computed
based on the `clientId` value and a secret stored in browser's local storage:
+2 -2
View File
@@ -26,7 +26,7 @@ angular-seed, and run the application in the browser.
<ol>
<li>In a <i>separate</i> terminal tab or window, run <code>node ./scripts/web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
@@ -54,7 +54,7 @@ angular-seed, and run the application in the browser.
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run <code>node scripts\web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
<li>Open a browser window for the app and navigate to <a href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
+5 -1
View File
@@ -184,7 +184,11 @@ 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:
http://karma-runner.github.io/ Karma}. Ensure that the necessary karma plugins are installed.
You can do this by issuing `npm install` into your terminal.
To run the test, do the following:
1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run
`./scripts/test.sh` to start the Karma server (the config file necessary to start the server
+3 -3
View File
@@ -127,8 +127,8 @@ end-to-end tests! Use `./scripts/e2e-test.sh` script for that. End-to-end tests
with unit tests, Karma will exit after the test run and will not automatically rerun the test
suite on every file change. To rerun the test suite, execute the `e2e-test.sh` script again.
Note: You must ensure you've installed karma-ng-scenario prior to running the `e2e-test.sh` script.
You can do this by issuing `npm install karma-ng-scenario` into your terminal.
Note: You must ensure you've installed the karma-ng-scenario framework plugin prior to running the
`e2e-test.sh` script. You can do this by issuing `npm install` into your terminal.
This test verifies that the search box and the repeater are correctly wired together. Notice how
easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it
@@ -154,7 +154,7 @@ really is that easy to set up any functional, readable, end-to-end test.
`ngController` declaration to the HTML element because it is the common parent of both the body
and title elements:
<html ng-app ng-controller="PhoneListCtrl">
<html ng-app="phonecatApp" ng-controller="PhoneListCtrl">
Be sure to __remove__ the `ng-controller` declaration from the body element.
+1 -1
View File
@@ -64,7 +64,7 @@ We also added phone images next to each record using an image tag with the {@lin
api/ng.directive:ngSrc ngSrc} directive. That directive prevents the
browser from treating the angular `{{ expression }}` markup literally, and initiating a request to
invalid url `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only
specified an attribute binding in a regular `src` attribute (`<img class="diagram" src="{{phone.imageUrl}}">`).
specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`).
Using the `ngSrc` directive prevents the browser from making an http request to an invalid location.
+1 -1
View File
@@ -120,7 +120,7 @@ Angular's server}.
<button ng-click="hello('Elmo')">Hello</button>
to the `phone-details.html` template.
to the `phone-detail.html` template.
<div style="display: none">
TODO!
+6 -6
View File
@@ -43,7 +43,7 @@ __`app/index.html`.__
<pre>
...
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<!-- required module to enable animation support in AngularJS -->
<script src="lib/angular/angular-animate.js"></script>
@@ -56,6 +56,10 @@ __`app/index.html`.__
...
</pre>
<div class="alert alert-error">
**Important:** Be sure to use jQuery version `1.10.x`. AngularJS does not yet support jQuery `2.x`.
</div>
Animations can now be created within the CSS code (`animations.css`) as well as the JavaScript code (`animations.js`).
But before we start, let's create a new module which uses the ngAnimate module as a dependency just like we did before
with `ngResource`.
@@ -153,7 +157,7 @@ __`app/css/animations.css`__
</pre>
As you can see our `phone-listing` CSS class is combined together with the animation hooks that occur when items are
inserted info and removed from the list:
inserted into and removed from the list:
* The `ng-enter` class is applied to the element when a new phone is added to the list and rendered on the page.
* The `ng-move` class is applied when items are moved around in the list.
@@ -383,10 +387,6 @@ isn't required to do JavaScript animations with AngularJS, but we're going to us
your own JavaScript animation library is beyond the scope of this tutorial. For more on
`jQuery.animate`, see the {@link http://api.jquery.com/animate/ jQuery documentation}.
<div class="alert alert-error">
**Important:** Be sure to use jQuery version `1.10.x`. AngularJS does not yet support jQuery `2.x`.
</div>
The `addClass` and `removeClass` callback functions are called whenever an a class is added or removed
on the element that contains the class we registered, which is in this case `.phone`. When the `.active`
class is added to the element (via the `ng-class` directive) the `addClass` JavaScript callback will
+1 -1
View File
@@ -364,7 +364,7 @@
<footer class="footer">
<div class="container">
<p class="pull-right"><a href="#">Back to top</a></p>
<p class="pull-right"><a back-to-top href="#">Back to top</a></p>
<p>
Super-powered by Google ©2010-2012
+15
View File
@@ -372,6 +372,21 @@ docsApp.directive.errorDisplay = ['$location', 'errorLinkFilter', function ($loc
}];
/**
* backToTop Directive
* @param {Function} $anchorScroll
*
* @description Ensure that the browser scrolls when the anchor is clicked
*/
docsApp.directive.backToTop = ['$anchorScroll', function($anchorScroll) {
return function link(scope, element) {
element.on('click', function(event) {
scope.$apply($anchorScroll);
});
};
}];
docsApp.serviceFactory.angularUrls = function($document) {
var urls = {};
+3 -1
View File
@@ -20,6 +20,8 @@ module.exports = function(config) {
junitReporter: {
outputFile: 'test_out/e2e.xml',
suite: 'E2E'
}
},
browserNoActivityTimeout: 90000
});
};
+70 -8
View File
@@ -6,21 +6,22 @@ module.exports = function(config, specificOptions) {
logColors: true,
browsers: ['Chrome'],
browserDisconnectTimeout: 10000,
browserDisconnectTolerance: 2,
browserNoActivityTimeout: 20000,
// config for Travis CI
// SauceLabs config for local development.
sauceLabs: {
testName: specificOptions.testName || 'AngularJS',
startConnect: false,
tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
startConnect: true
},
// BrowserStack config for Travis CI
// BrowserStack config for local development.
browserStack: {
startTunnel: false,
project: 'AngularJS',
name: specificOptions.testName,
build: process.env.TRAVIS_BUILD_NUMBER
startTunnel: true,
timeout: 600 // 10min
},
// For more browsers on Sauce Labs see:
@@ -58,6 +59,12 @@ module.exports = function(config, specificOptions) {
platform: 'Windows 2012',
version: '10'
},
'SL_IE_11': {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 8.1',
version: '11'
},
'BS_Chrome': {
base: 'BrowserStack',
@@ -110,11 +117,66 @@ module.exports = function(config, specificOptions) {
if (process.env.TRAVIS) {
var buildLabel = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
config.logLevel = config.LOG_DEBUG;
config.transports = ['websocket', 'xhr-polling'];
config.browserStack.build = buildLabel;
config.browserStack.startTunnel = false;
config.sauceLabs.build = buildLabel;
config.sauceLabs.startConnect = false;
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
// TODO(vojta): remove once SauceLabs supports websockets.
// This speeds up the capturing a bit, as browsers don't even try to use websocket.
config.transports = ['xhr-polling'];
// Debug logging into a file, that we print out at the end of the build.
config.loggers.push({
type: 'file',
filename: process.env.LOGS_DIR + '/' + (specificOptions.logFile || 'karma.log'),
level: config.LOG_DEBUG
filename: process.env.LOGS_DIR + '/' + (specificOptions.logFile || 'karma.log')
});
}
// Terrible hack to workaround inflexibility of log4js:
// - ignore web-server's 404 warnings,
// - ignore DEBUG logs (on Travis), we log them into a file instead.
var IGNORED_404 = [
'/favicon.ico',
'/%7B%7BtestUrl%7D%7D',
'/someSanitizedUrl',
'/{{testUrl}}'
];
var log4js = require('./node_modules/karma/node_modules/log4js');
var layouts = require('./node_modules/karma/node_modules/log4js/lib/layouts');
var originalConfigure = log4js.configure;
log4js.configure = function(log4jsConfig) {
var consoleAppender = log4jsConfig.appenders.shift();
var originalResult = originalConfigure.call(log4js, log4jsConfig);
var layout = layouts.layout(consoleAppender.layout.type, consoleAppender.layout);
log4js.addAppender(function(log) {
var msg = log.data[0];
// ignore web-server's 404s
if (log.categoryName === 'web-server' && log.level.levelStr === config.LOG_WARN &&
IGNORED_404.some(function(ignoredLog) {return msg.indexOf(ignoredLog) !== -1})) {
return;
}
// on Travis, ignore DEBUG statements
if (process.env.TRAVIS && log.level.levelStr === config.LOG_DEBUG) {
return;
}
console.log(layout(log));
});
return originalResult;
};
};
+2 -3
View File
@@ -3,7 +3,7 @@ var http = require('http');
var BrowserStackTunnel = require('browserstacktunnel-wrapper');
var HOSTNAME = 'localhost';
var PORTS = [9090, 9876];
var PORTS = require('../grunt/utils').availablePorts;
var ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY;
var READY_FILE = process.env.SAUCE_CONNECT_READY_FILE;
@@ -25,9 +25,8 @@ var tunnel = new BrowserStackTunnel({
hosts: hosts
});
console.log('Starting tunnel on ports', PORTS.join(', '));
tunnel.start(function(error) {
console.log('** callback **')
if (error) {
console.error('Can not establish the tunnel', error);
} else {
+19 -1
View File
@@ -5,6 +5,22 @@ var spawn = require('child_process').spawn;
var version;
var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP mode. */\n\n';
var PORT_MIN = 8000;
var PORT_MAX = 9999;
var TRAVIS_BUILD_NUMBER = parseInt(process.env.TRAVIS_BUILD_NUMBER, 10);
var getRandomPorts = function() {
if (!process.env.TRAVIS) {
return [9876, 9877];
}
// Generate two numbers between PORT_MIN and PORT_MAX, based on TRAVIS_BUILD_NUMBER.
return [
PORT_MIN + (TRAVIS_BUILD_NUMBER % (PORT_MAX - PORT_MIN)),
PORT_MIN + ((TRAVIS_BUILD_NUMBER + 100) % (PORT_MAX - PORT_MIN))
];
};
module.exports = {
init: function() {
@@ -295,5 +311,7 @@ module.exports = {
},
// see http://saucelabs.com/docs/connect#localhost
sauceLabsAvailablePorts: [9000, 9001, 9080, 9090, 9876]
sauceLabsAvailablePorts: [9000, 9001, 9080, 9090, 9876],
// pseudo-random port numbers for BrowserStack
availablePorts: getRandomPorts()
};
+6 -6
View File
@@ -1,8 +1,8 @@
{
"name": "angularjs",
"version": "1.2.3",
"cdnVersion": "1.2.2",
"codename": "unicorn-zapper",
"version": "1.2.4",
"cdnVersion": "1.2.3",
"codename": "wormhole-baster",
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
@@ -24,7 +24,7 @@
"karma-chrome-launcher": "~0.1.0",
"karma-firefox-launcher": "~0.1.0",
"karma-ng-scenario": "~0.1.0",
"karma-junit-reporter": "git://github.com/karma-runner/karma-junit-reporter#karma-0.11",
"karma-junit-reporter": "~0.2.1",
"karma-sauce-launcher": "~0.1.1",
"karma-script-launcher": "~0.1.0",
"yaml-js": "~0.0.8",
@@ -38,7 +38,7 @@
"grunt-shell": "~0.4.0",
"semver": "~2.1.0",
"lodash": "~2.1.0",
"karma-browserstack-launcher": "~0.0.4",
"karma-browserstack-launcher": "git://github.com/karma-runner/karma-browserstack-launcher.git#master",
"browserstacktunnel-wrapper": "~1.1.1"
},
"licenses": [
@@ -48,6 +48,6 @@
}
],
"dependencies": {
"grunt-contrib-jshint": "~0.6.4"
"grunt-contrib-jshint": "~0.7.2"
}
}
+12 -10
View File
@@ -565,9 +565,9 @@ var trim = (function() {
* @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
*/
function isElement(node) {
return node &&
return !!(node &&
(node.nodeName // we are a direct element
|| (node.on && node.find)); // we have an on and find method part of jQuery API
|| (node.on && node.find))); // we have an on and find method part of jQuery API
}
/**
@@ -768,7 +768,7 @@ function shallowCopy(src, dst) {
for(var key in src) {
// shallowCopy is only ever called by $compile nodeLinkFn, which has control over src
// so we don't need to worry hasOwnProperty here
// so we don't need to worry about using our custom hasOwnProperty here
if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
dst[key] = src[key];
}
@@ -1330,23 +1330,25 @@ function getter(obj, path, bindFnToScope) {
}
/**
* Return the siblings between `startNode` and `endNode`, inclusive
* @param {Object} object with `startNode` and `endNode` properties
* Return the DOM siblings between the first and last node in the given array.
* @param {Array} array like object
* @returns jQlite object containing the elements
*/
function getBlockElements(block) {
if (block.startNode === block.endNode) {
return jqLite(block.startNode);
function getBlockElements(nodes) {
var startNode = nodes[0],
endNode = nodes[nodes.length - 1];
if (startNode === endNode) {
return jqLite(startNode);
}
var element = block.startNode;
var element = startNode;
var elements = [element];
do {
element = element.nextSibling;
if (!element) break;
elements.push(element);
} while (element !== block.endNode);
} while (element !== endNode);
return jqLite(elements);
}
+1 -1
View File
@@ -221,7 +221,7 @@ function annotate(fn) {
* // ...
* }
* // Define function dependencies
* MyController.$inject = ['$scope', '$route'];
* MyController['$inject'] = ['$scope', '$route'];
*
* // Then
* expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
+5 -1
View File
@@ -822,7 +822,11 @@ forEach({
},
find: function(element, selector) {
return element.getElementsByTagName(selector);
if (element.getElementsByTagName) {
return element.getElementsByTagName(selector);
} else {
return [];
}
},
clone: jqLiteClone,
+5 -5
View File
@@ -192,7 +192,7 @@
* * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
* * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
* * `^` - Locate the required controller by searching the element's parents. Throw an error if not found.
* * `?^` - Attempt to locate the required controller by searching the element's parentsor pass `null` to the
* * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the
* `link` fn if not found.
*
*
@@ -931,7 +931,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn)
);
} else {
nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
nodeLinkFn(childLinkFn, childScope, node, $rootElement, boundTranscludeFn);
}
} else if (childLinkFn) {
childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
@@ -1438,13 +1438,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// we are out of sync and need to copy
if (parentValue !== lastValue) {
// parent changed and it has precedence
lastValue = isolateScope[scopeName] = parentValue;
isolateScope[scopeName] = parentValue;
} else {
// if the parent can be assigned then do so
parentSet(scope, parentValue = lastValue = isolateScope[scopeName]);
parentSet(scope, parentValue = isolateScope[scopeName]);
}
}
return parentValue;
return lastValue = parentValue;
});
break;
+15 -2
View File
@@ -21,9 +21,22 @@ var nullFormCtrl = {
* @property {Object} $error Is an object hash, containing references to all invalid controls or
* forms, where:
*
* - keys are validation tokens (error names) — such as `required`, `url` or `email`,
* - values are arrays of controls or forms that are invalid with given error.
* - keys are validation tokens (error names),
* - values are arrays of controls or forms that are invalid for given error name.
*
*
* Built-in validation tokens:
*
* - `email`
* - `max`
* - `maxlength`
* - `min`
* - `minlength`
* - `number`
* - `pattern`
* - `required`
* - `url`
*
* @description
* `FormController` keeps track of all its controls and nested forms as well as state of them,
* such as being valid/invalid or dirty/pristine.
+3 -34
View File
@@ -951,39 +951,6 @@ var VALID_CLASS = 'ng-valid',
</file>
* </example>
*
* ## Isolated Scope Pitfall
*
* Note that if you have a directive with an isolated scope, you cannot require `ngModel`
* since the model value will be looked up on the isolated scope rather than the outer scope.
* When the directive updates the model value, calling `ngModel.$setViewValue()` the property
* on the outer scope will not be updated. However you can get around this by using $parent.
*
* Here is an example of this situation. You'll notice that the first div is not updating the input.
* However the second div can update the input properly.
*
* <example module="badIsolatedDirective">
<file name="script.js">
angular.module('badIsolatedDirective', []).directive('isolate', function() {
return {
require: 'ngModel',
scope: { },
template: '<input ng-model="innerModel">',
link: function(scope, element, attrs, ngModel) {
scope.$watch('innerModel', function(value) {
console.log(value);
ngModel.$setViewValue(value);
});
}
};
});
</file>
<file name="index.html">
<input ng-model="someModel"/>
<div isolate ng-model="someModel"></div>
<div isolate ng-model="$parent.someModel"></div>
</file>
* </example>
*
*
*/
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
@@ -1130,7 +1097,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* It will update the $viewValue, then pass this value through each of the functions in `$parsers`,
* which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to
* `$modelValue` and the **expression** specified in the `ng-model` attribute.
*
*
* Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
*
* Note that calling this function does not trigger a `$digest`.
@@ -1187,6 +1154,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
ctrl.$render();
}
}
return value;
});
}];
+6 -3
View File
@@ -94,9 +94,12 @@ var ngIfDirective = ['$animate', function($animate) {
if (!childScope) {
childScope = $scope.$new();
$transclude(childScope, function (clone) {
clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
// Note: We only need the first/last node of the cloned nodes.
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
// by a directive with templateUrl when it's template arrives.
block = {
startNode: clone[0],
endNode: clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ')
clone: clone
};
$animate.enter(clone, $element.parent(), $element);
});
@@ -109,7 +112,7 @@ var ngIfDirective = ['$animate', function($animate) {
}
if (block) {
$animate.leave(getBlockElements(block));
$animate.leave(getBlockElements(block.clone));
block = null;
}
}
+3
View File
@@ -16,6 +16,8 @@
* to initialize values on a scope.
* </div>
*
* @priority 450
*
* @element ANY
* @param {expression} ngInit {@link guide/expression Expression} to eval.
*
@@ -47,6 +49,7 @@
</doc:example>
*/
var ngInitDirective = ngDirective({
priority: 450,
compile: function() {
return {
pre: function(scope, element, attrs) {
+20 -10
View File
@@ -301,7 +301,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
} else if (nextBlockMap.hasOwnProperty(trackById)) {
// restore lastBlockMap
forEach(nextBlockOrder, function(block) {
if (block && block.startNode) lastBlockMap[block.id] = block;
if (block && block.scope) lastBlockMap[block.id] = block;
});
// This is a duplicate and we need to throw an error
throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}",
@@ -318,7 +318,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
// lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn
if (lastBlockMap.hasOwnProperty(key)) {
block = lastBlockMap[key];
elementsToRemove = getBlockElements(block);
elementsToRemove = getBlockElements(block.clone);
$animate.leave(elementsToRemove);
forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; });
block.scope.$destroy();
@@ -330,9 +330,9 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
block = nextBlockOrder[index];
if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode;
if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]);
if (block.startNode) {
if (block.scope) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
childScope = block.scope;
@@ -342,11 +342,11 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
nextNode = nextNode.nextSibling;
} while(nextNode && nextNode[NG_REMOVED]);
if (block.startNode != nextNode) {
if (getBlockStart(block) != nextNode) {
// existing item which got moved
$animate.move(getBlockElements(block), null, jqLite(previousNode));
$animate.move(getBlockElements(block.clone), null, jqLite(previousNode));
}
previousNode = block.endNode;
previousNode = getBlockEnd(block);
} else {
// new item which we don't know about
childScope = $scope.$new();
@@ -362,14 +362,16 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
childScope.$odd = !(childScope.$even = (index&1) === 0);
// jshint bitwise: true
if (!block.startNode) {
if (!block.scope) {
$transclude(childScope, function(clone) {
clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' ');
$animate.enter(clone, null, jqLite(previousNode));
previousNode = clone;
block.scope = childScope;
block.startNode = previousNode && previousNode.endNode ? previousNode.endNode : clone[0];
block.endNode = clone[clone.length - 1];
// Note: We only need the first/last node of the cloned nodes.
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
// by a directive with templateUrl when it's template arrives.
block.clone = clone;
nextBlockMap[block.id] = block;
});
}
@@ -378,5 +380,13 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
});
}
};
function getBlockStart(block) {
return block.clone[0];
}
function getBlockEnd(block) {
return block.clone[block.clone.length - 1];
}
}];
+2 -2
View File
@@ -103,8 +103,8 @@ function $InterpolateProvider() {
*
<pre>
var $interpolate = ...; // injected
var exp = $interpolate('Hello {{name}}!');
expect(exp({name:'Angular'}).toEqual('Hello Angular!');
var exp = $interpolate('Hello {{name | uppercase}}!');
expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!');
</pre>
*
*
+17 -16
View File
@@ -44,23 +44,24 @@ function ensureSafeMemberName(name, fullExpression) {
function ensureSafeObject(obj, fullExpression) {
// nifty check if obj is Function that is fast and works across iframes and other contexts
if (obj && obj.constructor === obj) {
throw $parseMinErr('isecfn',
'Referencing Function in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else if (// isWindow(obj)
obj && obj.document && obj.location && obj.alert && obj.setInterval) {
throw $parseMinErr('isecwindow',
'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else if (// isElement(obj)
obj && (obj.nodeName || (obj.on && obj.find))) {
throw $parseMinErr('isecdom',
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else {
return obj;
if (obj) {
if (obj.constructor === obj) {
throw $parseMinErr('isecfn',
'Referencing Function in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else if (// isWindow(obj)
obj.document && obj.location && obj.alert && obj.setInterval) {
throw $parseMinErr('isecwindow',
'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else if (// isElement(obj)
obj.children && (obj.nodeName || (obj.on && obj.find))) {
throw $parseMinErr('isecdom',
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
fullExpression);
}
}
return obj;
}
var OPERATORS = {
+47 -25
View File
@@ -71,6 +71,7 @@
function $RootScopeProvider(){
var TTL = 10;
var $rootScopeMinErr = minErr('$rootScope');
var lastDirtyWatch = null;
this.digestTtl = function(value) {
if (arguments.length) {
@@ -172,7 +173,7 @@ function $RootScopeProvider(){
*
*/
$new: function(isolate) {
var Child,
var ChildScope,
child;
if (isolate) {
@@ -182,11 +183,11 @@ function $RootScopeProvider(){
child.$$asyncQueue = this.$$asyncQueue;
child.$$postDigestQueue = this.$$postDigestQueue;
} else {
Child = function() {}; // should be anonymous; This is so that when the minifier munges
ChildScope = function() {}; // should be anonymous; This is so that when the minifier munges
// the name it does not become random set of chars. This will then show up as class
// name in the debugger.
Child.prototype = this;
child = new Child();
ChildScope.prototype = this;
child = new ChildScope();
child.$id = nextUid();
}
child['this'] = child;
@@ -266,7 +267,7 @@ function $RootScopeProvider(){
// Using a listener function
// Using a listener function
var food;
scope.foodCounter = 0;
expect(scope.foodCounter).toEqual(0);
@@ -291,7 +292,7 @@ function $RootScopeProvider(){
// Update food and run digest. Now the counter will increment
food = 'cheeseburger';
scope.$digest();
expect(scope.foodCounter).toEqual(1);
expect(scope.foodCounter).toEqual(1);
* </pre>
*
@@ -325,6 +326,8 @@ function $RootScopeProvider(){
eq: !!objectEquality
};
lastDirtyWatch = null;
// in the case user pass string, we need to compile it, do we really need this ?
if (!isFunction(listener)) {
var listenFn = compileToFn(listener || noop, 'listener');
@@ -553,6 +556,8 @@ function $RootScopeProvider(){
beginPhase('$digest');
lastDirtyWatch = null;
do { // "while dirty" loop
dirty = false;
current = target;
@@ -562,10 +567,13 @@ function $RootScopeProvider(){
asyncTask = asyncQueue.shift();
asyncTask.scope.$eval(asyncTask.expression);
} catch (e) {
clearPhase();
$exceptionHandler(e);
}
lastDirtyWatch = null;
}
traverseScopesLoop:
do { // "traverse the scopes" loop
if ((watchers = current.$$watchers)) {
// process our watches
@@ -575,25 +583,34 @@ function $RootScopeProvider(){
watch = watchers[length];
// Most common watches are on primitives, in which case we can short
// circuit it with === operator, only when === fails do we use .equals
if (watch && (value = watch.get(current)) !== (last = watch.last) &&
!(watch.eq
? equals(value, last)
: (typeof value == 'number' && typeof last == 'number'
&& isNaN(value) && isNaN(last)))) {
dirty = true;
watch.last = watch.eq ? copy(value) : value;
watch.fn(value, ((last === initWatchVal) ? value : last), current);
if (ttl < 5) {
logIdx = 4 - ttl;
if (!watchLog[logIdx]) watchLog[logIdx] = [];
logMsg = (isFunction(watch.exp))
? 'fn: ' + (watch.exp.name || watch.exp.toString())
: watch.exp;
logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
watchLog[logIdx].push(logMsg);
if (watch) {
if ((value = watch.get(current)) !== (last = watch.last) &&
!(watch.eq
? equals(value, last)
: (typeof value == 'number' && typeof last == 'number'
&& isNaN(value) && isNaN(last)))) {
dirty = true;
lastDirtyWatch = watch;
watch.last = watch.eq ? copy(value) : value;
watch.fn(value, ((last === initWatchVal) ? value : last), current);
if (ttl < 5) {
logIdx = 4 - ttl;
if (!watchLog[logIdx]) watchLog[logIdx] = [];
logMsg = (isFunction(watch.exp))
? 'fn: ' + (watch.exp.name || watch.exp.toString())
: watch.exp;
logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
watchLog[logIdx].push(logMsg);
}
} else if (watch === lastDirtyWatch) {
// If the most recently dirty watcher is now clean, short circuit since the remaining watchers
// have already been tested.
dirty = false;
break traverseScopesLoop;
}
}
} catch (e) {
clearPhase();
$exceptionHandler(e);
}
}
@@ -602,13 +619,16 @@ function $RootScopeProvider(){
// Insanity Warning: scope depth-first traversal
// yes, this code is a bit crazy, but it works and we have tests to prove it!
// this piece should be kept in sync with the traversal in $broadcast
if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
if (!(next = (current.$$childHead ||
(current !== target && current.$$nextSibling)))) {
while(current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
}
}
} while ((current = next));
// `break traverseScopesLoop;` takes us to here
if(dirty && !(ttl--)) {
clearPhase();
throw $rootScopeMinErr('infdig',
@@ -616,6 +636,7 @@ function $RootScopeProvider(){
'Watchers fired in the last 5 iterations: {1}',
TTL, toJson(watchLog));
}
} while (dirty || asyncQueue.length);
clearPhase();
@@ -668,11 +689,12 @@ function $RootScopeProvider(){
*/
$destroy: function() {
// we can't destroy the root scope or a scope that has been already destroyed
if ($rootScope == this || this.$$destroyed) return;
if (this.$$destroyed) return;
var parent = this.$parent;
this.$broadcast('$destroy');
this.$$destroyed = true;
if (this === $rootScope) return;
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
@@ -710,7 +732,7 @@ function $RootScopeProvider(){
*
* - `string`: execute using the rules as defined in {@link guide/expression expression}.
* - `function(scope)`: execute the function with the current `scope` parameter.
*
*
* @param {(object)=} locals Local variables object, useful for overriding values in scope.
* @returns {*} The result of evaluating the expression.
*/
+4 -2
View File
@@ -20,13 +20,15 @@
<doc:source>
<script>
function Ctrl($scope, $window) {
$scope.$window = $window;
$scope.greeting = 'Hello, World!';
$scope.doGreeting = function(greeting) {
$window.alert(greeting);
};
}
</script>
<div ng-controller="Ctrl">
<input type="text" ng-model="greeting" />
<button ng-click="$window.alert(greeting)">ALERT</button>
<button ng-click="doGreeting(greeting)">ALERT</button>
</div>
</doc:source>
<doc:scenario>
+54 -25
View File
@@ -190,7 +190,7 @@
*
* <pre>
* //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application.
* var ngModule = angular.module('YourApp', []);
* var ngModule = angular.module('YourApp', ['ngAnimate']);
* ngModule.animation('.my-crazy-animation', function() {
* return {
* enter: function(element, done) {
@@ -199,8 +199,8 @@
* //this (optional) function will be called when the animation
* //completes or when the animation is cancelled (the cancelled
* //flag will be set to true if cancelled).
* }
* }
* };
* },
* leave: function(element, done) { },
* move: function(element, done) { },
*
@@ -215,7 +215,7 @@
*
* //animation that can be triggered after the class is removed
* removeClass: function(element, className, done) { }
* }
* };
* });
* </pre>
*
@@ -258,6 +258,19 @@ angular.module('ngAnimate', ['ng'])
var NG_ANIMATE_CLASS_NAME = 'ng-animate';
var rootAnimateState = {running: true};
function extractElementNode(element) {
for(var i = 0; i < element.length; i++) {
var elm = element[i];
if(elm.nodeType == ELEMENT_NODE) {
return elm;
}
}
}
function isMatchingElement(elm1, elm2) {
return extractElementNode(elm1) == extractElementNode(elm2);
}
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document',
function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope, $document) {
@@ -370,7 +383,7 @@ angular.module('ngAnimate', ['ng'])
* Runs the leave animation operation and, upon completion, removes the element from the DOM. Once
* the animation is started, the following CSS classes will be added for the duration of the animation:
*
* Below is a breakdown of each step that occurs during enter animation:
* Below is a breakdown of each step that occurs during leave animation:
*
* | Animation Step | What the element class attribute looks like |
* |----------------------------------------------------------------------------------------------|---------------------------------------------|
@@ -556,7 +569,16 @@ 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 currentClassName = element.attr('class') || '';
var node = extractElementNode(element);
//transcluded directives may sometimes fire an animation using only comment nodes
//best to catch this early on to prevent any animation operations from occurring
if(!node) {
fireDOMOperation();
closeAnimation();
return;
}
var currentClassName = node.className;
var classes = currentClassName + ' ' + className;
var animationLookup = (' ' + classes).replace(/\s+/g,'.');
if (!parentElement) {
@@ -760,11 +782,7 @@ angular.module('ngAnimate', ['ng'])
}
function cancelChildAnimations(element) {
var node = element[0];
if(node.nodeType != ELEMENT_NODE) {
return;
}
var node = extractElementNode(element);
forEach(node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
element = angular.element(element);
var data = element.data(NG_ANIMATE_STATE);
@@ -788,7 +806,7 @@ angular.module('ngAnimate', ['ng'])
}
function cleanup(element) {
if(element[0] == $rootElement[0]) {
if(isMatchingElement(element, $rootElement)) {
if(!rootAnimateState.disabled) {
rootAnimateState.running = false;
rootAnimateState.structural = false;
@@ -802,7 +820,7 @@ angular.module('ngAnimate', ['ng'])
function animationsDisabled(element, parentElement) {
if (rootAnimateState.disabled) return true;
if(element[0] == $rootElement[0]) {
if(isMatchingElement(element, $rootElement)) {
return rootAnimateState.disabled || rootAnimateState.running;
}
@@ -812,7 +830,7 @@ angular.module('ngAnimate', ['ng'])
//any animations on it
if(parentElement.length === 0) break;
var isRoot = parentElement[0] == $rootElement[0];
var isRoot = isMatchingElement(parentElement, $rootElement);
var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
var result = state && (!!state.disabled || !!state.running);
if(isRoot || result) {
@@ -865,6 +883,7 @@ angular.module('ngAnimate', ['ng'])
var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
var NG_ANIMATE_FALLBACK_CLASS_NAME = 'ng-animate-start';
var NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME = 'ng-animate-active';
var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
var lookupCache = {};
var parentCounter = 0;
@@ -959,7 +978,7 @@ angular.module('ngAnimate', ['ng'])
parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
parentID = parentCounter;
}
return parentID + '-' + element[0].className;
return parentID + '-' + extractElementNode(element).className;
}
function animateSetup(element, className) {
@@ -994,7 +1013,6 @@ angular.module('ngAnimate', ['ng'])
return false;
}
var node = element[0];
//temporarily disable the transition so that the enter styles
//don't animate twice (this is here to avoid a bug in Chrome/FF).
var activeClassName = '';
@@ -1024,35 +1042,37 @@ angular.module('ngAnimate', ['ng'])
}
function blockTransitions(element) {
element[0].style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
}
function blockKeyframeAnimations(element) {
element[0].style[ANIMATION_PROP] = 'none 0s';
extractElementNode(element).style[ANIMATION_PROP] = 'none 0s';
}
function unblockTransitions(element) {
var node = element[0], prop = TRANSITION_PROP + PROPERTY_KEY;
var prop = TRANSITION_PROP + PROPERTY_KEY;
var node = extractElementNode(element);
if(node.style[prop] && node.style[prop].length > 0) {
node.style[prop] = '';
}
}
function unblockKeyframeAnimations(element) {
var node = element[0], prop = ANIMATION_PROP;
var prop = ANIMATION_PROP;
var node = extractElementNode(element);
if(node.style[prop] && node.style[prop].length > 0) {
element[0].style[prop] = '';
node.style[prop] = '';
}
}
function animateRun(element, className, activeAnimationComplete) {
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
if(!element.hasClass(className) || !data) {
var node = extractElementNode(element);
if(node.className.indexOf(className) == -1 || !data) {
activeAnimationComplete();
return;
}
var node = element[0];
var timings = data.timings;
var stagger = data.stagger;
var maxDuration = data.maxDuration;
@@ -1095,6 +1115,9 @@ angular.module('ngAnimate', ['ng'])
}
if(appliedStyles.length > 0) {
//the element being animated may sometimes contain comment nodes in
//the jqLite object, so we're safe to use a single variable to house
//the styles since there is always only one element being animated
var oldStyle = node.getAttribute('style') || '';
node.setAttribute('style', oldStyle + ' ' + style);
}
@@ -1109,6 +1132,7 @@ angular.module('ngAnimate', ['ng'])
element.off(css3AnimationEvents, onAnimationProgress);
element.removeClass(activeClassName);
animateClose(element, className);
var node = extractElementNode(element);
for (var i in appliedStyles) {
node.style.removeProperty(appliedStyles[i]);
}
@@ -1118,6 +1142,11 @@ angular.module('ngAnimate', ['ng'])
event.stopPropagation();
var ev = event.originalEvent || event;
var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
/* Firefox (or possibly just Gecko) likes to not round values up
* when a ms measurement is used for the animation */
var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
/* $manualTimeStamp is a mocked timeStamp value which is set
* within browserTrigger(). This is only here so that tests can
* mock animations properly. Real events fallback to event.timeStamp,
@@ -1125,7 +1154,7 @@ angular.module('ngAnimate', ['ng'])
* We're checking to see if the timeStamp surpasses the expected delay,
* but we're using elapsedTime instead of the timeStamp on the 2nd
* pre-condition since animations sometimes close off early */
if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && ev.elapsedTime >= maxDuration) {
if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
activeAnimationComplete();
}
}
@@ -1203,7 +1232,7 @@ angular.module('ngAnimate', ['ng'])
}
var parentElement = element.parent();
var clone = angular.element(element[0].cloneNode());
var clone = angular.element(extractElementNode(element).cloneNode());
//make the element super hidden and override any CSS style values
clone.attr('style','position:absolute; top:-9999px; left:-9999px');
+17 -10
View File
@@ -360,25 +360,32 @@ function htmlParser( html, handler ) {
}
}
var hiddenPre=document.createElement("pre");
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
/**
* decodes all entities into regular string
* @param value
* @returns {string} A string with decoded entities.
*/
var hiddenPre=document.createElement("pre");
function decodeEntities(value) {
if (!value) {
return '';
}
if (!value) { return ''; }
// Note: IE8 does not preserve spaces at the start/end of innerHTML
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
// so we must capture them and reattach them afterward
var parts = spaceRe.exec(value);
parts[0] = '';
if (parts[2]) {
hiddenPre.innerHTML=parts[2].replace(/</g,"&lt;");
parts[2] = hiddenPre.innerText || hiddenPre.textContent;
var spaceBefore = parts[1];
var spaceAfter = parts[3];
var content = parts[2];
if (content) {
hiddenPre.innerHTML=content.replace(/</g,"&lt;");
// innerText depends on styling as it doesn't display hidden elements.
// Therefore, it's better to use textContent not to cause unnecessary
// reflows. However, IE<9 don't support textContent so the innerText
// fallback is necessary.
content = 'textContent' in hiddenPre ?
hiddenPre.textContent : hiddenPre.innerText;
}
return parts.join('');
return spaceBefore + content + spaceAfter;
}
/**
+13
View File
@@ -1079,4 +1079,17 @@ describe('angular', function() {
}
});
describe('isElement', function() {
it('should return a boolean value', inject(function($compile, $document, $rootScope) {
var element = $compile('<p>Hello, world!</p>')($rootScope),
body = $document.find('body')[0],
expected = [false, false, false, false, false, false, false, true, true],
tests = [null, undefined, "string", 1001, {}, 0, false, body, element];
angular.forEach(tests, function(value, idx) {
var result = angular.isElement(value);
expect(typeof result).toEqual('boolean');
expect(result).toEqual(expected[idx]);
});
}));
});
});
+6
View File
@@ -1335,6 +1335,12 @@ describe('jqLite', function() {
expect(innerDiv.length).toEqual(1);
expect(innerDiv.html()).toEqual('text');
});
it('should find child by name and not care about text nodes', function() {
var divs = jqLite('<div><span>aa</span></div>text<div><span>bb</span></div>');
var innerSpan = divs.find('span');
expect(innerSpan.length).toEqual(2);
});
});
+71
View File
@@ -969,6 +969,32 @@ describe('$compile', function() {
});
});
it('should resolve widgets after cloning in append mode without $templateCache', function() {
module(function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
});
inject(function($compile, $templateCache, $rootScope, $httpBackend, $browser,
$exceptionHandler) {
$httpBackend.expect('GET', 'cau.html').respond('<span>{{name}}</span>');
$rootScope.name = 'Elvis';
var template = $compile('<div class="cau"></div>');
var e1;
var e2;
e1 = template($rootScope.$new(), noop); // clone
expect(e1.text()).toEqual('');
$httpBackend.flush();
e2 = template($rootScope.$new(), noop); // clone
$rootScope.$digest();
expect(e1.text()).toEqual('Elvis');
expect(e2.text()).toEqual('Elvis');
dealoc(e1);
dealoc(e2);
});
});
it('should resolve widgets after cloning in inline mode', function() {
module(function($exceptionHandlerProvider) {
@@ -1010,6 +1036,33 @@ describe('$compile', function() {
});
});
it('should resolve widgets after cloning in inline mode without $templateCache', function() {
module(function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
});
inject(function($compile, $templateCache, $rootScope, $httpBackend, $browser,
$exceptionHandler) {
$httpBackend.expect('GET', 'cau.html').respond('<span>{{name}}</span>');
$rootScope.name = 'Elvis';
var template = $compile('<div class="i-cau"></div>');
var e1;
var e2;
e1 = template($rootScope.$new(), noop); // clone
expect(e1.text()).toEqual('');
$httpBackend.flush();
e2 = template($rootScope.$new(), noop); // clone
$rootScope.$digest();
expect(e1.text()).toEqual('Elvis');
expect(e2.text()).toEqual('Elvis');
dealoc(e1);
dealoc(e2);
});
});
it('should be implicitly terminal and not compile placeholder content in append', inject(
function($compile, $templateCache, $rootScope, log) {
@@ -2386,6 +2439,24 @@ describe('$compile', function() {
expect(componentScope.refAlias).toBe($rootScope.name);
}));
it('should not break if local and origin both change to the same value', inject(function() {
$rootScope.name = 'aaa';
compile('<div><span my-component ref="name">');
//change both sides to the same item withing the same digest cycle
componentScope.ref = 'same';
$rootScope.name = 'same';
$rootScope.$apply();
//change origin back to it's previous value
$rootScope.name = 'aaa';
$rootScope.$apply();
expect($rootScope.name).toBe('aaa');
expect(componentScope.ref).toBe('aaa');
}));
it('should complain on non assignable changes', inject(function() {
compile('<div><span my-component ref="\'hello \' + name">');
$rootScope.name = 'world';
+23
View File
@@ -383,6 +383,29 @@ describe('ngModel', function() {
dealoc(element);
});
});
it('should keep previously defined watches consistent when changes in validity are made',
inject(function($compile, $rootScope) {
var isFormValid;
$rootScope.$watch('myForm.$valid', function(value) { isFormValid = value; });
var element = $compile('<form name="myForm">' +
'<input name="myControl" ng-model="value" required >' +
'</form>')($rootScope);
$rootScope.$apply();
expect(isFormValid).toBe(false);
expect($rootScope.myForm.$valid).toBe(false);
$rootScope.value='value';
$rootScope.$apply();
expect(isFormValid).toBe(true);
expect($rootScope.myForm.$valid).toBe(true);
dealoc(element);
}));
});
+26 -1
View File
@@ -1,8 +1,11 @@
'use strict';
describe('ngIf', function () {
var $scope, $compile, element;
var $scope, $compile, element, $compileProvider;
beforeEach(module(function(_$compileProvider_) {
$compileProvider = _$compileProvider_;
}));
beforeEach(inject(function ($rootScope, _$compile_) {
$scope = $rootScope.$new();
$compile = _$compile_;
@@ -146,6 +149,28 @@ describe('ngIf', function () {
expect(element.children()[0].className).toContain('my-class');
});
it('should work when combined with an ASYNC template that loads after the first digest', inject(function($httpBackend, $compile, $rootScope) {
$compileProvider.directive('test', function() {
return {
templateUrl: 'test.html'
};
});
$httpBackend.whenGET('test.html').respond('hello');
element.append('<div ng-if="show" test></div>');
$compile(element)($rootScope);
$rootScope.show = true;
$rootScope.$apply();
expect(element.text()).toBe('');
$httpBackend.flush();
expect(element.text()).toBe('hello');
$rootScope.show = false;
$rootScope.$apply();
// Note: there are still comments in element!
expect(element.children().length).toBe(0);
expect(element.text()).toBe('');
}));
});
describe('ngIf and transcludes', function() {
+26
View File
@@ -13,4 +13,30 @@ describe('ngInit', function() {
element = $compile('<div ng-init="a=123"></div>')($rootScope);
expect($rootScope.a).toEqual(123);
}));
it("should be evaluated before ngInclude", inject(function($rootScope, $templateCache, $compile) {
$templateCache.put('template1.tpl', '<span>1</span>');
$templateCache.put('template2.tpl', '<span>2</span>');
$rootScope.template = 'template1.tpl';
element = $compile('<div><div ng-include="template" ' +
'ng-init="template=\'template2.tpl\'"></div></div>')($rootScope);
$rootScope.$digest();
expect($rootScope.template).toEqual('template2.tpl');
expect(element.find('span').text()).toEqual('2');
}));
it("should be evaluated after ngController", function() {
module(function($controllerProvider) {
$controllerProvider.register('TestCtrl', function($scope) {});
});
inject(function($rootScope, $compile) {
element = $compile('<div><div ng-controller="TestCtrl" ' +
'ng-init="test=123"></div></div>')($rootScope);
$rootScope.$digest();
expect($rootScope.test).toBeUndefined();
expect(element.children('div').scope().test).toEqual(123);
});
});
});
+23 -1
View File
@@ -749,8 +749,30 @@ describe('ngRepeat', function() {
expect(element.text()).toBe('123');
}));
}
});
it('should work when combined with an ASYNC template that loads after the first digest', inject(function($httpBackend, $compile, $rootScope) {
$compileProvider.directive('test', function() {
return {
templateUrl: 'test.html'
};
});
$httpBackend.whenGET('test.html').respond('hello');
element = jqLite('<div><div ng-repeat="i in items" test></div></div>');
$compile(element)($rootScope);
$rootScope.items = [1];
$rootScope.$apply();
expect(element.text()).toBe('');
$httpBackend.flush();
expect(element.text()).toBe('hello');
$rootScope.items = [];
$rootScope.$apply();
// Note: there are still comments in element!
expect(element.children().length).toBe(0);
expect(element.text()).toBe('');
}));
});
it('should add separator comments after each item', inject(function ($compile, $rootScope) {
var check = function () {
+90 -1
View File
@@ -127,6 +127,15 @@ describe('Scope', function() {
});
});
it('should clear phase if an exception interrupt $digest cycle', function() {
inject(function($rootScope) {
$rootScope.$watch('a', function() {throw new Error('abc');});
$rootScope.a = 1;
try { $rootScope.$digest(); } catch(e) { }
expect($rootScope.$$phase).toBeNull();
});
});
it('should fire watches in order of addition', inject(function($rootScope) {
// this is not an external guarantee, just our own sanity
@@ -568,6 +577,82 @@ describe('Scope', function() {
});
});
});
describe('optimizations', function() {
function setupWatches(scope, log) {
scope.$watch(function() { log('w1'); return scope.w1; }, log.fn('w1action'));
scope.$watch(function() { log('w2'); return scope.w2; }, log.fn('w2action'));
scope.$watch(function() { log('w3'); return scope.w3; }, log.fn('w3action'));
scope.$digest();
log.reset();
}
it('should check watches only once during an empty digest', inject(function(log, $rootScope) {
setupWatches($rootScope, log);
$rootScope.$digest();
expect(log).toEqual(['w1', 'w2', 'w3']);
}));
it('should quit digest early after we check the last watch that was previously dirty',
inject(function(log, $rootScope) {
setupWatches($rootScope, log);
$rootScope.w1 = 'x';
$rootScope.$digest();
expect(log).toEqual(['w1', 'w1action', 'w2', 'w3', 'w1']);
}));
it('should not quit digest early if a new watch was added from an existing watch action',
inject(function(log, $rootScope) {
setupWatches($rootScope, log);
$rootScope.$watch(log.fn('w4'), function() {
log('w4action');
$rootScope.$watch(log.fn('w5'), log.fn('w5action'));
});
$rootScope.$digest();
expect(log).toEqual(['w1', 'w2', 'w3', 'w4', 'w4action',
'w1', 'w2', 'w3', 'w4', 'w5', 'w5action',
'w1', 'w2', 'w3', 'w4', 'w5']);
}));
it('should not quit digest early if an evalAsync task was scheduled from a watch action',
inject(function(log, $rootScope) {
setupWatches($rootScope, log);
$rootScope.$watch(log.fn('w4'), function() {
log('w4action');
$rootScope.$evalAsync(function() {
log('evalAsync')
});
});
$rootScope.$digest();
expect(log).toEqual(['w1', 'w2', 'w3', 'w4', 'w4action', 'evalAsync',
'w1', 'w2', 'w3', 'w4']);
}));
it('should quit digest early but not too early when various watches fire', inject(function(log, $rootScope) {
setupWatches($rootScope, log);
$rootScope.$watch(function() { log('w4'); return $rootScope.w4; }, function(newVal) {
log('w4action');
$rootScope.w2 = newVal;
});
$rootScope.$digest();
log.reset();
$rootScope.w1 = 'x';
$rootScope.w4 = 'x';
$rootScope.$digest();
expect(log).toEqual(['w1', 'w1action', 'w2', 'w3', 'w4', 'w4action',
'w1', 'w2', 'w2action', 'w3', 'w4',
'w1', 'w2']);
}));
});
});
@@ -590,10 +675,14 @@ describe('Scope', function() {
}));
it('should ignore remove on root', inject(function($rootScope) {
it('should broadcast $destroy on rootScope', inject(function($rootScope) {
var spy = spyOn(angular, 'noop');
$rootScope.$on('$destroy', angular.noop);
$rootScope.$destroy();
$rootScope.$digest();
expect(log).toEqual('123');
expect(spy).toHaveBeenCalled();
expect($rootScope.$$destroyed).toBe(true);
}));
+74
View File
@@ -2852,5 +2852,79 @@ describe("ngAnimate", function() {
$timeout.flush();
});
});
it('should round up long elapsedTime values to close off a CSS3 animation',
inject(function($rootScope, $compile, $rootElement, $document, $animate, $sniffer, $timeout, $window) {
if (!$sniffer.animations) return;
ss.addRule('.millisecond-transition.ng-leave', '-webkit-transition:510ms linear all;' +
'transition:510ms linear all;');
var element = $compile('<div class="millisecond-transition"></div>')($rootScope);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$animate.leave(element);
$rootScope.$digest();
$timeout.flush();
browserTrigger(element, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 0.50999999991 });
expect($rootElement.children().length).toBe(0);
}));
it('should properly animate elements with compound directives', function() {
var capturedAnimation;
module(function($animateProvider) {
$animateProvider.register('.special', function() {
return {
enter : function(element, done) {
capturedAnimation = 'enter';
done();
},
leave : function(element, done) {
capturedAnimation = 'leave';
done();
}
}
});
});
inject(function($rootScope, $compile, $rootElement, $document, $timeout, $templateCache, $sniffer) {
if(!$sniffer.transitions) return;
$templateCache.put('item-template', 'item: #{{ item }} ');
var element = $compile('<div>' +
' <div ng-repeat="item in items"' +
' ng-include="tpl"' +
' class="special"></div>' +
'</div>')($rootScope);
ss.addRule('.special', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$rootScope.tpl = 'item-template';
$rootScope.items = [1,2,3];
$rootScope.$digest();
$timeout.flush();
expect(capturedAnimation).toBe('enter');
expect(element.text()).toContain('item: #1');
forEach(element.children(), function(kid) {
browserTrigger(kid, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
});
$timeout.flush();
$rootScope.items = [];
$rootScope.$digest();
$timeout.flush();
expect(capturedAnimation).toBe('leave');
});
});
});
});
+52
View File
@@ -411,3 +411,55 @@ describe('HTML', function() {
});
});
});
describe('decodeEntities', function() {
var handler, text,
origHiddenPre = window.hiddenPre;
beforeEach(function() {
text = '';
handler = {
start: function() {},
chars: function(text_){
text = text_;
},
end: function() {},
comment: function() {}
};
module('ngSanitize');
});
afterEach(function() {
window.hiddenPre = origHiddenPre;
});
it('should use innerText if textContent is not available (IE<9)', function() {
window.hiddenPre = {
innerText: 'INNER_TEXT'
};
inject(function($sanitize) {
htmlParser('<tag>text</tag>', handler);
expect(text).toEqual('INNER_TEXT');
});
});
it('should use textContent if available', function() {
window.hiddenPre = {
textContent: 'TEXT_CONTENT',
innerText: 'INNER_TEXT'
};
inject(function($sanitize) {
htmlParser('<tag>text</tag>', handler);
expect(text).toEqual('TEXT_CONTENT');
});
});
it('should use textContent even if empty', function() {
window.hiddenPre = {
textContent: '',
innerText: 'INNER_TEXT'
};
inject(function($sanitize) {
htmlParser('<tag>text</tag>', handler);
expect(text).toEqual('');
});
});
});
+2 -2
View File
@@ -5,5 +5,5 @@ set -e
export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
grunt parallel:travis --reporters dots \
--browsers BS_Chrome,BS_Safari,BS_Firefox,BS_IE_8,BS_IE_9,BS_IE_10,BS_IE_11 \
--e2e-browsers BS_Chrome
--browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_8,SL_IE_9,SL_IE_10,SL_IE_11 \
--e2e-browsers SL_Chrome