Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 512c081

Browse files
matskoNarretz
authored andcommitted
feat(ngMock): add support for $animate.closeAndFlush()
Use `$animate.closeAndFlush()` to close all running animations. Includes a fix that landed separately in the master branch: a801df7
1 parent 2563ff7 commit 512c081

File tree

6 files changed

+202
-13
lines changed

6 files changed

+202
-13
lines changed

src/AngularPublic.js

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
$AnchorScrollProvider,
5757
$AnimateProvider,
5858
$CoreAnimateCssProvider,
59+
$$CoreAnimateJsProvider,
5960
$$CoreAnimateQueueProvider,
6061
$$AnimateRunnerFactoryProvider,
6162
$$AnimateAsyncRunFactoryProvider,
@@ -217,6 +218,7 @@ function publishExternalAPI(angular) {
217218
$anchorScroll: $AnchorScrollProvider,
218219
$animate: $AnimateProvider,
219220
$animateCss: $CoreAnimateCssProvider,
221+
$$animateJs: $$CoreAnimateJsProvider,
220222
$$animateQueue: $$CoreAnimateQueueProvider,
221223
$$AnimateRunner: $$AnimateRunnerFactoryProvider,
222224
$$animateAsyncRun: $$AnimateAsyncRunFactoryProvider,

src/ng/animate.js

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ function prepareAnimateOptions(options) {
5353
: {};
5454
}
5555

56+
var $$CoreAnimateJsProvider = function() {
57+
this.$get = function() {};
58+
};
59+
5660
// this is prefixed with Core since it conflicts with
5761
// the animateQueueProvider defined in ngAnimate/animateQueue.js
5862
var $$CoreAnimateQueueProvider = function() {

src/ngAnimate/animateCss.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
352352
var gcsStaggerLookup = createLocalCacheLookup();
353353

354354
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
355-
'$$forceReflow', '$sniffer', '$$rAFScheduler', '$animate',
355+
'$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
356356
function($window, $$jqLite, $$AnimateRunner, $timeout,
357-
$$forceReflow, $sniffer, $$rAFScheduler, $animate) {
357+
$$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) {
358358

359359
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
360360

@@ -456,7 +456,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
456456
var node = getDomNode(element);
457457
if (!node
458458
|| !node.parentNode
459-
|| !$animate.enabled()) {
459+
|| !$$animateQueue.enabled()) {
460460
return closeAndReturnNoopAnimator();
461461
}
462462

src/ngAnimate/animateJs.js

+28-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
1111
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
1212
// $animateJs(element, 'enter');
1313
return function(element, event, classes, options) {
14+
var animationClosed = false;
15+
1416
// the `classes` argument is optional and if it is not used
1517
// then the classes will be resolved from the element's className
1618
// property as well as options.addClass/options.removeClass.
@@ -63,8 +65,32 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
6365
applyAnimationClasses(element, options);
6466
}
6567

68+
function close() {
69+
animationClosed = true;
70+
applyOptions();
71+
applyAnimationStyles(element, options);
72+
}
73+
74+
var runner;
75+
6676
return {
77+
$$willAnimate: true,
78+
end: function() {
79+
if (runner) {
80+
runner.end();
81+
} else {
82+
close();
83+
runner = new $$AnimateRunner();
84+
runner.complete(true);
85+
}
86+
return runner;
87+
},
6788
start: function() {
89+
if (runner) {
90+
return runner;
91+
}
92+
93+
runner = new $$AnimateRunner();
6894
var closeActiveAnimations;
6995
var chain = [];
7096

@@ -89,8 +115,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
89115
});
90116
}
91117

92-
var animationClosed = false;
93-
var runner = new $$AnimateRunner({
118+
runner.setHost({
94119
end: function() {
95120
endAnimations();
96121
},
@@ -103,9 +128,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
103128
return runner;
104129

105130
function onComplete(success) {
106-
animationClosed = true;
107-
applyOptions();
108-
applyAnimationStyles(element, options);
131+
close(success);
109132
runner.complete(success);
110133
}
111134

src/ngMock/angular-mocks.js

+82-4
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,15 @@ angular.mock.TzDate = function(offset, timestamp) {
751751
angular.mock.TzDate.prototype = Date.prototype;
752752
/* jshint +W101 */
753753

754+
755+
/**
756+
* @ngdoc service
757+
* @name $animate
758+
*
759+
* @description
760+
* Mock implementation of the {@link ng.$animate `$animate`} service. Exposes two additional methods
761+
* for testing animations.
762+
*/
754763
angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
755764

756765
.config(['$provide', function($provide) {
@@ -783,9 +792,50 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
783792
return queueFn;
784793
});
785794

786-
$provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF',
795+
$provide.decorator('$$animateJs', ['$delegate', function($delegate) {
796+
var runners = [];
797+
798+
var animateJsConstructor = function() {
799+
var animator = $delegate.apply($delegate, arguments);
800+
// If no javascript animation is found, animator is undefined
801+
if (animator) {
802+
runners.push(animator);
803+
}
804+
return animator;
805+
};
806+
807+
animateJsConstructor.$closeAndFlush = function() {
808+
runners.forEach(function(runner) {
809+
runner.end();
810+
});
811+
runners = [];
812+
};
813+
814+
return animateJsConstructor;
815+
}]);
816+
817+
$provide.decorator('$animateCss', ['$delegate', function($delegate) {
818+
var runners = [];
819+
820+
var animateCssConstructor = function(element, options) {
821+
var animator = $delegate(element, options);
822+
runners.push(animator);
823+
return animator;
824+
};
825+
826+
animateCssConstructor.$closeAndFlush = function() {
827+
runners.forEach(function(runner) {
828+
runner.end();
829+
});
830+
runners = [];
831+
};
832+
833+
return animateCssConstructor;
834+
}]);
835+
836+
$provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF', '$animateCss', '$$animateJs',
787837
'$$forceReflow', '$$animateAsyncRun', '$rootScope',
788-
function($delegate, $timeout, $browser, $$rAF,
838+
function($delegate, $timeout, $browser, $$rAF, $animateCss, $$animateJs,
789839
$$forceReflow, $$animateAsyncRun, $rootScope) {
790840
var animate = {
791841
queue: [],
@@ -797,7 +847,35 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
797847
return $$forceReflow.totalReflows;
798848
},
799849
enabled: $delegate.enabled,
800-
flush: function() {
850+
/**
851+
* @ngdoc method
852+
* @name $animate#closeAndFlush
853+
* @description
854+
*
855+
* This method will close all pending animations (both {@link ngAnimate#javascript-based-animations Javascript}
856+
* and {@link ngAnimate.$animateCss CSS}) and it will also flush any remaining animation frames and/or callbacks.
857+
*/
858+
closeAndFlush: function() {
859+
// we allow the flush command to swallow the errors
860+
// because depending on whether CSS or JS animations are
861+
// used, there may not be a RAF flush. The primary flush
862+
// at the end of this function must throw an exception
863+
// because it will track if there were pending animations
864+
this.flush(true);
865+
$animateCss.$closeAndFlush();
866+
$$animateJs.$closeAndFlush();
867+
this.flush();
868+
},
869+
/**
870+
* @ngdoc method
871+
* @name $animate#flush
872+
* @description
873+
*
874+
* This method is used to flush the pending callbacks and animation frames to either start
875+
* an animation or conclude an animation. Note that this will not actually close an
876+
* actively running animation (see {@link ngMock.$animate#closeAndFlush `closeAndFlush()`} for that).
877+
*/
878+
flush: function(hideErrors) {
801879
$rootScope.$digest();
802880

803881
var doNextRun, somethingFlushed = false;
@@ -814,7 +892,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
814892
}
815893
} while (doNextRun);
816894

817-
if (!somethingFlushed) {
895+
if (!somethingFlushed && !hideErrors) {
818896
throw new Error('No pending animations ready to be closed or flushed');
819897
}
820898

test/ngMock/angular-mocksSpec.js

+83-1
Original file line numberDiff line numberDiff line change
@@ -1834,7 +1834,7 @@ describe('ngMockE2E', function() {
18341834
beforeEach(module('ngAnimate'));
18351835
beforeEach(module('ngAnimateMock'));
18361836

1837-
var ss, element, trackedAnimations;
1837+
var ss, element, trackedAnimations, animationLog;
18381838

18391839
afterEach(function() {
18401840
if (element) {
@@ -1847,6 +1847,8 @@ describe('ngMockE2E', function() {
18471847

18481848
beforeEach(module(function($animateProvider) {
18491849
trackedAnimations = [];
1850+
animationLog = [];
1851+
18501852
$animateProvider.register('.animate', function() {
18511853
return {
18521854
leave: logFn('leave'),
@@ -1855,7 +1857,13 @@ describe('ngMockE2E', function() {
18551857

18561858
function logFn(method) {
18571859
return function(element) {
1860+
animationLog.push('start ' + method);
18581861
trackedAnimations.push(getDoneCallback(arguments));
1862+
1863+
return function closingFn(cancel) {
1864+
var lab = cancel ? 'cancel' : 'end';
1865+
animationLog.push(lab + ' ' + method);
1866+
};
18591867
};
18601868
}
18611869

@@ -2008,6 +2016,80 @@ describe('ngMockE2E', function() {
20082016
expect(spy.callCount).toBe(2);
20092017
}));
20102018
});
2019+
2020+
describe('$animate.closeAndFlush()', function() {
2021+
it('should close the currently running $animateCss animations',
2022+
inject(function($animateCss, $animate) {
2023+
2024+
if (!browserSupportsCssAnimations()) return;
2025+
2026+
var spy = jasmine.createSpy();
2027+
var runner = $animateCss(element, {
2028+
duration: 1,
2029+
to: { color: 'red' }
2030+
}).start();
2031+
2032+
runner.then(spy);
2033+
2034+
expect(spy).not.toHaveBeenCalled();
2035+
$animate.closeAndFlush();
2036+
expect(spy).toHaveBeenCalled();
2037+
}));
2038+
2039+
it('should close the currently running $$animateJs animations',
2040+
inject(function($$animateJs, $animate) {
2041+
2042+
var spy = jasmine.createSpy();
2043+
var runner = $$animateJs(element, 'leave', 'animate', {}).start();
2044+
runner.then(spy);
2045+
2046+
expect(spy).not.toHaveBeenCalled();
2047+
$animate.closeAndFlush();
2048+
expect(spy).toHaveBeenCalled();
2049+
}));
2050+
2051+
it('should run the closing javascript animation function upon flush',
2052+
inject(function($$animateJs, $animate) {
2053+
2054+
$$animateJs(element, 'leave', 'animate', {}).start();
2055+
2056+
expect(animationLog).toEqual(['start leave']);
2057+
$animate.closeAndFlush();
2058+
expect(animationLog).toEqual(['start leave', 'end leave']);
2059+
}));
2060+
2061+
it('should not throw when a regular animation has no javascript animation',
2062+
inject(function($animate, $$animation, $rootElement) {
2063+
2064+
if (!browserSupportsCssAnimations()) return;
2065+
2066+
var element = jqLite('<div></div>');
2067+
$rootElement.append(element);
2068+
2069+
// Make sure the animation has valid $animateCss options
2070+
$$animation(element, null, {
2071+
from: { background: 'red' },
2072+
to: { background: 'blue' },
2073+
duration: 1,
2074+
transitionStyle: '1s linear all'
2075+
});
2076+
2077+
expect(function() {
2078+
$animate.closeAndFlush();
2079+
}).not.toThrow();
2080+
2081+
dealoc(element);
2082+
}));
2083+
2084+
it('should throw an error if there are no animations to close and flush',
2085+
inject(function($animate) {
2086+
2087+
expect(function() {
2088+
$animate.closeAndFlush();
2089+
}).toThrow('No pending animations ready to be closed or flushed');
2090+
2091+
}));
2092+
});
20112093
});
20122094
});
20132095

0 commit comments

Comments
 (0)