// Go tests, go
describe('Deck JS', function() {
  describe('standard html structure', function() {
    beforeEach(function() {
      loadFixtures('standard.html');
      if (Modernizr.history) {
        history.replaceState({}, "", "#")
      }
      else {
        window.location.hash = '#';
      }
    });

    describe('init(options.selectors.slides)', function() {
      it('should create slides', function() {
        $.deck({
          selectors: {
            slides: '.slide3'
          }
        });
        expect($.deck('getSlides').length).toEqual($('.slide3').length);
      });
    });

    describe('init(selector)', function() {
      it('should create slides', function() {
        $.deck();
        expect($.deck('getSlides').length).toEqual($('.slide').length);
      });
    });

    describe('init([selectors])', function() {
      it('should create slides', function() {
        $.deck([
          '.slide1',
          '.slide2',
          '.slide3',
          '.slide4',
          '.slide5'
        ]);
        expect($.deck('getSlides').length).toEqual($('.slide').length);
      });
    });

    describe('navigation functions', function() {
      beforeEach(function() {
        $.deck();
      });

      describe('go(i)', function() {
        it('should go to the i slide (0 based index)', function() {
          $.deck('go', 3);
          expect($.deck('getSlide')).toHaveClass('slide4');
        });

        it('should go to the slide with specified id', function() {
          $.deck('go', 'custom-id');
          expect($.deck('getSlide')).toHaveId('custom-id');
        });

        it('should go nowhere if i is out of bounds', function() {
          $.deck('go', 5);
          expect($.deck('getSlide')).toHaveClass('slide1');
        });

        it('should go nowhere if id does not exist', function() {
          $.deck('go', 'i-dont-exist');
          expect($.deck('getSlide')).toHaveClass('slide1');
        });

        describe('aria attribute updates', function() {
          beforeEach(function() {
            loadFixtures('nesteds.html');
            $.deck();
            $.deck('go', 5);
          });

          it('should set offscreen slides to hidden true', function() {
            $([
              '.toplevel.deck-before:not(.deck-child-current)',
              '.toplevel.deck-previous:not(.deck-child-current)',
              '.deck-next',
              '.deck-after'
            ].join(', ')).each(function() {
              expect($(this)).toHaveAttr('aria-hidden', 'true');
            });
          });

          it('should set onscreen slides to hidden false', function() {
            $([
              '.deck-child-current.slide',
              '.deck-child-current .deck-before',
              '.deck-child-current .deck-previous',
              '.deck-current'
            ].join(', ')).each(function() {
              expect($(this)).toHaveAttr('aria-hidden', 'false');
            });
          });
        });
      });

      describe('next()', function() {
        it('should go to the next slide', function() {
          $.deck('next');
          expect($.deck('getSlide')).toHaveClass('slide2');
        });

        it('should go nowhere if on the last slide', function() {
          $.deck('go', 4);
          $.deck('next');
          expect($.deck('getSlide')).toHaveClass('slide5');
        });
      });

      describe('prev()', function() {
        it('should go to the previous slide', function() {
          $.deck('go', 2);
          $.deck('prev');
          expect($.deck('getSlide')).toHaveClass('slide2');
        });

        it('should go nowhere if on the first slide', function() {
          $.deck('prev');
          expect($.deck('getSlide')).toHaveClass('slide1');
        });
      });
    });

    describe('getters', function() {
      beforeEach(function() {
        $.deck();
      });

      describe('getSlide()', function() {
        it('should get the current slide', function() {
          expect($.deck('getSlide')).toHaveClass('slide1');
          $.deck('go', 2);
          expect($.deck('getSlide')).toHaveClass('slide3');
        });
      });

      describe('getSlide(i)', function() {
        it('should get slide number i (0 based index)', function() {
          expect($.deck('getSlide', 1)).toHaveClass('slide2');
          expect($.deck('getSlide', 3)).toHaveClass('slide4');
        });

        it('should return null if i is NaN', function() {
          expect($.deck('getSlide', 'barfoo')).toBeNull();
        });

        it('should return null if i is out of bounds', function() {
          expect($.deck('getSlide', 6)).toBeNull();
        });
      });

      describe('getSlides()', function() {
        it('should return an array of jQuery objects for each slide', function() {
          var expectation = [];
          var slides = $.deck('getSlides');
          $('.slide').each(function() {
            expectation.push($(this));
          });
          expect(slides).toEqual(expectation);
        });
      });

      describe('getContainer()', function() {
        it('should return a jQuery object with the container element(s)', function() {
          expect($.deck('getContainer')).toBe(defaults.selectors.container);
        });
      });

      describe('getOptions()', function() {
        it('should return the current options object', function() {
          expect($.deck('getOptions')).toEqual(defaults);
        });
      });

      describe('getTopLevelSlides()', function() {
        it('should return only root slides', function() {
          loadFixtures('nesteds.html');
          $.deck();
          var expectation = [];
          var topLevelSlides = $.deck('getTopLevelSlides');
          $('.toplevel').each(function() {
            expectation.push($(this));
          });
          expect(topLevelSlides).toEqual(expectation);
        });
      });

      describe('getNestedSlides()', function() {
        it('should return nested slides for current slide', function() {
          loadFixtures('nesteds.html');
          $.deck();
          $.deck('go', 2);
          var expectation = [];
          var nestedSlides = $.deck('getNestedSlides');
          $.deck('getSlide').find('.slide').each(function() {
            expectation.push($(this));
          });
          expect(nestedSlides).toEqual(expectation);
        });
      });
    });

    describe('container states', function() {
      beforeEach(function() {
        $.deck();
      });

      it('should start at state 0', function() {
        expect($(defaults.selectors.container)).toHaveClass(defaults.classes.onPrefix + '0');
      });

      it('should change states with the slide number', function() {
        $.deck('next');
        expect($(defaults.selectors.container)).toHaveClass(defaults.classes.onPrefix + '1');
        $.deck('go', 3);
        expect($(defaults.selectors.container)).toHaveClass(defaults.classes.onPrefix + '3');
        $.deck('prev');
        expect($(defaults.selectors.container)).toHaveClass(defaults.classes.onPrefix + '2');
      });
    });

    describe('options object', function() {
      var $d = $(document);

      beforeEach(function() {
        $.deck('.alt-slide', {
          classes: {
            after: 'alt-after',
            before: 'alt-before',
            current: 'alt-current',
            onPrefix: 'alt-on-',
            next: 'alt-next',
            previous: 'alt-prev'
          },

          selectors: {
            container: '.alt-container'
          },

          keys: {
            next: 87,
            previous: 69
          }
        });
      });

      describe('classes', function() {
        it('should use the specified after class', function() {
          expect($('.alt-slide3, .alt-slide4, .alt-slide5')).toHaveClass('alt-after');
        });

        it('should use the specified before class', function() {
          $.deck('go', 4);
          expect($('.alt-slide1, .alt-slide2, .alt-slide3')).toHaveClass('alt-before');
        });

        it('should use the specified container class', function() {
          $.deck('go', 2);
          expect($('.alt-container')).toHaveClass('alt-on-2');
        });

        it('should use the specified current class', function() {
          expect($.deck('getSlide')).toHaveClass('alt-current');
        });

        it('should use the specified next class', function() {
          expect($('.alt-slide2')).toHaveClass('alt-next');
        });

        it('should use the specified previous class', function() {
          $.deck('next');
          expect($('.alt-slide1')).toHaveClass('alt-prev');
        });
      });

      describe('key bindings', function() {
        var e;

        beforeEach(function() {
          e = jQuery.Event('keydown.deck');
        });

        it('should go to the next slide using the specified key', function() {
          e.which = 87; // 'w'
          $d.trigger(e);
          expect($.deck('getSlide')).toHaveClass('alt-slide2');
        });

        it('should go to the previous slide using the specified key', function() {
          $.deck('next');
          e.which = 69; // 'e'
          $d.trigger(e);
          expect($.deck('getSlide')).toHaveClass('alt-slide1');
        });

        it('should not trigger events that originate within editable elements', function() {
          var $outside = $('<input type="text" />').appendTo('body');
          e = jQuery.Event('keydown');
          e.which = 87;
          $outside.trigger(e);
          expect($.deck('getSlide')).toHaveClass('alt-slide1');
          $outside.remove();
        });
      });
    });

    describe('events', function() {
      var $d;

      beforeEach(function() {
        $d = $(document);
      });

      describe('deck.change', function() {
        var index, oldIndex;

        beforeEach(function() {
          $.deck();
          $.deck('go', 1);
          $d.one('deck.change', function(event, from, to) {
            index = to;
            oldIndex = from;
          });
        });

        it('should fire on go(i)', function() {
          $.deck('go', 3);
          expect(index).toEqual(3);
        });

        it('should fire on next()', function() {
          $.deck('next');
          expect(index).toEqual(2);
        });

        it('should fire on prev()', function() {
          $.deck('prev');
          expect(index).toEqual(0);
        });

        it('should pass parameters with from and to indices', function() {
          $.deck('go', 3);
          expect(index).toEqual(3);
          expect(oldIndex).toEqual(1);
        });

        it('should not fire if default prevented in beforeChange', function() {
          $d.bind('deck.beforeChange', false);
          $.deck('go', 3);
          expect($.deck('getSlide')).toEqual($.deck('getSlide', 1));
          $d.unbind('deck.beforeChange', false);
        });
      });

      describe('deck.init', function() {
        it('should fire on deck initialization', function() {
          $.deck();
          expect($.deck('getSlides').length).toBeGreaterThan(0);
        });
      });

      describe('deck.beforeInit', function() {
        var beforeHit;

        beforeEach(function() {
          beforeHit = false;
          $d.on('deck.beforeInit', function() {
            beforeHit = true;
          });
        });

        it('should fire on deck initialization', function() {
          $.deck();
          expect(beforeHit).toBeTruthy();
        });

        it('should have populated the slides array', function() {
          var f = function() {
            expect($.deck('getSlides').length).toEqual($('.slide').length);
          };

          $d.bind('deck.beforeInit', f);
          $.deck();
          $d.unbind('deck.beforeInit', f);
        });

        it('should prevent the init event if lockInit is called', function() {
          var initHit = false;
          var f = function(event) {
            event.lockInit();
          };
          var g = function() {
            initHit = true;
          };

          $d.bind('deck.beforeInit', f);
          $d.bind('deck.init', g);
          $.deck();
          $d.unbind('deck.beforeInit', f);
          $d.unbind('deck.init', g);
          expect(initHit).toBeFalsy();
        });

        it('should warn if locked without release', function() {
          var warned = false;
          var f = function(event) {
            event.lockInit();
          };
          var warn = console.warn;
          window.console.warn = function() {
            warned = true;
          };

          $d.bind('deck.beforeInit', f);
          $.deck('.slide', {
            initLockTimeout: 20
          });
          $d.unbind('deck.beforeInit', f);

          waitsFor(function() {
            return warned;
          }, 'warning', 2000);

          runs(function() {
            window.console.warn = warn;
          });
        });

        it('should fire init event once releaseInit is called', function() {
          var f = function(event) {
            event.lockInit();
            window.setTimeout(function() {
              event.releaseInit();
            }, 20);
          };

          runs(function() {
            $d.bind('deck.beforeInit', f);
            $.deck();
            $d.unbind('deck.beforeInit', f);
          });

          waitsFor(function() {
            return $.deck('getSlides').length > 0;
          }, 'lock to release', 2000);
        });
      });
    });

    describe('hash/id assignments', function() {
      beforeEach(function() {
        $.deck('.slide');
      });

      it('should assign ids to slides that do not have them', function() {
        var slides = $.deck('getSlides');
        $.each(slides, function(i, $e) {
          expect($e.attr('id')).toBeTruthy();
        });
      });

      it('should reassign ids on reinitialization', function() {
        var $firstSlide = $.deck('getSlide', 0);
        var firstID = $firstSlide.attr('id');

        $firstSlide.before('<div class="slide"></div>');
        $.deck('.slide');
        expect($firstSlide).not.toHaveId(firstID);
      });

      it('should update container with a state class including the slide id', function() {
        var $c = $.deck('getContainer');
        var osp = defaults.classes.onPrefix;

        expect($c).toHaveClass(osp + $.deck('getSlide', 0).attr('id'));
        $.deck('next');
        expect($c).toHaveClass(osp + $.deck('getSlide', 1).attr('id'));
        $.deck('next');
        expect($c).not.toHaveClass(osp + $.deck('getSlide', 1).attr('id'));
        expect($c).toHaveClass(osp + $.deck('getSlide', 2).attr('id'));
      });

      it('should use existing ids if they exist', function() {
        expect($('#custom-id')).toExist();
      });

      it('should update the URL on slide change (if supported)', function() {
        if (Modernizr.history) {
          $.deck('go', 3);
          expect(window.location.hash).toEqual('#slide-3');
        }
      });

      it('should deep link to slide on deck init', function() {
        window.location.hash = "#slide-3";
        $.deck('.slide');
        waitsFor(function() {
          return $.deck('getSlide').attr('id') === 'slide-3';
        });
      });

      it('should follow internal hash links using hashchange (if supported)', function() {
        window.location.hash = "#slide-3";
        // Hashchange event doesn't fire right when the hash changes?
        waitsFor(function() {
          return $.deck('getSlide').attr('id') === 'slide-3';
        }, 'hash to change to slide-3', 2000);
      });
    });
  });

  describe('empty deck', function() {
    beforeEach(function() {
      loadFixtures('empty.html');
      $.deck();
    });

    describe('getSlide()', function() {
      it('should not error on init', $.noop);
    });
  });
});