9ae51d751b
Previously the test was assuing that this function was attached to the window, which is not the case in production, nor in the isolated module tests.
3262 lines
110 KiB
JavaScript
3262 lines
110 KiB
JavaScript
'use strict';
|
|
|
|
describe('ngAnimate $animateCss', function() {
|
|
|
|
beforeEach(module('ngAnimate'));
|
|
beforeEach(module('ngAnimateMock'));
|
|
|
|
function assertAnimationRunning(element, not) {
|
|
var className = element.attr('class');
|
|
var regex = /\b\w+-active\b/;
|
|
if (not) {
|
|
expect(className).toMatch(regex);
|
|
} else {
|
|
expect(className).not.toMatch(regex);
|
|
}
|
|
}
|
|
|
|
function getPossiblyPrefixedStyleValue(element, styleProp) {
|
|
var value = element.css(styleProp);
|
|
if (isUndefined(value)) value = element.css('-webkit-' + styleProp);
|
|
|
|
return value;
|
|
}
|
|
|
|
function keyframeProgress(element, duration, delay) {
|
|
browserTrigger(element, 'animationend',
|
|
{ timeStamp: Date.now() + ((delay || 1) * 1000), elapsedTime: duration });
|
|
}
|
|
|
|
function transitionProgress(element, duration, delay) {
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now() + ((delay || 1) * 1000), elapsedTime: duration });
|
|
}
|
|
|
|
function isPromiseLike(p) {
|
|
return !!(p && p.then);
|
|
}
|
|
|
|
var fakeStyle = {
|
|
color: 'blue'
|
|
};
|
|
|
|
var ss, triggerAnimationStartFrame;
|
|
beforeEach(module(function() {
|
|
return function($document, $sniffer, $$rAF, $animate) {
|
|
ss = createMockStyleSheet($document);
|
|
|
|
$animate.enabled(true);
|
|
triggerAnimationStartFrame = function() {
|
|
$$rAF.flush();
|
|
};
|
|
};
|
|
}));
|
|
|
|
afterEach(function() {
|
|
if (ss) {
|
|
ss.destroy();
|
|
}
|
|
});
|
|
|
|
it('should return false if neither transitions or keyframes are supported by the browser',
|
|
inject(function($animateCss, $sniffer, $rootElement, $document) {
|
|
|
|
var animator;
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
$sniffer.transitions = $sniffer.animations = false;
|
|
animator = $animateCss(element, {
|
|
duration: 10,
|
|
to: { 'background': 'red' }
|
|
});
|
|
expect(animator.$$willAnimate).toBeFalsy();
|
|
}));
|
|
|
|
describe('when active', function() {
|
|
if (!browserSupportsCssAnimations()) return;
|
|
|
|
it('should not attempt an animation if animations are globally disabled',
|
|
inject(function($animateCss, $animate, $rootElement, $document) {
|
|
|
|
$animate.enabled(false);
|
|
|
|
var animator, element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
animator = $animateCss(element, {
|
|
duration: 10,
|
|
to: { 'height': '100px' }
|
|
});
|
|
|
|
expect(animator.$$willAnimate).toBeFalsy();
|
|
}));
|
|
|
|
it('should silently quit the animation and not throw when an element has no parent during preparation',
|
|
inject(function($animateCss, $rootScope, $document, $rootElement) {
|
|
|
|
var element = angular.element('<div></div>');
|
|
expect(function() {
|
|
$animateCss(element, {
|
|
duration: 1000,
|
|
event: 'fake',
|
|
to: fakeStyle
|
|
}).start();
|
|
}).not.toThrow();
|
|
|
|
expect(element).not.toHaveClass('fake');
|
|
triggerAnimationStartFrame();
|
|
expect(element).not.toHaveClass('fake-active');
|
|
}));
|
|
|
|
it('should silently quit the animation and not throw when an element has no parent before starting',
|
|
inject(function($animateCss, $$rAF, $rootScope, $document, $rootElement) {
|
|
|
|
var element = angular.element('<div></div>');
|
|
angular.element($document[0].body).append($rootElement);
|
|
$rootElement.append(element);
|
|
|
|
$animateCss(element, {
|
|
duration: 1000,
|
|
addClass: 'wait-for-it',
|
|
to: fakeStyle
|
|
}).start();
|
|
|
|
element.remove();
|
|
|
|
expect(function() {
|
|
triggerAnimationStartFrame();
|
|
}).not.toThrow();
|
|
}));
|
|
|
|
describe('rAF usage', function() {
|
|
it('should buffer all requests into a single requestAnimationFrame call',
|
|
inject(function($animateCss, $$rAF, $rootScope, $document, $rootElement) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var count = 0;
|
|
var runners = [];
|
|
function makeRequest() {
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
var runner = $animateCss(element, { duration: 5, to: fakeStyle }).start();
|
|
runner.then(function() {
|
|
count++;
|
|
});
|
|
runners.push(runner);
|
|
}
|
|
|
|
makeRequest();
|
|
makeRequest();
|
|
makeRequest();
|
|
|
|
expect(count).toBe(0);
|
|
|
|
triggerAnimationStartFrame();
|
|
forEach(runners, function(runner) {
|
|
runner.end();
|
|
});
|
|
|
|
$rootScope.$digest();
|
|
expect(count).toBe(3);
|
|
}));
|
|
|
|
it('should cancel previous requests to rAF to avoid premature flushing', function() {
|
|
var count = 0;
|
|
module(function($provide) {
|
|
$provide.value('$$rAF', function() {
|
|
return function cancellationFn() {
|
|
count++;
|
|
};
|
|
});
|
|
});
|
|
inject(function($animateCss, $$rAF, $document, $rootElement) {
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
function makeRequest() {
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
$animateCss(element, { duration: 5, to: fakeStyle }).start();
|
|
}
|
|
|
|
makeRequest();
|
|
makeRequest();
|
|
makeRequest();
|
|
expect(count).toBe(2);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('animator and runner', function() {
|
|
var animationDuration = 5;
|
|
var element, animator;
|
|
beforeEach(inject(function($animateCss, $rootElement, $document) {
|
|
element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
animator = $animateCss(element, {
|
|
event: 'enter',
|
|
structural: true,
|
|
duration: animationDuration,
|
|
to: fakeStyle
|
|
});
|
|
}));
|
|
|
|
it('should expose start and end functions for the animator object', inject(function() {
|
|
expect(typeof animator.start).toBe('function');
|
|
expect(typeof animator.end).toBe('function');
|
|
}));
|
|
|
|
it('should expose end, cancel, resume and pause methods on the runner object', inject(function() {
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(typeof runner.end).toBe('function');
|
|
expect(typeof runner.cancel).toBe('function');
|
|
expect(typeof runner.resume).toBe('function');
|
|
expect(typeof runner.pause).toBe('function');
|
|
}));
|
|
|
|
it('should start the animation', inject(function() {
|
|
expect(element).not.toHaveClass('ng-enter-active');
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element).toHaveClass('ng-enter-active');
|
|
}));
|
|
|
|
it('should end the animation when called from the animator object', inject(function() {
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
animator.end();
|
|
expect(element).not.toHaveClass('ng-enter-active');
|
|
}));
|
|
|
|
it('should end the animation when called from the runner object', inject(function() {
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
runner.end();
|
|
expect(element).not.toHaveClass('ng-enter-active');
|
|
}));
|
|
|
|
it('should permanently close the animation if closed before the next rAF runs', inject(function() {
|
|
var runner = animator.start();
|
|
runner.end();
|
|
|
|
triggerAnimationStartFrame();
|
|
expect(element).not.toHaveClass('ng-enter-active');
|
|
}));
|
|
|
|
it('should return a runner object at the start of the animation that contains a `then` method',
|
|
inject(function($rootScope) {
|
|
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(isPromiseLike(runner)).toBeTruthy();
|
|
|
|
var resolved;
|
|
runner.then(function() {
|
|
resolved = true;
|
|
});
|
|
|
|
runner.end();
|
|
$rootScope.$digest();
|
|
expect(resolved).toBeTruthy();
|
|
}));
|
|
|
|
it('should cancel the animation and reject', inject(function($rootScope) {
|
|
var rejected;
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
runner.then(noop, function() {
|
|
rejected = true;
|
|
});
|
|
|
|
runner.cancel();
|
|
$rootScope.$digest();
|
|
expect(rejected).toBeTruthy();
|
|
}));
|
|
|
|
it('should run pause, but not effect the transition animation', inject(function() {
|
|
var blockingDelay = '-' + animationDuration + 's';
|
|
|
|
expect(element.css('transition-delay')).toEqual(blockingDelay);
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element.css('transition-delay')).not.toEqual(blockingDelay);
|
|
runner.pause();
|
|
expect(element.css('transition-delay')).not.toEqual(blockingDelay);
|
|
}));
|
|
|
|
it('should pause the transition, have no effect, but not end it', inject(function() {
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
runner.pause();
|
|
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now(), elapsedTime: 5 });
|
|
|
|
expect(element).toHaveClass('ng-enter-active');
|
|
}));
|
|
|
|
it('should resume the animation', inject(function() {
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
runner.pause();
|
|
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now(), elapsedTime: 5 });
|
|
|
|
expect(element).toHaveClass('ng-enter-active');
|
|
runner.resume();
|
|
|
|
expect(element).not.toHaveClass('ng-enter-active');
|
|
}));
|
|
|
|
it('should pause and resume a keyframe animation using animation-play-state',
|
|
inject(function($animateCss) {
|
|
|
|
element.attr('style', '');
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:1.5s keyframe_animation;');
|
|
|
|
animator = $animateCss(element, {
|
|
event: 'enter',
|
|
structural: true
|
|
});
|
|
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
runner.pause();
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-play-state')).toEqual('paused');
|
|
runner.resume();
|
|
expect(element.attr('style')).toBeFalsy();
|
|
}));
|
|
|
|
it('should remove the animation-play-state style if the animation is closed',
|
|
inject(function($animateCss) {
|
|
|
|
element.attr('style', '');
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:1.5s keyframe_animation;');
|
|
|
|
animator = $animateCss(element, {
|
|
event: 'enter',
|
|
structural: true
|
|
});
|
|
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
runner.pause();
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-play-state')).toEqual('paused');
|
|
runner.end();
|
|
expect(element.attr('style')).toBeFalsy();
|
|
}));
|
|
});
|
|
|
|
describe('CSS', function() {
|
|
describe('detected styles', function() {
|
|
var element, options;
|
|
|
|
function assertAnimationComplete(bool) {
|
|
var assert = expect(element);
|
|
if (bool) {
|
|
assert = assert.not;
|
|
}
|
|
assert.toHaveClass('ng-enter');
|
|
assert.toHaveClass('ng-enter-active');
|
|
}
|
|
|
|
beforeEach(inject(function($rootElement, $document) {
|
|
element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
options = { event: 'enter', structural: true };
|
|
}));
|
|
|
|
it('should always return an object even if no animation is detected',
|
|
inject(function($animateCss) {
|
|
|
|
ss.addRule('.some-animation', 'background:red;');
|
|
|
|
element.addClass('some-animation');
|
|
var animator = $animateCss(element, options);
|
|
|
|
expect(animator).toBeTruthy();
|
|
expect(isFunction(animator.start)).toBeTruthy();
|
|
expect(animator.end).toBeTruthy();
|
|
expect(animator.$$willAnimate).toBe(false);
|
|
}));
|
|
|
|
it('should close the animation immediately, but still return an animator object if no animation is detected',
|
|
inject(function($animateCss) {
|
|
|
|
ss.addRule('.another-fake-animation', 'background:blue;');
|
|
|
|
element.addClass('another-fake-animation');
|
|
var animator = $animateCss(element, {
|
|
event: 'enter',
|
|
structural: true
|
|
});
|
|
|
|
expect(element).not.toHaveClass('ng-enter');
|
|
expect(isFunction(animator.start)).toBeTruthy();
|
|
}));
|
|
|
|
they('should close the animation, but still accept $prop callbacks if no animation is detected',
|
|
['done', 'then'], function(method) {
|
|
|
|
inject(function($animateCss, $animate, $rootScope) {
|
|
ss.addRule('.the-third-fake-animation', 'background:green;');
|
|
|
|
element.addClass('another-fake-animation');
|
|
var animator = $animateCss(element, {
|
|
event: 'enter',
|
|
structural: true
|
|
});
|
|
|
|
var done = false;
|
|
animator.start()[method](function() {
|
|
done = true;
|
|
});
|
|
|
|
expect(done).toBe(false);
|
|
$animate.flush();
|
|
|
|
if (method === 'then') {
|
|
$rootScope.$digest();
|
|
}
|
|
expect(done).toBe(true);
|
|
});
|
|
});
|
|
|
|
they('should close the animation, but still accept recognize runner.$prop if no animation is detected',
|
|
['done(cancel)', 'catch'], function(method) {
|
|
|
|
inject(function($animateCss, $rootScope) {
|
|
ss.addRule('.the-third-fake-animation', 'background:green;');
|
|
|
|
element.addClass('another-fake-animation');
|
|
var animator = $animateCss(element, {
|
|
event: 'enter',
|
|
structural: true
|
|
});
|
|
|
|
var cancelled = false;
|
|
var runner = animator.start();
|
|
|
|
if (method === 'catch') {
|
|
runner.catch(function() {
|
|
cancelled = true;
|
|
});
|
|
} else {
|
|
runner.done(function(status) {
|
|
cancelled = status === false;
|
|
});
|
|
}
|
|
|
|
expect(cancelled).toBe(false);
|
|
runner.cancel();
|
|
|
|
if (method === 'catch') {
|
|
$rootScope.$digest();
|
|
}
|
|
expect(cancelled).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('should use the highest transition duration value detected in the CSS class', inject(function($animateCss) {
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;' +
|
|
'transition-duration:10s, 15s, 20s;');
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
transitionProgress(element, 10);
|
|
assertAnimationComplete(false);
|
|
|
|
transitionProgress(element, 15);
|
|
assertAnimationComplete(false);
|
|
|
|
transitionProgress(element, 20);
|
|
assertAnimationComplete(true);
|
|
}));
|
|
|
|
it('should use the highest transition delay value detected in the CSS class', inject(function($animateCss) {
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;' +
|
|
'transition-delay:10s, 15s, 20s;');
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
transitionProgress(element, 1, 10);
|
|
assertAnimationComplete(false);
|
|
|
|
transitionProgress(element, 1, 15);
|
|
assertAnimationComplete(false);
|
|
|
|
transitionProgress(element, 1, 20);
|
|
assertAnimationComplete(true);
|
|
}));
|
|
|
|
it('should only close when both the animation delay and duration have passed',
|
|
inject(function($animateCss) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s 5s linear all;');
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
transitionProgress(element, 10, 2);
|
|
assertAnimationComplete(false);
|
|
|
|
transitionProgress(element, 9, 6);
|
|
assertAnimationComplete(false);
|
|
|
|
transitionProgress(element, 10, 5);
|
|
assertAnimationComplete(true);
|
|
}));
|
|
|
|
it('should not close a transition when a child element fires the transitionend event',
|
|
inject(function($animateCss) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:4s linear all;');
|
|
ss.addPossiblyPrefixedRule('.non-angular-animation', 'transition:5s linear all;');
|
|
|
|
var child = angular.element('<div class="non-angular-animation"></div>');
|
|
element.append(child);
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
browserTrigger(child, 'transitionend', {
|
|
timeStamp: Date.now(),
|
|
elapsedTime: 5,
|
|
bubbles: true
|
|
});
|
|
|
|
transitionProgress(element, 1);
|
|
|
|
assertAnimationComplete(false);
|
|
|
|
transitionProgress(element, 4);
|
|
assertAnimationComplete(true);
|
|
}));
|
|
|
|
it('should not close a keyframe animation when a child element fires the animationend event',
|
|
inject(function($animateCss) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:animation 4s;');
|
|
ss.addPossiblyPrefixedRule('.non-angular-animation', 'animation:animation 5s;');
|
|
|
|
var child = angular.element('<div class="non-angular-animation"></div>');
|
|
element.append(child);
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
browserTrigger(child, 'animationend', {
|
|
timeStamp: Date.now(),
|
|
elapsedTime: 5,
|
|
bubbles: true
|
|
});
|
|
|
|
keyframeProgress(element, 1);
|
|
|
|
assertAnimationComplete(false);
|
|
|
|
keyframeProgress(element, 4);
|
|
assertAnimationComplete(true);
|
|
}));
|
|
|
|
it('should use the highest keyframe duration value detected in the CSS class', inject(function($animateCss) {
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:animation 1s, animation 2s, animation 3s;');
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
keyframeProgress(element, 1);
|
|
assertAnimationComplete(false);
|
|
|
|
keyframeProgress(element, 2);
|
|
assertAnimationComplete(false);
|
|
|
|
keyframeProgress(element, 3);
|
|
assertAnimationComplete(true);
|
|
}));
|
|
|
|
it('should use the highest keyframe delay value detected in the CSS class', inject(function($animateCss) {
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:animation 1s 2s, animation 1s 10s, animation 1s 1000ms;');
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
keyframeProgress(element, 1, 1);
|
|
assertAnimationComplete(false);
|
|
|
|
keyframeProgress(element, 1, 2);
|
|
assertAnimationComplete(false);
|
|
|
|
keyframeProgress(element, 1, 10);
|
|
assertAnimationComplete(true);
|
|
}));
|
|
|
|
it('should use the highest keyframe duration value detected in the CSS class with respect to the animation-iteration-count property', inject(function($animateCss) {
|
|
ss.addPossiblyPrefixedRule('.ng-enter',
|
|
'animation:animation 1s 2s 3, animation 1s 10s 2, animation 1s 1000ms infinite;');
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
keyframeProgress(element, 1, 1);
|
|
assertAnimationComplete(false);
|
|
|
|
keyframeProgress(element, 1, 2);
|
|
assertAnimationComplete(false);
|
|
|
|
keyframeProgress(element, 3, 10);
|
|
assertAnimationComplete(true);
|
|
}));
|
|
|
|
it('should use the highest duration value when both transitions and keyframes are used', inject(function($animateCss) {
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;' +
|
|
'transition-duration:10s, 15s, 20s;' +
|
|
'animation:animation 1s, animation 2s, animation 3s 0s 7;');
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
transitionProgress(element, 10);
|
|
keyframeProgress(element, 10);
|
|
assertAnimationComplete(false);
|
|
|
|
transitionProgress(element, 15);
|
|
keyframeProgress(element, 15);
|
|
assertAnimationComplete(false);
|
|
|
|
transitionProgress(element, 20);
|
|
keyframeProgress(element, 20);
|
|
assertAnimationComplete(false);
|
|
|
|
// 7 * 3 = 21
|
|
transitionProgress(element, 21);
|
|
keyframeProgress(element, 21);
|
|
assertAnimationComplete(true);
|
|
}));
|
|
|
|
it('should use the highest delay value when both transitions and keyframes are used', inject(function($animateCss) {
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;' +
|
|
'transition-delay:10s, 15s, 20s;' +
|
|
'animation:animation 1s 2s, animation 1s 16s, animation 1s 19s;');
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
transitionProgress(element, 1, 10);
|
|
keyframeProgress(element, 1, 10);
|
|
assertAnimationComplete(false);
|
|
|
|
transitionProgress(element, 1, 16);
|
|
keyframeProgress(element, 1, 16);
|
|
assertAnimationComplete(false);
|
|
|
|
transitionProgress(element, 1, 19);
|
|
keyframeProgress(element, 1, 19);
|
|
assertAnimationComplete(false);
|
|
|
|
transitionProgress(element, 1, 20);
|
|
keyframeProgress(element, 1, 20);
|
|
assertAnimationComplete(true);
|
|
}));
|
|
});
|
|
|
|
describe('staggering', function() {
|
|
it('should apply a stagger based when an active ng-EVENT-stagger class with a transition-delay is detected',
|
|
inject(function($animateCss, $document, $rootElement, $timeout) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'transition-delay:0.2s');
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:2s linear all');
|
|
|
|
var elements = [];
|
|
var i;
|
|
var elm;
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
elm = angular.element('<div></div>');
|
|
elements.push(elm);
|
|
$rootElement.append(elm);
|
|
|
|
$animateCss(elm, { event: 'enter', structural: true }).start();
|
|
expect(elm).not.toHaveClass('ng-enter-stagger');
|
|
expect(elm).toHaveClass('ng-enter');
|
|
}
|
|
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(elements[0]).toHaveClass('ng-enter-active');
|
|
for (i = 1; i < 5; i++) {
|
|
elm = elements[i];
|
|
|
|
expect(elm).not.toHaveClass('ng-enter-active');
|
|
$timeout.flush(200);
|
|
expect(elm).toHaveClass('ng-enter-active');
|
|
|
|
browserTrigger(elm, 'transitionend',
|
|
{ timeStamp: Date.now() + 1000, elapsedTime: 2 });
|
|
|
|
expect(elm).not.toHaveClass('ng-enter');
|
|
expect(elm).not.toHaveClass('ng-enter-active');
|
|
expect(elm).not.toHaveClass('ng-enter-stagger');
|
|
}
|
|
}));
|
|
|
|
it('should apply a stagger based when for all provided addClass/removeClass CSS classes',
|
|
inject(function($animateCss, $document, $rootElement, $timeout) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.red-add-stagger,' +
|
|
'.blue-remove-stagger,' +
|
|
'.green-add-stagger', 'transition-delay:0.2s');
|
|
|
|
ss.addPossiblyPrefixedRule('.red-add,' +
|
|
'.blue-remove,' +
|
|
'.green-add', 'transition:2s linear all');
|
|
|
|
var elements = [];
|
|
var i;
|
|
var elm;
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
elm = angular.element('<div class="blue"></div>');
|
|
elements.push(elm);
|
|
$rootElement.append(elm);
|
|
|
|
$animateCss(elm, {
|
|
addClass: 'red green',
|
|
removeClass: 'blue'
|
|
}).start();
|
|
}
|
|
|
|
triggerAnimationStartFrame();
|
|
for (i = 0; i < 5; i++) {
|
|
elm = elements[i];
|
|
|
|
expect(elm).not.toHaveClass('red-add-stagger');
|
|
expect(elm).not.toHaveClass('green-add-stagger');
|
|
expect(elm).not.toHaveClass('blue-remove-stagger');
|
|
|
|
expect(elm).toHaveClass('red-add');
|
|
expect(elm).toHaveClass('green-add');
|
|
expect(elm).toHaveClass('blue-remove');
|
|
}
|
|
|
|
expect(elements[0]).toHaveClass('red-add-active');
|
|
expect(elements[0]).toHaveClass('green-add-active');
|
|
expect(elements[0]).toHaveClass('blue-remove-active');
|
|
for (i = 1; i < 5; i++) {
|
|
elm = elements[i];
|
|
|
|
expect(elm).not.toHaveClass('red-add-active');
|
|
expect(elm).not.toHaveClass('green-add-active');
|
|
expect(elm).not.toHaveClass('blue-remove-active');
|
|
|
|
$timeout.flush(200);
|
|
|
|
expect(elm).toHaveClass('red-add-active');
|
|
expect(elm).toHaveClass('green-add-active');
|
|
expect(elm).toHaveClass('blue-remove-active');
|
|
|
|
browserTrigger(elm, 'transitionend',
|
|
{ timeStamp: Date.now() + 1000, elapsedTime: 2 });
|
|
|
|
expect(elm).not.toHaveClass('red-add-active');
|
|
expect(elm).not.toHaveClass('green-add-active');
|
|
expect(elm).not.toHaveClass('blue-remove-active');
|
|
|
|
expect(elm).not.toHaveClass('red-add-stagger');
|
|
expect(elm).not.toHaveClass('green-add-stagger');
|
|
expect(elm).not.toHaveClass('blue-remove-stagger');
|
|
}
|
|
}));
|
|
|
|
it('should block the transition animation between start and animate when staggered',
|
|
inject(function($animateCss, $document, $rootElement) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'transition-delay:0.2s');
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:2s linear all;');
|
|
|
|
var element;
|
|
var i;
|
|
var elms = [];
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
element = angular.element('<div class="transition-animation"></div>');
|
|
$rootElement.append(element);
|
|
|
|
$animateCss(element, { event: 'enter', structural: true }).start();
|
|
elms.push(element);
|
|
}
|
|
|
|
triggerAnimationStartFrame();
|
|
for (i = 0; i < 5; i++) {
|
|
element = elms[i];
|
|
if (i === 0) {
|
|
expect(element.attr('style')).toBeFalsy();
|
|
} else {
|
|
expect(element.css('transition-delay')).toContain('-2s');
|
|
}
|
|
}
|
|
}));
|
|
|
|
it('should block (pause) the keyframe animation between start and animate when staggered',
|
|
inject(function($animateCss, $document, $rootElement) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'animation-delay:0.2s');
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:my_animation 2s;');
|
|
|
|
var i, element, elements = [];
|
|
for (i = 0; i < 5; i++) {
|
|
element = angular.element('<div class="transition-animation"></div>');
|
|
$rootElement.append(element);
|
|
|
|
$animateCss(element, { event: 'enter', structural: true }).start();
|
|
elements.push(element);
|
|
}
|
|
|
|
triggerAnimationStartFrame();
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
element = elements[i];
|
|
if (i === 0) { // the first element is always run right away
|
|
expect(element.attr('style')).toBeFalsy();
|
|
} else {
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-play-state')).toBe('paused');
|
|
}
|
|
}
|
|
}));
|
|
|
|
it('should not apply a stagger if the transition delay value is inherited from a earlier CSS class',
|
|
inject(function($animateCss, $document, $rootElement) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.transition-animation', 'transition:2s 5s linear all;');
|
|
|
|
for (var i = 0; i < 5; i++) {
|
|
var element = angular.element('<div class="transition-animation"></div>');
|
|
$rootElement.append(element);
|
|
|
|
$animateCss(element, { event: 'enter', structural: true }).start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(element).toHaveClass('ng-enter-active');
|
|
}
|
|
}));
|
|
|
|
it('should apply a stagger only if the transition duration value is zero when inherited from a earlier CSS class',
|
|
inject(function($animateCss, $document, $rootElement) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.transition-animation', 'transition:2s 5s linear all;');
|
|
ss.addPossiblyPrefixedRule('.transition-animation.ng-enter-stagger',
|
|
'transition-duration:0s; transition-delay:0.2s;');
|
|
|
|
var element, i, elms = [];
|
|
for (i = 0; i < 5; i++) {
|
|
element = angular.element('<div class="transition-animation"></div>');
|
|
$rootElement.append(element);
|
|
|
|
elms.push(element);
|
|
$animateCss(element, { event: 'enter', structural: true }).start();
|
|
}
|
|
|
|
triggerAnimationStartFrame();
|
|
for (i = 1; i < 5; i++) {
|
|
element = elms[i];
|
|
expect(element).not.toHaveClass('ng-enter-active');
|
|
}
|
|
}));
|
|
|
|
|
|
it('should ignore animation staggers if only transition animations were detected',
|
|
inject(function($animateCss, $document, $rootElement) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'animation-delay:0.2s');
|
|
ss.addPossiblyPrefixedRule('.transition-animation', 'transition:2s 5s linear all;');
|
|
|
|
for (var i = 0; i < 5; i++) {
|
|
var element = angular.element('<div class="transition-animation"></div>');
|
|
$rootElement.append(element);
|
|
|
|
$animateCss(element, { event: 'enter', structural: true }).start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(element).toHaveClass('ng-enter-active');
|
|
}
|
|
}));
|
|
|
|
it('should ignore transition staggers if only keyframe animations were detected',
|
|
inject(function($animateCss, $document, $rootElement) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'transition-delay:0.2s');
|
|
ss.addPossiblyPrefixedRule('.transition-animation', 'animation: 2s 5s my_animation;');
|
|
|
|
for (var i = 0; i < 5; i++) {
|
|
var elm = angular.element('<div class="transition-animation"></div>');
|
|
$rootElement.append(elm);
|
|
|
|
var animator = $animateCss(elm, { event: 'enter', structural: true }).start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(elm).toHaveClass('ng-enter-active');
|
|
}
|
|
}));
|
|
|
|
it('should start on the highest stagger value if both transition and keyframe staggers are used together',
|
|
inject(function($animateCss, $document, $rootElement, $timeout, $browser) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'transition-delay: 0.5s; ' +
|
|
'animation-delay: 1s');
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition: 10s linear all; ' +
|
|
'animation: 20s my_animation');
|
|
|
|
var i, elm, elements = [];
|
|
for (i = 0; i < 5; i++) {
|
|
elm = angular.element('<div></div>');
|
|
elements.push(elm);
|
|
$rootElement.append(elm);
|
|
|
|
$animateCss(elm, { event: 'enter', structural: true }).start();
|
|
|
|
expect(elm).toHaveClass('ng-enter');
|
|
}
|
|
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(elements[0]).toHaveClass('ng-enter-active');
|
|
for (i = 1; i < 5; i++) {
|
|
elm = elements[i];
|
|
|
|
expect(elm).not.toHaveClass('ng-enter-active');
|
|
|
|
$timeout.flush(500);
|
|
expect(elm).not.toHaveClass('ng-enter-active');
|
|
|
|
$timeout.flush(500);
|
|
expect(elm).toHaveClass('ng-enter-active');
|
|
}
|
|
}));
|
|
|
|
it('should apply the closing timeout ontop of the stagger timeout',
|
|
inject(function($animateCss, $document, $rootElement, $timeout, $browser) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'transition-delay:1s;');
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all;');
|
|
|
|
var elm, i, elms = [];
|
|
for (i = 0; i < 5; i++) {
|
|
elm = angular.element('<div></div>');
|
|
elms.push(elm);
|
|
$rootElement.append(elm);
|
|
|
|
$animateCss(elm, { event: 'enter', structural: true }).start();
|
|
triggerAnimationStartFrame();
|
|
}
|
|
|
|
for (i = 1; i < 2; i++) {
|
|
elm = elms[i];
|
|
expect(elm).toHaveClass('ng-enter');
|
|
$timeout.flush(1000);
|
|
$timeout.flush(15000);
|
|
expect(elm).not.toHaveClass('ng-enter');
|
|
}
|
|
}));
|
|
|
|
it('should apply the closing timeout ontop of the stagger timeout with an added delay',
|
|
inject(function($animateCss, $document, $rootElement, $timeout, $browser) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter-stagger', 'transition-delay:1s;');
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all; transition-delay:50s;');
|
|
|
|
var elm, i, elms = [];
|
|
for (i = 0; i < 5; i++) {
|
|
elm = angular.element('<div></div>');
|
|
elms.push(elm);
|
|
$rootElement.append(elm);
|
|
|
|
$animateCss(elm, { event: 'enter', structural: true }).start();
|
|
triggerAnimationStartFrame();
|
|
}
|
|
|
|
for (i = 1; i < 2; i++) {
|
|
elm = elms[i];
|
|
expect(elm).toHaveClass('ng-enter');
|
|
$timeout.flush(1000);
|
|
$timeout.flush(65000);
|
|
expect(elm).not.toHaveClass('ng-enter');
|
|
}
|
|
}));
|
|
|
|
it('should issue a stagger if a stagger value is provided in the options',
|
|
inject(function($animateCss, $document, $rootElement, $timeout) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:2s linear all');
|
|
|
|
var elm, i, elements = [];
|
|
for (i = 0; i < 5; i++) {
|
|
elm = angular.element('<div></div>');
|
|
elements.push(elm);
|
|
$rootElement.append(elm);
|
|
|
|
$animateCss(elm, {
|
|
event: 'enter',
|
|
structural: true,
|
|
stagger: 0.5
|
|
}).start();
|
|
expect(elm).toHaveClass('ng-enter');
|
|
}
|
|
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(elements[0]).toHaveClass('ng-enter-active');
|
|
for (i = 1; i < 5; i++) {
|
|
elm = elements[i];
|
|
|
|
expect(elm).not.toHaveClass('ng-enter-active');
|
|
$timeout.flush(500);
|
|
expect(elm).toHaveClass('ng-enter-active');
|
|
|
|
browserTrigger(elm, 'transitionend',
|
|
{ timeStamp: Date.now() + 1000, elapsedTime: 2 });
|
|
|
|
expect(elm).not.toHaveClass('ng-enter');
|
|
expect(elm).not.toHaveClass('ng-enter-active');
|
|
expect(elm).not.toHaveClass('ng-enter-stagger');
|
|
}
|
|
}));
|
|
|
|
it('should only add/remove classes once the stagger timeout has passed',
|
|
inject(function($animateCss, $document, $rootElement, $timeout) {
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var element = angular.element('<div class="green"></div>');
|
|
$rootElement.append(element);
|
|
|
|
$animateCss(element, {
|
|
addClass: 'red',
|
|
removeClass: 'green',
|
|
duration: 5,
|
|
stagger: 0.5,
|
|
staggerIndex: 3
|
|
}).start();
|
|
|
|
triggerAnimationStartFrame();
|
|
expect(element).toHaveClass('green');
|
|
expect(element).not.toHaveClass('red');
|
|
|
|
$timeout.flush(1500);
|
|
expect(element).not.toHaveClass('green');
|
|
expect(element).toHaveClass('red');
|
|
}));
|
|
});
|
|
|
|
describe('closing timeout', function() {
|
|
it('should close off the animation after 150% of the animation time has passed',
|
|
inject(function($animateCss, $document, $rootElement, $timeout) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all;');
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var animator = $animateCss(element, { event: 'enter', structural: true });
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(element).toHaveClass('ng-enter');
|
|
expect(element).toHaveClass('ng-enter-active');
|
|
|
|
$timeout.flush(15000);
|
|
|
|
expect(element).not.toHaveClass('ng-enter');
|
|
expect(element).not.toHaveClass('ng-enter-active');
|
|
}));
|
|
|
|
it('should close off the animation after 150% of the animation time has passed and consider the detected delay value',
|
|
inject(function($animateCss, $document, $rootElement, $timeout) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all; transition-delay:30s;');
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var animator = $animateCss(element, { event: 'enter', structural: true });
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(element).toHaveClass('ng-enter');
|
|
expect(element).toHaveClass('ng-enter-active');
|
|
|
|
$timeout.flush(45000);
|
|
|
|
expect(element).not.toHaveClass('ng-enter');
|
|
expect(element).not.toHaveClass('ng-enter-active');
|
|
}));
|
|
|
|
it('should still resolve the animation once expired',
|
|
inject(function($animateCss, $document, $rootElement, $timeout, $animate, $rootScope) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all;');
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var animator = $animateCss(element, { event: 'enter', structural: true });
|
|
|
|
var failed, passed;
|
|
animator.start().then(function() {
|
|
passed = true;
|
|
}, function() {
|
|
failed = true;
|
|
});
|
|
|
|
triggerAnimationStartFrame();
|
|
$timeout.flush(15000);
|
|
$animate.flush();
|
|
$rootScope.$digest();
|
|
expect(passed).toBe(true);
|
|
}));
|
|
|
|
it('should not resolve/reject after passing if the animation completed successfully',
|
|
inject(function($animateCss, $document, $rootElement, $timeout, $rootScope, $animate) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all;');
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var animator = $animateCss(element, { event: 'enter', structural: true });
|
|
|
|
var failed, passed;
|
|
animator.start().then(
|
|
function() {
|
|
passed = true;
|
|
},
|
|
function() {
|
|
failed = true;
|
|
}
|
|
);
|
|
triggerAnimationStartFrame();
|
|
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now() + 1000, elapsedTime: 10 });
|
|
|
|
$animate.flush();
|
|
$rootScope.$digest();
|
|
|
|
expect(passed).toBe(true);
|
|
expect(failed).not.toBe(true);
|
|
|
|
$timeout.flush(15000);
|
|
|
|
expect(passed).toBe(true);
|
|
expect(failed).not.toBe(true);
|
|
}));
|
|
|
|
it('should close all stacked animations after the last timeout runs on the same element',
|
|
inject(function($animateCss, $document, $rootElement, $timeout, $animate) {
|
|
|
|
var now = 0;
|
|
spyOn(Date, 'now').and.callFake(function() {
|
|
return now;
|
|
});
|
|
|
|
var cancelSpy = spyOn($timeout, 'cancel').and.callThrough();
|
|
var doneSpy = jasmine.createSpy();
|
|
|
|
ss.addPossiblyPrefixedRule('.elm', 'transition:1s linear all;');
|
|
ss.addRule('.elm.red', 'background:red;');
|
|
ss.addPossiblyPrefixedRule('.elm.blue', 'transition:2s linear all; background:blue;');
|
|
ss.addRule('.elm.green', 'background:green;');
|
|
|
|
var element = angular.element('<div class="elm"></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
// timeout will be at 1500s
|
|
animate(element, 'red', doneSpy);
|
|
expect(doneSpy).not.toHaveBeenCalled();
|
|
|
|
fastForwardClock(500); //1000s left to go
|
|
|
|
// timeout will not be at 500 + 3000s = 3500s
|
|
animate(element, 'blue', doneSpy);
|
|
expect(doneSpy).not.toHaveBeenCalled();
|
|
expect(cancelSpy).toHaveBeenCalled();
|
|
|
|
cancelSpy.calls.reset();
|
|
|
|
// timeout will not be set again since the former animation is longer
|
|
animate(element, 'green', doneSpy);
|
|
expect(doneSpy).not.toHaveBeenCalled();
|
|
expect(cancelSpy).not.toHaveBeenCalled();
|
|
|
|
// this will close the animations fully
|
|
fastForwardClock(3500);
|
|
$animate.flush();
|
|
|
|
expect(doneSpy).toHaveBeenCalled();
|
|
expect(doneSpy).toHaveBeenCalledTimes(3);
|
|
|
|
function fastForwardClock(time) {
|
|
now += time;
|
|
$timeout.flush(time);
|
|
}
|
|
|
|
function animate(element, klass, onDone) {
|
|
var animator = $animateCss(element, { addClass: klass }).start();
|
|
animator.done(onDone);
|
|
triggerAnimationStartFrame();
|
|
return animator;
|
|
}
|
|
}));
|
|
|
|
it('should not throw an error any pending timeout requests resolve after the element has already been removed',
|
|
inject(function($animateCss, $document, $rootElement, $timeout, $animate) {
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.red', 'transition:1s linear all;');
|
|
|
|
$animateCss(element, { addClass: 'red' }).start();
|
|
triggerAnimationStartFrame();
|
|
element.remove();
|
|
|
|
expect(function() {
|
|
$timeout.flush();
|
|
}).not.toThrow();
|
|
}));
|
|
|
|
it('should consider a positive options.delay value for the closing timeout',
|
|
inject(function($animateCss, $rootElement, $timeout, $document) {
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var options = {
|
|
delay: 3,
|
|
duration: 3,
|
|
to: {
|
|
height: '100px'
|
|
}
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
// At this point, the animation should still be running (closing timeout is 7500ms ... duration * 1.5 + delay => 7.5)
|
|
$timeout.flush(7000);
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, 'transition-delay')).toBe('3s');
|
|
expect(getPossiblyPrefixedStyleValue(element, 'transition-duration')).toBe('3s');
|
|
|
|
// Let's flush the remaining amount of time for the timeout timer to kick in
|
|
$timeout.flush(500);
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, 'transition-duration')).toBeOneOf('', '0s');
|
|
expect(getPossiblyPrefixedStyleValue(element, 'transition-delay')).toBeOneOf('', '0s');
|
|
}));
|
|
|
|
it('should ignore a boolean options.delay value for the closing timeout',
|
|
inject(function($animateCss, $rootElement, $timeout, $document) {
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var options = {
|
|
delay: true,
|
|
duration: 3,
|
|
to: {
|
|
height: '100px'
|
|
}
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
// At this point, the animation should still be running (closing timeout is 4500ms ... duration * 1.5 => 4.5)
|
|
$timeout.flush(4000);
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, 'transition-delay')).toBeOneOf('initial', '0s');
|
|
expect(getPossiblyPrefixedStyleValue(element, 'transition-duration')).toBe('3s');
|
|
|
|
// Let's flush the remaining amount of time for the timeout timer to kick in
|
|
$timeout.flush(500);
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, 'transition-duration')).toBeOneOf('', '0s');
|
|
expect(getPossiblyPrefixedStyleValue(element, 'transition-delay')).toBeOneOf('', '0s');
|
|
}));
|
|
|
|
|
|
it('should cancel the timeout when the animation is ended normally',
|
|
inject(function($animateCss, $document, $rootElement, $timeout) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:10s linear all;');
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var animator = $animateCss(element, { event: 'enter', structural: true });
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element).toHaveClass('ng-enter');
|
|
expect(element).toHaveClass('ng-enter-active');
|
|
|
|
animator.end();
|
|
|
|
expect(element.data(ANIMATE_TIMER_KEY)).toBeUndefined();
|
|
$timeout.verifyNoPendingTasks();
|
|
}));
|
|
|
|
});
|
|
|
|
describe('getComputedStyle', function() {
|
|
var count;
|
|
var acceptableTimingsData = {
|
|
transitionDuration: '10s'
|
|
};
|
|
|
|
beforeEach(module(function($provide) {
|
|
count = {};
|
|
$provide.value('$window', extend({}, window, {
|
|
document: angular.element(window.document),
|
|
getComputedStyle: function(node) {
|
|
var key = node.className.indexOf('stagger') >= 0
|
|
? 'stagger' : 'normal';
|
|
count[key] = count[key] || 0;
|
|
count[key]++;
|
|
return acceptableTimingsData;
|
|
}
|
|
}));
|
|
|
|
return function($document, $rootElement) {
|
|
angular.element($document[0].body).append($rootElement);
|
|
};
|
|
}));
|
|
|
|
it('should cache frequent calls to getComputedStyle before the next animation frame kicks in',
|
|
inject(function($animateCss, $document, $rootElement) {
|
|
|
|
var i, elm, animator;
|
|
for (i = 0; i < 5; i++) {
|
|
elm = angular.element('<div></div>');
|
|
$rootElement.append(elm);
|
|
animator = $animateCss(elm, { event: 'enter', structural: true });
|
|
var runner = animator.start();
|
|
}
|
|
|
|
expect(count.normal).toBe(1);
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
elm = angular.element('<div></div>');
|
|
$rootElement.append(elm);
|
|
animator = $animateCss(elm, { event: 'enter', structural: true });
|
|
animator.start();
|
|
}
|
|
|
|
expect(count.normal).toBe(1);
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(count.normal).toBe(2);
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
elm = angular.element('<div></div>');
|
|
$rootElement.append(elm);
|
|
animator = $animateCss(elm, { event: 'enter', structural: true });
|
|
animator.start();
|
|
}
|
|
|
|
expect(count.normal).toBe(3);
|
|
}));
|
|
|
|
it('should cache frequent calls to getComputedStyle for stagger animations before the next animation frame kicks in',
|
|
inject(function($animateCss, $document, $rootElement, $$rAF) {
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
var animator = $animateCss(element, { event: 'enter', structural: true });
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(count.stagger).toBeUndefined();
|
|
|
|
var i, elm;
|
|
for (i = 0; i < 5; i++) {
|
|
elm = angular.element('<div></div>');
|
|
$rootElement.append(elm);
|
|
animator = $animateCss(elm, { event: 'enter', structural: true });
|
|
animator.start();
|
|
}
|
|
|
|
expect(count.stagger).toBe(1);
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
elm = angular.element('<div></div>');
|
|
$rootElement.append(elm);
|
|
animator = $animateCss(elm, { event: 'enter', structural: true });
|
|
animator.start();
|
|
}
|
|
|
|
expect(count.stagger).toBe(1);
|
|
$$rAF.flush();
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
elm = angular.element('<div></div>');
|
|
$rootElement.append(elm);
|
|
animator = $animateCss(elm, { event: 'enter', structural: true });
|
|
animator.start();
|
|
}
|
|
|
|
triggerAnimationStartFrame();
|
|
expect(count.stagger).toBe(2);
|
|
}));
|
|
});
|
|
|
|
describe('transitionend/animationend event listeners', function() {
|
|
var element, elementOnSpy, elementOffSpy, progress;
|
|
|
|
function setStyles(event) {
|
|
switch (event) {
|
|
case TRANSITIONEND_EVENT:
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition: 10s linear all;');
|
|
progress = transitionProgress;
|
|
break;
|
|
case ANIMATIONEND_EVENT:
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation: animation 10s;');
|
|
progress = keyframeProgress;
|
|
break;
|
|
}
|
|
}
|
|
|
|
beforeEach(inject(function($rootElement, $document) {
|
|
element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
elementOnSpy = spyOn(element, 'on').and.callThrough();
|
|
elementOffSpy = spyOn(element, 'off').and.callThrough();
|
|
}));
|
|
|
|
they('should remove the $prop event listeners on cancel',
|
|
[TRANSITIONEND_EVENT, ANIMATIONEND_EVENT], function(event) {
|
|
inject(function($animateCss) {
|
|
|
|
setStyles(event);
|
|
|
|
var animator = $animateCss(element, {
|
|
event: 'enter',
|
|
structural: true
|
|
});
|
|
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(elementOnSpy).toHaveBeenCalledOnce();
|
|
expect(elementOnSpy.calls.mostRecent().args[0]).toBe(event);
|
|
|
|
runner.cancel();
|
|
|
|
expect(elementOffSpy).toHaveBeenCalledOnce();
|
|
expect(elementOffSpy.calls.mostRecent().args[0]).toBe(event);
|
|
});
|
|
});
|
|
|
|
they('should remove the $prop event listener when the animation is closed',
|
|
[TRANSITIONEND_EVENT, ANIMATIONEND_EVENT], function(event) {
|
|
inject(function($animateCss) {
|
|
|
|
setStyles(event);
|
|
|
|
var animator = $animateCss(element, {
|
|
event: 'enter',
|
|
structural: true
|
|
});
|
|
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(elementOnSpy).toHaveBeenCalledOnce();
|
|
expect(elementOnSpy.calls.mostRecent().args[0]).toBe(event);
|
|
|
|
progress(element, 10);
|
|
|
|
expect(elementOffSpy).toHaveBeenCalledOnce();
|
|
expect(elementOffSpy.calls.mostRecent().args[0]).toBe(event);
|
|
});
|
|
});
|
|
|
|
they('should remove the $prop event listener when the closing timeout occurs',
|
|
[TRANSITIONEND_EVENT, ANIMATIONEND_EVENT], function(event) {
|
|
inject(function($animateCss, $timeout) {
|
|
|
|
setStyles(event);
|
|
|
|
var animator = $animateCss(element, {
|
|
event: 'enter',
|
|
structural: true
|
|
});
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(elementOnSpy).toHaveBeenCalledOnce();
|
|
expect(elementOnSpy.calls.mostRecent().args[0]).toBe(event);
|
|
|
|
$timeout.flush(15000);
|
|
|
|
expect(elementOffSpy).toHaveBeenCalledOnce();
|
|
expect(elementOffSpy.calls.mostRecent().args[0]).toBe(event);
|
|
});
|
|
});
|
|
|
|
they('should not add or remove $prop event listeners when no animation styles are detected',
|
|
[TRANSITIONEND_EVENT, ANIMATIONEND_EVENT], function(event) {
|
|
inject(function($animateCss, $timeout) {
|
|
|
|
progress = event === TRANSITIONEND_EVENT ? transitionProgress : keyframeProgress;
|
|
|
|
// Make sure other event listeners are not affected
|
|
var otherEndSpy = jasmine.createSpy('otherEndSpy');
|
|
element.on(event, otherEndSpy);
|
|
|
|
expect(elementOnSpy).toHaveBeenCalledOnce();
|
|
elementOnSpy.calls.reset();
|
|
|
|
var animator = $animateCss(element, {
|
|
event: 'enter',
|
|
structural: true
|
|
});
|
|
|
|
expect(animator.$$willAnimate).toBeFalsy();
|
|
|
|
// This will close the animation because no styles have been detected
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(elementOnSpy).not.toHaveBeenCalled();
|
|
expect(elementOffSpy).not.toHaveBeenCalled();
|
|
|
|
progress(element, 10);
|
|
expect(otherEndSpy).toHaveBeenCalledOnce();
|
|
});
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
it('should avoid applying the same cache to an element a follow-up animation is run on the same element',
|
|
inject(function($animateCss, $rootElement, $document) {
|
|
|
|
function endTransition(element, elapsedTime) {
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now(), elapsedTime: elapsedTime });
|
|
}
|
|
|
|
function startAnimation(element, duration, color) {
|
|
$animateCss(element, {
|
|
duration: duration,
|
|
to: { background: color }
|
|
}).start();
|
|
triggerAnimationStartFrame();
|
|
}
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
startAnimation(element, 0.5, 'red');
|
|
expect(element.attr('style')).toContain('transition');
|
|
|
|
endTransition(element, 0.5);
|
|
expect(element.attr('style')).not.toContain('transition');
|
|
|
|
startAnimation(element, 0.8, 'blue');
|
|
expect(element.attr('style')).toContain('transition');
|
|
|
|
// Trigger an extra transitionend event that matches the original transition
|
|
endTransition(element, 0.5);
|
|
expect(element.attr('style')).toContain('transition');
|
|
|
|
endTransition(element, 0.8);
|
|
expect(element.attr('style')).not.toContain('transition');
|
|
}));
|
|
|
|
it('should clear cache if no animation so follow-up animation on the same element will not be from cache',
|
|
inject(function($animateCss, $rootElement, $document, $$rAF) {
|
|
var element = angular.element('<div class="rclass"></div>');
|
|
var options = {
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
var animator = $animateCss(element, options);
|
|
expect(animator.$$willAnimate).toBeFalsy();
|
|
|
|
$$rAF.flush();
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:3.5s keyframe_animation;');
|
|
animator = $animateCss(element, options);
|
|
expect(animator.$$willAnimate).toBeTruthy();
|
|
}));
|
|
|
|
it('should apply a custom temporary class when a non-structural animation is used',
|
|
inject(function($animateCss, $rootElement, $document) {
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
$animateCss(element, {
|
|
event: 'super',
|
|
duration: 1000,
|
|
to: fakeStyle
|
|
}).start();
|
|
expect(element).toHaveClass('super');
|
|
|
|
triggerAnimationStartFrame();
|
|
expect(element).toHaveClass('super-active');
|
|
}));
|
|
|
|
describe('structural animations', function() {
|
|
they('should decorate the element with the ng-$prop CSS class',
|
|
['enter', 'leave', 'move'], function(event) {
|
|
inject(function($animateCss, $rootElement, $document) {
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
$animateCss(element, {
|
|
event: event,
|
|
structural: true,
|
|
duration: 1000,
|
|
to: fakeStyle
|
|
});
|
|
expect(element).toHaveClass('ng-' + event);
|
|
});
|
|
});
|
|
|
|
they('should decorate the element with the ng-$prop-active CSS class',
|
|
['enter', 'leave', 'move'], function(event) {
|
|
inject(function($animateCss, $rootElement, $document) {
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var animator = $animateCss(element, {
|
|
event: event,
|
|
structural: true,
|
|
duration: 1000,
|
|
to: fakeStyle
|
|
});
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element).toHaveClass('ng-' + event + '-active');
|
|
});
|
|
});
|
|
|
|
they('should remove the ng-$prop and ng-$prop-active CSS classes from the element once the animation is done',
|
|
['enter', 'leave', 'move'], function(event) {
|
|
inject(function($animateCss, $rootElement, $document) {
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var animator = $animateCss(element, {
|
|
event: event,
|
|
structural: true,
|
|
duration: 1,
|
|
to: fakeStyle
|
|
});
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now() + 1000, elapsedTime: 1 });
|
|
|
|
expect(element).not.toHaveClass('ng-' + event);
|
|
expect(element).not.toHaveClass('ng-' + event + '-active');
|
|
});
|
|
});
|
|
|
|
they('should allow additional CSS classes to be added and removed alongside the $prop animation',
|
|
['enter', 'leave', 'move'], function(event) {
|
|
inject(function($animateCss, $rootElement) {
|
|
var element = angular.element('<div class="green"></div>');
|
|
$rootElement.append(element);
|
|
var animator = $animateCss(element, {
|
|
event: event,
|
|
structural: true,
|
|
duration: 1,
|
|
to: fakeStyle,
|
|
addClass: 'red',
|
|
removeClass: 'green'
|
|
});
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element).toHaveClass('ng-' + event);
|
|
expect(element).toHaveClass('ng-' + event + '-active');
|
|
|
|
expect(element).toHaveClass('red');
|
|
expect(element).toHaveClass('red-add');
|
|
expect(element).toHaveClass('red-add-active');
|
|
|
|
expect(element).not.toHaveClass('green');
|
|
expect(element).toHaveClass('green-remove');
|
|
expect(element).toHaveClass('green-remove-active');
|
|
});
|
|
});
|
|
|
|
they('should place a CSS transition block after the preparation function to block accidental style changes',
|
|
['enter', 'leave', 'move', 'addClass', 'removeClass'], function(event) {
|
|
|
|
inject(function($animateCss, $rootElement, $document) {
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.cool-animation', 'transition:1.5s linear all;');
|
|
element.addClass('cool-animation');
|
|
|
|
var data = {};
|
|
if (event === 'addClass') {
|
|
data.addClass = 'green';
|
|
} else if (event === 'removeClass') {
|
|
element.addClass('red');
|
|
data.removeClass = 'red';
|
|
} else {
|
|
data.event = event;
|
|
}
|
|
|
|
var animator = $animateCss(element, data);
|
|
expect(element.css('transition-delay')).toMatch('-1.5s');
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element.attr('style')).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
they('should not place a CSS transition block if options.skipBlocking is provided',
|
|
['enter', 'leave', 'move', 'addClass', 'removeClass'], function(event) {
|
|
|
|
inject(function($animateCss, $rootElement, $document) {
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.cool-animation', 'transition:1.5s linear all;');
|
|
element.addClass('cool-animation');
|
|
|
|
var data = {};
|
|
if (event === 'addClass') {
|
|
data.addClass = 'green';
|
|
} else if (event === 'removeClass') {
|
|
element.addClass('red');
|
|
data.removeClass = 'red';
|
|
} else {
|
|
data.event = event;
|
|
}
|
|
|
|
var blockSpy = spyOn(helpers, 'blockTransitions').and.callThrough();
|
|
|
|
data.skipBlocking = true;
|
|
var animator = $animateCss(element, data);
|
|
|
|
expect(blockSpy).not.toHaveBeenCalled();
|
|
|
|
expect(element.attr('style')).toBeFalsy();
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element.attr('style')).toBeFalsy();
|
|
|
|
// just to prove it works
|
|
data.skipBlocking = false;
|
|
$animateCss(element, { addClass: 'test' });
|
|
expect(blockSpy).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
they('should place a CSS transition block after the preparation function even if a duration is provided',
|
|
['enter', 'leave', 'move', 'addClass', 'removeClass'], function(event) {
|
|
|
|
inject(function($animateCss, $rootElement, $document) {
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
ss.addPossiblyPrefixedRule('.cool-animation', 'transition:1.5s linear all;');
|
|
element.addClass('cool-animation');
|
|
|
|
var data = {};
|
|
if (event === 'addClass') {
|
|
data.addClass = 'green';
|
|
} else if (event === 'removeClass') {
|
|
element.addClass('red');
|
|
data.removeClass = 'red';
|
|
} else {
|
|
data.event = event;
|
|
}
|
|
|
|
data.duration = 10;
|
|
var animator = $animateCss(element, data);
|
|
|
|
expect(element.css('transition-delay')).toMatch('-10s');
|
|
expect(element.css('transition-duration')).toMatch('');
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(element.attr('style')).not.toContain('transition-delay');
|
|
expect(element.css('transition-property')).toContain('all');
|
|
expect(element.css('transition-duration')).toContain('10s');
|
|
});
|
|
});
|
|
|
|
it('should allow multiple events to be animated at the same time',
|
|
inject(function($animateCss, $rootElement, $document) {
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
$animateCss(element, {
|
|
event: ['enter', 'leave', 'move'],
|
|
structural: true,
|
|
duration: 1,
|
|
to: fakeStyle
|
|
}).start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(element).toHaveClass('ng-enter');
|
|
expect(element).toHaveClass('ng-leave');
|
|
expect(element).toHaveClass('ng-move');
|
|
|
|
expect(element).toHaveClass('ng-enter-active');
|
|
expect(element).toHaveClass('ng-leave-active');
|
|
expect(element).toHaveClass('ng-move-active');
|
|
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now() + 1000, elapsedTime: 1 });
|
|
|
|
expect(element).not.toHaveClass('ng-enter');
|
|
expect(element).not.toHaveClass('ng-leave');
|
|
expect(element).not.toHaveClass('ng-move');
|
|
expect(element).not.toHaveClass('ng-enter-active');
|
|
expect(element).not.toHaveClass('ng-leave-active');
|
|
expect(element).not.toHaveClass('ng-move-active');
|
|
}));
|
|
|
|
it('should not break when running anchored animations without duration',
|
|
inject(function($animate, $document, $rootElement) {
|
|
var element1 = angular.element('<div class="item" ng-animate-ref="test">Item 1</div>');
|
|
var element2 = angular.element('<div class="item" ng-animate-ref="test">Item 2</div>');
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
$rootElement.append(element1);
|
|
|
|
expect($rootElement.text()).toBe('Item 1');
|
|
|
|
$animate.leave(element1);
|
|
$animate.enter(element2, $rootElement);
|
|
$animate.flush();
|
|
|
|
expect($rootElement.text()).toBe('Item 2');
|
|
})
|
|
);
|
|
});
|
|
|
|
describe('class-based animations', function() {
|
|
they('should decorate the element with the class-$prop CSS class',
|
|
['add', 'remove'], function(event) {
|
|
inject(function($animateCss, $rootElement) {
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
|
|
var options = {};
|
|
options[event + 'Class'] = 'class';
|
|
options.duration = 1000;
|
|
options.to = fakeStyle;
|
|
$animateCss(element, options);
|
|
expect(element).toHaveClass('class-' + event);
|
|
});
|
|
});
|
|
|
|
they('should decorate the element with the class-$prop-active CSS class',
|
|
['add', 'remove'], function(event) {
|
|
inject(function($animateCss, $rootElement) {
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
|
|
var options = {};
|
|
options[event + 'Class'] = 'class';
|
|
options.duration = 1000;
|
|
options.to = fakeStyle;
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(element).toHaveClass('class-' + event + '-active');
|
|
});
|
|
});
|
|
|
|
they('should remove the class-$prop-add and class-$prop-active CSS classes from the element once the animation is done',
|
|
['enter', 'leave', 'move'], function(event) {
|
|
inject(function($animateCss, $rootElement, $document) {
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var options = {};
|
|
options.event = event;
|
|
options.duration = 10;
|
|
options.to = fakeStyle;
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now() + 1000, elapsedTime: 10 });
|
|
|
|
expect(element).not.toHaveClass('ng-' + event);
|
|
expect(element).not.toHaveClass('ng-' + event + '-active');
|
|
});
|
|
});
|
|
|
|
they('should allow the class duration styles to be recalculated once started if the CSS classes being applied result new transition styles',
|
|
['add', 'remove'], function(event) {
|
|
inject(function($animateCss, $rootElement, $document) {
|
|
|
|
var element = angular.element('<div></div>');
|
|
|
|
if (event === 'add') {
|
|
ss.addPossiblyPrefixedRule('.natural-class', 'transition:1s linear all;');
|
|
} else {
|
|
ss.addPossiblyPrefixedRule('.natural-class', 'transition:0s linear none;');
|
|
ss.addPossiblyPrefixedRule('.base-class', 'transition:1s linear none;');
|
|
|
|
element.addClass('base-class');
|
|
element.addClass('natural-class');
|
|
}
|
|
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var options = {};
|
|
options[event + 'Class'] = 'natural-class';
|
|
var runner = $animateCss(element, options);
|
|
runner.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element).toHaveClass('natural-class-' + event);
|
|
expect(element).toHaveClass('natural-class-' + event + '-active');
|
|
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now(), elapsedTime: 1 });
|
|
|
|
expect(element).not.toHaveClass('natural-class-' + event);
|
|
expect(element).not.toHaveClass('natural-class-' + event + '-active');
|
|
});
|
|
});
|
|
|
|
they('should force the class-based values to be applied early if no options.applyClassEarly is used as an option',
|
|
['enter', 'leave', 'move'], function(event) {
|
|
inject(function($animateCss, $rootElement, $document) {
|
|
|
|
ss.addPossiblyPrefixedRule('.blue.ng-' + event, 'transition:2s linear all;');
|
|
|
|
var element = angular.element('<div class="red"></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
var runner = $animateCss(element, {
|
|
addClass: 'blue',
|
|
applyClassesEarly: true,
|
|
removeClass: 'red',
|
|
event: event,
|
|
structural: true
|
|
});
|
|
|
|
runner.start();
|
|
expect(element).toHaveClass('ng-' + event);
|
|
expect(element).toHaveClass('blue');
|
|
expect(element).not.toHaveClass('red');
|
|
|
|
triggerAnimationStartFrame();
|
|
expect(element).toHaveClass('ng-' + event);
|
|
expect(element).toHaveClass('ng-' + event + '-active');
|
|
expect(element).toHaveClass('blue');
|
|
expect(element).not.toHaveClass('red');
|
|
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now(), elapsedTime: 2 });
|
|
|
|
expect(element).not.toHaveClass('ng-' + event);
|
|
expect(element).not.toHaveClass('ng-' + event + '-active');
|
|
expect(element).toHaveClass('blue');
|
|
expect(element).not.toHaveClass('red');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('options', function() {
|
|
var element;
|
|
beforeEach(module(function() {
|
|
return function($rootElement, $document) {
|
|
angular.element($document[0].body).append($rootElement);
|
|
|
|
element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
};
|
|
}));
|
|
|
|
it('should not alter the provided options input in any way throughout the animation', inject(function($animateCss) {
|
|
var initialOptions = {
|
|
from: { height: '50px' },
|
|
to: { width: '50px' },
|
|
addClass: 'one',
|
|
removeClass: 'two',
|
|
duration: 10,
|
|
delay: 10,
|
|
structural: true,
|
|
keyframeStyle: '1s rotate',
|
|
transitionStyle: '1s linear',
|
|
stagger: 0.5,
|
|
staggerIndex: 3
|
|
};
|
|
|
|
var copiedOptions = copy(initialOptions);
|
|
expect(copiedOptions).toEqual(initialOptions);
|
|
|
|
var animator = $animateCss(element, copiedOptions);
|
|
expect(copiedOptions).toEqual(initialOptions);
|
|
|
|
var runner = animator.start();
|
|
expect(copiedOptions).toEqual(initialOptions);
|
|
|
|
triggerAnimationStartFrame();
|
|
expect(copiedOptions).toEqual(initialOptions);
|
|
|
|
runner.end();
|
|
expect(copiedOptions).toEqual(initialOptions);
|
|
}));
|
|
|
|
it('should not create a copy of the provided options if they have already been prepared earlier',
|
|
inject(function($animate, $animateCss) {
|
|
|
|
var options = {
|
|
from: { height: '50px' },
|
|
to: { width: '50px' },
|
|
addClass: 'one',
|
|
removeClass: 'two'
|
|
};
|
|
|
|
options.$$prepared = true;
|
|
var runner = $animateCss(element, options).start();
|
|
runner.end();
|
|
|
|
$animate.flush();
|
|
|
|
expect(options.addClass).toBeFalsy();
|
|
expect(options.removeClass).toBeFalsy();
|
|
expect(options.to).toBeFalsy();
|
|
expect(options.from).toBeFalsy();
|
|
}));
|
|
|
|
describe('[$$skipPreparationClasses]', function() {
|
|
it('should not apply and remove the preparation classes to the element when true',
|
|
inject(function($animateCss) {
|
|
|
|
var options = {
|
|
duration: 3000,
|
|
to: fakeStyle,
|
|
event: 'event',
|
|
structural: true,
|
|
addClass: 'klass',
|
|
$$skipPreparationClasses: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
expect(element).not.toHaveClass('klass-add');
|
|
expect(element).not.toHaveClass('ng-event');
|
|
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element).not.toHaveClass('klass-add');
|
|
expect(element).not.toHaveClass('ng-event');
|
|
|
|
expect(element).toHaveClass('klass-add-active');
|
|
expect(element).toHaveClass('ng-event-active');
|
|
|
|
element.addClass('klass-add ng-event');
|
|
|
|
runner.end();
|
|
|
|
expect(element).toHaveClass('klass-add');
|
|
expect(element).toHaveClass('ng-event');
|
|
|
|
expect(element).not.toHaveClass('klass-add-active');
|
|
expect(element).not.toHaveClass('ng-event-active');
|
|
}));
|
|
});
|
|
|
|
describe('[duration]', function() {
|
|
it('should be applied for a transition directly', inject(function($animateCss, $rootElement) {
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
|
|
var options = {
|
|
duration: 3000,
|
|
to: fakeStyle,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
var style = element.attr('style');
|
|
expect(style).toContain('3000s');
|
|
expect(style).toContain('linear');
|
|
}));
|
|
|
|
it('should be applied to a CSS keyframe animation directly if keyframes are detected within the CSS class',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:1.5s keyframe_animation;');
|
|
|
|
var options = {
|
|
duration: 5,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-duration')).toEqual('5s');
|
|
}));
|
|
|
|
it('should remove all inline keyframe styling when an animation completes if a custom duration was applied',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:1.5s keyframe_animation;');
|
|
|
|
var options = {
|
|
duration: 5,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
browserTrigger(element, 'animationend',
|
|
{ timeStamp: Date.now() + 5000, elapsedTime: 5 });
|
|
|
|
expect(element.attr('style')).toBeFalsy();
|
|
}));
|
|
|
|
it('should remove all inline keyframe delay styling when an animation completes if a custom duration was applied',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:1.5s keyframe_animation;');
|
|
|
|
var options = {
|
|
delay: 5,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toEqual('5s');
|
|
|
|
browserTrigger(element, 'animationend',
|
|
{ timeStamp: Date.now() + 5000, elapsedTime: 1.5 });
|
|
|
|
expect(element.attr('style')).toBeFalsy();
|
|
}));
|
|
|
|
it('should not prepare the animation at all if a duration of zero is provided',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;');
|
|
|
|
var options = {
|
|
duration: 0,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
expect(animator.$$willAnimate).toBeFalsy();
|
|
}));
|
|
|
|
it('should apply a transition and keyframe duration directly if both transitions and keyframe classes are detected',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:3s keyframe_animation;' +
|
|
'transition:5s linear all;');
|
|
|
|
var options = {
|
|
duration: 4,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
var animator = $animateCss(element, options);
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
var style = element.attr('style');
|
|
expect(style).toMatch(/animation(?:-duration)?:\s*4s/);
|
|
expect(element.css('transition-duration')).toMatch('4s');
|
|
expect(element.css('transition-property')).toMatch('all');
|
|
expect(style).toContain('linear');
|
|
}));
|
|
});
|
|
|
|
describe('[delay]', function() {
|
|
it('should be applied for a transition directly', inject(function($animateCss, $rootElement) {
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
|
|
var options = {
|
|
duration: 3000,
|
|
delay: 500,
|
|
to: fakeStyle,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
var prop = element.css('transition-delay');
|
|
expect(prop).toEqual('500s');
|
|
}));
|
|
|
|
it('should return false for the animator if a delay is provided but not a duration',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
|
|
var options = {
|
|
delay: 500,
|
|
to: fakeStyle,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
expect(animator.$$willAnimate).toBeFalsy();
|
|
}));
|
|
|
|
it('should override the delay value present in the CSS class',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;' +
|
|
'transition-delay:10s;');
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
|
|
var options = {
|
|
delay: 500,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
var prop = element.css('transition-delay');
|
|
expect(prop).toEqual('500s');
|
|
}));
|
|
|
|
it('should allow the delay value to zero if provided',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:1s linear all;' +
|
|
'transition-delay:10s;');
|
|
|
|
var element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
|
|
var options = {
|
|
delay: 0,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
var prop = element.css('transition-delay');
|
|
expect(prop).toEqual('0s');
|
|
}));
|
|
|
|
it('should be applied to a CSS keyframe animation if detected within the CSS class',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:1.5s keyframe_animation;');
|
|
|
|
var options = {
|
|
delay: 400,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toEqual('400s');
|
|
expect(element.attr('style')).not.toContain('transition-delay');
|
|
}));
|
|
|
|
it('should apply a transition and keyframe delay if both transitions and keyframe classes are detected',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:3s keyframe_animation;' +
|
|
'transition:5s linear all;');
|
|
|
|
var options = {
|
|
delay: 10,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
var animator = $animateCss(element, options);
|
|
|
|
expect(element.css('transition-delay')).toContain('-5s');
|
|
expect(element.attr('style')).not.toContain('animation-delay');
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toEqual('10s');
|
|
expect(element.css('transition-delay')).toEqual('10s');
|
|
}));
|
|
|
|
it('should apply the keyframe and transition duration value before the CSS classes are applied', function() {
|
|
var classSpy = jasmine.createSpy();
|
|
module(function($provide) {
|
|
$provide.value('$$jqLite', {
|
|
addClass: function() {
|
|
classSpy();
|
|
},
|
|
removeClass: function() {
|
|
classSpy();
|
|
}
|
|
});
|
|
});
|
|
inject(function($animateCss, $rootElement) {
|
|
element.addClass('element');
|
|
ss.addPossiblyPrefixedRule('.element', 'animation:3s keyframe_animation;' +
|
|
'transition:5s linear all;');
|
|
|
|
var options = {
|
|
delay: 2,
|
|
duration: 2,
|
|
addClass: 'superman',
|
|
$$skipPreparationClasses: true,
|
|
structural: true
|
|
};
|
|
var animator = $animateCss(element, options);
|
|
|
|
expect(element.attr('style') || '').not.toContain('animation-delay');
|
|
expect(element.attr('style') || '').not.toContain('transition-delay');
|
|
expect(classSpy).not.toHaveBeenCalled();
|
|
|
|
//redefine the classSpy to assert that the delay values have been
|
|
//applied before the classes are added
|
|
var assertionsRun = false;
|
|
classSpy = function() {
|
|
assertionsRun = true;
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toEqual('2s');
|
|
expect(element.css('transition-delay')).toEqual('2s');
|
|
expect(element).not.toHaveClass('superman');
|
|
};
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
expect(assertionsRun).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('should apply blocking before the animation starts, but then apply the detected delay when options.delay is true',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition:2s linear all; transition-delay: 1s;');
|
|
|
|
var options = {
|
|
delay: true,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
expect(element.css('transition-delay')).toEqual('-2s');
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element.attr('style') || '').not.toContain('transition-delay');
|
|
}));
|
|
|
|
it('should consider a negative value when delay:true is used with a keyframe animation',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation: 2s keyframe_animation; ' +
|
|
'animation-delay: -1s;');
|
|
|
|
var options = {
|
|
delay: true,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toContain('-1s');
|
|
}));
|
|
|
|
they('should consider a negative value when a negative option delay is provided for a $prop animation', {
|
|
'transition': function() {
|
|
return {
|
|
prop: 'transition-delay',
|
|
css: 'transition:2s linear all'
|
|
};
|
|
},
|
|
'keyframe': function() {
|
|
return {
|
|
prop: 'animation-delay',
|
|
css: 'animation: 2s keyframe_animation'
|
|
};
|
|
}
|
|
}, function(testDetailsFactory) {
|
|
inject(function($animateCss, $rootElement) {
|
|
var testDetails = testDetailsFactory();
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', testDetails.css);
|
|
var options = {
|
|
delay: -2,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, testDetails.prop)).toContain('-2s');
|
|
});
|
|
});
|
|
|
|
they('should expect the $propend event to always return the full duration even when negative values are used', {
|
|
'transition': function() {
|
|
return {
|
|
event: 'transitionend',
|
|
css: 'transition:5s linear all; transition-delay: -2s'
|
|
};
|
|
},
|
|
'animation': function() {
|
|
return {
|
|
event: 'animationend',
|
|
css: 'animation: 5s keyframe_animation; animation-delay: -2s;'
|
|
};
|
|
}
|
|
}, function(testDetailsFactory) {
|
|
inject(function($animateCss, $rootElement) {
|
|
var testDetails = testDetailsFactory();
|
|
var event = testDetails.event;
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', testDetails.css);
|
|
var options = { event: 'enter', structural: true };
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
// 5 + (-2s) = 3
|
|
browserTrigger(element, event, { timeStamp: Date.now(), elapsedTime: 3 });
|
|
|
|
assertAnimationRunning(element, true);
|
|
|
|
// 5 seconds is the full animation
|
|
browserTrigger(element, event, { timeStamp: Date.now(), elapsedTime: 5 });
|
|
|
|
assertAnimationRunning(element);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('[transitionStyle]', function() {
|
|
it('should apply the transition directly onto the element and animate accordingly',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
transitionStyle: '5.5s linear all',
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
var style = element.attr('style');
|
|
expect(element.css('transition-duration')).toMatch('5.5s');
|
|
expect(element.css('transition-property')).toMatch('all');
|
|
expect(style).toContain('linear');
|
|
|
|
expect(element).toHaveClass('ng-enter');
|
|
expect(element).toHaveClass('ng-enter-active');
|
|
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now() + 10000, elapsedTime: 5.5 });
|
|
|
|
expect(element).not.toHaveClass('ng-enter');
|
|
expect(element).not.toHaveClass('ng-enter-active');
|
|
|
|
expect(element.attr('style')).toBeFalsy();
|
|
}));
|
|
|
|
it('should give priority to the provided duration value, but only update the duration style itself',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
transitionStyle: '5.5s ease-in color',
|
|
duration: 4,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
var style = element.attr('style');
|
|
expect(element.css('transition-duration')).toMatch('4s');
|
|
expect(element.css('transition-property')).toMatch('color');
|
|
expect(style).toContain('ease-in');
|
|
}));
|
|
|
|
it('should give priority to the provided delay value, but only update the delay style itself',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
transitionStyle: '5.5s 4s ease-in color',
|
|
delay: 20,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
var style = element.attr('style');
|
|
expect(element.css('transition-delay')).toMatch('20s');
|
|
expect(element.css('transition-duration')).toMatch('5.5s');
|
|
expect(element.css('transition-property')).toMatch('color');
|
|
expect(style).toContain('ease-in');
|
|
}));
|
|
|
|
it('should execute the animation only if there is any provided CSS styling to go with the transition',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
transitionStyle: '6s 4s ease-out all'
|
|
};
|
|
|
|
$animateCss(element, options).start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, 'transition-delay')).not.toEqual('4s');
|
|
expect(getPossiblyPrefixedStyleValue(element, 'transition-duration')).not.toEqual('6s');
|
|
|
|
options.to = { color: 'brown' };
|
|
$animateCss(element, options).start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, 'transition-delay')).toEqual('4s');
|
|
expect(getPossiblyPrefixedStyleValue(element, 'transition-duration')).toEqual('6s');
|
|
}));
|
|
});
|
|
|
|
describe('[keyframeStyle]', function() {
|
|
it('should apply the keyframe animation directly onto the element and animate accordingly',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
keyframeStyle: 'my_animation 5.5s',
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
var detectedStyle = element.attr('style');
|
|
expect(detectedStyle).toContain('5.5s');
|
|
expect(detectedStyle).toContain('my_animation');
|
|
|
|
expect(element).toHaveClass('ng-enter');
|
|
expect(element).toHaveClass('ng-enter-active');
|
|
|
|
browserTrigger(element, 'animationend',
|
|
{ timeStamp: Date.now() + 10000, elapsedTime: 5.5 });
|
|
|
|
expect(element).not.toHaveClass('ng-enter');
|
|
expect(element).not.toHaveClass('ng-enter-active');
|
|
|
|
expect(element.attr('style')).toBeFalsy();
|
|
}));
|
|
|
|
it('should give priority to the provided duration value, but only update the duration style itself',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
keyframeStyle: 'my_animation 5.5s',
|
|
duration: 50,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
var detectedStyle = element.attr('style');
|
|
expect(detectedStyle).toContain('50s');
|
|
expect(detectedStyle).toContain('my_animation');
|
|
}));
|
|
|
|
it('should give priority to the provided delay value, but only update the duration style itself',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
keyframeStyle: 'my_animation 5.5s 10s',
|
|
delay: 50,
|
|
event: 'enter',
|
|
structural: true
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toEqual('50s');
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-duration')).toEqual('5.5s');
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-name')).toEqual('my_animation');
|
|
}));
|
|
|
|
it('should be able to execute the animation if it is the only provided value',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
keyframeStyle: 'my_animation 5.5s 10s'
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-delay')).toEqual('10s');
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-duration')).toEqual('5.5s');
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-name')).toEqual('my_animation');
|
|
}));
|
|
});
|
|
|
|
describe('[from] and [to]', function() {
|
|
it('should apply from styles to an element during the preparation phase',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
duration: 2.5,
|
|
event: 'enter',
|
|
structural: true,
|
|
from: { width: '50px' },
|
|
to: { width: '100px' }
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
expect(element.attr('style')).toMatch(/width:\s*50px/);
|
|
}));
|
|
|
|
it('should apply to styles to an element during the animation phase',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
duration: 2.5,
|
|
event: 'enter',
|
|
structural: true,
|
|
from: { width: '15px' },
|
|
to: { width: '25px' }
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
var runner = animator.start();
|
|
triggerAnimationStartFrame();
|
|
runner.end();
|
|
|
|
expect(element.css('width')).toBe('25px');
|
|
}));
|
|
|
|
it('should apply the union of from and to styles to the element if no animation will be run',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
event: 'enter',
|
|
structural: true,
|
|
from: { 'width': '10px', height: '50px' },
|
|
to: { 'width': '15px' }
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
|
|
expect(animator.$$willAnimate).toBeFalsy();
|
|
animator.start();
|
|
|
|
expect(element.css('width')).toBe('15px');
|
|
expect(element.css('height')).toBe('50px');
|
|
}));
|
|
|
|
it('should retain to and from styles on an element after an animation completes',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
event: 'enter',
|
|
structural: true,
|
|
duration: 10,
|
|
from: { 'width': '10px', height: '66px' },
|
|
to: { 'width': '5px' }
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now() + 10000, elapsedTime: 10 });
|
|
|
|
expect(element).not.toHaveClass('ng-enter');
|
|
expect(element.css('width')).toBe('5px');
|
|
expect(element.css('height')).toBe('66px');
|
|
}));
|
|
|
|
it('should always apply the from styles before the start function is called even if no transition is detected when started',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.my-class', 'transition: 0s linear color');
|
|
|
|
var options = {
|
|
addClass: 'my-class',
|
|
from: { height: '26px' },
|
|
to: { height: '500px' }
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
expect(element.css('height')).toBe('26px');
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element.css('height')).toBe('500px');
|
|
}));
|
|
|
|
it('should apply an inline transition if [to] styles and a duration are provided',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
event: 'enter',
|
|
structural: true,
|
|
duration: 2.5,
|
|
to: { background: 'red' }
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
var style = element.attr('style');
|
|
expect(element.css('transition-duration')).toMatch('2.5s');
|
|
expect(element.css('transition-property')).toMatch('all');
|
|
expect(style).toContain('linear');
|
|
}));
|
|
|
|
it('should remove all inline transition styling when an animation completes',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
event: 'enter',
|
|
structural: true,
|
|
duration: 2.5,
|
|
to: { background: 'red' }
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
var style = element.attr('style');
|
|
expect(style).toContain('transition');
|
|
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now() + 2500, elapsedTime: 2.5 });
|
|
|
|
style = element.attr('style');
|
|
expect(style).not.toContain('transition');
|
|
}));
|
|
|
|
it('should retain existing styles when an inline styled animation completes',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
event: 'enter',
|
|
structural: true,
|
|
duration: 2.5
|
|
};
|
|
|
|
element.css('font-size', '20px');
|
|
element.css('opacity', '0.5');
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
var style = element.attr('style');
|
|
expect(style).toContain('transition');
|
|
animator.end();
|
|
|
|
style = element.attr('style');
|
|
expect(element.attr('style')).not.toContain('transition');
|
|
expect(element.css('opacity')).toEqual('0.5');
|
|
}));
|
|
|
|
it('should remove all inline transition delay styling when an animation completes',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition: 1s linear color');
|
|
|
|
var options = {
|
|
event: 'enter',
|
|
structural: true,
|
|
delay: 5
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(element.css('transition-delay')).toEqual('5s');
|
|
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now() + 5000, elapsedTime: 1 });
|
|
|
|
expect(element.attr('style') || '').not.toContain('transition');
|
|
}));
|
|
|
|
it('should not apply an inline transition if only [from] styles and a duration are provided',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
duration: 3,
|
|
from: { background: 'blue' }
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
expect(animator.$$willAnimate).toBeFalsy();
|
|
}));
|
|
|
|
it('should apply a transition if [from] styles are provided with a class that is added',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
addClass: 'superb',
|
|
from: { background: 'blue' }
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
expect(isFunction(animator.start)).toBe(true);
|
|
}));
|
|
|
|
it('should apply an inline transition if only [from] styles, but classes are added or removed and a duration is provided',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var options = {
|
|
duration: 3,
|
|
addClass: 'sugar',
|
|
from: { background: 'yellow' }
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
expect(animator.$$willAnimate).toBeTruthy();
|
|
}));
|
|
|
|
it('should not apply an inline transition if no styles are provided',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
var emptyObject = {};
|
|
var options = {
|
|
duration: 3,
|
|
to: emptyObject,
|
|
from: emptyObject
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
expect(animator.$$willAnimate).toBeFalsy();
|
|
}));
|
|
|
|
it('should apply a transition duration if the existing transition duration\'s property value is not \'all\'',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'transition: 1s linear color');
|
|
|
|
var emptyObject = {};
|
|
var options = {
|
|
event: 'enter',
|
|
structural: true,
|
|
to: { background: 'blue' }
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
var style = element.attr('style');
|
|
expect(element.css('transition-duration')).toMatch('1s');
|
|
expect(element.css('transition-property')).toMatch('all');
|
|
expect(style).toContain('linear');
|
|
}));
|
|
|
|
it('should apply a transition duration and an animation duration if duration + styles options are provided for a matching keyframe animation',
|
|
inject(function($animateCss, $rootElement) {
|
|
|
|
ss.addPossiblyPrefixedRule('.ng-enter', 'animation:3.5s keyframe_animation;');
|
|
|
|
var emptyObject = {};
|
|
var options = {
|
|
event: 'enter',
|
|
structural: true,
|
|
duration: 10,
|
|
to: {
|
|
background: 'blue'
|
|
}
|
|
};
|
|
|
|
var animator = $animateCss(element, options);
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
|
|
expect(element.css('transition-duration')).toMatch('10s');
|
|
expect(getPossiblyPrefixedStyleValue(element, 'animation-duration')).toEqual('10s');
|
|
}));
|
|
});
|
|
|
|
describe('[easing]', function() {
|
|
|
|
var element;
|
|
beforeEach(inject(function($document, $rootElement) {
|
|
element = angular.element('<div></div>');
|
|
$rootElement.append(element);
|
|
angular.element($document[0].body).append($rootElement);
|
|
}));
|
|
|
|
it('should apply easing to a transition animation if it exists', inject(function($animateCss) {
|
|
ss.addPossiblyPrefixedRule('.red', 'transition:1s linear all;');
|
|
var easing = 'ease-out';
|
|
var animator = $animateCss(element, { addClass: 'red', easing: easing });
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
var style = element.attr('style');
|
|
expect(style).toContain('ease-out');
|
|
}));
|
|
|
|
it('should not apply easing to transitions nor keyframes on an element animation if nothing is detected',
|
|
inject(function($animateCss) {
|
|
|
|
ss.addRule('.red', ';');
|
|
var easing = 'ease-out';
|
|
var animator = $animateCss(element, { addClass: 'red', easing: easing });
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element.attr('style')).toBeFalsy();
|
|
}));
|
|
|
|
it('should apply easing to both keyframes and transition animations if detected',
|
|
inject(function($animateCss) {
|
|
|
|
ss.addPossiblyPrefixedRule('.red', 'transition: 1s linear all;');
|
|
ss.addPossiblyPrefixedRule('.blue', 'animation: 1s my_keyframe;');
|
|
var easing = 'ease-out';
|
|
var animator = $animateCss(element, { addClass: 'red blue', easing: easing });
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
var style = element.attr('style');
|
|
expect(style).toMatch(/animation(?:-timing-function)?:\s*ease-out/);
|
|
expect(style).toMatch(/transition(?:-timing-function)?:\s*ease-out/);
|
|
}));
|
|
});
|
|
|
|
describe('[cleanupStyles]', function() {
|
|
it('should cleanup [from] and [to] styles that have been applied for the animation when true',
|
|
inject(function($animateCss) {
|
|
|
|
var runner = $animateCss(element, {
|
|
duration: 1,
|
|
from: { background: 'gold' },
|
|
to: { color: 'brown' },
|
|
cleanupStyles: true
|
|
}).start();
|
|
|
|
assertStyleIsPresent(element, 'background', true);
|
|
assertStyleIsPresent(element, 'color', false);
|
|
|
|
triggerAnimationStartFrame();
|
|
|
|
assertStyleIsPresent(element, 'background', true);
|
|
assertStyleIsPresent(element, 'color', true);
|
|
|
|
runner.end();
|
|
|
|
assertStyleIsPresent(element, 'background', false);
|
|
assertStyleIsPresent(element, 'color', false);
|
|
|
|
function assertStyleIsPresent(element, style, bool) {
|
|
expect(element[0].style[style])[bool ? 'toBeTruthy' : 'toBeFalsy']();
|
|
}
|
|
}));
|
|
|
|
it('should restore existing overidden styles already present on the element when true',
|
|
inject(function($animateCss) {
|
|
|
|
element.css('height', '100px');
|
|
element.css('width', '111px');
|
|
|
|
var runner = $animateCss(element, {
|
|
duration: 1,
|
|
from: { height: '200px', 'font-size':'66px' },
|
|
to: { height: '300px', 'font-size': '99px', width: '222px' },
|
|
cleanupStyles: true
|
|
}).start();
|
|
|
|
assertStyle(element, 'height', '200px');
|
|
assertStyle(element, 'font-size', '66px');
|
|
assertStyle(element, 'width', '111px');
|
|
|
|
triggerAnimationStartFrame();
|
|
|
|
assertStyle(element, 'height', '300px');
|
|
assertStyle(element, 'width', '222px');
|
|
assertStyle(element, 'font-size', '99px');
|
|
|
|
runner.end();
|
|
|
|
assertStyle(element, 'width', '111px');
|
|
assertStyle(element, 'height', '100px');
|
|
|
|
expect(element[0].style.getPropertyValue('font-size')).not.toBe('66px');
|
|
|
|
function assertStyle(element, prop, value) {
|
|
expect(element[0].style.getPropertyValue(prop)).toBe(value);
|
|
}
|
|
}));
|
|
});
|
|
|
|
it('should round up long elapsedTime values to close off a CSS3 animation',
|
|
inject(function($animateCss) {
|
|
|
|
ss.addPossiblyPrefixedRule('.millisecond-transition.ng-leave', 'transition:510ms linear all;');
|
|
|
|
element.addClass('millisecond-transition');
|
|
var animator = $animateCss(element, {
|
|
event: 'leave',
|
|
structural: true
|
|
});
|
|
|
|
animator.start();
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element).toHaveClass('ng-leave-active');
|
|
|
|
browserTrigger(element, 'transitionend',
|
|
{ timeStamp: Date.now() + 1000, elapsedTime: 0.50999999991 });
|
|
|
|
expect(element).not.toHaveClass('ng-leave-active');
|
|
}));
|
|
});
|
|
|
|
describe('SVG', function() {
|
|
it('should properly apply transitions on an SVG element',
|
|
inject(function($animateCss, $rootScope, $compile, $document, $rootElement) {
|
|
|
|
var element = $compile('<svg width="500" height="500">' +
|
|
'<circle cx="15" cy="5" r="100" fill="orange" />' +
|
|
'</svg>')($rootScope);
|
|
|
|
angular.element($document[0].body).append($rootElement);
|
|
$rootElement.append(element);
|
|
|
|
$animateCss(element, {
|
|
event: 'enter',
|
|
structural: true,
|
|
duration: 10
|
|
}).start();
|
|
|
|
triggerAnimationStartFrame();
|
|
|
|
expect(element).toHaveClass('ng-enter');
|
|
expect(element).toHaveClass('ng-enter-active');
|
|
|
|
browserTrigger(element, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 10 });
|
|
|
|
expect(element).not.toHaveClass('ng-enter');
|
|
expect(element).not.toHaveClass('ng-enter-active');
|
|
}));
|
|
|
|
it('should properly remove classes from SVG elements', inject(function($animateCss) {
|
|
var element = angular.element('<svg width="500" height="500">' +
|
|
'<rect class="class-of-doom"></rect>' +
|
|
'</svg>');
|
|
var child = element.find('rect');
|
|
|
|
var animator = $animateCss(child, {
|
|
removeClass: 'class-of-doom',
|
|
duration: 0
|
|
});
|
|
animator.start();
|
|
|
|
var className = child[0].getAttribute('class');
|
|
expect(className).toBe('');
|
|
}));
|
|
});
|
|
});
|
|
});
|