Compare commits

...

33 Commits

Author SHA1 Message Date
jenkins 07ee29c563 chore(release): cut v1.2.9 release 2014-01-15 10:02:10 -08:00
Peter Bacon Darwin 9f5d0cf79f docs(changelog): release notes for 1.2.9 2014-01-15 17:24:18 +00:00
Matias Niemelä 1413328e6a fix(ngMock): ensure ngAnimate isn't a required mock 2014-01-15 03:43:52 -05:00
Matias Niemelä 7d09bd30f9 chore($animate): remove Moz statements from requestAnimationFrame 2014-01-15 03:29:20 -05:00
Matias Niemelä dde1b29497 feat($animate): provide support for DOM callbacks 2014-01-14 13:21:28 -05:00
Matias Niemelä 4ae3184c59 feat($animate): use requestAnimationFrame instead of a timeout to issue a reflow
Closes #4278
Closes #4225
2014-01-14 13:21:19 -05:00
Matias Niemelä ed53100a0d fix($animate): ensure the final closing timeout respects staggering animations 2014-01-14 13:20:50 -05:00
Matias Niemelä 6df598d9f5 chore($animate): remove useless and expired test 2014-01-14 13:20:27 -05:00
Matias Niemelä 4aa9df7a7a fix($animate): prevent race conditions for class-based animations when animating on the same CSS class
Closes #5588
2014-01-14 13:20:10 -05:00
Matias Niemelä 7d5d62dafe fix($animate): correctly detect and handle CSS transition changes during class addition and removal
When a CSS class containing transition code is added to an element then an animation should kick off.
ngAnimate doesn't do this. It only respects transition styles that are already present on the element
or on the setup class (but not the addClass animation).
2014-01-14 13:19:09 -05:00
Matias Niemelä 524650a40e fix($animate): avoid accidentally matching substrings when resolving the presence of className tokens 2014-01-14 13:18:50 -05:00
Sebastian Müller 02a45826f1 docs(docs): preserve path to current doc page when switching versions
Preserve URL path when switching between doc versions.

Closes #4661
Closes #5773
2014-01-14 06:39:44 +00:00
Lukas Ruebbelke e324c14907 docs(provider): replaced coffeescript with comparable javascript example 2014-01-13 23:03:36 -05:00
Hendrixer e1cfb1957f fix($http): ensure default headers PUT and POST are different objects
Send PUT and POST through copy() to make sure they are not the same.

Closes #5742
Closes #5747
Closes #5764
2014-01-13 16:50:15 -08:00
marcwright 2a3586381f docs(tutorial): fix a typo
Closes #5769
2014-01-13 16:40:56 -08:00
Igor Minar 834d316829 docs(forEach): remove obsolte note 2014-01-13 16:31:43 -08:00
Mehul Patel c61be8d0e6 docs(angular.forEach): specifies that .forEach filters using .hasOwnProperty
Closes #5180
Closes #5776
2014-01-13 16:31:42 -08:00
Pop 465212835f docs(guide): fix a typo 2014-01-13 16:24:56 -08:00
Frederik Creemers b3acddea37 docs(CONTRIBUTING.md): add link to coding rules
add a link insie the Submitting a Pull Request section
2014-01-13 16:18:14 -08:00
Igor Minar 308598795a revert: fix($route): update current route upon $route instantiation
This reverts commit 2b344dbd20.

I think I merged this commit prematurely and in addition to that
we found out that it's breaking google apps.

Jen Bourey will provide more info at the original PR #5681
2014-01-13 15:12:17 -08:00
Noam Lewis 2cd09c9f0e fix($rootScope): prevent infinite $digest by checking if asyncQueue is empty when decrementing ttl
An infinite $digest loop can be caused by expressions that invoke a promise.
The problem is that $digest does not decrement ttl unless it finds dirty changes;
it should check also if asyncQueue is empty.
Generally the condition for decrementing ttl should be the same as the
condition for terminating the $digest loop.

Fixes #2622
2014-01-13 09:53:38 -08:00
Leniel Macaferi 34fee06ca7 docs(tutorial): referenced test.bat for Windows users in step 2
Closes #5748
2014-01-11 20:15:50 -08:00
Igor Minar c7a46d4b8a docs(ngView): moar better autoscroll docs
Closes #5734
2014-01-11 14:29:05 -08:00
Gias Kay Lee de065f1961 docs(ngView): add param info
Closes #5734
Closes #5741
2014-01-11 14:18:07 -08:00
Seth Stone c3ab915d2e docs(tutorial): add missing beforeEach(module()) to test
Test snippet was missing this necessary statement that was present in the sample code.

Closes #5743
2014-01-11 14:09:52 -08:00
Roy Ling f4a4f42abb docs(api/index): fix typo
Closes #5738
2014-01-10 23:43:32 -08:00
jesse b2c84ccde3 docs($sce): corrected typo & markup.
the --> that

value --> `value`

Closes #5735
2014-01-10 23:42:36 -08:00
Daniel Zimmermann 2b344dbd20 fix($route): update current route upon $route instantiation
This fixes cases where the first ngView is loaded in a template asynchronously (such as through ngInclude), as the service will miss the first  event otherwise.

