From ea0279a1a5d0eeaac1d524d742050523e45acdbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 28 Apr 2015 17:17:21 -0700 Subject: [PATCH] fix(ngAnimate): close follow-up class-based animations when the same class is added/removed when removed/added This patch ensures that if the same CSS class is added/removed within a follow-up digest then the previous class-based animation is cancelled beforehand. Closes #11717 --- src/ngAnimate/animateQueue.js | 9 ++++++++ test/ngAnimate/animateSpec.js | 41 +++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/ngAnimate/animateQueue.js b/src/ngAnimate/animateQueue.js index d029bcff604f..374377de8b6e 100644 --- a/src/ngAnimate/animateQueue.js +++ b/src/ngAnimate/animateQueue.js @@ -58,6 +58,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { return currentAnimation.state === RUNNING_STATE && newAnimation.structural; }); + rules.cancel.push(function(element, newAnimation, currentAnimation) { + var nO = newAnimation.options; + var cO = currentAnimation.options; + + // if the exact same CSS class is added/removed then it's safe to cancel it + return (nO.addClass && nO.addClass === cO.removeClass) || (nO.removeClass && nO.removeClass === cO.addClass); + }); + this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap', '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', function($$rAF, $rootScope, $rootElement, $document, $$HashMap, @@ -358,6 +366,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { if (!isValidAnimation) { close(); + clearElementAnimationState(element); return runner; } diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js index 294c65ed4d9c..61810a891819 100644 --- a/test/ngAnimate/animateSpec.js +++ b/test/ngAnimate/animateSpec.js @@ -976,6 +976,47 @@ describe("animations", function() { expect(enterDone).toHaveBeenCalled(); })); + it('should cancel the previously running addClass animation if a follow-up removeClass animation is using the same class value', + inject(function($animate, $rootScope, $$rAF) { + + parent.append(element); + var runner = $animate.addClass(element, 'active-class'); + $rootScope.$digest(); + + var doneHandler = jasmine.createSpy('addClass done'); + runner.done(doneHandler); + + $$rAF.flush(); + + expect(doneHandler).not.toHaveBeenCalled(); + + $animate.removeClass(element, 'active-class'); + $rootScope.$digest(); + + expect(doneHandler).toHaveBeenCalled(); + })); + + it('should cancel the previously running removeClass animation if a follow-up addClass animation is using the same class value', + inject(function($animate, $rootScope, $$rAF) { + + element.addClass('active-class'); + parent.append(element); + var runner = $animate.removeClass(element, 'active-class'); + $rootScope.$digest(); + + var doneHandler = jasmine.createSpy('addClass done'); + runner.done(doneHandler); + + $$rAF.flush(); + + expect(doneHandler).not.toHaveBeenCalled(); + + $animate.addClass(element, 'active-class'); + $rootScope.$digest(); + + expect(doneHandler).toHaveBeenCalled(); + })); + it('should skip the class-based animation entirely if there is an active structural animation', inject(function($animate, $rootScope) {