<commit_before>// Copyright 2013-2016, University of Colorado Boulder

/**
 * Utility functions for scenery that are specifically useful for Accessibility.
 * These generally pertain to DOM traversal and manipulation.
 *
 * @author Jesse Greenberg
 */

define( function( require ) {
  'use strict';

  // modules
  var scenery = require( 'SCENERY/scenery' );
  var Random = require( 'DOT/Random' );

  // constants
  var NEXT = 'NEXT';
  var PREVIOUS = 'PREVIOUS';

  // HTML tag names
  var INPUT_TAG = 'INPUT';
  var LABEL_TAG = 'LABEL';
  var BUTTON_TAG = 'BUTTON';
  var TEXTAREA_TAG = 'TEXTAREA';
  var SELECT_TAG = 'SELECT';
  var OPTGROUP_TAG = 'OPTGROUP';
  var DATALIST_TAG = 'DATALIST';
  var OUTPUT_TAG = 'OUTPUT';
  var DIV_TAG = 'DIV';
  var A_TAG = 'A';
  var AREA_TAG = 'A';
  var P_TAG = 'P';
  var IFRAME_TAG = 'IFRAME';

  // tag names with special behavior
  var BOLD_TAG = 'B';
  var STRONG_TAG = 'STRONG';
  var I_TAG = 'I';
  var EM_TAG = 'EM';
  var MARK_TAG = 'MARK';
  var SMALL_TAG = 'SMALL';
  var DEL_TAG = 'DEL';
  var INS_TAG = 'INS';
  var SUB_TAG = 'SUB';
  var SUP_TAG = 'SUP';

  // These browser tags are a definition of default focusable elements, converted from Javascript types,
  // see https://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus
  var DEFAULT_FOCUSABLE_TAGS = [ A_TAG, AREA_TAG, INPUT_TAG, SELECT_TAG, TEXTAREA_TAG, BUTTON_TAG, IFRAME_TAG ];

  // collection of tags that are used for formatting text
  var FORMATTING_TAGS = [ BOLD_TAG, STRONG_TAG, I_TAG, EM_TAG, MARK_TAG, SMALL_TAG, DEL_TAG, INS_TAG, SUB_TAG, SUP_TAG ];

  // these elements do not have a closing tag, so they won't support features like innerHTML. This is how PhET treats
  // these elements, not necessary what is legal html.
  var ELEMENTS_WITHOUT_CLOSING_TAG = [ INPUT_TAG ];

  // valid types of DOM events that can be added to a node
  var DOM_EVENTS = [ 'input', 'change', 'click', 'keydown', 'keyup', 'focus', 'blur' ];

  /**
   * Get all 'element' nodes off the parent element, placing them in an array for easy traversal.  Note that this
   * includes all elements, even those that are 'hidden' or purely for structure.
   *
   * @param  {HTMLElement} domElement - parent whose children will be linearized
   * @returns {HTMLElement[]}
   * @private
   */
  function getLinearDOMElements( domElement ) {

    // gets ALL descendant children for the element
    var children = domElement.getElementsByTagName( '*' );

    var linearDOM = [];
    for ( var i = 0; i < children.length; i++ ) {

      // searching for the HTML element nodes (NOT Scenery nodes)
      if ( children[ i ].nodeType === Node.ELEMENT_NODE ) {
        linearDOM[ i ] = ( children[ i ] );
      }
    }
    return linearDOM;
  }

  /**
   * Determine if an element is hidden.  An element is considered 'hidden' if it (or any of its ancestors) has the
   * 'hidden' attribute.
   * @private
   *
   * @param {HTMLElement} domElement
   * @returns {Boolean}
   */
  function isElementHidden( domElement ) {
    if ( domElement.hidden ) {
      return true;
    }
    else if ( domElement === document.body ) {
      return false;
    }
    else {
      return isElementHidden( domElement.parentElement );
    }
  }

  /**
   * Get the next or previous focusable element in the parallel DOM, relative to this Node's primarySibling
   * depending on the direction. Useful if you need to set focus dynamically or need to prevent default behavior
   * when focus changes. If no next or previous focusable is found, it returns the currently focused element.
   * This function should not be used directly, use getNextFocusable() or getPreviousFocusable() instead.
   * @private
   *
   * @param {string} direction - direction of traversal, one of 'NEXT' | 'PREVIOUS'
   * @param {HTMLElement} [parentElement] - optional, search will be limited to children of this element
   * @returns {HTMLElement}
   */
  function getNextPreviousFocusable( direction, parentElement ) {

    // linearize the document [or the desired parent] for traversal
    var parent = parentElement || document.body;
    var linearDOM = getLinearDOMElements( parent );

    var activeElement = document.activeElement;
    var activeIndex = linearDOM.indexOf( activeElement );
    var delta = direction === NEXT ? +1 : -1;

    // find the next focusable element in the DOM
    var nextIndex = activeIndex + delta;
    while ( nextIndex < linearDOM.length && nextIndex >= 0 ) {
      var nextElement = linearDOM[ nextIndex ];
      nextIndex += delta;

      if ( isElementFocusable( nextElement ) ) {
        return nextElement;
      }
    }

    // if no next focusable is found, return the active DOM element
    return activeElement;
  }

  /**
   * Returns true if the element is focusable. Assumes that all focusable  elements have tabIndex >= 0, which
   * is only true for elements of the Parallel DOM.
   *
   * @param {HTMLElement} domElement
   * @return {boolean}
   */
  function isElementFocusable( domElement ) {

    // continue to next element if this one is meant to be hidden
    if ( isElementHidden( domElement ) ) {
      return false;
    }

    // if element is for formatting, skipe over it - required since IE gives these tabindex="0"
    if ( _.includes( FORMATTING_TAGS, domElement.tagName ) ) {
      return false;
    }

    // if tabindex is greater than -1, the element is focusable so break
    return domElement.tabIndex >= 0;
  }

  /**
   * Trims the white space from the left of the string.
   * Solution from https://stackoverflow.com/questions/1593859/left-trim-in-javascript
   * @param  {string} string
   * @return {string}
   */
  function trimLeft( string ) {

    // ^ - from the beginning of the string
    // \s - whitespace character
    // + - greedy
    return string.replace( /^\s+/, '' );
  }

  /**
   * Returns whether or not the tagName supports innerHTML or textContent in PhET.
   * @private
   * @param {string} tagName
   * @returns {boolean}
   */
  function tagNameSupportsContent( tagName ) {
    return !_.includes( ELEMENTS_WITHOUT_CLOSING_TAG, tagName.toUpperCase() );
  }

  var AccessibilityUtil = {

    /**
     * Get the next focusable element. This should very rarely be used.  The next focusable element can almost
     * always be focused automatically with 'Tab'.  However, if the 'Tab' key needs to be emulated this can be
     * helpful. If no next focusable can be found, it will return the currently focused element.
     * @public
     *
     * @param{HTMLElement} [parentElement] - optional, search will be limited to elements under this element
     * @returns {HTMLElement}
     */
    getNextFocusable: function( parentElement ) {
      return getNextPreviousFocusable( NEXT, parentElement );
    },

    /**
     * Get the previous focusable element. This should very rarely be used.  The previous focusable element can almost
     * always be found automatically with default 'Shift+Tab' behavior.  However, if the 'Tab' key needs to be emulated
     * this can be helpful.  If no next focusable can be found, it will return the currently focused element.
     * @public
     *
     * @param {HTMLElement} [parentElement] - optional, search will be limited to elements under this parent
     * @returns {HTMLElement}
     */
    getPreviousFocusable: function( parentElement ) {
      return getNextPreviousFocusable( PREVIOUS, parentElement );
    },

    /**
     * Return a random focusable element in the document. Particularly useful for fuzz testing.
     *
     * @return {HTMLElement}
     */
    getRandomFocusable: function() {

      var linearDOM = getLinearDOMElements( document.body );
      var focusableElements = [];
      for ( var i = 0; i < linearDOM.length; i++ ) {
        isElementFocusable( linearDOM[ i ] ) && focusableElements.push( linearDOM[ i ] );
      }

      return focusableElements[ new Random().nextInt( focusableElements.length ) ];
    },

    /**
     * If the textContent has any tags that are not formatting tags, return false. Only checking for
     * tags that are not in the whitelist FORMATTING_TAGS, if there are no tags this will still return
     * true.
     * @public
     *
     * @param  {string} textContent
     * @returns {boolean}
     */
    usesExclusivelyFormattingTags: function( textContent ) {

      // no-op for null case
      if ( textContent === null ) {
        return false;
      }
      assert && assert( typeof textContent === 'string', 'unsupported type for textContent.' );

      var i = 0;
      var openIndices = [];
      var closeIndices = [];

      // find open/close tag pairs in the text content
      while ( i < textContent.length ) {
        var openIndex = textContent.indexOf( '<', i );
        var closeIndex = textContent.indexOf( '>', i );

        if ( openIndex > -1 ) {
          openIndices.push( openIndex );
          i = openIndex + 1;
        }
        if ( closeIndex > -1 ) {
          closeIndices.push( closeIndex );
          i = closeIndex + 1;
        }
        else {
          i++;
        }
      }

      // check the name in between the open and close brackets - if anything other than formatting tags, return false
      var onlyFormatting = true;
      var upperCaseContent = textContent.toUpperCase();
      for ( var j = 0; j < openIndices.length; j++ ) {

        // get the name and remove the closing slash
        var subString = upperCaseContent.substring( openIndices[ j ] + 1, closeIndices[ j ] );
        subString = subString.replace( '/', '' );

        // if the left of the substring contains space, it is not a valid tag so allow
        var trimmed = trimLeft( subString );
        if ( subString.length - trimmed.length > 0 ) {
          continue;
        }

        if ( !_.includes( FORMATTING_TAGS, subString ) ) {
          onlyFormatting = false;
        }
      }

      return onlyFormatting;
    },

    /**
     * If the text content uses formatting tags, set the content as innerHTML. Otherwise, set as textContent.
     * In general, textContent is more secure and more performant because it doesn't trigger DOM styling and
     * element insertions.
     *
     * @param {HTMLElement} domElement
     * @param {string} textContent - could have acceptable HTML "formatting" tags in it
     */
    setTextContent: function( domElement, textContent ) {
      if ( tagNameSupportsContent( domElement.tagName ) ) {

        // returns true if there are no brackets at all
        if ( AccessibilityUtil.usesExclusivelyFormattingTags( textContent ) ) {
          domElement.innerHTML = textContent;
        }
        else {
          domElement.textContent = textContent;
        }
      }
    },

    /**
     * Add DOM event listeners contained in the accessibleInput directly to the DOM elements on each
     * accessibleInstance.  Never use this directly, use addAccessibleInputListener()
     * @private
     *
     * @param {Object} accessibleInput
     * @param {HTMLElement} domElement
     */
    addDOMEventListeners: function( accessibleInput, domElement ) {
      for ( var event in accessibleInput ) {
        if ( accessibleInput.hasOwnProperty( event ) && _.includes( DOM_EVENTS, event ) ) {
          domElement.addEventListener( event, accessibleInput[ event ] );
        }
      }
    },

    /**
     * Remove a DOM event listener contained in an accesssibleInput.  Never to be used directly, see
     * removeAccessibilityInputListener().
     * @private
     *
     * @param {Object} accessibleInput
     * @param {HTMLElement} domElement
     */
    removeDOMEventListeners: function( accessibleInput, domElement ) {
      for ( var event in accessibleInput ) {
        if ( accessibleInput.hasOwnProperty( event ) && _.includes( DOM_EVENTS, event ) ) {
          domElement.removeEventListener( event, accessibleInput[ event ] );
        }
      }
    },

    /**
     * Given a tagName, test if the element will be focuable by default by the browser.
     * Different from isElementFocusable, because this only looks at tags that the browser will automatically put
     * a >=0 tab index on.
     *
     * NOTE: Uses a set of browser types as the definition of default focusable elements,
     * see https://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus
     *
     * @param tagName
     * @returns {boolean}
     */
    tagIsDefaultFocusable: function( tagName ) {
      return _.includes( DEFAULT_FOCUSABLE_TAGS, tagName.toUpperCase() );
    },

    /**
     *
     * @param {string} tagName
     * @returns {boolean} - true if the tag does support inner content
     */
    tagNameSupportsContent: function( tagName ) {
      return tagNameSupportsContent( tagName );
    },

    TAGS: {
      INPUT: INPUT_TAG,
      LABEL: LABEL_TAG,
      BUTTON: BUTTON_TAG,
      TEXTAREA: TEXTAREA_TAG,
      SELECT: SELECT_TAG,
      OPTGROUP: OPTGROUP_TAG,
      DATALIST: DATALIST_TAG,
      OUTPUT: OUTPUT_TAG,
      DIV: DIV_TAG,
      A: A_TAG,
      P: P_TAG,
      B: BOLD_TAG,
      STRONG: STRONG_TAG,
      I: I_TAG,
      EM: EM_TAG,
      MARK: MARK_TAG,
      SMALL: SMALL_TAG,
      DEL: DEL_TAG,
      INS: INS_TAG,
      SUB: SUB_TAG,
      SUP: SUP_TAG
    },

    // these elements are typically associated with forms, and support certain attributes
    FORM_ELEMENTS: [ INPUT_TAG, BUTTON_TAG, TEXTAREA_TAG, SELECT_TAG, OPTGROUP_TAG, DATALIST_TAG, OUTPUT_TAG, A_TAG ],

    // default tags for html elements of the Node.
    DEFAULT_CONTAINER_TAG_NAME: DIV_TAG,
    DEFAULT_DESCRIPTION_TAG_NAME: P_TAG,
    DEFAULT_LABEL_TAG_NAME: P_TAG
  };

  scenery.register( 'AccessibilityUtil', AccessibilityUtil );

  return AccessibilityUtil;
} );
<commit_msg>Fix tag name for area, see #794<commit_after>// Copyright 2013-2016, University of Colorado Boulder

