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

Commit e1def1b

Browse files
matskoNarretz
authored andcommitted
feat(ngMock): add support for $animate.closeAndFlush()
Use `$animate.closeAndFlush()` to close all running animations. Closes #13005 Closes #13576 Closes #13707
1 parent e5cab95 commit e1def1b

File tree

6 files changed

+175
-13
lines changed

6 files changed

+175
-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,
@@ -218,6 +219,7 @@ function publishExternalAPI(angular) {
218219
$anchorScroll: $AnchorScrollProvider,
219220
$animate: $AnimateProvider,
220221
$animateCss: $CoreAnimateCssProvider,
222+
$$animateJs: $$CoreAnimateJsProvider,
221223
$$animateQueue: $$CoreAnimateQueueProvider,
222224
$$AnimateRunner: $$AnimateRunnerFactoryProvider,
223225
$$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

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

803878
var doNextRun, somethingFlushed = false;
@@ -814,7 +889,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
814889
}
815890
} while (doNextRun);
816891

817-
if (!somethingFlushed) {
892+
if (!somethingFlushed && !hideErrors) {
818893
throw new Error('No pending animations ready to be closed or flushed');
819894
}
820895

test/ngMock/angular-mocksSpec.js

+59-1
Original file line numberDiff line numberDiff line change
@@ -1924,7 +1924,7 @@ describe('ngMockE2E', function() {
19241924
beforeEach(module('ngAnimate'));
19251925
beforeEach(module('ngAnimateMock'));
19261926

1927-
var ss, element, trackedAnimations;
1927+
var ss, element, trackedAnimations, animationLog;
19281928

19291929
afterEach(function() {
19301930
if (element) {
@@ -1937,6 +1937,8 @@ describe('ngMockE2E', function() {
19371937

19381938
beforeEach(module(function($animateProvider) {
19391939
trackedAnimations = [];
1940+
animationLog = [];
1941+
19401942
$animateProvider.register('.animate', function() {
19411943
return {
19421944
leave: logFn('leave'),
@@ -1945,7 +1947,13 @@ describe('ngMockE2E', function() {
19451947

19461948
function logFn(method) {
19471949
return function(element) {
1950+
animationLog.push('start ' + method);
19481951
trackedAnimations.push(getDoneCallback(arguments));
1952+
1953+
return function closingFn(cancel) {
1954+
var lab = cancel ? 'cancel' : 'end';
1955+
animationLog.push(lab + ' ' + method);
1956+
};
19491957
};
19501958
}
19511959

@@ -2098,6 +2106,56 @@ describe('ngMockE2E', function() {
20982106
expect(spy.callCount).toBe(2);
20992107
}));
21002108
});
2109+
2110+
describe('$animate.closeAndFlush()', function() {
2111+
it('should close the currently running $animateCss animations',
2112+
inject(function($animateCss, $animate) {
2113+
2114+
if (!browserSupportsCssAnimations()) return;
2115+
2116+
var spy = jasmine.createSpy();
2117+
var runner = $animateCss(element, {
2118+
duration: 1,
2119+
to: { color: 'red' }
2120+
}).start();
2121+
2122+
runner.then(spy);
2123+
2124+
expect(spy).not.toHaveBeenCalled();
2125+
$animate.closeAndFlush();
2126+
expect(spy).toHaveBeenCalled();
2127+
}));
2128+
2129+
it('should close the currently running $$animateJs animations',
2130+
inject(function($$animateJs, $animate) {
2131+
2132+
var spy = jasmine.createSpy();
2133+
var runner = $$animateJs(element, 'leave', 'animate', {}).start();
2134+
runner.then(spy);
2135+
2136+
expect(spy).not.toHaveBeenCalled();
2137+
$animate.closeAndFlush();
2138+
expect(spy).toHaveBeenCalled();
2139+
}));
2140+
2141+
it('should run the closing javascript animation function upon flush',
2142+
inject(function($$animateJs, $animate) {
2143+
2144+
$$animateJs(element, 'leave', 'animate', {}).start();
2145+
2146+
expect(animationLog).toEqual(['start leave']);
2147+
$animate.closeAndFlush();
2148+
expect(animationLog).toEqual(['start leave', 'end leave']);
2149+
}));
2150+
2151+
it('should throw an error if there are no animations to close and flush',
2152+
inject(function($animate) {
2153+
2154+
expect(function() {
2155+
$animate.closeAndFlush();
2156+
}).toThrow('No pending animations ready to be closed or flushed');
2157+
}));
2158+
});
21012159
});
21022160
});
21032161

0 commit comments

Comments
 (0)