Skip to content

Commit e00f016

Browse files
committed
feat(ngMock): add support for $animate.closeAndFlush()
Use `$animate.closeAndFlush()` to close all running animations. Closes angular#13005
1 parent 4bcb307 commit e00f016

File tree

6 files changed

+147
-13
lines changed

6 files changed

+147
-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(success) {
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

+53-4
Original file line numberDiff line numberDiff line change
@@ -783,9 +783,47 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
783783
return queueFn;
784784
});
785785

786-
$provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF',
786+
$provide.decorator('$$animateJs', ['$delegate', function($delegate) {
787+
var runners = [];
788+
789+
var animateJsConstructor = function() {
790+
var animator = $delegate.apply($delegate, arguments);
791+
runners.push(animator);
792+
return animator;
793+
};
794+
795+
animateJsConstructor.$closeAndFlush = function() {
796+
runners.forEach(function(runner) {
797+
runner.end();
798+
});
799+
runners = [];
800+
};
801+
802+
return animateJsConstructor;
803+
}]);
804+
805+
$provide.decorator('$animateCss', ['$delegate', function($delegate) {
806+
var runners = [];
807+
808+
var animateCssConstructor = function(element, options) {
809+
var animator = $delegate(element, options);
810+
runners.push(animator);
811+
return animator;
812+
};
813+
814+
animateCssConstructor.$closeAndFlush = function() {
815+
runners.forEach(function(runner) {
816+
runner.end();
817+
});
818+
runners = [];
819+
};
820+
821+
return animateCssConstructor;
822+
}]);
823+
824+
$provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF', '$animateCss', '$$animateJs',
787825
'$$forceReflow', '$$animateAsyncRun', '$rootScope',
788-
function($delegate, $timeout, $browser, $$rAF,
826+
function($delegate, $timeout, $browser, $$rAF, $animateCss, $$animateJs,
789827
$$forceReflow, $$animateAsyncRun, $rootScope) {
790828
var animate = {
791829
queue: [],
@@ -797,7 +835,18 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
797835
return $$forceReflow.totalReflows;
798836
},
799837
enabled: $delegate.enabled,
800-
flush: function() {
838+
closeAndFlush: function() {
839+
// we allow the flush command to swallow the errors
840+
// because depending whether CSS or JS animations are
841+
// used there may not be a RAF flush. The primary flush
842+
// at the end of this function must throw an exception
843+
// because it will track if there were pending animations
844+
this.flush(true);
845+
$animateCss.$closeAndFlush();
846+
$$animateJs.$closeAndFlush();
847+
this.flush();
848+
},
849+
flush: function(hideErrors) {
801850
$rootScope.$digest();
802851

803852
var doNextRun, somethingFlushed = false;
@@ -814,7 +863,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
814863
}
815864
} while (doNextRun);
816865

817-
if (!somethingFlushed) {
866+
if (!somethingFlushed && !hideErrors) {
818867
throw new Error('No pending animations ready to be closed or flushed');
819868
}
820869

test/ngMock/angular-mocksSpec.js

+57-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,54 @@ 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+
var spy = jasmine.createSpy();
2115+
var runner = $animateCss(element, {
2116+
duration: 1,
2117+
to: { color: 'red' }
2118+
}).start();
2119+
2120+
runner.then(spy);
2121+
2122+
expect(spy).not.toHaveBeenCalled();
2123+
$animate.closeAndFlush();
2124+
expect(spy).toHaveBeenCalled();
2125+
}));
2126+
2127+
it('should close the currently running $$animateJs animations',
2128+
inject(function($$animateJs, $animate) {
2129+
2130+
var spy = jasmine.createSpy();
2131+
var runner = $$animateJs(element, 'leave', 'animate', {}).start();
2132+
runner.then(spy);
2133+
2134+
expect(spy).not.toHaveBeenCalled();
2135+
$animate.closeAndFlush();
2136+
expect(spy).toHaveBeenCalled();
2137+
}));
2138+
2139+
it('should run the closing javascript animation function upon flush',
2140+
inject(function($$animateJs, $animate) {
2141+
2142+
$$animateJs(element, 'leave', 'animate', {}).start();
2143+
2144+
expect(animationLog).toEqual(['start leave']);
2145+
$animate.closeAndFlush();
2146+
expect(animationLog).toEqual(['start leave', 'end leave']);
2147+
}));
2148+
2149+
it('should throw an error if there are no animations to close and flush',
2150+
inject(function($animate) {
2151+
2152+
expect(function() {
2153+
$animate.closeAndFlush();
2154+
}).toThrow('No pending animations ready to be closed or flushed');
2155+
}));
2156+
});
21012157
});
21022158
});
21032159

0 commit comments

Comments
 (0)