aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatias Niemelä2013-06-18 13:59:57 -0400
committerMisko Hevery2013-07-26 23:49:54 -0700
commit81923f1e41560327f7de6e8fddfda0d2612658f3 (patch)
treebbf8151bddf4d026f8f5fa3196b84a45ecd9c858
parent11521a4cde689c2bd6aaa227b1f45cb3fb53725b (diff)
downloadangular.js-81923f1e41560327f7de6e8fddfda0d2612658f3.tar.bz2
feat(ngAnimate): complete rewrite of animations
- ngAnimate directive is gone and was replaced with class based animations/transitions - support for triggering animations on css class additions and removals - done callback was added to all animation apis - $animation and $animator where merged into a single $animate service with api: - $animate.enter(element, parent, after, done); - $animate.leave(element, done); - $animate.move(element, parent, after, done); - $animate.addClass(element, className, done); - $animate.removeClass(element, className, done); BREAKING CHANGE: too many things changed, we'll write up a separate doc with migration instructions
-rw-r--r--Gruntfile.js5
-rwxr-xr-xangularFiles.js5
-rw-r--r--css/angular.css4
-rw-r--r--docs/component-spec/annotationsSpec.js39
-rw-r--r--docs/components/angular-bootstrap/bootstrap-prettify.js5
-rw-r--r--docs/components/angular-bootstrap/bootstrap.js9
-rw-r--r--docs/src/example.js2
-rw-r--r--docs/src/ngdoc.js67
-rw-r--r--docs/src/templates/css/animations.css43
-rw-r--r--docs/src/templates/index.html13
-rw-r--r--docs/src/templates/js/docs.js2
-rw-r--r--karma-docs.conf.js1
-rw-r--r--src/Angular.js6
-rwxr-xr-xsrc/AngularPublic.js3
-rw-r--r--src/loader.js28
-rw-r--r--src/ng/animate.js112
-rw-r--r--src/ng/animation.js61
-rw-r--r--src/ng/animator.js446
-rw-r--r--src/ng/directive/ngClass.js118
-rwxr-xr-xsrc/ng/directive/ngIf.js27
-rw-r--r--src/ng/directive/ngInclude.js14
-rw-r--r--src/ng/directive/ngRepeat.js34
-rw-r--r--src/ng/directive/ngShowHide.js64
-rw-r--r--src/ng/directive/ngSwitch.js26
-rw-r--r--src/ngAnimate/animate.js714
-rw-r--r--src/ngMock/angular-mocks.js37
-rw-r--r--src/ngRoute/directive/ngView.js22
-rw-r--r--test/matchers.js12
-rw-r--r--test/ng/animateSpec.js53
-rw-r--r--test/ng/animationSpec.js15
-rw-r--r--test/ng/animatorSpec.js773
-rwxr-xr-xtest/ng/compileSpec.js12
-rwxr-xr-xtest/ng/directive/ngIfSpec.js86
-rw-r--r--test/ng/directive/ngIncludeSpec.js98
-rw-r--r--test/ng/directive/ngRepeatSpec.js216
-rw-r--r--test/ng/directive/ngShowHideSpec.js159
-rw-r--r--test/ng/directive/ngSwitchSpec.js112
-rw-r--r--test/ngAnimate/animateSpec.js1524
-rw-r--r--test/ngRoute/directive/ngViewSpec.js183
-rw-r--r--test/testabilityPatch.js2
40 files changed, 2961 insertions, 2191 deletions
diff --git a/Gruntfile.js b/Gruntfile.js
index 264fe874..6d0395fb 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -160,6 +160,10 @@ module.exports = function(grunt) {
dest: 'build/angular-resource.js',
src: util.wrap(['src/ngResource/resource.js'], 'module')
},
+ animate: {
+ dest: 'build/angular-animate.js',
+ src: util.wrap(['src/ngAnimate/animate.js'], 'module')
+ },
route: {
dest: 'build/angular-route.js',
src: util.wrap([
@@ -178,6 +182,7 @@ module.exports = function(grunt) {
min: {
angular: 'build/angular.js',
+ animate: 'build/angular-animate.js',
cookies: 'build/angular-cookies.js',
loader: 'build/angular-loader.js',
mobile: 'build/angular-mobile.js',
diff --git a/angularFiles.js b/angularFiles.js
index dc98bcc1..c06f8adc 100755
--- a/angularFiles.js
+++ b/angularFiles.js
@@ -10,8 +10,7 @@ angularFiles = {
'src/auto/injector.js',
'src/ng/anchorScroll.js',
- 'src/ng/animation.js',
- 'src/ng/animator.js',
+ 'src/ng/animate.js',
'src/ng/browser.js',
'src/ng/cacheFactory.js',
'src/ng/compile.js',
@@ -66,6 +65,7 @@ angularFiles = {
],
'angularSrcModules': [
+ 'src/ngAnimate/animate.js',
'src/ngCookies/cookies.js',
'src/ngResource/resource.js',
'src/ngRoute/routeUtils.js',
@@ -107,6 +107,7 @@ angularFiles = {
'test/*.js',
'test/auto/*.js',
'test/ng/**/*.js',
+ 'test/ngAnimate/*.js',
'test/ngCookies/*.js',
'test/ngResource/*.js',
'test/ngRoute/**/*.js',
diff --git a/css/angular.css b/css/angular.css
index 0cd9d7dc..6833234f 100644
--- a/css/angular.css
+++ b/css/angular.css
@@ -8,3 +8,7 @@
ng\:form {
display: block;
}
+
+.ng-hide {
+ display: none;
+}
diff --git a/docs/component-spec/annotationsSpec.js b/docs/component-spec/annotationsSpec.js
index 321118ad..a17c906c 100644
--- a/docs/component-spec/annotationsSpec.js
+++ b/docs/component-spec/annotationsSpec.js
@@ -67,41 +67,33 @@ describe('Docs Annotations', function() {
var $scope, parent, element, url, window;
beforeEach(function() {
- module(function($provide, $animationProvider) {
+ module(function($provide, $animateProvider) {
$provide.value('$window', window = angular.mock.createMockWindow());
- $animationProvider.register('foldout-enter', function($window) {
+ $animateProvider.register('.foldout', function($window) {
return {
- start : function(element, done) {
+ enter : function(element, done) {
$window.setTimeout(done, 1000);
- }
- }
- });
- $animationProvider.register('foldout-hide', function($window) {
- return {
- start : function(element, done) {
+ },
+ show : function(element, done) {
$window.setTimeout(done, 500);
- }
- }
- });
- $animationProvider.register('foldout-show', function($window) {
- return {
- start : function(element, done) {
+ },
+ hide : function(element, done) {
$window.setTimeout(done, 200);
}
}
});
});
- inject(function($rootScope, $compile, $templateCache, $rootElement, $animator) {
- $animator.enabled(true);
+ inject(function($rootScope, $compile, $templateCache, $rootElement, $animate) {
+ $animate.enabled(true);
url = '/page.html';
$scope = $rootScope.$new();
parent = angular.element('<div class="parent"></div>');
- element = angular.element('<div data-url="' + url + '" foldout></div>');
//we're injecting the element to the $rootElement since the changes in
- //$animator only detect and perform animations if the root element has
+ //$animate only detect and perform animations if the root element has
//animations enabled. If the element is not apart of the DOM
//then animations are skipped.
+ element = angular.element('<div data-url="' + url + '" class="foldout" foldout></div>');
parent.append(element);
$rootElement.append(parent);
body.append($rootElement);
@@ -142,16 +134,19 @@ describe('Docs Annotations', function() {
$httpBackend.flush();
window.setTimeout.expect(1).process();
window.setTimeout.expect(1000).process();
+ window.setTimeout.expect(0).process();
//hide
element.triggerHandler('click');
window.setTimeout.expect(1).process();
- window.setTimeout.expect(500).process();
+ window.setTimeout.expect(200).process();
+ window.setTimeout.expect(0).process();
//show
element.triggerHandler('click');
window.setTimeout.expect(1).process();
- window.setTimeout.expect(200).process();
+ window.setTimeout.expect(500).process();
+ window.setTimeout.expect(0).process();
}));
});
@@ -160,7 +155,7 @@ describe('Docs Annotations', function() {
var window, $scope, ctrl;
beforeEach(function() {
- module(function($provide, $animationProvider) {
+ module(function($provide, $animateProvider) {
$provide.value('$window', window = angular.mock.createMockWindow());
});
inject(function($rootScope, $controller, $location, $cookies, sections) {
diff --git a/docs/components/angular-bootstrap/bootstrap-prettify.js b/docs/components/angular-bootstrap/bootstrap-prettify.js
index ad5340e5..fa40c6e7 100644
--- a/docs/components/angular-bootstrap/bootstrap-prettify.js
+++ b/docs/components/angular-bootstrap/bootstrap-prettify.js
@@ -183,8 +183,8 @@ directive.ngEvalJavascript = ['getEmbeddedTemplate', function(getEmbeddedTemplat
}];
-directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location', '$sniffer',
- function($templateCache, $browser, docsRootScope, $location, $sniffer) {
+directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location', '$sniffer', '$animate',
+ function($templateCache, $browser, docsRootScope, $location, $sniffer, $animate) {
return {
terminal: true,
link: function(scope, element, attrs) {
@@ -193,6 +193,7 @@ directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location',
deregisterEmbedRootScope;
modules.push(['$provide', function($provide) {
+ $provide.value('$animate', $animate);
$provide.value('$templateCache', $templateCache);
$provide.value('$anchorScroll', angular.noop);
$provide.value('$browser', $browser);
diff --git a/docs/components/angular-bootstrap/bootstrap.js b/docs/components/angular-bootstrap/bootstrap.js
index 170e8805..20a5741d 100644
--- a/docs/components/angular-bootstrap/bootstrap.js
+++ b/docs/components/angular-bootstrap/bootstrap.js
@@ -335,12 +335,11 @@ directive.tabPane = function() {
};
};
-directive.foldout = ['$http', '$animator','$window', function($http, $animator, $window) {
+directive.foldout = ['$http', '$animate','$window', function($http, $animate, $window) {
return {
restrict: 'A',
priority : 500,
link: function(scope, element, attrs) {
- var animator = $animator(scope, { ngAnimate: "'foldout'" });
var container, loading, url = attrs.url;
if(/\/build\//.test($window.location.href)) {
url = '/build/docs' + url;
@@ -353,7 +352,7 @@ directive.foldout = ['$http', '$animator','$window', function($http, $animator,
loading = true;
var par = element.parent();
container = angular.element('<div class="foldout">loading...</div>');
- animator.enter(container, null, par);
+ $animate.enter(container, null, par);
$http.get(url, { cache : true }).success(function(html) {
loading = false;
@@ -367,12 +366,12 @@ directive.foldout = ['$http', '$animator','$window', function($http, $animator,
//avoid showing the element if the user has already closed it
if(container.css('display') == 'block') {
container.css('display','none');
- animator.show(container);
+ $animate.show(container);
}
});
}
else {
- container.css('display') == 'none' ? animator.show(container) : animator.hide(container);
+ container.hasClass('ng-hide') ? $animate.show(container) : $animate.hide(container);
}
});
});
diff --git a/docs/src/example.js b/docs/src/example.js
index cdbc24a7..9471b3fa 100644
--- a/docs/src/example.js
+++ b/docs/src/example.js
@@ -134,7 +134,7 @@ exports.Example.prototype.toHtmlTabs = function() {
exports.Example.prototype.toHtmlEmbed = function() {
var out = [];
- out.push('<div class="well doc-example-live animator-container"');
+ out.push('<div class="well doc-example-live animate-container"');
if(this.animations) {
out.push(" ng-class=\"{'animations-off':animationsOff == true}\"");
}
diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js
index 394510c5..eb26bbf2 100644
--- a/docs/src/ngdoc.js
+++ b/docs/src/ngdoc.js
@@ -494,6 +494,19 @@ Doc.prototype = {
html_usage_parameters: function(dom) {
var self = this;
var params = this.param ? this.param : [];
+ if(this.animations) {
+ dom.h('Animations', this.animations, function(animations){
+ dom.html('<ul>');
+ var animations = animations.split("\n");
+ animations.forEach(function(ani) {
+ dom.html('<li>');
+ dom.text(ani);
+ dom.html('</li>');
+ });
+ dom.html('</ul>');
+ });
+ dom.html('<a href="api/ngAnimate.$animate">Click here</a> to learn more about the steps involved in the animation.');
+ }
if(params.length > 0) {
dom.html('<h2 id="parameters">Parameters</h2>');
dom.html('<table class="variables-matrix table table-bordered table-striped">');
@@ -538,18 +551,6 @@ Doc.prototype = {
dom.html('</tbody>');
dom.html('</table>');
}
- if(this.animations) {
- dom.h('Animations', this.animations, function(animations){
- dom.html('<ul>');
- var animations = animations.split("\n");
- animations.forEach(function(ani) {
- dom.html('<li>');
- dom.text(ani);
- dom.html('</li>');
- });
- dom.html('</ul>');
- });
- }
},
html_usage_returns: function(dom) {
@@ -665,48 +666,6 @@ Doc.prototype = {
dom.text('</' + element + '>');
});
}
- if(self.animations) {
- var animations = [], matches = self.animations.split("\n");
- matches.forEach(function(ani) {
- var name = ani.match(/^\s*(.+?)\s*-/)[1];
- animations.push(name);
- });
-
- dom.html('with <span id="animations">animations</span>');
- var comment;
- if(animations.length == 1) {
- comment = 'The ' + animations[0] + ' animation is supported';
- }
- else {
- var rhs = animations[animations.length-1];
- var lhs = '';
- for(var i=0;i<animations.length-1;i++) {
- if(i>0) {
- lhs += ', ';
- }
- lhs += animations[i];
- }
- comment = 'The ' + lhs + ' and ' + rhs + ' animations are supported';
- }
- var element = self.element || 'ANY';
- dom.code(function() {
- dom.text('//' + comment + "\n");
- dom.text('<' + element + ' ');
- dom.text(dashCase(self.shortName));
- renderParams('\n ', '="', '"', true);
- dom.text(' ng-animate="{');
- animations.forEach(function(ani, index) {
- if (index) {
- dom.text(', ');
- }
- dom.text(ani + ': \'' + ani + '-animation\'');
- });
- dom.text('}">\n ...\n');
- dom.text('</' + element + '>');
- });
-
- dom.html('<a href="api/ng.$animator#Methods">Click here</a> to learn more about the steps involved in the animation.');
- }
}
self.html_usage_directiveInfo(dom);
self.html_usage_parameters(dom);
diff --git a/docs/src/templates/css/animations.css b/docs/src/templates/css/animations.css
index 2d54bbfb..7324a8a1 100644
--- a/docs/src/templates/css/animations.css
+++ b/docs/src/templates/css/animations.css
@@ -1,4 +1,4 @@
-.reveal {
+.reveal.ng-enter {
-webkit-transition:1s linear all;
-moz-transition:1s linear all;
-o-transition:1s linear all;
@@ -6,7 +6,7 @@
opacity:0;
}
-.reveal.reveal-active {
+.reveal.ng-enter.ng-enter-active {
opacity:1;
}
@@ -15,48 +15,45 @@
overflow:hidden;
}
-.slide-reveal {
+.slide-reveal > .ng-enter {
-webkit-transition:0.5s linear all;
-moz-transition:0.5s linear all;
-o-transition:0.5s linear all;
transition:0.5s linear all;
- opacity:0.5;
+ opacity:0.5;
position:relative;
opacity:0;
top:10px;
}
-.slide-reveal.slide-reveal-active {
+.slide-reveal > .ng-enter.ng-enter-active {
top:0;
opacity:1;
}
-.expand-enter {
+.expand.ng-enter,
+.expand.ng-leave {
-webkit-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
-moz-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
-o-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
-
+}
+.expand.ng-enter {
opacity:0;
line-height:0;
height:0!important;
}
-.expand-enter.expand-enter-active {
+.expand.ng-enter.expand.ng-enter-active {
opacity:1;
line-height:20px;
height:20px!important;
}
-.expand-leave {
- -webkit-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
- -moz-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
- -o-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
- transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
-
+.expand.ng-leave {
opacity:1;
height:20px;
}
-.expand-leave.expand-leave-active {
+.expand.ng-leave.expand.ng-leave-active {
opacity:0;
height:0;
}
@@ -73,32 +70,36 @@
padding:1em;
}
-.animator-container.animations-off * {
+.animate-container.animations-off * {
-webkit-transition: none;
-moz-transition: none;
-o-transition: color 0 ease-in; /* opera is special :) */
transition: none;
}
-.foldout-show, .foldout-enter, .foldout-hide {
+.foldout.ng-enter,
+.foldout.ng-hide-add,
+.foldout.ng-hide-remove {
-webkit-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
-moz-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
-o-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
}
-.foldout-show, .foldout-enter {
+.foldout.ng-hide-remove,
+.foldout.ng-enter {
opacity:0;
}
-.foldout-show.foldout-show-active, .foldout-hide.foldout-hide-active {
+.foldout.ng-hide-remove.ng-hide-remove-active,
+.foldout.ng-enter.ng-enter-active {
opacity:1;
}
-.foldout-hide {
+.foldout.ng-hide-add {
opacity:1;
}
-.foldout-hide.foldout-hide-active {
+.foldout.ng-hide-add.ng-hide-active {
opacity:0;
}
diff --git a/docs/src/templates/index.html b/docs/src/templates/index.html
index 82a5c87e..3f3e83c8 100644
--- a/docs/src/templates/index.html
+++ b/docs/src/templates/index.html
@@ -43,6 +43,7 @@
addTag('script', {src: path('angular-cookies.js') }, sync);
addTag('script', {src: path('angular-sanitize.js') }, sync);
addTag('script', {src: path('angular-mobile.js') }, sync);
+ addTag('script', {src: path('angular-animate.js') }, sync);
addTag('script', {src: 'components/angular-bootstrap.js' }, sync);
addTag('script', {src: 'components/angular-bootstrap-prettify.js' }, sync);
addTag('script', {src: 'components/google-code-prettify.js' }, sync);
@@ -201,7 +202,7 @@
</header>
<div id="docs-fold-overlay" ng-show="docs_fold" ng-click="fold(null)"></div>
- <div id="docs-fold" ng-show="docs_fold" ng-animate="'fold'">
+ <div class="foldout" id="docs-fold" ng-show="docs_fold">
<div id="docs-fold-close" ng-click="fold(null)">
<span class="icon-remove-sign"></span>
</div>
@@ -283,21 +284,21 @@
<li class="nav-header section" ng-show="module.directives">
<a href="{{URL.directive}}" class="guide">directive</a>
</li>
- <li ng-repeat="page in module.directives track by page.url" ng-class="navClass(page)" ng-animate="'expand'" class="api-list-item">
+ <li ng-repeat="page in module.directives track by page.url" ng-class="navClass(page)" class="expand api-list-item">
<a href="{{page.url}}" tabindex="2">{{page.shortName}}</a>
</li>
<li class="nav-header section" ng-show="module.filters">
<a href="{{URL.filter}}" class="guide">filter</a>
</li>
- <li ng-repeat="page in module.filters track by page.url" ng-class="navClass(page)" ng-animate="'expand'" class="api-list-item">
+ <li ng-repeat="page in module.filters track by page.url" ng-class="navClass(page)" class="expand api-list-item">
<a href="{{page.url}}" tabindex="2">{{page.shortName}}</a>
</li>
<li class="nav-header section" ng-show="module.services">
<a href="{{URL.service}}" class="guide">service</a>
</li>
- <li ng-repeat="service in module.services track by service.instance.url" ng-animate="'expand'" ng-class="navClass(service.instance, service.provider)" class="api-list-item">
+ <li ng-repeat="service in module.services track by service.instance.url" ng-class="navClass(service.instance, service.provider)" class="api-list-item expand">
<a ng-show="service.provider" class="pull-right" href="{{service.provider.url}}" tabindex="2"><i class="icon-cog"></i></a>
<a href="{{service.instance.url}}" tabindex="2">{{service.name}}</a>
</li>
@@ -305,7 +306,7 @@
<li class="nav-header section" ng-show="module.types">
<a href="{{URL.type}}" class="guide">Types</a>
</li>
- <li ng-repeat="page in module.types track by page.url" ng-class="navClass(page)" ng-animate="'expand'" class="api-list-item">
+ <li ng-repeat="page in module.types track by page.url" ng-class="navClass(page)" class="expand api-list-item">
<a href="{{page.url}}" tabindex="2">{{page.shortName}}</a>
</li>
@@ -334,7 +335,7 @@
<div id="loading" ng-show="loading">Loading...</div>
- <div ng-hide="loading" ng-include src="currentPage.partialUrl" onload="afterPartialLoaded()" autoscroll class="content" ng-animate="{enter: 'slide-reveal'}" ></div>
+ <div ng-hide="loading" ng-include src="currentPage.partialUrl" onload="afterPartialLoaded()" autoscroll class="content slide-reveal"></div>
<div id="disqus" class="disqus">
<h2>Discussion</h2>
diff --git a/docs/src/templates/js/docs.js b/docs/src/templates/js/docs.js
index 7cac6a9a..05b09571 100644
--- a/docs/src/templates/js/docs.js
+++ b/docs/src/templates/js/docs.js
@@ -803,7 +803,7 @@ docsApp.controller.DocsController = function($scope, $location, $window, $cookie
};
-angular.module('docsApp', ['ngResource', 'ngRoute', 'ngCookies', 'ngSanitize', 'bootstrap', 'bootstrapPrettify', 'docsData']).
+angular.module('docsApp', ['ngResource', 'ngRoute', 'ngCookies', 'ngSanitize', 'ngAnimate', 'bootstrap', 'bootstrapPrettify', 'docsData']).
config(function($locationProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
}).
diff --git a/karma-docs.conf.js b/karma-docs.conf.js
index 65f51fdd..7d21570d 100644
--- a/karma-docs.conf.js
+++ b/karma-docs.conf.js
@@ -15,6 +15,7 @@ module.exports = function(config) {
'build/angular-mobile.js',
'build/angular-sanitize.js',
'build/angular-route.js',
+ 'build/angular-animate.js',
'build/docs/components/lunr.js',
'build/docs/components/google-code-prettify.js',
diff --git a/src/Angular.js b/src/Angular.js
index 4e050a0c..68768d34 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -1053,13 +1053,13 @@ function bootstrap(element, modules) {
}]);
modules.unshift('ng');
var injector = createInjector(modules);
- injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animator',
- function(scope, element, compile, injector, animator) {
+ injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',
+ function(scope, element, compile, injector, animate) {
scope.$apply(function() {
element.data('$injector', injector);
compile(element)(scope);
});
- animator.enabled(true);
+ animate.enabled(true);
}]
);
return injector;
diff --git a/src/AngularPublic.js b/src/AngularPublic.js
index 7745cce9..b225fc85 100755
--- a/src/AngularPublic.js
+++ b/src/AngularPublic.js
@@ -106,8 +106,7 @@ function publishExternalAPI(angular){
directive(ngEventDirectives);
$provide.provider({
$anchorScroll: $AnchorScrollProvider,
- $animation: $AnimationProvider,
- $animator: $AnimatorProvider,
+ $animate: $AnimateProvider,
$browser: $BrowserProvider,
$cacheFactory: $CacheFactoryProvider,
$controller: $ControllerProvider,
diff --git a/src/loader.js b/src/loader.js
index 712ff0f7..b28ccee2 100644
--- a/src/loader.js
+++ b/src/loader.js
@@ -173,24 +173,30 @@ function setupModuleLoader(window) {
* @param {Function} animationFactory Factory function for creating new instance of an animation.
* @description
*
- * Defines an animation hook that can be later used with {@link ng.directive:ngAnimate ngAnimate}
- * alongside {@link ng.directive:ngAnimate#Description common ng directives} as well as custom directives.
+ * **NOTE**: animations are take effect only if the **ngAnimate** module is loaded.
+ *
+ *
+ * Defines an animation hook that can be later used with {@link ngAnimate.$animate $animate} service and
+ * directives that use this service.
+ *
* <pre>
- * module.animation('animation-name', function($inject1, $inject2) {
+ * module.animation('.animation-name', function($inject1, $inject2) {
* return {
- * //this gets called in preparation to setup an animation
- * setup : function(element) { ... },
- *
- * //this gets called once the animation is run
- * start : function(element, done, memo) { ... }
+ * eventName : function(element, done) {
+ * //code to run the animation
+ * //once complete, then run done()
+ * return function cancellationFunction(element) {
+ * //code to cancel the animation
+ * }
+ * }
* }
* })
* </pre>
*
- * See {@link ng.$animationProvider#register $animationProvider.register()} and
- * {@link ng.directive:ngAnimate ngAnimate} for more information.
+ * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
+ * {@link ngAnimate ngAnimate module} for more information.
*/
- animation: invokeLater('$animationProvider', 'register'),
+ animation: invokeLater('$animateProvider', 'register'),
/**
* @ngdoc method
diff --git a/src/ng/animate.js b/src/ng/animate.js
new file mode 100644
index 00000000..7e515594
--- /dev/null
+++ b/src/ng/animate.js
@@ -0,0 +1,112 @@
+'use strict';
+
+/**
+ * @ngdoc object
+ * @name ng.$animateProvider
+ *
+ * @description
+ * Default implementation of $animate that doesn't perform any animations, instead just synchronously performs DOM
+ * updates and calls done() callbacks.
+ *
+ * In order to enable animations the ngAnimate module has to be loaded.
+ *
+ * To see the functional implementation check out src/ngAnimate/animate.js
+ */
+var $AnimateProvider = ['$provide', function($provide) {
+
+ this.$$selectors = [];
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$animateProvider#register
+ * @methodOf ng.$animateProvider
+ *
+ * @description
+ * Registers a new injectable animation factory function. The factory function produces the animation object which
+ * contains callback functions for each event that is expected to be animated.
+ *
+ * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` must be called once the
+ * element animation is complete. If a function is returned then the animation service will use this function to
+ * cancel the animation whenever a cancel event is triggered.
+ *
+ *
+ *<pre>
+ * return {
+ * eventFn : function(element, done) {
+ * //code to run the animation
+ * //once complete, then run done()
+ * return function cancellationFunction() {
+ * //code to cancel the animation
+ * }
+ * }
+ * }
+ *</pre>
+ *
+ * @param {string} name The name of the animation.
+ * @param {function} factory The factory function that will be executed to return the animation object.
+ */
+ this.register = function(name, factory) {
+ var classes = name.substr(1).split('.');
+ name += '-animation';
+ this.$$selectors.push({
+ selectors : classes,
+ name : name
+ });
+ $provide.factory(name, factory);
+ };
+
+ this.$get = function() {
+ return {
+ enter : function(element, parent, after, done) {
+ var afterNode = after && after[after.length - 1];
+ var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
+ // IE does not like undefined so we have to pass null.
+ var afterNextSibling = (afterNode && afterNode.nextSibling) || null;
+ forEach(element, function(node) {
+ parentNode.insertBefore(node, afterNextSibling);
+ });
+ (done || noop)();
+ },
+
+ leave : function(element, done) {
+ element.remove();
+ (done || noop)();
+ },
+
+ move : function(element, parent, after, done) {
+ // Do not remove element before insert. Removing will cause data associated with the
+ // element to be dropped. Insert will implicitly do the remove.
+ this.enter(element, parent, after, done);
+ },
+
+ show : function(element, done) {
+ element.removeClass('ng-hide');
+ (done || noop)();
+ },
+
+ hide : function(element, done) {
+ element.addClass('ng-hide');
+ (done || noop)();
+ },
+
+ addClass : function(element, className, done) {
+ className = isString(className) ?
+ className :
+ isArray(className) ? className.join(' ') : '';
+ element.addClass(className);
+ (done || noop)();
+ },
+
+ removeClass : function(element, className, done) {
+ className = isString(className) ?
+ className :
+ isArray(className) ? className.join(' ') : '';
+ element.removeClass(className);
+ (done || noop)();
+ },
+
+ enabled : noop
+ };
+ };
+}];
diff --git a/src/ng/animation.js b/src/ng/animation.js
deleted file mode 100644
index faed84ca..00000000
--- a/src/ng/animation.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * @ngdoc object
- * @name ng.$animationProvider
- * @description
- *
- * The $AnimationProvider provider allows developers to register and access custom JavaScript animations directly inside
- * of a module.
- *
- */
-$AnimationProvider.$inject = ['$provide'];
-function $AnimationProvider($provide) {
- var suffix = 'Animation';
-
- /**
- * @ngdoc function
- * @name ng.$animation#register
- * @methodOf ng.$animationProvider
- *
- * @description
- * Registers a new injectable animation factory function. The factory function produces the animation object which
- * has these two properties:
- *
- * * `setup`: `function(Element):*` A function which receives the starting state of the element. The purpose
- * of this function is to get the element ready for animation. Optionally the function returns an memento which
- * is passed to the `start` function.
- * * `start`: `function(Element, doneFunction, *)` The element to animate, the `doneFunction` to be called on
- * element animation completion, and an optional memento from the `setup` function.
- *
- * @param {string} name The name of the animation.
- * @param {function} factory The factory function that will be executed to return the animation object.
- *
- */
- this.register = function(name, factory) {
- $provide.factory(camelCase(name) + suffix, factory);
- };
-
- this.$get = ['$injector', function($injector) {
- /**
- * @ngdoc function
- * @name ng.$animation
- * @function
- *
- * @description
- * The $animation service is used to retrieve any defined animation functions. When executed, the $animation service
- * will return a object that contains the setup and start functions that were defined for the animation.
- *
- * @param {String} name Name of the animation function to retrieve. Animation functions are registered and stored
- * inside of the AngularJS DI so a call to $animate('custom') is the same as injecting `customAnimation`
- * via dependency injection.
- * @return {Object} the animation object which contains the `setup` and `start` functions that perform the animation.
- */
- return function $animation(name) {
- if (name) {
- var animationName = camelCase(name) + suffix;
- if ($injector.has(animationName)) {
- return $injector.get(animationName);
- }
- }
- };
- }];
-}
diff --git a/src/ng/animator.js b/src/ng/animator.js
deleted file mode 100644
index a9ea5743..00000000
--- a/src/ng/animator.js
+++ /dev/null
@@ -1,446 +0,0 @@
-'use strict';
-
-// NOTE: this is a pseudo directive.
-
-/**
- * @ngdoc directive
- * @name ng.directive:ngAnimate
- *
- * @description
- * The `ngAnimate` directive works as an attribute that is attached alongside pre-existing directives.
- * It effects how the directive will perform DOM manipulation. This allows for complex animations to take place
- * without burdening the directive which uses the animation with animation details. The built in directives
- * `ngRepeat`, `ngInclude`, `ngSwitch`, `ngShow`, `ngHide` and `ngView` already accept `ngAnimate` directive.
- * Custom directives can take advantage of animation through {@link ng.$animator $animator service}.
- *
- * Below is a more detailed breakdown of the supported callback events provided by pre-exisitng ng directives:
- *
- * | Directive | Supported Animations |
- * |---------------------------------------------------------- |----------------------------------------------------|
- * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move |
- * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
- * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
- * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
- * | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
- * | {@link ng.directive:ngShow#animations ngShow & ngHide} | show and hide |
- *
- * You can find out more information about animations upon visiting each directive page.
- *
- * Below is an example of a directive that makes use of the ngAnimate attribute:
- *
- * <pre>
- * <!-- you can also use data-ng-animate, ng:animate or x-ng-animate as well -->
- * <ANY ng-directive ng-animate="{event1: 'animation-name', event2: 'animation-name-2'}"></ANY>
- *
- * <!-- you can also use a short hand -->
- * //!annotate="animation" ngAnimate|This *expands* to `{ enter: 'animation-enter', leave: 'animation-leave', ...}`</strong>
- * <ANY ng-directive ng-animate=" 'animation' "></ANY>
- *
- * <!-- keep in mind that ng-animate can take expressions -->
- * //!annotate="computeCurrentAnimation\(\)" Scope Function|This will be called each time the scope changes...
- * <ANY ng-directive ng-animate=" computeCurrentAnimation() "></ANY>
- * </pre>
- *
- * The `event1` and `event2` attributes refer to the animation events specific to the directive that has been assigned.
- *
- * Keep in mind that if an animation is running, no child element of such animation can also be animated.
- *
- * <h2>CSS-defined Animations</h2>
- * By default, ngAnimate attaches two CSS classes per animation event to the DOM element to achieve the animation.
- * It is up to you, the developer, to ensure that the animations take place using cross-browser CSS3 transitions as
- * well as CSS animations.
- *
- * The following code below demonstrates how to perform animations using **CSS transitions** with ngAnimate:
- *
- * <pre>
- * <style type="text/css">
- * /&#42;
- * The animate-enter CSS class is the event name that you
- * have provided within the ngAnimate attribute.
- * &#42;/
- * .animate-enter {
- * -webkit-transition: 1s linear all; /&#42; Safari/Chrome &#42;/
- * -moz-transition: 1s linear all; /&#42; Firefox &#42;/
- * -o-transition: 1s linear all; /&#42; Opera &#42;/
- * transition: 1s linear all; /&#42; IE10+ and Future Browsers &#42;/
- *
- * /&#42; The animation preparation code &#42;/
- * opacity: 0;
- * }
- *
- * /&#42;
- * Keep in mind that you want to combine both CSS
- * classes together to avoid any CSS-specificity
- * conflicts
- * &#42;/
- * .animate-enter.animate-enter-active {
- * /&#42; The animation code itself &#42;/
- * opacity: 1;
- * }
- * </style>
- *
- * <div ng-directive ng-animate="{enter: 'animate-enter'}"></div>
- * </pre>
- *
- * The following code below demonstrates how to perform animations using **CSS animations** with ngAnimate:
- *
- * <pre>
- * <style type="text/css">
- * .animate-enter {
- * -webkit-animation: enter_sequence 1s linear; /&#42; Safari/Chrome &#42;/
- * -moz-animation: enter_sequence 1s linear; /&#42; Firefox &#42;/
- * -o-animation: enter_sequence 1s linear; /&#42; Opera &#42;/
- * animation: enter_sequence 1s linear; /&#42; IE10+ and Future Browsers &#42;/
- * }
- * &#64-webkit-keyframes enter_sequence {
- * from { opacity:0; }
- * to { opacity:1; }
- * }
- * &#64-moz-keyframes enter_sequence {
- * from { opacity:0; }
- * to { opacity:1; }
- * }
- * &#64-o-keyframes enter_sequence {
- * from { opacity:0; }
- * to { opacity:1; }
- * }
- * &#64keyframes enter_sequence {
- * from { opacity:0; }
- * to { opacity:1; }
- * }
- * </style>
- *
- * <div ng-directive ng-animate="{enter: 'animate-enter'}"></div>
- * </pre>
- *
- * ngAnimate will first examine any CSS animation code and then fallback to using CSS transitions.
- *
- * Upon DOM mutation, the event class is added first, then the browser is allowed to reflow the content and then,
- * the active class is added to trigger the animation. The ngAnimate directive will automatically extract the duration
- * of the animation to determine when the animation ends. Once the animation is over then both CSS classes will be
- * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end
- * immediately resulting in a DOM element that is at it's final state. This final state is when the DOM element
- * has no CSS transition/animation classes surrounding it.
- *
- * <h2>JavaScript-defined Animations</h2>
- * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations to browsers that do not
- * yet support them, then you can make use of JavaScript animations defined inside of your AngularJS module.
- *
- * <pre>
- * var ngModule = angular.module('YourApp', []);
- * ngModule.animation('animate-enter', function() {
- * return {
- * setup : function(element) {
- * //prepare the element for animation
- * element.css({ 'opacity': 0 });
- * var memo = "..."; //this value is passed to the start function
- * return memo;
- * },
- * start : function(element, done, memo) {
- * //start the animation
- * element.animate({
- * 'opacity' : 1
- * }, function() {
- * //call when the animation is complete
- * done()
- * });
- * }
- * }
- * });
- * </pre>
- *
- * As you can see, the JavaScript code follows a similar template to the CSS3 animations. Once defined, the animation
- * can be used in the same way with the ngAnimate attribute. Keep in mind that, when using JavaScript-enabled
- * animations, ngAnimate will also add in the same CSS classes that CSS-enabled animations do (even if you're not using
- * CSS animations) to animated the element, but it will not attempt to find any CSS3 transition or animation duration/delay values.
- * It will instead close off the animation once the provided done function is executed. So it's important that you
- * make sure your animations remember to fire off the done function once the animations are complete.
- *
- * @param {expression} ngAnimate Used to configure the DOM manipulation animations.
- *
- */
-
-var $AnimatorProvider = function() {
- var NG_ANIMATE_CONTROLLER = '$ngAnimateController';
- var rootAnimateController = {running:true};
-
- this.$get = ['$animation', '$window', '$sniffer', '$rootElement', '$rootScope',
- function($animation, $window, $sniffer, $rootElement, $rootScope) {
- $rootElement.data(NG_ANIMATE_CONTROLLER, rootAnimateController);
-
- /**
- * @ngdoc function
- * @name ng.$animator
- * @function
- *
- * @description
- * The $animator.create service provides the DOM manipulation API which is decorated with animations.
- *
- * @param {Scope} scope the scope for the ng-animate.
- * @param {Attributes} attr the attributes object which contains the ngAnimate key / value pair. (The attributes are
- * passed into the linking function of the directive using the `$animator`.)
- * @return {object} the animator object which contains the enter, leave, move, show, hide and animate methods.
- */
- var AnimatorService = function(scope, attrs) {
- var animator = {};
-
- /**
- * @ngdoc function
- * @name ng.animator#enter
- * @methodOf ng.$animator
- * @function
- *
- * @description
- * Injects the element object into the DOM (inside of the parent element) and then runs the enter animation.
- *
- * @param {jQuery/jqLite element} element the element that will be the focus of the enter animation
- * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the enter animation
- * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the enter animation
- */
- animator.enter = animateActionFactory('enter', insert, noop);
-
- /**
- * @ngdoc function
- * @name ng.animator#leave
- * @methodOf ng.$animator
- * @function
- *
- * @description
- * Runs the leave animation operation and, upon completion, removes the element from the DOM.
- *
- * @param {jQuery/jqLite element} element the element that will be the focus of the leave animation
- * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the leave animation
- */
- animator.leave = animateActionFactory('leave', noop, remove);
-
- /**
- * @ngdoc function
- * @name ng.animator#move
- * @methodOf ng.$animator
- * @function
- *
- * @description
- * Fires the move DOM operation. Just before the animation starts, the animator will either append it into the parent container or
- * add the element directly after the after element if present. Then the move animation will be run.
- *
- * @param {jQuery/jqLite element} element the element that will be the focus of the move animation
- * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the move animation
- * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the move animation
- */
- animator.move = animateActionFactory('move', move, noop);
-
- /**
- * @ngdoc function
- * @name ng.animator#show
- * @methodOf ng.$animator
- * @function
- *
- * @description
- * Reveals the element by setting the CSS property `display` to `block` and then starts the show animation directly after.
- *
- * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
- */
- animator.show = animateActionFactory('show', show, noop);
-
- /**
- * @ngdoc function
- * @name ng.animator#hide
- * @methodOf ng.$animator
- *
- * @description
- * Starts the hide animation first and sets the CSS `display` property to `none` upon completion.
- *
- * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
- */
- animator.hide = animateActionFactory('hide', noop, hide);
-
- /**
- * @ngdoc function
- * @name ng.animator#animate
- * @methodOf ng.$animator
- *
- * @description
- * Triggers a custom animation event to be executed on the given element
- *
- * @param {string} event the name of the custom event
- * @param {jQuery/jqLite element} element the element that will be animated
- */
- animator.animate = function(event, element) {
- animateActionFactory(event, noop, noop)(element);
- }
- return animator;
-
- function animateActionFactory(type, beforeFn, afterFn) {
- return function(element, parent, after) {
- var ngAnimateValue = scope.$eval(attrs.ngAnimate);
- var className = ngAnimateValue
- ? isObject(ngAnimateValue) ? ngAnimateValue[type] : ngAnimateValue + '-' + type
- : '';
- var animationPolyfill = $animation(className);
- var polyfillSetup = animationPolyfill && animationPolyfill.setup;
- var polyfillStart = animationPolyfill && animationPolyfill.start;
- var polyfillCancel = animationPolyfill && animationPolyfill.cancel;
-
- if (!className) {
- beforeFn(element, parent, after);
- afterFn(element, parent, after);
- } else {
- var activeClassName = className + '-active';
-
- if (!parent) {
- parent = after ? after.parent() : element.parent();
- }
- var disabledAnimation = { running : true };
- if ((!$sniffer.transitions && !polyfillSetup && !polyfillStart) ||
- (parent.inheritedData(NG_ANIMATE_CONTROLLER) || disabledAnimation).running) {
- beforeFn(element, parent, after);
- afterFn(element, parent, after);
- return;
- }
-
- var animationData = element.data(NG_ANIMATE_CONTROLLER) || {};
- if(animationData.running) {
- (polyfillCancel || noop)(element);
- animationData.done();
- }
-
- element.data(NG_ANIMATE_CONTROLLER, {running:true, done:done});
- element.addClass(className);
- beforeFn(element, parent, after);
- if (element.length == 0) return done();
-
- var memento = (polyfillSetup || noop)(element);
-
- // $window.setTimeout(beginAnimation, 0); this was causing the element not to animate
- // keep at 1 for animation dom rerender
- $window.setTimeout(beginAnimation, 1);
- }
-
- function parseMaxTime(str) {
- var total = 0, values = isString(str) ? str.split(/\s*,\s*/) : [];
- forEach(values, function(value) {
- total = Math.max(parseFloat(value) || 0, total);
- });
- return total;
- }
-
- function beginAnimation() {
- element.addClass(activeClassName);
- if (polyfillStart) {
- polyfillStart(element, done, memento);
- } else if (isFunction($window.getComputedStyle)) {
- //one day all browsers will have these properties
- var w3cAnimationProp = 'animation';
- var w3cTransitionProp = 'transition';
-
- //but some still use vendor-prefixed styles
- var vendorAnimationProp = $sniffer.vendorPrefix + 'Animation';
- var vendorTransitionProp = $sniffer.vendorPrefix + 'Transition';
-
- var durationKey = 'Duration',
- delayKey = 'Delay',
- animationIterationCountKey = 'IterationCount',
- duration = 0;
-
- //we want all the styles defined before and after
- var ELEMENT_NODE = 1;
- forEach(element, function(element) {
- if (element.nodeType == ELEMENT_NODE) {
- var elementStyles = $window.getComputedStyle(element) || {};
-
- var transitionDelay = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + delayKey]),
- parseMaxTime(elementStyles[vendorTransitionProp + delayKey]));
-
- var animationDelay = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + delayKey]),
- parseMaxTime(elementStyles[vendorAnimationProp + delayKey]));
-
- var transitionDuration = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + durationKey]),
- parseMaxTime(elementStyles[vendorTransitionProp + durationKey]));
-
- var animationDuration = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + durationKey]),
- parseMaxTime(elementStyles[vendorAnimationProp + durationKey]));
-
- if(animationDuration > 0) {
- animationDuration *= Math.max(parseInt(elementStyles[w3cAnimationProp + animationIterationCountKey]) || 0,
- parseInt(elementStyles[vendorAnimationProp + animationIterationCountKey]) || 0,
- 1);
- }
-
- duration = Math.max(animationDelay + animationDuration,
- transitionDelay + transitionDuration,
- duration);
- }
- });
- $window.setTimeout(done, duration * 1000);
- } else {
- done();
- }
- }
-
- function done() {
- if(!done.run) {
- done.run = true;
- afterFn(element, parent, after);
- element.removeClass(className);
- element.removeClass(activeClassName);
- element.removeData(NG_ANIMATE_CONTROLLER);
- }
- }
- };
- }
-
- function show(element) {
- element.css('display', '');
- }
-
- function hide(element) {
- element.css('display', 'none');
- }
-
- function insert(element, parent, after) {
- var afterNode = after && after[after.length - 1];
- var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
- var afterNextSibling = afterNode && afterNode.nextSibling;
- forEach(element, function(node) {
- if (afterNextSibling) {
- parentNode.insertBefore(node, afterNextSibling);
- } else {
- parentNode.appendChild(node);
- }
- });
- }
-
- function remove(element) {
- element.remove();
- }
-
- function move(element, parent, after) {
- // Do not remove element before insert. Removing will cause data associated with the
- // element to be dropped. Insert will implicitly do the remove.
- insert(element, parent, after);
- }
- };
-
- /**
- * @ngdoc function
- * @name ng.animator#enabled
- * @methodOf ng.$animator
- * @function
- *
- * @param {Boolean=} If provided then set the animation on or off.
- * @return {Boolean} Current animation state.
- *
- * @description
- * Globally enables/disables animations.
- *
- */
- AnimatorService.enabled = function(value) {
- if (arguments.length) {
- rootAnimateController.running = !value;
- }
- return !rootAnimateController.running;
- };
-
- return AnimatorService;
- }];
-};
diff --git a/src/ng/directive/ngClass.js b/src/ng/directive/ngClass.js
index 75e35a1e..a5b2acb6 100644
--- a/src/ng/directive/ngClass.js
+++ b/src/ng/directive/ngClass.js
@@ -2,59 +2,72 @@
function classDirective(name, selector) {
name = 'ngClass' + name;
- return ngDirective(function(scope, element, attr) {
- var oldVal = undefined;
-
- scope.$watch(attr[name], ngClassWatchAction, true);
-
- attr.$observe('class', function(value) {
- var ngClass = scope.$eval(attr[name]);
- ngClassWatchAction(ngClass, ngClass);
- });
+ return ['$animate', function($animate) {
+ return {
+ restrict: 'AC',
+ link: function(scope, element, attr) {
+ var oldVal = undefined;
+
+ scope.$watch(attr[name], ngClassWatchAction, true);
+
+ attr.$observe('class', function(value) {
+ var ngClass = scope.$eval(attr[name]);
+ ngClassWatchAction(ngClass, ngClass);
+ });
+
+
+ if (name !== 'ngClass') {
+ scope.$watch('$index', function($index, old$index) {
+ var mod = $index & 1;
+ if (mod !== old$index & 1) {
+ if (mod === selector) {
+ addClass(scope.$eval(attr[name]));
+ } else {
+ removeClass(scope.$eval(attr[name]));
+ }
+ }
+ });
+ }
- if (name !== 'ngClass') {
- scope.$watch('$index', function($index, old$index) {
- var mod = $index & 1;
- if (mod !== old$index & 1) {
- if (mod === selector) {
- addClass(scope.$eval(attr[name]));
- } else {
- removeClass(scope.$eval(attr[name]));
+ function ngClassWatchAction(newVal) {
+ if (selector === true || scope.$index % 2 === selector) {
+ if (oldVal && !equals(newVal,oldVal)) {
+ removeClass(oldVal);
+ }
+ addClass(newVal);
}
+ oldVal = copy(newVal);
}
- });
- }
- function ngClassWatchAction(newVal) {
- if (selector === true || scope.$index % 2 === selector) {
- if (oldVal && !equals(newVal,oldVal)) {
- removeClass(oldVal);
+ function removeClass(classVal) {
+ $animate.removeClass(element, flattenClasses(classVal));
}
- addClass(newVal);
- }
- oldVal = copy(newVal);
- }
- function removeClass(classVal) {
- if (isObject(classVal) && !isArray(classVal)) {
- classVal = map(classVal, function(v, k) { if (v) return k });
- }
- element.removeClass(isArray(classVal) ? classVal.join(' ') : classVal);
- }
+ function addClass(classVal) {
+ $animate.addClass(element, flattenClasses(classVal));
+ }
+ function flattenClasses(classVal) {
+ if(isArray(classVal)) {
+ return classVal.join(' ');
+ } else if (isObject(classVal)) {
+ var classes = [], i = 0;
+ forEach(classVal, function(v, k) {
+ if (v) {
+ classes.push(k);
+ }
+ });
+ return classes.join(' ');
+ }
- function addClass(classVal) {
- if (isObject(classVal) && !isArray(classVal)) {
- classVal = map(classVal, function(v, k) { if (v) return k });
+ return classVal;
+ };
}
- if (classVal) {
- element.addClass(isArray(classVal) ? classVal.join(' ') : classVal);
- }
- }
- });
+ };
+ }];
}
/**
@@ -70,6 +83,10 @@ function classDirective(name, selector) {
* When the expression changes, the previously added classes are removed and only then the
* new classes are added.
*
+ * @animations
+ * add - happens just before the class is applied to the element
+ * remove - happens just before the class is removed from the element
+ *
* @element ANY
* @param {expression} ngClass {@link guide/expression Expression} to eval. The result
* of the evaluation can be a string representing space delimited class
@@ -78,7 +95,7 @@ function classDirective(name, selector) {
* element.
*
* @example
- <example>
+ <example animations="true">
<file name="index.html">
<input type="button" value="set" ng-click="myVar='my-class'">
<input type="button" value="clear" ng-click="myVar=''">
@@ -86,8 +103,23 @@ function classDirective(name, selector) {
<span ng-class="myVar">Sample Text</span>
</file>
<file name="style.css">
- .my-class {
+ .my-class-add,
+ .my-class-remove {
+ -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
+ -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
+ -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
+ transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
+ }
+
+ .my-class,
+ .my-class-add.my-class-add-active {
color: red;
+ font-size:3em;
+ }
+
+ .my-class-remove.my-class-remove-active {
+ font-size:1.0em;
+ color:black;
}
</file>
<file name="scenario.js">
diff --git a/src/ng/directive/ngIf.js b/src/ng/directive/ngIf.js
index c8166ee5..9d99d859 100755
--- a/src/ng/directive/ngIf.js
+++ b/src/ng/directive/ngIf.js
@@ -30,7 +30,7 @@
* jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element
* the added class will be lost because the original compiled state is used to regenerate the element.
*
- * Additionally, you can provide animations via the ngAnimate attribute to animate the **enter**
+ * Additionally, you can provide animations via the ngAnimate module to animate the **enter**
* and **leave** effects.
*
* @animations
@@ -47,36 +47,32 @@
<file name="index.html">
Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/>
Show when checked:
- <span ng-if="checked" ng-animate="'example'">
+ <span ng-if="checked" class="example-if">
I'm removed when the checkbox is unchecked.
</span>
</file>
<file name="animations.css">
- .example-leave, .example-enter {
+ .example-if.ng-enter,
+ .example-if.ng-leave {
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
-moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
- -ms-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
-o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
}
- .example-enter {
+ .example-if.ng-enter,
+ .example-if.ng-leave.ng-leave-active {
opacity:0;
}
- .example-enter.example-enter-active {
- opacity:1;
- }
- .example-leave {
+ .example-if.ng-enter.ng-enter-active,
+ .example-if.ng-leave {
opacity:1;
}
- .example-leave.example-leave-active {
- opacity:0;
- }
</file>
</example>
*/
-var ngIfDirective = ['$animator', function($animator) {
+var ngIfDirective = ['$animate', function($animate) {
return {
transclude: 'element',
priority: 1000,
@@ -84,11 +80,10 @@ var ngIfDirective = ['$animator', function($animator) {
restrict: 'A',
compile: function (element, attr, transclude) {
return function ($scope, $element, $attr) {
- var animate = $animator($scope, $attr);
var childElement, childScope;
$scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
if (childElement) {
- animate.leave(childElement);
+ $animate.leave(childElement);
childElement = undefined;
}
if (childScope) {
@@ -99,7 +94,7 @@ var ngIfDirective = ['$animator', function($animator) {
childScope = $scope.$new();
transclude(childScope, function (clone) {
childElement = clone;
- animate.enter(clone, $element.parent(), $element);
+ $animate.enter(clone, $element.parent(), $element);
});
}
});
diff --git a/src/ng/directive/ngInclude.js b/src/ng/directive/ngInclude.js
index 72b5af08..d5ed1fc5 100644
--- a/src/ng/directive/ngInclude.js
+++ b/src/ng/directive/ngInclude.js
@@ -23,9 +23,6 @@
* (e.g. ngInclude won't work for cross-domain requests on all browsers and for `file://`
* access on some browsers)
*
- * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter**
- * and **leave** effects.
- *
* @animations
* enter - happens just after the ngInclude contents change and a new DOM element is created and injected into the ngInclude container
* leave - happens just after the ngInclude contents change and just before the former contents are removed from the DOM
@@ -143,8 +140,8 @@
* @description
* Emitted every time the ngInclude content is reloaded.
*/
-var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', '$animator', '$sce',
- function($http, $templateCache, $anchorScroll, $compile, $animator, $sce) {
+var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', '$animate', '$sce',
+ function($http, $templateCache, $anchorScroll, $compile, $animate, $sce) {
return {
restrict: 'ECA',
terminal: true,
@@ -154,7 +151,6 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
autoScrollExp = attr.autoscroll;
return function(scope, element, attr) {
- var animate = $animator(scope, attr);
var changeCounter = 0,
childScope;
@@ -163,7 +159,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
childScope.$destroy();
childScope = null;
}
- animate.leave(element.contents(), element);
+ $animate.leave(element.contents());
};
scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) {
@@ -175,11 +171,11 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
if (childScope) childScope.$destroy();
childScope = scope.$new();
- animate.leave(element.contents(), element);
+ $animate.leave(element.contents());
var contents = jqLite('<div/>').html(response).contents();
- animate.enter(contents, element);
+ $animate.enter(contents, element);
$compile(contents)(childScope);
if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
diff --git a/src/ng/directive/ngRepeat.js b/src/ng/directive/ngRepeat.js
index e0b2cb38..8f12b7c2 100644
--- a/src/ng/directive/ngRepeat.js
+++ b/src/ng/directive/ngRepeat.js
@@ -20,9 +20,6 @@
* | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
* | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
*
- * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter**,
- * **leave** and **move** effects.
- *
*
* # Special repeat start and end points
* To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
@@ -131,46 +128,40 @@
I have {{friends.length}} friends. They are:
<input type="search" ng-model="q" placeholder="filter friends..." />
<ul>
- <li ng-repeat="friend in friends | filter:q"
- ng-animate="{enter: 'example-repeat-enter',
- leave: 'example-repeat-leave',
- move: 'example-repeat-move'}">
+ <li class="animate-repeat" ng-repeat="friend in friends | filter:q">
[{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
</li>
</ul>
</div>
</file>
<file name="animations.css">
- .example-repeat-enter,
- .example-repeat-leave,
- .example-repeat-move {
+ .animate-repeat {
-webkit-transition:all linear 0.5s;
-moz-transition:all linear 0.5s;
- -ms-transition:all linear 0.5s;
-o-transition:all linear 0.5s;
transition:all linear 0.5s;
}
- .example-repeat-enter {
+ .animate-repeat.ng-enter {
line-height:0;
opacity:0;
}
- .example-repeat-enter.example-repeat-enter-active {
+ .animate-repeat.ng-enter.ng-enter-active {
line-height:20px;
opacity:1;
}
- .example-repeat-leave {
+ .animate-repeat.ng-leave {
opacity:1;
line-height:20px;
}
- .example-repeat-leave.example-repeat-leave-active {
+ .animate-repeat.ng-leave.ng-leave-active {
opacity:0;
line-height:0;
}
- .example-repeat-move { }
- .example-repeat-move.example-repeat-move-active { }
+ .animate-repeat.ng-move { }
+ .animate-repeat.ng-move.ng-move-active { }
</file>
<file name="scenario.js">
it('should render initial data set', function() {
@@ -195,7 +186,7 @@
</file>
</example>
*/
-var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {
+var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
var NG_REMOVED = '$$NG_REMOVED';
var ngRepeatMinErr = minErr('ngRepeat');
return {
@@ -204,7 +195,6 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {
terminal: true,
compile: function(element, attr, linker) {
return function($scope, $element, $attr){
- var animate = $animator($scope, $attr);
var expression = $attr.ngRepeat;
var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
trackByExp, trackByExpGetter, trackByIdFn, trackByIdArrayFn, trackByIdObjFn, lhs, rhs, valueIdentifier, keyIdentifier,
@@ -316,7 +306,7 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {
for (key in lastBlockMap) {
if (lastBlockMap.hasOwnProperty(key)) {
block = lastBlockMap[key];
- animate.leave(block.elements);
+ $animate.leave(block.elements);
forEach(block.elements, function(element) { element[NG_REMOVED] = true});
block.scope.$destroy();
}
@@ -342,7 +332,7 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {
// do nothing
} else {
// existing item which got moved
- animate.move(block.elements, null, jqLite(previousNode));
+ $animate.move(block.elements, null, jqLite(previousNode));
}
previousNode = block.endNode;
} else {
@@ -360,7 +350,7 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {
if (!block.startNode) {
linker(childScope, function(clone) {
- animate.enter(clone, null, jqLite(previousNode));
+ $animate.enter(clone, null, jqLite(previousNode));
previousNode = clone;
block.scope = childScope;
block.startNode = clone[0];
diff --git a/src/ng/directive/ngShowHide.js b/src/ng/directive/ngShowHide.js
index 7ef7008c..bdbcf463 100644
--- a/src/ng/directive/ngShowHide.js
+++ b/src/ng/directive/ngShowHide.js
@@ -12,8 +12,6 @@
* With ngHide this is the reverse whereas true values cause the element itself to become
* hidden.
*
- * Additionally, you can also provide animations via the ngAnimate attribute to animate the **show**
- * and **hide** effects.
*
* @animations
* show - happens after the ngShow expression evaluates to a truthy value and the contents are set to visible
@@ -29,36 +27,37 @@
Click me: <input type="checkbox" ng-model="checked"><br/>
<div>
Show:
- <span class="check-element"
- ng-show="checked"
- ng-animate="{show: 'example-show', hide: 'example-hide'}">
+ <span class="check-element example-show-hide" ng-show="checked">
<span class="icon-thumbs-up"></span> I show up when your checkbox is checked.
</span>
</div>
<div>
Hide:
- <span class="check-element"
- ng-hide="checked"
- ng-animate="{show: 'example-show', hide: 'example-hide'}">
+ <span class="check-element example-show-hide" ng-hide="checked">
<span class="icon-thumbs-down"></span> I hide when your checkbox is checked.
</span>
</div>
</file>
<file name="animations.css">
- .example-show, .example-hide {
+ .example-show-hide {
-webkit-transition:all linear 0.5s;
-moz-transition:all linear 0.5s;
-ms-transition:all linear 0.5s;
-o-transition:all linear 0.5s;
transition:all linear 0.5s;
+ display:block;
+ }
+ .example-show-hide.ng-hide {
+ display:none;
}
- .example-show {
+ .example-show-hide.ng-hide-remove {
+ display:block;
line-height:0;
opacity:0;
padding:0 10px;
}
- .example-show-active.example-show-active {
+ .example-show-hide.ng-hide-remove.ng-hide-remove-active {
line-height:20px;
opacity:1;
padding:10px;
@@ -66,14 +65,14 @@
background:white;
}
- .example-hide {
+ .example-show-hide.ng-hide-add {
line-height:20px;
opacity:1;
padding:10px;
border:1px solid black;
background:white;
}
- .example-hide-active.example-hide-active {
+ .example-show-hide.ng-hide-add.ng-hide-add-active {
line-height:0;
opacity:0;
padding:0 10px;
@@ -98,12 +97,10 @@
</file>
</example>
*/
-//TODO(misko): refactor to remove element from the DOM
-var ngShowDirective = ['$animator', function($animator) {
+var ngShowDirective = ['$animate', function($animate) {
return function(scope, element, attr) {
- var animate = $animator(scope, attr);
scope.$watch(attr.ngShow, function ngShowWatchAction(value){
- animate[toBoolean(value) ? 'show' : 'hide'](element);
+ $animate[toBoolean(value) ? 'show' : 'hide'](element);
});
};
}];
@@ -121,9 +118,6 @@ var ngShowDirective = ['$animator', function($animator) {
* With ngHide this is the reverse whereas true values cause the element itself to become
* hidden.
*
- * Additionally, you can also provide animations via the ngAnimate attribute to animate the **show**
- * and **hide** effects.
- *
* @animations
* show - happens after the ngHide expression evaluates to a non truthy value and the contents are set to visible
* hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden
@@ -138,36 +132,36 @@ var ngShowDirective = ['$animator', function($animator) {
Click me: <input type="checkbox" ng-model="checked"><br/>
<div>
Show:
- <span class="check-element"
- ng-show="checked"
- ng-animate="{show: 'example-show', hide: 'example-hide'}">
+ <span class="check-element example-show-hide" ng-show="checked">
<span class="icon-thumbs-up"></span> I show up when your checkbox is checked.
</span>
</div>
<div>
Hide:
- <span class="check-element"
- ng-hide="checked"
- ng-animate="{show: 'example-show', hide: 'example-hide'}">
+ <span class="check-element example-show-hide" ng-hide="checked">
<span class="icon-thumbs-down"></span> I hide when your checkbox is checked.
</span>
</div>
</file>
<file name="animations.css">
- .example-show, .example-hide {
+ .example-show-hide {
-webkit-transition:all linear 0.5s;
-moz-transition:all linear 0.5s;
- -ms-transition:all linear 0.5s;
-o-transition:all linear 0.5s;
transition:all linear 0.5s;
+ display:block;
+ }
+ .example-show-hide.ng-hide {
+ display:none;
}
- .example-show {
+ .example-show-hide.ng-hide-remove {
+ display:block;
line-height:0;
opacity:0;
padding:0 10px;
}
- .example-show.example-show-active {
+ .example-show-hide.ng-hide-remove.ng-hide-remove-active {
line-height:20px;
opacity:1;
padding:10px;
@@ -175,14 +169,14 @@ var ngShowDirective = ['$animator', function($animator) {
background:white;
}
- .example-hide {
+ .example-show-hide.ng-hide-add {
line-height:20px;
opacity:1;
padding:10px;
border:1px solid black;
background:white;
}
- .example-hide.example-hide-active {
+ .example-show-hide.ng-hide-add.ng-hide-add-active {
line-height:0;
opacity:0;
padding:0 10px;
@@ -207,12 +201,10 @@ var ngShowDirective = ['$animator', function($animator) {
</file>
</example>
*/
-//TODO(misko): refactor to remove element from the DOM
-var ngHideDirective = ['$animator', function($animator) {
+var ngHideDirective = ['$animate', function($animate) {
return function(scope, element, attr) {
- var animate = $animator(scope, attr);
scope.$watch(attr.ngHide, function ngHideWatchAction(value){
- animate[toBoolean(value) ? 'hide' : 'show'](element);
+ $animate[toBoolean(value) ? 'hide' : 'show'](element);
});
};
}];
diff --git a/src/ng/directive/ngSwitch.js b/src/ng/directive/ngSwitch.js
index f36e651c..38a123a2 100644
--- a/src/ng/directive/ngSwitch.js
+++ b/src/ng/directive/ngSwitch.js
@@ -19,9 +19,6 @@
* expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
* attribute is displayed.
*
- * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter**
- * and **leave** effects.
- *
* @animations
* enter - happens after the ngSwtich contents change and the matched child element is placed inside the container
* leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
@@ -55,9 +52,8 @@
<tt>selection={{selection}}</tt>
<hr/>
<div
- class="example-animate-container"
- ng-switch on="selection"
- ng-animate="{enter: 'example-enter', leave: 'example-leave'}">
+ class="example-animate-container animate-switch"
+ ng-switch on="selection">
<div ng-switch-when="settings">Settings Div</div>
<div ng-switch-when="home">Home Span</div>
<div ng-switch-default>default</div>
@@ -71,10 +67,9 @@
}
</file>
<file name="animations.css">
- .example-leave, .example-enter {
+ .animate-switch > * {
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
-moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
- -ms-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
-o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
@@ -90,17 +85,17 @@
padding:10px;
}
- .example-enter {
+ .animate-switch > .ng-enter {
top:-50px;
}
- .example-enter.example-enter-active {
+ .animate-switch > .ng-enter.ng-enter-active {
top:0;
}
- .example-leave {
+ .animate-switch > .ng-leave {
top:0;
}
- .example-leave.example-leave-active {
+ .animate-switch > .ng-leave.ng-leave-active {
top:50px;
}
</file>
@@ -119,7 +114,7 @@
</file>
</example>
*/
-var ngSwitchDirective = ['$animator', function($animator) {
+var ngSwitchDirective = ['$animate', function($animate) {
return {
restrict: 'EA',
require: 'ngSwitch',
@@ -129,7 +124,6 @@ var ngSwitchDirective = ['$animator', function($animator) {
this.cases = {};
}],
link: function(scope, element, attr, ngSwitchController) {
- var animate = $animator(scope, attr);
var watchExpr = attr.ngSwitch || attr.on,
selectedTranscludes,
selectedElements,
@@ -138,7 +132,7 @@ var ngSwitchDirective = ['$animator', function($animator) {
scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
for (var i= 0, ii=selectedScopes.length; i<ii; i++) {
selectedScopes[i].$destroy();
- animate.leave(selectedElements[i]);
+ $animate.leave(selectedElements[i]);
}
selectedElements = [];
@@ -153,7 +147,7 @@ var ngSwitchDirective = ['$animator', function($animator) {
var anchor = selectedTransclude.element;
selectedElements.push(caseElement);
- animate.enter(caseElement, anchor.parent(), anchor);
+ $animate.enter(caseElement, anchor.parent(), anchor);
});
});
}
diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js
new file mode 100644
index 00000000..ab7a19b0
--- /dev/null
+++ b/src/ngAnimate/animate.js
@@ -0,0 +1,714 @@
+/**
+ * @ngdoc overview
+ * @name ngAnimate
+ * @description
+ *
+ * ngAnimate
+ * =========
+ *
+ * The ngAnimate module is an optional module that comes packed with AngularJS that can be included within an AngularJS
+ * application to provide support for CSS and JavaScript animation hooks.
+ *
+ * To make use of animations with AngularJS, the `angular-animate.js` JavaScript file must be included into your application
+ * and the `ngAnimate` module must be included as a dependency.
+ *
+ * <pre>
+ * angular.module('App', ['ngAnimate']);
+ * </pre>
+ *
+ * Then, to see animations in action, all that is required is to define the appropriate CSS classes
+ * or to register a JavaScript animation via the $animation service. The directives that support animation automatically are:
+ * `ngRepeat`, `ngInclude`, `ngSwitch`, `ngShow`, `ngHide` and `ngView`. Custom directives can take advantage of animation
+ * by using the `$animate` service.
+ *
+ * Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives:
+ *
+ * | Directive | Supported Animations |
+ * |========================================================== |====================================================|
+ * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move |
+ * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
+ * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
+ * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
+ * | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
+ * | {@link ng.directive:ngShow#animations ngShow & ngHide} | show and hide |
+ * | {@link ng.directive:ngShow#animations ngClass} | add and remove |
+ *
+ * You can find out more information about animations upon visiting each directive page.
+ *
+ * Below is an example of how to apply animations to a directive that supports animation hooks:
+ *
+ * <pre>
+ * <style type="text/css">
+ * .slide.ng-enter > div,
+ * .slide.ng-leave > div {
+ * -webkit-transition:0.5s linear all;
+ * -moz-transition:0.5s linear all;
+ * -o-transition:0.5s linear all;
+ * transition:0.5s linear all;
+ * }
+ *
+ * .slide > .ng-enter { } /&#42; starting animations for enter &#42;/
+ * .slide > .ng-enter-active { } /&#42; terminal animations for enter &#42;/
+ * .slide > .ng-leave { } /&#42; starting animations for leave &#42;/
+ * .slide > .ng-leave-active { } /&#42; terminal animations for leave &#42;/
+ * </style>
+ *
+ * <!--
+ * the animate service will automatically add .ng-enter and .ng-leave to the element
+ * to trigger the CSS animations
+ * -->
+ * <ANY class="slide" ng-include="..."></ANY>
+ * </pre>
+ *
+ * Keep in mind that if an animation is running, any child elements cannot be animated until the parent element's
+ * animation has completed.
+ *
+ * <h2>CSS-defined Animations</h2>
+ * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes
+ * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported
+ * and can be used to play along with this naming structure.
+ *
+ * The following code below demonstrates how to perform animations using **CSS transitions** with Angular:
+ *
+ * <pre>
+ * <style type="text/css">
+ * /&#42;
+ * The animate class is apart of the element and the ng-enter class
+ * is attached to the element once the enter animation event is triggered
+ * &#42;/
+ * .reveal-animation.ng-enter {
+ * -webkit-transition: 1s linear all; /&#42; Safari/Chrome &#42;/
+ * -moz-transition: 1s linear all; /&#42; Firefox &#42;/
+ * -o-transition: 1s linear all; /&#42; Opera &#42;/
+ * transition: 1s linear all; /&#42; IE10+ and Future Browsers &#42;/
+ *
+ * /&#42; The animation preparation code &#42;/
+ * opacity: 0;
+ * }
+ *
+ * /&#42;
+ * Keep in mind that you want to combine both CSS
+ * classes together to avoid any CSS-specificity
+ * conflicts
+ * &#42;/
+ * .reveal-animation.ng-enter.ng-enter-active {
+ * /&#42; The animation code itself &#42;/
+ * opacity: 1;
+ * }
+ * </style>
+ *
+ * <div class="view-container">
+ * <div ng-view class="reveal-animation"></div>
+ * </div>
+ * </pre>
+ *
+ * The following code below demonstrates how to perform animations using **CSS animations** with Angular:
+ *
+ * <pre>
+ * <style type="text/css">
+ * .reveal-animation.ng-enter {
+ * -webkit-animation: enter_sequence 1s linear; /&#42; Safari/Chrome &#42;/
+ * -moz-animation: enter_sequence 1s linear; /&#42; Firefox &#42;/
+ * -o-animation: enter_sequence 1s linear; /&#42; Opera &#42;/
+ * animation: enter_sequence 1s linear; /&#42; IE10+ and Future Browsers &#42;/
+ * }
+ * &#64-webkit-keyframes enter_sequence {
+ * from { opacity:0; }
+ * to { opacity:1; }
+ * }
+ * &#64-moz-keyframes enter_sequence {
+ * from { opacity:0; }
+ * to { opacity:1; }
+ * }
+ * &#64-o-keyframes enter_sequence {
+ * from { opacity:0; }
+ * to { opacity:1; }
+ * }
+ * &#64keyframes enter_sequence {
+ * from { opacity:0; }
+ * to { opacity:1; }
+ * }
+ * </style>
+ *
+ * <div class="view-container">
+ * <div ng-view class="reveal-animation"></div>
+ * </div>
+ * </pre>
+ *
+ * Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing.
+ *
+ * Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add
+ * the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically
+ * detect the CSS code to determine when the animation ends. Once the animation is over then both CSS classes will be
+ * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end
+ * immediately resulting in a DOM element that is at its final state. This final state is when the DOM element
+ * has no CSS transition/animation classes applied to it.
+ *
+ * <h2>JavaScript-defined Animations</h2>
+ * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not
+ * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module.
+ *
+ * <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', []);
+ * ngModule.animation('.my-crazy-animation', function() {
+ * return {
+ * enter: function(element, done) {
+ * //run the animation
+ * //!annotate Cancel Animation|This function (if provided) will perform the cancellation of the animation when another is triggered
+ * return function(element, done) {
+ * //cancel the animation
+ * }
+ * }
+ * leave: function(element, done) { },
+ * move: function(element, done) { },
+ * show: function(element, done) { },
+ * hide: function(element, done) { },
+ * addClass: function(element, className, done) { },
+ * removeClass: function(element, className, done) { },
+ * }
+ * });
+ * </pre>
+ *
+ * JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run
+ * a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits
+ * the element's CSS class attribute value and then run the matching animation event function (if found).
+ * In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function
+ * be executed. It should be also noted that only simple or compound class selectors are allowed.
+ *
+ * Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned.
+ * As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run,
+ * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation
+ * or transition code that is defined via a stylesheet).
+ *
+ */
+
+angular.module('ngAnimate', ['ng'])
+
+ /**
+ * @ngdoc object
+ * @name ngAnimate.$animateProvider
+ * @description
+ *
+ * The $AnimationProvider provider allows developers to register and access custom JavaScript animations directly inside
+ * of a module. When an animation is triggered, the $animate service will query the $animation function to find any
+ * animations that match the provided name value.
+ *
+ * Please visit the {@link ngAnimate ngAnimate} module overview page learn more about how to use animations in your application.
+ *
+ */
+ .config(['$provide', '$animateProvider', function($provide, $animateProvider) {
+ var selectors = $animateProvider.$$selectors;
+
+ var NG_ANIMATE_STATE = '$$ngAnimateState';
+ var rootAnimateState = {running:true};
+
+ $provide.decorator('$animate', ['$delegate', '$injector', '$window', '$sniffer', '$rootElement',
+ function($delegate, $injector, $window, $sniffer, $rootElement) {
+
+ var noop = angular.noop;
+ var forEach = angular.forEach;
+
+ $rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
+
+ function lookup(name) {
+ if (name) {
+ var classes = name.substr(1).split('.'),
+ classMap = {};
+
+ for (var i = 0, ii = classes.length; i < ii; i++) {
+ classMap[classes[i]] = true;
+ }
+
+ var matches = [];
+ for (var i = 0, ii = selectors.length; i < ii; i++) {
+ var selectorFactory = selectors[i];
+ var found = true;
+ for(var j = 0, jj = selectorFactory.selectors.length; j < jj; j++) {
+ var klass = selectorFactory.selectors[j];
+ if(klass.length > 0) {
+ found = found && classMap[klass];
+ }
+ }
+ if(found) {
+ matches.push($injector.get(selectorFactory.name));
+ }
+ }
+ return matches;
+ }
+ };
+
+ /**
+ * @ngdoc object
+ * @name ngAnimate.$animate
+ * @requires $window, $sniffer, $rootElement
+ * @function
+ *
+ * @description
+ * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move)
+ * as well as during addClass and removeClass operations. When any of these operations are run, the $animate service
+ * will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object)
+ * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run.
+ *
+ * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives
+ * will work out of the box without any extra configuration.
+ *
+ * Please visit the {@link ngAnimate ngAnimate} module overview page learn more about how to use animations in your application.
+ *
+ */
+ return {
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#enter
+ * @methodOf ngAnimate.$animate
+ * @function
+ *
+ * @description
+ * Appends the element to the parent element that resides in the document and then runs the enter animation. Once
+ * the animation is started, the following CSS classes will be present on the element for the duration of the animation:
+ * <pre>
+ * .ng-enter
+ * .ng-enter-active
+ * </pre>
+ *
+ * Once the animation is complete then the done callback, if provided, will be also fired.
+ *
+ * @param {jQuery/jqLite element} element the element that will be the focus of the enter animation
+ * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the enter animation
+ * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the enter animation
+ * @param {function()=} done callback function that will be called once the animation is complete
+ */
+ enter : function(element, parent, after, done) {
+ $delegate.enter(element, parent, after);
+ performAnimation('enter', 'ng-enter', element, parent, after, done);
+ },
+
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#leave
+ * @methodOf ngAnimate.$animate
+ * @function
+ *
+ * @description
+ * 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:
+ * <pre>
+ * .ng-leave
+ * .ng-leave-active
+ * </pre>
+ *
+ * Once the animation is complete then the done callback, if provided, will be also fired.
+ *
+ * @param {jQuery/jqLite element} element the element that will be the focus of the leave animation
+ * @param {function()=} done callback function that will be called once the animation is complete
+ */
+ leave : function(element, done) {
+ performAnimation('leave', 'ng-leave', element, null, null, function() {
+ $delegate.leave(element, done);
+ });
+ },
+
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#move
+ * @methodOf ngAnimate.$animate
+ * @function
+ *
+ * @description
+ * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parent container or
+ * add the element directly after the after element if present. Then the move animation will be run. Once
+ * the animation is started, the following CSS classes will be added for the duration of the animation:
+ * <pre>
+ * .ng-move
+ * .ng-move-active
+ * </pre>
+ *
+ * Once the animation is complete then the done callback, if provided, will be also fired.
+ *
+ * @param {jQuery/jqLite element} element the element that will be the focus of the move animation
+ * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the move animation
+ * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the move animation
+ * @param {function()=} done callback function that will be called once the animation is complete
+ */
+ move : function(element, parent, after, done) {
+ $delegate.move(element, parent, after);
+ performAnimation('move', 'ng-move', element, null, null, done);
+ },
+
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#show
+ * @methodOf ngAnimate.$animate
+ * @function
+ *
+ * @description
+ * Reveals the element by removing the `ng-hide` class thus performing an animation in the process. During
+ * this animation the CSS classes present on the element will be:
+ *
+ * <pre>
+ * .ng-hide //already on the element if hidden
+ * .ng-hide-remove
+ * .ng-hide-remove-active
+ * </pre>
+ *
+ * Once the animation is complete then all three CSS classes will be removed from the element.
+ * The done callback, if provided, will be also fired once the animation is complete.
+ *
+ * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
+ * @param {function()=} done callback function that will be called once the animation is complete
+ */
+ show : function(element, done) {
+ performAnimation('show', 'ng-hide-remove', element, null, null, function() {
+ $delegate.show(element, done);
+ });
+ },
+
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#hide
+ * @methodOf ngAnimate.$animate
+ *
+ * @description
+ * Sets the element to hidden by adding the `ng-hide` class it. However, before the class is applied
+ * the following CSS classes will be added temporarily to trigger any animation code:
+ *
+ * <pre>
+ * .ng-hide-add
+ * .ng-hide-add-active
+ * </pre>
+ *
+ * Once the animation is complete then both CSS classes will be removed and `ng-hide` will be added to the element.
+ * The done callback, if provided, will be also fired once the animation is complete.
+ *
+ * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
+ * @param {function()=} done callback function that will be called once the animation is complete
+ */
+ hide : function(element, done) {
+ performAnimation('hide', 'ng-hide-add', element, null, null, function() {
+ $delegate.hide(element, done);
+ });
+ },
+
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#addClass
+ * @methodOf ngAnimate.$animate
+ *
+ * @description
+ * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class.
+ * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide
+ * the animate service the setup and active CSS classes in order to trigger the animation.
+ *
+ * For example, upon execution of:
+ *
+ * <pre>
+ * $animate.addClass(element, 'super');
+ * </pre>
+ *
+ * The generated CSS class values present on element will look like:
+ * <pre>
+ * .super-add
+ * .super-add-active
+ * </pre>
+ *
+ * And upon completion, the generated animation CSS classes will be removed from the element, but the className
+ * value will be attached to the element. In this case, based on the previous example, the resulting CSS class for the element
+ * will look like so:
+ *
+ * <pre>
+ * .super
+ * </pre>
+ *
+ * Once this is complete, then the done callback, if provided, will be fired.
+ *
+ * @param {jQuery/jqLite element} element the element that will be animated
+ * @param {string} className the CSS class that will be animated and then attached to the element
+ * @param {function()=} done callback function that will be called once the animation is complete
+ */
+ addClass : function(element, className, done) {
+ performAnimation('addClass', className, element, null, null, function() {
+ $delegate.addClass(element, className, done);
+ });
+ },
+
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#removeClass
+ * @methodOf ngAnimate.$animate
+ *
+ * @description
+ * Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value
+ * from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in
+ * order to provide the animate service the setup and active CSS classes in order to trigger the animation.
+ *
+ * For example, upon the execution of:
+ *
+ * <pre>
+ * $animate.removeClass(element, 'super');
+ * </pre>
+ *
+ * The CSS class values present on element during the animation will look like:
+ *
+ * <pre>
+ * .super //this was here from before
+ * .super-remove
+ * .super-remove-active
+ * </pre>
+ *
+ * And upon completion, the generated animation CSS classes will be removed from the element as well as the
+ * className value that was provided (in this case {@type super} will be removed). Once that is complete, then, if provided,
+ * the done callback will be fired.
+ *
+ * @param {jQuery/jqLite element} element the element that will be animated
+ * @param {string} className the CSS class that will be animated and then removed from the element
+ * @param {function()=} done callback function that will be called once the animation is complete
+ */
+ removeClass : function(element, className, done) {
+ performAnimation('removeClass', className, element, null, null, function() {
+ $delegate.removeClass(element, className, done);
+ });
+ },
+
+ /**
+ * @ngdoc function
+ * @name ngAnimate.$animate#enabled
+ * @methodOf ngAnimate.$animate
+ * @function
+ *
+ * @param {boolean=} If provided then set the animation on or off.
+ * @return {boolean} Current animation state.
+ *
+ * @description
+ * Globally enables/disables animations.
+ *
+ */
+ enabled : function(value) {
+ if (arguments.length) {
+ rootAnimateState.running = !value;
+ }
+ return !rootAnimateState.running;
+ }
+ };
+
+ /*
+ all animations call this shared animation triggering function internally.
+ The event variable refers to the JavaScript animation event that will be triggered
+ and the className value is the name of the animation that will be applied within the
+ CSS code. Element, parent and after are provided DOM elements for the animation
+ and the onComplete callback will be fired once the animation is fully complete.
+ */
+ function performAnimation(event, className, element, parent, after, onComplete) {
+ if(nothingToAnimate(className, element)) {
+ (onComplete || noop)();
+ } else {
+ var classes = ((element.attr('class') || '') + ' ' + className),
+ animationLookup = (' ' + classes).replace(/\s+/g,'.'),
+ animations = [];
+ forEach(lookup(animationLookup), function(animation, index) {
+ animations.push({
+ start : animation[event],
+ done : false
+ });
+ });
+
+ if (!parent) {
+ parent = after ? after.parent() : element.parent();
+ }
+ var disabledAnimation = { running : true };
+
+ //skip the animation if animations are disabled, a parent is already being animated
+ //or the element is not currently attached to the document body.
+ if ((parent.inheritedData(NG_ANIMATE_STATE) || disabledAnimation).running) {
+ //avoid calling done() since there is no need to remove any
+ //data or className values since this happens earlier than that
+ (onComplete || noop)();
+ return;
+ }
+
+ var animationData = element.data(NG_ANIMATE_STATE) || {};
+
+ //if an animation is currently running on the element then lets take the steps
+ //to cancel that animation and fire any required callbacks
+ if(animationData.running) {
+ cancelAnimations(animationData.animations);
+ animationData.done();
+ }
+
+ element.data(NG_ANIMATE_STATE, {
+ running:true,
+ animations:animations,
+ done:done
+ });
+
+ if(event == 'addClass') {
+ className = suffixClasses(className, '-add');
+ } else if(event == 'removeClass') {
+ className = suffixClasses(className, '-remove');
+ }
+
+ element.addClass(className);
+
+ forEach(animations, function(animation, index) {
+ var fn = function() {
+ progress(index);
+ };
+
+ if(animation.start) {
+ if(event == 'addClass' || event == 'removeClass') {
+ animation.cancel = animation.start(element, className, fn);
+ } else {
+ animation.cancel = animation.start(element, fn);
+ }
+ } else {
+ fn();
+ }
+ });
+ }
+
+ function nothingToAnimate(className, element) {
+ return !(className && className.length > 0 && element.length > 0);
+ }
+
+ function cancelAnimations(animations) {
+ forEach(animations, function(animation) {
+ (animation.cancel || noop)(element);
+ });
+ }
+
+ function suffixClasses(classes, suffix) {
+ var className = '';
+ classes = angular.isArray(classes) ? classes : classes.split(/\s+/);
+ forEach(classes, function(klass, i) {
+ if(klass && klass.length > 0) {
+ className += (i > 0 ? ' ' : '') + klass + suffix;
+ }
+ });
+ return className;
+ }
+
+ function progress(index) {
+ animations[index].done = true;
+ for(var i=0;i<animations.length;i++) {
+ if(!animations[i].done) return;
+ }
+ done();
+ };
+
+ function done() {
+ if(!done.hasBeenRun) {
+ done.hasBeenRun = true;
+ element.removeClass(className);
+ element.removeData(NG_ANIMATE_STATE);
+ (onComplete || noop)();
+ }
+ }
+ }
+ }]);
+ }])
+
+ .animation('', ['$window','$sniffer', function($window, $sniffer) {
+ return {
+ enter : function(element, done) {
+ return animate(element, 'ng-enter', done);
+ },
+ leave : function(element, done) {
+ return animate(element, 'ng-leave', done);
+ },
+ move : function(element, done) {
+ return animate(element, 'ng-move', done);
+ },
+ show : function(element, done) {
+ return animate(element, 'ng-hide-remove', done);
+ },
+ hide : function(element, done) {
+ return animate(element, 'ng-hide-add', done);
+ },
+ addClass : function(element, className, done) {
+ return animate(element, className, done);
+ },
+ removeClass : function(element, className, done) {
+ return animate(element, className, done);
+ }
+ };
+
+ function animate(element, className, done) {
+ if (!($sniffer.transitions || $sniffer.animations)) {
+ done();
+ } else {
+ var activeClassName = '';
+ $window.setTimeout(startAnimation, 1);
+
+ //this acts as the cancellation function in case
+ //a new animation is triggered while another animation
+ //is still going on (otherwise the active className
+ //would still hang around until the timer is complete).
+ return onComplete;
+ }
+
+ function parseMaxTime(str) {
+ var total = 0, values = angular.isString(str) ? str.split(/\s*,\s*/) : [];
+ forEach(values, function(value) {
+ total = Math.max(parseFloat(value) || 0, total);
+ });
+ return total;
+ }
+
+ function startAnimation() {
+ var duration = 0;
+ forEach(className.split(' '), function(klass, i) {
+ activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
+ });
+
+ element.addClass(activeClassName);
+
+ //one day all browsers will have these properties
+ var w3cAnimationProp = 'animation';
+ var w3cTransitionProp = 'transition';
+
+ //but some still use vendor-prefixed styles
+ var vendorAnimationProp = $sniffer.vendorPrefix + 'Animation';
+ var vendorTransitionProp = $sniffer.vendorPrefix + 'Transition';
+
+ var durationKey = 'Duration',
+ delayKey = 'Delay',
+ animationIterationCountKey = 'IterationCount';
+
+ //we want all the styles defined before and after
+ var ELEMENT_NODE = 1;
+ forEach(element, function(element) {
+ if (element.nodeType == ELEMENT_NODE) {
+ var elementStyles = $window.getComputedStyle(element) || {};
+
+ var transitionDelay = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + delayKey]),
+ parseMaxTime(elementStyles[vendorTransitionProp + delayKey]));
+
+ var animationDelay = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + delayKey]),
+ parseMaxTime(elementStyles[vendorAnimationProp + delayKey]));
+
+ var transitionDuration = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + durationKey]),
+ parseMaxTime(elementStyles[vendorTransitionProp + durationKey]));
+
+ var animationDuration = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + durationKey]),
+ parseMaxTime(elementStyles[vendorAnimationProp + durationKey]));
+
+ if(animationDuration > 0) {
+ animationDuration *= Math.max(parseInt(elementStyles[w3cAnimationProp + animationIterationCountKey]) || 0,
+ parseInt(elementStyles[vendorAnimationProp + animationIterationCountKey]) || 0,
+ 1);
+ }
+
+ duration = Math.max(animationDelay + animationDuration,
+ transitionDelay + transitionDuration,
+ duration);
+ }
+ });
+
+ $window.setTimeout(onComplete, duration * 1000);
+ }
+
+ function onComplete() {
+ element.removeClass(activeClassName);
+ done();
+ };
+ };
+ }]);
diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js
index e05e7a28..bfb601fd 100644
--- a/src/ngMock/angular-mocks.js
+++ b/src/ngMock/angular-mocks.js
@@ -627,6 +627,43 @@ angular.mock.$LogProvider = function() {
angular.mock.TzDate.prototype = Date.prototype;
})();
+angular.mock.animate = angular.module('mock.animate', ['ng'])
+
+ .config(['$provide', function($provide) {
+
+ $provide.decorator('$animate', function($delegate) {
+ var animate = {
+ queue : [],
+ enabled : $delegate.enabled,
+ process : function(name) {
+ var tick = animate.queue.shift();
+ expect(tick.method).toBe(name);
+ tick.fn();
+ return tick;
+ }
+ };
+
+ forEach(['enter','leave','move','show','hide','addClass','removeClass'], function(method) {
+ animate[method] = function() {
+ var params = arguments;
+ animate.queue.push({
+ method : method,
+ params : params,
+ element : angular.isElement(params[0]) && params[0],
+ parent : angular.isElement(params[1]) && params[1],
+ after : angular.isElement(params[2]) && params[2],
+ fn : function() {
+ $delegate[method].apply($delegate, params);
+ }
+ });
+ };
+ });
+
+ return animate;
+ });
+
+ }]);
+
/**
* @ngdoc function
* @name angular.mock.createMockWindow
diff --git a/src/ngRoute/directive/ngView.js b/src/ngRoute/directive/ngView.js
index 935ba05d..3074df49 100644
--- a/src/ngRoute/directive/ngView.js
+++ b/src/ngRoute/directive/ngView.js
@@ -14,9 +14,6 @@ ngRouteModule.directive('ngView', ngViewFactory);
* Every time the current route changes, the included view changes with it according to the
* configuration of the `$route` service.
*
- * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter**
- * and **leave** effects.
- *
* @animations
* enter - happens just after the ngView contents are changed (when the new view DOM element is inserted into the DOM)
* leave - happens just after the current ngView contents change and just before the former contents are removed from the DOM
@@ -35,8 +32,8 @@ ngRouteModule.directive('ngView', ngViewFactory);
<div
ng-view
- class="example-animate-container"
- ng-animate="{enter: 'example-enter', leave: 'example-leave'}"></div>
+ class="example-$animate-container"
+ ng-$animate="{enter: 'example-enter', leave: 'example-leave'}"></div>
<hr />
<pre>$location.path() = {{main.$location.path()}}</pre>
@@ -71,12 +68,12 @@ ngRouteModule.directive('ngView', ngViewFactory);
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
}
- .example-animate-container {
+ .example-$animate-container {
position:relative;
height:100px;
}
- .example-animate-container > * {
+ .example-$animate-container > * {
display:block;
width:100%;
border-left:1px solid black;
@@ -162,15 +159,14 @@ ngRouteModule.directive('ngView', ngViewFactory);
* @description
* Emitted every time the ngView content is reloaded.
*/
-ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animator'];
-function ngViewFactory( $route, $anchorScroll, $compile, $controller, $animator) {
+ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animate'];
+function ngViewFactory( $route, $anchorScroll, $compile, $controller, $animate) {
return {
restrict: 'ECA',
terminal: true,
link: function(scope, element, attr) {
var lastScope,
- onloadExp = attr.onload || '',
- animate = $animator(scope, attr);
+ onloadExp = attr.onload || '';
scope.$on('$routeChangeSuccess', update);
update();
@@ -184,7 +180,7 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
}
function clearContent() {
- animate.leave(element.contents(), element);
+ $animate.leave(element.contents());
destroyLastScope();
}
@@ -195,7 +191,7 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
if (template) {
clearContent();
var enterElements = jqLite('<div></div>').html(template).contents();
- animate.enter(enterElements, element);
+ $animate.enter(enterElements, element);
var link = $compile(enterElements),
current = $route.current,
diff --git a/test/matchers.js b/test/matchers.js
index 67efd3e7..ef89e3ee 100644
--- a/test/matchers.js
+++ b/test/matchers.js
@@ -30,11 +30,23 @@ beforeEach(function() {
return -1;
}
+ function isNgElementHidden(element) {
+ return angular.element(element).hasClass('ng-hide');
+ };
+
this.addMatchers({
toBeInvalid: cssMatcher('ng-invalid', 'ng-valid'),
toBeValid: cssMatcher('ng-valid', 'ng-invalid'),
toBeDirty: cssMatcher('ng-dirty', 'ng-pristine'),
toBePristine: cssMatcher('ng-pristine', 'ng-dirty'),
+ toBeShown: function() {
+ this.message = valueFn("Expected element to not have 'ng-hide' class");
+ return !isNgElementHidden(this.actual);
+ },
+ toBeHidden: function() {
+ this.message = valueFn("Expected element to have 'ng-hide' class");
+ return isNgElementHidden(this.actual);
+ },
toEqual: function(expected) {
if (this.actual && this.actual.$$log) {
diff --git a/test/ng/animateSpec.js b/test/ng/animateSpec.js
new file mode 100644
index 00000000..7f440bb5
--- /dev/null
+++ b/test/ng/animateSpec.js
@@ -0,0 +1,53 @@
+describe("$animate", function() {
+
+ describe("without animation", function() {
+ beforeEach(inject(function($compile, _$rootElement_, $rootScope) {
+ element = $compile('<div></div>')($rootScope);
+ $rootElement = _$rootElement_;
+ }));
+
+ it("should add element at the start of enter animation", inject(function($animate, $compile, $rootScope) {
+ var child = $compile('<div></div>')($rootScope);
+ expect(element.contents().length).toBe(0);
+ $animate.enter(child, element);
+ expect(element.contents().length).toBe(1);
+ }));
+
+ it("should remove the element at the end of leave animation", inject(function($animate, $compile, $rootScope) {
+ var child = $compile('<div></div>')($rootScope);
+ element.append(child);
+ expect(element.contents().length).toBe(1);
+ $animate.leave(child);
+ expect(element.contents().length).toBe(0);
+ }));
+
+ it("should reorder the move animation", inject(function($animate, $compile, $rootScope) {
+ var child1 = $compile('<div>1</div>')($rootScope);
+ var child2 = $compile('<div>2</div>')($rootScope);
+ element.append(child1);
+ element.append(child2);
+ expect(element.text()).toBe('12');
+ $animate.move(child1, element, child2);
+ expect(element.text()).toBe('21');
+ }));
+
+ it("should animate the show animation event", inject(function($animate) {
+ element.addClass('ng-hide');
+ $animate.show(element);
+ expect(element).toBeShown();
+ }));
+
+ it("should animate the hide animation event", inject(function($animate) {
+ expect(element).toBeShown();
+ $animate.hide(element);
+ expect(element).toBeHidden();
+ }));
+
+ it("should still perform DOM operations even if animations are disabled", inject(function($animate) {
+ $animate.enabled(false);
+ expect(element).toBeShown();
+ $animate.hide(element);
+ expect(element).toBeHidden();
+ }));
+ });
+});
diff --git a/test/ng/animationSpec.js b/test/ng/animationSpec.js
deleted file mode 100644
index 86592643..00000000
--- a/test/ng/animationSpec.js
+++ /dev/null
@@ -1,15 +0,0 @@
-'use strict';
-
-describe('$animation', function() {
-
- it('should allow animation registration', function() {
- var noopCustom = function(){};
- module(function($animationProvider) {
- $animationProvider.register('noop-custom', valueFn(noopCustom));
- });
- inject(function($animation) {
- expect($animation('noop-custom')).toBe(noopCustom);
- });
- });
-
-});
diff --git a/test/ng/animatorSpec.js b/test/ng/animatorSpec.js
deleted file mode 100644
index 8fb9f05f..00000000
--- a/test/ng/animatorSpec.js
+++ /dev/null
@@ -1,773 +0,0 @@
-'use strict';
-
-describe("$animator", function() {
-
- var body, element, $rootElement;
-
- function html(html) {
- body.append($rootElement);
- $rootElement.html(html);
- element = $rootElement.children().eq(0);
- return element;
- }
-
- beforeEach(function() {
- // we need to run animation on attached elements;
- body = jqLite(document.body);
- });
-
- afterEach(function(){
- dealoc(body);
- });
-
- describe("enable / disable", function() {
-
- beforeEach(function() {
- module(function($animationProvider, $provide) {
- $provide.value('$window', angular.mock.createMockWindow());
- });
- });
-
- it("should disable and enable the animations", function() {
- var initialState = null;
- var animator;
-
- angular.bootstrap(body, [function() {
- return function($animator) {
- animator = $animator;
- initialState = $animator.enabled();
- }
- }]);
-
- expect(initialState).toBe(false);
-
- expect(animator.enabled()).toBe(true);
-
- expect(animator.enabled(0)).toBe(false);
- expect(animator.enabled()).toBe(false);
-
- expect(animator.enabled(1)).toBe(true);
- expect(animator.enabled()).toBe(true);
- });
-
- });
-
- describe("without animation", function() {
- var window, animator;
-
- beforeEach(function() {
- module(function($animationProvider, $provide) {
- $provide.value('$window', window = angular.mock.createMockWindow());
- })
- inject(function($animator, $compile, $rootScope, _$rootElement_) {
- animator = $animator($rootScope, {});
- element = $compile('<div></div>')($rootScope);
- $rootElement = _$rootElement_;
- })
- });
-
- it("should add element at the start of enter animation", inject(function($animator, $compile, $rootScope) {
- var child = $compile('<div></div>')($rootScope);
- expect(element.contents().length).toBe(0);
- animator.enter(child, element);
- expect(element.contents().length).toBe(1);
- }));
-
- it("should remove the element at the end of leave animation", inject(function($animator, $compile, $rootScope) {
- var child = $compile('<div></div>')($rootScope);
- element.append(child);
- expect(element.contents().length).toBe(1);
- animator.leave(child, element);
- expect(element.contents().length).toBe(0);
- }));
-
- it("should reorder the move animation", inject(function($animator, $compile, $rootScope) {
- var child1 = $compile('<div>1</div>')($rootScope);
- var child2 = $compile('<div>2</div>')($rootScope);
- element.append(child1);
- element.append(child2);
- expect(element.text()).toBe('12');
- animator.move(child1, element, child2);
- expect(element.text()).toBe('21');
- }));
-
- it("should animate the show animation event", inject(function() {
- element.css('display','none');
- expect(element.css('display')).toBe('none');
- animator.show(element);
- expect(element[0].style.display).toBe('');
- }));
-
- it("should animate the hide animation event", inject(function() {
- element.css('display','block');
- expect(element.css('display')).toBe('block');
- animator.hide(element);
- expect(element.css('display')).toBe('none');
- }));
-
- it("should still perform DOM operations even if animations are disabled", inject(function($animator) {
- $animator.enabled(false);
- element.css('display','block');
- expect(element.css('display')).toBe('block');
- animator.hide(element);
- expect(element.css('display')).toBe('none');
- }));
- });
-
- describe("with polyfill", function() {
-
- var child, after, window, animator;
-
- beforeEach(function() {
- module(function($animationProvider, $provide) {
- $provide.value('$window', window = angular.mock.createMockWindow());
- $animationProvider.register('custom', function() {
- return {
- start: function(element, done) {
- done();
- }
- }
- });
- $animationProvider.register('custom-delay', function() {
- return {
- start: function(element, done) {
- window.setTimeout(done, 2000);
- },
- cancel : function(element) {
- element.addClass('animation-cancelled');
- }
- }
- });
- $animationProvider.register('setup-memo', function() {
- return {
- setup: function(element) {
- return "memento";
- },
- start: function(element, done, memento) {
- element.text(memento);
- done();
- }
- }
- });
- })
- inject(function($animator, $compile, $rootScope, $rootElement) {
- element = $compile('<div></div>')($rootScope);
- child = $compile('<div></div>')($rootScope);
- after = $compile('<div></div>')($rootScope);
- $rootElement.append(element);
- });
- })
-
- it("should animate the enter animation event", inject(function($animator, $rootScope) {
- $animator.enabled(true);
- animator = $animator($rootScope, {
- ngAnimate : '{enter: \'custom\'}'
- });
-
- expect(element.contents().length).toBe(0);
- animator.enter(child, element);
- window.setTimeout.expect(1).process();
- }));
-
- it("should animate the leave animation event", inject(function($animator, $rootScope) {
- $animator.enabled(true);
- animator = $animator($rootScope, {
- ngAnimate : '{leave: \'custom\'}'
- });
-
- element.append(child);
- expect(element.contents().length).toBe(1);
- animator.leave(child, element);
- window.setTimeout.expect(1).process();
- expect(element.contents().length).toBe(0);
- }));
-
- it("should animate the move animation event", inject(function($animator, $compile, $rootScope) {
- $animator.enabled(true);
- animator = $animator($rootScope, {
- ngAnimate : '{move: \'custom\'}'
- });
- $rootScope.$digest();
- var child1 = $compile('<div>1</div>')($rootScope);
- var child2 = $compile('<div>2</div>')($rootScope);
- element.append(child1);
- element.append(child2);
- expect(element.text()).toBe('12');
- animator.move(child1, element, child2);
- expect(element.text()).toBe('21');
- window.setTimeout.expect(1).process();
- }));
-
- it("should animate the show animation event", inject(function($animator, $rootScope) {
- $animator.enabled(true);
- animator = $animator($rootScope, {
- ngAnimate : '{show: \'custom\'}'
- });
- $rootScope.$digest();
- element.css('display','none');
- expect(element.css('display')).toBe('none');
- animator.show(element);
- expect(element[0].style.display).toBe('');
- window.setTimeout.expect(1).process();
- expect(element[0].style.display).toBe('');
- }));
-
- it("should animate the hide animation event", inject(function($animator, $rootScope) {
- $animator.enabled(true);
- animator = $animator($rootScope, {
- ngAnimate : '{hide: \'custom\'}'
- });
- $rootScope.$digest();
- element.css('display','block');
- expect(element.css('display')).toBe('block');
- animator.hide(element);
- expect(element.css('display')).toBe('block');
- window.setTimeout.expect(1).process();
- expect(element.css('display')).toBe('none');
- }));
-
- it("should assign the ngAnimate string to all events if a string is given",
- inject(function($animator, $sniffer, $rootScope) {
- $animator.enabled(true);
- if (!$sniffer.transitions) return;
- animator = $animator($rootScope, {
- ngAnimate : '"custom"'
- });
-
- $rootScope.$digest();
-
- //enter
- animator.enter(child, element);
- expect(child.attr('class')).toContain('custom-enter');
- window.setTimeout.expect(1).process();
- expect(child.attr('class')).toContain('custom-enter-active');
- window.setTimeout.expect(0).process();
-
- //leave
- element.append(after);
- animator.move(child, element, after);
- expect(child.attr('class')).toContain('custom-move');
- window.setTimeout.expect(1).process();
- expect(child.attr('class')).toContain('custom-move-active');
- window.setTimeout.expect(0).process();
-
- //hide
- animator.hide(child);
- expect(child.attr('class')).toContain('custom-hide');
- window.setTimeout.expect(1).process();
- expect(child.attr('class')).toContain('custom-hide-active');
- window.setTimeout.expect(0).process();
-
- //show
- animator.show(child);
- expect(child.attr('class')).toContain('custom-show');
- window.setTimeout.expect(1).process();
- expect(child.attr('class')).toContain('custom-show-active');
- window.setTimeout.expect(0).process();
-
- //leave
- animator.leave(child);
- expect(child.attr('class')).toContain('custom-leave');
- window.setTimeout.expect(1).process();
- expect(child.attr('class')).toContain('custom-leave-active');
- window.setTimeout.expect(0).process();
- }));
-
- it("should run polyfillSetup and return the memento", inject(function($animator, $rootScope) {
- $animator.enabled(true);
- animator = $animator($rootScope, {
- ngAnimate : '{show: \'setup-memo\'}'
- });
- $rootScope.$digest();
- expect(element.text()).toEqual('');
- animator.show(element);
- window.setTimeout.expect(1).process();
- expect(element.text()).toBe('memento');
- }));
-
- it("should not run if animations are disabled", inject(function($animator, $rootScope) {
- $animator.enabled(false);
-
- animator = $animator($rootScope, {
- ngAnimate : '{show: \'setup-memo\'}'
- });
- $rootScope.$digest();
-
- element.text('123');
- expect(element.text()).toBe('123');
- animator.show(element);
- expect(element.text()).toBe('123');
-
- $animator.enabled(true);
-
- animator.show(element);
- window.setTimeout.expect(1).process();
- expect(element.text()).toBe('memento');
- }));
-
- it("should only call done() once and right away if another animation takes place in between",
- inject(function($animator, $rootScope) {
- $animator.enabled(true);
-
- animator = $animator($rootScope, {
- ngAnimate : '{hide: \'custom-delay\', leave: \'custom-delay\'}'
- });
-
- element.append(child);
-
- child.css('display','block');
- animator.hide(child);
- window.setTimeout.expect(1).process();
- expect(child.css('display')).toBe('block');
-
- animator.leave(child);
- expect(child.css('display')).toBe('none'); //hides instantly
-
- //lets change this to prove that done doesn't fire anymore for the previous hide() operation
- child.css('display','block');
-
- window.setTimeout.expect(2000).process();
- expect(child.css('display')).toBe('block'); //doesn't run the done() method to hide it
-
- expect(element.children().length).toBe(1); //still animating
-
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(2000).process();
- expect(element.children().length).toBe(0);
- }));
-
- it("should call the cancel callback when another animation is called on the same element",
- inject(function($animator, $rootScope) {
- $animator.enabled(true);
-
- animator = $animator($rootScope, {
- ngAnimate : '{hide: \'custom-delay\', show: \'custom-delay\'}'
- });
-
- child.css('display','none');
- element.data('foo', 'bar');
- animator.show(element);
- window.setTimeout.expect(1).process();
-
- animator.hide(element);
-
- expect(element.hasClass('animation-cancelled')).toBe(true);
- expect(element.data('foo')).toEqual('bar');
- }));
-
- it("should NOT clobber all data on an element when animation is finished",
- inject(function($animator, $rootScope) {
- $animator.enabled(true);
-
- animator = $animator($rootScope, {
- ngAnimate : '{hide: \'custom-delay\', show: \'custom-delay\'}'
- });
-
- child.css('display','none');
- element.data('foo', 'bar');
-
- animator.show(element);
- window.setTimeout.expect(1).process();
-
- animator.hide(element);
-
- expect(element.data('foo')).toEqual('bar');
- }));
-
-
- it("should properly animate custom animation events", inject(function($animator, $rootScope) {
- $animator.enabled(true);
- animator = $animator($rootScope, {
- ngAnimate : '{custom: \'setup-memo\'}'
- });
-
- element.text('123');
- animator.animate('custom',element);
- window.setTimeout.expect(1).process();
- expect(element.text()).toBe('memento');
- }));
- });
-
- describe("with CSS3", function() {
- var window, animator, prefix, vendorPrefix;
-
- beforeEach(function() {
- module(function($animationProvider, $provide) {
- $provide.value('$window', window = angular.mock.createMockWindow());
- return function($sniffer, _$rootElement_, $animator) {
- vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
- $rootElement = _$rootElement_;
- $animator.enabled(true);
- };
- })
- });
-
- it("should properly animate custom animations for specific animation events",
- inject(function($animator, $rootScope, $compile, $sniffer) {
-
- $animator.enabled(true);
- var element = $compile(html('<div></div>'))($rootScope);
-
- animator = $animator($rootScope, {
- ngAnimate : '{custom: \'special\'}'
- });
-
- animator.animate('custom',element);
- if($sniffer.transitions) {
- expect(element.hasClass('special')).toBe(true);
- window.setTimeout.expect(1).process();
- expect(element.hasClass('special-active')).toBe(true);
- }
- else {
- expect(window.setTimeout.queue.length).toBe(0);
- }
- }));
-
- it("should not animate custom animations if not specifically defined",
- inject(function($animator, $rootScope, $compile) {
-
- $animator.enabled(true);
- var element = $compile(html('<div></div>'))($rootScope);
-
- animator = $animator($rootScope, {
- ngAnimate : '{custom: \'special\'}'
- });
-
- expect(window.setTimeout.queue.length).toBe(0);
- animator.animate('custom1',element);
- expect(element.hasClass('special')).toBe(false);
- expect(window.setTimeout.queue.length).toBe(0);
- }));
-
- it("should properly animate custom animations for general animation events",
- inject(function($animator, $rootScope, $compile, $sniffer) {
-
- $animator.enabled(true);
- var element = $compile(html('<div></div>'))($rootScope);
-
- animator = $animator($rootScope, {
- ngAnimate : "'special'"
- });
-
- animator.animate('custom',element);
- if($sniffer.transitions) {
- expect(element.hasClass('special-custom')).toBe(true);
- window.setTimeout.expect(1).process();
- expect(element.hasClass('special-custom-active')).toBe(true);
- }
- else {
- expect(window.setTimeout.queue.length).toBe(0);
- }
- }));
-
- describe("Animations", function() {
- it("should properly detect and make use of CSS Animations",
- inject(function($animator, $rootScope, $compile, $sniffer) {
- var style = 'animation: some_animation 4s linear 0s 1 alternate;' +
- vendorPrefix + 'animation: some_animation 4s linear 0s 1 alternate;';
- element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
- var animator = $animator($rootScope, {
- ngAnimate : '{show: \'inline-show\'}'
- });
-
- element.css('display','none');
- expect(element.css('display')).toBe('none');
-
- animator.show(element);
- if ($sniffer.animations) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(4000).process();
- }
- else {
- expect(window.setTimeout.queue.length).toBe(0);
- }
- expect(element[0].style.display).toBe('');
- }));
-
- it("should properly detect and make use of CSS Animations with multiple iterations",
- inject(function($animator, $rootScope, $compile, $sniffer) {
- var style = 'animation-duration: 2s;' +
- 'animation-iteration-count: 3;' +
- vendorPrefix + 'animation-duration: 2s;' +
- vendorPrefix + 'animation-iteration-count: 3;';
- element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
- var animator = $animator($rootScope, {
- ngAnimate : '{show: \'inline-show\'}'
- });
-
- element.css('display','none');
- expect(element.css('display')).toBe('none');
-
- animator.show(element);
- if ($sniffer.animations) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(6000).process();
- }
- else {
- expect(window.setTimeout.queue.length).toBe(0);
- }
- expect(element[0].style.display).toBe('');
- }));
-
- it("should fallback to the animation duration if an infinite iteration is provided",
- inject(function($animator, $rootScope, $compile, $sniffer) {
- var style = 'animation-duration: 2s;' +
- 'animation-iteration-count: infinite;' +
- vendorPrefix + 'animation-duration: 2s;' +
- vendorPrefix + 'animation-iteration-count: infinite;';
- element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
- var animator = $animator($rootScope, {
- ngAnimate : '{show: \'inline-show\'}'
- });
-
- element.css('display','none');
- expect(element.css('display')).toBe('none');
-
- animator.show(element);
- if ($sniffer.animations) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(2000).process();
- }
- else {
- expect(window.setTimeout.queue.length).toBe(0);
- }
- expect(element[0].style.display).toBe('');
- }));
-
- it("should consider the animation delay is provided",
- inject(function($animator, $rootScope, $compile, $sniffer) {
- var style = 'animation-duration: 2s;' +
- 'animation-delay: 10s;' +
- 'animation-iteration-count: 5;' +
- vendorPrefix + 'animation-duration: 2s;' +
- vendorPrefix + 'animation-delay: 10s;' +
- vendorPrefix + 'animation-iteration-count: 5;';
- element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
- var animator = $animator($rootScope, {
- ngAnimate : '{show: \'inline-show\'}'
- });
-
- element.css('display','none');
- expect(element.css('display')).toBe('none');
-
- animator.show(element);
- if ($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(20000).process();
- }
- else {
- expect(window.setTimeout.queue.length).toBe(0);
- }
- expect(element[0].style.display).toBe('');
- }));
-
- it("should skip animations if disabled and run when enabled",
- inject(function($animator, $rootScope, $compile, $sniffer) {
- $animator.enabled(false);
- var style = 'animation: some_animation 2s linear 0s 1 alternate;' +
- vendorPrefix + 'animation: some_animation 2s linear 0s 1 alternate;'
-
- element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
- var animator = $animator($rootScope, {
- ngAnimate : '{show: \'inline-show\'}'
- });
- element.css('display','none');
- expect(element.css('display')).toBe('none');
- animator.show(element);
- expect(element[0].style.display).toBe('');
- }));
-
- it("should finish the previous animation when a new animation is started",
- inject(function($animator, $rootScope, $compile, $sniffer) {
- var style = 'animation: some_animation 2s linear 0s 1 alternate;' +
- vendorPrefix + 'animation: some_animation 2s linear 0s 1 alternate;'
-
- element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
- var animator = $animator($rootScope, {
- ngAnimate : '{show: \'show\', hide: \'hide\'}'
- });
-
- animator.show(element);
- if($sniffer.animations) {
- window.setTimeout.expect(1).process();
- expect(element.hasClass('show')).toBe(true);
- expect(element.hasClass('show-active')).toBe(true);
- }
- else { //animation is skipped
- expect(window.setTimeout.queue.length).toBe(0);
- }
-
- animator.hide(element);
- if(!$sniffer.animations) {
- expect(window.setTimeout.queue.length).toBe(0);
- }
- expect(element.hasClass('show')).toBe(false);
- expect(element.hasClass('show-active')).toBe(false);
- }));
- });
-
- describe("Transitions", function() {
- it("should skip transitions if disabled and run when enabled",
- inject(function($animator, $rootScope, $compile, $sniffer) {
- $animator.enabled(false);
- element = $compile(html('<div style="' + vendorPrefix + 'transition: 1s linear all">1</div>'))($rootScope);
- var animator = $animator($rootScope, {
- ngAnimate : '{show: \'inline-show\'}'
- });
-
- element.css('display','none');
- expect(element.css('display')).toBe('none');
- animator.show(element);
- expect(element[0].style.display).toBe('');
-
- $animator.enabled(true);
-
- element.css('display','none');
- expect(element.css('display')).toBe('none');
-
- animator.show(element);
- if ($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(1000).process();
- }
- else {
- expect(window.setTimeout.queue.length).toBe(0);
- }
- expect(element[0].style.display).toBe('');
- }));
-
- it("should skip animations if disabled and run when enabled picking the longest specified duration",
- inject(function($animator, $rootScope, $compile, $sniffer) {
- $animator.enabled(true);
- element = $compile(html('<div style="' + vendorPrefix + 'transition-duration: 1s, 2000ms, 1s; ' + vendorPrefix + 'transition-property: height, left, opacity">foo</div>'))($rootScope);
- var animator = $animator($rootScope, {
- ngAnimate : '{show: \'inline-show\'}'
- });
- element.css('display','none');
- animator.show(element);
- if ($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(2000).process();
- }
- else {
- expect(window.setTimeout.queue.length).toBe(0);
- }
- expect(element[0].style.display).toBe('');
- }));
-
- it("should skip animations if disabled and run when enabled picking the longest specified duration/delay combination",
- inject(function($animator, $rootScope, $compile, $sniffer) {
- $animator.enabled(false);
- element = $compile(html('<div style="' + vendorPrefix +
- 'transition-duration: 1s, 0s, 1s; ' + vendorPrefix +
- 'transition-delay: 2s, 1000ms, 2s; ' + vendorPrefix +
- 'transition-property: height, left, opacity">foo</div>'))($rootScope);
-
- var animator = $animator($rootScope, {
- ngAnimate : '{show: \'inline-show\'}'
- });
-
- element.css('display','none');
- expect(element.css('display')).toBe('none');
- animator.show(element);
- expect(element[0].style.display).toBe('');
-
- $animator.enabled(true);
-
- element.css('display','none');
- expect(element.css('display')).toBe('none');
-
- animator.show(element);
- if ($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(3000).process();
- }
- else {
- expect(window.setTimeout.queue.length).toBe(0);
- }
- expect(element[0].style.display).toBe('');
- }));
-
- it("should select the highest duration and delay",
- inject(function($animator, $rootScope, $compile, $sniffer) {
- var styles = 'transition:1s linear all 2s;' +
- vendorPrefix + 'transition:1s linear all 2s;' +
- 'animation:my_ani 10s 1s;' +
- vendorPrefix + 'animation:my_ani 10s 1s;';
-
- element = $compile(html('<div style="' + styles + '">foo</div>'))($rootScope);
-
- var animator = $animator($rootScope, {
- ngAnimate : '{show: \'inline-show\'}'
- });
-
- element.css('display','none');
- expect(element.css('display')).toBe('none');
-
- animator.show(element);
- if ($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(11000).process();
- }
- else {
- expect(window.setTimeout.queue.length).toBe(0);
- }
- expect(element[0].style.display).toBe('');
- }));
-
- it("should finish the previous transition when a new animation is started",
- inject(function($animator, $rootScope, $compile, $sniffer) {
- var style = 'transition: 1s linear all;' +
- vendorPrefix + 'transition: 1s linear all;'
-
- element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
- var animator = $animator($rootScope, {
- ngAnimate : '{show: \'show\', hide: \'hide\'}'
- });
-
- animator.show(element);
- if($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- expect(element.hasClass('show')).toBe(true);
- expect(element.hasClass('show-active')).toBe(true);
- }
- else { //animation is skipped
- expect(window.setTimeout.queue.length).toBe(0);
- }
-
- animator.hide(element);
- if(!$sniffer.transitions) {
- expect(window.setTimeout.queue.length).toBe(0);
- }
- expect(element.hasClass('show')).toBe(false);
- expect(element.hasClass('show-active')).toBe(false);
- }));
- });
- });
-
- describe('anmation evaluation', function () {
- it('should re-evaluate the animation expression on each animation', inject(function($animator, $rootScope) {
- var parent = jqLite('<div><span></span></div>');
- var element = parent.find('span');
-
- $rootScope.animationFn = function () { throw new Error('too early'); };
- var animate = $animator($rootScope, { ngAnimate: 'animationFn()' });
- var log = '';
-
- $rootScope.animationFn = function () { log = 'abc' };
- animate.enter(element, parent);
- expect(log).toEqual('abc');
-
- $rootScope.animationFn = function () { log = 'xyz' };
- animate.enter(element, parent);
- expect(log).toEqual('xyz');
- }));
- });
-
- it("should throw an error when an invalid ng-animate syntax is provided", inject(function($animator, $rootScope) {
- expect(function() {
- var animate = $animator($rootScope, { ngAnimate: ':' });
- animate.enter();
- }).toThrow("[$parse:syntax] Syntax Error: Token ':' not a primary expression at column 1 of the expression [:] starting at [:].");
- }));
-});
diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js
index 1f5aae95..74090932 100755
--- a/test/ng/compileSpec.js
+++ b/test/ng/compileSpec.js
@@ -3096,8 +3096,8 @@ describe('$compile', function() {
'</div>')($rootScope);
$rootScope.$digest();
var spans = element.find('span');
- expect(spans.eq(0).css('display')).toBe('none');
- expect(spans.eq(1).css('display')).toBe('none');
+ expect(spans.eq(0)).toBeHidden();
+ expect(spans.eq(1)).toBeHidden();
}));
@@ -3216,10 +3216,10 @@ describe('$compile', function() {
'</div>')($rootScope);
$rootScope.$digest();
var spans = element.find('span');
- expect(spans.eq(0).css('display')).toBe('none');
- expect(spans.eq(1).css('display')).toBe('none');
- expect(spans.eq(2).css('display')).toBe('none');
- expect(spans.eq(3).css('display')).toBe('none');
+ expect(spans.eq(0)).toBeHidden();
+ expect(spans.eq(1)).toBeHidden();
+ expect(spans.eq(2)).toBeHidden();
+ expect(spans.eq(3)).toBeHidden();
}));
});
});
diff --git a/test/ng/directive/ngIfSpec.js b/test/ng/directive/ngIfSpec.js
index 0cca57d5..8f2cb793 100755
--- a/test/ng/directive/ngIfSpec.js
+++ b/test/ng/directive/ngIfSpec.js
@@ -75,8 +75,7 @@ describe('ngIf', function () {
});
-describe('ngIf ngAnimate', function () {
- var vendorPrefix, window;
+describe('ngIf animations', function () {
var body, element, $rootElement;
function html(html) {
@@ -85,6 +84,8 @@ describe('ngIf ngAnimate', function () {
return element;
}
+ beforeEach(module('mock.animate'));
+
beforeEach(module(function() {
// we need to run animation on attached elements;
return function(_$rootElement_) {
@@ -99,97 +100,52 @@ describe('ngIf ngAnimate', function () {
dealoc(element);
});
- beforeEach(module(function($animationProvider, $provide) {
- $provide.value('$window', window = angular.mock.createMockWindow());
- return function($sniffer, $animator) {
- vendorPrefix = '-' + $sniffer.vendorPrefix + '-';
- $animator.enabled(true);
+ beforeEach(module(function($animateProvider, $provide) {
+ return function($animate) {
+ $animate.enabled(true);
};
}));
- it('should fire off the enter animation + add and remove the css classes',
- inject(function($compile, $rootScope, $sniffer) {
+ it('should fire off the enter animation',
+ inject(function($compile, $rootScope, $animate) {
+ var item;
var $scope = $rootScope.$new();
- var style = vendorPrefix + 'transition: 1s linear all';
element = $compile(html(
'<div>' +
- '<div ng-if="value" style="' + style + '" ng-animate="{enter: \'custom-enter\', leave: \'custom-leave\'}"><div>Hi</div></div>' +
+ '<div ng-if="value"><div>Hi</div></div>' +
'</div>'
))($scope);
$rootScope.$digest();
$scope.$apply('value = true');
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('Hi');
expect(element.children().length).toBe(1);
- var first = element.children()[0];
-
- if ($sniffer.transitions) {
- expect(first.className).toContain('custom-enter');
- window.setTimeout.expect(1).process();
- expect(first.className).toContain('custom-enter-active');
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
-
- expect(first.className).not.toContain('custom-enter');
- expect(first.className).not.toContain('custom-enter-active');
}));
- it('should fire off the leave animation + add and remove the css classes',
- inject(function ($compile, $rootScope, $sniffer) {
+ it('should fire off the leave animation',
+ inject(function ($compile, $rootScope, $animate) {
+ var item;
var $scope = $rootScope.$new();
- var style = vendorPrefix + 'transition: 1s linear all';
element = $compile(html(
'<div>' +
- '<div ng-if="value" style="' + style + '" ng-animate="{enter: \'custom-enter\', leave: \'custom-leave\'}"><div>Hi</div></div>' +
+ '<div ng-if="value"><div>Hi</div></div>' +
'</div>'
))($scope);
$scope.$apply('value = true');
- expect(element.children().length).toBe(1);
- var first = element.children()[0];
-
- if ($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('Hi');
$scope.$apply('value = false');
- expect(element.children().length).toBe($sniffer.transitions ? 1 : 0);
+ expect(element.children().length).toBe(1);
- if ($sniffer.transitions) {
- expect(first.className).toContain('custom-leave');
- window.setTimeout.expect(1).process();
- expect(first.className).toContain('custom-leave-active');
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
+ item = $animate.process('leave').element;
+ expect(item.text()).toBe('Hi');
expect(element.children().length).toBe(0);
}));
- it('should catch and use the correct duration for animation',
- inject(function ($compile, $rootScope, $sniffer) {
- var $scope = $rootScope.$new();
- var style = vendorPrefix + 'transition: 0.5s linear all';
- element = $compile(html(
- '<div>' +
- '<div ng-if="value" style="' + style + '" ng-animate="{enter: \'custom-enter\', leave: \'custom-leave\'}"><div>Hi</div></div>' +
- '</div>'
- ))($scope);
- $scope.$apply('value = true');
-
- if ($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(500).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
- }));
-
});
diff --git a/test/ng/directive/ngIncludeSpec.js b/test/ng/directive/ngIncludeSpec.js
index 6cb78755..286ee5af 100644
--- a/test/ng/directive/ngIncludeSpec.js
+++ b/test/ng/directive/ngIncludeSpec.js
@@ -341,8 +341,7 @@ describe('ngInclude', function() {
});
});
-describe('ngInclude ngAnimate', function() {
- var vendorPrefix, window;
+describe('ngInclude animations', function() {
var body, element, $rootElement;
function html(html) {
@@ -351,11 +350,6 @@ describe('ngInclude ngAnimate', function() {
return element;
}
- function applyCSS(element, cssProp, cssValue) {
- element.css(cssProp, cssValue);
- element.css(vendorPrefix + cssProp, cssValue);
- }
-
beforeEach(module(function() {
// we need to run animation on attached elements;
return function(_$rootElement_) {
@@ -370,107 +364,51 @@ describe('ngInclude ngAnimate', function() {
dealoc(element);
});
- beforeEach(module(function($animationProvider, $provide) {
- $provide.value('$window', window = angular.mock.createMockWindow());
- return function($sniffer, $animator) {
- vendorPrefix = '-' + $sniffer.vendorPrefix + '-';
- $animator.enabled(true);
- };
- }));
+ beforeEach(module('mock.animate'));
afterEach(function(){
dealoc(element);
});
- it('should fire off the enter animation + add and remove the css classes',
- inject(function($compile, $rootScope, $templateCache, $sniffer) {
+ it('should fire off the enter animation',
+ inject(function($compile, $rootScope, $templateCache, $animate) {
+ var item;
$templateCache.put('enter', [200, '<div>data</div>', {}]);
$rootScope.tpl = 'enter';
element = $compile(html(
'<div ' +
- 'ng-include="tpl" ' +
- 'ng-animate="{enter: \'custom-enter\'}">' +
+ 'ng-include="tpl">' +
'</div>'
))($rootScope);
$rootScope.$digest();
- //if we add the custom css stuff here then it will get picked up before the animation takes place
- var child = jqLite(element.children()[0]);
- applyCSS(child, 'transition', '1s linear all');
-
- if ($sniffer.transitions) {
- expect(child.attr('class')).toContain('custom-enter');
- window.setTimeout.expect(1).process();
-
- expect(child.attr('class')).toContain('custom-enter-active');
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
-
- expect(child.attr('class')).not.toContain('custom-enter');
- expect(child.attr('class')).not.toContain('custom-enter-active');
+ item = $animate.process('leave').element;
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('data');
}));
- it('should fire off the leave animation + add and remove the css classes',
- inject(function($compile, $rootScope, $templateCache, $sniffer) {
+ it('should fire off the leave animation',
+ inject(function($compile, $rootScope, $templateCache, $animate) {
+ var item;
$templateCache.put('enter', [200, '<div>data</div>', {}]);
$rootScope.tpl = 'enter';
element = $compile(html(
'<div ' +
- 'ng-include="tpl" ' +
- 'ng-animate="{leave: \'custom-leave\'}">' +
+ 'ng-include="tpl">' +
'</div>'
))($rootScope);
$rootScope.$digest();
- //if we add the custom css stuff here then it will get picked up before the animation takes place
- var child = jqLite(element.children()[0]);
- applyCSS(child, 'transition', '1s linear all');
+ item = $animate.process('leave').element;
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('data');
$rootScope.tpl = '';
$rootScope.$digest();
- if ($sniffer.transitions) {
- expect(child.attr('class')).toContain('custom-leave');
- window.setTimeout.expect(1).process();
-
- expect(child.attr('class')).toContain('custom-leave-active');
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
-
- expect(child.attr('class')).not.toContain('custom-leave');
- expect(child.attr('class')).not.toContain('custom-leave-active');
- }));
-
- it('should catch and use the correct duration for animation',
- inject(function($compile, $rootScope, $templateCache, $sniffer) {
- $templateCache.put('enter', [200, '<div>data</div>', {}]);
- $rootScope.tpl = 'enter';
- element = $compile(html(
- '<div ' +
- 'ng-include="tpl" ' +
- 'ng-animate="{enter: \'custom-enter\'}">' +
- '</div>'
- ))($rootScope);
- $rootScope.$digest();
-
- //if we add the custom css stuff here then it will get picked up before the animation takes place
- var child = jqLite(element.children()[0]);
- applyCSS(child, 'transition', '0.5s linear all');
-
- $rootScope.tpl = 'enter';
- $rootScope.$digest();
-
- if ($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(500).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
+ item = $animate.process('leave').element;
+ expect(item.text()).toBe('data');
}));
});
diff --git a/test/ng/directive/ngRepeatSpec.js b/test/ng/directive/ngRepeatSpec.js
index 26562f4e..a85fd5ab 100644
--- a/test/ng/directive/ngRepeatSpec.js
+++ b/test/ng/directive/ngRepeatSpec.js
@@ -822,10 +822,30 @@ describe('ngRepeat', function() {
expect(newLis[2]).toEqual(lis[1]);
});
});
+
+ it('should grow multi-node repeater', inject(function($compile, $rootScope) {
+ $rootScope.show = false;
+ $rootScope.books = [
+ {title:'T1', description: 'D1'},
+ {title:'T2', description: 'D2'}
+ ];
+ element = $compile(
+ '<div>' +
+ '<dt ng-repeat-start="book in books">{{book.title}}:</dt>' +
+ '<dd ng-repeat-end>{{book.description}};</dd>' +
+ '</div>')($rootScope);
+
+ $rootScope.$digest();
+ expect(element.text()).toEqual('T1:D1;T2:D2;');
+ $rootScope.books.push({title:'T3', description: 'D3'});
+ $rootScope.$digest();
+ expect(element.text()).toEqual('T1:D1;T2:D2;T3:D3;');
+ }));
+
+
});
-describe('ngRepeat ngAnimate', function() {
- var vendorPrefix, window;
+describe('ngRepeat animations', function() {
var body, element, $rootElement;
function html(html) {
@@ -834,10 +854,7 @@ describe('ngRepeat ngAnimate', function() {
return element;
}
- function applyCSS(element, cssProp, cssValue) {
- element.css(cssProp, cssValue);
- element.css(vendorPrefix + cssProp, cssValue);
- }
+ beforeEach(module('mock.animate'));
beforeEach(module(function() {
// we need to run animation on attached elements;
@@ -853,21 +870,14 @@ describe('ngRepeat ngAnimate', function() {
dealoc(element);
});
- beforeEach(module(function($animationProvider, $provide) {
- $provide.value('$window', window = angular.mock.createMockWindow());
- return function($sniffer, $animator) {
- vendorPrefix = '-' + $sniffer.vendorPrefix + '-';
- $animator.enabled(true);
- };
- }));
+ it('should fire off the enter animation',
+ inject(function($compile, $rootScope, $animate) {
- it('should fire off the enter animation + add and remove the css classes',
- inject(function($compile, $rootScope, $sniffer) {
+ var item;
element = $compile(html(
'<div><div ' +
- 'ng-repeat="item in items" ' +
- 'ng-animate="{enter: \'custom-enter\'}">' +
+ 'ng-repeat="item in items">' +
'{{ item }}' +
'</div></div>'
))($rootScope);
@@ -877,40 +887,24 @@ describe('ngRepeat ngAnimate', function() {
$rootScope.items = ['1','2','3'];
$rootScope.$digest();
- //if we add the custom css stuff here then it will get picked up before the animation takes place
- var kids = element.children();
- for(var i=0;i<kids.length;i++) {
- kids[i] = jqLite(kids[i]);
- applyCSS(kids[i], 'transition', '1s linear all');
- }
-
- if ($sniffer.transitions) {
- angular.forEach(kids, function(kid) {
- expect(kid.attr('class')).toContain('custom-enter');
- window.setTimeout.expect(1).process();
- });
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('1');
- angular.forEach(kids, function(kid) {
- expect(kid.attr('class')).toContain('custom-enter-active');
- window.setTimeout.expect(1000).process();
- });
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('2');
- angular.forEach(kids, function(kid) {
- expect(kid.attr('class')).not.toContain('custom-enter');
- expect(kid.attr('class')).not.toContain('custom-enter-active');
- });
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('3');
}));
- it('should fire off the leave animation + add and remove the css classes',
- inject(function($compile, $rootScope, $sniffer) {
+ it('should fire off the leave animation',
+ inject(function($compile, $rootScope, $animate) {
+
+ var item;
element = $compile(html(
'<div><div ' +
- 'ng-repeat="item in items" ' +
- 'ng-animate="{leave: \'custom-leave\'}">' +
+ 'ng-repeat="item in items">' +
'{{ item }}' +
'</div></div>'
))($rootScope);
@@ -918,36 +912,30 @@ describe('ngRepeat ngAnimate', function() {
$rootScope.items = ['1','2','3'];
$rootScope.$digest();
- //if we add the custom css stuff here then it will get picked up before the animation takes place
- var kids = element.children();
- for(var i=0;i<kids.length;i++) {
- kids[i] = jqLite(kids[i]);
- applyCSS(kids[i], 'transition', '1s linear all');
- }
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('1');
+
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('2');
+
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('3');
$rootScope.items = ['1','3'];
$rootScope.$digest();
- //the last element gets pushed down when it animates
- var kid = jqLite(element.children()[1]);
- if ($sniffer.transitions) {
- expect(kid.attr('class')).toContain('custom-leave');
- window.setTimeout.expect(1).process();
- expect(kid.attr('class')).toContain('custom-leave-active');
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
-
- expect(kid.attr('class')).not.toContain('custom-leave');
- expect(kid.attr('class')).not.toContain('custom-leave-active');
+ item = $animate.process('leave').element;
+ expect(item.text()).toBe('2');
}));
- it('should fire off the move animation + add and remove the css classes',
- inject(function($compile, $rootScope, $sniffer) {
+ it('should fire off the move animation',
+ inject(function($compile, $rootScope, $animate) {
+
+ var item;
+
element = $compile(html(
'<div>' +
- '<div ng-repeat="item in items" ng-animate="{move: \'custom-move\'}">' +
+ '<div ng-repeat="item in items">' +
'{{ item }}' +
'</div>' +
'</div>'
@@ -956,97 +944,23 @@ describe('ngRepeat ngAnimate', function() {
$rootScope.items = ['1','2','3'];
$rootScope.$digest();
- //if we add the custom css stuff here then it will get picked up before the animation takes place
- var kids = element.children();
- for(var i=0;i<kids.length;i++) {
- kids[i] = jqLite(kids[i]);
- applyCSS(kids[i], 'transition', '1s linear all');
- }
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('1');
- $rootScope.items = ['2','3','1'];
- $rootScope.$digest();
-
- //the last element gets pushed down when it animates
- kids = element.children();
- var first = jqLite(kids[0]);
- var left = jqLite(kids[1]);
- var right = jqLite(kids[2]);
-
- if ($sniffer.transitions) {
- expect(first.attr('class')).toContain('custom-move');
- window.setTimeout.expect(1).process();
- expect(left.attr('class')).toContain('custom-move');
- window.setTimeout.expect(1).process();
-
- expect(first.attr('class')).toContain('custom-move-active');
- window.setTimeout.expect(1000).process();
- expect(left.attr('class')).toContain('custom-move-active');
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
-
- expect(first.attr('class')).not.toContain('custom-move');
- expect(first.attr('class')).not.toContain('custom-move-active');
- expect(left.attr('class')).not.toContain('custom-move');
- expect(left.attr('class')).not.toContain('custom-move-active');
- expect(right.attr('class')).not.toContain('custom-move');
- expect(right.attr('class')).not.toContain('custom-move-active');
- }));
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('2');
- it('should catch and use the correct duration for animation',
- inject(function($compile, $rootScope, $sniffer) {
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('3');
- element = $compile(html(
- '<div><div ' +
- 'ng-repeat="item in items" ' +
- 'ng-animate="{enter: \'custom-enter\'}">' +
- '{{ item }}' +
- '</div></div>'
- ))($rootScope);
-
- $rootScope.$digest(); // re-enable the animations;
-
- $rootScope.items = ['a','b'];
+ $rootScope.items = ['2','3','1'];
$rootScope.$digest();
- //if we add the custom css stuff here then it will get picked up before the animation takes place
- var kids = element.children();
- var first = jqLite(kids[0]);
- var second = jqLite(kids[1]);
- var cssProp = 'transition';
- var cssValue = '0.5s linear all';
- applyCSS(first, cssProp, cssValue);
- applyCSS(second, cssProp, cssValue);
-
- if ($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(500).process();
- window.setTimeout.expect(500).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
- }));
+ item = $animate.process('move').element;
+ expect(item.text()).toBe('2');
- it('should grow multi-node repeater', inject(function($compile, $rootScope) {
- $rootScope.show = false;
- $rootScope.books = [
- {title:'T1', description: 'D1'},
- {title:'T2', description: 'D2'}
- ];
- element = $compile(
- '<div>' +
- '<dt ng-repeat-start="book in books">{{book.title}}:</dt>' +
- '<dd ng-repeat-end>{{book.description}};</dd>' +
- '</div>')($rootScope);
-
- $rootScope.$digest();
- expect(element.text()).toEqual('T1:D1;T2:D2;');
- $rootScope.books.push({title:'T3', description: 'D3'});
- $rootScope.$digest();
- expect(element.text()).toEqual('T1:D1;T2:D2;T3:D3;');
+ item = $animate.process('move').element;
+ expect(item.text()).toBe('1');
}));
-
});
diff --git a/test/ng/directive/ngShowHideSpec.js b/test/ng/directive/ngShowHideSpec.js
index 9b95440d..f8193a12 100644
--- a/test/ng/directive/ngShowHideSpec.js
+++ b/test/ng/directive/ngShowHideSpec.js
@@ -13,20 +13,20 @@ describe('ngShow / ngHide', function() {
element = jqLite('<div ng-show="exp"></div>');
element = $compile(element)($rootScope);
$rootScope.$digest();
- expect(isCssVisible(element)).toEqual(false);
+ expect(element).toBeHidden();
$rootScope.exp = true;
$rootScope.$digest();
- expect(isCssVisible(element)).toEqual(true);
+ expect(element).toBeShown();
}));
it('should make hidden element visible', inject(function($rootScope, $compile) {
- element = jqLite('<div style="display: none" ng-show="exp"></div>');
+ element = jqLite('<div class="ng-hide" ng-show="exp"></div>');
element = $compile(element)($rootScope);
- expect(isCssVisible(element)).toBe(false);
+ expect(element).toBeHidden();
$rootScope.exp = true;
$rootScope.$digest();
- expect(isCssVisible(element)).toBe(true);
+ expect(element).toBeShown();
}));
});
@@ -34,17 +34,15 @@ describe('ngShow / ngHide', function() {
it('should hide an element', inject(function($rootScope, $compile) {
element = jqLite('<div ng-hide="exp"></div>');
element = $compile(element)($rootScope);
- expect(isCssVisible(element)).toBe(true);
+ expect(element).toBeShown();
$rootScope.exp = true;
$rootScope.$digest();
- expect(isCssVisible(element)).toBe(false);
+ expect(element).toBeHidden();
}));
});
});
-describe('ngShow / ngHide - ngAnimate', function() {
- var window;
- var vendorPrefix;
+describe('ngShow / ngHide animations', function() {
var body, element, $rootElement;
function html(html) {
@@ -65,152 +63,57 @@ describe('ngShow / ngHide - ngAnimate', function() {
body.removeAttr('ng-animation-running');
});
- beforeEach(module(function($animationProvider, $provide) {
- $provide.value('$window', window = angular.mock.createMockWindow());
- return function($sniffer, _$rootElement_, $animator) {
- vendorPrefix = '-' + $sniffer.vendorPrefix + '-';
+ beforeEach(module('mock.animate'));
+
+ beforeEach(module(function($animateProvider, $provide) {
+ return function(_$rootElement_) {
$rootElement = _$rootElement_;
- $animator.enabled(true);
};
}));
describe('ngShow', function() {
- it('should fire off the animator.show and animator.hide animation', inject(function($compile, $rootScope, $sniffer) {
+ it('should fire off the $animate.show and $animate.hide animation', inject(function($compile, $rootScope, $animate) {
+ var item;
var $scope = $rootScope.$new();
$scope.on = true;
element = $compile(html(
- '<div ' +
- 'style="'+vendorPrefix+'transition: 1s linear all"' +
- 'ng-show="on" ' +
- 'ng-animate="{show: \'custom-show\', hide: \'custom-hide\', animateFirst: true}">' +
- '</div>'
+ '<div ng-show="on">data</div>'
))($scope);
$scope.$digest();
- if ($sniffer.transitions) {
- expect(element.attr('class')).toContain('custom-show');
- window.setTimeout.expect(1).process();
-
- expect(element.attr('class')).toContain('custom-show-active');
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
-
- expect(element.attr('class')).not.toContain('custom-show-active');
- expect(element.attr('class')).not.toContain('custom-show');
+ item = $animate.process('show').element;
+ expect(item.text()).toBe('data');
+ expect(item).toBeShown();
$scope.on = false;
$scope.$digest();
- if ($sniffer.transitions) {
- expect(element.attr('class')).toContain('custom-hide');
- window.setTimeout.expect(1).process();
- expect(element.attr('class')).toContain('custom-hide-active');
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
-
- expect(element.attr('class')).not.toContain('custom-hide-active');
- expect(element.attr('class')).not.toContain('custom-hide');
- }));
- it('should skip animation if parent animation running', function() {
- var fired = false;
- inject(function($animator, $compile, $rootScope, $sniffer) {
- $animator.enabled(true);
- $rootScope.$digest();
- $rootScope.val = true;
- var element = $compile(html('<div ng-show="val" ng-animate="\'animation\'">123</div>'))($rootScope);
- $rootElement.controller('ngAnimate').running = true;
- element.css('display','none');
- expect(element.css('display')).toBe('none');
-
- $rootScope.$digest();
- expect(element[0].style.display).toBe('');
- expect(fired).toBe(false);
-
- $rootElement.controller('ngAnimate').running = false;
- $rootScope.val = false;
- $rootScope.$digest();
- if ($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(0).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
- expect(element[0].style.display).toBe('none');
- });
- });
+ item = $animate.process('hide').element;
+ expect(item.text()).toBe('data');
+ expect(item).toBeHidden();
+ }));
});
describe('ngHide', function() {
- it('should fire off the animator.show and animator.hide animation', inject(function($compile, $rootScope, $sniffer) {
+ it('should fire off the $animate.show and $animate.hide animation', inject(function($compile, $rootScope, $animate) {
+ var item;
var $scope = $rootScope.$new();
$scope.off = true;
element = $compile(html(
- '<div ' +
- 'style="'+vendorPrefix+'transition: 1s linear all"' +
- 'ng-hide="off" ' +
- 'ng-animate="{show: \'custom-show\', hide: \'custom-hide\', animateFirst: true}">' +
- '</div>'
+ '<div ng-hide="off">datum</div>'
))($scope);
$scope.$digest();
- if ($sniffer.transitions) {
- expect(element.attr('class')).toContain('custom-hide');
- window.setTimeout.expect(1).process();
-
- expect(element.attr('class')).toContain('custom-hide-active');
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
-
- expect(element.attr('class')).not.toContain('custom-hide-active');
- expect(element.attr('class')).not.toContain('custom-hide');
+ item = $animate.process('hide').element;
+ expect(item.text()).toBe('datum');
+ expect(item).toBeHidden();
$scope.off = false;
$scope.$digest();
- if ($sniffer.transitions) {
- expect(element.attr('class')).toContain('custom-show');
- window.setTimeout.expect(1).process();
- expect(element.attr('class')).toContain('custom-show-active');
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
-
- expect(element.attr('class')).not.toContain('custom-show-active');
- expect(element.attr('class')).not.toContain('custom-show');
+ item = $animate.process('show').element;
+ expect(item.text()).toBe('datum');
+ expect(item).toBeShown();
}));
-
- it('should disable animation when parent animation is running', function() {
- var fired = false;
- module(function($animationProvider) {
- $animationProvider.register('destructive-animation', function() {
- return {
- setup : function() {},
- start : function(element, done) {
- fired = true;
- }
- };
- });
- });
- inject(function($compile, $rootScope) {
- $rootScope.val = false;
- var element = $compile(html('<div ng-hide="val" ng-animate="{ hide:\'destructive-animation\' }">123</div>'))($rootScope);
- $rootElement.controller('ngAnimate').running = true;
- element.css('display','block');
- expect(element.css('display')).toBe('block');
-
- $rootScope.val = true;
- $rootScope.$digest();
-
- expect(element.css('display')).toBe('none');
- expect(fired).toBe(false);
- });
- });
});
});
diff --git a/test/ng/directive/ngSwitchSpec.js b/test/ng/directive/ngSwitchSpec.js
index ab231ec2..8750b187 100644
--- a/test/ng/directive/ngSwitchSpec.js
+++ b/test/ng/directive/ngSwitchSpec.js
@@ -214,8 +214,7 @@ describe('ngSwitch', function() {
}));
});
-describe('ngSwitch ngAnimate', function() {
- var vendorPrefix, window;
+describe('ngSwitch animations', function() {
var body, element, $rootElement;
function html(html) {
@@ -224,6 +223,8 @@ describe('ngSwitch ngAnimate', function() {
return element;
}
+ beforeEach(module('mock.animate'));
+
beforeEach(module(function() {
// we need to run animation on attached elements;
return function(_$rootElement_) {
@@ -238,23 +239,15 @@ describe('ngSwitch ngAnimate', function() {
dealoc(element);
});
- beforeEach(module(function($animationProvider, $provide) {
- $provide.value('$window', window = angular.mock.createMockWindow());
- return function($sniffer, $animator) {
- vendorPrefix = '-' + $sniffer.vendorPrefix + '-';
- $animator.enabled(true);
- };
- }));
-
- it('should fire off the enter animation + set and remove the classes',
- inject(function($compile, $rootScope, $sniffer) {
+ it('should fire off the enter animation',
+ inject(function($compile, $rootScope, $animate) {
+ var item;
var $scope = $rootScope.$new();
- var style = vendorPrefix + 'transition: 1s linear all';
element = $compile(html(
- '<div ng-switch on="val" ng-animate="{enter: \'cool-enter\', leave: \'cool-leave\'}">' +
- '<div ng-switch-when="one" style="' + style + '">one</div>' +
- '<div ng-switch-when="two" style="' + style + '">two</div>' +
- '<div ng-switch-when="three" style="' + style + '">three</div>' +
+ '<div ng-switch on="val">' +
+ '<div ng-switch-when="one">one</div>' +
+ '<div ng-switch-when="two">two</div>' +
+ '<div ng-switch-when="three">three</div>' +
'</div>'
))($scope);
@@ -262,33 +255,20 @@ describe('ngSwitch ngAnimate', function() {
$scope.val = 'one';
$scope.$digest();
- expect(element.children().length).toBe(1);
- var first = element.children()[0];
-
- if ($sniffer.transitions) {
- expect(first.className).toContain('cool-enter');
- window.setTimeout.expect(1).process();
-
- expect(first.className).toContain('cool-enter-active');
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
-
- expect(first.className).not.toContain('cool-enter');
- expect(first.className).not.toContain('cool-enter-active');
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('one');
}));
- it('should fire off the leave animation + set and remove the classes',
- inject(function($compile, $rootScope, $sniffer) {
+ it('should fire off the leave animation',
+ inject(function($compile, $rootScope, $animate) {
+ var item;
var $scope = $rootScope.$new();
- var style = vendorPrefix + 'transition: 1s linear all';
element = $compile(html(
- '<div ng-switch on="val" ng-animate="{enter: \'cool-enter\', leave: \'cool-leave\'}">' +
- '<div ng-switch-when="one" style="' + style + '">one</div>' +
- '<div ng-switch-when="two" style="' + style + '">two</div>' +
- '<div ng-switch-when="three" style="' + style + '">three</div>' +
+ '<div ng-switch on="val">' +
+ '<div ng-switch-when="one">one</div>' +
+ '<div ng-switch-when="two">two</div>' +
+ '<div ng-switch-when="three">three</div>' +
'</div>'
))($scope);
@@ -296,59 +276,17 @@ describe('ngSwitch ngAnimate', function() {
$scope.val = 'two';
$scope.$digest();
- if ($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('two');
$scope.val = 'three';
$scope.$digest();
- expect(element.children().length).toBe($sniffer.transitions ? 2 : 1);
- var first = element.children()[0];
-
-
- if ($sniffer.transitions) {
- expect(first.className).toContain('cool-leave');
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(1).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
+ item = $animate.process('leave').element;
+ expect(item.text()).toBe('two');
-
- if ($sniffer.transitions) {
- expect(first.className).toContain('cool-leave-active');
- window.setTimeout.expect(1000).process();
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
-
- expect(first.className).not.toContain('cool-leave');
- expect(first.className).not.toContain('cool-leave-active');
- }));
-
- it('should catch and use the correct duration for animation',
- inject(function($compile, $rootScope, $sniffer) {
- element = $compile(html(
- '<div ng-switch on="val" ng-animate="{enter: \'cool-enter\', leave: \'cool-leave\'}">' +
- '<div ng-switch-when="one" style="' + vendorPrefix + 'transition: 0.5s linear all">one</div>' +
- '</div>'
- ))($rootScope);
-
- $rootScope.$digest(); // re-enable the animations;
- $rootScope.val = 'one';
- $rootScope.$digest();
-
- if ($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect(500).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('three');
}));
});
diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js
new file mode 100644
index 00000000..52bf5e0b
--- /dev/null
+++ b/test/ngAnimate/animateSpec.js
@@ -0,0 +1,1524 @@
+'use strict';
+
+describe("ngAnimate", function() {
+
+ beforeEach(module('ngAnimate'));
+
+ describe("$animate", function() {
+
+ var body, element, $rootElement;
+
+ function html(html) {
+ body.append($rootElement);
+ $rootElement.html(html);
+ element = $rootElement.children().eq(0);
+ return element;
+ }
+
+ beforeEach(module(function() {
+ // we need to run animation on attached elements;
+ body = jqLite(document.body);
+ return function($animate) {
+ $animate.enabled(true);
+ };
+ }));
+
+ afterEach(function(){
+ dealoc(body);
+ });
+
+ describe("enable / disable", function() {
+
+ beforeEach(function() {
+ module(function($animateProvider, $provide) {
+ $provide.value('$window', angular.mock.createMockWindow());
+ });
+ });
+
+ it("should disable and enable the animations", function() {
+ var $animate, initialState = null;
+
+ angular.bootstrap(body, ['ngAnimate',function() {
+ return function(_$animate_) {
+ $animate = _$animate_;
+ initialState = $animate.enabled();
+ }
+ }]);
+
+ expect(initialState).toBe(false);
+
+ expect($animate.enabled()).toBe(true);
+
+ expect($animate.enabled(0)).toBe(false);
+ expect($animate.enabled()).toBe(false);
+
+ expect($animate.enabled(1)).toBe(true);
+ expect($animate.enabled()).toBe(true);
+ });
+
+ });
+
+ describe("with polyfill", function() {
+
+ var child, after, window;
+
+ beforeEach(function() {
+ module(function($animateProvider, $provide) {
+ $provide.value('$window', window = angular.mock.createMockWindow());
+ $animateProvider.register('.custom', function() {
+ return {
+ start: function(element, done) {
+ done();
+ }
+ }
+ });
+ $animateProvider.register('.custom-delay', function() {
+ function animate(element, done) {
+ done = arguments.length == 3 ? arguments[2] : done;
+ window.setTimeout(done, 2000);
+ return function() {
+ element.addClass('animation-cancelled');
+ }
+ }
+ return {
+ show : animate,
+ hide : animate,
+ leave : animate,
+ addClass : animate,
+ removeClass : animate
+ }
+ });
+ $animateProvider.register('.custom-long-delay', function() {
+ function animate(element, done) {
+ done = arguments.length == 3 ? arguments[2] : done;
+ window.setTimeout(done, 20000);
+ return function() {
+ element.addClass('animation-cancelled');
+ }
+ }
+ return {
+ show : animate,
+ hide : animate,
+ leave : animate,
+ addClass : animate,
+ removeClass : animate
+ }
+ });
+ $animateProvider.register('.setup-memo', function() {
+ return {
+ show: function(element, done) {
+ element.text('memento');
+ done();
+ }
+ }
+ });
+ return function($animate, $compile, $rootScope, $rootElement) {
+ element = $compile('<div></div>')($rootScope);
+ child = $compile('<div></div>')($rootScope);
+ after = $compile('<div></div>')($rootScope);
+ $rootElement.append(element);
+ };
+ });
+ })
+
+ it("should animate the enter animation event", inject(function($animate, $rootScope, $sniffer) {
+ expect(element.contents().length).toBe(0);
+ $animate.enter(child, element);
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(0).process();
+ }
+ expect(element.contents().length).toBe(1);
+ }));
+
+ it("should animate the leave animation event", inject(function($animate, $rootScope, $sniffer) {
+ element.append(child);
+ expect(element.contents().length).toBe(1);
+ $animate.leave(child);
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(0).process();
+ }
+ expect(element.contents().length).toBe(0);
+ }));
+
+ it("should animate the move animation event", inject(function($animate, $compile, $rootScope, $sniffer) {
+ $rootScope.$digest();
+ var child1 = $compile('<div>1</div>')($rootScope);
+ var child2 = $compile('<div>2</div>')($rootScope);
+ element.append(child1);
+ element.append(child2);
+ expect(element.text()).toBe('12');
+ $animate.move(child1, element, child2);
+ expect(element.text()).toBe('21');
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ }
+ }));
+
+ it("should animate the show animation event", inject(function($animate, $rootScope, $sniffer) {
+ $rootScope.$digest();
+ element.addClass('ng-hide');
+ expect(element).toBeHidden();
+ $animate.show(element);
+ if($sniffer.transitions) {
+ expect(element.hasClass('ng-hide-remove')).toBe(true);
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('ng-hide-remove-active')).toBe(true);
+ window.setTimeout.expect(0).process();
+ }
+ expect(element).toBeShown();
+ }));
+
+ it("should animate the hide animation event", inject(function($animate, $rootScope, $sniffer) {
+ $rootScope.$digest();
+ expect(element).toBeShown();
+ $animate.hide(element);
+ if($sniffer.transitions) {
+ expect(element.hasClass('ng-hide-add')).toBe(true);
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('ng-hide-add-active')).toBe(true);
+ window.setTimeout.expect(0).process();
+ }
+ expect(element).toBeHidden();
+ }));
+
+ it("should assign the ngAnimate string to all events if a string is given",
+ inject(function($animate, $sniffer, $rootScope) {
+
+ if (!$sniffer.transitions) return;
+
+ $rootScope.$digest();
+
+ //enter
+ $animate.enter(child, element);
+ expect(child.attr('class')).toContain('ng-enter');
+ window.setTimeout.expect(1).process();
+ expect(child.attr('class')).toContain('ng-enter-active');
+ window.setTimeout.expect(0).process();
+
+ //leave
+ element.append(after);
+ $animate.move(child, element, after);
+ expect(child.attr('class')).toContain('ng-move');
+ window.setTimeout.expect(1).process();
+ expect(child.attr('class')).toContain('ng-move-active');
+ window.setTimeout.expect(0).process();
+
+ //hide
+ $animate.hide(child);
+ expect(child.attr('class')).toContain('ng-hide-add');
+ window.setTimeout.expect(1).process();
+ expect(child.attr('class')).toContain('ng-hide-add-active');
+ window.setTimeout.expect(0).process();
+
+ //show
+ $animate.show(child);
+ expect(child.attr('class')).toContain('ng-hide-remove');
+ window.setTimeout.expect(1).process();
+ expect(child.attr('class')).toContain('ng-hide-remove-active');
+ window.setTimeout.expect(0).process();
+
+ //leave
+ $animate.leave(child);
+ expect(child.attr('class')).toContain('ng-leave');
+ window.setTimeout.expect(1).process();
+ expect(child.attr('class')).toContain('ng-leave-active');
+ window.setTimeout.expect(0).process();
+ }));
+
+ it("should not run if animations are disabled", inject(function($animate, $rootScope, $sniffer) {
+ $animate.enabled(false);
+
+ $rootScope.$digest();
+
+ element.addClass('setup-memo');
+
+ element.text('123');
+ expect(element.text()).toBe('123');
+ $animate.show(element);
+ expect(element.text()).toBe('123');
+
+ $animate.enabled(true);
+
+ $animate.show(element);
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(0).process();
+ }
+ expect(element.text()).toBe('memento');
+ }));
+
+ it("should only call done() once and right away if another animation takes place in between",
+ inject(function($animate, $rootScope, $sniffer) {
+
+ element.append(child);
+ child.addClass('custom-delay');
+
+ expect(element).toBeShown();
+ $animate.hide(child);
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ }
+ expect(child).toBeShown();
+
+ $animate.leave(child);
+ expect(child).toBeHidden(); //hides instantly
+
+ //lets change this to prove that done doesn't fire anymore for the previous hide() operation
+ child.css('display','block');
+ child.removeClass('ng-hide');
+
+ window.setTimeout.expect(2000).process();
+ if($sniffer.transitions) {
+ window.setTimeout.expect(0).process();
+ }
+ expect(child).toBeShown();
+
+ expect(element.children().length).toBe(1); //still animating
+
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ }
+ window.setTimeout.expect(2000).process();
+ if($sniffer.transitions) {
+ window.setTimeout.expect(0).process();
+ }
+ expect(element.children().length).toBe(0);
+ }));
+
+ it("should call the cancel callback when another animation is called on the same element",
+ inject(function($animate, $rootScope, $sniffer) {
+
+ element.append(child);
+
+ child.addClass('custom-delay ng-hide');
+ $animate.show(child);
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ }
+
+ $animate.hide(child);
+
+ expect(child.hasClass('animation-cancelled')).toBe(true);
+ }));
+
+
+ it("should NOT clobber all data on an element when animation is finished",
+ inject(function($animate, $rootScope, $sniffer) {
+
+ child.css('display','none');
+ element.data('foo', 'bar');
+
+ $animate.show(element);
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ }
+
+ $animate.hide(element);
+
+ expect(element.data('foo')).toEqual('bar');
+ }));
+
+
+ it("should allow multiple JS animations which run in parallel",
+ inject(function($animate, $rootScope, $compile, $sniffer) {
+
+ $animate.addClass(element, 'custom-delay custom-long-delay');
+ if($sniffer.transitions) {
+ expect(element[0].className).toContain('custom-delay-add custom-long-delay-add');
+ window.setTimeout.expect(1).process();
+ expect(element[0].className).toContain('custom-delay-add-active custom-long-delay-add-active');
+ }
+ window.setTimeout.expect(2000).process();
+ window.setTimeout.expect(20000).process();
+ if($sniffer.transitions) {
+ window.setTimeout.expect(0).process(); //css animation
+ }
+
+ expect(element.hasClass('custom-delay')).toBe(true);
+ expect(element.hasClass('custom-delay-add')).toBe(false);
+ expect(element.hasClass('custom-delay-add-active')).toBe(false);
+
+ expect(element.hasClass('custom-long-delay')).toBe(true);
+ expect(element.hasClass('custom-long-delay-add')).toBe(false);
+ expect(element.hasClass('custom-long-delay-add-active')).toBe(false);
+ }));
+
+ it("should allow both multiple JS and CSS animations which run in parallel",
+ inject(function($animate, $rootScope, $compile, $sniffer, _$rootElement_) {
+ $rootElement = _$rootElement_;
+
+ var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
+ var style = 'transition: 1s linear all;' +
+ vendorPrefix + 'transition: 1s linear all;'
+
+ element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
+ element.addClass('custom-delay custom-long-delay');
+ $rootScope.$digest();
+
+ $animate.show(element);
+
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ }
+ window.setTimeout.expect(2000).process(); //1st JavaScript Animation
+ window.setTimeout.expect(20000).process(); //2nd JavaScript Animation
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1000).process(); //CSS animation
+ }
+
+ expect(element.hasClass('custom-delay')).toBe(true);
+ expect(element.hasClass('custom-delay-add')).toBe(false);
+ expect(element.hasClass('custom-delay-add-active')).toBe(false);
+
+ expect(element.hasClass('custom-long-delay')).toBe(true);
+ expect(element.hasClass('custom-long-delay-add')).toBe(false);
+ expect(element.hasClass('custom-long-delay-add-active')).toBe(false);
+ }));
+ });
+
+ describe("with CSS3", function() {
+ var window, prefix, vendorPrefix;
+
+ beforeEach(function() {
+ module(function($animateProvider, $provide) {
+ $provide.value('$window', window = angular.mock.createMockWindow());
+ return function($sniffer, _$rootElement_, $animate) {
+ vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
+ $rootElement = _$rootElement_;
+ };
+ })
+ });
+
+ describe("Animations", function() {
+ it("should properly detect and make use of CSS Animations",
+ inject(function($animate, $rootScope, $compile, $sniffer) {
+ var style = 'animation: some_animation 4s linear 0s 1 alternate;' +
+ vendorPrefix + 'animation: some_animation 4s linear 0s 1 alternate;';
+ element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
+
+ element.addClass('ng-hide');
+ expect(element).toBeHidden();
+
+ $animate.show(element);
+ if ($sniffer.animations) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(4000).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ expect(element).toBeShown();
+ }));
+
+ it("should properly detect and make use of CSS Animations with multiple iterations",
+ inject(function($animate, $rootScope, $compile, $sniffer) {
+ var style = 'animation-duration: 2s;' +
+ 'animation-iteration-count: 3;' +
+ vendorPrefix + 'animation-duration: 2s;' +
+ vendorPrefix + 'animation-iteration-count: 3;';
+ element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
+
+ element.addClass('ng-hide');
+ expect(element).toBeHidden();
+
+ $animate.show(element);
+ if ($sniffer.animations) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(6000).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ expect(element).toBeShown();
+ }));
+
+ it("should fallback to the animation duration if an infinite iteration is provided",
+ inject(function($animate, $rootScope, $compile, $sniffer) {
+ var style = 'animation-duration: 2s;' +
+ 'animation-iteration-count: infinite;' +
+ vendorPrefix + 'animation-duration: 2s;' +
+ vendorPrefix + 'animation-iteration-count: infinite;';
+ element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
+
+ element.addClass('ng-hide');
+ expect(element).toBeHidden();
+
+ $animate.show(element);
+ if ($sniffer.animations) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(2000).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ expect(element).toBeShown();
+ }));
+
+ it("should consider the animation delay is provided",
+ inject(function($animate, $rootScope, $compile, $sniffer) {
+ var style = 'animation-duration: 2s;' +
+ 'animation-delay: 10s;' +
+ 'animation-iteration-count: 5;' +
+ vendorPrefix + 'animation-duration: 2s;' +
+ vendorPrefix + 'animation-delay: 10s;' +
+ vendorPrefix + 'animation-iteration-count: 5;';
+ element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
+
+ element.addClass('ng-hide');
+ expect(element).toBeHidden();
+
+ $animate.show(element);
+ if ($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(20000).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ expect(element).toBeShown();
+ }));
+
+ it("should skip animations if disabled and run when enabled",
+ inject(function($animate, $rootScope, $compile, $sniffer) {
+ $animate.enabled(false);
+ var style = 'animation: some_animation 2s linear 0s 1 alternate;' +
+ vendorPrefix + 'animation: some_animation 2s linear 0s 1 alternate;'
+
+ element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
+ element.addClass('ng-hide');
+ expect(element).toBeHidden();
+ $animate.show(element);
+ expect(element).toBeShown();
+ }));
+
+ it("should finish the previous animation when a new animation is started",
+ inject(function($animate, $rootScope, $compile, $sniffer) {
+ var style = 'animation: some_animation 2s linear 0s 1 alternate;' +
+ vendorPrefix + 'animation: some_animation 2s linear 0s 1 alternate;'
+
+ element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
+ element.addClass('custom');
+
+ $animate.show(element);
+ if($sniffer.animations) {
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('ng-hide-remove')).toBe(true);
+ expect(element.hasClass('ng-hide-remove-active')).toBe(true);
+ }
+ else { //animation is skipped
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+
+ $animate.hide(element);
+ expect(element.hasClass('ng-hide-remove')).toBe(false); //added right away
+
+ if($sniffer.animations) { //cleanup some pending animations
+ window.setTimeout.expect(2000).process();
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('ng-hide-add')).toBe(true);
+ expect(element.hasClass('ng-hide-add-active')).toBe(true);
+ }
+
+ expect(element.hasClass('ng-hide-remove-active')).toBe(false);
+ }));
+ });
+
+ describe("Transitions", function() {
+ it("should skip transitions if disabled and run when enabled",
+ inject(function($animate, $rootScope, $compile, $sniffer) {
+ $animate.enabled(false);
+ element = $compile(html('<div style="' + vendorPrefix + 'transition: 1s linear all">1</div>'))($rootScope);
+
+ element.addClass('ng-hide');
+ expect(element).toBeHidden();
+ $animate.show(element);
+ expect(element).toBeShown();
+
+ $animate.enabled(true);
+
+ element.addClass('ng-hide');
+ expect(element).toBeHidden();
+
+ $animate.show(element);
+ if ($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(1000).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ expect(element).toBeShown();
+ }));
+
+ it("should skip animations if disabled and run when enabled picking the longest specified duration",
+ inject(function($animate, $rootScope, $compile, $sniffer) {
+ element = $compile(html('<div style="' + vendorPrefix + 'transition-duration: 1s, 2000ms, 1s; ' + vendorPrefix + 'transition-property: height, left, opacity">foo</div>'))($rootScope);
+ element.addClass('ng-hide');
+ $animate.show(element);
+ if ($sniffer.transitions) {
+ expect(element).toBeHidden();
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(2000).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ expect(element).toBeShown();
+ }));
+
+ it("should skip animations if disabled and run when enabled picking the longest specified duration/delay combination",
+ inject(function($animate, $rootScope, $compile, $sniffer) {
+ $animate.enabled(false);
+ element = $compile(html('<div style="' + vendorPrefix +
+ 'transition-duration: 1s, 0s, 1s; ' + vendorPrefix +
+ 'transition-delay: 2s, 1000ms, 2s; ' + vendorPrefix +
+ 'transition-property: height, left, opacity">foo</div>'))($rootScope);
+
+ element.addClass('ng-hide');
+ $animate.show(element);
+ expect(element).toBeShown();
+
+ $animate.enabled(true);
+
+ element.addClass('ng-hide');
+ expect(element).toBeHidden();
+
+ $animate.show(element);
+ if ($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(3000).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ expect(element).toBeShown();
+ }));
+
+ it("should select the highest duration and delay",
+ inject(function($animate, $rootScope, $compile, $sniffer) {
+ var styles = 'transition:1s linear all 2s;' +
+ vendorPrefix + 'transition:1s linear all 2s;' +
+ 'animation:my_ani 10s 1s;' +
+ vendorPrefix + 'animation:my_ani 10s 1s;';
+
+ element = $compile(html('<div style="' + styles + '">foo</div>'))($rootScope);
+
+ element.addClass('ng-hide');
+ expect(element).toBeHidden();
+
+ $animate.show(element);
+ if ($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(11000).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ expect(element).toBeShown();
+ }));
+
+ it("should finish the previous transition when a new animation is started",
+ inject(function($animate, $rootScope, $compile, $sniffer) {
+ var style = 'transition: 1s linear all;' +
+ vendorPrefix + 'transition: 1s linear all;'
+
+ element = $compile(html('<div style="' + style + '">1</div>'))($rootScope);
+
+ element.addClass('ng-hide');
+ $animate.show(element);
+ if($sniffer.transitions) {
+ expect(element.hasClass('ng-hide-remove')).toBe(true);
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('ng-hide-remove-active')).toBe(true);
+ window.setTimeout.expect(1000).process();
+ }
+ else { //animation is skipped
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ expect(element.hasClass('ng-hide-remove')).toBe(false);
+ expect(element.hasClass('ng-hide-remove-active')).toBe(false);
+ expect(element).toBeShown();
+
+ $animate.hide(element);
+ if($sniffer.transitions) {
+ expect(element.hasClass('ng-hide-add')).toBe(true);
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('ng-hide-add-active')).toBe(true);
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ }));
+ });
+ });
+
+ describe('animation evaluation', function () {
+ beforeEach(module(function($provide) {
+ $provide.value('$window', window = angular.mock.createMockWindow());
+ }));
+
+ it('should re-evaluate the CSS classes for an animation each time',
+ inject(function($animate, $rootScope, $sniffer, $rootElement) {
+
+ var parent = jqLite('<div><span></span></div>');
+ var element = parent.find('span');
+ $rootElement.append(parent);
+ angular.element(document.body).append($rootElement);
+
+ element[0].className = 'abc';
+ $animate.enter(element, parent);
+ if ($sniffer.transitions) {
+ expect(element.hasClass('abc ng-enter')).toBe(true);
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('abc ng-enter ng-enter-active')).toBe(true);
+ window.setTimeout.expect(0).process();
+ }
+ expect(element.hasClass('abc')).toBe(true);
+
+ element[0].className = 'xyz';
+ $animate.enter(element, parent);
+ if ($sniffer.transitions) {
+ expect(element.hasClass('xyz')).toBe(true);
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('xyz ng-enter ng-enter-active')).toBe(true);
+ window.setTimeout.expect(0).process();
+ }
+ expect(element.hasClass('xyz')).toBe(true);
+ }));
+
+ it('should only append active to the newly append CSS className values',
+ inject(function($animate, $rootScope, $sniffer, $rootElement) {
+
+ var parent = jqLite('<div><span></span></div>');
+ var element = parent.find('span');
+ $rootElement.append(parent);
+ angular.element(document.body).append($rootElement);
+
+ element.attr('class','one two');
+
+ $animate.enter(element, parent);
+ if($sniffer.transitions) {
+ expect(element.hasClass('one two ng-enter')).toBe(true);
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('one two ng-enter ng-enter-active')).toBe(true);
+ expect(element.hasClass('one-active')).toBe(false);
+ expect(element.hasClass('two-active')).toBe(false);
+ window.setTimeout.expect(0).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+
+ expect(element.hasClass('one two')).toBe(true);
+ }));
+ });
+
+ describe("Callbacks", function() {
+
+ var window, vendorPrefix;
+ beforeEach(function() {
+ module(function($animateProvider, $provide) {
+ $provide.value('$window', window = angular.mock.createMockWindow());
+ $animateProvider.register('.custom', function() {
+ return {
+ show : function(element, done) {
+ window.setTimeout(done, 2000);
+ }
+ }
+ });
+ $animateProvider.register('.other', function() {
+ return {
+ start : function(element, done) {
+ window.setTimeout(done, 10000);
+ }
+ }
+ });
+ })
+ inject(function($sniffer, $animate) {
+ vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
+ });
+ });
+
+ it("should fire the enter callback",
+ inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) {
+
+ var parent = jqLite('<div><span></span></div>');
+ var element = parent.find('span');
+ $rootElement.append(parent);
+ body.append($rootElement);
+
+ var flag = false;
+ $animate.enter(element, parent, null, function() {
+ flag = true;
+ });
+
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(0).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ expect(flag).toBe(true);
+ }));
+
+ it("should fire the leave callback",
+ inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) {
+
+ var parent = jqLite('<div><span></span></div>');
+ var element = parent.find('span');
+ $rootElement.append(parent);
+ body.append($rootElement);
+
+ var flag = false;
+ $animate.leave(element, function() {
+ flag = true;
+ });
+
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(0).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ expect(flag).toBe(true);
+ }));
+
+ it("should fire the move callback",
+ inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) {
+
+ var parent = jqLite('<div><span></span></div>');
+ var parent2 = jqLite('<div id="nice"></div>');
+ var element = parent.find('span');
+ $rootElement.append(parent);
+ body.append($rootElement);
+
+ var flag = false;
+ $animate.move(element, parent, parent2, function() {
+ flag = true;
+ });
+
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(0).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+
+ expect(flag).toBe(true);
+ expect(element.parent().id).toBe(parent2.id);
+ }));
+
+ it("should fire the addClass/removeClass callbacks",
+ inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) {
+
+ var parent = jqLite('<div><span></span></div>');
+ var element = parent.find('span');
+ $rootElement.append(parent);
+ body.append($rootElement);
+
+ var signature = '';
+ $animate.addClass(element, 'on', function() {
+ signature += 'A';
+ });
+
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(0).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+
+ $animate.removeClass(element, 'on', function() {
+ signature += 'B';
+ });
+
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(0).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+
+ expect(signature).toBe('AB');
+ }));
+
+ it("should fire a done callback when provided with no animation",
+ inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) {
+
+ var parent = jqLite('<div><span></span></div>');
+ var element = parent.find('span');
+ $rootElement.append(parent);
+ body.append($rootElement);
+
+ var flag = false;
+ $animate.show(element, function() {
+ flag = true;
+ });
+
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(0).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ expect(flag).toBe(true);
+ }));
+
+ it("should fire a done callback when provided with a css animation/transition",
+ inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) {
+
+ var transition = 'transition:1s linear all;';
+ var style = transition + ' ' + vendorPrefix + transition;
+ var parent = jqLite('<div><span style="' + style + '"></span></div>');
+ $rootElement.append(parent);
+ body.append($rootElement);
+ var element = parent.find('span');
+
+ var flag = false;
+ $animate.show(element, function() {
+ flag = true;
+ });
+
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(1000).process();
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+ expect(flag).toBe(true);
+ }));
+
+ it("should fire a done callback when provided with a JS animation",
+ inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) {
+
+ var parent = jqLite('<div><span></span></div>');
+ $rootElement.append(parent);
+ body.append($rootElement);
+ var element = parent.find('span');
+ element.addClass('custom');
+
+ var flag = false;
+ $animate.show(element, function() {
+ flag = true;
+ });
+
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ }
+ window.setTimeout.expect(2000).process();
+ if($sniffer.transitions) {
+ window.setTimeout.expect(0).process();
+ }
+ expect(flag).toBe(true);
+ }));
+
+ it("should fire the callback right away if another animation is called right after",
+ inject(function($animate, $rootScope, $compile, $sniffer, $rootElement) {
+
+ var parent = jqLite('<div><span></span></div>');
+ $rootElement.append(parent);
+ body.append($rootElement);
+ var element = parent.find('span');
+
+ var signature = '';
+ $animate.show(element, function() {
+ signature += 'A';
+ });
+ $animate.hide(element, function() {
+ signature += 'B';
+ });
+
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ }
+ $animate.hide(element); //earlier animation cancelled
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ }
+ expect(signature).toBe('AB');
+ }));
+ });
+
+ describe("addClass / removeClass", function() {
+
+ var window, vendorPrefix;
+ beforeEach(function() {
+ module(function($animateProvider, $provide) {
+ $provide.value('$window', window = angular.mock.createMockWindow());
+ $animateProvider.register('.klassy', function() {
+ return {
+ addClass : function(element, className, done) {
+ window.setTimeout(done, 500);
+ },
+ removeClass : function(element, className, done) {
+ window.setTimeout(done, 3000);
+ }
+ }
+ });
+ })
+ inject(function($sniffer, $animate) {
+ vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
+ });
+ });
+
+ it("should add and remove CSS classes after an animation even if no animation is present",
+ inject(function($animate, $rootScope, $sniffer, $rootElement) {
+
+ var parent = jqLite('<div><span></span></div>');
+ $rootElement.append(parent);
+ body.append($rootElement);
+ var element = jqLite(parent.find('span'));
+
+ $animate.addClass(element,'klass');
+
+ if($sniffer.transitions) {
+ expect(element.hasClass('klass-add')).toBe(true);
+ }
+
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('klass-add')).toBe(true);
+ expect(element.hasClass('klass-add-active')).toBe(true);
+ expect(element.hasClass('klass')).toBe(false);
+
+ window.setTimeout.expect(0).process();
+ expect(element.hasClass('klass-add')).toBe(false);
+ expect(element.hasClass('klass-add-active')).toBe(false);
+ }
+ expect(element.hasClass('klass')).toBe(true);
+
+ $animate.removeClass(element,'klass');
+
+ if($sniffer.transitions) {
+ expect(element.hasClass('klass')).toBe(true);
+ expect(element.hasClass('klass-remove')).toBe(true);
+
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('klass')).toBe(true);
+ expect(element.hasClass('klass-remove')).toBe(true);
+ expect(element.hasClass('klass-remove-active')).toBe(true);
+
+ window.setTimeout.expect(0).process();
+ }
+
+ expect(element.hasClass('klass')).toBe(false);
+ expect(element.hasClass('klass-remove')).toBe(false);
+ expect(element.hasClass('klass-remove-active')).toBe(false);
+ }));
+
+ it("should add and remove CSS classes with a callback",
+ inject(function($animate, $rootScope, $sniffer, $rootElement) {
+
+ var parent = jqLite('<div><span></span></div>');
+ $rootElement.append(parent);
+ body.append($rootElement);
+ var element = jqLite(parent.find('span'));
+
+ var signature = '';
+
+ $animate.addClass(element,'klass', function() {
+ signature += 'A';
+ });
+
+ if($sniffer.transitions) {
+ expect(element.hasClass('klass')).toBe(false);
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(0).process();
+ }
+ expect(element.hasClass('klass')).toBe(true);
+
+ $animate.removeClass(element,'klass', function() {
+ signature += 'B';
+ });
+
+ if($sniffer.transitions) {
+ expect(element.hasClass('klass')).toBe(true);
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(0).process();
+ }
+ expect(element.hasClass('klass')).toBe(false);
+
+ expect(signature).toBe('AB');
+ }));
+
+ it("should end the current addClass animation, add the CSS class and then run the removeClass animation",
+ inject(function($animate, $rootScope, $sniffer, $rootElement) {
+
+ var parent = jqLite('<div><span></span></div>');
+ $rootElement.append(parent);
+ body.append($rootElement);
+ var element = jqLite(parent.find('span'));
+
+ var signature = '';
+
+ $animate.addClass(element,'klass', function() {
+ signature += '1';
+ });
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('klass')).toBe(false);
+ expect(element.hasClass('klass-add')).toBe(true);
+ }
+
+ //this cancels out the older animation
+ $animate.removeClass(element,'klass', function() {
+ signature += '2';
+ });
+
+ if($sniffer.transitions) {
+ expect(element.hasClass('klass')).toBe(true);
+ expect(element.hasClass('klass-add')).toBe(false);
+ expect(element.hasClass('klass-add-active')).toBe(false);
+
+ expect(element.hasClass('klass-remove')).toBe(true);
+ window.setTimeout.expect(0).process();
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(0).process();
+ }
+
+ expect(element.hasClass('klass')).toBe(false);
+ expect(signature).toBe('12');
+ }));
+
+ it("should properly execute JS animations and use callbacks when using addClass / removeClass",
+ inject(function($animate, $rootScope, $sniffer, $rootElement) {
+ var parent = jqLite('<div><span></span></div>');
+ $rootElement.append(parent);
+ body.append($rootElement);
+ var element = jqLite(parent.find('span'));
+
+ var signature = '';
+
+ $animate.addClass(element,'klassy', function() {
+ signature += 'X';
+ });
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ }
+ window.setTimeout.expect(500).process();
+ if($sniffer.transitions) {
+ window.setTimeout.expect(0).process();
+ }
+ expect(element.hasClass('klassy')).toBe(true);
+
+ $animate.removeClass(element,'klassy', function() {
+ signature += 'Y';
+ });
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ }
+ window.setTimeout.expect(3000).process();
+ if($sniffer.transitions) {
+ window.setTimeout.expect(0).process();
+ }
+ expect(element.hasClass('klassy')).toBe(false);
+
+ expect(signature).toBe('XY');
+ }));
+
+ it("should properly execute CSS animations/transitions and use callbacks when using addClass / removeClass",
+ inject(function($animate, $rootScope, $sniffer, $rootElement) {
+
+ var transition = 'transition:11s linear all;';
+ var style = transition + ' ' + vendorPrefix + transition;
+ var parent = jqLite('<div><span style="' + style + '"></span></div>');
+ $rootElement.append(parent);
+ body.append($rootElement);
+ var element = jqLite(parent.find('span'));
+
+ var signature = '';
+
+ $animate.addClass(element,'klass', function() {
+ signature += 'd';
+ });
+ if($sniffer.transitions) {
+ expect(element.hasClass('klass-add')).toBe(true);
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('klass-add-active')).toBe(true);
+ window.setTimeout.expect(11000).process();
+ expect(element.hasClass('klass-add')).toBe(false);
+ expect(element.hasClass('klass-add-active')).toBe(false);
+ }
+ expect(element.hasClass('klass')).toBe(true);
+
+ $animate.removeClass(element,'klass', function() {
+ signature += 'b';
+ });
+ if($sniffer.transitions) {
+ expect(element.hasClass('klass-remove')).toBe(true);
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('klass-remove-active')).toBe(true);
+ window.setTimeout.expect(11000).process();
+ expect(element.hasClass('klass-remove')).toBe(false);
+ expect(element.hasClass('klass-remove-active')).toBe(false);
+ }
+ expect(element.hasClass('klass')).toBe(false);
+
+ expect(signature).toBe('db');
+ }));
+
+ it("should allow for multiple css classes to be animated plus a callback when added",
+ inject(function($animate, $rootScope, $sniffer, $rootElement) {
+
+ var transition = 'transition:7s linear all;';
+ var style = transition + ' ' + vendorPrefix + transition;
+ var parent = jqLite('<div><span style="' + style + '"></span></div>');
+ $rootElement.append(parent);
+ body.append($rootElement);
+ var element = jqLite(parent.find('span'));
+
+ var flag = false;
+ $animate.addClass(element,'one two', function() {
+ flag = true;
+ });
+
+ if($sniffer.transitions) {
+ expect(element.hasClass('one-add')).toBe(true);
+ expect(element.hasClass('two-add')).toBe(true);
+ window.setTimeout.expect(1).process();
+
+ expect(element.hasClass('one-add-active')).toBe(true);
+ expect(element.hasClass('two-add-active')).toBe(true);
+ window.setTimeout.expect(7000).process();
+
+ expect(element.hasClass('one-add')).toBe(false);
+ expect(element.hasClass('one-add-active')).toBe(false);
+ expect(element.hasClass('two-add')).toBe(false);
+ expect(element.hasClass('two-add-active')).toBe(false);
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+
+ expect(element.hasClass('one')).toBe(true);
+ expect(element.hasClass('two')).toBe(true);
+
+ expect(flag).toBe(true);
+ }));
+
+ it("should allow for multiple css classes to be animated plus a callback when removed",
+ inject(function($animate, $rootScope, $sniffer, $rootElement) {
+
+ var transition = 'transition:9s linear all;';
+ var style = transition + ' ' + vendorPrefix + transition;
+ var parent = jqLite('<div><span style="' + style + '"></span></div>');
+ $rootElement.append(parent);
+ body.append($rootElement);
+ var element = jqLite(parent.find('span'));
+
+ element.addClass('one two');
+ expect(element.hasClass('one')).toBe(true);
+ expect(element.hasClass('two')).toBe(true);
+
+ var flag = false;
+ $animate.removeClass(element,'one two', function() {
+ flag = true;
+ });
+
+ if($sniffer.transitions) {
+ expect(element.hasClass('one-remove')).toBe(true);
+ expect(element.hasClass('two-remove')).toBe(true);
+ window.setTimeout.expect(1).process();
+
+ expect(element.hasClass('one-remove-active')).toBe(true);
+ expect(element.hasClass('two-remove-active')).toBe(true);
+ window.setTimeout.expect(9000).process();
+
+ expect(element.hasClass('one-remove')).toBe(false);
+ expect(element.hasClass('one-remove-active')).toBe(false);
+ expect(element.hasClass('two-remove')).toBe(false);
+ expect(element.hasClass('two-remove-active')).toBe(false);
+ }
+ else {
+ expect(window.setTimeout.queue.length).toBe(0);
+ }
+
+ expect(element.hasClass('one')).toBe(false);
+ expect(element.hasClass('two')).toBe(false);
+
+ expect(flag).toBe(true);
+ }));
+ });
+ });
+
+ var $rootElement, $document, window;
+ beforeEach(module(function($provide) {
+ $provide.value('$window', window = angular.mock.createMockWindow());
+
+ return function(_$rootElement_, _$document_, $animate) {
+ $rootElement = _$rootElement_;
+ $document = _$document_;
+ $animate.enabled(true);
+ }
+ }));
+
+ function html(element) {
+ var body = jqLite($document[0].body);
+ $rootElement.append(element);
+ body.append($rootElement);
+ return element;
+ }
+
+ it("should properly animate and parse CSS3 transitions",
+ inject(function($compile, $rootScope, $animate, $sniffer) {
+
+ var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
+ var style = 'transition: 1s linear all;' +
+ vendorPrefix + 'transition: 1s linear all;';
+ var element = html($compile('<div>...</div>')($rootScope));
+ var child = $compile('<div style="' + style + '">...</div>')($rootScope);
+
+ $animate.enter(child, element);
+
+ if($sniffer.transitions) {
+ expect(child.hasClass('ng-enter')).toBe(true);
+ window.setTimeout.expect(1).process();
+ expect(child.hasClass('ng-enter-active')).toBe(true);
+ window.setTimeout.expect(1000).process();
+ }
+ expect(child.hasClass('ng-enter')).toBe(false);
+ expect(child.hasClass('ng-enter-active')).toBe(false);
+ }));
+
+ it("should properly animate and parse CSS3 animations",
+ inject(function($compile, $rootScope, $animate, $sniffer) {
+
+ var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
+ var style = 'animation: some_animation 4s linear 1s 2 alternate;' +
+ vendorPrefix + 'animation: some_animation 4s linear 1s 2 alternate;';
+ var element = html($compile('<div>...</div>')($rootScope));
+ var child = $compile('<div style="' + style + '">...</div>')($rootScope);
+ $animate.enter(child, element);
+
+ if($sniffer.transitions) {
+ expect(child.hasClass('ng-enter')).toBe(true);
+ window.setTimeout.expect(1).process();
+ expect(child.hasClass('ng-enter-active')).toBe(true);
+ window.setTimeout.expect(9000).process();
+ }
+ expect(child.hasClass('ng-enter')).toBe(false);
+ expect(child.hasClass('ng-enter-active')).toBe(false);
+ }));
+
+ it("should skip animations if the browser does not support CSS3 transitions and CSS3 animations",
+ inject(function($compile, $rootScope, $animate, $sniffer) {
+
+ $sniffer.animations = false;
+ $sniffer.transitions = false;
+
+ var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
+ var style = 'animation: some_animation 4s linear 1s 2 alternate;' +
+ vendorPrefix + 'animation: some_animation 4s linear 1s 2 alternate;';
+ var element = html($compile('<div>...</div>')($rootScope));
+ var child = $compile('<div style="' + style + '">...</div>')($rootScope);
+
+ expect(child.hasClass('ng-enter')).toBe(false);
+ $animate.enter(child, element);
+ expect(window.setTimeout.queue.length).toBe(0);
+ expect(child.hasClass('ng-enter')).toBe(false);
+ }));
+
+ it("should run other defined animations inline with CSS3 animations", function() {
+ module(function($animateProvider) {
+ $animateProvider.register('.custom', function($window) {
+ return {
+ enter : function(element, done) {
+ element.addClass('i-was-animated');
+ $window.setTimeout(done, 10);
+ }
+ }
+ });
+ })
+ inject(function($compile, $rootScope, $animate, $sniffer) {
+ var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
+ var style = 'transition: 1s linear all;' +
+ vendorPrefix + 'transition: 1s linear all;';
+ var element = html($compile('<div>...</div>')($rootScope));
+ var child = $compile('<div style="' + style + '">...</div>')($rootScope);
+
+ expect(child.hasClass('i-was-animated')).toBe(false);
+
+ child.addClass('custom');
+ $animate.enter(child, element);
+
+ expect(child.hasClass('ng-enter')).toBe(true);
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ expect(child.hasClass('ng-enter-active')).toBe(true);
+ }
+
+ window.setTimeout.expect(10).process();
+
+ if($sniffer.transitions) {
+ expect(child.hasClass('ng-enter-active')).toBe(true);
+ window.setTimeout.expect(1000).process();
+ expect(child.hasClass('ng-enter')).toBe(false);
+ expect(child.hasClass('ng-enter-active')).toBe(false);
+ }
+
+ expect(child.hasClass('i-was-animated')).toBe(true);
+ });
+ });
+
+ it("should properly cancel CSS transitions or animations if another animation is fired", function() {
+ module(function($animateProvider) {
+ $animateProvider.register('.usurper', function($window) {
+ return {
+ leave : function(element, done) {
+ element.addClass('this-is-mine-now');
+ $window.setTimeout(done, 55);
+ }
+ }
+ });
+ });
+ inject(function($compile, $rootScope, $animate, $sniffer) {
+ var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
+ var style = 'transition: 2s linear all;' +
+ vendorPrefix + 'transition: 2s linear all;';
+ var element = html($compile('<div>...</div>')($rootScope));
+ var child = $compile('<div style="' + style + '">...</div>')($rootScope);
+
+ $animate.enter(child, element);
+
+ if($sniffer.transitions) {
+ expect(child.hasClass('ng-enter')).toBe(true);
+ }
+
+ expect(child.hasClass('this-is-mine-now')).toBe(false);
+ child.addClass('usurper');
+ $animate.leave(child);
+
+ expect(child.hasClass('ng-enter')).toBe(false);
+ expect(child.hasClass('ng-enter-active')).toBe(false);
+
+ expect(child.hasClass('usurper')).toBe(true);
+ expect(child.hasClass('this-is-mine-now')).toBe(true);
+ if($sniffer.transitions) {
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(1).process();
+ }
+ window.setTimeout.expect(55).process();
+ if($sniffer.transitions) {
+ window.setTimeout.expect(2000).process();
+
+ //even though this exists, the animation will still not happen
+ //since the done function has already been called in the cancellation
+ window.setTimeout.expect(2000).process();
+ }
+
+ expect(child.hasClass('usurper-active')).toBe(false);
+ });
+ });
+
+ it("should add and remove CSS classes and perform CSS animations during the process",
+ inject(function($compile, $rootScope, $animate, $sniffer) {
+
+ var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
+ var style = 'transition: 10s linear all;' +
+ vendorPrefix + 'transition: 10s linear all;';
+ var element = html($compile('<div style="' + style + '"></div>')($rootScope));
+
+ expect(element.hasClass('on')).toBe(false);
+
+ $animate.addClass(element, 'on');
+
+ if($sniffer.transitions) {
+ expect(element.hasClass('on')).toBe(false);
+ expect(element.hasClass('on-add')).toBe(true);
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('on-add-active')).toBe(true);
+ window.setTimeout.expect(10000).process();
+ }
+
+ expect(element.hasClass('on')).toBe(true);
+ expect(element.hasClass('on-add')).toBe(false);
+ expect(element.hasClass('on-add-active')).toBe(false);
+
+ $animate.removeClass(element, 'on');
+ if($sniffer.transitions) {
+ expect(element.hasClass('on')).toBe(true);
+ expect(element.hasClass('on-remove')).toBe(true);
+ window.setTimeout.expect(1).process();
+ expect(element.hasClass('on-remove-active')).toBe(true);
+ window.setTimeout.expect(10000).process();
+ }
+
+ expect(element.hasClass('on')).toBe(false);
+ expect(element.hasClass('on-remove')).toBe(false);
+ expect(element.hasClass('on-remove-active')).toBe(false);
+ }));
+
+ it("should show and hide elements with CSS & JS animations being performed in the process", function() {
+ module(function($animateProvider) {
+ $animateProvider.register('.displayer', function($window) {
+ return {
+ show : function(element, done) {
+ element.removeClass('hiding');
+ element.addClass('showing');
+ $window.setTimeout(done, 25);
+ },
+ hide : function(element, done) {
+ element.removeClass('showing');
+ element.addClass('hiding');
+ $window.setTimeout(done, 555);
+ }
+ }
+ });
+ })
+ inject(function($compile, $rootScope, $animate, $sniffer) {
+ var vendorPrefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
+ var style = 'transition: 5s linear all;' +
+ vendorPrefix + 'transition: 5s linear all;';
+ var element = html($compile('<div style="' + style + '"></div>')($rootScope));
+
+ element.addClass('displayer');
+
+ expect(element).toBeShown();
+ expect(element.hasClass('showing')).toBe(false);
+ expect(element.hasClass('hiding')).toBe(false);
+
+ $animate.hide(element);
+
+ if($sniffer.transitions) {
+ expect(element).toBeShown(); //still showing
+ window.setTimeout.expect(1).process();
+ expect(element).toBeShown();
+ }
+ window.setTimeout.expect(555).process();
+ if($sniffer.transitions) {
+ expect(element).toBeShown();
+ window.setTimeout.expect(5000).process();
+ }
+ expect(element).toBeHidden();
+
+ expect(element.hasClass('showing')).toBe(false);
+ expect(element.hasClass('hiding')).toBe(true);
+ $animate.show(element);
+
+ if($sniffer.transitions) {
+ expect(element).toBeHidden();
+ window.setTimeout.expect(1).process();
+ expect(element).toBeHidden();
+ }
+ window.setTimeout.expect(25).process();
+ if($sniffer.transitions) {
+ expect(element).toBeHidden();
+ window.setTimeout.expect(5000).process();
+ }
+ expect(element).toBeShown();
+
+ expect(element.hasClass('showing')).toBe(true);
+ expect(element.hasClass('hiding')).toBe(false);
+ });
+ });
+
+});
diff --git a/test/ngRoute/directive/ngViewSpec.js b/test/ngRoute/directive/ngViewSpec.js
index 50531c18..5f021f2d 100644
--- a/test/ngRoute/directive/ngViewSpec.js
+++ b/test/ngRoute/directive/ngViewSpec.js
@@ -7,9 +7,9 @@ describe('ngView', function() {
beforeEach(module(function($provide) {
$provide.value('$window', angular.mock.createMockWindow());
- return function($rootScope, $compile, $animator) {
+ return function($rootScope, $compile, $animate) {
element = $compile('<ng:view onload="load()"></ng:view>')($rootScope);
- $animator.enabled(true);
+ $animate.enabled(true);
};
}));
@@ -509,8 +509,7 @@ describe('ngView', function() {
});
});
- describe('ngAnimate ', function() {
- var window, vendorPrefix;
+ describe('animations', function() {
var body, element, $rootElement;
function html(html) {
@@ -520,11 +519,6 @@ describe('ngView', function() {
return element;
}
- function applyCSS(element, cssProp, cssValue) {
- element.css(cssProp, cssValue);
- element.css(vendorPrefix + cssProp, cssValue);
- }
-
beforeEach(module(function() {
// we need to run animation on attached elements;
return function(_$rootElement_) {
@@ -540,128 +534,131 @@ describe('ngView', function() {
beforeEach(module(function($provide, $routeProvider) {
- $provide.value('$window', window = angular.mock.createMockWindow());
$routeProvider.when('/foo', {controller: noop, templateUrl: '/foo.html'});
- return function($sniffer, $templateCache, $animator) {
- vendorPrefix = '-' + $sniffer.vendorPrefix + '-';
+ return function($templateCache) {
$templateCache.put('/foo.html', [200, '<div>data</div>', {}]);
- $animator.enabled(true);
}
}));
- it('should fire off the enter animation + add and remove the css classes',
- inject(function($compile, $rootScope, $sniffer, $location) {
- element = $compile(html('<div ng-view ng-animate="{enter: \'custom-enter\'}"></div>'))($rootScope);
+ describe('hooks', function() {
+ beforeEach(module('mock.animate'));
- $location.path('/foo');
- $rootScope.$digest();
+ it('should fire off the enter animation',
+ inject(function($compile, $rootScope, $location, $animate) {
+ var item;
+ element = $compile(html('<div ng-view></div>'))($rootScope);
- //if we add the custom css stuff here then it will get picked up before the animation takes place
- var child = jqLite(element.children()[0]);
- applyCSS(child, 'transition', '1s linear all');
+ $location.path('/foo');
+ $rootScope.$digest();
- if ($sniffer.transitions) {
- expect(child.attr('class')).toContain('custom-enter');
- window.setTimeout.expect(1).process();
-
- expect(child.attr('class')).toContain('custom-enter-active');
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
+ item = $animate.process('leave').element;
+ item = $animate.process('leave').element;
+ item = $animate.process('leave').element;
- expect(child.attr('class')).not.toContain('custom-enter');
- expect(child.attr('class')).not.toContain('custom-enter-active');
- }));
-
- it('should fire off the leave animation + add and remove the css classes',
- inject(function($compile, $rootScope, $sniffer, $location, $templateCache) {
- $templateCache.put('/foo.html', [200, '<div>foo</div>', {}]);
- element = $compile(html('<div ng-view ng-animate="{leave: \'custom-leave\'}"></div>'))($rootScope);
-
- $location.path('/foo');
- $rootScope.$digest();
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('data');
- //if we add the custom css stuff here then it will get picked up before the animation takes place
- var child = jqLite(element.children()[0]);
- applyCSS(child, 'transition', '1s linear all');
+ item = $animate.process('leave').element;
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('data');
+ }));
- $location.path('/');
- $rootScope.$digest();
+ it('should fire off the leave animation',
+ inject(function($compile, $rootScope, $location, $templateCache, $animate) {
- if ($sniffer.transitions) {
- expect(child.attr('class')).toContain('custom-leave');
- window.setTimeout.expect(1).process();
+ var item;
+ $templateCache.put('/foo.html', [200, '<div>foo</div>', {}]);
+ element = $compile(html('<div ng-view></div>'))($rootScope);
- expect(child.attr('class')).toContain('custom-leave-active');
- window.setTimeout.expect(1000).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
+ $location.path('/foo');
+ $rootScope.$digest();
- expect(child.attr('class')).not.toContain('custom-leave');
- expect(child.attr('class')).not.toContain('custom-leave-active');
- }));
+ item = $animate.process('leave').element;
+ item = $animate.process('leave').element;
+ item = $animate.process('leave').element;
- it('should catch and use the correct duration for animations',
- inject(function($compile, $rootScope, $sniffer, $location, $templateCache) {
- $templateCache.put('/foo.html', [200, '<div>foo</div>', {}]);
- element = $compile(html(
- '<div ' +
- 'ng-view ' +
- 'ng-animate="{enter: \'customEnter\'}">' +
- '</div>'
- ))($rootScope);
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('foo');
- $location.path('/foo');
- $rootScope.$digest();
+ item = $animate.process('leave').element;
+ item = $animate.process('enter').element;
+ expect(item.text()).toBe('foo');
- //if we add the custom css stuff here then it will get picked up before the animation takes place
- var child = jqLite(element.children()[0]);
- applyCSS(child, 'transition', '0.5s linear all');
+ $location.path('/');
+ $rootScope.$digest();
- if($sniffer.transitions) {
- window.setTimeout.expect(1).process();
- window.setTimeout.expect($sniffer.transitions ? 500 : 0).process();
- } else {
- expect(window.setTimeout.queue).toEqual([]);
- }
- }));
+ item = $animate.process('leave').element;
+ expect(item.text()).toBe('foo');
+ item = $animate.process('leave').element;
+ expect(item.text()).toBe('foo');
+ }));
+ });
it('should not double compile when route changes', function() {
- module(function($routeProvider, $animationProvider, $provide) {
+ module('ngAnimate');
+ module('mock.animate');
+ module(function($routeProvider, $animateProvider, $provide) {
$routeProvider.when('/foo', {template: '<div ng-repeat="i in [1,2]">{{i}}</div>'});
$routeProvider.when('/bar', {template: '<div ng-repeat="i in [3,4]">{{i}}</div>'});
- $animationProvider.register('my-animation-leave', function() {
+ $animateProvider.register('.my-animation', function() {
return {
- start: function(element, done) {
+ leave: function(element, done) {
+ dump('yes');
done();
}
};
});
});
- inject(function($rootScope, $compile, $location, $route, $window, $rootElement, $sniffer) {
- element = $compile(html('<ng:view onload="load()" ng-animate="\'my-animation\'"></ng:view>'))($rootScope);
+ inject(function($rootScope, $compile, $location, $route, $window, $rootElement, $sniffer, $animate) {
+ if (!$sniffer.transitions) return;
+
+ element = $compile(html('<ng:view onload="load()"></ng:view>'))($rootScope);
$location.path('/foo');
$rootScope.$digest();
- if ($sniffer.transitions) {
- $window.setTimeout.expect(1).process();
- $window.setTimeout.expect(0).process();
- }
+
+ $animate.process('leave');
+ $animate.process('leave');
+ $animate.process('leave');
+ $animate.process('enter');
+ $animate.process('leave');
+ $animate.process('enter');
+ $animate.process('enter');
+ $animate.process('enter');
+ $animate.process('enter');
+ $animate.process('enter');
+ $window.setTimeout.expect(1).process();
+ $window.setTimeout.expect(1).process();
+ $window.setTimeout.expect(1).process();
+ $window.setTimeout.expect(0).process();
+ $window.setTimeout.expect(0).process();
+ $window.setTimeout.expect(0).process();
+
expect(element.text()).toEqual('12');
$location.path('/bar');
$rootScope.$digest();
+ $animate.process('leave');
+ $animate.process('enter');
+ $animate.process('leave');
+ $animate.process('enter');
+ $animate.process('enter');
+ $animate.process('enter');
+ $animate.process('enter');
+ $animate.process('enter');
expect(n(element.text())).toEqual('1234');
- if ($sniffer.transitions) {
- $window.setTimeout.expect(1).process();
- $window.setTimeout.expect(1).process();
- } else {
- $window.setTimeout.expect(1).process();
- }
+
+ $window.setTimeout.expect(1).process();
+ $window.setTimeout.expect(1).process();
+ $window.setTimeout.expect(1).process();
+ $window.setTimeout.expect(1).process();
+ $window.setTimeout.expect(0).process();
+ $window.setTimeout.expect(0).process();
+ $window.setTimeout.expect(0).process();
+ $window.setTimeout.expect(0).process();
+
expect(element.text()).toEqual('34');
function n(text) {
diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js
index d97d88a1..32dd75ea 100644
--- a/test/testabilityPatch.js
+++ b/test/testabilityPatch.js
@@ -233,7 +233,7 @@ function sortedHtml(element, showNgClass) {
*/
function isCssVisible(node) {
var display = node.css('display');
- return display != 'none';
+ return !node.hasClass('ng-hide') && display != 'none';
}
function assertHidden(node) {