'use strict';

describe("ngAnimate $animateCss", function() {

  beforeEach(module('ngAnimate'));

  function assertAnimationRunning(element, not) {
    var className = element.attr('class');
    var regex = /\b\w+-active\b/;
    not ? expect(className).toMatch(regex)
        : expect(className).not.toMatch(regex);
  }

  var fakeStyle = {
    color: 'blue'
  };

  var ss, prefix, triggerAnimationStartFrame;
  beforeEach(module(function() {
    return function($document, $window, $sniffer, $$rAF) {
      prefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
      ss = createMockStyleSheet($document, $window);
      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, $$body) {

    var animator;
    var element = jqLite('<div></div>');
    $rootElement.append(element);
    $$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 silently quit the animation and not throw when an element has no parent during preparation",
      inject(function($animateCss, $$rAF, $rootScope, $document, $rootElement) {

      var element = jqLite('<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 = jqLite('<div></div>');
      jqLite($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, $$body, $rootElement) {

        $$body.append($rootElement);

        var count = 0;
        var runners = [];
        function makeRequest() {
          var element = jqLite('<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, $$body, $rootElement) {
          $$body.append($rootElement);

          function makeRequest() {
            var element = jqLite('<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, $$body) {
        element = jqLite('<div></div>');
        $rootElement.append(element);
        $$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.addRule('.ng-enter', '-webkit-animation:1.5s keyframe_animation;' +
                                        'animation:1.5s keyframe_animation;');

        animator = $animateCss(element, {
          event: 'enter',
          structural: true
        });

        var runner = animator.start();
        triggerAnimationStartFrame();

        runner.pause();
        expect(element.css(prefix + '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.addRule('.ng-enter', '-webkit-animation:1.5s keyframe_animation;' +
                                        'animation:1.5s keyframe_animation;');

        animator = $animateCss(element, {
          event: 'enter',
          structural: true
        });

        var runner = animator.start();
        triggerAnimationStartFrame();

        runner.pause();
        expect(element.css(prefix + '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');
        }

        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 });
        }

        beforeEach(inject(function($rootElement, $$body) {
          element = jqLite('<div></div>');
          $rootElement.append(element);
          $$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, $$rAF, $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);
            $$rAF.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, $$rAF, $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.addRule('.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.addRule('.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.addRule('.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 use the highest keyframe duration value detected in the CSS class", inject(function($animateCss) {
          ss.addRule('.ng-enter', 'animation:animation 1s, animation 2s, animation 3s;' +
                          '-webkit-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.addRule('.ng-enter', 'animation:animation 1s 2s, animation 1s 10s, animation 1s 1000ms;' +
                          '-webkit-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.addRule('.ng-enter',
                    'animation:animation 1s 2s 3, animation 1s 10s 2, animation 1s 1000ms infinite;' +
            '-webkit-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.addRule('.ng-enter', 'transition:1s linear all;' +
                                  'transition-duration:10s, 15s, 20s;' +
                                  'animation:animation 1s, animation 2s, animation 3s 0s 7;' +
                          '-webkit-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.addRule('.ng-enter', 'transition:1s linear all;' +
                                  'transition-delay:10s, 15s, 20s;' +
                                  'animation:animation 1s 2s, animation 1s 16s, animation 1s 19s;' +
                          '-webkit-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, $$body, $rootElement, $timeout) {

          $$body.append($rootElement);

          ss.addRule('.ng-enter-stagger', 'transition-delay:0.2s');
          ss.addRule('.ng-enter', 'transition:2s linear all');

          var elements = [];
          var i;
          var elm;

          for (i = 0; i < 5; i++) {
            elm = jqLite('<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, $$body, $rootElement, $timeout) {

          $$body.append($rootElement);

          ss.addRule('.red-add-stagger,' +
                     '.blue-remove-stagger,' +
                     '.green-add-stagger', 'transition-delay:0.2s');

          ss.addRule('.red-add,' +
                     '.blue-remove,' +
                     '.green-add', 'transition:2s linear all');

          var elements = [];
          var i;
          var elm;

          for (i = 0; i < 5; i++) {
            elm = jqLite('<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, $$body, $rootElement) {

          $$body.append($rootElement);

          ss.addRule('.ng-enter-stagger', 'transition-delay:0.2s');
          ss.addRule('.ng-enter', 'transition:2s linear all;');

          var element;
          var i;
          var elms = [];

          for (i = 0; i < 5; i++) {
            element = jqLite('<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, $$body, $rootElement) {

          $$body.append($rootElement);

          ss.addRule('.ng-enter-stagger', prefix + 'animation-delay:0.2s');
          ss.addRule('.ng-enter', prefix + 'animation:my_animation 2s;');

          var i, element, elements = [];
          for (i = 0; i < 5; i++) {
            element = jqLite('<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(element.css(prefix + '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, $$body, $rootElement) {

          $$body.append($rootElement);

          ss.addRule('.transition-animation', 'transition:2s 5s linear all;');

          for (var i = 0; i < 5; i++) {
            var element = jqLite('<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, $$body, $rootElement) {

          $$body.append($rootElement);

          ss.addRule('.transition-animation', 'transition:2s 5s linear all;');
          ss.addRule('.transition-animation.ng-enter-stagger',
            'transition-duration:0s; transition-delay:0.2s;');

          var element, i, elms = [];
          for (i = 0; i < 5; i++) {
            element = jqLite('<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, $$body, $rootElement) {

          $$body.append($rootElement);

          ss.addRule('.ng-enter-stagger', prefix + 'animation-delay:0.2s');
          ss.addRule('.transition-animation', 'transition:2s 5s linear all;');

          for (var i = 0; i < 5; i++) {
            var element = jqLite('<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, $$body, $rootElement) {

          $$body.append($rootElement);

          ss.addRule('.ng-enter-stagger', 'transition-delay:0.2s');
          ss.addRule('.transition-animation', prefix + 'animation:2s 5s my_animation;');

          for (var i = 0; i < 5; i++) {
            var elm = jqLite('<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, $$body, $rootElement, $timeout, $browser) {

          $$body.append($rootElement);

          ss.addRule('.ng-enter-stagger', 'transition-delay:0.5s;' +
                                 prefix + 'animation-delay:1s');

          ss.addRule('.ng-enter', 'transition:10s linear all;' +
                         prefix + 'animation:my_animation 20s');

          var i, elm, elements = [];
          for (i = 0; i < 5; i++) {
            elm = jqLite('<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, $$body, $rootElement, $timeout, $browser) {

          $$body.append($rootElement);

          ss.addRule('.ng-enter-stagger', 'transition-delay:1s;');
          ss.addRule('.ng-enter', 'transition:10s linear all;');

          var elm, i, elms = [];
          for (i = 0; i < 5; i++) {
            elm = jqLite('<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, $$body, $rootElement, $timeout, $browser) {

          $$body.append($rootElement);

          ss.addRule('.ng-enter-stagger', 'transition-delay:1s;');
          ss.addRule('.ng-enter', 'transition:10s linear all; transition-delay:50s;');

          var elm, i, elms = [];
          for (i = 0; i < 5; i++) {
            elm = jqLite('<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, $$body, $rootElement, $timeout) {

          $$body.append($rootElement);
          ss.addRule('.ng-enter', 'transition:2s linear all');

          var elm, i, elements = [];
          for (i = 0; i < 5; i++) {
            elm = jqLite('<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, $$body, $rootElement, $timeout) {

          $$body.append($rootElement);

          var element = jqLite('<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, $$body, $rootElement, $timeout) {

          ss.addRule('.ng-enter', 'transition:10s linear all;');

          var element = jqLite('<div></div>');
          $rootElement.append(element);
          $$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, $$body, $rootElement, $timeout) {

          ss.addRule('.ng-enter', 'transition:10s linear all; transition-delay:30s;');

          var element = jqLite('<div></div>');
          $rootElement.append(element);
          $$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, $$body, $rootElement, $timeout) {

          ss.addRule('.ng-enter', 'transition:10s linear all;');

          var element = jqLite('<div></div>');
          $rootElement.append(element);
          $$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);
          expect(passed).toBe(true);
        }));

        it("should not resolve/reject after passing if the animation completed successfully",
          inject(function($animateCss, $$body, $rootElement, $timeout, $rootScope) {

          ss.addRule('.ng-enter', 'transition:10s linear all;');

          var element = jqLite('<div></div>');
          $rootElement.append(element);
          $$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 });

          $rootScope.$digest();

          expect(passed).toBe(true);
          expect(failed).not.toBe(true);

          $timeout.flush(15000);

          expect(passed).toBe(true);
          expect(failed).not.toBe(true);
        }));
      });

      describe("getComputedStyle", function() {
        var count;
        var acceptableTimingsData = {
          transitionDuration: "10s"
        };

        beforeEach(module(function($provide) {
          count = {};
          $provide.value('$window', extend({}, window, {
            document: jqLite(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($$body, $rootElement) {
            $$body.append($rootElement);
          };
        }));

        it("should cache frequent calls to getComputedStyle before the next animation frame kicks in",
          inject(function($animateCss, $document, $rootElement, $$rAF) {

          var i, elm, animator;
          for (i = 0; i < 5; i++) {
            elm = jqLite('<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 = jqLite('<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 = jqLite('<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 = jqLite('<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 = jqLite('<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 = jqLite('<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 = jqLite('<div></div>');
            $rootElement.append(elm);
            animator = $animateCss(elm, { event: 'enter', structural: true });
            animator.start();
          }

          triggerAnimationStartFrame();
          expect(count.stagger).toBe(2);
        }));
      });
    });

    it('should avoid applying the same cache to an element a follow-up animation is run on the same element',
      inject(function($animateCss, $rootElement, $$body) {

      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 = jqLite('<div></div>');
      $rootElement.append(element);
      $$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 apply a custom temporary class when a non-structural animation is used',
      inject(function($animateCss, $rootElement, $$body) {

      var element = jqLite('<div></div>');
      $rootElement.append(element);
      $$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, $$body) {
          var element = jqLite('<div></div>');
          $rootElement.append(element);
          $$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, $$body) {
          var element = jqLite('<div></div>');
          $rootElement.append(element);
          $$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, $$body) {
          var element = jqLite('<div></div>');
          $rootElement.append(element);
          $$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 = jqLite('<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, $$body) {
          var element = jqLite('<div></div>');
          $rootElement.append(element);
          $$body.append($rootElement);

          ss.addRule('.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, $$body, $window) {
          var element = jqLite('<div></div>');
          $rootElement.append(element);
          $$body.append($rootElement);

          ss.addRule('.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($window, 'blockTransitions').andCallThrough();

          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, $$body) {
          var element = jqLite('<div></div>');
          $rootElement.append(element);
          $$body.append($rootElement);

          ss.addRule('.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, $$body) {

        var element = jqLite('<div></div>');
        $rootElement.append(element);
        $$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');
      }));
    });

    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 = jqLite('<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 = jqLite('<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, $$body) {
          var element = jqLite('<div></div>');
          $rootElement.append(element);
          $$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, $$body) {

          var element = jqLite('<div></div>');

          if (event == 'add') {
            ss.addRule('.natural-class', 'transition:1s linear all;');
          } else {
            ss.addRule('.natural-class', 'transition:0s linear none;');
            ss.addRule('.base-class', 'transition:1s linear none;');

            element.addClass('base-class');
            element.addClass('natural-class');
          }

          $rootElement.append(element);
          $$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, $$body) {

          ss.addRule('.blue.ng-' + event, 'transition:2s linear all;');

          var element = jqLite('<div class="red"></div>');
          $rootElement.append(element);
          $$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(inject(function($rootElement, $$body) {
        $$body.append($rootElement);

        element = jqLite('<div></div>');
        $rootElement.append(element);
      }));

      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 = jqLite('<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.addRule('.ng-enter', '-webkit-animation:1.5s keyframe_animation;' +
                                          'animation:1.5s keyframe_animation;');

          var options = {
            duration: 5,
            event: 'enter',
            structural: true
          };
          var animator = $animateCss(element, options);
          animator.start();
          triggerAnimationStartFrame();


          expect(element.css(prefix + '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.addRule('.ng-enter', '-webkit-animation:1.5s keyframe_animation;' +
                                          '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.addRule('.ng-enter', '-webkit-animation:1.5s keyframe_animation;' +
                                          'animation:1.5s keyframe_animation;');

          var options = {
            delay: 5,
            event: 'enter',
            structural: true
          };
          var animator = $animateCss(element, options);
          animator.start();
          triggerAnimationStartFrame();


          expect(element.css(prefix + '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.addRule('.ng-enter', '-webkit-transition:1s linear all;' +
                                          '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.addRule('.ng-enter', '-webkit-animation:3s keyframe_animation;' +
                                          '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 = jqLite('<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 = jqLite('<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.addRule('.ng-enter', '-webkit-transition:1s linear all;' +
                                          'transition:1s linear all;' +
                                  '-webkit-transition-delay:10s;' +
                                          'transition-delay:10s;');

          var element = jqLite('<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.addRule('.ng-enter', '-webkit-transition:1s linear all;' +
                                          'transition:1s linear all;' +
                                  '-webkit-transition-delay:10s;' +
                                          'transition-delay:10s;');

          var element = jqLite('<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.addRule('.ng-enter', '-webkit-animation:1.5s keyframe_animation;' +
                                          'animation:1.5s keyframe_animation;');

          var options = {
            delay: 400,
            event: 'enter',
            structural: true
          };
          var animator = $animateCss(element, options);
          animator.start();
          triggerAnimationStartFrame();


          expect(element.css(prefix + '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.addRule('.ng-enter', '-webkit-animation:3s keyframe_animation;' +
                                          '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(element.css(prefix + 'animation-delay')).toEqual('10s');
          expect(element.css('transition-delay')).toEqual('10s');
        }));

        it("should apply blocking before the animation starts, but then apply the detected delay when options.delay is true",
          inject(function($animateCss, $rootElement) {

          ss.addRule('.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.css('transition-delay')).toEqual('1s');
        }));

        they("should consider a negative value when delay:true is used with a $prop animation", {
          'transition': function() {
            return {
              prop: 'transition-delay',
              css: 'transition:2s linear all; transition-delay: -1s'
            };
          },
          'keyframe': function(prefix) {
            return {
              prop: prefix + 'animation-delay',
              css: prefix + 'animation:2s keyframe_animation; ' + prefix + 'animation-delay: -1s;'
            };
          }
        }, function(testDetailsFactory) {
          inject(function($animateCss, $rootElement) {
            var testDetails = testDetailsFactory(prefix);

            ss.addRule('.ng-enter', testDetails.css);
            var options = {
              delay: true,
              event: 'enter',
              structural: true
            };

            var animator = $animateCss(element, options);

            animator.start();
            triggerAnimationStartFrame();

            expect(element.css(testDetails.prop)).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(prefix) {
            return {
              prop: prefix + 'animation-delay',
              css: prefix + 'animation:2s keyframe_animation'
            };
          }
        }, function(testDetailsFactory) {
          inject(function($animateCss, $rootElement) {
            var testDetails = testDetailsFactory(prefix);

            ss.addRule('.ng-enter', testDetails.css);
            var options = {
              delay: -2,
              event: 'enter',
              structural: true
            };

            var animator = $animateCss(element, options);

            animator.start();
            triggerAnimationStartFrame();

            expect(element.css(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(prefix) {
            return {
              event: 'animationend',
              css: prefix + 'animation:5s keyframe_animation; ' + prefix + 'animation-delay: -2s;'
            };
          }
        }, function(testDetailsFactory) {
          inject(function($animateCss, $rootElement) {
            var testDetails = testDetailsFactory(prefix);
            var event = testDetails.event;

            ss.addRule('.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(element.css(prefix + 'transition-delay')).not.toEqual('4s');
          expect(element.css(prefix + 'transition-duration')).not.toEqual('6s');

          options.to = { color: 'brown' };
          $animateCss(element, options).start();
          triggerAnimationStartFrame();

          expect(element.css(prefix + 'transition-delay')).toEqual('4s');
          expect(element.css(prefix + '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(element.css(prefix + 'animation-delay')).toEqual('50s');
          expect(element.css(prefix + 'animation-duration')).toEqual('5.5s');
          expect(element.css(prefix + '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(element.css(prefix + 'animation-delay')).toEqual('10s');
          expect(element.css(prefix + 'animation-duration')).toEqual('5.5s');
          expect(element.css(prefix + '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.addRule('.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.addRule('.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.addRule('.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.addRule('.ng-enter', '-webkit-animation:3.5s keyframe_animation;' +
                                          '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(element.css(prefix + 'animation-duration')).toEqual('10s');
        }));
      });

      describe("[easing]", function() {

        var element;
        beforeEach(inject(function($$body, $rootElement) {
          element = jqLite('<div></div>');
          $rootElement.append(element);
          $$body.append($rootElement);
        }));

        it("should apply easing to a transition animation if it exists", inject(function($animateCss) {
          ss.addRule('.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.addRule('.red', 'transition: 1s linear all;');
          ss.addRule('.blue', prefix + 'animation:my_keyframe 1s;');
          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/);
        }));
      });

      it('should round up long elapsedTime values to close off a CSS3 animation',
        inject(function($animateCss) {

        ss.addRule('.millisecond-transition.ng-leave', '-webkit-transition:510ms linear all;' +
                                                       '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, $$body, $rootElement) {

        var element = $compile('<svg width="500" height="500">' +
                                 '<circle cx="15" cy="5" r="100" fill="orange" />' +
                               '</svg>')($rootScope);

        $$body.append($rootElement);
        $rootElement.append(element);

        $animateCss(element, {
          event: 'enter',
          structural: true,
          duration: 10
        }).start();

        triggerAnimationStartFrame();

        expect(jqLiteHasClass(element[0], 'ng-enter')).toBe(true);
        expect(jqLiteHasClass(element[0], 'ng-enter-active')).toBe(true);

        browserTrigger(element, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 10 });

        expect(jqLiteHasClass(element[0], 'ng-enter')).toBe(false);
        expect(jqLiteHasClass(element[0], 'ng-enter-active')).toBe(false);
      }));

      it('should properly remove classes from SVG elements', inject(function($animateCss) {
        var element = jqLite('<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('');
      }));
    });
  });
});
