describe('carousel', function() {
  beforeEach(module('ui.bootstrap.carousel'));
  beforeEach(module('ngAnimateMock'));
  beforeEach(module('uib/template/carousel/carousel.html', 'uib/template/carousel/slide.html'));

  var $rootScope, $compile, $controller, $interval, $templateCache, $timeout, $animate;
  beforeEach(inject(function(_$rootScope_, _$compile_, _$controller_, _$interval_, _$templateCache_, _$timeout_, _$animate_) {
    $rootScope = _$rootScope_;
    $compile = _$compile_;
    $controller = _$controller_;
    $interval = _$interval_;
    $templateCache = _$templateCache_;
    $timeout = _$timeout_;
    $animate = _$animate_;
  }));

  describe('basics', function() {
    var elm, scope;
    beforeEach(function() {
      scope = $rootScope.$new();
      scope.slides = [
        {content: 'one', index: 0},
        {content: 'two', index: 1},
        {content: 'three', index: 2}
      ];
      elm = $compile(
        '<div uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' +
          '<div uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
            '{{slide.content}}' +
          '</div>' +
        '</div>'
      )(scope);
      scope.interval = 5000;
      scope.nopause = undefined;
      scope.$apply();
    });

    function testSlideActive(slideIndex) {
      for (var i = 0; i < scope.slides.length; i++) {
        if (i === slideIndex) {
          expect(scope.active).toBe(scope.slides[i].index);
        } else {
          expect(scope.active).not.toBe(scope.slides[i].index);
        }
      }
    }

    it('should allow overriding of the carousel template', function() {
      $templateCache.put('foo/bar.html', '<div>foo</div>');

      elm = $compile('<div uib-carousel template-url="foo/bar.html"></div>')(scope);
      $rootScope.$digest();

      expect(elm.html()).toBe('<div>foo</div>');
    });

    it('should allow overriding of the slide template', function() {
      $templateCache.put('foo/bar.html', '<div class="slide">bar</div>');

      elm = $compile(
        '<div uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
          '<div uib-slide template-url="foo/bar.html"></div>' +
        '</div>'
      )(scope);
      $rootScope.$digest();

      var slide = elm.find('.slide');
      expect(slide.html()).toBe('bar');
    });

    it('should be able to select a slide via model changes', function() {
      testSlideActive(0);
      scope.$apply('active=1');
      testSlideActive(1);
    });

    it('should create clickable prev nav button', function() {
      var navPrev = elm.find('a.carousel-control-prev');
      var navNext = elm.find('a.carousel-control-next');

      expect(navPrev.length).toBe(1);
      expect(navNext.length).toBe(1);
    });

    it('should display clickable slide indicators', function () {
      var indicators = elm.find('ol.carousel-indicators > li');
      expect(indicators.length).toBe(3);
    });

    it('should stop cycling slides forward when noWrap is truthy', function () {
      elm = $compile(
        '<div uib-carousel active="active" interval="interval" no-wrap="noWrap">' +
          '<div uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
            '{{slide.content}}' +
          '</div>' +
        '</div>'
      )(scope);

      scope.noWrap = true;
      scope.$apply();

      var $scope = elm.isolateScope();
      spyOn($scope, 'pause');

      scope.active = $scope.slides.length - 1;
      scope.$apply();
      testSlideActive($scope.slides.length - 1);
      $scope.next();
      testSlideActive($scope.slides.length - 1);
      expect($scope.pause).toHaveBeenCalled();
    });

    it('should stop cycling slides backward when noWrap is truthy', function () {
      elm = $compile(
        '<div uib-carousel active="active" interval="interval" no-wrap="noWrap">' +
          '<div uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
            '{{slide.content}}' +
          '</div>' +
        '</div>'
      )(scope);

      scope.noWrap = true;
      scope.$apply();

      var $scope = elm.isolateScope();
      spyOn($scope, 'pause');

      testSlideActive(0);
      $scope.prev();
      testSlideActive(0);
      expect($scope.pause).toHaveBeenCalled();
    });

    it('should hide navigation when only one slide', function () {
      scope.slides = [{active:false,content:'one'}];
      scope.$apply();
      elm = $compile(
        '<div uib-carousel active="active" interval="interval" no-transition="true">' +
          '<div uib-slide ng-repeat="slide in slides" index="$index">' +
            '{{slide.content}}' +
          '</div>' +
        '</div>'
      )(scope);
      var indicators = elm.find('ol.carousel-indicators > li');
      expect(indicators.length).toBe(0);

      var navNext = elm.find('a.carousel-control-prev');
      expect(navNext.length).toBe(0);

      var navPrev = elm.find('a.carousel-control-next');
      expect(navPrev.length).toBe(0);
    });

    it('should disable prev button when slide index is 0 and noWrap is truthy', function() {
      scope.$apply();

      var $scope = elm.isolateScope();
      $scope.noWrap = function() {return true;};

      $scope.isPrevDisabled();
      scope.$apply();

      var navPrev = elm.find('a.carousel-control-prev');
      expect(navPrev.hasClass('disabled')).toBe(true);
    });


    it('should show navigation when there are 3 slides', function () {
      var indicators = elm.find('ol.carousel-indicators > li');
      expect(indicators.length).not.toBe(0);

      var navNext = elm.find('a.carousel-control-next');
      expect(navNext.length).not.toBe(0);

      var navPrev = elm.find('a.carousel-control-prev');
      expect(navPrev.length).not.toBe(0);
    });

    it('should go to next when clicking next button', function() {
      var navNext = elm.find('a.carousel-control-next');
      testSlideActive(0);
      navNext.click();
      testSlideActive(1);
      navNext.click();
      testSlideActive(2);
      navNext.click();
      testSlideActive(0);
    });

    it('should go to prev when clicking prev button', function() {
      var navPrev = elm.find('a.carousel-control-prev');
      testSlideActive(0);
      navPrev.click();
      testSlideActive(2);
      navPrev.click();
      testSlideActive(1);
      navPrev.click();
      testSlideActive(0);
    });

    it('should select a slide when clicking on slide indicators', function () {
      var indicators = elm.find('ol.carousel-indicators > li');
      indicators.eq(1).click();
      testSlideActive(1);
    });

    it('shouldnt go forward if interval is NaN or negative or has no slides', function() {
      testSlideActive(0);
      var previousInterval = scope.interval;
      scope.$apply('interval = -1');
      $interval.flush(previousInterval);
      testSlideActive(0);
      scope.$apply('interval = 1000');
      $interval.flush(1000);
      testSlideActive(1);
      scope.$apply('interval = false');
      $interval.flush(1000);
      testSlideActive(1);
      scope.$apply('interval = 1000');
      $interval.flush(1000);
      testSlideActive(2);
      scope.$apply('slides = []');
      $interval.flush(1000);
      testSlideActive(2);
    });

    it('should bind the content to slides', function() {
      var contents = elm.find('div.carousel-item [ng-transclude]');

      expect(contents.length).toBe(3);
      expect(contents.eq(0).text()).toBe('one');
      expect(contents.eq(1).text()).toBe('two');
      expect(contents.eq(2).text()).toBe('three');

      scope.$apply(function() {
        scope.slides[0].content = 'what';
        scope.slides[1].content = 'no';
        scope.slides[2].content = 'maybe';
      });

      expect(contents.eq(0).text()).toBe('what');
      expect(contents.eq(1).text()).toBe('no');
      expect(contents.eq(2).text()).toBe('maybe');
    });

    it('should be playing by default and cycle through slides', function() {
      testSlideActive(0);
      $interval.flush(scope.interval);
      testSlideActive(1);
      $interval.flush(scope.interval);
      testSlideActive(2);
      $interval.flush(scope.interval);
      testSlideActive(0);
    });

    it('should pause and play on mouseover', function() {
      testSlideActive(0);
      $interval.flush(scope.interval);
      testSlideActive(1);
      elm.trigger('mouseenter');
      testSlideActive(1);
      $interval.flush(scope.interval);
      testSlideActive(1);
      elm.trigger('mouseleave');
      $interval.flush(scope.interval);
      testSlideActive(2);
    });

    it('should not pause on mouseover if noPause', function() {
      scope.$apply('nopause = true');
      testSlideActive(0);
      elm.trigger('mouseenter');
      $interval.flush(scope.interval);
      testSlideActive(1);
      elm.trigger('mouseleave');
      $interval.flush(scope.interval);
      testSlideActive(2);
    });

    it('should remove slide from dom and change active slide', function() {
      scope.$apply('active = 2');
      testSlideActive(2);
      scope.$apply('slides.splice(2,1)');
      $timeout.flush(0);
      expect(elm.find('div.carousel-item').length).toBe(2);
      testSlideActive(1);
      $interval.flush(scope.interval);
      testSlideActive(0);
      scope.$apply('slides.splice(1,1)');
      $timeout.flush(0);
      expect(elm.find('div.carousel-item').length).toBe(1);
      testSlideActive(0);
    });

    it('should change dom when you reassign ng-repeat slides array', function() {
      scope.slides = [
        {content:'new1', index: 4},
        {content:'new2', index: 5},
        {content:'new3', index: 6}
      ];
      scope.$apply();
      var contents = elm.find('div.carousel-item [ng-transclude]');
      expect(contents.length).toBe(3);
      expect(contents.eq(0).text()).toBe('new1');
      expect(contents.eq(1).text()).toBe('new2');
      expect(contents.eq(2).text()).toBe('new3');
    });

    it('should not change if next is clicked while transitioning', function() {
      var carouselScope = elm.children().scope();
      var next = elm.find('a.carousel-control-next');

      testSlideActive(0);
      carouselScope.$currentTransition = true;
      next.click();

      testSlideActive(0);

      carouselScope.$currentTransition = null;
      next.click();
      testSlideActive(1);
    });

    it('should buffer the slides if transition is clicked and only transition to the last requested', function() {
      var carouselScope = elm.children().scope();

      testSlideActive(0);
      carouselScope.$currentTransition = null;
      carouselScope.select(carouselScope.slides[1]);
      $animate.flush();

      testSlideActive(1);

      carouselScope.$currentTransition = true;
      carouselScope.select(carouselScope.slides[2]);
      scope.$apply();

      testSlideActive(1);

      carouselScope.select(carouselScope.slides[0]);
      scope.$apply();

      testSlideActive(1);

      carouselScope.$currentTransition = null;
      $interval.flush(scope.interval);
      $animate.flush();

      testSlideActive(2);

      $interval.flush(scope.interval);
      $animate.flush();

      testSlideActive(0);
    });

    it('issue 1414 - should not continue running timers after scope is destroyed', function() {
      testSlideActive(0);
      $interval.flush(scope.interval);
      testSlideActive(1);
      $interval.flush(scope.interval);
      testSlideActive(2);
      $interval.flush(scope.interval);
      testSlideActive(0);
      spyOn($interval, 'cancel').and.callThrough();
      scope.$destroy();
      expect($interval.cancel).toHaveBeenCalled();
    });

    it('issue 4390 - should reset the currentTransition if there are no slides', function() {
      var carouselScope = elm.children().scope();
      var next = elm.find('a.carousel-control-next');
      scope.slides = [
        {content:'new1', index: 1},
        {content:'new2', index: 2},
        {content:'new3', index: 3}
      ];
      scope.$apply();

      testSlideActive(0);
      carouselScope.$currentTransition = true;

      scope.slides = [];
      scope.$apply();

      expect(carouselScope.$currentTransition).toBe(null);
    });
  });

  describe('slide order', function() {
    var elm, scope;
    beforeEach(function() {
      scope = $rootScope.$new();
      scope.slides = [
        {content: 'one', id: 3},
        {content: 'two', id: 1},
        {content: 'three', id: 2}
      ];
      elm = $compile(
        '<div uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' +
          '<div uib-slide ng-repeat="slide in slides | orderBy: \'id\' track by slide.id" index="slide.id">' +
            '{{slide.content}}' +
          '</div>' +
        '</div>'
      )(scope);
      scope.$apply();
    });

    function testSlideActive(slideIndex) {
      for (var i = 0; i < scope.slides.length; i++) {
        if (i === slideIndex) {
          expect(scope.active).toBe(scope.slides[i].id);
        } else {
          expect(scope.active).not.toBe(scope.slides[i].id);
        }
      }
    }

    it('should change dom when the order of the slides changes', function() {
      scope.slides[0].id = 3;
      scope.slides[1].id = 2;
      scope.slides[2].id = 1;
      scope.$apply();
      var contents = elm.find('div.carousel-item [ng-transclude]');
      expect(contents.length).toBe(3);
      expect(contents.eq(0).text()).toBe('three');
      expect(contents.eq(1).text()).toBe('two');
      expect(contents.eq(2).text()).toBe('one');
    });

    it('should select next after order change', function() {
      testSlideActive(1);
      var next = elm.find('a.carousel-control-next');
      next.click();
      testSlideActive(2);
    });

    it('should select prev after order change', function() {
      testSlideActive(1);
      var prev = elm.find('a.carousel-control-prev');
      prev.click();
      testSlideActive(0);
    });

    it('should add slide in the specified position', function() {
      testSlideActive(1);
      scope.slides[2].id = 4;
      scope.slides.push({content:'four', id: 5});
      scope.$apply();
      var contents = elm.find('div.carousel-item [ng-transclude]');
      expect(contents.length).toBe(4);
      expect(contents.eq(0).text()).toBe('two');
      expect(contents.eq(1).text()).toBe('one');
      expect(contents.eq(2).text()).toBe('three');
      expect(contents.eq(3).text()).toBe('four');
    });

    it('should remove slide after order change', function() {
      testSlideActive(1);
      scope.slides.splice(1, 1);
      scope.$apply();
      var contents = elm.find('div.carousel-item [ng-transclude]');
      expect(contents.length).toBe(2);
      expect(contents.eq(0).text()).toBe('three');
      expect(contents.eq(1).text()).toBe('one');
    });
  });

  describe('controller', function() {
    var scope, ctrl;
    //create an array of slides and add to the scope
    var slides = [
      {'content': 1, index: 0},
      {'content': 2, index: 1},
      {'content': 3, index: 2},
      {'content': 4, index: 3}
    ];

    beforeEach(function() {
      scope = $rootScope.$new();
      scope.noWrap = angular.noop;
      ctrl = $controller('UibCarouselController', {$scope: scope, $element: angular.element('<div></div>')});
      for (var i = 0; i < slides.length; i++) {
        ctrl.addSlide(slides[i]);
      }
    });

    it('should set first slide to active = true and the rest to false', function() {
      angular.forEach(ctrl.slides, function(slide, i) {
        if (i !== 0) {
          expect(slide.slide.active).not.toBe(true);
        } else {
          expect(slide.slide.active).toBe(true);
        }
      });
    });

    it('should add a new slide and not change the active slide', function() {
      var newSlide = {active: false, index: 4};
      expect(ctrl.slides.length).toBe(4);
      ctrl.addSlide(newSlide);
      expect(ctrl.slides.length).toBe(5);
      expect(ctrl.slides[4].slide.active).toBe(false);
      expect(ctrl.slides[0].slide.active).toBe(true);
    });

    it('should remove slide and change active slide if needed', function() {
      expect(ctrl.slides.length).toBe(4);
      ctrl.removeSlide(ctrl.slides[0].slide);
      $timeout.flush(0);
      expect(ctrl.slides.length).toBe(3);
      expect(scope.active).toBe(1);
      ctrl.select(ctrl.slides[2]);
      ctrl.removeSlide(ctrl.slides[2].slide);
      $timeout.flush(0);
      expect(ctrl.slides.length).toBe(2);
      expect(scope.active).toBe(2);
      ctrl.removeSlide(ctrl.slides[0].slide);
      $timeout.flush(0);
      expect(ctrl.slides.length).toBe(1);
      expect(scope.active).toBe(1);
    });

    it('issue 1414 - should not continue running timers after scope is destroyed', function() {
      spyOn(scope, 'next');
      scope.interval = 2000;
      scope.$digest();

      $interval.flush(scope.interval);
      expect(scope.next.calls.count()).toBe(1);

      scope.$destroy();

      $interval.flush(scope.interval);
      expect(scope.next.calls.count()).toBe(1);
    });

    it('should be exposed in the template', inject(function($templateCache) {
      $templateCache.put('uib/template/carousel/carousel.html', '<div>{{carousel.text}}</div>');

      var scope = $rootScope.$new();
      var elm = $compile('<div uib-carousel interval="bar" no-transition="false" no-pause="true"></div>')(scope);
      $rootScope.$digest();

      var ctrl = elm.controller('uibCarousel');

      expect(ctrl).toBeDefined();

      ctrl.text = 'foo';
      $rootScope.$digest();

      expect(elm.html()).toBe('<div class="ng-binding">foo</div>');
    }));
  });

  it('should expose a custom model in the carousel slide', function() {
    var scope = $rootScope.$new();
    scope.slides = [
      {active:false,content:'one'},
      {active:false,content:'two'},
      {active:false,content:'three'}
    ];
    var elm = $compile(
      '<div uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' +
        '<div uib-slide ng-repeat="slide in slides" index="$index" actual="slide">' +
          '{{slide.content}}' +
        '</div>' +
      '</div>'
    )(scope);
    $rootScope.$digest();

    var ctrl = elm.controller('uibCarousel');

    expect(angular.equals(ctrl.slides.map(function(slide) {
      return slide.slide.actual;
    }), scope.slides)).toBe(true);
  });
});