/**
 * Utility functions for scenery that are specifically useful for Accessibility.
 * These generally pertain to DOM traversal and manipulation.
 *
 * @author Jesse Greenberg
 */

define( function( require ) {
  'use strict';

  // modules
  var scenery = require( 'SCENERY/scenery' );
  var Random = require( 'DOT/Random' );

  // constants
  var NEXT = 'NEXT';
  var PREVIOUS = 'PREVIOUS';

  // HTML tag names
  var INPUT_TAG = 'INPUT';
  var LABEL_TAG = 'LABEL';
  var BUTTON_TAG = 'BUTTON';
  var TEXTAREA_TAG = 'TEXTAREA';
  var SELECT_TAG = 'SELECT';
  var OPTGROUP_TAG = 'OPTGROUP';
  var DATALIST_TAG = 'DATALIST';
  var OUTPUT_TAG = 'OUTPUT';
  var DIV_TAG = 'DIV';
  var A_TAG = 'A';
  var AREA_TAG = 'AREA';
  var P_TAG = 'P';
  var IFRAME_TAG = 'IFRAME';

  // tag names with special behavior
  var BOLD_TAG = 'B';
  var STRONG_TAG = 'STRONG';
  var I_TAG = 'I';
  var EM_TAG = 'EM';
  var MARK_TAG = 'MARK';
  var SMALL_TAG = 'SMALL';
  var DEL_TAG = 'DEL';
  var INS_TAG = 'INS';
  var SUB_TAG = 'SUB';
  var SUP_TAG = 'SUP';

  // These browser tags are a definition of default focusable elements, converted from Javascript types,
  // see https://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus
  var DEFAULT_FOCUSABLE_TAGS = [ A_TAG, AREA_TAG, INPUT_TAG, SELECT_TAG, TEXTAREA_TAG, BUTTON_TAG, IFRAME_TAG ];

  // collection of tags that are used for formatting text
  var FORMATTING_TAGS = [ BOLD_TAG, STRONG_TAG, I_TAG, EM_TAG, MARK_TAG, SMALL_TAG, DEL_TAG, INS_TAG, SUB_TAG, SUP_TAG ];

  // these elements do not have a closing tag, so they won't support features like innerHTML. This is how PhET treats
  // these elements, not necessary what is legal html.
  var ELEMENTS_WITHOUT_CLOSING_TAG = [ INPUT_TAG ];

  // valid types of DOM events that can be added to a node
  var DOM_EVENTS = [ 'input', 'change', 'click', 'keydown', 'keyup', 'focus', 'blur' ];

  /**
   * Get all 'element' nodes off the parent element, placing them in an array for easy traversal.  Note that this
   * includes all elements, even those that are 'hidden' or purely for structure.
   *
   * @param  {HTMLElement} domElement - parent whose children will be linearized
   * @returns {HTMLElement[]}
   * @private
   */
  function getLinearDOMElements( domElement ) {

    // gets ALL descendant children for the element
    var children = domElement.getElementsByTagName( '*' );

    var linearDOM = [];
    for ( var i = 0; i < children.length; i++ ) {

      // searching for the HTML element nodes (NOT Scenery nodes)
      if ( children[ i ].nodeType === Node.ELEMENT_NODE ) {
        linearDOM[ i ] = ( children[ i ] );
      }
    }
    return linearDOM;
  }

  /**
   * Determine if an element is hidden.  An element is considered 'hidden' if it (or any of its ancestors) has the
   * 'hidden' attribute.
   * @private
   *
   * @param {HTMLElement} domElement
   * @returns {Boolean}
   */
  function isElementHidden( domElement ) {
    if ( domElement.hidden ) {
      return true;
    }
    else if ( domElement === document.body ) {
      return false;
    }
    else {
      return isElementHidden( domElement.parentElement );
    }
  }

  /**
   * Get the next or previous focusable element in the parallel DOM, relative to this Node's primarySibling
   * depending on the direction. Useful if you need to set focus dynamically or need to prevent default behavior
   * when focus changes. If no next or previous focusable is found, it returns the currently focused element.
   * This function should not be used directly, use getNextFocusable() or getPreviousFocusable() instead.
   * @private
   *
   * @param {string} direction - direction of traversal, one of 'NEXT' | 'PREVIOUS'
   * @param {HTMLElement} [parentElement] - optional, search will be limited to children of this element
   * @returns {HTMLElement}
   */
  function getNextPreviousFocusable( direction, parentElement ) {

    // linearize the document [or the desired parent] for traversal
    var parent = parentElement || document.body;
    var linearDOM = getLinearDOMElements( parent );

    var activeElement = document.activeElement;
    var activeIndex = linearDOM.indexOf( activeElement );
    var delta = direction === NEXT ? +1 : -1;

    // find the next focusable element in the DOM
    var nextIndex = activeIndex + delta;
    while ( nextIndex < linearDOM.length && nextIndex >= 0 ) {
      var nextElement = linearDOM[ nextIndex ];
      nextIndex += delta;

      if ( isElementFocusable( nextElement ) ) {
        return nextElement;
      }
    }

    // if no next focusable is found, return the active DOM element
    return activeElement;
  }

  /**
   * Returns true if the element is focusable. Assumes that all focusable  elements have tabIndex >= 0, which
   * is only true for elements of the Parallel DOM.
   *
   * @param {HTMLElement} domElement
   * @return {boolean}
   */
  function isElementFocusable( domElement ) {

    // continue to next element if this one is meant to be hidden
    if ( isElementHidden( domElement ) ) {
      return false;
    }

    // if element is for formatting, skipe over it - required since IE gives these tabindex="0"
    if ( _.includes( FORMATTING_TAGS, domElement.tagName ) ) {
      return false;
    }

    // if tabindex is greater than -1, the element is focusable so break
    return domElement.tabIndex >= 0;
  }

  /**
   * Trims the white space from the left of the string.
   * Solution from https://stackoverflow.com/questions/1593859/left-trim-in-javascript
   * @param  {string} string
   * @return {string}
   */
  function trimLeft( string ) {

    // ^ - from the beginning of the string
    // \s - whitespace character
    // + - greedy
    return string.replace( /^\s+/, '' );
  }

  /**
   * Returns whether or not the tagName supports innerHTML or textContent in PhET.
   * @private
   * @param {string} tagName
   * @returns {boolean}
   */
  function tagNameSupportsContent( tagName ) {
    return !_.includes( ELEMENTS_WITHOUT_CLOSING_TAG, tagName.toUpperCase() );
  }

  var AccessibilityUtil = {

    /**
     * Get the next focusable element. This should very rarely be used.  The next focusable element can almost
     * always be focused automatically with 'Tab'.  However, if the 'Tab' key needs to be emulated this can be
     * helpful. If no next focusable can be found, it will return the currently focused element.
     * @public
     *
     * @param{HTMLElement} [parentElement] - optional, search will be limited to elements under this element
     * @returns {HTMLElement}
     */
    getNextFocusable: function( parentElement ) {
      return getNextPreviousFocusable( NEXT, parentElement );
    },

    /**
     * Get the previous focusable element. This should very rarely be used.  The previous focusable element can almost
     * always be found automatically with default 'Shift+Tab' behavior.  However, if the 'Tab' key needs to be emulated
     * this can be helpful.  If no next focusable can be found, it will return the currently focused element.
     * @public
     *
     * @param {HTMLElement} [parentElement] - optional, search will be limited to elements under this parent
     * @returns {HTMLElement}
     */
    getPreviousFocusable: function( parentElement ) {
      return getNextPreviousFocusable( PREVIOUS, parentElement );
    },

    /**
     * Return a random focusable element in the document. Particularly useful for fuzz testing.
     *
     * @return {HTMLElement}
     */
    getRandomFocusable: function() {

      var linearDOM = getLinearDOMElements( document.body );
      var focusableElements = [];
      for ( var i = 0; i < linearDOM.length; i++ ) {
        isElementFocusable( linearDOM[ i ] ) && focusableElements.push( linearDOM[ i ] );
      }

      return focusableElements[ new Random().nextInt( focusableElements.length ) ];
    },

    /**
     * If the textContent has any tags that are not formatting tags, return false. Only checking for
     * tags that are not in the whitelist FORMATTING_TAGS, if there are no tags this will still return
     * true.
     * @public
     *
     * @param  {string} textContent
     * @returns {boolean}
     */
    usesExclusivelyFormattingTags: function( textContent ) {

      // no-op for null case
      if ( textContent === null ) {
        return false;
      }
      assert && assert( typeof textContent === 'string', 'unsupported type for textContent.' );

      var i = 0;
      var openIndices = [];
      var closeIndices = [];

      // find open/close tag pairs in the text content
      while ( i < textContent.length ) {
        var openIndex = textContent.indexOf( '<', i );
        var closeIndex = textContent.indexOf( '>', i );

        if ( openIndex > -1 ) {
          openIndices.push( openIndex );
          i = openIndex + 1;
        }
        if ( closeIndex > -1 ) {
          closeIndices.push( closeIndex );
          i = closeIndex + 1;
        }
        else {
          i++;
        }
      }

      // check the name in between the open and close brackets - if anything other than formatting tags, return false
      var onlyFormatting = true;
      var upperCaseContent = textContent.toUpperCase();
      for ( var j = 0; j < openIndices.length; j++ ) {

        // get the name and remove the closing slash
        var subString = upperCaseContent.substring( openIndices[ j ] + 1, closeIndices[ j ] );
        subString = subString.replace( '/', '' );

        // if the left of the substring contains space, it is not a valid tag so allow
        var trimmed = trimLeft( subString );
        if ( subString.length - trimmed.length > 0 ) {
          continue;
        }

        if ( !_.includes( FORMATTING_TAGS, subString ) ) {
          onlyFormatting = false;
        }
      }

      return onlyFormatting;
    },

    /**
     * If the text content uses formatting tags, set the content as innerHTML. Otherwise, set as textContent.
     * In general, textContent is more secure and more performant because it doesn't trigger DOM styling and
     * element insertions.
     *
     * @param {HTMLElement} domElement
     * @param {string} textContent - could have acceptable HTML "formatting" tags in it
     */
    setTextContent: function( domElement, textContent ) {
      if ( tagNameSupportsContent( domElement.tagName ) ) {

        // returns true if there are no brackets at all
        if ( AccessibilityUtil.usesExclusivelyFormattingTags( textContent ) ) {
          domElement.innerHTML = textContent;
        }
        else {
          domElement.textContent = textContent;
        }
      }
    },

    /**
     * Add DOM event listeners contained in the accessibleInput directly to the DOM elements on each
     * accessibleInstance.  Never use this directly, use addAccessibleInputListener()
     * @private
     *
     * @param {Object} accessibleInput
     * @param {HTMLElement} domElement
     */
    addDOMEventListeners: function( accessibleInput, domElement ) {
      for ( var event in accessibleInput ) {
        if ( accessibleInput.hasOwnProperty( event ) && _.includes( DOM_EVENTS, event ) ) {
          domElement.addEventListener( event, accessibleInput[ event ] );
        }
      }
    },

    /**
     * Remove a DOM event listener contained in an accesssibleInput.  Never to be used directly, see
     * removeAccessibilityInputListener().
     * @private
     *
     * @param {Object} accessibleInput
     * @param {HTMLElement} domElement
     */
    removeDOMEventListeners: function( accessibleInput, domElement ) {
      for ( var event in accessibleInput ) {
        if ( accessibleInput.hasOwnProperty( event ) && _.includes( DOM_EVENTS, event ) ) {
          domElement.removeEventListener( event, accessibleInput[ event ] );
        }
      }
    },

    /**
     * Given a tagName, test if the element will be focuable by default by the browser.
     * Different from isElementFocusable, because this only looks at tags that the browser will automatically put
     * a >=0 tab index on.
     *
     * NOTE: Uses a set of browser types as the definition of default focusable elements,
     * see https://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus
     *
     * @param tagName
     * @returns {boolean}
     */
    tagIsDefaultFocusable: function( tagName ) {
      return _.includes( DEFAULT_FOCUSABLE_TAGS, tagName.toUpperCase() );
    },

    /**
     *
     * @param {string} tagName
     * @returns {boolean} - true if the tag does support inner content
     */
    tagNameSupportsContent: function( tagName ) {
      return tagNameSupportsContent( tagName );
    },

    TAGS: {
      INPUT: INPUT_TAG,
      LABEL: LABEL_TAG,
      BUTTON: BUTTON_TAG,
      TEXTAREA: TEXTAREA_TAG,
      SELECT: SELECT_TAG,
      OPTGROUP: OPTGROUP_TAG,
      DATALIST: DATALIST_TAG,
      OUTPUT: OUTPUT_TAG,
      DIV: DIV_TAG,
      A: A_TAG,
      P: P_TAG,
      B: BOLD_TAG,
      STRONG: STRONG_TAG,
      I: I_TAG,
      EM: EM_TAG,
      MARK: MARK_TAG,
      SMALL: SMALL_TAG,
      DEL: DEL_TAG,
      INS: INS_TAG,
      SUB: SUB_TAG,
      SUP: SUP_TAG
    },

    // these elements are typically associated with forms, and support certain attributes
    FORM_ELEMENTS: [ INPUT_TAG, BUTTON_TAG, TEXTAREA_TAG, SELECT_TAG, OPTGROUP_TAG, DATALIST_TAG, OUTPUT_TAG, A_TAG ],

    // default tags for html elements of the Node.
    DEFAULT_CONTAINER_TAG_NAME: DIV_TAG,
    DEFAULT_DESCRIPTION_TAG_NAME: P_TAG,
    DEFAULT_LABEL_TAG_NAME: P_TAG
  };

  scenery.register( 'AccessibilityUtil', AccessibilityUtil );

  return AccessibilityUtil;
} );
<|endoftext|>


