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

feat(ngMock): add support for $animate.closeAndFlush() #13707

Merged
merged 1 commit into from
Jan 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/AngularPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
$AnchorScrollProvider,
$AnimateProvider,
$CoreAnimateCssProvider,
$$CoreAnimateJsProvider,
$$CoreAnimateQueueProvider,
$$AnimateRunnerFactoryProvider,
$$AnimateAsyncRunFactoryProvider,
Expand Down Expand Up @@ -218,6 +219,7 @@ function publishExternalAPI(angular) {
$anchorScroll: $AnchorScrollProvider,
$animate: $AnimateProvider,
$animateCss: $CoreAnimateCssProvider,
$$animateJs: $$CoreAnimateJsProvider,
$$animateQueue: $$CoreAnimateQueueProvider,
$$AnimateRunner: $$AnimateRunnerFactoryProvider,
$$animateAsyncRun: $$AnimateAsyncRunFactoryProvider,
Expand Down
4 changes: 4 additions & 0 deletions src/ng/animate.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ function prepareAnimateOptions(options) {
: {};
}

var $$CoreAnimateJsProvider = function() {
this.$get = function() {};
};

// this is prefixed with Core since it conflicts with
// the animateQueueProvider defined in ngAnimate/animateQueue.js
var $$CoreAnimateQueueProvider = function() {
Expand Down
6 changes: 3 additions & 3 deletions src/ngAnimate/animateCss.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
var gcsStaggerLookup = createLocalCacheLookup();

this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
'$$forceReflow', '$sniffer', '$$rAFScheduler', '$animate',
'$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
function($window, $$jqLite, $$AnimateRunner, $timeout,
$$forceReflow, $sniffer, $$rAFScheduler, $animate) {
$$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) {

var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);

Expand Down Expand Up @@ -456,7 +456,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
var node = getDomNode(element);
if (!node
|| !node.parentNode
|| !$animate.enabled()) {
|| !$$animateQueue.enabled()) {
return closeAndReturnNoopAnimator();
}

Expand Down
33 changes: 28 additions & 5 deletions src/ngAnimate/animateJs.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
// $animateJs(element, 'enter');
return function(element, event, classes, options) {
var animationClosed = false;

// the `classes` argument is optional and if it is not used
// then the classes will be resolved from the element's className
// property as well as options.addClass/options.removeClass.
Expand Down Expand Up @@ -63,8 +65,32 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
applyAnimationClasses(element, options);
}

function close() {
animationClosed = true;
applyOptions();
applyAnimationStyles(element, options);
}

var runner;

return {
$$willAnimate: true,
end: function() {
if (runner) {
runner.end();
} else {
close();
runner = new $$AnimateRunner();
runner.complete(true);
}
return runner;
},
start: function() {
if (runner) {
return runner;
}

runner = new $$AnimateRunner();
var closeActiveAnimations;
var chain = [];

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

var animationClosed = false;
var runner = new $$AnimateRunner({
runner.setHost({
end: function() {
endAnimations();
},
Expand All @@ -103,9 +128,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
return runner;

function onComplete(success) {
animationClosed = true;
applyOptions();
applyAnimationStyles(element, options);
close(success);
runner.complete(success);
}

Expand Down
83 changes: 79 additions & 4 deletions src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,15 @@ angular.mock.TzDate = function(offset, timestamp) {
angular.mock.TzDate.prototype = Date.prototype;
/* jshint +W101 */


/**
* @ngdoc service
* @name $animate
*
* @description
* Mock implementation of the {@link ng.$animate `$animate`} service. Exposes two additional methods
* for testing animations.
*/
angular.mock.animate = angular.module('ngAnimateMock', ['ng'])

.config(['$provide', function($provide) {
Expand Down Expand Up @@ -783,9 +792,47 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
return queueFn;
});

$provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF',
$provide.decorator('$$animateJs', ['$delegate', function($delegate) {
var runners = [];

var animateJsConstructor = function() {
var animator = $delegate.apply($delegate, arguments);
runners.push(animator);
return animator;
};

animateJsConstructor.$closeAndFlush = function() {
runners.forEach(function(runner) {
runner.end();
});
runners = [];
};

return animateJsConstructor;
}]);

$provide.decorator('$animateCss', ['$delegate', function($delegate) {
var runners = [];

var animateCssConstructor = function(element, options) {
var animator = $delegate(element, options);
runners.push(animator);
return animator;
};

animateCssConstructor.$closeAndFlush = function() {
runners.forEach(function(runner) {
runner.end();
});
runners = [];
};

return animateCssConstructor;
}]);

$provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF', '$animateCss', '$$animateJs',
'$$forceReflow', '$$animateAsyncRun', '$rootScope',
function($delegate, $timeout, $browser, $$rAF,
function($delegate, $timeout, $browser, $$rAF, $animateCss, $$animateJs,
$$forceReflow, $$animateAsyncRun, $rootScope) {
var animate = {
queue: [],
Expand All @@ -797,7 +844,35 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
return $$forceReflow.totalReflows;
},
enabled: $delegate.enabled,
flush: function() {
/**
* @ngdoc method
* @name $animate#closeAndFlush
* @description
*
* This method will close all pending animations (both {@link ngAnimate#javascript-based-animations Javascript}
* and {@link ngAnimate.$animateCss CSS}) and it will also flush any remaining animation frames and/or callbacks.
*/
closeAndFlush: function() {
// we allow the flush command to swallow the errors
// because depending on whether CSS or JS animations are
// used, there may not be a RAF flush. The primary flush
// at the end of this function must throw an exception
// because it will track if there were pending animations
this.flush(true);
$animateCss.$closeAndFlush();
$$animateJs.$closeAndFlush();
this.flush();
},
/**
* @ngdoc method
* @name $animate#flush
* @description
*
* This method is used to flush the pending callbacks and animation frames to either start
* an animation or conclude an animation. Note that this will not actually close an
* actively running animation (see {@link ngMock.$animate#closeAndFlush `closeAndFlush()`} for that).
*/
flush: function(hideErrors) {
$rootScope.$digest();

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

if (!somethingFlushed) {
if (!somethingFlushed && !hideErrors) {
throw new Error('No pending animations ready to be closed or flushed');
}

Expand Down
60 changes: 59 additions & 1 deletion test/ngMock/angular-mocksSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1924,7 +1924,7 @@ describe('ngMockE2E', function() {
beforeEach(module('ngAnimate'));
beforeEach(module('ngAnimateMock'));

var ss, element, trackedAnimations;
var ss, element, trackedAnimations, animationLog;

afterEach(function() {
if (element) {
Expand All @@ -1937,6 +1937,8 @@ describe('ngMockE2E', function() {

beforeEach(module(function($animateProvider) {
trackedAnimations = [];
animationLog = [];

$animateProvider.register('.animate', function() {
return {
leave: logFn('leave'),
Expand All @@ -1945,7 +1947,13 @@ describe('ngMockE2E', function() {

function logFn(method) {
return function(element) {
animationLog.push('start ' + method);
trackedAnimations.push(getDoneCallback(arguments));

return function closingFn(cancel) {
var lab = cancel ? 'cancel' : 'end';
animationLog.push(lab + ' ' + method);
};
};
}

Expand Down Expand Up @@ -2098,6 +2106,56 @@ describe('ngMockE2E', function() {
expect(spy.callCount).toBe(2);
}));
});

describe('$animate.closeAndFlush()', function() {
it('should close the currently running $animateCss animations',
inject(function($animateCss, $animate) {

if (!browserSupportsCssAnimations()) return;

var spy = jasmine.createSpy();
var runner = $animateCss(element, {
duration: 1,
to: { color: 'red' }
}).start();

runner.then(spy);

expect(spy).not.toHaveBeenCalled();
$animate.closeAndFlush();
expect(spy).toHaveBeenCalled();
}));

it('should close the currently running $$animateJs animations',
inject(function($$animateJs, $animate) {

var spy = jasmine.createSpy();
var runner = $$animateJs(element, 'leave', 'animate', {}).start();
runner.then(spy);

expect(spy).not.toHaveBeenCalled();
$animate.closeAndFlush();
expect(spy).toHaveBeenCalled();
}));

it('should run the closing javascript animation function upon flush',
inject(function($$animateJs, $animate) {

$$animateJs(element, 'leave', 'animate', {}).start();

expect(animationLog).toEqual(['start leave']);
$animate.closeAndFlush();
expect(animationLog).toEqual(['start leave', 'end leave']);
}));

it('should throw an error if there are no animations to close and flush',
inject(function($animate) {

expect(function() {
$animate.closeAndFlush();
}).toThrow('No pending animations ready to be closed or flushed');
}));
});
});
});

Expand Down