describe('carousel', function() {
  beforeEach(module('ui.bootstrap.carousel', function($compileProvider, $provide) {
    angular.forEach(['ngSwipeLeft', 'ngSwipeRight'], makeMock);
    function makeMock(name) {
      $provide.value(name + 'Directive', []); //remove existing directive if it exists
      $compileProvider.directive(name, function() {
        return function(scope, element, attr) {
          element.on(name, function() {
            scope.$apply(attr[name]);
          });
        };
      });
    }
  }));
  beforeEach(module('template/carousel/carousel.html', 'template/carousel/slide.html'));

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

  describe('basics', function() {
    var elm, scope;
    beforeEach(function() {
      scope = $rootScope.$new();
      scope.slides = [
        {active:false,content:'one'},
        {active:false,content:'two'},
        {active:false,content:'three'}
      ];
      elm = $compile(
        '<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
          '<uib-slide ng-repeat="slide in slides" active="slide.active">' +
            '{{slide.content}}' +
          '</uib-slide>' +
        '</uib-carousel>'
      )(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.slides[i].active).toBe(true);
        } else {
          expect(scope.slides[i].active).not.toBe(true);
        }
      }
    }

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

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

      expect(elm.html()).toBe('foo');
    });

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

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

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

    it('should set the selected slide to active = true', function() {
      expect(scope.slides[0].content).toBe('one');
      testSlideActive(0);
      scope.$apply('slides[1].active=true');
      testSlideActive(1);
    });

    it('should create clickable prev nav button', function() {
      var navPrev = elm.find('a.left');
      var navNext = elm.find('a.right');

      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(
          '<uib-carousel interval="interval" no-wrap="noWrap">' +
            '<uib-slide ng-repeat="slide in slides" active="slide.active">' +
              '{{slide.content}}' +
            '</uib-slide>' +
          '</uib-carousel>'
        )(scope);

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

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

      for (var i = 0; i < scope.slides.length - 1; ++i) {
        scope.next();
      }
      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(
          '<uib-carousel interval="interval" no-wrap="noWrap">' +
            '<uib-slide ng-repeat="slide in slides" active="slide.active">' +
              '{{slide.content}}' +
            '</uib-slide>' +
          '</uib-carousel>'
        )(scope);

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

      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(
          '<uib-carousel interval="interval" no-transition="true">' +
            '<uib-slide ng-repeat="slide in slides" active="slide.active">' +
              '{{slide.content}}' +
            '</uib-slide>' +
          '</uib-carousel>'
        )(scope);
      var indicators = elm.find('ol.carousel-indicators > li');
      expect(indicators.length).toBe(0);

      var navNext = elm.find('a.right');
      expect(navNext.length).toBe(0);

      var navPrev = elm.find('a.left');
      expect(navPrev.length).toBe(0);
    });

    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.right');
      expect(navNext.length).not.toBe(0);

      var navPrev = elm.find('a.left');
      expect(navPrev.length).not.toBe(0);
    });

    it('should go to next when clicking next button', function() {
      var navNext = elm.find('a.right');
      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.left');
      testSlideActive(0);
      navPrev.click();
      testSlideActive(2);
      navPrev.click();
      testSlideActive(1);
      navPrev.click();
      testSlideActive(0);
    });

    describe('swiping', function() {
      it('should go next on swipeLeft', function() {
        testSlideActive(0);
        elm.triggerHandler('ngSwipeLeft');
        testSlideActive(1);
      });

      it('should go prev on swipeRight', function() {
        testSlideActive(0);
        elm.triggerHandler('ngSwipeRight');
        testSlideActive(2);
      });
    });

    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.item');

      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('slides[2].active = true');
      testSlideActive(2);
      scope.$apply('slides.splice(0,1)');
      expect(elm.find('div.item').length).toBe(2);
      testSlideActive(1);
      $interval.flush(scope.interval);
      testSlideActive(0);
      scope.$apply('slides.splice(1,1)');
      expect(elm.find('div.item').length).toBe(1);
      testSlideActive(0);
    });

    it('should change dom when you reassign ng-repeat slides array', function() {
      scope.slides = [{content:'new1'},{content:'new2'},{content:'new3'}];
      scope.$apply();
      var contents = elm.find('div.item');
      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.right');

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

      testSlideActive(0);

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

    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.right');
      scope.slides = [{content:'new1'},{content:'new2'},{content:'new3'}];
      scope.$apply();

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

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

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

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

      it('should change dom when an order of the slides was changed', function() {
        testSlideActive(0);
        var contents = elm.find('div.item');
        expect(contents.length).toBe(3);
        expect(contents.eq(0).text()).toBe('two');
        expect(contents.eq(1).text()).toBe('three');
        expect(contents.eq(2).text()).toBe('one');
      });

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

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

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

      it('should remove slide after order change', function() {
        testSlideActive(0);
        scope.slides.splice(1, 1);
        scope.$apply();
        var contents = elm.find('div.item');
        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},{'content':2},{'content':3},{'content':4}];

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

    describe('addSlide', function() {
      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.active).not.toBe(true);
          } else {
            expect(slide.active).toBe(true);
          }
        });
      });

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

      it('should add a new slide and not change the active slide', function() {
        var newSlide = {active: false};
        expect(ctrl.slides.length).toBe(4);
        ctrl.addSlide(newSlide);
        expect(ctrl.slides.length).toBe(5);
        expect(ctrl.slides[4].active).toBe(false);
        expect(ctrl.slides[0].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]);
        expect(ctrl.slides.length).toBe(3);
        expect(ctrl.currentSlide).toBe(ctrl.slides[0]);
        ctrl.select(ctrl.slides[2]);
        ctrl.removeSlide(ctrl.slides[2]);
        expect(ctrl.slides.length).toBe(2);
        expect(ctrl.currentSlide).toBe(ctrl.slides[1]);
        ctrl.removeSlide(ctrl.slides[0]);
        expect(ctrl.slides.length).toBe(1);
        expect(ctrl.currentSlide).toBe(ctrl.slides[0]);
      });

      it('issue 1414 - should not continue running timers after scope is destroyed', function() {
        spyOn(scope, 'next').and.callThrough();
        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('template/carousel/carousel.html', '<div>{{carousel.text}}</div>');

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

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

      expect(ctrl).toBeDefined();

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

      expect(elm.html()).toBe('foo');
    }));
  });

  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(
      '<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
        '<uib-slide ng-repeat="slide in slides" active="slide.active" actual="slide">' +
          '{{slide.content}}' +
        '</uib-slide>' +
      '</uib-carousel>'
    )(scope);
    $rootScope.$digest();

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

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

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

  it('should suppress warning', function() {
    module(function($provide) {
      $provide.value('$carouselSuppressWarning', true);
    });

    inject(function($compile, $log, $rootScope) {
      spyOn($log, 'warn');

      var element = '<carousel interval="interval" no-transition="true" no-pause="nopause">' +
          '<slide ng-repeat="slide in slides" active="slide.active">' +
            '{{slide.content}}' +
          '</slide>' +
        '</carousel>';
      element = $compile(element)($rootScope);
      $rootScope.$digest();
      expect($log.warn.calls.count()).toBe(0);
    });
  });

  it('should give warning by default', inject(function($compile, $log, $rootScope) {
    spyOn($log, 'warn');

    var element = '<carousel interval="interval" no-transition="true" no-pause="nopause">' +
        '<slide ng-repeat="slide in slides" active="slide.active">' +
          '{{slide.content}}' +
        '</slide>' +
      '</carousel>';
    element = $compile(element)($rootScope);
    $rootScope.$digest();

    expect($log.warn.calls.count()).toBe(2);
    expect($log.warn.calls.argsFor(0)).toEqual(['CarouselController is now deprecated. Use UibCarouselController instead.']);
    expect($log.warn.calls.argsFor(1)).toEqual(['carousel is now deprecated. Use uib-carousel instead.']);
  }));
  
   it('should give warning by default for slider', inject(function($compile, $log, $rootScope) {
    spyOn($log, 'warn');

    var element = '<carousel interval="interval" no-transition="true" no-pause="nopause">' +
        '<slide></slide>' + 
      '</carousel>';
    element = $compile(element)($rootScope);
    $rootScope.$digest();

    expect($log.warn.calls.count()).toBe(3);
    expect($log.warn.calls.argsFor(0)).toEqual(['CarouselController is now deprecated. Use UibCarouselController instead.']);
    expect($log.warn.calls.argsFor(1)).toEqual(['slide is now deprecated. Use uib-slide instead.']);
    expect($log.warn.calls.argsFor(2)).toEqual(['carousel is now deprecated. Use uib-carousel instead.']);
  }));
});