<commit_before>$("#infowindow").mouseover(function() {
  var headline = document.getElementById('headline');
  var infobox = document.createElement('p');
  infobox.setAttribute('id','infotext')
  infobox.style.display = 'block'
  infobox.style.background = '#e4e4e4'
  infobox.innerHTML = "Click on 'Top Words' to toggle a live-updating table of each word's count, along with thesarus suggestions for word counts above 3."
  headline.appendChild(infobox)
});

$("#infowindow").mouseleave(function() {
  $('#infotext').remove();
});
<commit_msg>infowindow content changed<commit_after>$("#infowindow").mouseover(function() {
  var headline = document.getElementById('headline');
  var infobox = document.createElement('p');
  infobox.setAttribute('id','infotext')
  infobox.style.display = 'block'
  infobox.style.background = '#e4e4e4'
  infobox.innerHTML = "Simply type to get a live-updating table of word counts and thesaurus suggestions for most used words. Use the buttons to toggle between features."
  headline.appendChild(infobox)
});

$("#infowindow").mouseleave(function() {
  $('#infotext').remove();
});
<|endoftext|>


<commit_before>/**
* aggCtrl
* @desc this controller is attached to each aggregation displayed
*/
Curiosity.controller('aggCtrl', function($scope, $modal, aggFactory, template){
	$scope.aggTypeSelected = false;
	$scope.data = aggFactory.info;
	$scope.tplData = template.info;

	/**
	* @desc function call by ng-init, called to load aggregation specifique data, and load template in $templateCache
	* @param string name : the name of the aggregation attached to the controller
	*/
	$scope.init = function (name) {
		$scope.curAgg = {};
		$scope.curAgg.validate = false;
		if (typeof(name) === "undefined") {
			$scope.curAgg = aggFactory.newEmptyAggregation(false);
		}
		else {
			$scope.curAgg = aggFactory.getAggregation(name);
			initTemplate($scope.curAgg);
		}
	}

	/**
	* @desc remove an aggregation from a container, used for nestedAgg
	* @param array container target array
	* @param int idx row's index to remove
	*/
	$scope.removeAgg = function (container, idx) {
		container.splice(idx, 1);
	}

	/**
	* @desc remove an aggregation from the aggregation list of aggFactory
	* @param object agg : the aggregation to remove
	* @param int id : the aggregation id
	*/
	$scope.removeMainAgg = function (agg, id) {
		aggFactory.removeMainAgg(agg, id);
	}

	/**
	* @desc change an aggregation type (terms, range ...)
	* @param object agg the aggregation to change type
	* @idx int idx the index of the new aggregation type in aggList
	*/
	$scope.changeAggType = function (agg,idx) {
		if (typeof (idx) != "undefined" && idx != null && idx >= 0) {
			agg.type = $scope.data.aggList[idx].type;
			agg.displayTemplate = $scope.data.aggList[idx].template;
			agg.resultTemplate = $scope.data.aggList[idx].resultTemplate;
			agg.aggTypeSelected = true;
		}
	}

	/**
	* @desc modifie the validate attr of an aggregation
	* @param object agg the agg to modify
	*/
	$scope.validateAgg = function (agg) {
		agg.validate = !agg.validate;
	}

	/**
	* @desc add a nested aggregation to an aggregation
	* @param object agg the agg to modify
	*/
	$scope.addNestedAgg = function (agg) {
		aggFactory.addNestedAgg(agg)
	}

	/**
	* @desc ask template service to add a template to the $templateCache whenever the template id different from default
	* @param string the template name
	*/
	$scope.loadTpl = function (tpl) {
		if (typeof(tpl) !== "undefined" && tpl != "default") {
			template.addTemplateToCacheFromName("aggregationsTemplates", tpl);
		}
	}

	/**
	* @desc add an interval to a range aggregation
	* @param object agg target aggregation
	*/
	$scope.addInterval = function (agg) {
		if (typeof (agg.intervals) === "undefined") {
			agg.intervals = [];
		}
		agg.intervals.push({});
	}
	/**
 	* @desc remove an interval to a range aggregation
 	* @param object agg target aggregation
 	* @param int index index to remove
 	*/
	$scope.removeInterval =  function(agg, index) {
		if (typeof (agg.intervals) !== "undefined") {
			agg.intervals.splice(index, 1);
		}
	}

	/**
	* @desc open a modal which contains the fields list. When closed change aggregation field attr value
	* @params 'sm' | 'lg' size modal size
	*/
	$scope.openModalFields = function (size, curAgg) {
		var modalInstance = $modal.open({
			templateUrl: 'template/modal/fieldsModal.html',
			controller: mappingModalCtrl,
			resolve: {
				item: function () {
					return curAgg;
				}}
			});
		modalInstance.result.then(function (value) {
			value.item.field = value.value;
		}, function () {
		})
	};

	/**
	* @desc load aggregation and nested aggregation template in $templateCache
	* @param object agg target aggregation
	*/
	function initTemplate(agg) {
		$scope.loadTpl(agg.tpl);
		if (typeof(agg.nested) !== "undefined") {
			for (sub_agg in agg.nested)
				initTemplate(sub_agg);
			}
		}
	}
});<commit_msg>Fix regression in agg nested association<commit_after>/**
* aggCtrl
* @desc this controller is attached to each aggregation displayed
*/
Curiosity.controller('aggCtrl', function($scope, $modal, aggFactory, template){
	$scope.aggTypeSelected = false;
	$scope.data = aggFactory.info;
	$scope.tplData = template.info;

	/**
	* @desc function call by ng-init, called to load aggregation specifique data, and load template in $templateCache
	* @param string name : the name of the aggregation attached to the controller
	*/
	$scope.init = function (name) {
		$scope.curAgg = {};
		$scope.curAgg.validate = false;
		if (typeof(name) === "undefined") {
			$scope.curAgg = aggFactory.newEmptyAggregation(false);
		}
		else {
			$scope.curAgg = aggFactory.getAggregation(name);
			initTemplate($scope.curAgg);
		}
	}

	/**
	* @desc remove an aggregation from a container, used for nestedAgg
	* @param array container target array
	* @param int idx row's index to remove
	*/
	$scope.removeAgg = function (container, idx) {
		container.splice(idx, 1);
	}

	/**
	* @desc remove an aggregation from the aggregation list of aggFactory
	* @param object agg : the aggregation to remove
	* @param int id : the aggregation id
	*/
	$scope.removeMainAgg = function (agg, id) {
		aggFactory.removeMainAgg(agg, id);
	}

	/**
	* @desc change an aggregation type (terms, range ...)
	* @param object agg the aggregation to change type
	* @idx int idx the index of the new aggregation type in aggList
	*/
	$scope.changeAggType = function (agg,idx) {
		if (typeof (idx) != "undefined" && idx != null && idx >= 0) {
			agg.type = $scope.data.aggList[idx].type;
			agg.displayTemplate = $scope.data.aggList[idx].template;
			agg.resultTemplate = $scope.data.aggList[idx].resultTemplate;
			agg.aggTypeSelected = true;
		}
	}

	/**
	* @desc modifie the validate attr of an aggregation
	* @param object agg the agg to modify
	*/
	$scope.validateAgg = function (agg) {
		agg.validate = !agg.validate;
	}

	/**
	* @desc add a nested aggregation to an aggregation
	* @param object agg the agg to modify
	*/
	$scope.addNestedAgg = function (agg) {
		aggFactory.addNestedAgg(agg)
	}

	/**
	* @desc ask template service to add a template to the $templateCache whenever the template id different from default
	* @param string the template name
	*/
	$scope.loadTpl = function (tpl) {
		if (typeof(tpl) !== "undefined" && tpl != "default") {
			template.addTemplateToCacheFromName("aggregationsTemplates", tpl);
		}
	}

	/**
	* @desc add an interval to a range aggregation
	* @param object agg target aggregation
	*/
	$scope.addInterval = function (agg) {
		if (typeof (agg.intervals) === "undefined") {
			agg.intervals = [];
		}
		agg.intervals.push({});
	}
	/**
 	* @desc remove an interval to a range aggregation
 	* @param object agg target aggregation
 	* @param int index index to remove
 	*/
	$scope.removeInterval =  function(agg, index) {
		if (typeof (agg.intervals) !== "undefined") {
			agg.intervals.splice(index, 1);
		}
	}

	/**
	* @desc open a modal which contains the fields list. When closed change aggregation field attr value
	* @params 'sm' | 'lg' size modal size
	*/
	$scope.openModalFields = function (size, curAgg) {
		var modalInstance = $modal.open({
			templateUrl: 'template/modal/fieldsModal.html',
			controller: mappingModalCtrl,
			resolve: {
				item: function () {
					return curAgg;
				}}
			});
		modalInstance.result.then(function (value) {
			value.item.field = value.value;
		}, function () {
		})
	};

	/**
	* @desc load aggregation and nested aggregation template in $templateCache
	* @param object agg target aggregation
	*/
	function initTemplate(agg) {
		$scope.loadTpl(agg.tpl);
		if (typeof(agg.nested) !== "undefined") {
		for (sub_agg in agg.nested)
			initTemplate(sub_agg);
		}
	}
});<|endoftext|>


