'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('
'); $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(''); $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(''); 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(''); 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(''); $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(''); $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(''); $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(''); $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(''); 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(''); 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(''); 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(''); 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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); 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(''); 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(''); 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(''); 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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); 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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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(''); $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('