From 4c4537e65e6cf911c9659b562d89e3330ce3ffae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Mon, 24 Feb 2014 19:43:57 -0500 Subject: [PATCH] perf($animate): use rAF instead of timeouts to issue animation callbacks --- src/ng/animate.js | 16 +-- src/ngAnimate/animate.js | 54 ++++------ src/ngMock/angular-mocks.js | 15 +-- test/ng/directive/ngClassSpec.js | 5 +- test/ng/directive/ngIncludeSpec.js | 14 +-- test/ngAnimate/animateSpec.js | 153 ++++++++++++++++----------- test/ngRoute/directive/ngViewSpec.js | 12 +-- 7 files changed, 143 insertions(+), 126 deletions(-) diff --git a/src/ng/animate.js b/src/ng/animate.js index 130e61e7e79f..d90f086badb9 100644 --- a/src/ng/animate.js +++ b/src/ng/animate.js @@ -81,7 +81,11 @@ var $AnimateProvider = ['$provide', function($provide) { return this.$$classNameFilter; }; - this.$get = ['$timeout', function($timeout) { + this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) { + + function async(fn) { + fn && $$asyncCallback(fn); + } /** * @@ -126,7 +130,7 @@ var $AnimateProvider = ['$provide', function($provide) { } parent.append(element); } - done && $timeout(done, 0, false); + async(done); }, /** @@ -142,7 +146,7 @@ var $AnimateProvider = ['$provide', function($provide) { */ leave : function(element, done) { element.remove(); - done && $timeout(done, 0, false); + async(done); }, /** @@ -189,7 +193,7 @@ var $AnimateProvider = ['$provide', function($provide) { forEach(element, function (element) { jqLiteAddClass(element, className); }); - done && $timeout(done, 0, false); + async(done); }, /** @@ -212,7 +216,7 @@ var $AnimateProvider = ['$provide', function($provide) { forEach(element, function (element) { jqLiteRemoveClass(element, className); }); - done && $timeout(done, 0, false); + async(done); }, /** @@ -234,7 +238,7 @@ var $AnimateProvider = ['$provide', function($provide) { jqLiteAddClass(element, add); jqLiteRemoveClass(element, remove); }); - done && $timeout(done, 0, false); + async(done); }, enabled : noop diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js index 5c0ed91761c9..6ccf6a5d2a7d 100644 --- a/src/ngAnimate/animate.js +++ b/src/ngAnimate/animate.js @@ -247,42 +247,24 @@ angular.module('ngAnimate', ['ng']) * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application. * */ - .factory('$$animateReflow', ['$window', '$timeout', '$document', - function($window, $timeout, $document) { + + //this private service is only used within CSS-enabled animations + //IE8 + IE9 do not support rAF natively, but that is fine since they + //also don't support transitions and keyframes which means that the code + //below will never be used by the two browsers. + .factory('$$animateReflow', ['$$rAF', '$document', function($$rAF, $document) { var bod = $document[0].body; - var requestAnimationFrame = $window.requestAnimationFrame || - $window.webkitRequestAnimationFrame || - function(fn) { - return $timeout(fn, 10, false); - }; - - var cancelAnimationFrame = $window.cancelAnimationFrame || - $window.webkitCancelAnimationFrame || - function(timer) { - return $timeout.cancel(timer); - }; return function(fn) { - var id = requestAnimationFrame(function() { + //the returned function acts as the cancellation function + return $$rAF(function() { + //the line below will force the browser to perform a repaint + //so that all the animated elements within the animation frame + //will be properly updated and drawn on screen. This is + //required to perform multi-class CSS based animations with + //Firefox. DO NOT REMOVE THIS LINE. var a = bod.offsetWidth + 1; fn(); }); - return function() { - cancelAnimationFrame(id); - }; - }; - }]) - - .factory('$$asyncQueueBuffer', ['$timeout', function($timeout) { - var timer, queue = []; - return function(fn) { - $timeout.cancel(timer); - queue.push(fn); - timer = $timeout(function() { - for(var i = 0; i < queue.length; i++) { - queue[i](); - } - queue = []; - }, 0, false); }; }]) @@ -313,8 +295,8 @@ angular.module('ngAnimate', ['ng']) return extractElementNode(elm1) == extractElementNode(elm2); } - $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncQueueBuffer', '$rootScope', '$document', - function($delegate, $injector, $sniffer, $rootElement, $$asyncQueueBuffer, $rootScope, $document) { + $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', + function($delegate, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document) { var globalAnimationCounter = 0; $rootElement.data(NG_ANIMATE_STATE, rootAnimateState); @@ -876,7 +858,7 @@ angular.module('ngAnimate', ['ng']) function fireDOMCallback(animationPhase) { var eventName = '$animate:' + animationPhase; if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) { - $$asyncQueueBuffer(function() { + $$asyncCallback(function() { element.triggerHandler(eventName, { event : animationEvent, className : className @@ -896,7 +878,7 @@ angular.module('ngAnimate', ['ng']) function fireDoneCallbackAsync() { fireDOMCallback('close'); if(doneCallback) { - $$asyncQueueBuffer(function() { + $$asyncCallback(function() { doneCallback(); }); } @@ -923,7 +905,7 @@ angular.module('ngAnimate', ['ng']) if(isClassBased) { cleanup(element, className); } else { - $$asyncQueueBuffer(function() { + $$asyncCallback(function() { var data = element.data(NG_ANIMATE_STATE) || {}; if(localAnimationCount == data.index) { cleanup(element, className, animationEvent); diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index bcd6cc1fa4eb..c9f314318771 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -757,21 +757,24 @@ angular.mock.TzDate.prototype = Date.prototype; angular.mock.animate = angular.module('ngAnimateMock', ['ng']) .config(['$provide', function($provide) { - var reflowQueue = []; + var reflowQueue = []; $provide.value('$$animateReflow', function(fn) { + var index = reflowQueue.length; reflowQueue.push(fn); - return angular.noop; + return function cancel() { + reflowQueue.splice(index, 1); + }; }); - $provide.decorator('$animate', function($delegate) { + $provide.decorator('$animate', function($delegate, $$asyncCallback) { var animate = { queue : [], enabled : $delegate.enabled, + triggerCallbacks : function() { + $$asyncCallback.flush(); + }, triggerReflow : function() { - if(reflowQueue.length === 0) { - throw new Error('No animation reflows present'); - } angular.forEach(reflowQueue, function(fn) { fn(); }); diff --git a/test/ng/directive/ngClassSpec.js b/test/ng/directive/ngClassSpec.js index b11c4766c05b..83cd30008844 100644 --- a/test/ng/directive/ngClassSpec.js +++ b/test/ng/directive/ngClassSpec.js @@ -345,6 +345,7 @@ describe('ngClass animations', function() { //mocks are not used since the enter delegation method is called before addClass and //it makes it impossible to test to see that addClass is called first module('ngAnimate'); + module('ngAnimateMock'); var digestQueue = []; module(function($animateProvider) { @@ -367,7 +368,7 @@ describe('ngClass animations', function() { }; }; }); - inject(function($compile, $rootScope, $rootElement, $animate, $timeout, $document) { + inject(function($compile, $rootScope, $browser, $rootElement, $animate, $timeout, $document) { // Enable animations by triggering the first item in the postDigest queue digestQueue.shift()(); @@ -407,7 +408,7 @@ describe('ngClass animations', function() { //is spaced-out then it is required so that the original digestion //is kicked into gear $rootScope.$digest(); - $timeout.flush(); + $animate.triggerCallbacks(); expect(element.data('state')).toBe('crazy-enter'); expect(enterComplete).toBe(true); diff --git a/test/ng/directive/ngIncludeSpec.js b/test/ng/directive/ngIncludeSpec.js index ebb35147045c..9f37d1fe606c 100644 --- a/test/ng/directive/ngIncludeSpec.js +++ b/test/ng/directive/ngIncludeSpec.js @@ -368,7 +368,7 @@ describe('ngInclude', function() { expect(autoScrollSpy).not.toHaveBeenCalled(); expect($animate.queue.shift().event).toBe('enter'); - $timeout.flush(); + $animate.triggerCallbacks(); expect(autoScrollSpy).toHaveBeenCalledOnce(); })); @@ -385,7 +385,7 @@ describe('ngInclude', function() { }); expect($animate.queue.shift().event).toBe('enter'); - $timeout.flush(); + $animate.triggerCallbacks(); $rootScope.$apply(function () { $rootScope.tpl = 'another.html'; @@ -394,7 +394,7 @@ describe('ngInclude', function() { expect($animate.queue.shift().event).toBe('leave'); expect($animate.queue.shift().event).toBe('enter'); - $timeout.flush(); + $animate.triggerCallbacks(); $rootScope.$apply(function() { $rootScope.tpl = 'template.html'; @@ -403,7 +403,7 @@ describe('ngInclude', function() { expect($animate.queue.shift().event).toBe('leave'); expect($animate.queue.shift().event).toBe('enter'); - $timeout.flush(); + $animate.triggerCallbacks(); expect(autoScrollSpy).toHaveBeenCalled(); expect(autoScrollSpy.callCount).toBe(3); @@ -419,7 +419,7 @@ describe('ngInclude', function() { }); expect($animate.queue.shift().event).toBe('enter'); - $timeout.flush(); + $animate.triggerCallbacks(); expect(autoScrollSpy).not.toHaveBeenCalled(); })); @@ -435,7 +435,7 @@ describe('ngInclude', function() { }); expect($animate.queue.shift().event).toBe('enter'); - $timeout.flush(); + $animate.triggerCallbacks(); $rootScope.$apply(function () { $rootScope.tpl = 'template.html'; @@ -457,7 +457,7 @@ describe('ngInclude', function() { $rootScope.$apply("tpl = 'template.html'"); expect($animate.queue.shift().event).toBe('enter'); - $timeout.flush(); + $animate.triggerCallbacks(); expect(autoScrollSpy).toHaveBeenCalledOnce(); })); diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js index b732d7b501da..47e7afffb83e 100644 --- a/test/ngAnimate/animateSpec.js +++ b/test/ngAnimate/animateSpec.js @@ -380,7 +380,7 @@ describe("ngAnimate", function() { expect(child.attr('class')).toContain('ng-enter'); expect(child.attr('class')).toContain('ng-enter-active'); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); - $timeout.flush(); + $animate.triggerCallbacks(); //move element.append(after); @@ -391,7 +391,7 @@ describe("ngAnimate", function() { expect(child.attr('class')).toContain('ng-move'); expect(child.attr('class')).toContain('ng-move-active'); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); - $timeout.flush(); + $animate.triggerCallbacks(); //hide $animate.addClass(child, 'ng-hide'); @@ -537,7 +537,7 @@ describe("ngAnimate", function() { $animate.triggerReflow(); browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } - $timeout.flush(); + $animate.triggerCallbacks(); expect(completed).toBe(true); })); @@ -782,7 +782,7 @@ describe("ngAnimate", function() { $animate.enabled(true); - ss.addRule('.real-animation.ng-enter, .real-animation.ng-leave, .real-animation-fake.ng-enter, .real-animation-fake.ng-leave', + ss.addRule('.real-animation.ng-enter, .real-animation.ng-leave', '-webkit-animation:1s my_animation;' + 'animation:1s my_animation;'); @@ -817,6 +817,9 @@ describe("ngAnimate", function() { expect(elements[3].attr('style')).toMatch(/animation-delay: 0\.3\d*s/); expect(elements[4].attr('style')).toMatch(/animation-delay: 0\.4\d*s/); + //final closing timeout + $timeout.flush(); + for(var i = 0; i < 5; i++) { dealoc(elements[i]); var newScope = $rootScope.$new(); @@ -826,13 +829,9 @@ describe("ngAnimate", function() { }; $rootScope.$digest(); - var expectFailure = true; - try { - $animate.triggerReflow(); - expectFailure = false; - } catch(e) {} - expect(expectFailure).toBe(true); + //this means no animations were triggered + $timeout.verifyNoPendingTasks(); expect(elements[0].attr('style')).toBeFalsy(); expect(elements[1].attr('style')).not.toMatch(/animation-delay: 0\.1\d*s/); @@ -1117,14 +1116,7 @@ describe("ngAnimate", function() { }; $rootScope.$digest(); - - var expectFailure = true; - try { - $animate.triggerReflow(); - expectFailure = false; - } catch(e) {} - - expect(expectFailure).toBe(true); + $animate.triggerReflow(); expect(elements[0].attr('style')).toBeFalsy(); expect(elements[1].attr('style')).not.toMatch(/transition-delay: 0\.1\d*s/); @@ -1337,7 +1329,7 @@ describe("ngAnimate", function() { expect(element.hasClass('ng-enter')).toBe(true); expect(element.hasClass('ng-enter-active')).toBe(true); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 22000, elapsedTime: 22 }); - $timeout.flush(); + $animate.triggerCallbacks(); } expect(element.hasClass('abc')).toBe(true); @@ -1351,7 +1343,7 @@ describe("ngAnimate", function() { expect(element.hasClass('ng-enter')).toBe(true); expect(element.hasClass('ng-enter-active')).toBe(true); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000, elapsedTime: 11 }); - $timeout.flush(); + $animate.triggerCallbacks(); } expect(element.hasClass('xyz')).toBe(true); })); @@ -1426,7 +1418,7 @@ describe("ngAnimate", function() { }); $rootScope.$digest(); - $timeout.flush(); + $animate.triggerCallbacks(); expect(flag).toBe(true); })); @@ -1446,7 +1438,7 @@ describe("ngAnimate", function() { }); $rootScope.$digest(); - $timeout.flush(); + $animate.triggerCallbacks(); expect(flag).toBe(true); })); @@ -1467,7 +1459,7 @@ describe("ngAnimate", function() { }); $rootScope.$digest(); - $timeout.flush(); + $animate.triggerCallbacks(); expect(flag).toBe(true); expect(element.parent().id).toBe(parent2.id); @@ -1493,7 +1485,7 @@ describe("ngAnimate", function() { signature += 'B'; }); - $timeout.flush(); + $animate.triggerCallbacks(); expect(signature).toBe('AB'); })); @@ -1529,17 +1521,19 @@ describe("ngAnimate", function() { steps.push(['done', 'klass', 'addClass']); }); - $timeout.flush(1); + $animate.triggerCallbacks(); expect(steps.pop()).toEqual(['before', 'klass', 'addClass']); $animate.triggerReflow(); - $timeout.flush(1); + + $animate.triggerCallbacks(); expect(steps.pop()).toEqual(['after', 'klass', 'addClass']); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); - $timeout.flush(1); + + $animate.triggerCallbacks(); expect(steps.shift()).toEqual(['close', 'klass', 'addClass']); @@ -1568,7 +1562,7 @@ describe("ngAnimate", function() { $animate.enter(element, parent); $rootScope.$digest(); - $timeout.flush(1); + $animate.triggerCallbacks(); expect(steps.shift()).toEqual(['before', 'ng-enter', 'enter']); expect(steps.shift()).toEqual(['after', 'ng-enter', 'enter']); @@ -1602,7 +1596,7 @@ describe("ngAnimate", function() { flag = true; }); - $timeout.flush(); + $animate.triggerCallbacks(); expect(flag).toBe(true); })); @@ -1629,7 +1623,7 @@ describe("ngAnimate", function() { $animate.triggerReflow(); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); } - $timeout.flush(); + $animate.triggerCallbacks(); expect(flag).toBe(true); })); @@ -1648,7 +1642,7 @@ describe("ngAnimate", function() { flag = true; }); - $timeout.flush(); + $animate.triggerCallbacks(); expect(flag).toBe(true); })); @@ -1677,8 +1671,9 @@ describe("ngAnimate", function() { $animate.addClass(element, 'ng-hide'); //earlier animation cancelled if($sniffer.transitions) { $animate.triggerReflow(); + browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 9 }); } - $timeout.flush(); + $animate.triggerCallbacks(); expect(signature).toBe('AB'); })); }); @@ -1707,7 +1702,7 @@ describe("ngAnimate", function() { it("should not perform an animation, and the followup DOM operation, if the class is " + "already present during addClass or not present during removeClass on the element", - inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout) { + inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout, $browser) { var element = jqLite('
'); $rootElement.append(element); @@ -1781,7 +1776,7 @@ describe("ngAnimate", function() { signature += 'B'; }); - $timeout.flush(); + $animate.triggerCallbacks(); expect(element.hasClass('klass')).toBe(false); expect(signature).toBe('AB'); })); @@ -1813,7 +1808,8 @@ describe("ngAnimate", function() { expect(element.hasClass('klass-add-active')).toBe(true); browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000, elapsedTime: 3 }); } - $timeout.flush(); + + $animate.triggerCallbacks(); //this cancels out the older animation $animate.removeClass(element,'klass', function() { @@ -1830,7 +1826,8 @@ describe("ngAnimate", function() { browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000, elapsedTime: 3 }); } - $timeout.flush(); + + $animate.triggerCallbacks(); expect(element.hasClass('klass')).toBe(false); expect(signature).toBe('12'); @@ -1863,6 +1860,7 @@ describe("ngAnimate", function() { expect(element.hasClass('klassy')).toBe(false); + $animate.triggerCallbacks(); expect(signature).toBe('XY'); })); @@ -1895,7 +1893,7 @@ describe("ngAnimate", function() { expect(element.hasClass('klass-add-active')).toBe(false); } - $timeout.flush(); + $animate.triggerCallbacks(); expect(element.hasClass('klass')).toBe(true); $animate.removeClass(element,'klass', function() { @@ -1911,7 +1909,7 @@ describe("ngAnimate", function() { expect(element.hasClass('klass-remove-active')).toBe(false); } - $timeout.flush(); + $animate.triggerCallbacks(); expect(element.hasClass('klass')).toBe(false); expect(signature).toBe('db'); @@ -1951,7 +1949,7 @@ describe("ngAnimate", function() { expect(element.hasClass('two-add-active')).toBe(false); } - $timeout.flush(); + $animate.triggerCallbacks(); expect(element.hasClass('one')).toBe(true); expect(element.hasClass('two')).toBe(true); @@ -1997,7 +1995,7 @@ describe("ngAnimate", function() { expect(element.hasClass('two-remove-active')).toBe(false); } - $timeout.flush(); + $animate.triggerCallbacks(); expect(element.hasClass('one')).toBe(false); expect(element.hasClass('two')).toBe(false); @@ -2159,7 +2157,7 @@ describe("ngAnimate", function() { child.addClass('usurper'); $animate.leave(child); $rootScope.$digest(); - $timeout.flush(); + $animate.triggerCallbacks(); expect(child.hasClass('ng-enter')).toBe(false); expect(child.hasClass('ng-enter-active')).toBe(false); @@ -2213,10 +2211,10 @@ describe("ngAnimate", function() { // if($sniffer.transitions) { // expect(element.hasClass('on')).toBe(false); // expect(element.hasClass('on-add')).toBe(true); - // $timeout.flush(); + // $animate.triggerCallbacks(); // } // - // $timeout.flush(); + // $animate.triggerCallbacks(); // // expect(element.hasClass('on')).toBe(true); // expect(element.hasClass('on-add')).toBe(false); @@ -2229,7 +2227,7 @@ describe("ngAnimate", function() { // $timeout.flush(10000); // } // - // $timeout.flush(); + // $animate.triggerCallbacks(); // expect(element.hasClass('on')).toBe(false); // expect(element.hasClass('on-remove')).toBe(false); // expect(element.hasClass('on-remove-active')).toBe(false); @@ -2272,11 +2270,11 @@ describe("ngAnimate", function() { // // if($sniffer.transitions) { // expect(element).toBeShown(); //still showing - // $timeout.flush(); + // $animate.triggerCallbacks(); // expect(element).toBeShown(); // $timeout.flush(5555); // } - // $timeout.flush(); + // $animate.triggerCallbacks(); // expect(element).toBeHidden(); // // expect(element.hasClass('showing')).toBe(false); @@ -2285,11 +2283,11 @@ describe("ngAnimate", function() { // // if($sniffer.transitions) { // expect(element).toBeHidden(); - // $timeout.flush(); + // $animate.triggerCallbacks(); // expect(element).toBeHidden(); // $timeout.flush(5580); // } - // $timeout.flush(); + // $animate.triggerCallbacks(); // expect(element).toBeShown(); // // expect(element.hasClass('showing')).toBe(true); @@ -2484,7 +2482,7 @@ describe("ngAnimate", function() { expect(animationState).toBe('enter-cancel'); $rootScope.$digest(); - $timeout.flush(); + $animate.triggerCallbacks(); $animate.addClass(child, 'something'); if($sniffer.transitions) { @@ -2646,16 +2644,16 @@ describe("ngAnimate", function() { expect(intercepted).toBe('move'); - //flush the enter reflow - $timeout.flush(); + //flush the POST enter callback + $animate.triggerCallbacks(); $animate.addClass(child2, 'testing'); expect(intercepted).toBe('move'); continueAnimation(); - //flush the move reflow - $timeout.flush(); + //flush the POST move callback + $animate.triggerCallbacks(); $animate.leave(child2); $rootScope.$digest(); @@ -3100,7 +3098,7 @@ describe("ngAnimate", function() { forEach(element.children(), function(kid) { browserTrigger(kid, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 }); }); - $timeout.flush(); + $animate.triggerCallbacks(); $rootScope.items = []; $rootScope.$digest(); @@ -3141,7 +3139,7 @@ describe("ngAnimate", function() { }); $rootScope.$digest(); - $timeout.flush(); + $animate.triggerCallbacks(); expect(captures['enter']).toBeUndefined(); expect(enterDone).toBe(true); @@ -3152,8 +3150,9 @@ describe("ngAnimate", function() { $animate.leave(element, function() { leaveDone = true; }); + $rootScope.$digest(); - $timeout.flush(); + $animate.triggerCallbacks(); expect(captures['leave']).toBe(true); expect(leaveDone).toBe(true); @@ -3184,11 +3183,11 @@ describe("ngAnimate", function() { $animate.triggerReflow(); browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 1 }); - $timeout.flush(1); expect(ready).toBe(false); browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 5 }); - $timeout.flush(1); + $animate.triggerReflow(); + $animate.triggerCallbacks(); expect(ready).toBe(true); ready = false; @@ -3198,7 +3197,7 @@ describe("ngAnimate", function() { $animate.triggerReflow(); browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 1 }); - $timeout.flush(1); + $animate.triggerCallbacks(); expect(ready).toBe(true); })); @@ -3221,7 +3220,7 @@ describe("ngAnimate", function() { ready = true; }); - $timeout.flush(1); + $animate.triggerCallbacks(); expect(ready).toBe(true); })); @@ -3248,14 +3247,42 @@ describe("ngAnimate", function() { }); $animate.triggerReflow(); - - $timeout.flush(1); + $animate.triggerCallbacks(); expect(signature).toBe('A'); browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2000 }); - $timeout.flush(1); + $animate.triggerCallbacks(); expect(signature).toBe('AB'); })); + + it('should cancel the previous reflow when new animations are added', function() { + var cancelReflowCallback = jasmine.createSpy('callback'); + module(function($provide) { + $provide.value('$$animateReflow', function(fn) { + return cancelReflowCallback; + }); + }); + inject(function($animate, $sniffer, $rootScope, $compile) { + if (!$sniffer.transitions) return; + + ss.addRule('.fly', '-webkit-transition:2s linear all;' + + 'transition:2s linear all;'); + + $animate.enabled(true); + + var element = $compile('
')($rootScope); + $rootElement.append(element); + jqLite($document[0].body).append($rootElement); + + expect(cancelReflowCallback).not.toHaveBeenCalled(); + + $animate.addClass(element, 'fast'); + $animate.addClass(element, 'smooth'); + $animate.triggerReflow(); + + expect(cancelReflowCallback).toHaveBeenCalled(); + }); + }); }); }); diff --git a/test/ngRoute/directive/ngViewSpec.js b/test/ngRoute/directive/ngViewSpec.js index 072c232b09ad..259940c646dd 100644 --- a/test/ngRoute/directive/ngViewSpec.js +++ b/test/ngRoute/directive/ngViewSpec.js @@ -707,7 +707,7 @@ describe('ngView animations', function() { $location.path('/foo'); $rootScope.$digest(); - $timeout.flush(); + $animate.triggerCallbacks(); $location.path('/'); $rootScope.$digest(); @@ -872,7 +872,7 @@ describe('ngView animations', function() { $location.path('/foo'); $rootScope.$digest(); expect($animate.queue.shift().event).toBe('enter'); - $timeout.flush(); + $animate.triggerCallbacks(); expect(autoScrollSpy).toHaveBeenCalledOnce(); })); @@ -886,7 +886,7 @@ describe('ngView animations', function() { $location.path('/foo'); $rootScope.$digest(); expect($animate.queue.shift().event).toBe('enter'); - $timeout.flush(); + $animate.triggerCallbacks(); expect(autoScrollSpy).toHaveBeenCalledOnce(); })); @@ -899,7 +899,7 @@ describe('ngView animations', function() { $location.path('/foo'); $rootScope.$digest(); expect($animate.queue.shift().event).toBe('enter'); - $timeout.flush(); + $animate.triggerCallbacks(); expect(autoScrollSpy).not.toHaveBeenCalled(); })); @@ -913,7 +913,7 @@ describe('ngView animations', function() { $location.path('/foo'); $rootScope.$digest(); expect($animate.queue.shift().event).toBe('enter'); - $timeout.flush(); + $animate.triggerCallbacks(); expect(autoScrollSpy).not.toHaveBeenCalled(); })); @@ -930,7 +930,7 @@ describe('ngView animations', function() { expect(autoScrollSpy).not.toHaveBeenCalled(); expect($animate.queue.shift().event).toBe('enter'); - $timeout.flush(); + $animate.triggerCallbacks(); expect($animate.enter).toHaveBeenCalledOnce(); expect(autoScrollSpy).toHaveBeenCalledOnce();