<commit_before>var $ = require('xtraverse'),
    xmldsig = require('xmldsig'),
    moment = require('moment');

module.exports = function(options) {
  options = options || {};

  var issuer = options.issuer
    , key = options.key
    , kid = options.kid
    , algorithm = options.algorithm || 'RS256';

  if (!issuer) { throw new TypeError('SAML 2.0 assertion encoder requires an issuer'); }
  if (!key) { throw new TypeError('SAML 2.0 assertion encoder requires a key'); }

  return function saml2(info, cb) {
    var assertion = $('<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>')
      , subject, subjectNameID, subjectConfirmation, subjectConfirmationData
      , conditions
      , authnStatement, authnContext;

    assertion.c('saml:Issuer').t(issuer);

    // <saml:Subject/>
    subject = assertion.c('saml:Subject');

    subjectNameID = subject.c('saml:NameID').t(info.subject).up();
    switch (info.subjectIdentiferFormat) {
    case 'persistent':
    case 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent':
    default:
      subjectNameID.attr('Format', 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent');
      break;
    }

    subjectConfirmation = subject.c('saml:SubjectConfirmation');
    switch (info.subjectConfirmationMethod) {
    case 'bearer':
    case 'urn:oasis:names:tc:SAML:2.0:cm:bearer':
    default:
      subjectConfirmation.attr('Method', 'urn:oasis:names:tc:SAML:2.0:cm:bearer');
      subjectConfirmationData = subjectConfirmation.c('saml:SubjectConfirmationData');
      if (info.recipient) { subjectConfirmationData.attr('Recipient', info.recipient); }
      if (info.inResponseTo) { subjectConfirmationData.attr('InResponseTo', info.inResponseTo); }
      if (info.expiresAt) { subjectConfirmationData.attr('NotOnOrAfter', moment(info.expiresAt).utc().format('YYYY-MM-DDTHH:mm:ss') + 'Z'); }
      break;
    }

    // <saml:Conditions/>
    conditions = assertion.c('saml:Conditions');
    if (info.notBefore) { subjectConfirmationData.attr('NotBefore', moment(info.notBefore).utc().format('YYYY-MM-DDTHH:mm:ss') + 'Z'); }
    if (info.expiresAt) { subjectConfirmationData.attr('NotOnOrAfter', moment(info.expiresAt).utc().format('YYYY-MM-DDTHH:mm:ss') + 'Z'); }
    if (Array.isArray(info.audience)) {
      // TODO:
    } else {
      conditions.c('saml:AudienceRestriction').c('saml:Audience').t(info.audience);
    }

    // <saml:AuthnStatement/>
    if (info.authenticatedAt) {
      authnStatement = assertion.c('saml:AuthnStatement', { AuthnInstant: moment(info.authenticatedAt).utc().format('YYYY-MM-DDTHH:mm:ss') + 'Z' });
      authnContext = authnStatement.c('saml:AuthnContext')
      if (!info.authenticationContextClassReference) {
        authnContext.c('saml:AuthnContextClassRef').t('urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified');
      } else {
        // TODO
      }
    }

    // TODO:

    console.log(assertion);
    console.log(assertion.toString());

    var sa = xmldsig.sign(assertion.toString());
    console.log(sa);

    cb(null, sa);
  }
}
<commit_msg>Add TODO note.<commit_after>var $ = require('xtraverse'),
    xmldsig = require('xmldsig'),
    moment = require('moment');

module.exports = function(options) {
  options = options || {};

  var issuer = options.issuer
    , key = options.key
    , kid = options.kid
    , algorithm = options.algorithm || 'RS256';

  if (!issuer) { throw new TypeError('SAML 2.0 assertion encoder requires an issuer'); }
  if (!key) { throw new TypeError('SAML 2.0 assertion encoder requires a key'); }

  return function saml2(info, cb) {
    var assertion = $('<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>')
      , subject, subjectNameID, subjectConfirmation, subjectConfirmationData
      , conditions
      , authnStatement, authnContext;

    assertion.c('saml:Issuer').t(issuer);

    // <saml:Subject/>
    subject = assertion.c('saml:Subject');

    subjectNameID = subject.c('saml:NameID').t(info.subject).up();
    switch (info.subjectIdentiferFormat) {
    case 'persistent':
    case 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent':
    default:
      subjectNameID.attr('Format', 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent');
      break;
    }

    subjectConfirmation = subject.c('saml:SubjectConfirmation');
    switch (info.subjectConfirmationMethod) {
    case 'bearer':
    case 'urn:oasis:names:tc:SAML:2.0:cm:bearer':
    default:
      subjectConfirmation.attr('Method', 'urn:oasis:names:tc:SAML:2.0:cm:bearer');
      subjectConfirmationData = subjectConfirmation.c('saml:SubjectConfirmationData');
      if (info.recipient) { subjectConfirmationData.attr('Recipient', info.recipient); }
      if (info.inResponseTo) { subjectConfirmationData.attr('InResponseTo', info.inResponseTo); }
      if (info.expiresAt) { subjectConfirmationData.attr('NotOnOrAfter', moment(info.expiresAt).utc().format('YYYY-MM-DDTHH:mm:ss') + 'Z'); }
      break;
    }

    // <saml:Conditions/>
    conditions = assertion.c('saml:Conditions');
    if (info.notBefore) { subjectConfirmationData.attr('NotBefore', moment(info.notBefore).utc().format('YYYY-MM-DDTHH:mm:ss') + 'Z'); }
    if (info.expiresAt) { subjectConfirmationData.attr('NotOnOrAfter', moment(info.expiresAt).utc().format('YYYY-MM-DDTHH:mm:ss') + 'Z'); }
    if (Array.isArray(info.audience)) {
      // TODO:
    } else {
      conditions.c('saml:AudienceRestriction').c('saml:Audience').t(info.audience);
    }

    // <saml:AuthnStatement/>
    if (info.authenticatedAt) {
      authnStatement = assertion.c('saml:AuthnStatement', { AuthnInstant: moment(info.authenticatedAt).utc().format('YYYY-MM-DDTHH:mm:ss') + 'Z' });
      authnContext = authnStatement.c('saml:AuthnContext')
      if (!info.authenticationContextClassReference) {
        authnContext.c('saml:AuthnContextClassRef').t('urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified');
      } else {
        // TODO
      }
    }

    // TODO: User profile atributes

    console.log(assertion);
    console.log(assertion.toString());

    var sa = xmldsig.sign(assertion.toString());
    console.log(sa);

    cb(null, sa);
  }
}
<|endoftext|>


<commit_before>import { getKeyForAddresses } from './helpers';

export function getLocations( state ) {
  return state.library.locations;
}

export function getLocationById( state, id ) {
  return getLocations( state ).reduce( ( found, location ) => {
    if ( location._id === id ) return location;
    return found;
  }, null );
}

export function getLocationsForTrip( state ) {
  return state.trip.map( id => getLocationById( state, id ) )
  .filter( location => !! location );
}

export function getAddressesForTrip( state ) {
  return getLocationsForTrip( state )
  .map( location => location.address );
}

export function getAddressPairs( state ) {
  const addrs = getAddressesForTrip( state );
  return addrs.reduce( ( pairs, start, index ) => {
    const dest = addrs[ index + 1 ];
    return dest ? pairs.concat( { start, dest } ) : pairs;
  }, [] );
}

export function getTripDistances( state ) {
  const pairs = getAddressPairs( state );
  return pairs.map( pair => getDistanceForKey( state, getKeyForAddresses( pair.start, pair.dest ) ) );
}

export function getTotalTripDistance( state ) {
  return getTripDistances( state ).reduce( ( prev, num ) => prev + num, 0 );
}

export function getDistanceDataForKey( state, key ) {
  return state.distances[ key ] || {};
}

export function getDistanceForKey( state, key ) {
  return getDistanceDataForKey( state, key ).distance;
}

export function isDistanceComplete( state ) {
  const tripLocations = getLocationsForTrip( state );
  const tripDistances = getTripDistances( state );
  return ( tripLocations.length - 1 ) === tripDistances.length;
}

export function isDistanceExpired( state, key ) {
  const maxDistanceAge = 7 * 24 * 60 * 60 * 1000;
  const lastUpdatedAt = getDistanceDataForKey( state, key ).lastUpdatedAt || 0;
  const age = Date.now() - lastUpdatedAt;
  return age > maxDistanceAge;
}
<commit_msg>Fix isDistanceComplete for empty trips<commit_after>import { getKeyForAddresses } from './helpers';

export function getLocations( state ) {
  return state.library.locations;
}

export function getLocationById( state, id ) {
  return getLocations( state ).reduce( ( found, location ) => {
    if ( location._id === id ) return location;
    return found;
  }, null );
}

export function getLocationsForTrip( state ) {
  return state.trip.map( id => getLocationById( state, id ) )
  .filter( location => !! location );
}

export function getAddressesForTrip( state ) {
  return getLocationsForTrip( state )
  .map( location => location.address );
}

export function getAddressPairs( state ) {
  const addrs = getAddressesForTrip( state );
  return addrs.reduce( ( pairs, start, index ) => {
    const dest = addrs[ index + 1 ];
    return dest ? pairs.concat( { start, dest } ) : pairs;
  }, [] );
}

export function getTripDistances( state ) {
  const pairs = getAddressPairs( state );
  return pairs.map( pair => getDistanceForKey( state, getKeyForAddresses( pair.start, pair.dest ) ) );
}

export function getTotalTripDistance( state ) {
  return getTripDistances( state ).reduce( ( prev, num ) => prev + num, 0 );
}

export function getDistanceDataForKey( state, key ) {
  return state.distances[ key ] || {};
}

export function getDistanceForKey( state, key ) {
  return getDistanceDataForKey( state, key ).distance;
}

export function isDistanceComplete( state ) {
  const tripLocations = getLocationsForTrip( state );
  const tripDistances = getTripDistances( state );
  return ( tripLocations.length - 1 ) <= tripDistances.length;
}

export function isDistanceExpired( state, key ) {
  const maxDistanceAge = 7 * 24 * 60 * 60 * 1000;
  const lastUpdatedAt = getDistanceDataForKey( state, key ).lastUpdatedAt || 0;
  const age = Date.now() - lastUpdatedAt;
  return age > maxDistanceAge;
}
<|endoftext|>


<commit_before>"use strict";

var assert = require('assert');

var Grid = function(h, w) {
  return {
    _mines: [],

    width: w,
    height: h,

    addMine: function(r,c) {
      this._mines.push(r + ',' + c);
    },

    isMine: function(r,c) {
      return this._mines.indexOf(r + ',' + c) !== -1;
    }
  };
};

var createGridFromString = function(str) {
  var lines = str.split('\n');
  var hw = lines[0].split(' ');
  var height = hw[0] | 0;
  var width = hw[1] | 0;

  var grid = new Grid(hw[0]|0,hw[1]|0);

  for (var i=0;i<height;++i) {
    var row = lines[i+1];
    for (var j=0;j<width;++j) {
      var isMine = row[j] === '*';
      if (isMine) {
        grid.addMine(j,i);
      }
    }
  }

  return grid;
};

describe('minefield', function() {
  var sampleGrid = '3 4\n*...\n..*.\n....';

  describe('grid', function() {
    it('should be able to create a grid', function() {
      var grid = new Grid(5,6);

      assert.equal(grid.width, 6);
      assert.equal(grid.height,5);
    });

    it('should be able to create a grid of size from a string', function() {
      var grid = createGridFromString(sampleGrid);

      assert.equal(grid.width,4);
      assert.equal(grid.height,3);
    });

    it('reads in the correct values', function() {
      var grid = createGridFromString(sampleGrid);

      assert(grid.isMine(0,0));
      assert(grid.isMine(2,1));
      assert(!grid.isMine(0,1));
    });
  });
});
<commit_msg>failing test for number of surrounding mines<commit_after>"use strict";

var assert = require('assert');

var Grid = function(h, w) {
  return {
    _mines: [],

    width: w,
    height: h,

    addMine: function(r,c) {
      this._mines.push(r + ',' + c);
    },

    isMine: function(r,c) {
      return this._mines.indexOf(r + ',' + c) !== -1;
    }
  };
};

var createGridFromString = function(str) {
  var lines = str.split('\n');
  var hw = lines[0].split(' ');
  var height = hw[0] | 0;
  var width = hw[1] | 0;

  var grid = new Grid(hw[0]|0,hw[1]|0);

  for (var i=0;i<height;++i) {
    var row = lines[i+1];
    for (var j=0;j<width;++j) {
      var isMine = row[j] === '*';
      if (isMine) {
        grid.addMine(j,i);
      }
    }
  }

  return grid;
};

describe('minefield', function() {
  var sampleGrid = '3 4\n*...\n..*.\n....';

  describe('grid', function() {
    it('should be able to create a grid', function() {
      var grid = new Grid(5,6);

      assert.equal(grid.width, 6);
      assert.equal(grid.height,5);
    });

    it('should be able to create a grid of size from a string', function() {
      var grid = createGridFromString(sampleGrid);

      assert.equal(grid.width,4);
      assert.equal(grid.height,3);
    });

    it('reads in the correct values', function() {
      var grid = createGridFromString(sampleGrid);

      assert(grid.isMine(0,0));
      assert(grid.isMine(2,1));
      assert(!grid.isMine(0,1));
    });

    it('knows the surrounding number of mines', function() {
      var grid = createGridFromString(sampleGrid);

      assert.equal(0, grid.surroundingMines(0,2));
    });
  });
});
<|endoftext|>


<commit_before>/*The MIT License (MIT)

Copyright (c) 2015 Jens Malmborg

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.*/

var Utils = require('/utils.js').Utils;
var Quaternion = require('/matrix.js').Quaternion;
var Vector3 = require('/matrix.js').Vector3;
var Matrix = require('/matrix.js').Matrix;

var _forward = new Vector3(0,0,1);
var _up = new Vector3(0,1,0);
var _right = new Vector3(-1,0,0);

function Entity() {
    this.transform = new Transform();
    this.components = {};
}

Entity.prototype.addComponent = function (component) {
    if (component.entity) {
        var name = component.constructor.name.toLowerCase();
        throw new TypeError(
            "Component \'" + name + "\' is owned by another entity.");
    }
    component.entity = this;
    var name = component.constructor.name.toLowerCase();
    this.components[name.replace('component', '')] = component;
};

Entity.prototype.update = function (elapsed) {
    for (var name in this.components) {
        if (!this.components[name].enabled) {
            continue;
        }
        this.components[name].update(elapsed);
    }
};

Entity.prototype.draw = function () {
    for (var name in this.components) {
        if (!this.components[name].visible) {
            continue;
        }
        this.components[name].draw();
    }
};

function Transform() {
    var self = this;

    this._localScale = new Vector3(1,1,1);
    this._localRotation = new Quaternion();
    this._parent = null;
    this._local = new Matrix();
    this._forward = new Vector3();
    this._up = new Vector3();
    this._right = new Vector3();

    Object.defineProperty(this, 'localPosition', {
        get: (function() {
            var _localPosition = new Vector3();

            return function() {
                return _localPosition.set(
                    self._local[12], self._local[13], self._local[14]);
            }
        })(),
        set: function(value) {
            self._local[12] = value[0];
            self._local[13] = value[1];
            self._local[14] = value[2];
        }
    });

    Object.defineProperty(this, 'position', {
        get: (function() {
            var _position = new Vector3();

            return function() {
                _position.set(
                    self._local[12], self._local[13], self._local[14]);
                if (!self._parent) {
                    return _position;
                }
                return _position.transform(self._parent.world, _position);
            }
        })(),
        set: (function() {
            var _inverted = new Matrix();
            var _position = new Vector3();

            return function(value) {
                if (!self._parent) {
                    self._local[12] = value[0];
                    self._local[13] = value[1];
                    self._local[14] = value[2];
                } else {
                    // Convert world space to local.
                    self._parent.world.invert(_inverted);
                    value.transform(_inverted, _position);
                    self._local[12] = _position[0];
                    self._local[13] = _position[1];
                    self._local[14] = _position[2];
                }
            }
        })()
    });

    Object.defineProperty(this, 'localRotation', {
        get: function() {
            return self._localRotation;
        },
        set: (function() {
            var _diff = new Vector3();

            return function(value) {
                value.eulerAngles.sub(self._localRotation.eulerAngles, _diff);
                self.rotate(_diff[0], _diff[1], _diff[2]);
            }
        })()
    });

    Object.defineProperty(this, 'rotation', {
        get: (function() {
            var _rotation = new Quaternion();

            return function() {
                if (!self._parent) {
                    return self._localRotation;
                }
                return self._parent.rotation.mul(
                    self._localRotation, _rotation);
            }
        })(),
        set: (function() {
            var _diff = new Vector3();

            return function(value) {
                value.eulerAngles.sub(self._localRotation.eulerAngles, _diff);
                self.rotate(_diff[0], _diff[1], _diff[2]);
            }
        })()
    });

    Object.defineProperty(this, 'localScale', {
        get: function() {
            return self._localScale;
        },
        set: (function() {
            var _diff = new Vector3();

            return function(value) {
                value.div(self._localScale, _diff);
                self.scale(_diff[0], _diff[1], _diff[2]);
            }
        })()
    });

    Object.defineProperty(this, 'scaling', {
        get: (function() {
            var _scaling = new Vector3();

            return function() {
                if (!self._parent) {
                    return self._localScale;
                }
                return self._parent.scaling.mul(
                    self._localScale, _scaling);
            }
        })()
    });

    Object.defineProperty(this, 'eulerAngles', {
        get: function() {
            return self.rotation.eulerAngles;
        }
    });

    Object.defineProperty(this, 'world', {
        get: (function() {
            var _world = new Matrix();

            return function() {
                if (!self._parent) {
                    return self._local;
                }
                return self._parent.world.multiply(
                    self._local, _world);
            }
        })()
    });

}

Transform.prototype.parent = function(parent) {
    if (parent instanceof Transform) {
        this._parent = parent;
    } else if (parent instanceof Entity) {
        this._parent = parent.transform;
    } else {
        return this._parent;
    }
};

Transform.prototype.translate = function(x, y, z) {
    this._local.translate(x,y,z);
};

Transform.prototype.rotate = function(x, y, z) {
    this._local.rotate(x,y,z);
    this._localRotation.rotateX(x);
    this._localRotation.rotateY(y);
    this._localRotation.rotateZ(z);
};

Transform.prototype.scale = (function() {
    var _scale = new Vector3();

    return function(x, y, z) {
        this._local.scale(x,y,z);
        _scale.set(x,y,z);
        this._localScale.mul(_scale, this._localScale);
    }
})();

Transform.prototype.move = (function() {
    var _move = new Vector3();

    return function(x, y, z) {
        if (arguments.length === 1) {
            _move.set(arguments[0]);
            this.position = this.position.add(_move, _move);
        } else if (arguments.length === 3) {
            _move.set(x,y,z);
            this.position = this.position.add(_move, _move);
        } else {
            throw new TypeError('Wrong number of arguments.');
        }
    }
})();

Transform.prototype.forward = function() {
    this._forward = _forward.transform(this._localRotation, this._forward);
    return this._forward.normalize(this._forward);
};

Transform.prototype.up = function() {
    this._up = _up.transform(this._localRotation, this._up);
    return this._up.normalize(this._up);
};

Transform.prototype.right = function() {
    this._right = _right.transform(this._localRotation, this._right);
    return this._right.normalize(this._right);
};

function Component() {
    this.enabled = true;
    this.visible = true;
}

Component.prototype.update = function(elapsed) {
};

Component.prototype.draw = function() {
};

module.exports.Entity = Entity;
module.exports.Transform = Transform;
module.exports.Component = Component;<commit_msg>Removed functionality for setting rotation, it did not work correctly.<commit_after>/*The MIT License (MIT)

Copyright (c) 2015 Jens Malmborg

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.*/

var Utils = require('/utils.js').Utils;
var Quaternion = require('/matrix.js').Quaternion;
var Vector3 = require('/matrix.js').Vector3;
var Matrix = require('/matrix.js').Matrix;

var _forward = new Vector3(0,0,1);
var _up = new Vector3(0,1,0);
var _right = new Vector3(-1,0,0);

function Entity() {
    this.transform = new Transform();
    this.components = {};
}

Entity.prototype.addComponent = function (component) {
    if (component.entity) {
        var name = component.constructor.name.toLowerCase();
        throw new TypeError(
            "Component \'" + name + "\' is owned by another entity.");
    }
    component.entity = this;
    var name = component.constructor.name.toLowerCase();
    this.components[name.replace('component', '')] = component;
};

Entity.prototype.update = function (elapsed) {
    for (var name in this.components) {
        if (!this.components[name].enabled) {
            continue;
        }
        this.components[name].update(elapsed);
    }
};

Entity.prototype.draw = function () {
    for (var name in this.components) {
        if (!this.components[name].visible) {
            continue;
        }
        this.components[name].draw();
    }
};

function Transform() {
    var self = this;

    this._localScale = new Vector3(1,1,1);
    this._localRotation = new Quaternion();
    this._parent = null;
    this._local = new Matrix();
    this._forward = new Vector3();
    this._up = new Vector3();
    this._right = new Vector3();

    Object.defineProperty(this, 'localPosition', {
        get: (function() {
            var _localPosition = new Vector3();

            return function() {
                return _localPosition.set(
                    self._local[12], self._local[13], self._local[14]);
            }
        })(),
        set: function(value) {
            self._local[12] = value[0];
            self._local[13] = value[1];
            self._local[14] = value[2];
        }
    });

    Object.defineProperty(this, 'position', {
        get: (function() {
            var _position = new Vector3();

            return function() {
                _position.set(
                    self._local[12], self._local[13], self._local[14]);
                if (!self._parent) {
                    return _position;
                }
                return _position.transform(self._parent.world, _position);
            }
        })(),
        set: (function() {
            var _inverted = new Matrix();
            var _position = new Vector3();

            return function(value) {
                if (!self._parent) {
                    self._local[12] = value[0];
                    self._local[13] = value[1];
                    self._local[14] = value[2];
                } else {
                    // Convert world space to local.
                    self._parent.world.invert(_inverted);
                    value.transform(_inverted, _position);
                    self._local[12] = _position[0];
                    self._local[13] = _position[1];
                    self._local[14] = _position[2];
                }
            }
        })()
    });

    Object.defineProperty(this, 'localRotation', {
        get: function() {
            return self._localRotation;
        },
        set: (function() {
            var _diff = new Vector3();

            return function(value) {
                value.eulerAngles.sub(self._localRotation.eulerAngles, _diff);
                self.rotate(_diff[0], _diff[1], _diff[2]);
            }
        })()
    });

    Object.defineProperty(this, 'rotation', {
        get: (function() {
            var _rotation = new Quaternion();

            return function() {
                if (!self._parent) {
                    return self._localRotation;
                }
                return self._parent.rotation.mul(
                    self._localRotation, _rotation);
            }
        })()
    });

    Object.defineProperty(this, 'localScale', {
        get: function() {
            return self._localScale;
        },
        set: (function() {
            var _diff = new Vector3();

            return function(value) {
                value.div(self._localScale, _diff);
                self.scale(_diff[0], _diff[1], _diff[2]);
            }
        })()
    });

    Object.defineProperty(this, 'scaling', {
        get: (function() {
            var _scaling = new Vector3();

            return function() {
                if (!self._parent) {
                    return self._localScale;
                }
                return self._parent.scaling.mul(
                    self._localScale, _scaling);
            }
        })()
    });

    Object.defineProperty(this, 'eulerAngles', {
        get: function() {
            return self.rotation.eulerAngles;
        }
    });

    Object.defineProperty(this, 'world', {
        get: (function() {
            var _world = new Matrix();

            return function() {
                if (!self._parent) {
                    return self._local;
                }
                return self._parent.world.multiply(
                    self._local, _world);
            }
        })()
    });

}

Transform.prototype.parent = function(parent) {
    if (parent instanceof Transform) {
        this._parent = parent;
    } else if (parent instanceof Entity) {
        this._parent = parent.transform;
    } else {
        return this._parent;
    }
};

Transform.prototype.translate = function(x, y, z) {
    this._local.translate(x,y,z);
};

Transform.prototype.rotate = function(x, y, z) {
    this._local.rotate(x,y,z);
    this._localRotation.rotateX(x);
    this._localRotation.rotateY(y);
    this._localRotation.rotateZ(z);
};

Transform.prototype.scale = (function() {
    var _scale = new Vector3();

    return function(x, y, z) {
        this._local.scale(x,y,z);
        _scale.set(x,y,z);
        this._localScale.mul(_scale, this._localScale);
    }
})();

Transform.prototype.move = (function() {
    var _move = new Vector3();

    return function(x, y, z) {
        if (arguments.length === 1) {
            _move.set(arguments[0]);
            this.position = this.position.add(_move, _move);
        } else if (arguments.length === 3) {
            _move.set(x,y,z);
            this.position = this.position.add(_move, _move);
        } else {
            throw new TypeError('Wrong number of arguments.');
        }
    }
})();

Transform.prototype.forward = function() {
    this._forward = _forward.transform(this._localRotation, this._forward);
    return this._forward.normalize(this._forward);
};

Transform.prototype.up = function() {
    this._up = _up.transform(this._localRotation, this._up);
    return this._up.normalize(this._up);
};

Transform.prototype.right = function() {
    this._right = _right.transform(this._localRotation, this._right);
    return this._right.normalize(this._right);
};

function Component() {
    this.enabled = true;
    this.visible = true;
}

Component.prototype.update = function(elapsed) {
};

Component.prototype.draw = function() {
};

module.exports.Entity = Entity;
module.exports.Transform = Transform;
module.exports.Component = Component;<|endoftext|>


<commit_before>var util = require('util');

var ClientError = module.exports.ClientError = function (body, statusCode) {
    this.message = body;
    this.statusCode = statusCode;
};

util.inherits(ClientError, Error);

var AuthorizationError = module.exports.AuthorizationError = function (body) {
    ClientError.call(this, body, 403);
};

util.inherits(AuthorizationError, ClientError);

var NotFoundError = module.exports.NotFoundError = function (body) {
    ClientError.call(this, body, 404);
};

util.inherits(NotFoundError, ClientError);

module.exports.get = function (resp) {
    switch (resp.statusCode) {
    case 403:
        return new AuthorizationError(resp.body);
    case 404:
        return new NotFoundError(resp.body);
    default:
        return new ClientError(resp.body, resp.statusCode);
    }

};
<commit_msg>New error type<commit_after>var util = require('util');

var ClientError = module.exports.ClientError = function (body, statusCode) {
    this.message = body;
    this.statusCode = statusCode;
};

util.inherits(ClientError, Error);

var InvalidDataError = module.exports.InvalidDataError = function (body) {
    ClientError.call(this, body, 400);
};

util.inherits(InvalidDataError, ClientError);

var AuthorizationError = module.exports.AuthorizationError = function (body) {
    ClientError.call(this, body, 403);
};

util.inherits(AuthorizationError, ClientError);

var NotFoundError = module.exports.NotFoundError = function (body) {
    ClientError.call(this, body, 404);
};

util.inherits(NotFoundError, ClientError);

module.exports.get = function (resp) {
    switch (resp.statusCode) {
    case 400:
        return new InvalidDataError(resp.body);
    case 403:
        return new AuthorizationError(resp.body);
    case 404:
        return new NotFoundError(resp.body);
    default:
        return new ClientError(resp.body, resp.statusCode);
    }

};
<|endoftext|>


<commit_before>var assert = require('assert');
var chalk = require('chalk');
var TokenAssert = require('./token-assert');

/**
 * Set of errors for specified file.
 *
 * @name Errors
 * @param {JsFile} file
 * @param {Boolean} verbose
 */
var Errors = function(file, verbose) {
    this._errorList = [];
    this._file = file;
    this._currentRule = '';
    this._verbose = verbose || false;

    /**
     * @type {TokenAssert}
     * @public
     */
    this.assert = new TokenAssert(file);
    this.assert.on('error', this._addError.bind(this));
};

Errors.prototype = {
    /**
     * Adds style error to the list
     *
     * @param {String} message
     * @param {cst.types.Element} element
     * @param {Number} [offset] relative offset
     */
    add: function(message, element, offset) {
        this._addError({
            message: message,
            element: element,
            offset: offset || element.sourceCodeLength
        });
    },

    /**
     * Adds style error to the list
     *
     * @param {Object} errorInfo
     */
    cast: function(errorInfo) {
        var additional = errorInfo.additional;

        assert(typeof additional !== undefined,
               '`additional` argument should not be empty');

        this._addError(errorInfo);
    },

    /**
     * Adds error to error list.
     *
     * @param {Object} errorInfo
     * @private
     */
    _addError: function(errorInfo) {
        if (!this._file.isEnabledRule(this._currentRule, errorInfo.element)) {
            return;
        }

        var position = Errors.getPosition(errorInfo.element);
        this._errorList.push({
            filename: this._file.getFilename(),
            rule: this._currentRule,
            message: this._prepareMessage(errorInfo),
            element: errorInfo.element,
            line: position.line,
            column: position.column,
            offset: errorInfo.offset,
            additional: errorInfo.additional,
            fixed: errorInfo.fixed
        });
    },

    /**
     * Prepare error message.
     *
     * @param {Object} errorInfo
     * @private
     */
    _prepareMessage: function(errorInfo) {
        if (this._verbose && this._currentRule) {
            return this._currentRule + ': ' + errorInfo.message;
        }

        return errorInfo.message;
    },

    /**
     * Returns style error list.
     *
     * @returns {Object[]}
     */
    getErrorList: function() {
        return this._errorList;
    },

    /**
     * Returns filename of file this error list is for.
     *
     * @returns {String}
     */
    getFilename: function() {
        return this._file.getFilename();
    },

    /**
     * Returns true if no errors are added.
     *
     * @returns {Boolean}
     */
    isEmpty: function() {
        return this._errorList.length === 0;
    },

    /**
     * Returns amount of errors added by the rules.
     *
     * @returns {Number}
     */
    getValidationErrorCount: function() {
        return this._errorList.filter(function(error) {
            return error.rule !== 'parseError' && error.rule !== 'internalError';
        });
    },

    /**
     * Returns amount of errors added by the rules.
     *
     * @returns {Number}
     */
    getErrorCount: function() {
        return this._errorList.length;
    },

    /**
     * Strips error list to the specified length.
     *
     * @param {Number} length
     */
    stripErrorList: function(length) {
        this._errorList.splice(length);
    },

    /**
     * Filters out errors based on the supplied filter function
     *
     * @param {Function} filter
     */
    filter: function(filter) {
        this._errorList = this._errorList.filter(filter);
    },

    /**
     * Formats error for further output.
     *
     * @param {Object} error
     * @param {Boolean} [colorize = false]
     * @returns {String}
     */
    explainError: function(error, colorize) {
        var lineNumber = error.line - 1;
        var lines = this._file.getLines();
        var result = [
            renderLine(lineNumber, lines[lineNumber], colorize),
            renderPointer(error.column, colorize)
        ];
        var i = lineNumber - 1;
        var linesAround = 2;
        while (i >= 0 && i >= (lineNumber - linesAround)) {
            result.unshift(renderLine(i, lines[i], colorize));
            i--;
        }
        i = lineNumber + 1;
        while (i < lines.length && i <= (lineNumber + linesAround)) {
            result.push(renderLine(i, lines[i], colorize));
            i++;
        }
        result.unshift(formatErrorMessage(error.message, this.getFilename(), colorize));
        return result.join('\n');
    },

    /**
     * Sets the current rule so that errors are aware
     * of which rule triggered them.
     *
     * @param {String} rule
     */
    setCurrentRule: function(rule) {
        this._currentRule = rule;
    }

};

/**
 * Formats error message header.
 *
 * @param {String} message
 * @param {String} filename
 * @param {Boolean} colorize
 * @returns {String}
 */
function formatErrorMessage(message, filename, colorize) {
    return (colorize ? chalk.bold(message) : message) +
        ' at ' +
        (colorize ? chalk.green(filename) : filename) + ' :';
}

/**
 * Simple util for prepending spaces to the string until it fits specified size.
 *
 * @param {String} s
 * @param {Number} len
 * @returns {String}
 */
function prependSpaces(s, len) {
    while (s.length < len) {
        s = ' ' + s;
    }
    return s;
}

/**
 * Renders single line of code in style error formatted output.
 *
 * @param {Number} n line number
 * @param {String} line
 * @param {Boolean} [colorize = false]
 * @returns {String}
 */
function renderLine(n, line, colorize) {
    // Convert tabs to spaces, so errors in code lines with tabs as indention symbol
    // could be correctly rendered, plus it will provide less verbose output
    line = line.replace(/\t/g, ' ');

    // "n + 1" to print lines in human way (counted from 1)
    var lineNumber = prependSpaces((n + 1).toString(), 5) + ' |';
    return ' ' + (colorize ? chalk.grey(lineNumber) : lineNumber) + line;
}

/**
 * Renders pointer:
 * ---------------^
 *
 * @param {Number} column
 * @param {Boolean} [colorize = false]
 * @returns {String}
 */
function renderPointer(column, colorize) {
    var res = (new Array(column + 9)).join('-') + '^';
    return colorize ? chalk.grey(res) : res;
}

/**
 * Get position of the element
 * TODO: 3.0 - need to account for non-zero offset
 *
 * @param {Object} element
 * @param {Number} offset
 * @return {Object}
 */
Errors.getPosition = function(element) {
    return {
        line: element && element.loc && element.loc.start.line,
        column: element && element.loc && element.loc.end.column
    };
}

module.exports = Errors;
<commit_msg>errors: take start column not the end one<commit_after>var assert = require('assert');
var chalk = require('chalk');
var TokenAssert = require('./token-assert');

/**
 * Set of errors for specified file.
 *
 * @name Errors
 * @param {JsFile} file
 * @param {Boolean} verbose
 */
var Errors = function(file, verbose) {
    this._errorList = [];
    this._file = file;
    this._currentRule = '';
    this._verbose = verbose || false;

    /**
     * @type {TokenAssert}
     * @public
     */
    this.assert = new TokenAssert(file);
    this.assert.on('error', this._addError.bind(this));
};

Errors.prototype = {
    /**
     * Adds style error to the list
     *
     * @param {String} message
     * @param {cst.types.Element} element
     * @param {Number} [offset] relative offset
     */
    add: function(message, element, offset) {
        this._addError({
            message: message,
            element: element,
            offset: offset || element.sourceCodeLength
        });
    },

    /**
     * Adds style error to the list
     *
     * @param {Object} errorInfo
     */
    cast: function(errorInfo) {
        var additional = errorInfo.additional;

        assert(typeof additional !== undefined,
               '`additional` argument should not be empty');

        this._addError(errorInfo);
    },

    /**
     * Adds error to error list.
     *
     * @param {Object} errorInfo
     * @private
     */
    _addError: function(errorInfo) {
        if (!this._file.isEnabledRule(this._currentRule, errorInfo.element)) {
            return;
        }

        var position = Errors.getPosition(errorInfo.element);
        this._errorList.push({
            filename: this._file.getFilename(),
            rule: this._currentRule,
            message: this._prepareMessage(errorInfo),
            element: errorInfo.element,
            line: position.line,
            column: position.column,
            offset: errorInfo.offset,
            additional: errorInfo.additional,
            fixed: errorInfo.fixed
        });
    },

    /**
     * Prepare error message.
     *
     * @param {Object} errorInfo
     * @private
     */
    _prepareMessage: function(errorInfo) {
        if (this._verbose && this._currentRule) {
            return this._currentRule + ': ' + errorInfo.message;
        }

        return errorInfo.message;
    },

    /**
     * Returns style error list.
     *
     * @returns {Object[]}
     */
    getErrorList: function() {
        return this._errorList;
    },

    /**
     * Returns filename of file this error list is for.
     *
     * @returns {String}
     */
    getFilename: function() {
        return this._file.getFilename();
    },

    /**
     * Returns true if no errors are added.
     *
     * @returns {Boolean}
     */
    isEmpty: function() {
        return this._errorList.length === 0;
    },

    /**
     * Returns amount of errors added by the rules.
     *
     * @returns {Number}
     */
    getValidationErrorCount: function() {
        return this._errorList.filter(function(error) {
            return error.rule !== 'parseError' && error.rule !== 'internalError';
        });
    },

    /**
     * Returns amount of errors added by the rules.
     *
     * @returns {Number}
     */
    getErrorCount: function() {
        return this._errorList.length;
    },

    /**
     * Strips error list to the specified length.
     *
     * @param {Number} length
     */
    stripErrorList: function(length) {
        this._errorList.splice(length);
    },

    /**
     * Filters out errors based on the supplied filter function
     *
     * @param {Function} filter
     */
    filter: function(filter) {
        this._errorList = this._errorList.filter(filter);
    },

    /**
     * Formats error for further output.
     *
     * @param {Object} error
     * @param {Boolean} [colorize = false]
     * @returns {String}
     */
    explainError: function(error, colorize) {
        var lineNumber = error.line - 1;
        var lines = this._file.getLines();
        var result = [
            renderLine(lineNumber, lines[lineNumber], colorize),
            renderPointer(error.column, colorize)
        ];
        var i = lineNumber - 1;
        var linesAround = 2;
        while (i >= 0 && i >= (lineNumber - linesAround)) {
            result.unshift(renderLine(i, lines[i], colorize));
            i--;
        }
        i = lineNumber + 1;
        while (i < lines.length && i <= (lineNumber + linesAround)) {
            result.push(renderLine(i, lines[i], colorize));
            i++;
        }
        result.unshift(formatErrorMessage(error.message, this.getFilename(), colorize));
        return result.join('\n');
    },

    /**
     * Sets the current rule so that errors are aware
     * of which rule triggered them.
     *
     * @param {String} rule
     */
    setCurrentRule: function(rule) {
        this._currentRule = rule;
    }

};

/**
 * Formats error message header.
 *
 * @param {String} message
 * @param {String} filename
 * @param {Boolean} colorize
 * @returns {String}
 */
function formatErrorMessage(message, filename, colorize) {
    return (colorize ? chalk.bold(message) : message) +
        ' at ' +
        (colorize ? chalk.green(filename) : filename) + ' :';
}

/**
 * Simple util for prepending spaces to the string until it fits specified size.
 *
 * @param {String} s
 * @param {Number} len
 * @returns {String}
 */
function prependSpaces(s, len) {
    while (s.length < len) {
        s = ' ' + s;
    }
    return s;
}

/**
 * Renders single line of code in style error formatted output.
 *
 * @param {Number} n line number
 * @param {String} line
 * @param {Boolean} [colorize = false]
 * @returns {String}
 */
function renderLine(n, line, colorize) {
    // Convert tabs to spaces, so errors in code lines with tabs as indention symbol
    // could be correctly rendered, plus it will provide less verbose output
    line = line.replace(/\t/g, ' ');

    // "n + 1" to print lines in human way (counted from 1)
    var lineNumber = prependSpaces((n + 1).toString(), 5) + ' |';
    return ' ' + (colorize ? chalk.grey(lineNumber) : lineNumber) + line;
}

/**
 * Renders pointer:
 * ---------------^
 *
 * @param {Number} column
 * @param {Boolean} [colorize = false]
 * @returns {String}
 */
function renderPointer(column, colorize) {
    var res = (new Array(column + 9)).join('-') + '^';
    return colorize ? chalk.grey(res) : res;
}

/**
 * Get position of the element
 * TODO: 3.0 - need to account for non-zero offset
 *
 * @param {Object} element
 * @param {Number} offset
 * @return {Object}
 */
Errors.getPosition = function(element) {
    return {
        line: element && element.loc && element.loc.start.line,
        column: element && element.loc && element.loc.start.column
    };
}

module.exports = Errors;
<|endoftext|>


<commit_before>/**

  errors.js

  Provides basic and custom error pages. Note that error handling in express breaks
  if the handler is attached to a router before being used in the server. As a
  result, we require the "server" variable.

**/

//
//  Dependencies
//

var Utils = require("./utils");

//
//  Helpers
//

/**

  Checks for error-related status code.

  @param {Number} code The status code
  @returns {Boolean} Returns true if is an error code

**/

function isErrorCode(code) {
  return (code && code >= 400);
}

/**

  Default page for status code errors

  @param {Object} context Data for templating
  @returns {String} The rendered html

**/

function renderDefault(context, next) {
  var html = "";
  html += "
";
  html += context.statusCode + "

";
  html += "Our apologies but you've found an error!
";
  next(null, html);
}

//
//  Exports
//

module.exports = function(server, handler) {

  var routes = global.shipp.config.routes,
      render,
      views;

  // Search our views directories for a file named errors.html. Alternative
  // extensions are permitted and will inform the compiler how to compile.
  for (var route in routes) {
    if ("views" === routes[route].type) {
      views = Utils.readDirectory(routes[route].path, false);

      for (var i = 0, n = views.length; i < n; i++)
        if (/errors?/i.test(views[i].name)) {
          render = global.shipp.pipelines.compileFile.bind(global.shipp.pipelines, views[i].path);
          break;
        }

      if (render) break;
    }
  }

  // Fall back to default rendered
  render = render || renderDefault;

  // Attach 404s
  server.use(function(req, res, next) {
    if (!isErrorCode(res.statusCode)) res.status(404);
    next(new Error("Not Found"));
  });

  // Attach error and render
  server.use(function(err, req, res, next) {

    var context;

    if (err && !isErrorCode(res.statusCode)) res.statusCode = 500;

    // Our compilation engine is async...
    context = { statusCode : res.statusCode };
    render(context, function(renderError, html) {

      // Proceeds to default or user error-handling
      function done(renderError, html) {
        res.send(html);
        next(err);
      }

      // If compilation engine fails, fall back to default engine
      if (renderError) return renderDefault(context, done);
      done(null, html);

    });

  });

  // Use user-provided error handler (otherwise pass errors to express)
  if (handler) {
    if (4 !== handler.length) throw new Error("Error handler must have 4 arguments");
    server.use(handler);
  }

};
<commit_msg>Include url in 404 errors<commit_after>/**

  errors.js

  Provides basic and custom error pages. Note that error handling in express breaks
  if the handler is attached to a router before being used in the server. As a
  result, we require the "server" variable.

**/

//
//  Dependencies
//

var Utils = require("./utils");

//
//  Helpers
//

/**

  Checks for error-related status code.

  @param {Number} code The status code
  @returns {Boolean} Returns true if is an error code

**/

function isErrorCode(code) {
  return (code && code >= 400);
}

/**

  Default page for status code errors

  @param {Object} context Data for templating
  @returns {String} The rendered html

**/

function renderDefault(context, next) {
  var html = "";
  html += "
";
  html += context.statusCode + "

";
  html += "Our apologies but you've found an error!
";
  next(null, html);
}

//
//  Exports
//

module.exports = function(server, handler) {

  var routes = global.shipp.config.routes,
      render,
      views;

  // Search our views directories for a file named errors.html. Alternative
  // extensions are permitted and will inform the compiler how to compile.
  for (var route in routes) {
    if ("views" === routes[route].type) {
      views = Utils.readDirectory(routes[route].path, false);

      for (var i = 0, n = views.length; i < n; i++)
        if (/errors?/i.test(views[i].name)) {
          render = global.shipp.pipelines.compileFile.bind(global.shipp.pipelines, views[i].path);
          break;
        }

      if (render) break;
    }
  }

  // Fall back to default rendered
  render = render || renderDefault;

  // Attach 404s
  server.use(function(req, res, next) {
    if (!isErrorCode(res.statusCode)) res.status(404);
    next(new Error("Not Found: " + req.url));
  });

  // Attach error and render
  server.use(function(err, req, res, next) {

    var context;

    if (err && !isErrorCode(res.statusCode)) res.statusCode = 500;

    // Our compilation engine is async...
    context = { statusCode : res.statusCode };
    render(context, function(renderError, html) {

      // Proceeds to default or user error-handling
      function done(renderError, html) {
        res.send(html);
        next(err);
      }

      // If compilation engine fails, fall back to default engine
      if (renderError) return renderDefault(context, done);
      done(null, html);

    });

  });

  // Use user-provided error handler (otherwise pass errors to express)
  if (handler) {
    if (4 !== handler.length) throw new Error("Error handler must have 4 arguments");
    server.use(handler);
  }

};
<|endoftext|>


<commit_before>'use strict';

/**
 * Application errors
 */
module.exports = function(app) {

  app.use(function(req, res, next) {
    var err = new Error('Nothing routes to this URL: ' + req.url);
    err.status = 404;
    next(err);
  });

  app.use(function(err, req, res, next) {  // jshint ignore:line
    res.status(err.status || 500);

    console.error(err.stack);

    if (err.status === 404) {
      res.render('404.jade', {'req': req});
    } else {
      var acceptHeader = req.headers.accept || '';
      if (acceptHeader.indexOf('application/json') !== -1) {
        res.json({
          title: 'An error occurred',
          message: err.message,
          error: {}
        });
      } else {
        res.render('500.jade', {
          'req': req,
          'err': err
        });
      }
    }
  });
};
<commit_msg>Errors should only be rendered as HTML when the browser is not accepting json<commit_after>'use strict';

/**
 * Application errors
 */
module.exports = function(app) {

  app.use(function(req, res, next) {
    var err = new Error('Nothing routes to this URL: ' + req.url);
    err.status = 404;
    next(err);
  });

  app.use(function(err, req, res, next) {  // jshint ignore:line
    res.status(err.status || 500);

    console.error(err.stack);

    var acceptHeader = req.headers.accept || '';
    if (acceptHeader.indexOf('application/json') !== -1) {
      res.json({
        title: 'An error occurred',
        message: err.message,
        error: {}
      });
    } else {
      if (err.status === 404) {
        res.render('404.jade', {
          'req': req
        });
      } else {
        res.render('500.jade', {
          'req': req,
          'err': err
        });
      }
    }
  });
};
<|endoftext|>


<commit_before>// @flow
/* globals Request, Response, Headers */

import {AsyncStorage} from 'react-native'
import CachePolicy from 'http-cache-semantics'
import fromPairs from 'lodash/fromPairs'

const ROOT = 'fp'

async function serializeResponse(r: Request) {
	let {headers, status, statusText} = r
	let body = await r.clone().text()
	if ('entries' in headers) {
		headers = [...headers.entries()]
	}
	return {headers, status, statusText, body}
}

function responseForCachePolicy(response: Response) {
	// Request and response must have a headers property with all header names
	// in lower case. url, status and method are optional (defaults are any
	// URL, status 200, and GET method).

	// const request = {
	//     url: '/',
	//     method: 'GET',
	//     headers: {
	//         accept: '*/*',
	//     },
	// };

	let {url, method, headers} = response

	// now we need to convert from a Headers object to an object-of-headers

	headers = fromPairs([...Object.entries(headers)])

	return {url, method, headers}
}

function requestForCachePolicy(request: Request) {
	// Request and response must have a headers property with all header names
	// in lower case. url, status and method are optional (defaults are any
	// URL, status 200, and GET method).

	// const response = {
	//     status: 200,
	//     headers: {
	//         'cache-control': 'public, max-age=7234',
	//     },
	// };

	let {status, headers} = request

	// now we need to convert from a Headers object to an object-of-headers

	headers = fromPairs([...Object.entries(headers)])

	return {status, headers}
}

function headersObjectToHeadersClass(headers: {[string]: string}) {
	let updatedHeaders = new Headers()
	for (let [key, value] of Object.entries(headers)) {
		updatedHeaders.append(key, value)
	}
	return updatedHeaders
}

async function cacheItem(args: {
	key: string,
	response: Response,
	policy: CachePolicy,
}) {
	let {key, response, policy} = args
	response = await serializeResponse(response)

	await AsyncStorage.multiSet([
		[`${ROOT}:${key}:response`, JSON.stringify(response)],
		[`${ROOT}:${key}:policy`, JSON.stringify(policy.toObject())],
		[`${ROOT}:${key}:ttl`, JSON.stringify(policy.timeToLive())],
	])
}

async function getItem(
	key: string,
): Promise<{response: Response, policy: ?CachePolicy}> {
	let [[, response], [, policy]] = await AsyncStorage.multiGet([
		`${ROOT}:${key}:response`,
		`${ROOT}:${key}:policy`,
	])

	if (!response) {
		return {response, policy: null}
	}

	let {body, ...init} = JSON.parse(response)

	return {
		response: new Response(body, init),
		policy: CachePolicy.fromObject(JSON.parse(policy)),
	}
}

// global.AsyncStorage = AsyncStorage
// global.getItem = getItem
// global.cachedFetch = cachedFetch

export async function cachedFetch(request: Request): Promise<Response> {
	let {url} = request

	let key = `urlcache:${url}`
	let {response: oldResponse, policy: oldPolicy} = await getItem(key)

	// if nothing has ever been cached, go fetch it
	if (!oldPolicy) {
		console.log(`fetch(${request.url}): no policy cached; fetching`)

		let response = await fetch(request)
		// console.log(response.headers)
		// global.xyz = response.headers
		let policy = new CachePolicy(requestForCachePolicy(request), responseForCachePolicy(response))

		if (policy.storable()) {
			console.log(`fetch(${request.url}): caching`)
			await cacheItem({key, response, policy})
		} else {
			console.log(`fetch(${request.url}): not cachable`)
		}

		return response
	}

	// if we can re-use the cached data, return it
	if (oldPolicy.satisfiesWithoutRevalidation(requestForCachePolicy(request))) {
		console.log(`fetch(${request.url}): fresh; returning`)
		oldResponse.headers = headersObjectToHeadersClass(oldPolicy.responseHeaders())
		return oldResponse
	}

	// otherwise, we're serving requests from the cache

	// Change the request to ask the origin server if the cached response can be used
	request.headers = headersObjectToHeadersClass(oldPolicy.revalidationHeaders(requestForCachePolicy(request)))

	// console.log('revalidationHeaders', oldPolicy.revalidationHeaders(request))
	// console.log('old headers', oldPolicy._resHeaders)

	console.log(`fetch(${request.url}): stale; validating`)

	// Send request to the origin server. The server may respond with status 304
	let newResponse = await fetch(request)

	// Create updated policy and combined response from the old and new data
	let {policy, modified} = oldPolicy.revalidatedPolicy(requestForCachePolicy(request), responseForCachePolicy(newResponse))

	if (modified) {
		console.log(`fetch(${request.url}): validated; did change`)
		console.log('old', oldPolicy.responseHeaders())
		console.log('new', policy.responseHeaders())
	} else {
		console.log(`fetch(${request.url}): validated; 304 no change`)
	}

	let response = modified ? newResponse : oldResponse

	// Update the cache with the newer/fresher response
	await cacheItem({key, policy, response})

	// And proceed returning cached response as usual
	response.headers = headersObjectToHeadersClass(policy.responseHeaders())

	console.log(`fetch(${request.url}): returning updated response`)

	return response
}
<commit_msg>clean up naming and documentation in cachedFetch<commit_after>// @flow
/* globals Request, Response, Headers */

import {AsyncStorage} from 'react-native'
import CachePolicy from 'http-cache-semantics'
import fromPairs from 'lodash/fromPairs'

const ROOT = 'fp'
const debug = false

// Pulls out the important bits from a Request for storage
async function serializeResponse(r: Request) {
	let {headers, status, statusText} = r
	let body = await r.clone().text()
	if ('entries' in headers) {
		headers = [...headers.entries()]
	}
	return {headers, status, statusText, body}
}

// Converts a whatwg Headers instance into a plain object for http-cache-semantics
function headersInstanceToObject(headers: Headers) {
	return fromPairs([...Object.entries(headers)])
}

// Converts a whatwg Response into a plain object for http-cache-semantics
function responseForCachePolicy({url, method, headers}: Response) {
	// Response must have a headers property with all header names in lower
	// case. `url` and `method` are optional.

	return {url, method, headers: headersInstanceToObject(headers)}
}

// Converts a whatwg Request into a plain object for http-cache-semantics
function requestForCachePolicy({status, headers}: Request) {
	// Request must have a headers property with all header names in lower
	// case. `url` and `status` are optional.

	return {status, headers: headersInstanceToObject(headers)}
}

// Does the magic: stores a Request into AsyncStorage
type CacheItemArgs = {key: string, response: Response, policy: CachePolicy}
async function cacheItem({key, response, policy}: CacheItemArgs) {
	response = await serializeResponse(response)

	await AsyncStorage.multiSet([
		[`${ROOT}:${key}:response`, JSON.stringify(response)],
		[`${ROOT}:${key}:policy`, JSON.stringify(policy.toObject())],
		[`${ROOT}:${key}:ttl`, JSON.stringify(policy.timeToLive())],
	])
}

// Does more magic: gets a Request from AsyncStorage
type GetItemResult = {response: Response, policy: ?CachePolicy}
async function getItem(key: string): Promise<GetItemResult> {
	let [[, response], [, policy]] = await AsyncStorage.multiGet([
		`${ROOT}:${key}:response`,
		`${ROOT}:${key}:policy`,
	])

	if (!response) {
		return {response, policy: null}
	}

	let {body, ...init} = JSON.parse(response)

	return {
		response: new Response(body, init),
		policy: CachePolicy.fromObject(JSON.parse(policy)),
	}
}

// Requests an URL and retrieves it from the cache if possible
export async function cachedFetch(request: Request): Promise<Response> {
	let {url} = request

	let cachePolicyRequest = requestForCachePolicy(request)

	let key = `urlcache:${url}`
	let {response: oldResponse, policy: oldPolicy} = await getItem(key)

	// If nothing has ever been cached, go fetch it
	if (!oldPolicy) {
		debug && console.log(`fetch(${request.url}): no policy cached; fetching`)

		let response = await fetch(request)
		let cachePolicyResponse = responseForCachePolicy(response)

		let policy = new CachePolicy(cachePolicyRequest, cachePolicyResponse)

		if (policy.storable()) {
			debug && console.log(`fetch(${request.url}): caching`)
			await cacheItem({key, response, policy})
		} else {
			debug && console.log(`fetch(${request.url}): not cachable`)
		}

		return response
	}

	// If we can re-use the cached data, return it; otherwise, we're serving requests from the cache
	if (oldPolicy.satisfiesWithoutRevalidation(cachePolicyRequest)) {
		debug && console.log(`fetch(${request.url}): fresh; returning`)
		oldResponse.headers = new Headers(oldPolicy.responseHeaders())
		return oldResponse
	}

	// Update the request to ask the origin server if the cached response can be used
	request.headers = new Headers(
		oldPolicy.revalidationHeaders(cachePolicyRequest),
	)

	debug && console.log(`fetch(${request.url}): stale; validating`)

	// Send request to the origin server. The server may respond with status 304
	let newResponse = await fetch(request)
	let newCachePolicyResponse = responseForCachePolicy(newResponse)

	// Create updated policy and combined response from the old and new data
	let {policy, modified} = oldPolicy.revalidatedPolicy(
		cachePolicyRequest,
		newCachePolicyResponse,
	)

	if (debug) {
		if (modified) {
			console.log(`fetch(${request.url}): validated; did change`)
		} else {
			console.log(`fetch(${request.url}): validated; 304 no change`)
		}
	}

	let response = modified ? newResponse : oldResponse

	// Update the cache with the newer/fresher response
	await cacheItem({key, policy, response})

	// And proceed returning cached response as usual
	response.headers = new Headers(policy.responseHeaders())

	debug && console.log(`fetch(${request.url}): returning updated response`)

	return response
}
<|endoftext|>