Closes #4957
2014-01-10 23:42:36 -08:00
Jeff Cross cde840fdf8 docs(i18n): remove use of gendered pronoun 2014-01-10 20:15:18 -08:00
Naomi Black f9656dab2d docs: update step_04.ngdoc with a clarification
closes #5730
2014-01-10 17:11:39 -08:00
Naomi Black a0d759c613 docs: add clarification in step 2 of the tutorial 2014-01-10 16:54:20 -08:00
jenkins 0d421f093d chore(release): update cdn version 2014-01-10 14:33:27 -08:00
jenkins 5f937e54df chore(release): start v1.2.9 (1.2.9) 2014-01-10 12:51:00 -08:00
22 changed files with 638 additions and 179 deletions
+36
View File
@@ -1,3 +1,39 @@
<a name="1.2.9"></a>
# 1.2.9 enchanted-articulacy (2014-01-15)
## Bug Fixes
- **$animate:**
- ensure the final closing timeout respects staggering animations
([ed53100a](https://github.com/angular/angular.js/commit/ed53100a0dbc9119d5dfc8b7248845d4f6989df2))
- prevent race conditions for class-based animations when animating on the same CSS class
([4aa9df7a](https://github.com/angular/angular.js/commit/4aa9df7a7ae533531dfae1e3eb9646245d6b5ff4),
[#5588](https://github.com/angular/angular.js/issues/5588))
- correctly detect and handle CSS transition changes during class addition and removal
([7d5d62da](https://github.com/angular/angular.js/commit/7d5d62dafe11620082c79da35958f8014eeb008c))
- avoid accidentally matching substrings when resolving the presence of className tokens
([524650a4](https://github.com/angular/angular.js/commit/524650a40ed20f01571e5466475749874ee67288))
- **$http:** ensure default headers PUT and POST are different objects
([e1cfb195](https://github.com/angular/angular.js/commit/e1cfb1957feaf89408bccf48fae6f529e57a82fe),
[#5742](https://github.com/angular/angular.js/issues/5742), [#5747](https://github.com/angular/angular.js/issues/5747), [#5764](https://github.com/angular/angular.js/issues/5764))
- **$rootScope:** prevent infinite $digest by checking if asyncQueue is empty when decrementing ttl
([2cd09c9f](https://github.com/angular/angular.js/commit/2cd09c9f0e7766bcd191662841b7b1ffc3b6dc3f),
[#2622](https://github.com/angular/angular.js/issues/2622))
- **$route:** update current route upon $route instantiation
([2b344dbd](https://github.com/angular/angular.js/commit/2b344dbd20777fb1283b3a5bcf35a6ae8d09469d),
[#4957](https://github.com/angular/angular.js/issues/4957))
## Features
- **$animate:**
- provide support for DOM callbacks
([dde1b294](https://github.com/angular/angular.js/commit/dde1b2949727c297e214c99960141bfad438d7a4))
- use requestAnimationFrame instead of a timeout to issue a reflow
([4ae3184c](https://github.com/angular/angular.js/commit/4ae3184c5915aac9aa00889aa2153c8e84c14966),
[#4278](https://github.com/angular/angular.js/issues/4278), [#4225](https://github.com/angular/angular.js/issues/4225))
<a name="1.2.8"></a>
# 1.2.8 interdimensional-cartography (2014-01-10)
+1 -2
View File
@@ -80,7 +80,7 @@ Before you submit your pull request consider the following guidelines:
```
* Create your patch, including appropriate test cases.
* Follow our Coding Rules
* Follow our [Coding Rules](#coding-rules)
* Commit your changes and create a descriptive commit message (the
commit message is used to generate release notes, please check out our
[commit message conventions](#commit-message-format) and our commit message presubmit hook
@@ -259,5 +259,4 @@ You can find out more detailed information about contributing in the
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
[github-pr-helper]: https://chrome.google.com/webstore/detail/github-pr-helper/mokbklfnaddkkbolfldepnkfmanfhpen
[![Analytics](https://ga-beacon.appspot.com/UA-8594346-11/angular.js/CONTRIBUTING.md?pixel)](https://github.com/igrigorik/ga-beacon)
+1 -1
View File
@@ -63,7 +63,7 @@ This module is provided by default and contains the core components of AngularJS
</td>
<td>
<p>
The core filters available in the ng module are used to transform template data before it is renders within directives and expressions.
The core filters available in the ng module are used to transform template data before it is rendered within directives and expressions.
</p>
<p>
Some examples include:
+1 -1
View File
@@ -184,7 +184,7 @@ The following graphic shows how everything works together after we introduced th
# View independent business logic: Services
Right now, the `InvoiceController` contains all logic of our example. When the application grows it
is a good practise to move view independent logic from the controller into a so called
is a good practice to move view independent logic from the controller into a so called
<a name="service">"{@link dev_guide.services service}"</a>, so it can be reused by other parts
of the application as well. Later on, we could also change that service to load the exchange rates
from the web, e.g. by calling the Yahoo Finance API, without changing the controller.
+1 -1
View File
@@ -99,7 +99,7 @@ actual value is understood.
For example, if you want to display an account balance of 1000 dollars with the following binding
containing currency filter: `{{ 1000 | currency }}`, and your app is currently in en-US locale.
'$1000.00' will be shown. However, if someone in a different local (say, Japan) views your app, her
'$1000.00' will be shown. However, if someone in a different local (say, Japan) views your app, their
browser will specify the locale as ja, and the balance of '¥1000.00' will be shown instead. This
will really upset your client.
+4 -4
View File
@@ -191,8 +191,8 @@ 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
is located at `./config/karma.conf.js`).
`./scripts/test.sh` (if you are on Windows, run scripts\test.bat) to start the Karma server (the
config file necessary to start the server is located at `./config/karma.conf.js`).
2. Karma will start a new instance of Chrome browser automatically. Just ignore it and let it run in
the background. Karma will use this browser for test execution.
@@ -206,7 +206,7 @@ is located at `./config/karma.conf.js`).
Yay! The test passed! Or not...
4. To rerun the tests, just change any of the source or test files. Karma will notice the change
4. To rerun the tests, just change any of the source or test .js files. Karma will notice the change
and will rerun the tests for you. Now isn't that sweet?
# Experiments
@@ -223,7 +223,7 @@ is located at `./config/karma.conf.js`).
<p>Hello, {{name}}!</p>
Refresh your browser and verifies that it says "Hello, World!".
Refresh your browser and verify that it says "Hello, World!".
* Create a repeater that constructs a simple table:
+2 -2
View File
@@ -88,8 +88,8 @@ phonecatApp.controller('PhoneListCtrl', function ($scope) {
record. This property is used to order phones by age.
* We added a line to the controller that sets the default value of `orderProp` to `age`. If we had
not set the default value here, the model would stay uninitialized until our user picks an
option from the drop down menu.
not set a default value here, the `orderBy` filter would remain uninitialized until our
user picked an option from the drop down menu.
This is a good time to talk about two-way data-binding. Notice that when the app is loaded in the
browser, "Newest" is selected in the drop down menu. This is because we set `orderProp` to `'age'`
+3
View File
@@ -170,6 +170,9 @@ describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
var scope, ctrl, $httpBackend;
// Load our app module definition before each test.
beforeEach(module('phonecatApp'));
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service.
+13 -5
View File
@@ -5,11 +5,19 @@ var docsApp = {
filter: {}
};
docsApp.controller.DocsVersionsCtrl = ['$scope', '$window', 'NG_VERSIONS', 'NG_VERSION', function($scope, $window, NG_VERSIONS, NG_VERSION) {
docsApp.controller.DocsVersionsCtrl = ['$scope', '$rootScope', '$window', 'NG_VERSIONS', 'NG_VERSION', function($scope, $rootScope, $window, NG_VERSIONS, NG_VERSION) {
$scope.docs_versions = NG_VERSIONS;
$scope.docs_version = NG_VERSIONS[0];
$scope.jumpToDocsVersion = function(version) {
$window.location = version.url;
var currentPagePath = '';
// preserve URL path when switching between doc versions
if (angular.isObject($rootScope.currentPage) && $rootScope.currentPage.section && $rootScope.currentPage.id) {
currentPagePath = '/' + $rootScope.currentPage.section + '/' + $rootScope.currentPage.id;
}
$window.location = version.url + currentPagePath;
};
}];
@@ -645,7 +653,7 @@ docsApp.serviceFactory.sections = ['NG_PAGES', function sections(NG_PAGES) {
}];
docsApp.controller.DocsController = function($scope, $location, $window, $cookies, sections) {
docsApp.controller.DocsController = function($scope, $rootScope, $location, $window, $cookies, sections) {
$scope.fold = function(url) {
if(url) {
$scope.docs_fold = '/notes/' + url;
@@ -736,9 +744,9 @@ docsApp.controller.DocsController = function($scope, $location, $window, $cookie
sectionName = SECTION_NAME[sectionId] || sectionId,
page = sections.getPage(sectionId, partialId);
$scope.currentPage = sections.getPage(sectionId, partialId);
$rootScope.currentPage = sections.getPage(sectionId, partialId);
if (!$scope.currentPage) {
if (!$rootScope.currentPage) {
$scope.partialTitle = 'Error: Page Not Found!';
}
+1 -1
View File
@@ -10,12 +10,12 @@ module.exports = function(config) {
'build/angular.js',
'build/angular-cookies.js',
'build/angular-mocks.js',
'build/angular-resource.js',
'build/angular-touch.js',
'build/angular-sanitize.js',
'build/angular-route.js',
'build/angular-animate.js',
'build/angular-mocks.js',
'build/docs/components/lunr.js',
'build/docs/components/google-code-prettify.js',
+3 -3
View File
@@ -1,8 +1,8 @@
{
"name": "angularjs",
"version": "1.2.8",
"cdnVersion": "1.2.7",
"codename": "interdimensional-cartography",
"version": "1.2.9",
"cdnVersion": "1.2.8",
"codename": "enchanted-articulacy",
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
+2 -1
View File
@@ -192,7 +192,8 @@ function isArrayLike(obj) {
* is the value of an object property or an array element and `key` is the object property key or
* array element index. Specifying a `context` for the function is optional.
*
* Note: this function was previously known as `angular.foreach`.
* It is worth nothing that `.forEach` does not iterate over inherited properties because it filters
* using the `hasOwnProperty` method.
*
<pre>
var values = {name: 'misko', gender: 'male'};
+16 -11
View File
@@ -474,7 +474,7 @@ function annotate(fn) {
* constructor function that will be used to instantiate the service instance.
*
* You should use {@link AUTO.$provide#methods_service $provide.service(class)} if you define your service
* as a type/class. This is common when using {@link http://coffeescript.org CoffeeScript}.
* as a type/class.
*
* @param {string} name The name of the instance.
* @param {Function} constructor A class (constructor function) that will be instantiated.
@@ -482,20 +482,25 @@ function annotate(fn) {
*
* @example
* Here is an example of registering a service using
* {@link AUTO.$provide#methods_service $provide.service(class)} that is defined as a CoffeeScript class.
* {@link AUTO.$provide#methods_service $provide.service(class)}.
* <pre>
* class Ping
* constructor: (@$http) ->
* send: () =>
* @$http.get('/ping')
*
* $provide.service('ping', ['$http', Ping])
* $provide.service('ping', ['$http', function($http) {
* var Ping = function() {
* this.$http = $http;
* };
*
* Ping.prototype.send = function() {
* return this.$http.get('/ping');
* };
*
* return Ping;
* }]);
* </pre>
* You would then inject and use this service like this:
* <pre>
* someModule.controller 'Ctrl', ['ping', (ping) ->
* ping.send()
* ]
* someModule.controller('Ctrl', ['ping', function(ping) {
* ping.send();
* }]);
* </pre>
*/
+4 -4
View File
@@ -111,9 +111,9 @@ function $HttpProvider() {
common: {
'Accept': 'application/json, text/plain, */*'
},
post: CONTENT_TYPE_APPLICATION_JSON,
put: CONTENT_TYPE_APPLICATION_JSON,
patch: CONTENT_TYPE_APPLICATION_JSON
post: copy(CONTENT_TYPE_APPLICATION_JSON),
put: copy(CONTENT_TYPE_APPLICATION_JSON),
patch: copy(CONTENT_TYPE_APPLICATION_JSON)
},
xsrfCookieName: 'XSRF-TOKEN',
@@ -324,7 +324,7 @@ function $HttpProvider() {
* to `push` or `unshift` a new transformation function into the transformation chain. You can
* also decide to completely override any default transformations by assigning your
* transformation functions to these properties directly without the array wrapper. These defaults
* are again available on the $http factory at run-time, which may be useful if you have run-time
* are again available on the $http factory at run-time, which may be useful if you have run-time
* services you wish to be involved in your transformations.
*
* Similarly, to locally override the request/response transforms, augment the
+1 -1
View File
@@ -632,7 +632,7 @@ function $RootScopeProvider(){
// `break traverseScopesLoop;` takes us to here
if(dirty && !(ttl--)) {
if((dirty || asyncQueue.length) && !(ttl--)) {
clearPhase();
throw $rootScopeMinErr('infdig',
'{0} $digest() iterations reached. Aborting!\n' +
+1 -1
View File
@@ -321,7 +321,7 @@ function $SceDelegateProvider() {
*
* @param {*} value The result of a prior {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}
* call or anything else.
* @returns {*} The value the was originally provided to {@link ng.$sceDelegate#methods_trustAs
* @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#methods_trustAs
* `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
* `value` unchanged.
*/
+135 -32
View File
@@ -248,6 +248,26 @@ angular.module('ngAnimate', ['ng'])
* Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
*
*/
.factory('$$animateReflow', ['$window', '$timeout', function($window, $timeout) {
var requestAnimationFrame = $window.requestAnimationFrame ||
$window.webkitRequestAnimationFrame ||
function(fn) {
return $timeout(fn, 10, false);
};
var cancelAnimationFrame = $window.cancelAnimationFrame ||
$window.webkitCancelAnimationFrame ||
function(timer) {
return $timeout.cancel(timer);
};
return function(fn) {
var id = requestAnimationFrame(fn);
return function() {
cancelAnimationFrame(id);
};
};
}])
.config(['$provide', '$animateProvider', function($provide, $animateProvider) {
var noop = angular.noop;
var forEach = angular.forEach;
@@ -295,6 +315,10 @@ angular.module('ngAnimate', ['ng'])
return classNameFilter.test(className);
};
function async(fn) {
return $timeout(fn, 0, false);
}
function lookup(name) {
if (name) {
var matches = [],
@@ -586,6 +610,8 @@ angular.module('ngAnimate', ['ng'])
//best to catch this early on to prevent any animation operations from occurring
if(!node || !isAnimatableClassName(classes)) {
fireDOMOperation();
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
closeAnimation();
return;
}
@@ -605,6 +631,8 @@ angular.module('ngAnimate', ['ng'])
//NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found.
if (animationsDisabled(element, parentElement) || matches.length === 0) {
fireDOMOperation();
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
closeAnimation();
return;
}
@@ -643,14 +671,17 @@ angular.module('ngAnimate', ['ng'])
//animation do it's thing and close this one early
if(animations.length === 0) {
fireDOMOperation();
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
fireDoneCallbackAsync();
return;
}
var ONE_SPACE = ' ';
//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 + ' ';
var futureClassName = ONE_SPACE + currentClassName + ONE_SPACE;
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
@@ -658,12 +689,23 @@ angular.module('ngAnimate', ['ng'])
cleanup(element);
cancelAnimations(ngAnimateState.animations);
//in the event that the CSS is class is quickly added and removed back
//then we don't want to wait until after the reflow to add/remove the CSS
//class since both class animations may run into a race condition.
//The code below will check to see if that is occurring and will
//immediately remove the former class before the reflow so that the
//animation can snap back to the original animation smoothly
var isFullyClassBasedAnimation = isClassBased && !ngAnimateState.structural;
var isRevertingClassAnimation = isFullyClassBasedAnimation &&
ngAnimateState.className == className &&
animationEvent != ngAnimateState.event;
//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) {
if(ngAnimateState.beforeComplete || isRevertingClassAnimation) {
(ngAnimateState.done || noop)(true);
} else if(isClassBased && !ngAnimateState.structural) {
} else if(isFullyClassBasedAnimation) {
//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
@@ -671,8 +713,8 @@ angular.module('ngAnimate', ['ng'])
//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 + ' ';
futureClassName.replace(ONE_SPACE + ngAnimateState.className + ONE_SPACE, ONE_SPACE) :
futureClassName + ngAnimateState.className + ONE_SPACE;
}
}
@@ -680,10 +722,12 @@ angular.module('ngAnimate', ['ng'])
//(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.
var classNameToken = ' ' + className + ' ';
var classNameToken = ONE_SPACE + className + ONE_SPACE;
if((animationEvent == 'addClass' && futureClassName.indexOf(classNameToken) >= 0) ||
(animationEvent == 'removeClass' && futureClassName.indexOf(classNameToken) == -1)) {
fireDOMOperation();
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
fireDoneCallbackAsync();
return;
}
@@ -724,6 +768,10 @@ angular.module('ngAnimate', ['ng'])
}
function invokeRegisteredAnimationFns(animations, phase, allAnimationFnsComplete) {
phase == 'after' ?
fireAfterCallbackAsync() :
fireBeforeCallbackAsync();
var endFnName = phase + 'End';
forEach(animations, function(animation, index) {
var animationPhaseCompleted = function() {
@@ -760,8 +808,27 @@ angular.module('ngAnimate', ['ng'])
}
}
function fireDOMCallback(animationPhase) {
element.triggerHandler('$animate:' + animationPhase, {
event : animationEvent,
className : className
});
}
function fireBeforeCallbackAsync() {
async(function() {
fireDOMCallback('before');
});
}
function fireAfterCallbackAsync() {
async(function() {
fireDOMCallback('after');
});
}
function fireDoneCallbackAsync() {
doneCallback && $timeout(doneCallback, 0, false);
doneCallback && async(doneCallback);
}
//it is less complicated to use a flag than managing and cancelling
@@ -785,9 +852,9 @@ angular.module('ngAnimate', ['ng'])
if(isClassBased) {
cleanup(element);
} else {
data.closeAnimationTimeout = $timeout(function() {
data.closeAnimationTimeout = async(function() {
cleanup(element);
}, 0, false);
});
element.data(NG_ANIMATE_STATE, data);
}
}
@@ -811,10 +878,10 @@ angular.module('ngAnimate', ['ng'])
function cancelAnimations(animations) {
var isCancelledFlag = true;
forEach(animations, function(animation) {
if(!animations.beforeComplete) {
if(!animation.beforeComplete) {
(animation.beforeEnd || noop)(isCancelledFlag);
}
if(!animations.afterComplete) {
if(!animation.afterComplete) {
(animation.afterEnd || noop)(isCancelledFlag);
}
});
@@ -860,7 +927,8 @@ angular.module('ngAnimate', ['ng'])
}
}]);
$animateProvider.register('', ['$window', '$sniffer', '$timeout', function($window, $sniffer, $timeout) {
$animateProvider.register('', ['$window', '$sniffer', '$timeout', '$$animateReflow',
function($window, $sniffer, $timeout, $$animateReflow) {
// Detect proper transitionend/animationend event names.
var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
@@ -905,11 +973,13 @@ angular.module('ngAnimate', ['ng'])
var parentCounter = 0;
var animationReflowQueue = [];
var animationElementQueue = [];
var animationTimer;
var cancelAnimationReflow;
var closingAnimationTime = 0;
var timeOut = false;
function afterReflow(element, callback) {
$timeout.cancel(animationTimer);
if(cancelAnimationReflow) {
cancelAnimationReflow();
}
animationReflowQueue.push(callback);
@@ -918,15 +988,19 @@ angular.module('ngAnimate', ['ng'])
animationElementQueue.push(element);
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
closingAnimationTime = Math.max(closingAnimationTime,
(elementData.maxDelay + elementData.maxDuration) * CLOSING_TIME_BUFFER * ONE_SECOND);
var stagger = elementData.stagger;
var staggerTime = elementData.itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
var animationTime = (elementData.maxDelay + elementData.maxDuration) * CLOSING_TIME_BUFFER;
closingAnimationTime = Math.max(closingAnimationTime, (staggerTime + animationTime) * ONE_SECOND);
//by placing a counter we can avoid an accidental
//race condition which may close an animation when
//a follow-up animation is midway in its animation
elementData.animationCount = animationCounter;
animationTimer = $timeout(function() {
cancelAnimationReflow = $$animateReflow(function() {
forEach(animationReflowQueue, function(fn) {
fn();
});
@@ -947,11 +1021,11 @@ angular.module('ngAnimate', ['ng'])
animationReflowQueue = [];
animationElementQueue = [];
animationTimer = null;
cancelAnimationReflow = null;
lookupCache = {};
closingAnimationTime = 0;
animationCounter++;
}, 10, false);
});
}
function closeAllAnimations(elements, count) {
@@ -1042,13 +1116,13 @@ angular.module('ngAnimate', ['ng'])
return parentID + '-' + extractElementNode(element).className;
}
function animateSetup(element, className) {
function animateSetup(element, className, calculationDecorator) {
var cacheKey = getCacheKey(element);
var eventCacheKey = cacheKey + ' ' + className;
var stagger = {};
var ii = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
if(ii > 0) {
if(itemIndex > 0) {
var staggerClassName = className + '-stagger';
var staggerCacheKey = cacheKey + ' ' + staggerClassName;
var applyClasses = !lookupCache[staggerCacheKey];
@@ -1060,9 +1134,16 @@ angular.module('ngAnimate', ['ng'])
applyClasses && element.removeClass(staggerClassName);
}
/* the animation itself may need to add/remove special CSS classes
* before calculating the anmation styles */
calculationDecorator = calculationDecorator ||
function(fn) { return fn(); };
element.addClass(className);
var timings = getElementAnimationDetails(element, eventCacheKey);
var timings = calculationDecorator(function() {
return getElementAnimationDetails(element, eventCacheKey);
});
/* there is no point in performing a reflow if the animation
timeout is empty (this would cause a flicker bug normally
@@ -1094,7 +1175,7 @@ angular.module('ngAnimate', ['ng'])
classes : className + ' ' + activeClassName,
timings : timings,
stagger : stagger,
ii : ii
itemIndex : itemIndex
});
return true;
@@ -1139,7 +1220,7 @@ angular.module('ngAnimate', ['ng'])
var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * ONE_SECOND;
var startTime = Date.now();
var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
var ii = elementData.ii;
var itemIndex = elementData.itemIndex;
var style = '', appliedStyles = [];
if(timings.transitionDuration > 0) {
@@ -1152,17 +1233,17 @@ angular.module('ngAnimate', ['ng'])
}
}
if(ii > 0) {
if(itemIndex > 0) {
if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
var delayStyle = timings.transitionDelayStyle;
style += CSS_PREFIX + 'transition-delay: ' +
prepareStaggerDelay(delayStyle, stagger.transitionDelay, ii) + '; ';
prepareStaggerDelay(delayStyle, stagger.transitionDelay, itemIndex) + '; ';
appliedStyles.push(CSS_PREFIX + 'transition-delay');
}
if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
style += CSS_PREFIX + 'animation-delay: ' +
prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, ii) + '; ';
prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, itemIndex) + '; ';
appliedStyles.push(CSS_PREFIX + 'animation-delay');
}
}
@@ -1227,8 +1308,8 @@ angular.module('ngAnimate', ['ng'])
return style;
}
function animateBefore(element, className) {
if(animateSetup(element, className)) {
function animateBefore(element, className, calculationDecorator) {
if(animateSetup(element, className, calculationDecorator)) {
return function(cancelled) {
cancelled && animateClose(element, className);
};
@@ -1323,7 +1404,18 @@ angular.module('ngAnimate', ['ng'])
},
beforeAddClass : function(element, className, animationCompleted) {
var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'));
var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'), function(fn) {
/* when a CSS class is added to an element then the transition style that
* is applied is the transition defined on the element when the CSS class
* is added at the time of the animation. This is how CSS3 functions
* outside of ngAnimate. */
element.addClass(className);
var timings = fn();
element.removeClass(className);
return timings;
});
if(cancellationMethod) {
afterReflow(element, function() {
unblockTransitions(element);
@@ -1340,7 +1432,18 @@ angular.module('ngAnimate', ['ng'])
},
beforeRemoveClass : function(element, className, animationCompleted) {
var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'));
var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'), function(fn) {
/* when classes are removed from an element then the transition style
* that is applied is the transition defined on the element without the
* CSS class being there. This is how CSS3 functions outside of ngAnimate.
* http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */
var klass = element.attr('class');
element.removeClass(className);
var timings = fn();
element.attr('class', klass);
return timings;
});
if(cancellationMethod) {
afterReflow(element, function() {
unblockTransitions(element);
+30 -1
View File
@@ -756,6 +756,36 @@ angular.mock.TzDate = function (offset, timestamp) {
angular.mock.TzDate.prototype = Date.prototype;
/* jshint +W101 */
// TODO(matias): remove this IMMEDIATELY once we can properly detect the
// presence of a registered module
var animateLoaded;
try {
angular.module('ngAnimate');
animateLoaded = true;
} catch(e) {}
if(animateLoaded) {
angular.module('ngAnimate').config(['$provide', function($provide) {
var reflowQueue = [];
$provide.value('$$animateReflow', function(fn) {
reflowQueue.push(fn);
return angular.noop;
});
$provide.decorator('$animate', function($delegate) {
$delegate.triggerReflow = function() {
if(reflowQueue.length === 0) {
throw new Error('No animation reflows present');
}
angular.forEach(reflowQueue, function(fn) {
fn();
});
reflowQueue = [];
};
return $delegate;
});
}]);
}
angular.mock.animate = angular.module('mock.animate', ['ng'])
.config(['$provide', function($provide) {
@@ -1913,7 +1943,6 @@ angular.mock.clearDataCache = function() {
};
if(window.jasmine || window.mocha) {
var currentSpec = null,
+9
View File
@@ -26,6 +26,15 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
*
* @scope
* @priority 400
* @param {string=} onload Expression to evaluate whenever the view updates.
*
* @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll
* $anchorScroll} to scroll the viewport after the view is updated.
*
* - If the attribute is not set, disable scrolling.
* - If the attribute is set without value, enable scrolling.
* - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated
* as an expression yields a truthy value.
* @example
<example module="ngViewExample" deps="angular-route.js" animations="true">
<file name="index.html">
+6
View File
@@ -1439,6 +1439,12 @@ describe('$http', function() {
$http.get('/url');
$httpBackend.flush();
});
it('should have seperate opbjects for defaults PUT and POST', function() {
expect($http.defaults.headers.post).not.toBe($http.defaults.headers.put);
expect($http.defaults.headers.post).not.toBe($http.defaults.headers.patch);
expect($http.defaults.headers.put).not.toBe($http.defaults.headers.patch);
})
});
});
+25
View File
@@ -258,6 +258,31 @@ describe('Scope', function() {
}));
it('should prevent infinite loop when creating and resolving a promise in a watched expression', function() {
module(function($rootScopeProvider) {
$rootScopeProvider.digestTtl(10);
});
inject(function($rootScope, $q) {
var d = $q.defer();
d.resolve('Hello, world.');
$rootScope.$watch(function () {
var $d2 = $q.defer();
$d2.resolve('Goodbye.');
$d2.promise.then(function () { });
return d.promise;
}, function () { return 0; });
expect(function() {
$rootScope.$digest();
}).toThrowMinErr('$rootScope', 'infdig', '10 $digest() iterations reached. Aborting!\n'+
'Watchers fired in the last 5 iterations: []');
expect($rootScope.$$phase).toBeNull();
});
});
it('should not fire upon $watch registration on initial $digest', inject(function($rootScope) {
var log = '';
$rootScope.a = 1;
+343 -108
View File
@@ -280,7 +280,7 @@ describe("ngAnimate", function() {
$rootScope.$digest();
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(child.hasClass('ng-enter')).toBe(true);
expect(child.hasClass('ng-enter-active')).toBe(true);
browserTrigger(element, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
@@ -298,7 +298,7 @@ describe("ngAnimate", function() {
$rootScope.$digest();
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(child.hasClass('ng-leave')).toBe(true);
expect(child.hasClass('ng-leave-active')).toBe(true);
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
@@ -322,7 +322,7 @@ describe("ngAnimate", function() {
$animate.move(child1, element, child2);
$rootScope.$digest();
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
}
expect(element.text()).toBe('21');
}));
@@ -336,7 +336,7 @@ describe("ngAnimate", function() {
expect(child).toBeHidden();
$animate.removeClass(child, 'ng-hide');
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(child.hasClass('ng-hide-remove')).toBe(true);
expect(child.hasClass('ng-hide-remove-active')).toBe(true);
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
@@ -354,7 +354,7 @@ describe("ngAnimate", function() {
expect(child).toBeShown();
$animate.addClass(child, 'ng-hide');
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(child.hasClass('ng-hide-add')).toBe(true);
expect(child.hasClass('ng-hide-add-active')).toBe(true);
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
@@ -374,7 +374,7 @@ describe("ngAnimate", function() {
//enter
$animate.enter(child, element);
$rootScope.$digest();
$timeout.flush();
$animate.triggerReflow();
expect(child.attr('class')).toContain('ng-enter');
expect(child.attr('class')).toContain('ng-enter-active');
@@ -385,7 +385,7 @@ describe("ngAnimate", function() {
element.append(after);
$animate.move(child, element, after);
$rootScope.$digest();
$timeout.flush();
$animate.triggerReflow();
expect(child.attr('class')).toContain('ng-move');
expect(child.attr('class')).toContain('ng-move-active');
@@ -394,14 +394,14 @@ describe("ngAnimate", function() {
//hide
$animate.addClass(child, 'ng-hide');
$timeout.flush();
$animate.triggerReflow();
expect(child.attr('class')).toContain('ng-hide-add');
expect(child.attr('class')).toContain('ng-hide-add-active');
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
//show
$animate.removeClass(child, 'ng-hide');
$timeout.flush();
$animate.triggerReflow();
expect(child.attr('class')).toContain('ng-hide-remove');
expect(child.attr('class')).toContain('ng-hide-remove-active');
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
@@ -409,7 +409,7 @@ describe("ngAnimate", function() {
//leave
$animate.leave(child);
$rootScope.$digest();
$timeout.flush();
$animate.triggerReflow();
expect(child.attr('class')).toContain('ng-leave');
expect(child.attr('class')).toContain('ng-leave-active');
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
@@ -435,7 +435,7 @@ describe("ngAnimate", function() {
element.addClass('ng-hide');
$animate.removeClass(element, 'ng-hide');
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
}
expect(element.text()).toBe('memento');
}));
@@ -455,7 +455,9 @@ describe("ngAnimate", function() {
$animate.leave(child);
$rootScope.$digest();
$timeout.flush();
if($sniffer.transitions) {
$animate.triggerReflow();
}
expect(child).toBeHidden(); //hides instantly
//lets change this to prove that done doesn't fire anymore for the previous hide() operation
@@ -485,7 +487,7 @@ describe("ngAnimate", function() {
$rootScope.$digest();
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
//this is to verify that the existing style is appended with a semicolon automatically
expect(child.attr('style')).toMatch(/width: 20px;.+?/i);
@@ -504,6 +506,7 @@ describe("ngAnimate", function() {
child.addClass('custom-delay ng-hide');
$animate.removeClass(child, 'ng-hide');
if($sniffer.transitions) {
$animate.triggerReflow();
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
}
$timeout.flush(2000);
@@ -530,7 +533,7 @@ describe("ngAnimate", function() {
expect(completed).toBe(false);
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
}
$timeout.flush();
@@ -661,7 +664,7 @@ describe("ngAnimate", function() {
$animate.removeClass(element, 'ng-hide');
if ($sniffer.animations) {
$timeout.flush();
$animate.triggerReflow();
browserTrigger(element,'animationend', { timeStamp: Date.now() + 4000, elapsedTime: 4 });
}
expect(element).toBeShown();
@@ -686,7 +689,7 @@ describe("ngAnimate", function() {
$animate.removeClass(element, 'ng-hide');
if ($sniffer.animations) {
$timeout.flush();
$animate.triggerReflow();
browserTrigger(element,'animationend', { timeStamp: Date.now() + 6000, elapsedTime: 6 });
}
expect(element).toBeShown();
@@ -713,7 +716,7 @@ describe("ngAnimate", function() {
$animate.removeClass(element, 'ng-hide');
if ($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
browserTrigger(element,'animationend', { timeStamp : Date.now() + 20000, elapsedTime: 10 });
}
expect(element).toBeShown();
@@ -751,7 +754,7 @@ describe("ngAnimate", function() {
$animate.removeClass(element, 'ng-hide');
if($sniffer.animations) {
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('ng-hide-remove')).toBe(true);
expect(element.hasClass('ng-hide-remove-active')).toBe(true);
}
@@ -762,7 +765,7 @@ describe("ngAnimate", function() {
if($sniffer.animations) { //cleanup some pending animations
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('ng-hide-add')).toBe(true);
expect(element.hasClass('ng-hide-add-active')).toBe(true);
browserTrigger(element,'animationend', { timeStamp: Date.now() + 2000, elapsedTime: 2 });
@@ -806,7 +809,7 @@ describe("ngAnimate", function() {
};
$rootScope.$digest();
$timeout.flush();
$animate.triggerReflow();
expect(elements[0].attr('style')).toBeFalsy();
expect(elements[1].attr('style')).toMatch(/animation-delay: 0\.1\d*s/);
@@ -823,7 +826,13 @@ describe("ngAnimate", function() {
};
$rootScope.$digest();
$timeout.flush();
var expectFailure = true;
try {
$animate.triggerReflow();
expectFailure = false;
} catch(e) {}
expect(expectFailure).toBe(true);
expect(elements[0].attr('style')).toBeFalsy();
expect(elements[1].attr('style')).not.toMatch(/animation-delay: 0\.1\d*s/);
@@ -859,7 +868,7 @@ describe("ngAnimate", function() {
};
$rootScope.$digest();
$timeout.flush();
$animate.triggerReflow();
expect(elements[0].attr('style')).toBeFalsy();
expect(elements[1].attr('style')).toMatch(/animation-delay: 1\.1\d*s,\s*2\.1\d*s/);
@@ -896,7 +905,7 @@ describe("ngAnimate", function() {
$animate.removeClass(element, 'ng-hide');
if ($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
}
expect(element).toBeShown();
@@ -920,7 +929,7 @@ describe("ngAnimate", function() {
$animate.removeClass(element, 'ng-hide');
if ($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
var now = Date.now();
browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 });
browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 });
@@ -948,7 +957,6 @@ describe("ngAnimate", function() {
element.addClass('ng-hide');
$animate.removeClass(element, 'ng-hide');
$timeout.flush(0);
expect(element).toBeShown();
$animate.enabled(true);
@@ -957,7 +965,7 @@ describe("ngAnimate", function() {
$animate.removeClass(element, 'ng-hide');
if ($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
var now = Date.now();
browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 });
browserTrigger(element,'transitionend', { timeStamp: now + 3000, elapsedTime: 3 });
@@ -985,7 +993,7 @@ describe("ngAnimate", function() {
$animate.removeClass(element, 'ng-hide');
$timeout.flush();
$animate.triggerReflow();
var now = Date.now();
browserTrigger(element,'transitionend', { timeStamp: now + 1000, elapsedTime: 1 });
@@ -1014,7 +1022,7 @@ describe("ngAnimate", function() {
$animate.removeClass(element, 'ng-hide');
if ($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
}
expect(element).toBeShown();
if ($sniffer.transitions) {
@@ -1039,7 +1047,7 @@ describe("ngAnimate", function() {
$animate.removeClass(element, 'ng-hide');
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('ng-hide-remove')).toBe(true);
expect(element.hasClass('ng-hide-remove-active')).toBe(true);
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
@@ -1051,7 +1059,7 @@ describe("ngAnimate", function() {
$animate.addClass(element, 'ng-hide');
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('ng-hide-add')).toBe(true);
expect(element.hasClass('ng-hide-add-active')).toBe(true);
}
@@ -1092,7 +1100,7 @@ describe("ngAnimate", function() {
};
$rootScope.$digest();
$timeout.flush();
$animate.triggerReflow();
expect(elements[0].attr('style')).toBeFalsy();
expect(elements[1].attr('style')).toMatch(/transition-delay: 0\.1\d*s/);
@@ -1109,7 +1117,14 @@ describe("ngAnimate", function() {
};
$rootScope.$digest();
$timeout.flush();
var expectFailure = true;
try {
$animate.triggerReflow();
expectFailure = false;
} catch(e) {}
expect(expectFailure).toBe(true);
expect(elements[0].attr('style')).toBeFalsy();
expect(elements[1].attr('style')).not.toMatch(/transition-delay: 0\.1\d*s/);
@@ -1145,7 +1160,7 @@ describe("ngAnimate", function() {
};
$rootScope.$digest();
$timeout.flush();
$animate.triggerReflow();
expect(elements[0].attr('style')).toMatch(/transition-duration: 1\d*s,\s*3\d*s;/);
expect(elements[0].attr('style')).not.toContain('transition-delay');
@@ -1155,7 +1170,7 @@ describe("ngAnimate", function() {
}));
it("apply a closing timeout to close all pending transitions",
it("should apply a closing timeout to close all pending transitions",
inject(function($animate, $rootScope, $compile, $sniffer, $timeout) {
if (!$sniffer.transitions) return;
@@ -1167,13 +1182,55 @@ describe("ngAnimate", function() {
$animate.addClass(element, 'some-class');
$timeout.flush(10); //reflow
$animate.triggerReflow(); //reflow
expect(element.hasClass('some-class-add-active')).toBe(true);
$timeout.flush(7500); //closing timeout
expect(element.hasClass('some-class-add-active')).toBe(false);
}));
it("apply a closing timeout with respect to a staggering animation",
inject(function($animate, $rootScope, $compile, $sniffer, $timeout) {
if (!$sniffer.transitions) return;
ss.addRule('.entering-element.ng-enter',
'-webkit-transition:5s linear all;' +
'transition:5s linear all;');
ss.addRule('.entering-element.ng-enter-stagger',
'-webkit-transition-delay:0.5s;' +
'transition-delay:0.5s;');
element = $compile(html('<div></div>'))($rootScope);
var kids = [];
for(var i = 0; i < 5; i++) {
kids.push(angular.element('<div class="entering-element"></div>'));
$animate.enter(kids[i], element);
}
$rootScope.$digest();
$animate.triggerReflow(); //reflow
expect(element.children().length).toBe(5);
for(var i = 0; i < 5; i++) {
expect(kids[i].hasClass('ng-enter-active')).toBe(true);
}
$timeout.flush(7500);
for(var i = 0; i < 5; i++) {
expect(kids[i].hasClass('ng-enter-active')).toBe(true);
}
//(stagger * index) + (duration + delay) * 150%
$timeout.flush(9500); //0.5 * 4 + 5 * 1.5 = 9500;
for(var i = 0; i < 5; i++) {
expect(kids[i].hasClass('ng-enter-active')).toBe(false);
}
}));
it("should not allow the closing animation to close off a successive animation midway",
inject(function($animate, $rootScope, $compile, $sniffer, $timeout) {
@@ -1189,12 +1246,12 @@ describe("ngAnimate", function() {
$animate.addClass(element, 'some-class');
$timeout.flush(10); //reflow
$animate.triggerReflow(); //reflow
expect(element.hasClass('some-class-add-active')).toBe(true);
$animate.removeClass(element, 'some-class');
$timeout.flush(10); //second reflow
$animate.triggerReflow(); //second reflow
$timeout.flush(7500); //closing timeout for the first animation
expect(element.hasClass('some-class-remove-active')).toBe(true);
@@ -1237,7 +1294,7 @@ describe("ngAnimate", function() {
};
$rootScope.$digest();
$timeout.flush();
$animate.triggerReflow();
expect(elements[0].attr('style')).toBeFalsy();
@@ -1275,7 +1332,7 @@ describe("ngAnimate", function() {
$rootScope.$digest();
if ($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('abc')).toBe(true);
expect(element.hasClass('ng-enter')).toBe(true);
expect(element.hasClass('ng-enter-active')).toBe(true);
@@ -1289,7 +1346,7 @@ describe("ngAnimate", function() {
$rootScope.$digest();
if ($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('xyz')).toBe(true);
expect(element.hasClass('ng-enter')).toBe(true);
expect(element.hasClass('ng-enter-active')).toBe(true);
@@ -1317,7 +1374,7 @@ describe("ngAnimate", function() {
$rootScope.$digest();
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('one')).toBe(true);
expect(element.hasClass('two')).toBe(true);
expect(element.hasClass('ng-enter')).toBe(true);
@@ -1439,6 +1496,68 @@ describe("ngAnimate", function() {
expect(signature).toBe('AB');
}));
it('should fire DOM callbacks on the element being animated',
inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) {
if(!$sniffer.transitions) return;
$animate.enabled(true);
ss.addRule('.klass-add', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');
var element = jqLite('<div></div>');
$rootElement.append(element);
body.append($rootElement);
var steps = [];
element.on('$animate:before', function(e, data) {
steps.push(['before', data.className, data.event]);
});
element.on('$animate:after', function(e, data) {
steps.push(['after', data.className, data.event]);
});
$animate.addClass(element, 'klass');
$timeout.flush(1);
expect(steps.pop()).toEqual(['before', 'klass', 'addClass']);
$animate.triggerReflow();
$timeout.flush(1);
expect(steps.pop()).toEqual(['after', 'klass', 'addClass']);
}));
it('should fire the DOM callbacks even if no animation is rendered',
inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) {
$animate.enabled(true);
var parent = jqLite('<div></div>');
var element = jqLite('<div></div>');
$rootElement.append(parent);
body.append($rootElement);
var steps = [];
element.on('$animate:before', function(e, data) {
steps.push(['before', data.className, data.event]);
});
element.on('$animate:after', function(e, data) {
steps.push(['after', data.className, data.event]);
});
$animate.enter(element, parent);
$rootScope.$digest();
$timeout.flush(1);
expect(steps.shift()).toEqual(['before', 'ng-enter', 'enter']);
expect(steps.shift()).toEqual(['after', 'ng-enter', 'enter']);
}));
it("should fire a done callback when provided with no animation",
inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) {
@@ -1525,6 +1644,9 @@ describe("ngAnimate", function() {
});
$animate.addClass(element, 'ng-hide'); //earlier animation cancelled
if($sniffer.transitions) {
$animate.triggerReflow();
}
$timeout.flush();
expect(signature).toBe('AB');
}));
@@ -1655,7 +1777,7 @@ describe("ngAnimate", function() {
if($sniffer.transitions) {
expect(element.hasClass('klass-add')).toBe(true);
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('klass')).toBe(true);
expect(element.hasClass('klass-add-active')).toBe(true);
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000, elapsedTime: 3 });
@@ -1670,7 +1792,7 @@ describe("ngAnimate", function() {
if($sniffer.transitions) {
expect(element.hasClass('klass-remove')).toBe(true);
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('klass')).toBe(false);
expect(element.hasClass('klass-add')).toBe(false);
expect(element.hasClass('klass-add-active')).toBe(false);
@@ -1734,7 +1856,7 @@ describe("ngAnimate", function() {
});
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('klass-add')).toBe(true);
expect(element.hasClass('klass-add-active')).toBe(true);
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000, elapsedTime: 11 });
@@ -1750,7 +1872,7 @@ describe("ngAnimate", function() {
});
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('klass-remove')).toBe(true);
expect(element.hasClass('klass-remove-active')).toBe(true);
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000, elapsedTime: 11 });
@@ -1784,7 +1906,7 @@ describe("ngAnimate", function() {
});
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('one-add')).toBe(true);
expect(element.hasClass('two-add')).toBe(true);
@@ -1830,7 +1952,7 @@ describe("ngAnimate", function() {
});
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('one-remove')).toBe(true);
expect(element.hasClass('two-remove')).toBe(true);
@@ -1884,7 +2006,7 @@ describe("ngAnimate", function() {
$rootScope.$digest();
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(child.hasClass('ng-enter')).toBe(true);
expect(child.hasClass('ng-enter-active')).toBe(true);
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
@@ -1908,7 +2030,7 @@ describe("ngAnimate", function() {
$rootScope.$digest();
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(child.hasClass('ng-enter')).toBe(true);
expect(child.hasClass('ng-enter-active')).toBe(true);
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 9000, elapsedTime: 9 });
@@ -1918,41 +2040,6 @@ describe("ngAnimate", function() {
}));
it("should not set the transition property flag if only CSS animations are used",
inject(function($compile, $rootScope, $animate, $sniffer, $timeout) {
if (!$sniffer.animations) return;
ss.addRule('.sleek-animation.ng-enter', '-webkit-animation: my_animation 2s linear;' +
'animation: my_animation 2s linear');
ss.addRule('.trans.ng-enter', '-webkit-transition:1s linear all;' +
'transition:1s linear all');
var propertyKey = ($sniffer.vendorPrefix == 'Webkit' ? '-webkit-' : '') + 'transition-property';
var element = html($compile('<div>...</div>')($rootScope));
var child = $compile('<div class="skeep-animation">...</div>')($rootScope);
child.css(propertyKey,'background-color');
$animate.enter(child, element);
$rootScope.$digest();
$timeout.flush();
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 2000, elapsedTime: 2 });
expect(child.css(propertyKey)).toBe('background-color');
child.remove();
child = $compile('<div class="sleek-animation">...</div>')($rootScope);
child.attr('class','trans');
$animate.enter(child, element);
$rootScope.$digest();
expect(child.css(propertyKey)).not.toBe('background-color');
}));
it("should skip animations if the browser does not support CSS3 transitions and CSS3 animations",
inject(function($compile, $rootScope, $animate, $sniffer) {
@@ -1997,9 +2084,8 @@ describe("ngAnimate", function() {
$animate.enter(child, element);
$rootScope.$digest();
$timeout.flush(10);
if($sniffer.transitions) {
$animate.triggerReflow();
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
}
@@ -2033,7 +2119,7 @@ describe("ngAnimate", function() {
//this is added/removed right away otherwise
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
expect(child.hasClass('ng-enter')).toBe(true);
expect(child.hasClass('ng-enter-active')).toBe(true);
}
@@ -2073,7 +2159,7 @@ describe("ngAnimate", function() {
$animate.leave(child);
$rootScope.$digest();
$timeout.flush();
$animate.triggerReflow();
expect(child.hasClass('ng-enter-active')).toBe(false);
});
});
@@ -2268,7 +2354,7 @@ describe("ngAnimate", function() {
var empty = true;
try {
$timeout.flush();
$animate.triggerReflow();
empty = false;
}
catch(e) {}
@@ -2294,7 +2380,7 @@ describe("ngAnimate", function() {
$animate.enter(child, element);
$rootScope.$digest();
$timeout.flush();
$animate.triggerReflow();
expect(child.hasClass('ng-enter')).toBe(true);
expect(child.hasClass('ng-enter-active')).toBe(true);
@@ -2354,7 +2440,7 @@ describe("ngAnimate", function() {
expect(animationState).toBe('enter');
if($sniffer.transitions) {
expect(child.hasClass('ng-enter')).toBe(true);
$timeout.flush();
$animate.triggerReflow();
expect(child.hasClass('ng-enter-active')).toBe(true);
}
@@ -2371,7 +2457,7 @@ describe("ngAnimate", function() {
$animate.addClass(child, 'something');
if($sniffer.transitions) {
$timeout.flush();
$animate.triggerReflow();
}
expect(animationState).toBe('addClass');
if($sniffer.transitions) {
@@ -2390,7 +2476,7 @@ describe("ngAnimate", function() {
it("should wait until a queue of animations are complete before performing a reflow",
inject(function($rootScope, $compile, $timeout,$sniffer) {
inject(function($rootScope, $compile, $timeout, $sniffer, $animate) {
if(!$sniffer.transitions) return;
@@ -2402,7 +2488,7 @@ describe("ngAnimate", function() {
$rootScope.$digest();
expect(element[0].querySelectorAll('.ng-enter-active').length).toBe(0);
$timeout.flush();
$animate.triggerReflow();
expect(element[0].querySelectorAll('.ng-enter-active').length).toBe(5);
forEach(element.children(), function(kid) {
@@ -2463,7 +2549,7 @@ describe("ngAnimate", function() {
});
it("should disable all child animations on structural animations until the first reflow has passed", function() {
it("should disable all child animations on structural animations until the post animation timeout has passed", function() {
var intercepted;
module(function($animateProvider) {
$animateProvider.register('.animated', function() {
@@ -2577,7 +2663,6 @@ describe("ngAnimate", function() {
$animate.enter(kid, element);
}
$rootScope.$digest();
$timeout.flush();
//called three times since the classname is the same
expect(count).toBe(2);
@@ -2591,7 +2676,6 @@ describe("ngAnimate", function() {
}
$rootScope.$digest();
$timeout.flush();
expect(count).toBe(20);
});
@@ -2659,12 +2743,51 @@ describe("ngAnimate", function() {
expect(element.hasClass('green')).toBe(false);
expect(element.hasClass('red')).toBe(false);
$timeout.flush();
$animate.triggerReflow();
expect(element.hasClass('green')).toBe(true);
expect(element.hasClass('red')).toBe(true);
}));
it("should avoid mixing up substring classes during add and remove operations", function() {
var currentAnimation, currentFn;
module(function($animateProvider) {
$animateProvider.register('.on', function() {
return {
beforeAddClass : function(element, className, done) {
currentAnimation = 'addClass';
currentFn = done;
return function(cancelled) {
currentAnimation = cancelled ? null : currentAnimation;
}
},
beforeRemoveClass : function(element, className, done) {
currentAnimation = 'removeClass';
currentFn = done;
return function(cancelled) {
currentAnimation = cancelled ? null : currentAnimation;
}
}
};
});
});
inject(function($compile, $rootScope, $animate, $sniffer, $timeout) {
var element = $compile('<div class="animation-enabled only"></div>')($rootScope);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$animate.addClass(element, 'on');
expect(currentAnimation).toBe('addClass');
currentFn();
currentAnimation = null;
$animate.removeClass(element, 'on');
$animate.addClass(element, 'on');
expect(currentAnimation).toBe(null);
});
});
it('should enable and disable animations properly on the root element', function() {
var count = 0;
@@ -2770,17 +2893,17 @@ describe("ngAnimate", function() {
$animate.removeClass(element, 'base-class one two');
//still true since we're before the reflow
expect(element.hasClass('base-class')).toBe(true);
expect(element.hasClass('base-class')).toBe(false);
//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);
expect(element.hasClass('base-class')).toBe(false);
//the reflow...
$timeout.flush();
$animate.triggerReflow();
//the reflow DOM operation was commenced but it ran before so it
//shouldn't run agaun
@@ -2814,9 +2937,10 @@ describe("ngAnimate", function() {
node._setAttribute(prop, val);
};
expect(capturedProperty).toBe('none');
$animate.addClass(element, 'trigger-class');
$timeout.flush();
$animate.triggerReflow();
expect(capturedProperty).not.toBe('none');
}));
@@ -2843,7 +2967,7 @@ describe("ngAnimate", function() {
expect(node.style[animationKey]).toContain('none');
$timeout.flush();
$animate.triggerReflow();
expect(node.style[animationKey]).not.toContain('none');
}));
@@ -2888,7 +3012,7 @@ describe("ngAnimate", function() {
expect(element[0].style[prop]).toContain('none');
expect($window.getComputedStyle(element[0])[prop + 'Duration']).toBe('0s');
$timeout.flush();
$animate.triggerReflow();
});
});
@@ -2907,7 +3031,7 @@ describe("ngAnimate", function() {
$animate.leave(element);
$rootScope.$digest();
$timeout.flush();
$animate.triggerReflow();
browserTrigger(element, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 0.50999999991 });
@@ -2931,7 +3055,7 @@ describe("ngAnimate", function() {
}
});
});
inject(function($rootScope, $compile, $rootElement, $document, $timeout, $templateCache, $sniffer) {
inject(function($rootScope, $compile, $rootElement, $document, $timeout, $templateCache, $sniffer, $animate) {
if(!$sniffer.transitions) return;
$templateCache.put('item-template', 'item: #{{ item }} ');
@@ -2950,7 +3074,7 @@ describe("ngAnimate", function() {
$rootScope.tpl = 'item-template';
$rootScope.items = [1,2,3];
$rootScope.$digest();
$timeout.flush();
$animate.triggerReflow();
expect(capturedAnimation).toBe('enter');
expect(element.text()).toContain('item: #1');
@@ -2962,7 +3086,7 @@ describe("ngAnimate", function() {
$rootScope.items = [];
$rootScope.$digest();
$timeout.flush();
$animate.triggerReflow();
expect(capturedAnimation).toBe('leave');
});
@@ -3017,5 +3141,116 @@ describe("ngAnimate", function() {
expect(leaveDone).toBe(true);
});
});
it('should respect the most relevant CSS transition property if defined in multiple classes',
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;');
ss.addRule('.base-class.on', '-webkit-transition:5s linear all;' +
'transition:5s linear all;');
$animate.enabled(true);
var element = $compile('<div class="base-class"></div>')($rootScope);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
var ready = false;
$animate.addClass(element, 'on', function() {
ready = true;
});
$animate.triggerReflow();
browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 1 });
$timeout.flush(1);
expect(ready).toBe(false);
browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 5 });
$timeout.flush(1);
expect(ready).toBe(true);
ready = false;
$animate.removeClass(element, 'on', function() {
ready = true;
});
$animate.triggerReflow();
browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 1 });
$timeout.flush(1);
expect(ready).toBe(true);
}));
it('should not apply a transition upon removal of a class that has a transition',
inject(function($sniffer, $compile, $rootScope, $rootElement, $animate, $timeout) {
if (!$sniffer.transitions) return;
ss.addRule('.base-class.on', '-webkit-transition:5s linear all;' +
'transition:5s linear all;');
$animate.enabled(true);
var element = $compile('<div class="base-class on"></div>')($rootScope);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
var ready = false;
$animate.removeClass(element, 'on', function() {
ready = true;
});
$timeout.flush(1);
expect(ready).toBe(true);
}));
it('should avoid skip animations if the same CSS class is added / removed synchronously before the reflow kicks in',
inject(function($sniffer, $compile, $rootScope, $rootElement, $animate, $timeout) {
if (!$sniffer.transitions) return;
ss.addRule('.water-class', '-webkit-transition:2s linear all;' +
'transition:2s linear all;');
$animate.enabled(true);
var element = $compile('<div class="water-class on"></div>')($rootScope);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
var signature = '';
$animate.removeClass(element, 'on', function() {
signature += 'A';
});
$animate.addClass(element, 'on', function() {
signature += 'B';
});
$timeout.flush(1);
expect(signature).toBe('AB');
signature = '';
$animate.removeClass(element, 'on', function() {
signature += 'A';
});
$animate.addClass(element, 'on', function() {
signature += 'B';
});
$animate.removeClass(element, 'on', function() {
signature += 'C';
});
$timeout.flush(1);
expect(signature).toBe('AB');
$animate.triggerReflow();
browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2000 });
$timeout.flush(1);
expect(signature).toBe('ABC');
}));
});
});