<commit_before>/*
 * scrollNav
 * http://scrollnav.com
 *
 * Copyright (c) 2013-2016 James Wilson
 * Licensed under the MIT license.
 */

(function($) {

  // Animate scrolling to section location
  var scroll_to = function(value, speed, offset, animated) {
    if ( $(value).length > 0 ) {
      var destination = $(value).offset().top;
      speed = animated ? speed : 0;

	    // Add a class to the scrolled-to section
	    $('.' + S.settings.className + '__focused-section').removeClass(S.settings.className + '__focused-section');
	    $(value).addClass(S.settings.className + '__focused-section');

      $('html:not(:animated),body:not(:animated)')
        .animate({scrollTop: destination - offset }, speed );
    }
  };

  // Get url hash if one exists
  var get_hash = function() {
    return window.location.hash;
  };

  var S = {
    classes: {
      loading: 'sn-loading',
      failed: 'sn-failed',
      success: 'sn-active'
    },
    defaults: {
      sections: 'h2',
      subSections: false,
      sectionElem: 'section',
      className: 'scroll-nav',
      showHeadline: true,
      headlineText: 'Scroll To',
      showTopLink: true,
      topLinkText: 'Top',
      fixedMargin: 40,
      scrollOffset: 40,
      animated: true,
      speed: 500,
      insertLocation: 'insertBefore',
      arrowKeys: false,
      scrollToHash: true,
      onInit: null,
      onRender: null,
      onDestroy: null,
      onResetPos: null
    },
    _set_body_class: function(state) {
      // Set and swap our loading hooks to the body

      var $body = $('body');

      if (state === 'loading') {
        $body.addClass(S.classes.loading);
      } else if (state === 'success') {
        $body.removeClass(S.classes.loading).addClass(S.classes.success);
      } else {
        $body.removeClass(S.classes.loading).addClass(S.classes.failed);
      }
    },
    _find_sections: function($el) {
      // Find the html for each section

      var target_elems = S.settings.sections;
      var raw_html = [];

      if (S.settings.showTopLink) {
        var $firstElem = $el.children().first();

        if ( !$firstElem.is(target_elems) ) {
          raw_html.push( $firstElem.nextUntil(target_elems).andSelf() );
        }
      }

      $el.find(target_elems).each(function() {
        raw_html.push( $(this).nextUntil(target_elems).andSelf() );
      });

      S.sections = {
        raw: raw_html
      };
    },
    _setup_sections: function(sections) {
      // Wrap each section and add it's details to the section array

      var section_data = [];

      $(sections).each(function(i) {
        var sub_data      = [];
        var $this_section = $(this);
        var section_id    = 'scrollNav-' + (i + 1);
        var isFirst       = function() { return i === 0; };
        var hasHeading    = function() { return !$this_section.eq(0).is(S.settings.sections); };
        var text          = ( S.settings.showTopLink && isFirst() && hasHeading() ) ? S.settings.topLinkText : $this_section.filter(S.settings.sections).text();

        $this_section.wrapAll('<' + S.settings.sectionElem + ' id="' + section_id + '" class="' + S.settings.className + '__section" />');

        if (S.settings.subSections) {
          var $sub_sections  = $this_section.filter(S.settings.subSections);

          if ($sub_sections.length > 0) {
            $sub_sections.each(function(i) {
              var sub_id      = section_id + '-' + (i + 1);
              var sub_text    = $(this).text();
              var $this_sub   = $this_section.filter($(this).nextUntil($sub_sections).andSelf());

              $this_sub.wrapAll('
');
              sub_data.push( {id: sub_id, text: sub_text} );
            });
          }
        }

        section_data.push( {id: section_id, text: text, sub_sections: sub_data} );
      });

      S.sections.data = section_data;
    },
    _tear_down_sections: function(sections) {
      $(sections).each(function() {
        var sub_sections = this.sub_sections;

        $('#' + this.id).children().unwrap();

        if (sub_sections.length > 0) {
          $(sub_sections).each(function() {
            $('#' + this.id).children().unwrap();
          });
        }
      });
    },
    _setup_nav: function(sections) {
    // Populate an ordered list from the section array we built

      var $headline = $('', {'class': S.settings.className + '__heading', text: S.settings.headlineText});
      var $wrapper  = $('
', {'class': S.settings.className + '__wrapper'});
      var $nav      = $('', {'class': S.settings.className, 'role': 'navigation'});
      var $nav_list = $('
', {'class': S.settings.className + '__list'});

      $.each(sections, function(i) {
        var $item     = (i === 0) ? $('
', {'class': S.settings.className + '__item active'}) : $('
', {'class': S.settings.className + '__item'});
        var $link     = $('', {'href': '#' + this.id, 'class': S.settings.className + '__link', text: this.text});
        var $sub_nav_list;

        if (this.sub_sections.length > 0) {
          $item.addClass('is-parent-item');
          $sub_nav_list = $('
', {'class': S.settings.className + '__sub-list'});

          $.each(this.sub_sections, function() {
            var $sub_item = $('
', {'class': S.settings.className + '__sub-item'});
            var $sub_link = $('', {'href': '#' + this.id, 'class': S.settings.className + '__sub-link', text: this.text});

            $sub_nav_list.append( $sub_item.append($sub_link) );
          });
        }

        $nav_list.append( $item.append($link).append($sub_nav_list) );
      });

      if (S.settings.showHeadline) {
        $nav.append( $wrapper.append($headline).append($nav_list) );
      } else {
        $nav.append( $wrapper.append($nav_list) );
      }

      S.nav = $nav;
    },
    _insert_nav: function() {
      // Add the nav to our page

      var insert_location = S.settings.insertLocation;
      var $insert_target = S.settings.insertTarget;

      S.nav[insert_location]($insert_target);
    },
    _setup_pos: function() {
      // Find the offset positions of each section

      var $nav        = S.nav;
      var vp_height   = $(window).height();
      var nav_offset  = $nav.offset().top;

      var set_offset = function(section) {
        var $this_section  = $('#' + section.id);
        var this_height    = $this_section.height();

        section.top_offset    = $this_section.offset().top;
        section.bottom_offset = section.top_offset + this_height;
      };

      $.each(S.sections.data, function() {
        set_offset(this);

        $.each(this.sub_sections, function() {
          set_offset(this);
        });
      });

      S.dims = {
        vp_height:  vp_height,
        nav_offset: nav_offset
      };
    },
    _check_pos: function() {
      // Set nav to fixed after scrolling past the header and add an in-view class to any
      // sections currently within the bounds of our view and active class to the first
      // in-view section

      var $nav                = S.nav;
      var win_top             = $(window).scrollTop();
      var boundry_top         = win_top + S.settings.scrollOffset;
      var boundry_bottom      = win_top + S.dims.vp_height - S.settings.scrollOffset;
      var sections_active     = [];
      var sub_sections_active = [];

      if ( win_top > (S.dims.nav_offset - S.settings.fixedMargin) ) { $nav.addClass('fixed'); }
      else { $nav.removeClass('fixed'); }

      var in_view = function(section) {
        return (section.top_offset >= boundry_top && section.top_offset <= boundry_bottom) || (section.bottom_offset > boundry_top && section.bottom_offset < boundry_bottom) || (section.top_offset < boundry_top && section.bottom_offset > boundry_bottom);
      };

      $.each(S.sections.data, function() {
        if ( in_view(this) ) {
          sections_active.push(this);
        }
        $.each(this.sub_sections, function() {
          if ( in_view(this) ) {
            sub_sections_active.push(this);
          }
        });
      });

      $nav.find('.' + S.settings.className + '__item').removeClass('active').removeClass('in-view');
      $nav.find('.' + S.settings.className + '__sub-item').removeClass('active').removeClass('in-view');

      $.each(sections_active, function(i) {
        if (i === 0) {
          $nav.find('a[href="#' + this.id + '"]').parents('.' + S.settings.className + '__item').addClass('active').addClass('in-view');
        } else {
          $nav.find('a[href="#' + this.id + '"]').parents('.' + S.settings.className + '__item').addClass('in-view');
        }
      });
      S.sections.active = sections_active;

      $.each(sub_sections_active, function(i) {
        if (i === 0) {
          $nav.find('a[href="#' + this.id + '"]').parents('.' + S.settings.className + '__sub-item').addClass('active').addClass('in-view');
        } else {
          $nav.find('a[href="#' + this.id + '"]').parents('.' + S.settings.className + '__sub-item').addClass('in-view');
        }
      });
    },
    _init_scroll_listener: function() {
      // Set a scroll listener to update the fixed and active classes

      $(window).on('scroll.scrollNav', function() {
        S._check_pos();
      });
    },
    _rm_scroll_listeners: function() {
      $(window).off('scroll.scrollNav');
    },
    _init_resize_listener: function() {
      // Set a resize listener to update position values and the fixed and active classes

      $(window).on('resize.scrollNav', function() {
        S._setup_pos();
        S._check_pos();
      });
    },
    _rm_resize_listener: function() {
      $(window).off('resize.scrollNav');
    },
    _init_click_listener: function() {
      // Scroll to section on click

      $('.' + S.settings.className).find('a').on('click.scrollNav', function(e) {
        e.preventDefault();

        var value     = $(this).attr('href');
        var speed     = S.settings.speed;
        var offset    = S.settings.scrollOffset;
        var animated  = S.settings.animated;

        scroll_to(value, speed, offset, animated);
      });
    },
    _rm_click_listener: function() {
      $('.' + S.settings.className).find('a').off('click.scrollNav');
    },
    _init_keyboard_listener: function(sections) {
      // Scroll to section on arrow key press

      if (S.settings.arrowKeys) {
        $(document).on('keydown.scrollNav', function(e) {
          if (e.keyCode === 40 || e.keyCode === 38) {
            var findSection = function(key) {
              var i = 0;
              var l = sections.length;

              for (i; i < l; i++) {
                if (sections[i].id === S.sections.active[0].id) {
                  var array_offset  = (key === 40) ? i + 1 : i -1;
                  var id            = (sections[array_offset] === undefined) ? undefined : sections[array_offset].id;

                  return id;
                }
              }
            };

            var target_section = findSection(e.keyCode);

            if (target_section !== undefined) {
              e.preventDefault();

              var value     = '#' + target_section;
              var speed     = S.settings.speed;
              var offset    = S.settings.scrollOffset;
              var animated  = S.settings.animated;

              scroll_to(value, speed, offset, animated);
            }
          }
        });
      }
    },
    _rm_keyboard_listener: function() {
      $(document).off('keydown.scrollNav');
    },
    init: function(options) {
      return this.each(function() {
        var $el = $(this);

        // Merge default settings with user defined options
        S.settings = $.extend({}, S.defaults, options);

        // If the insert target isn't set, use the initialized element
        S.settings.insertTarget = S.settings.insertTarget ? $(S.settings.insertTarget) : $el;

        if ($el.length > 0) {
          // Initialize

          // Fire custom init callback
          if (S.settings.onInit) { S.settings.onInit.call(this); }

          S._set_body_class('loading');
          S._find_sections($el);

          if ( $el.find(S.settings.sections).length > 0 ) {
            // BUILD!!!!

            S._setup_sections(S.sections.raw);
            S._setup_nav(S.sections.data);

            if ( S.settings.insertTarget.length > 0 ) {
              //Add to page

              S._insert_nav();
              S._setup_pos();
              S._check_pos();
              S._init_scroll_listener();
              S._init_resize_listener();
              S._init_click_listener();
              S._init_keyboard_listener(S.sections.data);
              S._set_body_class('success');
              if (S.settings.scrollToHash){
                scroll_to( get_hash() );
              }

              // Fire custom render callback
              if (S.settings.onRender) { S.settings.onRender.call(this); }

            } else {
              console.log('Build failed, scrollNav could not find "' + S.settings.insertTarget + '"');
              S._set_body_class('failed');
            }

          } else {
            console.log('Build failed, scrollNav could not find any "' + S.settings.sections + 's" inside of "' + $el.selector + '"');
            S._set_body_class('failed');
          }

        } else {
          console.log('Build failed, scrollNav could not find "' + $el.selector + '"');
          S._set_body_class('failed');
        }
      });
    },
    destroy: function() {
      return this.each(function() {

        // Unbind event listeners
        S._rm_scroll_listeners();
        S._rm_resize_listener();
        S._rm_click_listener();
        S._rm_keyboard_listener();

        // Remove any of the loading hooks
        $('body').removeClass('sn-loading sn-active sn-failed');

        // Remove the nav from the dom
        $('.' + S.settings.className).remove();

        // Teardown sections
        S._tear_down_sections(S.sections.data);

        // Fire custom destroy callback
        if (S.settings.onDestroy) { S.settings.onDestroy.call(this); }

        // Remove the saved settings
        S.settings = [];
        S.sections = undefined;
      });
    },
    resetPos: function() {
      S._setup_pos();
      S._check_pos();

      // Fire custom reset position callback
      if (S.settings.onResetPos) { S.settings.onResetPos.call(this); }
    }
  };

  $.fn.scrollNav = function() {
    var options;
    var method  = arguments[0];

    if (S[method]) {
      // Method exists, so use it

      method  = S[method];
      options = Array.prototype.slice.call(arguments, 1);
    } else if (typeof(method) === 'object' || !method) {
      // No method passed, default to init

      method  = S.init;
      options = arguments;
    } else {
      // Method doesn't exist

      $.error( 'Method ' +  method + ' does not exist in the scrollNav plugin' );
      return this;
    }

    return method.apply(this, options);
  };
})(jQuery);
<commit_msg>Replaced andSelf() calls with addBack().<commit_after>/*
 * scrollNav
 * http://scrollnav.com
 *
 * Copyright (c) 2013-2016 James Wilson
 * Licensed under the MIT license.
 */

(function($) {

  // Animate scrolling to section location
  var scroll_to = function(value, speed, offset, animated) {
    if ( $(value).length > 0 ) {
      var destination = $(value).offset().top;
      speed = animated ? speed : 0;

	    // Add a class to the scrolled-to section
	    $('.' + S.settings.className + '__focused-section').removeClass(S.settings.className + '__focused-section');
	    $(value).addClass(S.settings.className + '__focused-section');

      $('html:not(:animated),body:not(:animated)')
        .animate({scrollTop: destination - offset }, speed );
    }
  };

  // Get url hash if one exists
  var get_hash = function() {
    return window.location.hash;
  };

  var S = {
    classes: {
      loading: 'sn-loading',
      failed: 'sn-failed',
      success: 'sn-active'
    },
    defaults: {
      sections: 'h2',
      subSections: false,
      sectionElem: 'section',
      className: 'scroll-nav',
      showHeadline: true,
      headlineText: 'Scroll To',
      showTopLink: true,
      topLinkText: 'Top',
      fixedMargin: 40,
      scrollOffset: 40,
      animated: true,
      speed: 500,
      insertLocation: 'insertBefore',
      arrowKeys: false,
      scrollToHash: true,
      onInit: null,
      onRender: null,
      onDestroy: null,
      onResetPos: null
    },
    _set_body_class: function(state) {
      // Set and swap our loading hooks to the body

      var $body = $('body');

      if (state === 'loading') {
        $body.addClass(S.classes.loading);
      } else if (state === 'success') {
        $body.removeClass(S.classes.loading).addClass(S.classes.success);
      } else {
        $body.removeClass(S.classes.loading).addClass(S.classes.failed);
      }
    },
    _find_sections: function($el) {
      // Find the html for each section

      var target_elems = S.settings.sections;
      var raw_html = [];

      if (S.settings.showTopLink) {
        var $firstElem = $el.children().first();

        if ( !$firstElem.is(target_elems) ) {
          raw_html.push( $firstElem.nextUntil(target_elems).addBack() );
        }
      }

      $el.find(target_elems).each(function() {
        raw_html.push( $(this).nextUntil(target_elems).addBack() );
      });

      S.sections = {
        raw: raw_html
      };
    },
    _setup_sections: function(sections) {
      // Wrap each section and add it's details to the section array

      var section_data = [];

      $(sections).each(function(i) {
        var sub_data      = [];
        var $this_section = $(this);
        var section_id    = 'scrollNav-' + (i + 1);
        var isFirst       = function() { return i === 0; };
        var hasHeading    = function() { return !$this_section.eq(0).is(S.settings.sections); };
        var text          = ( S.settings.showTopLink && isFirst() && hasHeading() ) ? S.settings.topLinkText : $this_section.filter(S.settings.sections).text();

        $this_section.wrapAll('<' + S.settings.sectionElem + ' id="' + section_id + '" class="' + S.settings.className + '__section" />');

        if (S.settings.subSections) {
          var $sub_sections  = $this_section.filter(S.settings.subSections);

          if ($sub_sections.length > 0) {
            $sub_sections.each(function(i) {
              var sub_id      = section_id + '-' + (i + 1);
              var sub_text    = $(this).text();
              var $this_sub   = $this_section.filter($(this).nextUntil($sub_sections).addBack());

              $this_sub.wrapAll('
');
              sub_data.push( {id: sub_id, text: sub_text} );
            });
          }
        }

        section_data.push( {id: section_id, text: text, sub_sections: sub_data} );
      });

      S.sections.data = section_data;
    },
    _tear_down_sections: function(sections) {
      $(sections).each(function() {
        var sub_sections = this.sub_sections;

        $('#' + this.id).children().unwrap();

        if (sub_sections.length > 0) {
          $(sub_sections).each(function() {
            $('#' + this.id).children().unwrap();
          });
        }
      });
    },
    _setup_nav: function(sections) {
    // Populate an ordered list from the section array we built

      var $headline = $('', {'class': S.settings.className + '__heading', text: S.settings.headlineText});
      var $wrapper  = $('
', {'class': S.settings.className + '__wrapper'});
      var $nav      = $('', {'class': S.settings.className, 'role': 'navigation'});
      var $nav_list = $('
', {'class': S.settings.className + '__list'});

      $.each(sections, function(i) {
        var $item     = (i === 0) ? $('
', {'class': S.settings.className + '__item active'}) : $('
', {'class': S.settings.className + '__item'});
        var $link     = $('', {'href': '#' + this.id, 'class': S.settings.className + '__link', text: this.text});
        var $sub_nav_list;

        if (this.sub_sections.length > 0) {
          $item.addClass('is-parent-item');
          $sub_nav_list = $('
', {'class': S.settings.className + '__sub-list'});

          $.each(this.sub_sections, function() {
            var $sub_item = $('
', {'class': S.settings.className + '__sub-item'});
            var $sub_link = $('', {'href': '#' + this.id, 'class': S.settings.className + '__sub-link', text: this.text});

            $sub_nav_list.append( $sub_item.append($sub_link) );
          });
        }

        $nav_list.append( $item.append($link).append($sub_nav_list) );
      });

      if (S.settings.showHeadline) {
        $nav.append( $wrapper.append($headline).append($nav_list) );
      } else {
        $nav.append( $wrapper.append($nav_list) );
      }

      S.nav = $nav;
    },
    _insert_nav: function() {
      // Add the nav to our page

      var insert_location = S.settings.insertLocation;
      var $insert_target = S.settings.insertTarget;

      S.nav[insert_location]($insert_target);
    },
    _setup_pos: function() {
      // Find the offset positions of each section

      var $nav        = S.nav;
      var vp_height   = $(window).height();
      var nav_offset  = $nav.offset().top;

      var set_offset = function(section) {
        var $this_section  = $('#' + section.id);
        var this_height    = $this_section.height();

        section.top_offset    = $this_section.offset().top;
        section.bottom_offset = section.top_offset + this_height;
      };

      $.each(S.sections.data, function() {
        set_offset(this);

        $.each(this.sub_sections, function() {
          set_offset(this);
        });
      });

      S.dims = {
        vp_height:  vp_height,
        nav_offset: nav_offset
      };
    },
    _check_pos: function() {
      // Set nav to fixed after scrolling past the header and add an in-view class to any
      // sections currently within the bounds of our view and active class to the first
      // in-view section

      var $nav                = S.nav;
      var win_top             = $(window).scrollTop();
      var boundry_top         = win_top + S.settings.scrollOffset;
      var boundry_bottom      = win_top + S.dims.vp_height - S.settings.scrollOffset;
      var sections_active     = [];
      var sub_sections_active = [];

      if ( win_top > (S.dims.nav_offset - S.settings.fixedMargin) ) { $nav.addClass('fixed'); }
      else { $nav.removeClass('fixed'); }

      var in_view = function(section) {
        return (section.top_offset >= boundry_top && section.top_offset <= boundry_bottom) || (section.bottom_offset > boundry_top && section.bottom_offset < boundry_bottom) || (section.top_offset < boundry_top && section.bottom_offset > boundry_bottom);
      };

      $.each(S.sections.data, function() {
        if ( in_view(this) ) {
          sections_active.push(this);
        }
        $.each(this.sub_sections, function() {
          if ( in_view(this) ) {
            sub_sections_active.push(this);
          }
        });
      });

      $nav.find('.' + S.settings.className + '__item').removeClass('active').removeClass('in-view');
      $nav.find('.' + S.settings.className + '__sub-item').removeClass('active').removeClass('in-view');

      $.each(sections_active, function(i) {
        if (i === 0) {
          $nav.find('a[href="#' + this.id + '"]').parents('.' + S.settings.className + '__item').addClass('active').addClass('in-view');
        } else {
          $nav.find('a[href="#' + this.id + '"]').parents('.' + S.settings.className + '__item').addClass('in-view');
        }
      });
      S.sections.active = sections_active;

      $.each(sub_sections_active, function(i) {
        if (i === 0) {
          $nav.find('a[href="#' + this.id + '"]').parents('.' + S.settings.className + '__sub-item').addClass('active').addClass('in-view');
        } else {
          $nav.find('a[href="#' + this.id + '"]').parents('.' + S.settings.className + '__sub-item').addClass('in-view');
        }
      });
    },
    _init_scroll_listener: function() {
      // Set a scroll listener to update the fixed and active classes

      $(window).on('scroll.scrollNav', function() {
        S._check_pos();
      });
    },
    _rm_scroll_listeners: function() {
      $(window).off('scroll.scrollNav');
    },
    _init_resize_listener: function() {
      // Set a resize listener to update position values and the fixed and active classes

      $(window).on('resize.scrollNav', function() {
        S._setup_pos();
        S._check_pos();
      });
    },
    _rm_resize_listener: function() {
      $(window).off('resize.scrollNav');
    },
    _init_click_listener: function() {
      // Scroll to section on click

      $('.' + S.settings.className).find('a').on('click.scrollNav', function(e) {
        e.preventDefault();

        var value     = $(this).attr('href');
        var speed     = S.settings.speed;
        var offset    = S.settings.scrollOffset;
        var animated  = S.settings.animated;

        scroll_to(value, speed, offset, animated);
      });
    },
    _rm_click_listener: function() {
      $('.' + S.settings.className).find('a').off('click.scrollNav');
    },
    _init_keyboard_listener: function(sections) {
      // Scroll to section on arrow key press

      if (S.settings.arrowKeys) {
        $(document).on('keydown.scrollNav', function(e) {
          if (e.keyCode === 40 || e.keyCode === 38) {
            var findSection = function(key) {
              var i = 0;
              var l = sections.length;

              for (i; i < l; i++) {
                if (sections[i].id === S.sections.active[0].id) {
                  var array_offset  = (key === 40) ? i + 1 : i -1;
                  var id            = (sections[array_offset] === undefined) ? undefined : sections[array_offset].id;

                  return id;
                }
              }
            };

            var target_section = findSection(e.keyCode);

            if (target_section !== undefined) {
              e.preventDefault();

              var value     = '#' + target_section;
              var speed     = S.settings.speed;
              var offset    = S.settings.scrollOffset;
              var animated  = S.settings.animated;

              scroll_to(value, speed, offset, animated);
            }
          }
        });
      }
    },
    _rm_keyboard_listener: function() {
      $(document).off('keydown.scrollNav');
    },
    init: function(options) {
      return this.each(function() {
        var $el = $(this);

        // Merge default settings with user defined options
        S.settings = $.extend({}, S.defaults, options);

        // If the insert target isn't set, use the initialized element
        S.settings.insertTarget = S.settings.insertTarget ? $(S.settings.insertTarget) : $el;

        if ($el.length > 0) {
          // Initialize

          // Fire custom init callback
          if (S.settings.onInit) { S.settings.onInit.call(this); }

          S._set_body_class('loading');
          S._find_sections($el);

          if ( $el.find(S.settings.sections).length > 0 ) {
            // BUILD!!!!

            S._setup_sections(S.sections.raw);
            S._setup_nav(S.sections.data);

            if ( S.settings.insertTarget.length > 0 ) {
              //Add to page

              S._insert_nav();
              S._setup_pos();
              S._check_pos();
              S._init_scroll_listener();
              S._init_resize_listener();
              S._init_click_listener();
              S._init_keyboard_listener(S.sections.data);
              S._set_body_class('success');
              if (S.settings.scrollToHash){
                scroll_to( get_hash() );
              }

              // Fire custom render callback
              if (S.settings.onRender) { S.settings.onRender.call(this); }

            } else {
              console.log('Build failed, scrollNav could not find "' + S.settings.insertTarget + '"');
              S._set_body_class('failed');
            }

          } else {
            console.log('Build failed, scrollNav could not find any "' + S.settings.sections + 's" inside of "' + $el.selector + '"');
            S._set_body_class('failed');
          }

        } else {
          console.log('Build failed, scrollNav could not find "' + $el.selector + '"');
          S._set_body_class('failed');
        }
      });
    },
    destroy: function() {
      return this.each(function() {

        // Unbind event listeners
        S._rm_scroll_listeners();
        S._rm_resize_listener();
        S._rm_click_listener();
        S._rm_keyboard_listener();

        // Remove any of the loading hooks
        $('body').removeClass('sn-loading sn-active sn-failed');

        // Remove the nav from the dom
        $('.' + S.settings.className).remove();

        // Teardown sections
        S._tear_down_sections(S.sections.data);

        // Fire custom destroy callback
        if (S.settings.onDestroy) { S.settings.onDestroy.call(this); }

        // Remove the saved settings
        S.settings = [];
        S.sections = undefined;
      });
    },
    resetPos: function() {
      S._setup_pos();
      S._check_pos();

      // Fire custom reset position callback
      if (S.settings.onResetPos) { S.settings.onResetPos.call(this); }
    }
  };

  $.fn.scrollNav = function() {
    var options;
    var method  = arguments[0];

    if (S[method]) {
      // Method exists, so use it

      method  = S[method];
      options = Array.prototype.slice.call(arguments, 1);
    } else if (typeof(method) === 'object' || !method) {
      // No method passed, default to init

      method  = S.init;
      options = arguments;
    } else {
      // Method doesn't exist

      $.error( 'Method ' +  method + ' does not exist in the scrollNav plugin' );
      return this;
    }

    return method.apply(this, options);
  };
})(jQuery);
<|endoftext|>


<commit_before>// Load in modules and set up routes
var assert = require('assert'),
    fs = require('fs'),
    path = require('path'),
    _ = require('underscore'),
    smith = require('../src/smith.js'),
    spriteDir = path.join(__dirname, 'test_sprites'),
    expectedDir = __dirname + '/expected_files';

describe('An array of sprites', function () {
  describe('when processed via spritesmith', function () {
    it('renders a top-down spritesheet', function () {

    });
    it('has the proper coordinates', function () {

    });
    it('has the proper properties', function () {

    });
  });

  describe('when converted from left to right', function () {
    it('renders a left-right spritesheet', function () {

    });
    it('has the proper coordinates', function () {

    });
    it('has the proper properties', function () {

    });
  });

  describe('when provided with a padding parameter', function () {
    it('renders a padded spritesheet', function () {

    });
    it('has the proper coordinates', function () {

    });
    it('has the proper properties', function () {

    });
  });

  describe('when told not to sort', function () {
    it('renders an unsorted spritesheet', function () {

    });
    it('has the proper coordinates', function () {

    });
    it('has the proper properties', function () {

    });
  });
});

describe('An empty array', function () {
  describe('when processed via spritesmith', function () {
    it('renders an empty spritesheet', function () {

    });
    it('returns an empty coordinate mapping', function () {

    });
    it('has the proper properties', function () {

    });
  });
});

function addEngineTest(engine) {
  // Attempt to load the engine
  try {
    require(engine);
  } catch (e) {}

  // Create an engine-specific test
  var outline = {};
  outline[engine] = [{
    'when processed via spritesmith': [
      'returns an image'
    ]
  }];

  // Add it to our list
  outlines.push(outline);
}

// TODO: Add this back
// // Test specific engines
// addEngineTest('phantomjssmith');
// addEngineTest('gmsmith');
// addEngineTest('canvassmith');

module.exports = {
  // Setup
  'An array of sprites': function () {
    // DEV: These were unsorted for testing `sort: false` but these work for all tests as is =D
    this.sprites = [
      path.join(spriteDir, 'sprite1.png'),
      path.join(spriteDir, 'sprite3.png'),
      path.join(spriteDir, 'sprite2.jpg')
    ];

    // By default, write to the topDown namespace
    this.namespace = 'topDown.';
  },
  'An empty array': function () {
    this.namespace = 'empty.';
    this.sprites = [];
  },

  // Processing
  'when processed via spritesmith': '_when processed via spritesmith',
  '_when processed via spritesmith': function (done) {
    var that = this;

    // Load in params and add on to src
    var options = this.options || {},
        params = _.extend({'src': this.sprites}, options);

    // Attempt to smith out the sprites
    smith(params, function (err, result) {
      // If there is an error, throw it
      if (err) {
        throw err;
      } else {
      // Otherwise, save the result
        that.result = result;
      }

      // Callback
      done(err);
    });
  },
  'when converted from left to right': [function () {
    this.namespace = 'leftRight.';
    this.options = {'algorithm': 'left-right'};
  }, 'when processed via spritesmith'],
  'when provided with a padding parameter': [function () {
    this.namespace = 'padding.';
    this.options = {'algorithm': 'binary-tree', 'padding': 2};
  }, 'when processed via spritesmith'],
  'when told not to sort': [function () {
    this.namespace = 'unsorted.';
    this.options = {'algorithm': 'top-down', 'algorithmOpts': {'sort': false}};
  }, 'when processed via spritesmith'],

  // Engine-specific setups
  'phantomjssmith': ['An array of sprites', function () {
    this.namespace = 'phantomjs.';
    this.options = {'engine': 'phantomjs'};
  }],
  'gmsmith': ['An array of sprites', function () {
    this.namespace = 'gm.';
    this.options = {'engine': 'gm'};
  }],
  'canvassmith': ['An array of sprites', function () {
    this.namespace = 'canvas.';
    this.options = {'engine': 'canvas'};
  }],

  // Assertions
  'renders a top-down spritesheet': 'assertSpritesheet',
  'renders a left-right spritesheet': 'assertSpritesheet',
  'renders a padded spritesheet': 'assertSpritesheet',
  'renders an unsorted spritesheet': 'assertSpritesheet',
  'has the proper coordinates': 'assertCoordinates',
  'has the proper properties': 'assertProps',
  'returns an image': function () {
    // DEV: Write out to actual_files
    if (process.env.TEST_DEBUG) {
      try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) {}
      fs.writeFileSync(__dirname + '/actual_files/' + this.namespace + 'sprite.png', this.result.image, 'binary');
    }

    assert.notEqual(this.result.image, '');
  },
  'renders an empty spritesheet': function () {
    assert.strictEqual(this.result.image, '');
  },
  'returns an empty coordinate mapping': function () {
    assert.deepEqual(this.result.coordinates, {});
  },

  // Bulky assertions
  assertSpritesheet: function () {
    var result = this.result,
        namespace = this.namespace;

    // DEV: Write out to actual_files
    if (process.env.TEST_DEBUG) {
      try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) {}
      fs.writeFileSync(__dirname + '/actual_files/' + namespace + 'sprite.png', result.image, 'binary');
      fs.writeFileSync(__dirname + '/actual_files/' + namespace + 'sprite.jpg', result.image, 'binary');
      fs.writeFileSync(__dirname + '/actual_files/' + namespace + 'sprite.tiff', result.image, 'binary');
      fs.writeFileSync(__dirname + '/actual_files/' + namespace + 'coordinates.json', JSON.stringify(result.coordinates, null, 4));
      fs.writeFileSync(__dirname + '/actual_files/' + namespace + 'properties.json', JSON.stringify(result.properties, null, 4));
    }

    // Assert the actual image is the same expected
    var actualImage = result.image,
        expectedFilenames = ['canvas.png', 'gm.png', 'gm2.png', 'phantomjs.png', 'phantomjs2.png'],
        matchesAnImage = false;

    // ANTI-PATTERN: Looping over set without identifiable lines for stack traces
    expectedFilenames.forEach(function testAgainstExpected (filename) {
      if (!matchesAnImage) {
        var filepath = path.join(expectedDir, namespace + filename);
        if (fs.existsSync(filepath)) {
          var expectedImage = fs.readFileSync(filepath, 'binary');
          matchesAnImage = actualImage === expectedImage;
        }
      }
    });

    assert(matchesAnImage, "Actual image does not match expected image");
  },
  assertCoordinates: function () {
    // Load in the coordinates
    var result = this.result,
        expectedCoords = require(expectedDir + '/' + this.namespace + 'coordinates.json');

    // Normalize the actual coordinates
    // TODO: Normalize dir should be an option
    var actualCoords = result.coordinates,
        normCoords = {};
    assert(actualCoords, "Result does not have a coordinates property");

    Object.getOwnPropertyNames(actualCoords).forEach(function (filepath) {
      var file = path.relative(spriteDir, filepath);
      normCoords[file] = actualCoords[filepath];
    });

    // Assert that the returned coordinates deep equal those in the coordinates.json
    assert.deepEqual(expectedCoords, normCoords, "Actual coordinates do not match expected coordinates");
  },
  assertProps: function () {
    // Load in the properties
    var actualProps = this.result.properties,
        expectedProps = require(expectedDir + '/' + this.namespace + 'properties.json');

    // Assert that the returned properties equals the expected properties
    assert.deepEqual(expectedProps, actualProps, "Actual properties do not match expected properties");
  }
};
<commit_msg>Converted most assertions/actions into mocha compatible formats but still sloppy<commit_after>// Load in modules and set up routes
var assert = require('assert'),
    fs = require('fs'),
    path = require('path'),
    _ = require('underscore'),
    smith = require('../src/smith.js'),
    spriteDir = path.join(__dirname, 'test_sprites'),
    expectedDir = __dirname + '/expected_files';

function processViaSpritesmith() {
  before(function processViaSpritesmithFn (done) {
    var that = this;

    // Load in params and add on to src
    var options = this.options || {},
        params = _.extend({'src': this.sprites}, options);

    // Attempt to smith out the sprites
    smith(params, function (err, result) {
      // If there is an error, throw it
      if (err) {
        throw err;
      } else {
      // Otherwise, save the result
        that.result = result;
      }

      // Callback
      done(err);
    });
  });
}

function assertSpritesheet() {
  var result = this.result,
      namespace = this.namespace;

  // DEV: Write out to actual_files
  if (process.env.TEST_DEBUG) {
    try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) {}
    fs.writeFileSync(__dirname + '/actual_files/' + namespace + 'sprite.png', result.image, 'binary');
    fs.writeFileSync(__dirname + '/actual_files/' + namespace + 'sprite.jpg', result.image, 'binary');
    fs.writeFileSync(__dirname + '/actual_files/' + namespace + 'sprite.tiff', result.image, 'binary');
    fs.writeFileSync(__dirname + '/actual_files/' + namespace + 'coordinates.json', JSON.stringify(result.coordinates, null, 4));
    fs.writeFileSync(__dirname + '/actual_files/' + namespace + 'properties.json', JSON.stringify(result.properties, null, 4));
  }

  // Assert the actual image is the same expected
  var actualImage = result.image,
      expectedFilenames = ['canvas.png', 'gm.png', 'gm2.png', 'phantomjs.png', 'phantomjs2.png'],
      matchesAnImage = false;

  // ANTI-PATTERN: Looping over set without identifiable lines for stack traces
  expectedFilenames.forEach(function testAgainstExpected (filename) {
    if (!matchesAnImage) {
      var filepath = path.join(expectedDir, namespace + filename);
      if (fs.existsSync(filepath)) {
        var expectedImage = fs.readFileSync(filepath, 'binary');
        matchesAnImage = actualImage === expectedImage;
      }
    }
  });

  assert(matchesAnImage, "Actual image does not match expected image");
}

function assertCoordinates() {
  // Load in the coordinates
  var result = this.result,
      expectedCoords = require(expectedDir + '/' + this.namespace + 'coordinates.json');

  // Normalize the actual coordinates
  // TODO: Normalize dir should be an option
  var actualCoords = result.coordinates,
      normCoords = {};
  assert(actualCoords, "Result does not have a coordinates property");

  Object.getOwnPropertyNames(actualCoords).forEach(function (filepath) {
    var file = path.relative(spriteDir, filepath);
    normCoords[file] = actualCoords[filepath];
  });

  // Assert that the returned coordinates deep equal those in the coordinates.json
  assert.deepEqual(expectedCoords, normCoords, "Actual coordinates do not match expected coordinates");
}

function assertProps() {
  // Load in the properties
  var actualProps = this.result.properties,
      expectedProps = require(expectedDir + '/' + this.namespace + 'properties.json');

  // Assert that the returned properties equals the expected properties
  assert.deepEqual(expectedProps, actualProps, "Actual properties do not match expected properties");
}

describe('An array of sprites', function () {
  before(function setupSprites () {
    // DEV: These were unsorted for testing `sort: false` but these work for all tests as is =D
    this.sprites = [
      path.join(spriteDir, 'sprite1.png'),
      path.join(spriteDir, 'sprite3.png'),
      path.join(spriteDir, 'sprite2.jpg')
    ];

    // TODO: Namespace should be define in first item again to make tests randomizable
    // By default, write to the topDown namespace
    this.namespace = 'topDown.';
  });

  describe('when processed via spritesmith', function () {
    processViaSpritesmith();

    it('renders a top-down spritesheet', assertSpritesheet);
    it('has the proper coordinates', assertCoordinates);
    it('has the proper properties', assertProps);
  });

  describe('when converted from left to right', function () {
    before(function adjustToLeftRight () {
      this.namespace = 'leftRight.';
      this.options = {'algorithm': 'left-right'};
    });
    processViaSpritesmith();

    it('renders a left-right spritesheet', assertSpritesheet);
    it('has the proper coordinates', assertCoordinates);
    it('has the proper properties', assertProps);
  });

  describe('when provided with a padding parameter', function () {
    before(function addPaddingParams () {
      this.namespace = 'padding.';
      this.options = {'algorithm': 'binary-tree', 'padding': 2};
    });
    processViaSpritesmith();

    it('renders a padded spritesheet', assertSpritesheet);
    it('has the proper coordinates', assertCoordinates);
    it('has the proper properties', assertProps);
  });

  describe('when told not to sort', function () {
    before(function removeSorting () {
      this.namespace = 'unsorted.';
      this.options = {'algorithm': 'top-down', 'algorithmOpts': {'sort': false}};
    });
    processViaSpritesmith();

    it('renders an unsorted spritesheet', assertSpritesheet);
    it('has the proper coordinates', assertCoordinates);
    it('has the proper properties', assertProps);
  });
});

describe('An empty array', function () {
  before(function setupEmptySprites () {
    this.namespace = 'empty.';
    this.sprites = [];
  });

  describe('when processed via spritesmith', function () {
    it('renders an empty spritesheet', function () {

    });
    it('returns an empty coordinate mapping', function () {

    });
    it('has the proper properties', assertProps);
  });
});

function addEngineTest(engine) {
  // Attempt to load the engine
  try {
    require(engine);
  } catch (e) {}

  // Create an engine-specific test
  var outline = {};
  outline[engine] = [{
    'when processed via spritesmith': [
      'returns an image'
    ]
  }];

  // Add it to our list
  outlines.push(outline);
}

// TODO: Add this back
// // Test specific engines
// addEngineTest('phantomjssmith');
// addEngineTest('gmsmith');
// addEngineTest('canvassmith');

  // // Engine-specific setups
  // 'phantomjssmith': ['An array of sprites', function () {
  //   this.namespace = 'phantomjs.';
  //   this.options = {'engine': 'phantomjs'};
  // }],
  // 'gmsmith': ['An array of sprites', function () {
  //   this.namespace = 'gm.';
  //   this.options = {'engine': 'gm'};
  // }],
  // 'canvassmith': ['An array of sprites', function () {
  //   this.namespace = 'canvas.';
  //   this.options = {'engine': 'canvas'};
  // }],

  // Assertions
  'has the proper properties': 'assertProps',
  'returns an image': function () {
    // DEV: Write out to actual_files
    if (process.env.TEST_DEBUG) {
      try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) {}
      fs.writeFileSync(__dirname + '/actual_files/' + this.namespace + 'sprite.png', this.result.image, 'binary');
    }

    assert.notEqual(this.result.image, '');
  },
  'renders an empty spritesheet': function () {
    assert.strictEqual(this.result.image, '');
  },
  'returns an empty coordinate mapping': function () {
    assert.deepEqual(this.result.coordinates, {});
  },
};
<|endoftext|>


<commit_before>// Load in modules
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var async = require('async');
var concat = require('concat-stream');
var getPixels = require('get-pixels');
var Vinyl = require('vinyl');
var spritesmith = require('../src/smith.js');

// Set up paths
var spriteDir = path.join(__dirname, 'test_sprites');
var expectedDir = __dirname + '/expected_files';

// DEV: These were unsorted for testing `sort: false` but these work for all tests as is =D
var multipleSprites = [
  path.join(spriteDir, 'sprite1.png'),
  path.join(spriteDir, 'sprite3.png'),
  path.join(spriteDir, 'sprite2.jpg')
];

// Define common utilities
var spritesmithUtils = {
  run: function (params) {
    before(function runFn (done) {
      // Attempt to spritesmith out the sprites
      var that = this;
      var spriteData = spritesmith(params);
      async.parallel([
        function handleInfoStream (cb) {
          var errEncountered = false;
          spriteData.info.on('error', function saveInfoError (err) {
            errEncountered = true;
            that.infoErr = err;
            cb();
          });
          spriteData.info.pipe(concat(function saveInfo (infoArr) {
            that.infoArr = infoArr;
            if (!errEncountered) {
              cb();
            }
          }));
        },
        function handleImgStream (cb) {
          spriteData.img.on('error', function saveImgError (err) {
            that.imgErr = err;
            cb();
          });
          spriteData.img.pipe(concat(function saveImg (buff) {
            that.img = buff;
            cb();
          }));
        }
      ], done);
    });
    after(function cleanupResult () {
      delete this.infoErr;
      delete this.infoArr;
      delete this.imgErr;
      delete this.img;
    });
  },

  assertNoError: function () {
    return function assertNoErrorFn () {
      assert.strictEqual(this.imgErr, null);
      assert.strictEqual(this.infoErr, null);
    };
  },

  assertCoordinates: function (filename) {
    return function assertCoordinatesFn () {
      // Load in the coordinates
      assert.strictEqual(this.infoArr.length, 1);
      var result = this.infoArr[0];

      // DEV: Write out to actual_files
      if (process.env.TEST_DEBUG) {
        try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) {}
        fs.writeFileSync(__dirname + '/actual_files/' + filename, JSON.stringify(result.coordinates, null, 4));
      }

      // Normalize the actual coordinates
      var expectedCoords = require(expectedDir + '/' + filename);
      var actualCoords = result.coordinates;
      var normCoords = {};
      assert(actualCoords, 'Result does not have a coordinates property');

      Object.getOwnPropertyNames(actualCoords).forEach(function (filepath) {
        var file = path.relative(spriteDir, filepath);
        normCoords[file] = actualCoords[filepath];
      });

      // Assert that the returned coordinates deep equal those in the coordinates.json
      assert.deepEqual(expectedCoords, normCoords, 'Actual coordinates do not match expected coordinates');
    };
  },

  assertProps: function (filename) {
    return function assertPropsFn () {
      // Load in the properties
      assert.strictEqual(this.infoArr.length, 1);
      var result = this.infoArr[0];

      // DEV: Write out to actual_files
      if (process.env.TEST_DEBUG) {
        try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) {}
        fs.writeFileSync(__dirname + '/actual_files/' + filename, JSON.stringify(result.properties, null, 4));
      }

      // Assert that the returned properties equals the expected properties
      var actualProps = result.properties;
      var expectedProps = require(expectedDir + '/' + filename);
      assert.deepEqual(expectedProps, actualProps, 'Actual properties do not match expected properties');
    };
  },

  assertSpritesheet: function (filename) {
    return function assertSpritesheetFn (done) {
      // Load our variables
      var actualImageBuff = this.img;
      var expectedFilepath = path.join(expectedDir, filename);

      // DEV: Write out to actual_files
      if (process.env.TEST_DEBUG) {
        try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) {}
        fs.writeFileSync(__dirname + '/actual_files/' + filename, actualImageBuff);
      }

      // Assert the actual image is the same expected
      // DEV: We are using pngjs for decoding/encoding in the library but this is testing one more cycle
      getPixels(actualImageBuff, 'image/png', function handleActualPixels (err, actualImage) {
        if (err) { return done(err); }
        getPixels(expectedFilepath, function handleExpectedPixels (err, expectedImage) {
          if (err) { return done(err); }
          assert.deepEqual(actualImage, expectedImage, 'Actual image does not match expected image');
          done();
        });
      });
    };
  }
};

// Start our tests
describe('An array of sprites', function () {
  describe('when processed via spritesmith', function () {
    spritesmithUtils.run({
      src: multipleSprites
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a binary-tree spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('binaryTree.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('binaryTree.properties.json'));
  });

  describe('when converted from left to right', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      algorithm: 'left-right'
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a left-right spritesheet', spritesmithUtils.assertSpritesheet('leftRight.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('leftRight.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('leftRight.properties.json'));
  });

  describe('when provided with a padding parameter', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      algorithm: 'binary-tree',
      padding: 2
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a padded spritesheet', spritesmithUtils.assertSpritesheet('padding.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('padding.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('padding.properties.json'));
  });

  describe('when told not to sort', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      algorithm: 'top-down',
      algorithmOpts: {sort: false}
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders an unsorted spritesheet', spritesmithUtils.assertSpritesheet('unsorted.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('unsorted.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('unsorted.properties.json'));
  });
});

describe('An array of vinyl object sprites', function () {
  describe('when processed via spritesmith', function () {
    spritesmithUtils.run({
      src: multipleSprites.map(function createVinylObject (filepath) {
        return new Vinyl({
          path: filepath,
          contents: fs.readFileSync(filepath)
        });
      })
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a binary-tree spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('binaryTree.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('binaryTree.properties.json'));
  });
});

describe('An empty array', function () {
  var emptySprites = [];

  describe('when processed via spritesmith', function () {
    spritesmithUtils.run({
      src: emptySprites
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders an empty spritesheet', function () {
      assert.deepEqual(this.result.image, new Buffer(0));
    });
    it('returns an empty coordinate mapping', function () {
      assert.deepEqual(this.result.coordinates, {});
    });
    it('has the proper properties', spritesmithUtils.assertProps('empty.properties.json'));
  });
});

describe('`spritesmith` using a custom engine via string', function () {
  describe('processing a set of images', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      engine: 'phantomjssmith'
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.phantomjs.png'));
  });
});

describe('`spritesmith` using a custom engine via an object', function () {
  describe('processing a set of images', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      engine: require('phantomjssmith')
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.phantomjs.png'));
  });
});

// Edge cases
// Test for https://github.com/twolfson/gulp.spritesmith/issues/22
var canvassmith;
try {
  canvassmith = require('canvassmith');
} catch (err) {}
var describeIfCanvassmithExists = canvassmith ? describe : describe.skip;
describeIfCanvassmithExists('`spritesmith` using `canvassmith`', function () {
  describe('processing a bad image', function () {
    spritesmithUtils.run({
      src: [path.join(spriteDir, 'malformed.png')],
      engine: 'canvassmith'
    });

    it('calls back with an error', function () {
      assert.notEqual(this.err, null);
    });
  });
});
<commit_msg>Fixed up img double callback as well<commit_after>// Load in modules
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var async = require('async');
var concat = require('concat-stream');
var getPixels = require('get-pixels');
var Vinyl = require('vinyl');
var spritesmith = require('../src/smith.js');

// Set up paths
var spriteDir = path.join(__dirname, 'test_sprites');
var expectedDir = __dirname + '/expected_files';

// DEV: These were unsorted for testing `sort: false` but these work for all tests as is =D
var multipleSprites = [
  path.join(spriteDir, 'sprite1.png'),
  path.join(spriteDir, 'sprite3.png'),
  path.join(spriteDir, 'sprite2.jpg')
];

// Define common utilities
var spritesmithUtils = {
  run: function (params) {
    before(function runFn (done) {
      // Attempt to spritesmith out the sprites
      var that = this;
      var spriteData = spritesmith(params);
      async.parallel([
        function handleInfoStream (cb) {
          var errEncountered = false;
          spriteData.info.on('error', function saveInfoError (err) {
            errEncountered = true;
            that.infoErr = err;
            cb();
          });
          spriteData.info.pipe(concat(function saveInfo (infoArr) {
            that.infoArr = infoArr;
            if (!errEncountered) {
              cb();
            }
          }));
        },
        function handleImgStream (cb) {
          var errEncountered = false;
          spriteData.img.on('error', function saveImgError (err) {
            errEncountered = true;
            that.imgErr = err;
            cb();
          });
          spriteData.img.pipe(concat(function saveImg (buff) {
            that.img = buff;
            if (!errEncountered) {
              cb();
            }
          }));
        }
      ], done);
    });
    after(function cleanupResult () {
      delete this.infoErr;
      delete this.infoArr;
      delete this.imgErr;
      delete this.img;
    });
  },

  assertNoError: function () {
    return function assertNoErrorFn () {
      assert.strictEqual(this.imgErr, null);
      assert.strictEqual(this.infoErr, null);
    };
  },

  assertCoordinates: function (filename) {
    return function assertCoordinatesFn () {
      // Load in the coordinates
      assert.strictEqual(this.infoArr.length, 1);
      var result = this.infoArr[0];

      // DEV: Write out to actual_files
      if (process.env.TEST_DEBUG) {
        try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) {}
        fs.writeFileSync(__dirname + '/actual_files/' + filename, JSON.stringify(result.coordinates, null, 4));
      }

      // Normalize the actual coordinates
      var expectedCoords = require(expectedDir + '/' + filename);
      var actualCoords = result.coordinates;
      var normCoords = {};
      assert(actualCoords, 'Result does not have a coordinates property');

      Object.getOwnPropertyNames(actualCoords).forEach(function (filepath) {
        var file = path.relative(spriteDir, filepath);
        normCoords[file] = actualCoords[filepath];
      });

      // Assert that the returned coordinates deep equal those in the coordinates.json
      assert.deepEqual(expectedCoords, normCoords, 'Actual coordinates do not match expected coordinates');
    };
  },

  assertProps: function (filename) {
    return function assertPropsFn () {
      // Load in the properties
      assert.strictEqual(this.infoArr.length, 1);
      var result = this.infoArr[0];

      // DEV: Write out to actual_files
      if (process.env.TEST_DEBUG) {
        try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) {}
        fs.writeFileSync(__dirname + '/actual_files/' + filename, JSON.stringify(result.properties, null, 4));
      }

      // Assert that the returned properties equals the expected properties
      var actualProps = result.properties;
      var expectedProps = require(expectedDir + '/' + filename);
      assert.deepEqual(expectedProps, actualProps, 'Actual properties do not match expected properties');
    };
  },

  assertSpritesheet: function (filename) {
    return function assertSpritesheetFn (done) {
      // Load our variables
      var actualImageBuff = this.img;
      var expectedFilepath = path.join(expectedDir, filename);

      // DEV: Write out to actual_files
      if (process.env.TEST_DEBUG) {
        try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) {}
        fs.writeFileSync(__dirname + '/actual_files/' + filename, actualImageBuff);
      }

      // Assert the actual image is the same expected
      // DEV: We are using pngjs for decoding/encoding in the library but this is testing one more cycle
      getPixels(actualImageBuff, 'image/png', function handleActualPixels (err, actualImage) {
        if (err) { return done(err); }
        getPixels(expectedFilepath, function handleExpectedPixels (err, expectedImage) {
          if (err) { return done(err); }
          assert.deepEqual(actualImage, expectedImage, 'Actual image does not match expected image');
          done();
        });
      });
    };
  }
};

// Start our tests
describe('An array of sprites', function () {
  describe('when processed via spritesmith', function () {
    spritesmithUtils.run({
      src: multipleSprites
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a binary-tree spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('binaryTree.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('binaryTree.properties.json'));
  });

  describe('when converted from left to right', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      algorithm: 'left-right'
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a left-right spritesheet', spritesmithUtils.assertSpritesheet('leftRight.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('leftRight.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('leftRight.properties.json'));
  });

  describe('when provided with a padding parameter', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      algorithm: 'binary-tree',
      padding: 2
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a padded spritesheet', spritesmithUtils.assertSpritesheet('padding.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('padding.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('padding.properties.json'));
  });

  describe('when told not to sort', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      algorithm: 'top-down',
      algorithmOpts: {sort: false}
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders an unsorted spritesheet', spritesmithUtils.assertSpritesheet('unsorted.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('unsorted.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('unsorted.properties.json'));
  });
});

describe('An array of vinyl object sprites', function () {
  describe('when processed via spritesmith', function () {
    spritesmithUtils.run({
      src: multipleSprites.map(function createVinylObject (filepath) {
        return new Vinyl({
          path: filepath,
          contents: fs.readFileSync(filepath)
        });
      })
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a binary-tree spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.pixelsmith.png'));
    it('has the proper coordinates', spritesmithUtils.assertCoordinates('binaryTree.coordinates.json'));
    it('has the proper properties', spritesmithUtils.assertProps('binaryTree.properties.json'));
  });
});

describe('An empty array', function () {
  var emptySprites = [];

  describe('when processed via spritesmith', function () {
    spritesmithUtils.run({
      src: emptySprites
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders an empty spritesheet', function () {
      assert.deepEqual(this.result.image, new Buffer(0));
    });
    it('returns an empty coordinate mapping', function () {
      assert.deepEqual(this.result.coordinates, {});
    });
    it('has the proper properties', spritesmithUtils.assertProps('empty.properties.json'));
  });
});

describe('`spritesmith` using a custom engine via string', function () {
  describe('processing a set of images', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      engine: 'phantomjssmith'
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.phantomjs.png'));
  });
});

describe('`spritesmith` using a custom engine via an object', function () {
  describe('processing a set of images', function () {
    spritesmithUtils.run({
      src: multipleSprites,
      engine: require('phantomjssmith')
    });

    it('has no errors', spritesmithUtils.assertNoError());
    it('renders a spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.phantomjs.png'));
  });
});

// Edge cases
// Test for https://github.com/twolfson/gulp.spritesmith/issues/22
var canvassmith;
try {
  canvassmith = require('canvassmith');
} catch (err) {}
var describeIfCanvassmithExists = canvassmith ? describe : describe.skip;
describeIfCanvassmithExists('`spritesmith` using `canvassmith`', function () {
  describe('processing a bad image', function () {
    spritesmithUtils.run({
      src: [path.join(spriteDir, 'malformed.png')],
      engine: 'canvassmith'
    });

    it('calls back with an error', function () {
      assert.notEqual(this.err, null);
    });
  });
});
<|endoftext|>


<commit_before>var path = require('path')
var cp = require('child_process')
var _ = require('lodash')
var debug = require('debug')('thought:helpers')
var Promise = require('bluebird')
var glob = Promise.promisify(require('glob'))
var findPackage = require('find-package')
var Handlebars = require('handlebars')
var qfs = require('m-io/fs')
var util = require('util')
var Q = require('q')

module.exports = {

  /**
   * Display an object as indented JSON-String.
   * This is mainly for testing purposes when adapting templates
   * @param {object} obj the object
   * @returns {string} JSON.stringify()
   */
  json: function (obj) {
    return '```json\n' + JSON.stringify(obj, null, 2) + '\n```\n'
  },

  /**
   * Include a file into a markdown code-block
   * @param filename
   * @param language the programming language used for the code-block
   * @returns {string}
   */
  include: function (filename, language) {
    return qfs.read(filename).then(function (contents) {
      return '```' +
        (_.isString(language) ? language : path.extname(filename).substr(1)) +
        '\n' +
        contents +
        '\n```\n'
    })
  },

  /**
   * Directly include a file.
   * @param filename
   */
  includeRaw: function (filename) {
    return qfs.read(filename)
  },

  /**
   * Includes an example file into the template, replacing `require()` calls to the current
   * module by `require('module-name')` (only single-quotes are replaced)
   * If your file is `examples/example.js`, you would do
   * ```js
   * var fn = require('../')
   * ```
   * to load your module. That way, you get an executable script.
   * The helper will when include
   * ```js
   * var fn = require('module-name')
   * ```
   * in your docs, which is what a user of the module will do.
   *
   * @param {string} filename the name of the example file
   * @param {object} options.hash
   * @param {boolean} options.hash.snippet If set to true, only the lines between
   *    <snip> and </snip> will be included
   * @api public
   */
  example: function (filename, options) {
    return qfs.read(filename)
      .then(function (contents) {
        // Relative path to the current module (e.g. "../"). This path must be replaced
        // by the module name in the
        var modulePath = path.relative(path.dirname(filename), '.')
        debug('example modulepath', modulePath)
        var requireModuleRegex = new RegExp(regex`require\('${modulePath}/?(.*?)'\)`, 'g')
        if (options && options.hash && options.hash.snippet) {
          contents = contents.match(/---<snip>---.*\n([\S\s]*?)\n.*---<\/snip>---/)[1]
        }

        return util.format('```%s\n%s\n```',
          path.extname(filename).substr(1),
          contents.trim().replace(requireModuleRegex, function (match, suffix) {
            return `require('${require(process.cwd() + '/package').name}${suffix ? '/' + suffix : ''}')`
          })
        )
      })
  },

  /**
   * Return true if a file exists
   * @param {string} filename the path to the file
   * @return {boolean} true, if the file or diectory exists
   * @api public
   */
  exists: function (filename) {
    return qfs.exists(filename)
  },

  /**
   * Execute a command and include the output in a fenced code-block.
   *
   * @param {string} command the command, passed to `child-process#execSync()`
   * @param {object} options optional arguments and Handlebars internal args.
   * @param {string} options.hash.lang the language tag that should be attached to the fence
   *    (like `js` or `bash`). If this is set to `raw`, the output is included as-is, without fences.
   * @param {string} options.hash.cwd the current working directory of the example process
   * @returns {string} the output of `execSync`, enclosed in fences.
   * @api public
   */
  exec: function (command, options) {
    var start
    var end
    var lang = options.hash && options.hash.lang
    switch (lang) {
      case 'raw':
        start = end = ''
        break
      case 'inline':
        start = end = '`'
        break
      default:
        var fenceLanguage = _.isString(lang) ? lang : ''
        start = '```' + fenceLanguage + '\n'
        end = '\n```'
    }
    var output = cp.execSync(command, {
      encoding: 'utf8',
      cwd: options.hash && options.hash.cwd
    })
    return start + output.trim() + end
  },

  /**
   * Return a drawing of a directory tree (using [archy](https://www.npmjs.com/package/archy))
   * @param {string} globPattern a pattern describing all files and directories to include into the tree-view.
   * @param {string=} baseDir the base directory from which the `globPattern` is applied.
   * @returns {string} a display of the directory tree of the selected files and directories.
   * @api public
   */
  dirTree: function (baseDir, globPattern, options) {
    // Is basedir is not a string, it is probably the handlebars "options" object
    if (!_.isString(globPattern)) {
      globPattern = '**'
    }

    const label = options && options.data && options.data.label
    return glob(globPattern, {cwd: baseDir, mark: true})
      .then((files) => {
        debug('dirTree glob result', files)
        if (files.length === 0) {
          throw new Error('Cannot find a single file for \'' + globPattern + '\' in \'' + baseDir + '\'')
        }
        files.sort()
        // Split paths into components
        const pathComponents = files.map((file) => {
          // a/b/c  or a/b/dir/
          return file.split(path.sep)
          // a, b, c  or  a, b, dir, ''
            .map((component, index, all) => component + (index < all.length - 1 ? '/' : ''))
            // a/, b/, c  or  a/, b/, dir/, ''
            .filter(component => component) // Filter empty parts
        })
        const treeObject = treeFromPathComponents(pathComponents, label)
        const tree = require('archy')(treeObject)
        return '
\n' + tree.trim() + '\n
'
      })
  },

  /**
   * Render an object hierarchy of the form
   *
   * ```
   * {
   *   prop1: 'value',
   *   prop2: 'value',
   *   ...,
   *   children: [
   *     {
   *        prop1: 'value',
   *        propt2: 'value',
   *        ...,
   *        children: ...
   *     }
   *   ]
   * }
   * ```
   * @param object
   * @param options
   * @param {function} options.fn computes the label for a node based on the node itself
   * @returns {string}
   */
  renderTree: function (object, options) {
    var tree = require('archy')(transformTree(object, options.fn))
    return '
\n' + tree + '
'
  },

  /**
   * Block helper that executes the block in the current context but sets to special variables:
   *
   * * The github-url of the given file in the current package version is stored into `@url`
   * * The `package.json` of the file's module is stored into `@package`
   * @param filePath file that is used to find that package.json
   * @param options block-helper options
   */
  withPackageOf: function (filePath, options) {
    const data = Handlebars.createFrame(options.data)
    data.url = githubUrl(filePath)
    data.package = findPackage(path.resolve(filePath), false)
    return options.fn(this, {data: data})
  },

  github: function (filePath) {
    return githubUrl(filePath)
  },

  /**
   * Returns the path to the github repository (below github.com) based on the $.repository.url.
   * @param options
   * @returns {string=} the repository path within github.com (or null)
   */
  githubRepo: githubRepo,

  /**
   * Create a link to the npm-package of a package
   * @param packageName the name of the package
   */
  npm: function (packageName) {
    return '[' + packageName + '](https://npmjs.com/package/' + packageName + ')'
  },

  /**
   * Replace all characters that may not be used in HTML id-attributes by '-'.
   * There is still the restriction that IDs may only start with letters, which
   * is not addressed by this helper.
   */
  'htmlId': function (value) {
    return value.replace(/[^A-Za-z0-9-_:.]/g, '').toLowerCase()
  },

  /**
   * Check the .travis.yml and the appveyor.yml files for the string 'coveralls'
   * and return true if any of them exists and contains the string.
   * We expect coveralls to be configured then
   */
  'hasCoveralls': function hasCoveralls () {
    var travis = qfs.read('.travis.yml')
    var appveyor = qfs.read('appveyor.yml')
    return Q.allSettled([travis, appveyor]).then(function (files) {
      var i
      for (i = 0; i < files.length; i++) {
        if (files[i].state === 'fulfilled' && files[i].value.indexOf('coveralls') >= 0) {
          return true
        }
      }
      return false
    })
  },

  'hasGreenkeeper': function hasGreenkeeper (options) {
    var slug = githubRepo(options)
    return require('request-promise')(`https://badges.greenkeeper.io/${slug}.svg`)
      .then(function (response) {
        return require('cheerio')(response).find('text').last().text() !== 'not found'
      })
  }

}

/**
 * Transform a tree-structure of the form
 * ```
 * {
 *   prop1: 'value',
 *   prop2: 'value',
 *   ...,
 *   children: [
 *     {
 *        prop1: 'value',
 *        propt2: 'value',
 *        ...,
 *        children: ...
 *     }
 *   ]
 * }
 * ```
 * Into an [archy](https://www.npmjs.com/package/archy)-compatible format, by passing each node to a block-helper function.
 * The result of the function should be a string which is then used as label for the node.
 *
 * @param {object} object the tree data
 * @param fn the block-helper function (options.fn) of Handlebars (http://handlebarsjs.com/block_helpers.html)
 */
function transformTree (object, fn) {
  var label = fn(object).trim()
  if (object.children) {
    return {
      label: label,
      nodes: object.children.map(function (child) {
        return transformTree(child, fn)
      })
    }
  } else {
    return label
  }
}

/**
 * Transform an array of path components into an [archy](https://www.npmjs.com/package/archy)-compatible tree structure.
 *
 * ```
 * [ [ 'abc', 'cde', 'efg' ], [ 'abc','cde','abc'], ['abc','zyx'] ]
 * ```
 *
 * becomes
 *
 * ```
 * {
 *   label: 'abc',
 *   nodes: [
        {
          label: 'cde',
          nodes: [
            'efg',
            'abc'
          ]
        },
        'zyx'
 *   ]
 * }
 * ```
 *
 * Nodes with a single subnode are collapsed and the resulting node gets the label `node/subnode`.
 *
 * @param {string[][]} files an array of filenames, split by `path.sep`
 * @param {string} label the label for the current tree node
 * @returns {object} a tree structure as needed by [archy](https://www.npmjs.com/package/archy)
 */
function treeFromPathComponents (files, label) {
  debug('treeFromPathComponents', files, label)
  if (files.length === 0) {
    return label
  }
  var result = {
    label: label,
    nodes: _(files)
      .groupBy('0')
      .map(function (group, key) {
        var values = group
          .map(function (item) {
            return item.slice(1)
          })
          .filter(function (item) {
            return item.length > 0
          })
        return treeFromPathComponents(values, key)
      }).value()
  }

  // Condense path if directory only has one entry
  if (result.nodes.length === 1 && _.isPlainObject(result.nodes[0])) {
    return {
      label: (result.label || '') + result.nodes[0].label,
      nodes: result.nodes[0].nodes
    }
  } else {
    return result
  }
}

function githubUrl (filePath) {
  // Build url to correct version and file in github
  var packageJson = findPackage(path.resolve(filePath), true)
  var url = packageJson && packageJson.repository && packageJson.repository.url
  if (url && url.match(/github.com/)) {
    var version = packageJson.version
    // path within the package
    var relativePath = path.relative(path.dirname(packageJson.paths.absolute), filePath)
    return url.replace(/^git\+/, '').replace(/\.git$/, '') + '/blob/v' + version + '/' + relativePath
  }
}

function githubRepo (options) {
  try {
    var url = options.data.root.package.repository.url
    var match = url.match(/.*?(:\/\/|@)github\.com[/:](.*?)(#.*?)?$/)
    if (match) {
      return match[2].replace(/\.git$/, '')
    } else {
      return null
    }
  } catch (e) {
    // No repositor-url exists
    return null
  }
}

function regex (strings, ...args) {
  return String.raw(strings, ...args.map(_.escapeRegExp))
}
<commit_msg>BREAKING: Change "htmlId"-helper to match the GitHub way of creating url-hashes<commit_after>var path = require('path')
var cp = require('child_process')
var _ = require('lodash')
var debug = require('debug')('thought:helpers')
var Promise = require('bluebird')
var glob = Promise.promisify(require('glob'))
var findPackage = require('find-package')
var Handlebars = require('handlebars')
var qfs = require('m-io/fs')
var util = require('util')
var Q = require('q')

module.exports = {

  /**
   * Display an object as indented JSON-String.
   * This is mainly for testing purposes when adapting templates
   * @param {object} obj the object
   * @returns {string} JSON.stringify()
   */
  json: function (obj) {
    return '```json\n' + JSON.stringify(obj, null, 2) + '\n```\n'
  },

  /**
   * Include a file into a markdown code-block
   * @param filename
   * @param language the programming language used for the code-block
   * @returns {string}
   */
  include: function (filename, language) {
    return qfs.read(filename).then(function (contents) {
      return '```' +
        (_.isString(language) ? language : path.extname(filename).substr(1)) +
        '\n' +
        contents +
        '\n```\n'
    })
  },

  /**
   * Directly include a file.
   * @param filename
   */
  includeRaw: function (filename) {
    return qfs.read(filename)
  },

  /**
   * Includes an example file into the template, replacing `require()` calls to the current
   * module by `require('module-name')` (only single-quotes are replaced)
   * If your file is `examples/example.js`, you would do
   * ```js
   * var fn = require('../')
   * ```
   * to load your module. That way, you get an executable script.
   * The helper will when include
   * ```js
   * var fn = require('module-name')
   * ```
   * in your docs, which is what a user of the module will do.
   *
   * @param {string} filename the name of the example file
   * @param {object} options.hash
   * @param {boolean} options.hash.snippet If set to true, only the lines between
   *    <snip> and </snip> will be included
   * @api public
   */
  example: function (filename, options) {
    return qfs.read(filename)
      .then(function (contents) {
        // Relative path to the current module (e.g. "../"). This path must be replaced
        // by the module name in the
        var modulePath = path.relative(path.dirname(filename), '.')
        debug('example modulepath', modulePath)
        var requireModuleRegex = new RegExp(regex`require\('${modulePath}/?(.*?)'\)`, 'g')
        if (options && options.hash && options.hash.snippet) {
          contents = contents.match(/---<snip>---.*\n([\S\s]*?)\n.*---<\/snip>---/)[1]
        }

        return util.format('```%s\n%s\n```',
          path.extname(filename).substr(1),
          contents.trim().replace(requireModuleRegex, function (match, suffix) {
            return `require('${require(process.cwd() + '/package').name}${suffix ? '/' + suffix : ''}')`
          })
        )
      })
  },

  /**
   * Return true if a file exists
   * @param {string} filename the path to the file
   * @return {boolean} true, if the file or diectory exists
   * @api public
   */
  exists: function (filename) {
    return qfs.exists(filename)
  },

  /**
   * Execute a command and include the output in a fenced code-block.
   *
   * @param {string} command the command, passed to `child-process#execSync()`
   * @param {object} options optional arguments and Handlebars internal args.
   * @param {string} options.hash.lang the language tag that should be attached to the fence
   *    (like `js` or `bash`). If this is set to `raw`, the output is included as-is, without fences.
   * @param {string} options.hash.cwd the current working directory of the example process
   * @returns {string} the output of `execSync`, enclosed in fences.
   * @api public
   */
  exec: function (command, options) {
    var start
    var end
    var lang = options.hash && options.hash.lang
    switch (lang) {
      case 'raw':
        start = end = ''
        break
      case 'inline':
        start = end = '`'
        break
      default:
        var fenceLanguage = _.isString(lang) ? lang : ''
        start = '```' + fenceLanguage + '\n'
        end = '\n```'
    }
    var output = cp.execSync(command, {
      encoding: 'utf8',
      cwd: options.hash && options.hash.cwd
    })
    return start + output.trim() + end
  },

  /**
   * Return a drawing of a directory tree (using [archy](https://www.npmjs.com/package/archy))
   * @param {string} globPattern a pattern describing all files and directories to include into the tree-view.
   * @param {string=} baseDir the base directory from which the `globPattern` is applied.
   * @returns {string} a display of the directory tree of the selected files and directories.
   * @api public
   */
  dirTree: function (baseDir, globPattern, options) {
    // Is basedir is not a string, it is probably the handlebars "options" object
    if (!_.isString(globPattern)) {
      globPattern = '**'
    }

    const label = options && options.data && options.data.label
    return glob(globPattern, {cwd: baseDir, mark: true})
      .then((files) => {
        debug('dirTree glob result', files)
        if (files.length === 0) {
          throw new Error('Cannot find a single file for \'' + globPattern + '\' in \'' + baseDir + '\'')
        }
        files.sort()
        // Split paths into components
        const pathComponents = files.map((file) => {
          // a/b/c  or a/b/dir/
          return file.split(path.sep)
          // a, b, c  or  a, b, dir, ''
            .map((component, index, all) => component + (index < all.length - 1 ? '/' : ''))
            // a/, b/, c  or  a/, b/, dir/, ''
            .filter(component => component) // Filter empty parts
        })
        const treeObject = treeFromPathComponents(pathComponents, label)
        const tree = require('archy')(treeObject)
        return '
\n' + tree.trim() + '\n
'
      })
  },

  /**
   * Render an object hierarchy of the form
   *
   * ```
   * {
   *   prop1: 'value',
   *   prop2: 'value',
   *   ...,
   *   children: [
   *     {
   *        prop1: 'value',
   *        propt2: 'value',
   *        ...,
   *        children: ...
   *     }
   *   ]
   * }
   * ```
   * @param object
   * @param options
   * @param {function} options.fn computes the label for a node based on the node itself
   * @returns {string}
   */
  renderTree: function (object, options) {
    var tree = require('archy')(transformTree(object, options.fn))
    return '
\n' + tree + '
'
  },

  /**
   * Block helper that executes the block in the current context but sets to special variables:
   *
   * * The github-url of the given file in the current package version is stored into `@url`
   * * The `package.json` of the file's module is stored into `@package`
   * @param filePath file that is used to find that package.json
   * @param options block-helper options
   */
  withPackageOf: function (filePath, options) {
    const data = Handlebars.createFrame(options.data)
    data.url = githubUrl(filePath)
    data.package = findPackage(path.resolve(filePath), false)
    return options.fn(this, {data: data})
  },

  github: function (filePath) {
    return githubUrl(filePath)
  },

  /**
   * Returns the path to the github repository (below github.com) based on the $.repository.url.
   * @param options
   * @returns {string=} the repository path within github.com (or null)
   */
  githubRepo: githubRepo,

  /**
   * Create a link to the npm-package of a package
   * @param packageName the name of the package
   */
  npm: function (packageName) {
    return '[' + packageName + '](https://npmjs.com/package/' + packageName + ')'
  },

  /**
   * This helper creates valid html-ids from header name. It attempts to
   * follow the same rules that github uses to convert header names (h1, h2) into the
   * hash-part of the URL referencing this header.
   *
   * ```js
   * htmlId('abc') === 'abc'
   * htmlId('abc cde') === 'abc-cde' // Replace spaces by '-'
   * htmlId('a$b&c%d') === 'abcd'  // Remove all characters execpt alpahnumericals and minus
   * htmlId('mäxchen' === 'mäxchen' // Do not remove german umlauts
   * htmlId('ハッピークリスマス') === 'ハッピークリスマス' // Do not remove japanase word characters
   * htmlId('ABCDE') === 'abcde'   // Convert to lowercase
   * ```
   */
  'htmlId': function (value) {
    // see http://stackoverflow.com/questions/19001140/amend-regular-expression-to-allow-german-umlauts-french-accents-and-other-valid
    // and https://github.com/mathiasbynens/unicode-data/blob/17a920119b8015c6f7fe922ee7ab9fe29bef4394/4.0.1/blocks/Katakana-regex.js
    // There are probably a lot of characters missing. Please make a PR, if you want to include more ranges in the valid characters.
    // Make sure to add a test-case
    var validChars = /[^A-Za-z0-9-_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u30A0-\u30FF]/g
    return value
      .replace(/ /g, '-')
      .replace(validChars, '')
      .toLowerCase()
  },

  /**
   * Check the .travis.yml and the appveyor.yml files for the string 'coveralls'
   * and return true if any of them exists and contains the string.
   * We expect coveralls to be configured then
   */
  'hasCoveralls': function hasCoveralls () {
    var travis = qfs.read('.travis.yml')
    var appveyor = qfs.read('appveyor.yml')
    return Q.allSettled([travis, appveyor]).then(function (files) {
      var i
      for (i = 0; i < files.length; i++) {
        if (files[i].state === 'fulfilled' && files[i].value.indexOf('coveralls') >= 0) {
          return true
        }
      }
      return false
    })
  },

  'hasGreenkeeper': function hasGreenkeeper (options) {
    var slug = githubRepo(options)
    return require('request-promise')(`https://badges.greenkeeper.io/${slug}.svg`)
      .then(function (response) {
        return require('cheerio')(response).find('text').last().text() !== 'not found'
      })
  }

}

/**
 * Transform a tree-structure of the form
 * ```
 * {
 *   prop1: 'value',
 *   prop2: 'value',
 *   ...,
 *   children: [
 *     {
 *        prop1: 'value',
 *        propt2: 'value',
 *        ...,
 *        children: ...
 *     }
 *   ]
 * }
 * ```
 * Into an [archy](https://www.npmjs.com/package/archy)-compatible format, by passing each node to a block-helper function.
 * The result of the function should be a string which is then used as label for the node.
 *
 * @param {object} object the tree data
 * @param fn the block-helper function (options.fn) of Handlebars (http://handlebarsjs.com/block_helpers.html)
 */
function transformTree (object, fn) {
  var label = fn(object).trim()
  if (object.children) {
    return {
      label: label,
      nodes: object.children.map(function (child) {
        return transformTree(child, fn)
      })
    }
  } else {
    return label
  }
}

/**
 * Transform an array of path components into an [archy](https://www.npmjs.com/package/archy)-compatible tree structure.
 *
 * ```
 * [ [ 'abc', 'cde', 'efg' ], [ 'abc','cde','abc'], ['abc','zyx'] ]
 * ```
 *
 * becomes
 *
 * ```
 * {
 *   label: 'abc',
 *   nodes: [
        {
          label: 'cde',
          nodes: [
            'efg',
            'abc'
          ]
        },
        'zyx'
 *   ]
 * }
 * ```
 *
 * Nodes with a single subnode are collapsed and the resulting node gets the label `node/subnode`.
 *
 * @param {string[][]} files an array of filenames, split by `path.sep`
 * @param {string} label the label for the current tree node
 * @returns {object} a tree structure as needed by [archy](https://www.npmjs.com/package/archy)
 */
function treeFromPathComponents (files, label) {
  debug('treeFromPathComponents', files, label)
  if (files.length === 0) {
    return label
  }
  var result = {
    label: label,
    nodes: _(files)
      .groupBy('0')
      .map(function (group, key) {
        var values = group
          .map(function (item) {
            return item.slice(1)
          })
          .filter(function (item) {
            return item.length > 0
          })
        return treeFromPathComponents(values, key)
      }).value()
  }

  // Condense path if directory only has one entry
  if (result.nodes.length === 1 && _.isPlainObject(result.nodes[0])) {
    return {
      label: (result.label || '') + result.nodes[0].label,
      nodes: result.nodes[0].nodes
    }
  } else {
    return result
  }
}

function githubUrl (filePath) {
  // Build url to correct version and file in github
  var packageJson = findPackage(path.resolve(filePath), true)
  var url = packageJson && packageJson.repository && packageJson.repository.url
  if (url && url.match(/github.com/)) {
    var version = packageJson.version
    // path within the package
    var relativePath = path.relative(path.dirname(packageJson.paths.absolute), filePath)
    return url.replace(/^git\+/, '').replace(/\.git$/, '') + '/blob/v' + version + '/' + relativePath
  }
}

function githubRepo (options) {
  try {
    var url = options.data.root.package.repository.url
    var match = url.match(/.*?(:\/\/|@)github\.com[/:](.*?)(#.*?)?$/)
    if (match) {
      return match[2].replace(/\.git$/, '')
    } else {
      return null
    }
  } catch (e) {
    // No repositor-url exists
    return null
  }
}

function regex (strings, ...args) {
  return String.raw(strings, ...args.map(_.escapeRegExp))
}
<|endoftext|>


<commit_before>import test from 'ava'

import { ConfigService } from '../../../src/services/config'

test('test', t => {
  t.deepEqual([1, 2], [1, 2])
})
<commit_msg>Add tests for configFileExist & configData methods<commit_after>import test, { beforeEach, afterEach } from 'ava'
import fs from 'fs'
import path from 'path'
import rimraf from 'rimraf'

import { ConfigService, ConfigurationFileNotExist } from '../../../src/services/config'

const tmpData = path.join(__dirname, './data')
let configService

beforeEach('init temp files for tests', t => {
  fs.mkdirSync(tmpData)
})

beforeEach('init service', t => {
  configService = new ConfigService(path.join(tmpData, 'configfile'))
})

afterEach.always('clean temp files', t => {
  rimraf.sync(tmpData)
})

test('configFileExist() should return false when main configuration file does not exist', t => {
  t.false(configService.configFileExist())
})

test('configFileExist() should return true when main configuration file exist', t => {
  fs.writeFileSync(configService.configPath, JSON.stringify({}))
  t.true(configService.configFileExist())
})

test('configData() should return good data when main configuration file exist', t => {
  fs.writeFileSync(configService.configPath, JSON.stringify({ 'repo_url': 'https://', 'folder_path': './' }))
  t.deepEqual(configService.configData(), { 'folder_path': './', 'repo_url': 'https://' })
})

test('configData() should throw an error when main configuration file exist', t => {
  t.throws(() => configService.configData(), ConfigurationFileNotExist)
})
<|endoftext|>


<commit_before>// Dependencies.
import interact from "interactjs";
import Cast from "../helpers/cast";
import Events from "../helpers/events";
import Grid from "../helpers/grid";

export default {
  bind: function(element, binding, vnode) {
    interact(element)
      .dropzone({
        ondragenter: function() {
          element.classList.add("droppable");
        },
        ondragleave: function() {
          element.classList.remove("droppable");
        },
        ondrop: function(event) {
          // Convert moved pixels to change in timestamp.
          let change = Math.round(
            (event.dragEvent.dx / vnode.context.$root.$el.clientWidth) *
              vnode.context.state.time.duration()
          );

          // Build object.
          let booking = {
            resource: vnode.context.id,
            start: Cast.date(event.relatedTarget.__vue__.start + change),
            end: Cast.date(event.relatedTarget.__vue__.end + change)
          };

          // Create copy if ALT key is pressed, otherwise edit existing.
          if (event.dragEvent.altKey) {
            // Emit event to create booking.
            Events.$emit("bookings-create", booking);
          } else {
            // Append ID of moved booking.
            booking.id = event.relatedTarget.__vue__.id;

            // Emit event to update booking.
            Events.$emit("bookings-update", booking);
          }

          element.classList.remove("droppable");
        }
      })
      .on("doubletap", function(event) {
        // Only listen to clicks directly on the resource, not bookings.
        if (event.target !== element) {
          event.stopPropagation();
          return;
        }

        let position = Math.round(
          (event.offsetX / vnode.context.$root.$el.clientWidth) *
            vnode.context.state.time.duration()
        );
        let stepSize =
          vnode.context.state.time.duration() /
          vnode.context.state.config.steps;
        let start = Grid.round(
          position + vnode.context.state.time.start,
          vnode.context.state.time.duration(),
          vnode.context.state.config.steps
        );

        // Emit event to create booking.
        Events.$emit("bookings-create", {
          resource: vnode.context.id,
          start: start,
          end: start + stepSize * 2
        });
      });
  },
  unbind: function(element) {
    interact(element).unset();
  }
};
<commit_msg>Make sure start and end values are integers.<commit_after>// Dependencies.
import interact from "interactjs";
import Cast from "../helpers/cast";
import Events from "../helpers/events";
import Grid from "../helpers/grid";

export default {
  bind: function(element, binding, vnode) {
    interact(element)
      .dropzone({
        ondragenter: function() {
          element.classList.add("droppable");
        },
        ondragleave: function() {
          element.classList.remove("droppable");
        },
        ondrop: function(event) {
          // Convert moved pixels to change in timestamp.
          let change = Math.round(
            (event.dragEvent.dx / vnode.context.$root.$el.clientWidth) *
              vnode.context.state.time.duration()
          );

          // Build object.
          let booking = {
            resource: vnode.context.id,
            start: Cast.date(
              parseInt(event.relatedTarget.__vue__.start) + change
            ),
            end: Cast.date(parseInt(event.relatedTarget.__vue__.end) + change)
          };

          // Create copy if ALT key is pressed, otherwise edit existing.
          if (event.dragEvent.altKey) {
            // Emit event to create booking.
            Events.$emit("bookings-create", booking);
          } else {
            // Append ID of moved booking.
            booking.id = event.relatedTarget.__vue__.id;

            // Emit event to update booking.
            Events.$emit("bookings-update", booking);
          }

          element.classList.remove("droppable");
        }
      })
      .on("doubletap", function(event) {
        // Only listen to clicks directly on the resource, not bookings.
        if (event.target !== element) {
          event.stopPropagation();
          return;
        }

        let position = Math.round(
          (event.offsetX / vnode.context.$root.$el.clientWidth) *
            vnode.context.state.time.duration()
        );
        let stepSize =
          vnode.context.state.time.duration() /
          vnode.context.state.config.steps;
        let start = Grid.round(
          position + vnode.context.state.time.start,
          vnode.context.state.time.duration(),
          vnode.context.state.config.steps
        );

        // Emit event to create booking.
        Events.$emit("bookings-create", {
          resource: vnode.context.id,
          start: start,
          end: start + stepSize * 2
        });
      });
  },
  unbind: function(element) {
    interact(element).unset();
  }
};
<|endoftext|>