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

feat(ngAnimate): expose a core version of $animateCss #12570

Closed
wants to merge 1 commit into from
Closed
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
1 change: 1 addition & 0 deletions angularFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var angularFiles = {

'src/ng/anchorScroll.js',
'src/ng/animate.js',
'src/ng/animateCss.js',
'src/ng/browser.js',
'src/ng/cacheFactory.js',
'src/ng/compile.js',
Expand Down
2 changes: 2 additions & 0 deletions src/AngularPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@

$AnchorScrollProvider,
$AnimateProvider,
$CoreAnimateCssProvider,
$$CoreAnimateQueueProvider,
$$CoreAnimateRunnerProvider,
$BrowserProvider,
Expand Down Expand Up @@ -212,6 +213,7 @@ function publishExternalAPI(angular) {
$provide.provider({
$anchorScroll: $AnchorScrollProvider,
$animate: $AnimateProvider,
$animateCss: $CoreAnimateCssProvider,
$$animateQueue: $$CoreAnimateQueueProvider,
$$AnimateRunner: $$CoreAnimateRunnerProvider,
$browser: $BrowserProvider,
Expand Down
84 changes: 84 additions & 0 deletions src/ng/animateCss.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use strict';

/**
* @ngdoc service
* @name $animateCss
* @kind object
*
* @description
* This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included,
* then the `$animateCss` service will actually perform animations.
*
* Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
*/
var $CoreAnimateCssProvider = function() {
this.$get = ['$$rAF', '$q', function($$rAF, $q) {

var RAFPromise = function() {};
RAFPromise.prototype = {
done: function(cancel) {
this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
},
end: function() {
this.done();
},
cancel: function() {
this.done(true);
},
getPromise: function() {
if (!this.defer) {
this.defer = $q.defer();
}
return this.defer.promise;
},
then: function(f1,f2) {
return this.getPromise().then(f1,f2);
},
'catch': function(f1) {
return this.getPromise().catch(f1);
},
'finally': function(f1) {
return this.getPromise().finally(f1);
}
};

return function(element, options) {
if (options.from) {
element.css(options.from);
options.from = null;
}

var closed, runner = new RAFPromise();
return {
start: run,
end: run
};

function run() {
$$rAF(function() {
close();
if (!closed) {
runner.done();
}
closed = true;
});
return runner;
}

function close() {
if (options.addClass) {
element.addClass(options.addClass);
options.addClass = null;
}
if (options.removeClass) {
element.removeClass(options.removeClass);
options.removeClass = null;
}
if (options.to) {
element.css(options.to);
options.to = null;
}
}
};
}];
};
120 changes: 120 additions & 0 deletions test/ng/animateCssSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use strict';

describe("$animateCss", function() {

var triggerRAF, element;
beforeEach(inject(function($$rAF, $rootElement, $document) {
triggerRAF = function() {
$$rAF.flush();
};

var body = jqLite($document[0].body);
element = jqLite('<div></div>');
$rootElement.append(element);
body.append($rootElement);
}));

describe("without animation", function() {

it("should apply the provided [from] CSS to the element", inject(function($animateCss) {
$animateCss(element, { from: { height: '50px' }}).start();
expect(element.css('height')).toBe('50px');
}));

it("should apply the provided [to] CSS to the element after the first frame", inject(function($animateCss) {
$animateCss(element, { to: { width: '50px' }}).start();
expect(element.css('width')).not.toBe('50px');
triggerRAF();
expect(element.css('width')).toBe('50px');
}));

it("should apply the provided [addClass] CSS classes to the element after the first frame", inject(function($animateCss) {
$animateCss(element, { addClass: 'golden man' }).start();
expect(element).not.toHaveClass('golden man');
triggerRAF();
expect(element).toHaveClass('golden man');
}));

it("should apply the provided [removeClass] CSS classes to the element after the first frame", inject(function($animateCss) {
element.addClass('silver');
$animateCss(element, { removeClass: 'silver dude' }).start();
expect(element).toHaveClass('silver');
triggerRAF();
expect(element).not.toHaveClass('silver');
}));

it("should return an animator with a start method which returns a promise", inject(function($animateCss) {
var promise = $animateCss(element, { addClass: 'cool' }).start();
expect(isPromiseLike(promise)).toBe(true);
}));

it("should return an animator with an end method which returns a promise", inject(function($animateCss) {
var promise = $animateCss(element, { addClass: 'cool' }).end();
expect(isPromiseLike(promise)).toBe(true);
}));

it("should only resolve the promise once both a digest and RAF have passed after start",
inject(function($animateCss, $rootScope) {

var doneSpy = jasmine.createSpy();
var runner = $animateCss(element, { addClass: 'cool' }).start();

runner.then(doneSpy);
expect(doneSpy).not.toHaveBeenCalled();

triggerRAF();
expect(doneSpy).not.toHaveBeenCalled();

$rootScope.$digest();
expect(doneSpy).toHaveBeenCalled();
}));

it("should resolve immediately if runner.end() is called",
inject(function($animateCss, $rootScope) {

var doneSpy = jasmine.createSpy();
var runner = $animateCss(element, { addClass: 'cool' }).start();

runner.then(doneSpy);
runner.end();
expect(doneSpy).not.toHaveBeenCalled();

$rootScope.$digest();
expect(doneSpy).toHaveBeenCalled();
}));

it("should reject immediately if runner.end() is called",
inject(function($animateCss, $rootScope) {

var cancelSpy = jasmine.createSpy();
var runner = $animateCss(element, { addClass: 'cool' }).start();

runner.catch(cancelSpy);
runner.cancel();
expect(cancelSpy).not.toHaveBeenCalled();

$rootScope.$digest();
expect(cancelSpy).toHaveBeenCalled();
}));

it("should not resolve after the next frame if the runner has already been cancelled",
inject(function($animateCss, $rootScope) {

var doneSpy = jasmine.createSpy();
var cancelSpy = jasmine.createSpy();
var runner = $animateCss(element, { addClass: 'cool' }).start();

runner.then(doneSpy, cancelSpy);
runner.cancel();

$rootScope.$digest();
expect(cancelSpy).toHaveBeenCalled();
expect(doneSpy).not.toHaveBeenCalled();

triggerRAF();
expect(cancelSpy).toHaveBeenCalled();
expect(doneSpy).not.toHaveBeenCalled();
}));
});

});