<commit_before>// Copyright 2014-2017, University of Colorado Boulder

/**
 * Radio buttons. See ButtonsView for example usage.
 *
 * This type creates a group of radio buttons in either a horizontal or vertical formation.
 * Each button inherits from RectangularButtonView, and can take a Scenery Node as its content.
 * A typical use case is when you want to have different modes of a view to select from. Typically,
 * RadioButtonGroup radio buttons display some kind of icon or image, but that is not mandatory.
 *
 * @author Aaron Davis (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  var Color = require( 'SCENERY/util/Color' );
  var ColorConstants = require( 'SUN/ColorConstants' );
  var FocusHighlightPath = require( 'SCENERY/accessibility/FocusHighlightPath' );
  var inherit = require( 'PHET_CORE/inherit' );
  var InstanceRegistry = require( 'PHET_CORE/documentation/InstanceRegistry' );
  var LayoutBox = require( 'SCENERY/nodes/LayoutBox' );
  var Property = require( 'AXON/Property' );
  var RadioButtonGroupAppearance = require( 'SUN/buttons/RadioButtonGroupAppearance' );
  var RadioButtonGroupMember = require( 'SUN/buttons/RadioButtonGroupMember' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );

  // constants
  var BUTTON_CONTENT_X_ALIGN_VALUES = [ 'center', 'left', 'right' ];
  var BUTTON_CONTENT_Y_ALIGN_VALUES = [ 'center', 'top', 'bottom' ];

  // a11y - integer to allow distinct name attributes for each set of RadioButtonGroupMembers, increments with each
  // instantiation of RadioButtonGroup
  var instanceCount = 0;

  /**
   * RadioButtonGroup constructor.
   *
   * @param {Property} property
   * @param {Array} contentArray an array of objects that have two keys each: value and node the node key holds a
   * scenery Node that is the content for a given radio button. the value key should hold the value that the property
   * takes on for the corresponding node to be selected. Optionally, these objects can have an attribute 'label', which
   * is a {Node} used to label the button. You can also pass some specific a11y options
   * (labelContent/descriptionContent) through, see "new RadioButtonGroupMember" construction.
   * @param {Object} [options]
   * @constructor
   */
  function RadioButtonGroup( property, contentArray, options ) {
    options = _.extend( {
      tandem: Tandem.required,

      // a11y
      tagName: 'ul',
      labelTagName: 'h3',
      ariaRole: 'radiogroup',
      groupFocusHighlight: true
    }, options );

    // increment instance count
    instanceCount++;

    assert && assert( !options.hasOwnProperty( 'children' ), 'Cannot pass in children to a RadioButtonGroup, ' +
                                                             'create siblings in the parent node instead' );

    // make sure every object in the content array has properties 'node' and 'value'
    assert && assert( _.every( contentArray, function( obj ) {
      return obj.hasOwnProperty( 'node' ) && obj.hasOwnProperty( 'value' );
    } ), 'contentArray must be an array of objects with properties "node" and "value"' );

    var i; // for loops

    // make sure that each value passed into the contentArray is unique
    var uniqueValues = [];
    for ( i = 0; i < contentArray.length; i++ ) {
      if ( uniqueValues.indexOf( contentArray[ i ].value ) < 0 ) {
        uniqueValues.push( contentArray[ i ].value );
      }
      else {
        throw new Error( 'Duplicate value: "' + contentArray[ i ].value + '" passed into RadioButtonGroup.js' );
      }
    }

    // make sure that the property passed in currently has a value from the contentArray
    if ( uniqueValues.indexOf( property.get() ) === -1 ) {
      throw new Error( 'The property passed in to RadioButtonGroup has an illegal value "' + property.get() +
                       '" that is not present in the contentArray' );
    }

    var defaultOptions = {

      // LayoutBox options (super class of RadioButtonGroup)
      spacing: 10,
      orientation: 'vertical',

      enabledProperty: new Property( true ), // whether or not the set of radio buttons as a whole is enabled

      // The fill for the rectangle behind the radio buttons.  Default color is bluish color, as in the other button library.
      baseColor: ColorConstants.LIGHT_BLUE,
      disabledBaseColor: ColorConstants.LIGHT_GRAY,

      // Opacity can be set separately for the buttons and button content.
      selectedButtonOpacity: 1,
      deselectedButtonOpacity: 0.6,
      selectedContentOpacity: 1,
      deselectedContentOpacity: 0.6,
      overButtonOpacity: 0.8,
      overContentOpacity: 0.8,

      selectedStroke: 'black',
      deselectedStroke: new Color( 50, 50, 50 ),
      selectedLineWidth: 1.5,
      deselectedLineWidth: 1,

      // The following options specify highlight behavior overrides, leave as null to get the default behavior
      // Note that highlighting applies only to deselected buttons
      overFill: null,
      overStroke: null,
      overLineWidth: null,

      // These margins are *within* each button
      buttonContentXMargin: 5,
      buttonContentYMargin: 5,

      // alignment of the content nodes *within* each button
      buttonContentXAlign: 'center', // {string} see BUTTON_CONTENT_X_ALIGN_VALUES
      buttonContentYAlign: 'center', // {string} see BUTTON_CONTENT_Y_ALIGN_VALUES

      // TouchArea expansion
      touchAreaXDilation: 0,
      touchAreaYDilation: 0,

      // MouseArea expansion
      mouseAreaXDilation: 0,
      mouseAreaYDilation: 0,

      //The radius for each button
      cornerRadius: 4,

      // How far from the button the text label is (only applies if labels are passed in)
      labelSpacing: 0,

      // Which side of the button the label will appear, options are 'top', 'bottom', 'left', 'right'
      // (only applies if labels are passed in)
      labelAlign: 'bottom',

      // The default appearances use the color values specified above, but other appearances could be specified for more
      // customized behavior.  Generally setting the color values above should be enough to specify the desired look.
      buttonAppearanceStrategy: RadioButtonGroupAppearance.defaultRadioButtonsAppearance,
      contentAppearanceStrategy: RadioButtonGroupAppearance.contentAppearanceStrategy,

      // a11y - focus highlight expansion
      a11yHighlightXDilation: 0,
      a11yHighlightYDilation: 0
    };

    options = _.extend( _.clone( defaultOptions ), options );

    assert && assert( _.includes( BUTTON_CONTENT_X_ALIGN_VALUES, options.buttonContentXAlign ),
      'invalid buttonContentXAlign: ' + options.buttonContentXAlign );
    assert && assert( _.includes( BUTTON_CONTENT_Y_ALIGN_VALUES, options.buttonContentYAlign ),
      'invalid buttonContentYAlign: ' + options.buttonContentYAlign );

    // make a copy of the options to pass to individual buttons that includes all default options but not scenery options
    var buttonOptions = _.pick( options, _.keys( defaultOptions ) );

    // calculate the maximum width and height of the content so we can make all radio buttons the same size
    var widestContentWidth = _.maxBy( contentArray, function( content ) { return content.node.width; } ).node.width;
    var tallestContentHeight = _.maxBy( contentArray, function( content ) { return content.node.height; } ).node.height;

    // make sure all radio buttons are the same size and create the RadioButtons
    var buttons = [];
    var button;
    for ( i = 0; i < contentArray.length; i++ ) {
      var currentContent = contentArray[ i ];

      assert && assert( !currentContent.hasOwnProperty( 'phetioType' ), 'phetioType should be provided by ' +
                                                                        'the property passed to the ' +
                                                                        'RadioButtonGroup constructor' );

      assert && assert( !currentContent.tandem, 'content arrays should not have tandem instances, they should use ' +
                                                'tandemName instead' );

      var opts = _.extend( {
        content: currentContent.node,
        xMargin: options.buttonContentXMargin,
        yMargin: options.buttonContentYMargin,
        xAlign: options.buttonContentXAlign,
        yAlign: options.buttonContentYAlign,
        minWidth: widestContentWidth + 2 * options.buttonContentXMargin,
        minHeight: tallestContentHeight + 2 * options.buttonContentYMargin,
        phetioInstanceDocumentation: currentContent.phetioInstanceDocumentation
      }, buttonOptions );

      // Pass through the tandem given the tandemName, but also support uninstrumented simulations
      if ( currentContent.tandemName ) {
        opts.tandem = options.tandem.createTandem( currentContent.tandemName );
      }

      // a11y create the label for the radio button
      if ( currentContent.labelContent ) {
        opts.labelContent = currentContent.labelContent;
      }

      // a11y create description for radio button
      // use if block to prevent empty 'p' tag being added when no option is present
      if ( currentContent.descriptionContent ) {
        opts.descriptionContent = currentContent.descriptionContent;
      }

      var radioButton = new RadioButtonGroupMember( property, currentContent.value, opts );

      // a11y - so the browser and assistive technology recognizes that these buttons are in the same group
      radioButton.setAccessibleAttribute( 'name', 'radioButtonGroup' + instanceCount + 'Member' );

      // ensure the buttons don't resize when selected vs unselected by adding a rectangle with the max size
      var maxLineWidth = Math.max( options.selectedLineWidth, options.deselectedLineWidth );
      var maxButtonWidth = maxLineWidth + widestContentWidth + options.buttonContentXMargin * 2;
      var maxButtonHeight = maxLineWidth + tallestContentHeight + options.buttonContentYMargin * 2;
      var boundingRect = new Rectangle( 0, 0, maxButtonWidth, maxButtonHeight, {
        fill: 'rgba(0,0,0,0)',
        center: radioButton.center
      } );
      radioButton.addChild( boundingRect );

      // default bounds for focus highlight, will include label if one exists
      var defaultHighlightBounds = null;

      // if a label is given, the button becomes a LayoutBox with the label and button
      if ( currentContent.label ) {
        var label = currentContent.label;
        var labelOrientation = ( options.labelAlign === 'bottom' || options.labelAlign === 'top' ) ? 'vertical' : 'horizontal';
        var labelChildren = ( options.labelAlign === 'left' || options.labelAlign === 'top' ) ? [ label, radioButton ] : [ radioButton, label ];
        button = new LayoutBox( {
          children: labelChildren,
          spacing: options.labelSpacing,
          orientation: labelOrientation
        } );

        var xDilation = options.touchAreaXDilation;
        var yDilation = options.touchAreaYDilation;

        // override the touch and mouse areas defined in RectangularButtonView
        // extra width is added to the SingleRadioButtons so they don't change size if the line width changes,
        // that is why lineWidth is subtracted from the width and height when calculating these new areas
        radioButton.touchArea = Shape.rectangle(
          -xDilation,
          -yDilation,
          button.width + 2 * xDilation - maxLineWidth,
          button.height + 2 * yDilation - maxLineWidth
        );

        xDilation = options.mouseAreaXDilation;
        yDilation = options.mouseAreaYDilation;
        radioButton.mouseArea = Shape.rectangle(
          -xDilation,
          -yDilation,
          button.width + 2 * xDilation - maxLineWidth,
          button.height + 2 * yDilation - maxLineWidth
        );

        // make sure the label mouse and touch areas don't block the expanded button touch and mouse areas
        label.pickable = false;

        // use the same content appearance strategy for the labels that is used for the button content
        options.contentAppearanceStrategy( label, radioButton.interactionStateProperty, options );

        // a11y - include label in focus highlight
        defaultHighlightBounds = radioButton.mouseArea.bounds.dilated( 5 );
      }
      else {
        button = radioButton;
        defaultHighlightBounds = button.bounds.dilated( FocusHighlightPath.getDilationCoefficient( button ) );
      }

      // a11y - set the focus highlight, dilated by the optional expansion values
      var highlightBounds = defaultHighlightBounds.dilatedX( opts.a11yHighlightXDilation ).dilatedY( opts.a11yHighlightYDilation );
      radioButton.setFocusHighlight( Shape.bounds( highlightBounds ) );

      buttons.push( button );
    }

    // @private
    this.enabledProperty = options.enabledProperty;

    // super call
    options.children = buttons;
    LayoutBox.call( this, options );
    var self = this;

    // a11y - this node's primary sibling is aria-labelledby its own label so the label content is read whenever
    // a member of the group receives focus
    this.addAriaLabelledbyAssociation( {
      thisElementName: AccessiblePeer.PRIMARY_SIBLING,
      otherNode: this,
      otherElementName: AccessiblePeer.LABEL_SIBLING
    } );

    // When the entire RadioButtonGroup gets disabled, gray them out and make them unpickable (and vice versa)
    var enabledListener = function( isEnabled ) {
      self.pickable = isEnabled;

      for ( i = 0; i < contentArray.length; i++ ) {
        if ( buttons[ i ] instanceof LayoutBox ) {
          for ( var j = 0; j < 2; j++ ) {
            buttons[ i ].children[ j ].enabled = isEnabled;
          }
        }
        else {
          buttons[ i ].enabled = isEnabled;
        }
      }
    };
    this.enabledProperty.link( enabledListener );

    // make the unselected buttons pickable and have a pointer cursor
    var propertyListener = function( value ) {
      if ( self.enabledProperty.get() ) {
        for ( i = 0; i < contentArray.length; i++ ) {
          if ( contentArray[ i ].value === value ) {
            buttons[ i ].pickable = false;
            buttons[ i ].cursor = null;
          }
          else {
            buttons[ i ].pickable = true;
            buttons[ i ].cursor = 'pointer';
          }
        }
      }
    };
    property.link( propertyListener );

    // @private - remove listeners from buttons and make eligible for garbage collection
    this.disposeRadioButtonGroup = function() {
      self.enabledProperty.unlink( enabledListener );
      property.unlink( propertyListener );

      // dispose all buttons
      for ( i = 0; i < contentArray.length; i++ ) {
        buttons[ i ].dispose();
      }
    };

    // a11y - register component for binder docs
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'RadioButtonGroup', this );
  }

  sun.register( 'RadioButtonGroup', RadioButtonGroup );

  return inherit( LayoutBox, RadioButtonGroup, {

    // @public
    dispose: function() {
      this.disposeRadioButtonGroup();
      LayoutBox.prototype.dispose.call( this );
    },

    // @public
    set enabled( value ) {
      assert && assert( typeof value === 'boolean', 'RadioButtonGroup.enabled must be a boolean value' );
      this.enabledProperty.set( value );
    },

    // @public
    get enabled() {
      return this.enabledProperty.get();
    }
  } );
} );
<commit_msg>keep same default for RadioButton's phetioInstanceDocumentation, https://github.com/phetsims/tandem/issues/59<commit_after>// Copyright 2014-2017, University of Colorado Boulder

/**
 * Radio buttons. See ButtonsView for example usage.
 *
 * This type creates a group of radio buttons in either a horizontal or vertical formation.
 * Each button inherits from RectangularButtonView, and can take a Scenery Node as its content.
 * A typical use case is when you want to have different modes of a view to select from. Typically,
 * RadioButtonGroup radio buttons display some kind of icon or image, but that is not mandatory.
 *
 * @author Aaron Davis (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  var Color = require( 'SCENERY/util/Color' );
  var ColorConstants = require( 'SUN/ColorConstants' );
  var FocusHighlightPath = require( 'SCENERY/accessibility/FocusHighlightPath' );
  var inherit = require( 'PHET_CORE/inherit' );
  var InstanceRegistry = require( 'PHET_CORE/documentation/InstanceRegistry' );
  var LayoutBox = require( 'SCENERY/nodes/LayoutBox' );
  var Property = require( 'AXON/Property' );
  var RadioButtonGroupAppearance = require( 'SUN/buttons/RadioButtonGroupAppearance' );
  var RadioButtonGroupMember = require( 'SUN/buttons/RadioButtonGroupMember' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );

  // constants
  var BUTTON_CONTENT_X_ALIGN_VALUES = [ 'center', 'left', 'right' ];
  var BUTTON_CONTENT_Y_ALIGN_VALUES = [ 'center', 'top', 'bottom' ];

  // a11y - integer to allow distinct name attributes for each set of RadioButtonGroupMembers, increments with each
  // instantiation of RadioButtonGroup
  var instanceCount = 0;

  /**
   * RadioButtonGroup constructor.
   *
   * @param {Property} property
   * @param {Array} contentArray an array of objects that have two keys each: value and node the node key holds a
   * scenery Node that is the content for a given radio button. the value key should hold the value that the property
   * takes on for the corresponding node to be selected. Optionally, these objects can have an attribute 'label', which
   * is a {Node} used to label the button. You can also pass some specific a11y options
   * (labelContent/descriptionContent) through, see "new RadioButtonGroupMember" construction.
   * @param {Object} [options]
   * @constructor
   */
  function RadioButtonGroup( property, contentArray, options ) {
    options = _.extend( {
      tandem: Tandem.required,

      // a11y
      tagName: 'ul',
      labelTagName: 'h3',
      ariaRole: 'radiogroup',
      groupFocusHighlight: true
    }, options );

    // increment instance count
    instanceCount++;

    assert && assert( !options.hasOwnProperty( 'children' ), 'Cannot pass in children to a RadioButtonGroup, ' +
                                                             'create siblings in the parent node instead' );

    // make sure every object in the content array has properties 'node' and 'value'
    assert && assert( _.every( contentArray, function( obj ) {
      return obj.hasOwnProperty( 'node' ) && obj.hasOwnProperty( 'value' );
    } ), 'contentArray must be an array of objects with properties "node" and "value"' );

    var i; // for loops

    // make sure that each value passed into the contentArray is unique
    var uniqueValues = [];
    for ( i = 0; i < contentArray.length; i++ ) {
      if ( uniqueValues.indexOf( contentArray[ i ].value ) < 0 ) {
        uniqueValues.push( contentArray[ i ].value );
      }
      else {
        throw new Error( 'Duplicate value: "' + contentArray[ i ].value + '" passed into RadioButtonGroup.js' );
      }
    }

    // make sure that the property passed in currently has a value from the contentArray
    if ( uniqueValues.indexOf( property.get() ) === -1 ) {
      throw new Error( 'The property passed in to RadioButtonGroup has an illegal value "' + property.get() +
                       '" that is not present in the contentArray' );
    }

    var defaultOptions = {

      // LayoutBox options (super class of RadioButtonGroup)
      spacing: 10,
      orientation: 'vertical',

      enabledProperty: new Property( true ), // whether or not the set of radio buttons as a whole is enabled

      // The fill for the rectangle behind the radio buttons.  Default color is bluish color, as in the other button library.
      baseColor: ColorConstants.LIGHT_BLUE,
      disabledBaseColor: ColorConstants.LIGHT_GRAY,

      // Opacity can be set separately for the buttons and button content.
      selectedButtonOpacity: 1,
      deselectedButtonOpacity: 0.6,
      selectedContentOpacity: 1,
      deselectedContentOpacity: 0.6,
      overButtonOpacity: 0.8,
      overContentOpacity: 0.8,

      selectedStroke: 'black',
      deselectedStroke: new Color( 50, 50, 50 ),
      selectedLineWidth: 1.5,
      deselectedLineWidth: 1,

      // The following options specify highlight behavior overrides, leave as null to get the default behavior
      // Note that highlighting applies only to deselected buttons
      overFill: null,
      overStroke: null,
      overLineWidth: null,

      // These margins are *within* each button
      buttonContentXMargin: 5,
      buttonContentYMargin: 5,

      // alignment of the content nodes *within* each button
      buttonContentXAlign: 'center', // {string} see BUTTON_CONTENT_X_ALIGN_VALUES
      buttonContentYAlign: 'center', // {string} see BUTTON_CONTENT_Y_ALIGN_VALUES

      // TouchArea expansion
      touchAreaXDilation: 0,
      touchAreaYDilation: 0,

      // MouseArea expansion
      mouseAreaXDilation: 0,
      mouseAreaYDilation: 0,

      //The radius for each button
      cornerRadius: 4,

      // How far from the button the text label is (only applies if labels are passed in)
      labelSpacing: 0,

      // Which side of the button the label will appear, options are 'top', 'bottom', 'left', 'right'
      // (only applies if labels are passed in)
      labelAlign: 'bottom',

      // The default appearances use the color values specified above, but other appearances could be specified for more
      // customized behavior.  Generally setting the color values above should be enough to specify the desired look.
      buttonAppearanceStrategy: RadioButtonGroupAppearance.defaultRadioButtonsAppearance,
      contentAppearanceStrategy: RadioButtonGroupAppearance.contentAppearanceStrategy,

      // a11y - focus highlight expansion
      a11yHighlightXDilation: 0,
      a11yHighlightYDilation: 0
    };

    options = _.extend( _.clone( defaultOptions ), options );

    assert && assert( _.includes( BUTTON_CONTENT_X_ALIGN_VALUES, options.buttonContentXAlign ),
      'invalid buttonContentXAlign: ' + options.buttonContentXAlign );
    assert && assert( _.includes( BUTTON_CONTENT_Y_ALIGN_VALUES, options.buttonContentYAlign ),
      'invalid buttonContentYAlign: ' + options.buttonContentYAlign );

    // make a copy of the options to pass to individual buttons that includes all default options but not scenery options
    var buttonOptions = _.pick( options, _.keys( defaultOptions ) );

    // calculate the maximum width and height of the content so we can make all radio buttons the same size
    var widestContentWidth = _.maxBy( contentArray, function( content ) { return content.node.width; } ).node.width;
    var tallestContentHeight = _.maxBy( contentArray, function( content ) { return content.node.height; } ).node.height;

    // make sure all radio buttons are the same size and create the RadioButtons
    var buttons = [];
    var button;
    for ( i = 0; i < contentArray.length; i++ ) {
      var currentContent = contentArray[ i ];

      assert && assert( !currentContent.hasOwnProperty( 'phetioType' ), 'phetioType should be provided by ' +
                                                                        'the property passed to the ' +
                                                                        'RadioButtonGroup constructor' );

      assert && assert( !currentContent.tandem, 'content arrays should not have tandem instances, they should use ' +
                                                'tandemName instead' );

      var opts = _.extend( {
        content: currentContent.node,
        xMargin: options.buttonContentXMargin,
        yMargin: options.buttonContentYMargin,
        xAlign: options.buttonContentXAlign,
        yAlign: options.buttonContentYAlign,
        minWidth: widestContentWidth + 2 * options.buttonContentXMargin,
        minHeight: tallestContentHeight + 2 * options.buttonContentYMargin,
        phetioInstanceDocumentation: currentContent.phetioInstanceDocumentation || null // this null should be the default in PhetioObject
      }, buttonOptions );

      // Pass through the tandem given the tandemName, but also support uninstrumented simulations
      if ( currentContent.tandemName ) {
        opts.tandem = options.tandem.createTandem( currentContent.tandemName );
      }

      // a11y create the label for the radio button
      if ( currentContent.labelContent ) {
        opts.labelContent = currentContent.labelContent;
      }

      // a11y create description for radio button
      // use if block to prevent empty 'p' tag being added when no option is present
      if ( currentContent.descriptionContent ) {
        opts.descriptionContent = currentContent.descriptionContent;
      }

      var radioButton = new RadioButtonGroupMember( property, currentContent.value, opts );

      // a11y - so the browser and assistive technology recognizes that these buttons are in the same group
      radioButton.setAccessibleAttribute( 'name', 'radioButtonGroup' + instanceCount + 'Member' );

      // ensure the buttons don't resize when selected vs unselected by adding a rectangle with the max size
      var maxLineWidth = Math.max( options.selectedLineWidth, options.deselectedLineWidth );
      var maxButtonWidth = maxLineWidth + widestContentWidth + options.buttonContentXMargin * 2;
      var maxButtonHeight = maxLineWidth + tallestContentHeight + options.buttonContentYMargin * 2;
      var boundingRect = new Rectangle( 0, 0, maxButtonWidth, maxButtonHeight, {
        fill: 'rgba(0,0,0,0)',
        center: radioButton.center
      } );
      radioButton.addChild( boundingRect );

      // default bounds for focus highlight, will include label if one exists
      var defaultHighlightBounds = null;

      // if a label is given, the button becomes a LayoutBox with the label and button
      if ( currentContent.label ) {
        var label = currentContent.label;
        var labelOrientation = ( options.labelAlign === 'bottom' || options.labelAlign === 'top' ) ? 'vertical' : 'horizontal';
        var labelChildren = ( options.labelAlign === 'left' || options.labelAlign === 'top' ) ? [ label, radioButton ] : [ radioButton, label ];
        button = new LayoutBox( {
          children: labelChildren,
          spacing: options.labelSpacing,
          orientation: labelOrientation
        } );

        var xDilation = options.touchAreaXDilation;
        var yDilation = options.touchAreaYDilation;

        // override the touch and mouse areas defined in RectangularButtonView
        // extra width is added to the SingleRadioButtons so they don't change size if the line width changes,
        // that is why lineWidth is subtracted from the width and height when calculating these new areas
        radioButton.touchArea = Shape.rectangle(
          -xDilation,
          -yDilation,
          button.width + 2 * xDilation - maxLineWidth,
          button.height + 2 * yDilation - maxLineWidth
        );

        xDilation = options.mouseAreaXDilation;
        yDilation = options.mouseAreaYDilation;
        radioButton.mouseArea = Shape.rectangle(
          -xDilation,
          -yDilation,
          button.width + 2 * xDilation - maxLineWidth,
          button.height + 2 * yDilation - maxLineWidth
        );

        // make sure the label mouse and touch areas don't block the expanded button touch and mouse areas
        label.pickable = false;

        // use the same content appearance strategy for the labels that is used for the button content
        options.contentAppearanceStrategy( label, radioButton.interactionStateProperty, options );

        // a11y - include label in focus highlight
        defaultHighlightBounds = radioButton.mouseArea.bounds.dilated( 5 );
      }
      else {
        button = radioButton;
        defaultHighlightBounds = button.bounds.dilated( FocusHighlightPath.getDilationCoefficient( button ) );
      }

      // a11y - set the focus highlight, dilated by the optional expansion values
      var highlightBounds = defaultHighlightBounds.dilatedX( opts.a11yHighlightXDilation ).dilatedY( opts.a11yHighlightYDilation );
      radioButton.setFocusHighlight( Shape.bounds( highlightBounds ) );

      buttons.push( button );
    }

    // @private
    this.enabledProperty = options.enabledProperty;

    // super call
    options.children = buttons;
    LayoutBox.call( this, options );
    var self = this;

    // a11y - this node's primary sibling is aria-labelledby its own label so the label content is read whenever
    // a member of the group receives focus
    this.addAriaLabelledbyAssociation( {
      thisElementName: AccessiblePeer.PRIMARY_SIBLING,
      otherNode: this,
      otherElementName: AccessiblePeer.LABEL_SIBLING
    } );

    // When the entire RadioButtonGroup gets disabled, gray them out and make them unpickable (and vice versa)
    var enabledListener = function( isEnabled ) {
      self.pickable = isEnabled;

      for ( i = 0; i < contentArray.length; i++ ) {
        if ( buttons[ i ] instanceof LayoutBox ) {
          for ( var j = 0; j < 2; j++ ) {
            buttons[ i ].children[ j ].enabled = isEnabled;
          }
        }
        else {
          buttons[ i ].enabled = isEnabled;
        }
      }
    };
    this.enabledProperty.link( enabledListener );

    // make the unselected buttons pickable and have a pointer cursor
    var propertyListener = function( value ) {
      if ( self.enabledProperty.get() ) {
        for ( i = 0; i < contentArray.length; i++ ) {
          if ( contentArray[ i ].value === value ) {
            buttons[ i ].pickable = false;
            buttons[ i ].cursor = null;
          }
          else {
            buttons[ i ].pickable = true;
            buttons[ i ].cursor = 'pointer';
          }
        }
      }
    };
    property.link( propertyListener );

    // @private - remove listeners from buttons and make eligible for garbage collection
    this.disposeRadioButtonGroup = function() {
      self.enabledProperty.unlink( enabledListener );
      property.unlink( propertyListener );

      // dispose all buttons
      for ( i = 0; i < contentArray.length; i++ ) {
        buttons[ i ].dispose();
      }
    };

    // a11y - register component for binder docs
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'RadioButtonGroup', this );
  }

  sun.register( 'RadioButtonGroup', RadioButtonGroup );

  return inherit( LayoutBox, RadioButtonGroup, {

    // @public
    dispose: function() {
      this.disposeRadioButtonGroup();
      LayoutBox.prototype.dispose.call( this );
    },

    // @public
    set enabled( value ) {
      assert && assert( typeof value === 'boolean', 'RadioButtonGroup.enabled must be a boolean value' );
      this.enabledProperty.set( value );
    },

    // @public
    get enabled() {
      return this.enabledProperty.get();
    }
  } );
} );
<|endoftext|>


<commit_before>// TODO: Replace placeholder shaders by original ones (requires fixing projection matrix)
import Promise from 'bluebird'
import decodeArrayToString from '../../utils/array/decode-to-string.js'
import fetchScript from '../../utils/io/fetch-script.js'
import PromiseCache from '../../utils/promise-cache.js'
import fragmentShader from './gblock/fragment-placeholder.glsl'
import vertexShader from './gblock/vertex-placeholder.glsl'

// configs

var LEGACY_GLFT_LOADER_URL = 'https://cdn.rawgit.com/mrdoob/three.js/r86/examples/js/loaders/GLTFLoader.js'
var GBLOCK_API_GET_OFFICIAL_GLTF_URL = 'https://us-central1-gblock-api.cloudfunctions.net/get-gltf-url/?url='

// internals

var promiseCache = new PromiseCache()

// aframe module

export default {

  schema: {type: 'asset'},

  init: function () {

    this.model = null

  },

  update: function () {

    var self = this
    var el = this.el
    var src = this.data

    if (!src) { return; }

    this.remove()

    Promise.all([
      getGltfUrl(src),
      fetchGblockLoader()
    ]).then(function (results) {

      var gltfUrl = results[0]
      var GblockLoader = results[1]

      // load glTF model from original URL
      new GblockLoader().load( gltfUrl, function onLoaded (gltfModel) {

        self.model = gltfModel.scene || gltfModel.scenes[0]
        // FIXME: adapt projection matrix in original shaders and do not replace materials
        self.model.traverse(function (child) {
          if (child.material) child.material = new THREE.MeshPhongMaterial({ vertexColors: THREE.VertexColors })
        })
        self.model.animations = gltfModel.animations

        el.setObject3D('mesh', self.model)
        el.emit('model-loaded', {format: 'gltf', model: self.model})

      }, function onProgress() {
        // do nothing

      }, function onError(error) {

        console.error('ERROR loading gblock model "'+ src +'" : ' + error)
        el.emit('model-error', { message: error })

      })

    }).catch(function(errorMessage){
      console.error('ERROR loading gblock model from "' + src +'" : ' + errorMessage)
      el.emit('model-error', { message: errorMessage })
    })

  },

  remove: function () {

    if (!this.model) { return; }
    this.el.removeObject3D('mesh')

  }

}

// private shared methods

// FIXME: Replace this with an official API URL once available
// This API call is only needed to obtain the official glTF URL of a google block model.
// The glTF itself is not being proxied and gets fetched from https://vr.google.com/downloads/* directly.
// https://github.com/archilogic-com/aframe-gblock/issues/1
// API server code: server/index.js
// try promise cache (could be in loading state)
function getGltfUrl (src) {

  // try cache
  var getUrlPromise = promiseCache.get(src)

  if (!getUrlPromise) {

    getUrlPromise = fetch(GBLOCK_API_GET_OFFICIAL_GLTF_URL + src).then(function (response) {

      // parse response
      return response.json().catch(function(error){
        // handle JSON parsing error
        console.log('ERROR parsing gblock API server response JSON.\nRequested Model: "' + src + '"\nError: "' + JSON.stringify(error) + '"')
        return Promise.reject('gblock API server error. Check console for details.')
      }).then(function (message) {
        if (response.ok) {
          // return glTF URL
          return message.gltfUrl
        } else {
          // handle error response
          console.error('ERROR loading gblock model "'+ src +'" : ' + response.status + ' "' + message.message)
          return Promise.reject(message.message)
        }
      })

    })

    // add to cache
    promiseCache.add(src, getUrlPromise)

  }

  return getUrlPromise

}

// loader has to be created asynchronously because we have to fetch legacy glTF loader script
var GblockLoaderPromise
function fetchGblockLoader() {

  if (!GblockLoaderPromise) {

    // legacy loader will overwrite THREE.GLTFLoader so we need to keep reference to it
    THREE.___OriginalGLTFLoader = THREE.GLTFLoader

    GblockLoaderPromise = fetchScript(LEGACY_GLFT_LOADER_URL).then(function(){

      // keep reference to fetched legacy loader
      var LegacyGLTFLoader = THREE.GLTFLoader

      // restore current GLTFLoader
      THREE.GLTFLoader = THREE.___OriginalGLTFLoader

      // create modified GLTF loader for google blocks
      function GBlockLoader () {

        LegacyGLTFLoader.call(this)

        var self = this

        this._parse = this.parse
        this.parse = function (data, path, onLoad, onError) {
          // convert uint8 array to json
          var json = JSON.parse(decodeArrayToString.utf8(data))
          // use base64 shaders
          Object.keys(json.shaders).forEach(function (key, i) {
            // Replacing original shaders with placeholders
            if (key.indexOf('fragment') > -1) json.shaders[key].uri = fragmentShader.base64
            else if (key.indexOf('vertex') > -1) json.shaders[key].uri = vertexShader.base64
          })
          // convert json to uint8
          var uint8array = new TextEncoder('utf-8').encode(JSON.stringify(json))
          // parse data
          self._parse.call(self, uint8array, path, onLoad, onError)
        }

      }
      GBlockLoader.prototype = LegacyGLTFLoader.prototype

      // expose loader
      return GBlockLoader

    })

  }

  return GblockLoaderPromise

}<commit_msg>updates gblock API url<commit_after>// TODO: Replace placeholder shaders by original ones (requires fixing projection matrix)
import Promise from 'bluebird'
import decodeArrayToString from '../../utils/array/decode-to-string.js'
import fetchScript from '../../utils/io/fetch-script.js'
import PromiseCache from '../../utils/promise-cache.js'
import fragmentShader from './gblock/fragment-placeholder.glsl'
import vertexShader from './gblock/vertex-placeholder.glsl'

// configs

var LEGACY_GLFT_LOADER_URL = 'https://cdn.rawgit.com/mrdoob/three.js/r86/examples/js/loaders/GLTFLoader.js'
var GBLOCK_API_GET_OFFICIAL_GLTF_URL = 'https://gblock.3d.io/api/get-gltf-url/?url='

// internals

var promiseCache = new PromiseCache()

// aframe module

export default {

  schema: {type: 'asset'},

  init: function () {

    this.model = null

  },

  update: function () {

    var self = this
    var el = this.el
    var src = this.data

    if (!src) { return; }

    this.remove()

    Promise.all([
      getGltfUrl(src),
      fetchGblockLoader()
    ]).then(function (results) {

      var gltfUrl = results[0]
      var GblockLoader = results[1]

      // load glTF model from original URL
      new GblockLoader().load( gltfUrl, function onLoaded (gltfModel) {

        self.model = gltfModel.scene || gltfModel.scenes[0]
        // FIXME: adapt projection matrix in original shaders and do not replace materials
        self.model.traverse(function (child) {
          if (child.material) child.material = new THREE.MeshPhongMaterial({ vertexColors: THREE.VertexColors })
        })
        self.model.animations = gltfModel.animations

        el.setObject3D('mesh', self.model)
        el.emit('model-loaded', {format: 'gltf', model: self.model})

      }, function onProgress() {
        // do nothing

      }, function onError(error) {

        console.error('ERROR loading gblock model "'+ src +'" : ' + error)
        el.emit('model-error', { message: error })

      })

    }).catch(function(errorMessage){
      console.error('ERROR loading gblock model from "' + src +'" : ' + errorMessage)
      el.emit('model-error', { message: errorMessage })
    })

  },

  remove: function () {

    if (!this.model) { return; }
    this.el.removeObject3D('mesh')

  }

}

// private shared methods

// FIXME: Replace this with an official API URL once available
// This API call is only needed to obtain the official glTF URL of a google block model.
// The glTF itself is not being proxied and gets fetched from https://vr.google.com/downloads/* directly.
// https://github.com/archilogic-com/aframe-gblock/issues/1
// API server code: server/index.js
// try promise cache (could be in loading state)
function getGltfUrl (src) {

  // try cache
  var getUrlPromise = promiseCache.get(src)

  if (!getUrlPromise) {

    getUrlPromise = fetch(GBLOCK_API_GET_OFFICIAL_GLTF_URL + src).then(function (response) {

      // parse response
      return response.json().catch(function(error){
        // handle JSON parsing error
        console.log('ERROR parsing gblock API server response JSON.\nRequested Model: "' + src + '"\nError: "' + JSON.stringify(error) + '"')
        return Promise.reject('gblock API server error. Check console for details.')
      }).then(function (message) {
        if (response.ok) {
          // return glTF URL
          return message.gltfUrl
        } else {
          // handle error response
          console.error('ERROR loading gblock model "'+ src +'" : ' + response.status + ' "' + message.message)
          return Promise.reject(message.message)
        }
      })

    })

    // add to cache
    promiseCache.add(src, getUrlPromise)

  }

  return getUrlPromise

}

// loader has to be created asynchronously because we have to fetch legacy glTF loader script
var GblockLoaderPromise
function fetchGblockLoader() {

  if (!GblockLoaderPromise) {

    // legacy loader will overwrite THREE.GLTFLoader so we need to keep reference to it
    THREE.___OriginalGLTFLoader = THREE.GLTFLoader

    GblockLoaderPromise = fetchScript(LEGACY_GLFT_LOADER_URL).then(function(){

      // keep reference to fetched legacy loader
      var LegacyGLTFLoader = THREE.GLTFLoader

      // restore current GLTFLoader
      THREE.GLTFLoader = THREE.___OriginalGLTFLoader

      // create modified GLTF loader for google blocks
      function GBlockLoader () {

        LegacyGLTFLoader.call(this)

        var self = this

        this._parse = this.parse
        this.parse = function (data, path, onLoad, onError) {
          // convert uint8 array to json
          var json = JSON.parse(decodeArrayToString.utf8(data))
          // use base64 shaders
          Object.keys(json.shaders).forEach(function (key, i) {
            // Replacing original shaders with placeholders
            if (key.indexOf('fragment') > -1) json.shaders[key].uri = fragmentShader.base64
            else if (key.indexOf('vertex') > -1) json.shaders[key].uri = vertexShader.base64
          })
          // convert json to uint8
          var uint8array = new TextEncoder('utf-8').encode(JSON.stringify(json))
          // parse data
          self._parse.call(self, uint8array, path, onLoad, onError)
        }

      }
      GBlockLoader.prototype = LegacyGLTFLoader.prototype

      // expose loader
      return GBlockLoader

    })

  }

  return GblockLoaderPromise

}<|endoftext|>


<commit_before>new Webos.ScriptFile('usr/lib/webos/config.js');

Webos.Theme = function WTheme(configFile) {
	var data = configFile.data();

	var defaults = {
		desktop: '',
		icons: '',
		background: '',
		animations: false
	};
	data = $.extend({}, defaults, data);

	Webos.Model.call(this, data);

	this._configFile = configFile;
};
Webos.Theme.prototype = {
	load: function(callback) {
		callback = Webos.Callback.toCallback(callback);
		var that = this;

		new W.ServerCall({
			'class': 'ThemeController',
			'method': 'loadCss',
			'arguments': {
				'theme': that.get('desktop'),
				'ui': Webos.UserInterface.current.name()
			}
		}).load(new Webos.Callback(function(response) {
			that._setAnimations();
			var css = response.getData().css;
			for (var index in css) {
				Webos.Stylesheet.insertCss(css[index], '#'+W.UserInterface.current.element.attr('id'));
			}
			that._loadBackground();
			Webos.Theme._current = that;
			callback.success();
		}, callback.error));
	},
	_loadBackground: function() {
		var bg = Webos.File.get(this.get('background')).get('realpath');
		Webos.UserInterface.current.element
			.css('background', 'url("'+bg+'") no-repeat center center fixed #27001f')
			.css('-webkit-background-size', 'cover')
			.css('-moz-background-size', 'cover')
			.css('-o-background-size', 'cover')
			.css('background-size', 'cover')
			.css('filter', 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\''+bg+'\', sizingMethod=\'scale\')')
			.css('-ms-filter', '"progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\''+bg+'\', sizingMethod=\'scale\')"');
	},
	_setAnimations: function() {
		$.fx.off = !this.get('animations');
	},
	background: function() {
		var bg = this._get('background');
		if (!bg) {
			bg = Webos.Theme.defaultBackground();
		}
		return bg;
	},
	setDesktop: function(value) {
		this._set('desktop', String(value));
		return true;
	},
	setBackground: function(value) {
		value = String(value);
		if (value == Webos.Theme.defaultBackground()) {
			value = '';
		}
		this._set('background', value);
		return true;
	},
	setIcons: function(value) {
		this._set('icons', String(value));
		return true;
	},
	setAnimations: function(value) {
		this._set('animations', (value) ? 1 : 0);
		return true;
	},
	sync: function(callback) {
		callback = Webos.Callback.toCallback(callback);

		var that = this;

		var data = {}, nbrChanges = 0;
		for (var key in this._unsynced) {
			if (this._unsynced[key].state === 1) {
				this._unsynced[key].state = 2;
				data[key] = this._unsynced[key].value;
				this._configFile.set(key, data[key]);
				nbrChanges++;
			}
		}

		if (nbrChanges == 0) {
			callback.success(this);
			return;
		}

		this._configFile.sync([function() {
			for (var key in that._unsynced) {
				if (that._unsynced[key].state === 2) {
					that._data[key] = that._unsynced[key].value;
					delete that._unsynced[key];

					switch (key) {
						case 'background':
							that._loadBackground();
							break;
						case 'animations':
							that._setAnimations();
							break;
					}

					that.notify('update', { key: key, value: that._data[key].value });
				}
			}
			callback.success(that);
		}, callback.error]);
	}
};
Webos.inherit(Webos.Theme, Webos.Model);

Webos.Theme._current = null;
Webos.Theme.current = function() {
	return Webos.Theme._current || new Webos.Theme(Webos.ConfigFile.get('~/.theme/'+Webos.UserInterface.current.name()+'/config.xml'));
};
Webos.Theme._defaultBackground = 'usr/share/images/backgrounds/default.jpg';
Webos.Theme.defaultBackground = function() {
	return Webos.Theme._defaultBackground;
};

Webos.Theme.get = function(callback) {
	callback = Webos.Callback.toCallback(callback);

	var ui = Webos.UserInterface.current.name();
	Webos.ConfigFile.loadUserConfig('~/.theme/'+ui+'/config.xml', '/usr/etc/uis/'+ui+'/config.xml', [function(config) {
		var theme = new Webos.Theme(config);
		callback.success(theme);
	}, callback.error]);
};

Webos.Theme.backgroundsDir = '/usr/share/images/backgrounds/';
Webos.Theme.iconsDir = '/usr/share/icons/themes/';
Webos.Theme.getAvailable = function(module, callback) {
	callback = Webos.Callback.toCallback(callback);

	new W.ServerCall({
		'class': 'ThemeController',
		'method': 'getAvailable',
		'arguments': {
			'ui': W.UserInterface.current.name()
		}
	}).load(new Webos.Callback(function(response) {
		callback.success(response.getData());
	}, function(response) {
		callback.error(response);
	}));
};<commit_msg>Resolution d'un bug avec la configuration pour les animations.<commit_after>new Webos.ScriptFile('usr/lib/webos/config.js');

Webos.Theme = function WTheme(configFile) {
	var data = configFile.data();

	var defaults = {
		desktop: '',
		icons: '',
		background: '',
		animations: 0
	};
	data = $.extend({}, defaults, data);

	if (typeof data.animations == 'string') {
		data.animations = parseInt(data.animations) || 0;
	}

	Webos.Model.call(this, data);

	this._configFile = configFile;
};
Webos.Theme.prototype = {
	load: function(callback) {
		callback = Webos.Callback.toCallback(callback);
		var that = this;

		new W.ServerCall({
			'class': 'ThemeController',
			'method': 'loadCss',
			'arguments': {
				'theme': that.get('desktop'),
				'ui': Webos.UserInterface.current.name()
			}
		}).load(new Webos.Callback(function(response) {
			that._setAnimations();
			var css = response.getData().css;
			for (var index in css) {
				Webos.Stylesheet.insertCss(css[index], '#'+W.UserInterface.current.element.attr('id'));
			}
			that._loadBackground();
			Webos.Theme._current = that;
			callback.success();
		}, callback.error));
	},
	_loadBackground: function() {
		var bg = Webos.File.get(this.get('background')).get('realpath');
		Webos.UserInterface.current.element
			.css('background', 'url("'+bg+'") no-repeat center center fixed #27001f')
			.css('-webkit-background-size', 'cover')
			.css('-moz-background-size', 'cover')
			.css('-o-background-size', 'cover')
			.css('background-size', 'cover')
			.css('filter', 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\''+bg+'\', sizingMethod=\'scale\')')
			.css('-ms-filter', '"progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\''+bg+'\', sizingMethod=\'scale\')"');
	},
	_setAnimations: function() {
		$.fx.off = !this.get('animations');
	},
	background: function() {
		var bg = this._get('background');
		if (!bg) {
			bg = Webos.Theme.defaultBackground();
		}
		return bg;
	},
	setDesktop: function(value) {
		this._set('desktop', String(value));
		return true;
	},
	setBackground: function(value) {
		value = String(value);
		if (value == Webos.Theme.defaultBackground()) {
			value = '';
		}
		this._set('background', value);
		return true;
	},
	setIcons: function(value) {
		this._set('icons', String(value));
		return true;
	},
	setAnimations: function(value) {
		this._set('animations', (value) ? 1 : 0);
		return true;
	},
	sync: function(callback) {
		callback = Webos.Callback.toCallback(callback);

		var that = this;

		var data = {}, nbrChanges = 0;
		for (var key in this._unsynced) {
			if (this._unsynced[key].state === 1) {
				this._unsynced[key].state = 2;
				data[key] = this._unsynced[key].value;
				this._configFile.set(key, data[key]);
				nbrChanges++;
			}
		}

		if (nbrChanges == 0) {
			callback.success(this);
			return;
		}

		this._configFile.sync([function() {
			for (var key in that._unsynced) {
				if (that._unsynced[key].state === 2) {
					that._data[key] = that._unsynced[key].value;
					delete that._unsynced[key];

					switch (key) {
						case 'background':
							that._loadBackground();
							break;
						case 'animations':
							that._setAnimations();
							break;
					}

					that.notify('update', { key: key, value: that._data[key].value });
				}
			}
			callback.success(that);
		}, callback.error]);
	}
};
Webos.inherit(Webos.Theme, Webos.Model);

Webos.Theme._current = null;
Webos.Theme.current = function() {
	return Webos.Theme._current || new Webos.Theme(Webos.ConfigFile.get('~/.theme/'+Webos.UserInterface.current.name()+'/config.xml'));
};
Webos.Theme._defaultBackground = 'usr/share/images/backgrounds/default.jpg';
Webos.Theme.defaultBackground = function() {
	return Webos.Theme._defaultBackground;
};

Webos.Theme.get = function(callback) {
	callback = Webos.Callback.toCallback(callback);

	var ui = Webos.UserInterface.current.name();
	Webos.ConfigFile.loadUserConfig('~/.theme/'+ui+'/config.xml', '/usr/etc/uis/'+ui+'/config.xml', [function(config) {
		var theme = new Webos.Theme(config);
		callback.success(theme);
	}, callback.error]);
};

Webos.Theme.backgroundsDir = '/usr/share/images/backgrounds/';
Webos.Theme.iconsDir = '/usr/share/icons/themes/';
Webos.Theme.getAvailable = function(module, callback) {
	callback = Webos.Callback.toCallback(callback);

	new W.ServerCall({
		'class': 'ThemeController',
		'method': 'getAvailable',
		'arguments': {
			'ui': W.UserInterface.current.name()
		}
	}).load(new Webos.Callback(function(response) {
		callback.success(response.getData());
	}, function(response) {
		callback.error(response);
	}));
};<|endoftext|>


<commit_before><commit_msg>write first tests<commit_after>const assert = require("assert");
const Heap = require("../src/index");
const { isArrayLike, swap, defaultCmp } = require("../src/internal");

describe("Internals", function() {

  describe("isArrayLike", function() {
    it("should recognize array like objects", function() {
      assert.ok(isArrayLike([]));
      assert.ok(isArrayLike((function() { return arguments; })(1, 2, 3)));
      assert.ok(isArrayLike({ 0: "a", 1: "b", 2: "c", length: 3 }));
    });

    describe("not array like arguments", function() {
      [{}, "foobar", 2, null, undefined, true, false].forEach(function(item) {
        it("should return false", function() {
          assert.strictEqual(isArrayLike(item), false);
        });
      });
    });
  });

  describe("swap", function() {
    it("should swap two elements from an array like object", function() {
      var array = [1, 2, 3];
      swap(array, 0, 2);

      assert.deepEqual([3, 2, 1], array);
    });

    it("should throw if coll is not array like", function() {
      assert.throws(swap.bind(null, "foobar", 0, 2));
    });

    describe("indices out of bound", function() {
      [[-1, 0], [0, 4], [0, -1], [4, 0]].forEach(function(pair) {
        it("should throw", function() {
          assert.throws(swap.bind(null, [1, 2, 3], pair[0], pair[1]));
        });
      });
    });
  });

  describe("defaultCmp", function() {
    it("should compare numbers by size", function() {
      assert.strictEqual(defaultCmp(3, 5), -1);
      assert.strictEqual(defaultCmp(5, 3), 1);
      assert.strictEqual(defaultCmp(7, 7), 0);
    });

    it("should compare strings lexicographically", function() {
      assert.strictEqual(defaultCmp("bar", "foo"), -1);
      assert.strictEqual(defaultCmp("foo", "bar"), 1);
      assert.strictEqual(defaultCmp("foo", "foo"), 0);
      assert.strictEqual(defaultCmp("long", "longer"), -1);
      assert.strictEqual(defaultCmp("longer", "long"), 1);
    });

    it("should regard the only comparable value as preferable", function() {
      assert.strictEqual(defaultCmp(null, 7), 1);
      assert.strictEqual(defaultCmp(7, undefined), -1);
      assert.strictEqual(defaultCmp(true, false), 0);
    });
  });
});

describe("Static methods", function() {

  function hasHeapProperty(array, cmp) {
    let lChild, rChild, parent;
    for(let i = 0; i < (array.length - 1) / 2; i++) {
      parent = array[i];

      if(2*i + 1 < array.length) {
        lChild = array[2*i + 1];
        assert.ok(cmp(parent, lChild) <= -1);
      }
      if(2*i + 2 < array.length) {
        rChild = array[2*i + 2];
        assert.ok(cmp(parent, rChild) <= -1);
      }
    }
  }

  describe("heapify", function() {
    it("should arange the array elements s.t they satisfy the heap property", function() {
      const array = [5, 19, 39, 6, 11, 13, 25, 1];

      Heap.heapify(array);

      hasHeapProperty(array, defaultCmp);
    });

    it("should create a heap with a custom comparator function", function() {
      const array = [{ value: 5 }, { value: 39 }, { value: 3 }, { value: 0 }, { value: 16 }];
      const comparator = (a, b) => a.value - b.value;

      Heap.heapify(array, comparator);

      hasHeapProperty(array, comparator);
    });

    it("should throw if custom comparator is not a function", function() {
      const array = [5, 19, 39, 6, 11, 13, 25, 1];
      const comparator = {};

      assert.throws(Heap.heapify.bind(null, array, comparator));
    });

    describe("not array like arguments", function() {
      [{}, "foobar", 2, null, undefined, true, false].forEach(function(item) {
        it("should throw", function() {
          assert.throws(Heap.heapify.bind(null, item));
        });
      });
    });
  });

  describe("merge", function() {

  });
});

describe("Instance methods", function() {
  describe("peek", function() {

  });

  describe("push", function() {

  });

  describe("pop", function() {

  });

  describe("size", function() {

  });

  describe("isEmpty", function() {

  });
});
<|endoftext|>


<commit_before>/* global describe, it, expect */

var MultiHash = require('../lib/multihash');

describe('MultiHash', function() {

  describe('with no elements', function() {
    var hash = new MultiHash();

    it('should have length of zero', function() {
      expect(hash.length).to.equal(0);
    });

    it('should not have non-present key', function() {
      expect(hash.has('foo')).to.be.false;
      expect(hash.count('foo')).to.equal(0);
    });

    it('should not yield any key-value pairs when iterating', function() {
      var obj = [];
      hash.forEach(function(key, value) {
        obj.push({ key: key, value: value });
      });

      expect(obj).to.have.length(0);
    });
  });

});
<commit_msg>Add more test cases.<commit_after>/* global describe, it, expect */

var MultiHash = require('../lib/multihash');

describe('MultiHash', function() {

  describe('with no elements', function() {
    var hash = new MultiHash();

    it('should have length of zero', function() {
      expect(hash.length).to.equal(0);
    });

    it('should not have non-present key', function() {
      expect(hash.has('foo')).to.be.false;
      expect(hash.count('foo')).to.equal(0);
    });

    it('should not yield any key-value pairs when iterating', function() {
      var obj = [];
      hash.forEach(function(key, value) {
        obj.push({ key: key, value: value });
      });

      expect(obj).to.have.length(0);
    });
  });

  describe('with two single-value elements', function() {
    var hash = new MultiHash();
    hash.put('hello', 'world');
    hash.put('foo', 'bar');

    it('should have length of two', function() {
      expect(hash.length).to.equal(2);
    });

    it('should have present keys', function() {
      expect(hash.has('hello')).to.be.true;
      expect(hash.count('hello')).to.equal(1);

      expect(hash.has('foo')).to.be.true;
      expect(hash.count('foo')).to.equal(1);
    });

    it('should yield key-value pairs when iterating', function() {
      var obj = [];
      hash.forEach(function(key, value) {
        obj.push({ key: key, value: value });
      });

      expect(obj).to.have.length(2);
      expect(obj[0].key).to.equal('hello');
      expect(obj[0].value).to.equal('world');
      expect(obj[1].key).to.equal('foo');
      expect(obj[1].value).to.equal('bar');
    });
  });

  describe('with one multi-value element', function() {
    var hash = new MultiHash();
    hash.put('foo', 'bar');
    hash.put('foo', 'baz');

    it('should have length of two', function() {
      expect(hash.length).to.equal(1);
    });

    it('should have present key', function() {
      expect(hash.has('foo')).to.be.true;
      expect(hash.count('foo')).to.equal(2);
    });

    it('should yield key-value pairs when iterating', function() {
      var obj = [];
      hash.forEach(function(key, value) {
        obj.push({ key: key, value: value });
      });

      expect(obj).to.have.length(2);
      expect(obj[0].key).to.equal('foo');
      expect(obj[0].value).to.equal('bar');
      expect(obj[1].key).to.equal('foo');
      expect(obj[1].value).to.equal('baz');
    });

    it('should yield last key-value pair when iterating with flatten option', function() {
      var obj = [];
      hash.forEach(function(key, value) {
        obj.push({ key: key, value: value });
      }, { flatten: true });

      expect(obj).to.have.length(1);
      expect(obj[0].key).to.equal('foo');
      expect(obj[0].value).to.equal('baz');
    });
  });

});
<|endoftext|>


<commit_before>const Promise = require('bluebird');
const Errors = require('common-errors');
const ld = require('lodash');
const paypal = require('paypal-rest-sdk');
const url = require('url');
const key = require('../../redisKey.js');

function agreementCreate(message) {
  const { _config, redis, amqp } = this;
  const promise = Promise.bind(this);

  function sendRequest() {
    return new Promise((resolve, reject) => {
      paypal.billingAgreement.create(message.agreement, _config.paypal, (error, newAgreement) => {
        if (error) {
          reject(error);
        } else {
          const approval = ld.findWhere(newAgreement.links, {rel: 'approval_url'});
          if (approval === null) {
            reject(new Errors.NotSupportedError('Unexpected PayPal response!'));
          } else {
            const token = url.parse(approval.href, true).query.token;
            resolve({token, url: approval.href, agreement: newAgreement});
          }
        }
      });
    });
  }

  function saveToRedis(response) {
    const agreementKey = key('agreements-data', response.agreement.id);
    const pipeline = redis.pipeline;

    pipeline.hsetnx(agreementKey, 'agreement', JSON.stringify(response.agreement));
    pipeline.hsetnx(agreementKey, 'state', response.agreement.state);
    pipeline.hsetnx(agreementKey, 'name', response.agreement.name);
    pipeline.hsetnx(agreementKey, 'token', response.agreement.token);
    pipeline.hsetnx(agreementKey, 'plan', response.agreement.plan.id);
    pipeline.hsetnx(agreementKey, 'owner', message.owner);

    pipeline.sadd('agreements-index', response.agreement.id);

    return pipeline.exec().then(() => {
      return response;
    });
  }

  function updateMetadata(response) {
    const updateRequest = {
      'username': message.owner,
      'audience': _config.billing.audience,
      '$set': {
        'agreement': response.agreement.id,
      },
    };

    return amqp
      .publishAndWait(_config.users.prefix + '.' + _config.users.postfix.updateMetadata, updateRequest, {timeout: 5000})
      .then(() => {
        return response;
      });
  }

  return promise.then(sendRequest).then(saveToRedis).then(updateMetadata);
}

module.exports = agreementCreate;
<commit_msg>Fix missing metadata on agreement create<commit_after>const Promise = require('bluebird');
const Errors = require('common-errors');
const ld = require('lodash');
const paypal = require('paypal-rest-sdk');
const url = require('url');
const key = require('../../redisKey.js');
const getPlan = require('../plan/get');
const moment = require('moment');

function agreementCreate(message) {
  const { _config, redis, amqp } = this;
  const promise = Promise.bind(this);

  function sendRequest() {
    return new Promise((resolve, reject) => {
      paypal.billingAgreement.create(message.agreement, _config.paypal, (error, newAgreement) => {
        if (error) {
          reject(error);
        } else {
          const approval = ld.findWhere(newAgreement.links, {rel: 'approval_url'});
          if (approval === null) {
            reject(new Errors.NotSupportedError('Unexpected PayPal response!'));
          } else {
            const token = url.parse(approval.href, true).query.token;
            resolve({token, url: approval.href, agreement: newAgreement});
          }
        }
      });
    });
  }

  function saveToRedis(response) {
    const agreementKey = key('agreements-data', response.agreement.id);
    const pipeline = redis.pipeline;

    pipeline.hsetnx(agreementKey, 'agreement', JSON.stringify(response.agreement));
    pipeline.hsetnx(agreementKey, 'state', response.agreement.state);
    pipeline.hsetnx(agreementKey, 'name', response.agreement.name);
    pipeline.hsetnx(agreementKey, 'token', response.agreement.token);
    pipeline.hsetnx(agreementKey, 'plan', response.agreement.plan.id);
    pipeline.hsetnx(agreementKey, 'owner', message.owner);

    pipeline.sadd('agreements-index', response.agreement.id);

    return pipeline.exec().return(response.agreement);
  }

  function fetchPlan(agreement) {
    return getPlan(agreement.plan.id).then((plan) => {
      return { agreement, plan };
    });
  }

  function updateMetadata(data) {
    const { plan, agreement } = data;

    const subscription = ld.findWhere(plan.subscriptions, { name: 'free' });
    const nextCycle = moment().add(1, 'month').format();

    const updateRequest = {
      'username': message.owner,
      'audience': _config.billing.audience,
      '$set': {
        'agreement': agreement.id,
        plan: plan.id,
        models: subscription.models,
        model_price: subscription.price,
        next_cycle: nextCycle,
      },
    };

    return amqp
      .publishAndWait(_config.users.prefix + '.' + _config.users.postfix.updateMetadata, updateRequest, {timeout: 5000})
      .return(agreement);
  }

  return promise.then(sendRequest).then(saveToRedis).then(fetchPlan).then(updateMetadata);
}

module.exports = agreementCreate;
<|endoftext|>


<commit_before>var path          = require('path'),
	fs            = require('fs'),
	child_process = require('child_process'),
	os            = require('os'),

	// Resolve directories
	rhinoDir      = path.join( path.dirname( fs.realpathSync( __filename )), '../../..' ),
	canDir        = path.join( rhinoDir, 'can' ),
	docsDir       = path.join( canDir, "docs" ),
	doccoDir      = path.join( canDir, "util/docco" ),
	doccoOutDir   = path.join( doccoDir, "docs" ),
	sourceDir     = path.join( doccoDir, 'standalone' ),
	makePath      = path.join( doccoDir, "makestandalone.js" ),
	genCommand;

function execCommandWithOutput( command, cwd, callback ) {

	var spawn, parts;

	parts = command.split(" ");
	spawn = child_process.spawn( parts.shift(), parts, {
		cwd : cwd,
		env : process.env
	});

	["stdout", "stderr"].forEach( function( stream ) {
		spawn[stream].setEncoding("utf-8");
		spawn[stream].pipe( process[stream] );
	});

	spawn.on("exit", callback );

}

function runDocco() {

	fs.mkdir(docsDir, function() {

		fs.readdir( path.join( doccoDir, "temp" ), function( err, files ) {

			files = files.map(function( file ) {
				return path.join( "temp", file );
			});

			var command = os.platform() != "win32" ?
				"docco " :
				"sh docco ";

			console.log( "Generating docco annotated source..." );

			execCommandWithOutput( command + " " + files.join(" "), doccoDir, function( exitCode ) {
				if ( exitCode == 0 ) {
					fs.readdir( doccoOutDir, function( err, files ) {
						console.log("Moving files into place...");
						files.forEach(function( file ) {
							console.log( "\t" + file );
							fs.renameSync( path.join( doccoDir, "docs", file ), path.join( canDir, "docs", file ));
						});
						console.log("Cleaning up...");
						["temp", "standalone", "docs"].forEach(function( dir ) {
							fs.readdir( path.join( doccoDir, dir ), function( e, files ) {
								files.forEach(function( file ) {
									fs.unlinkSync( path.join( doccoDir, dir, file ));
								});
								fs.rmdir( path.join( doccoDir, dir ));
							});
						});
						console.log("Done!");
					});
				} else {
					console.log("Error generating annotated source.");
				}
			});

		});

	});

}

function format( exitCode ) {

	if ( exitCode != 0 ) {
		console.log("Error generating unminified sources.");
		return
	}

	fs.readdir( sourceDir, function( err, files ) {

		var count = 0;

		if ( ! files.length ) {
			console.log("Error - Source directory is empty");
		}

		// Only annotate full srcs
		files = files.filter(function( file ) {
			return file.indexOf(".min.") < 0;
		});

		// Create the temp directory for stripped code
		fs.mkdir( path.join( doccoDir, "temp"), function() {

			// Generate source for all standalones
			console.log( "Stripping multiline comments and steal removes..." );
			console.log( "Converting tabs to 4 spaces." );

			files.forEach(function( file ) {
				fs.readFile( path.join( sourceDir, file ), "utf-8", function( err, code ) {
					console.log( "\t" + file );

					// Remove multiline comments
					code = code.replace( /\/\*(?:.*)(?:\n\s+\*.*)*\n/gim, "");

					// Remove double semicolons from steal pluginify
					code = code.replace( /;[\s]*;/gim, ";");
					code = code.replace( /(\/\/.*)\n[\s]*;/gi, "$1");

					// Tabs -> four spaces
					code = code.replace( /\t/gim, "    ");

					// Only single new lines
					code = code.replace( /(\n){3,}/gim, "\n\n");

					fs.writeFile( path.join( doccoDir, "temp", file ), code, "utf-8", function() {
						if ( ++count == files.length ) {
							runDocco();
						}
					});
				});
			});
		});
	});
}

console.log("Generating unminified sources...");

if ( os.platform() != "win32" ) {
	genCommand = "./js " + makePath;
} else {
	genCommand = "js.bat " + makePath;
}

execCommandWithOutput( genCommand, rhinoDir, format );
<commit_msg>Type in generate script.<commit_after>var path          = require('path'),
	fs            = require('fs'),
	child_process = require('child_process'),
	os            = require('os'),

	// Resolve directories
	rhinoDir      = path.join( path.dirname( fs.realpathSync( __filename )), '../../..' ),
	canDir        = path.join( rhinoDir, 'can' ),
	docsDir       = path.join( canDir, "docs" ),
	doccoDir      = path.join( canDir, "util/docco" ),
	doccoOutDir   = path.join( doccoDir, "docs" ),
	sourceDir     = path.join( doccoDir, 'standalone' ),
	makePath      = path.join( doccoDir, "makestandalone.js" ),
	genCommand;

function execCommandWithOutput( command, cwd, callback ) {

	var spawn, parts;

	parts = command.split(" ");
	spawn = child_process.spawn( parts.shift(), parts, {
		cwd : cwd,
		env : process.env
	});

	["stdout", "stderr"].forEach( function( stream ) {
		spawn[stream].setEncoding("utf-8");
		spawn[stream].pipe( process[stream] );
	});

	spawn.on("exit", callback );

}

function runDocco() {

	fs.mkdir(docsDir, function() {

		fs.readdir( path.join( doccoDir, "temp" ), function( err, files ) {

			files = files.map(function( file ) {
				return path.join( "temp", file );
			});

			var command = os.platform() != "win32" ?
				"docco " :
				"sh docco ";

			console.log( "Generating docco annotated source..." );

			execCommandWithOutput( command + files.join(" "), doccoDir, function( exitCode ) {
				if ( exitCode == 0 ) {
					fs.readdir( doccoOutDir, function( err, files ) {
						console.log("Moving files into place...");
						files.forEach(function( file ) {
							console.log( "\t" + file );
							fs.renameSync( path.join( doccoDir, "docs", file ), path.join( canDir, "docs", file ));
						});
						console.log("Cleaning up...");
						["temp", "standalone", "docs"].forEach(function( dir ) {
							fs.readdir( path.join( doccoDir, dir ), function( e, files ) {
								files.forEach(function( file ) {
									fs.unlinkSync( path.join( doccoDir, dir, file ));
								});
								fs.rmdir( path.join( doccoDir, dir ));
							});
						});
						console.log("Done!");
					});
				} else {
					console.log("Error generating annotated source.");
				}
			});

		});

	});

}

function format( exitCode ) {

	if ( exitCode != 0 ) {
		console.log("Error generating unminified sources.");
		return
	}

	fs.readdir( sourceDir, function( err, files ) {

		var count = 0;

		if ( ! files.length ) {
			console.log("Error - Source directory is empty");
		}

		// Only annotate full srcs
		files = files.filter(function( file ) {
			return file.indexOf(".min.") < 0;
		});

		// Create the temp directory for stripped code
		fs.mkdir( path.join( doccoDir, "temp"), function() {

			// Generate source for all standalones
			console.log( "Stripping multiline comments and steal removes..." );
			console.log( "Converting tabs to 4 spaces." );

			files.forEach(function( file ) {
				fs.readFile( path.join( sourceDir, file ), "utf-8", function( err, code ) {
					console.log( "\t" + file );

					// Remove multiline comments
					code = code.replace( /\/\*(?:.*)(?:\n\s+\*.*)*\n/gim, "");

					// Remove double semicolons from steal pluginify
					code = code.replace( /;[\s]*;/gim, ";");
					code = code.replace( /(\/\/.*)\n[\s]*;/gi, "$1");

					// Tabs -> four spaces
					code = code.replace( /\t/gim, "    ");

					// Only single new lines
					code = code.replace( /(\n){3,}/gim, "\n\n");

					fs.writeFile( path.join( doccoDir, "temp", file ), code, "utf-8", function() {
						if ( ++count == files.length ) {
							runDocco();
						}
					});
				});
			});
		});
	});
}

console.log("Generating unminified sources...");

if ( os.platform() != "win32" ) {
	genCommand = "./js " + makePath;
} else {
	genCommand = "js.bat " + makePath;
}

execCommandWithOutput( genCommand, rhinoDir, format );
<|endoftext|>


<commit_before>#!/usr/bin/env node

const fs = require("fs");
const path = require("path");
const shell = require("shelljs");
const Main = require("../output/Main");
const Samples = require("../output/Samples");

function exec(s, opts, cb) {
    let result = shell.exec(s, opts, cb);
    if (result.code !== 0) {
        shell.exit(result.code);
    }
    return result;
}

function absolutize(p) {
    if (path.isAbsolute(p))
        return p;
    return path.join(process.cwd(), p);
}

function testCSharp(samples) {
    shell.cd("test/csharp");
    shell.exec("dotnet restore", { silent: true });

    samples.forEach((sample) => {
        console.error(`* Building C# code for ${sample}`);

        var p = sample;
        if (!path.isAbsolute(p))
            p = path.join("..", "..", "app", "public", "sample", "json", p);
        exec(`node ../../cli/quicktype.js --lang cs "${p}" > QuickType.cs`);
        exec(`dotnet run "${p}"`);
    });

    shell.cd("../..");
}

function testGolang(samples) {
    shell.cd("test/golang");

    samples.forEach((sample) => {
        console.error(`* Building Go code for ${sample}`);

        var p = sample;
        if (!path.isAbsolute(p))
            p = path.join("..", "..", "app", "public", "sample", "json", p);
        exec(`node ../../cli/quicktype.js --lang go "${p}" > quicktype.go`);
        exec(`go run main.go quicktype.go < "${p}"`, {silent:true});
    });

    shell.cd("../..");
}

function testAll(samples) {
    testCSharp(samples);
    testGolang(samples);
}

if (process.argv.length == 2) {
    testAll(Samples.samples)
} else {
    for (var i = 2; i < process.argv.length; i++) {
        let arg = process.argv[i];

        if (fs.lstatSync(arg).isDirectory()) {
            fs.readdir(arg, function(err, items) {
                if (err) {
                    console.log("Error: Could not read directory " + arg);
                    process.exit(1);
                }
                let samples = [];
                for (var i=0; i<items.length; i++) {
                    let name = items[i];
                    if (name.startsWith(".") || !name.endsWith(".json"))
                        continue;
                    samples.push(absolutize(path.join(arg, name)));
                }
                testAll(samples);
            });
        } else {
            testAll([absolutize(arg)]);
        }
    }
}
<commit_msg>Faster tests first<commit_after>#!/usr/bin/env node

const fs = require("fs");
const path = require("path");
const shell = require("shelljs");
const Main = require("../output/Main");
const Samples = require("../output/Samples");

function exec(s, opts, cb) {
    let result = shell.exec(s, opts, cb);
    if (result.code !== 0) {
        shell.exit(result.code);
    }
    return result;
}

function absolutize(p) {
    if (path.isAbsolute(p))
        return p;
    return path.join(process.cwd(), p);
}

function testCSharp(samples) {
    shell.cd("test/csharp");
    shell.exec("dotnet restore", { silent: true });

    samples.forEach((sample) => {
        console.error(`* Building C# code for ${sample}`);

        var p = sample;
        if (!path.isAbsolute(p))
            p = path.join("..", "..", "app", "public", "sample", "json", p);
        exec(`node ../../cli/quicktype.js --lang cs "${p}" > QuickType.cs`);
        exec(`dotnet run "${p}"`);
    });

    shell.cd("../..");
}

function testGolang(samples) {
    shell.cd("test/golang");

    samples.forEach((sample) => {
        console.error(`* Building Go code for ${sample}`);

        var p = sample;
        if (!path.isAbsolute(p))
            p = path.join("..", "..", "app", "public", "sample", "json", p);
        exec(`node ../../cli/quicktype.js --lang go "${p}" > quicktype.go`);
        exec(`go run main.go quicktype.go < "${p}"`, {silent:true});
    });

    shell.cd("../..");
}

function testAll(samples) {
    testGolang(samples);
    testCSharp(samples);
}

if (process.argv.length == 2) {
    testAll(Samples.samples)
} else {
    for (var i = 2; i < process.argv.length; i++) {
        let arg = process.argv[i];

        if (fs.lstatSync(arg).isDirectory()) {
            fs.readdir(arg, function(err, items) {
                if (err) {
                    console.log("Error: Could not read directory " + arg);
                    process.exit(1);
                }
                let samples = [];
                for (var i=0; i<items.length; i++) {
                    let name = items[i];
                    if (name.startsWith(".") || !name.endsWith(".json"))
                        continue;
                    samples.push(absolutize(path.join(arg, name)));
                }
                testAll(samples);
            });
        } else {
            testAll([absolutize(arg)]);
        }
    }
}
<|endoftext|>


<commit_before>"use strict";

var test = require('tape');
var isEqual = require('./');

test('primitives', function (t) {
	t.ok(isEqual(), 'undefineds are equal');
	t.ok(isEqual(null, null), 'nulls are equal');
	t.ok(isEqual(true, true), 'trues are equal');
	t.ok(isEqual(false, false), 'falses are equal');
	t.ok(isEqual('foo', 'foo'), 'strings are equal');
	t.ok(isEqual(42, 42), 'numbers are equal');
	t.ok(isEqual(0 / Infinity, -0 / Infinity), 'opposite sign zeroes are equal');
	t.end();
});

test('dates', function (t) {
	t.ok(isEqual(new Date(123), new Date(123)), 'two dates with the same timestamp are equal');
	t.notOk(isEqual(new Date(123), new Date(456)), 'two dates with different timestamp are not equal');
	t.end();
});

test('regexes', function (t) {
	t.ok(isEqual(/a/g, /a/g), 'two regex literals are equal');
	t.notOk(isEqual(/a/g, /b/g), 'two different regex literals are not equal');
	t.ok(isEqual(new RegExp('a', 'g'), new RegExp('a', 'g')), 'two regex objects are equal');
	t.notOk(isEqual(new RegExp('a', 'g'), new RegExp('b', 'g')), 'two different regex objects are equal');
	t.ok(isEqual(new RegExp('a', 'g'), /a/g), 'regex object and literal, same content, are equal');
	t.notOk(isEqual(new RegExp('a', 'g'), /b/g), 'regex object and literal, different content, are not equal');
	t.end();
});

test('arrays', function (t) {
	t.ok(isEqual([], []), 'empty arrays are equal');
	t.ok(isEqual([1, 2, 3], [1, 2, 3]), 'same arrays are equal');
	t.notOk(isEqual([1, 2, 3], [3, 2, 1]), 'arrays in different order with same values are not equal');
	t.notOk(isEqual([1, 2], [1, 2, 3]), 'arrays with different lengths are not equal');
	t.notOk(isEqual([1, 2, 3], [1, 2]), 'arrays with different lengths are not equal');

	t.test('nested values', function (st) {
		st.ok(isEqual([[1, 2], [2, 3], [3, 4]], [[1, 2], [2, 3], [3, 4]]), 'arrays with same array values are equal');
		st.end();
	});
	t.end();
});

test('objects', function (t) {
	t.test('prototypes', function (st) {
		var F = function F() {
			this.foo = 42;
		};
		var G = function G() {};
		G.prototype = new F();
		var H = function H() {};
		H.prototype = G.prototype;
		var I = function I() {};

		var f1 = new F();
		var f2 = new F();
		var g1 = new G();
		var h1 = new H();
		var i1 = new I();

		st.ok(isEqual(f1, f2), 'two instances of the same thing are equal');

		st.ok(isEqual(g1, h1), 'two instances of different things with the same prototype are equal');
		st.notOk(isEqual(f1, i1), 'two instances of different things with a different prototype are not equal');
		st.notOk(isEqual(f1, g1), 'two instances of a parent and child are not equal');

		g1.foo = 'bar';
		var g2 = new G();
		st.notOk(isEqual(g1, g2), 'two instances of the same thing with different properties are not equal');
		st.notOk(isEqual(g2, g1), 'two instances of the same thing with different properties are not equal');
		st.end();
	});

	t.test('literals', function (st) {
		var a = { foo: 42 };
		var b = { foo: 42 };
		st.ok(isEqual(a, a), 'same hash is equal to itself');
		st.ok(isEqual(a, b), 'two similar hashes are equal');
		st.ok(isEqual({ nested: a }, { nested: a }), 'similar hashes with same nested hash are equal');
		st.ok(isEqual({ nested: a }, { nested: b }), 'similar hashes with similar nested hash are equal');

		st.notOk(isEqual({ a: 42, b: 0 }, { a: 42 }), 'first hash missing a key is not equal');
		st.notOk(isEqual({ a: 42 }, { a: 42, b: 0 }), 'second hash missing a key is not equal');
		st.end();
	});

	t.end();
});

test('functions', function (t) {
	var f1 = function f() { /* SOME STUFF */ };
	var f2 = function f() { /* SOME STUFF */ };
	var f3 = function f() { /* SOME DIFFERENT STUFF */ };
	var g = function g() { /* SOME STUFF */};
	var anon1 = function () { /* ANONYMOUS! */ };
	var anon2 = function () { /* ANONYMOUS! */ };

	var anon1withArg = function (a) { /* ANONYMOUS! */ };

	t.ok(isEqual(f1, f1), 'same function is equal to itself');
	t.ok(isEqual(anon1, anon1), 'same anon function is equal to itself');
	t.notOk(isEqual(anon1, anon1withArg), 'similar anon function with different lengths are not equal');

	t.notOk(isEqual(f1, g), 'functions with different names but same implementations are not equal');
	t.ok(isEqual(f1, f2), 'functions with same names but same implementations are equal');
	t.notOk(isEqual(f1, f3), 'functions with same names but different implementations are not equal');
	t.ok(isEqual(anon1, anon2), 'anon functions with same implementations are equal');

	t.end();
});

<commit_msg>Firefox <= 16 elides comments from a function's string representation.<commit_after>"use strict";

var test = require('tape');
var isEqual = require('./');

test('primitives', function (t) {
	t.ok(isEqual(), 'undefineds are equal');
	t.ok(isEqual(null, null), 'nulls are equal');
	t.ok(isEqual(true, true), 'trues are equal');
	t.ok(isEqual(false, false), 'falses are equal');
	t.ok(isEqual('foo', 'foo'), 'strings are equal');
	t.ok(isEqual(42, 42), 'numbers are equal');
	t.ok(isEqual(0 / Infinity, -0 / Infinity), 'opposite sign zeroes are equal');
	t.end();
});

test('dates', function (t) {
	t.ok(isEqual(new Date(123), new Date(123)), 'two dates with the same timestamp are equal');
	t.notOk(isEqual(new Date(123), new Date(456)), 'two dates with different timestamp are not equal');
	t.end();
});

test('regexes', function (t) {
	t.ok(isEqual(/a/g, /a/g), 'two regex literals are equal');
	t.notOk(isEqual(/a/g, /b/g), 'two different regex literals are not equal');
	t.ok(isEqual(new RegExp('a', 'g'), new RegExp('a', 'g')), 'two regex objects are equal');
	t.notOk(isEqual(new RegExp('a', 'g'), new RegExp('b', 'g')), 'two different regex objects are equal');
	t.ok(isEqual(new RegExp('a', 'g'), /a/g), 'regex object and literal, same content, are equal');
	t.notOk(isEqual(new RegExp('a', 'g'), /b/g), 'regex object and literal, different content, are not equal');
	t.end();
});

test('arrays', function (t) {
	t.ok(isEqual([], []), 'empty arrays are equal');
	t.ok(isEqual([1, 2, 3], [1, 2, 3]), 'same arrays are equal');
	t.notOk(isEqual([1, 2, 3], [3, 2, 1]), 'arrays in different order with same values are not equal');
	t.notOk(isEqual([1, 2], [1, 2, 3]), 'arrays with different lengths are not equal');
	t.notOk(isEqual([1, 2, 3], [1, 2]), 'arrays with different lengths are not equal');

	t.test('nested values', function (st) {
		st.ok(isEqual([[1, 2], [2, 3], [3, 4]], [[1, 2], [2, 3], [3, 4]]), 'arrays with same array values are equal');
		st.end();
	});
	t.end();
});

test('objects', function (t) {
	t.test('prototypes', function (st) {
		var F = function F() {
			this.foo = 42;
		};
		var G = function G() {};
		G.prototype = new F();
		var H = function H() {};
		H.prototype = G.prototype;
		var I = function I() {};

		var f1 = new F();
		var f2 = new F();
		var g1 = new G();
		var h1 = new H();
		var i1 = new I();

		st.ok(isEqual(f1, f2), 'two instances of the same thing are equal');

		st.ok(isEqual(g1, h1), 'two instances of different things with the same prototype are equal');
		st.notOk(isEqual(f1, i1), 'two instances of different things with a different prototype are not equal');
		st.notOk(isEqual(f1, g1), 'two instances of a parent and child are not equal');

		g1.foo = 'bar';
		var g2 = new G();
		st.notOk(isEqual(g1, g2), 'two instances of the same thing with different properties are not equal');
		st.notOk(isEqual(g2, g1), 'two instances of the same thing with different properties are not equal');
		st.end();
	});

	t.test('literals', function (st) {
		var a = { foo: 42 };
		var b = { foo: 42 };
		st.ok(isEqual(a, a), 'same hash is equal to itself');
		st.ok(isEqual(a, b), 'two similar hashes are equal');
		st.ok(isEqual({ nested: a }, { nested: a }), 'similar hashes with same nested hash are equal');
		st.ok(isEqual({ nested: a }, { nested: b }), 'similar hashes with similar nested hash are equal');

		st.notOk(isEqual({ a: 42, b: 0 }, { a: 42 }), 'first hash missing a key is not equal');
		st.notOk(isEqual({ a: 42 }, { a: 42, b: 0 }), 'second hash missing a key is not equal');
		st.end();
	});

	t.end();
});

test('functions', function (t) {
	var f1 = function f() { /* SOME STUFF */ return 1; };
	var f2 = function f() { /* SOME STUFF */ return 1; };
	var f3 = function f() { /* SOME DIFFERENT STUFF */ return 2; };
	var g = function g() { /* SOME STUFF */ return 1; };
	var anon1 = function () { /* ANONYMOUS! */ return 'anon'; };
	var anon2 = function () { /* ANONYMOUS! */ return 'anon'; };

	/* for code coverage */
	f1();
	f2();
	f3();
	g();
	anon1();
	anon2();
	/* end for code coverage */

	var anon1withArg = function (a) { /* ANONYMOUS! */ };

	t.ok(isEqual(f1, f1), 'same function is equal to itself');
	t.ok(isEqual(anon1, anon1), 'same anon function is equal to itself');
	t.notOk(isEqual(anon1, anon1withArg), 'similar anon function with different lengths are not equal');

	t.notOk(isEqual(f1, g), 'functions with different names but same implementations are not equal');
	t.ok(isEqual(f1, f2), 'functions with same names but same implementations are equal');
	t.notOk(isEqual(f1, f3), 'functions with same names but different implementations are not equal');
	t.ok(isEqual(anon1, anon2), 'anon functions with same implementations are equal');

	t.end();
});

<|endoftext|>


<commit_before>const chai = require('chai');
const assert = chai.assert;

const NextMove = require('../lib/next-move.js');
const Board = require('../lib/board.js');

describe('NextMove', function() {
  before(function() {
    this.nextMove = new NextMove();
  });

  function arraysAreEqual(a, b) {
    for(var i = 0; i < a.length; i++){
      if(a[i] !== b[i]) return false;
    }
    return true;
  }

  it("knows upper left light on board", function() {
    var level = {
      "0" : "11000",
      "1" : "11000",
      "2" : "00000",
      "3" : "00000",
      "4" : "00000"
    };

    var board = new Board(level);

    var expected = [0, 0];
    var result = this.nextMove.upperLeftLit(board.lightGrid);

    assert(board.lightGrid[0][0].lit);
    assert(arraysAreEqual(expected, result));
  });

  it("knows different upper left light", function() {
    var level = {
      "0" : "01000",
      "1" : "11000",
      "2" : "00000",
      "3" : "00000",
      "4" : "00000"
    };

    var board = new Board(level);

    var expected = [1, 0];
    var result = this.nextMove.upperLeftLit(board.lightGrid);

    assert(board.lightGrid[1][0].lit);
    assert(arraysAreEqual(expected, result));
  });

  it("knows upper left light in second row", function() {
    var level = {
      "0" : "00000",
      "1" : "11000",
      "2" : "00000",
      "3" : "00000",
      "4" : "00000"
    };

    var board = new Board(level);

    var expected = [0, 1];
    var result = this.nextMove.upperLeftLit(board.lightGrid);
    assert(arraysAreEqual(expected, result));
  });

  it("knows for single light on top row", function(){
    var level = {
      "0" : "01000",
      "1" : "11100",
      "2" : "01000",
      "3" : "00000",
      "4" : "00000"
    };
    var board = new Board(level);
    assert(arraysAreEqual([1,1], this.nextMove.forBoard(board)));
  });

  it("knows for different single light on top row", function(){
    var level = {
      "0" : "00100",
      "1" : "01110",
      "2" : "00100",
      "3" : "00000",
      "4" : "00000"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(arraysAreEqual([2,1], result));// this.nextMove.forBoard(board)));
  });

  it("knows for another board for above last row", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "10001",
      "3" : "11011",
      "4" : "11111"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(arraysAreEqual([0,3], result));
  });

  it("knows first move for end game for 10001", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "10001"
    };

    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(arraysAreEqual([0,0], result));
  });

  it("knows second move for end game for 10001", function() {
    var level = {
      "0" : "11000",
      "1" : "10000",
      "2" : "00000",
      "3" : "00000",
      "4" : "10001"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(arraysAreEqual([1,0], result));
  });

  it("knows first move for end game for 01010", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "01010"
    };

    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(arraysAreEqual([0,0], result));
  });

  it("knows second move for end game for 01010", function() {
    var level = {
      "0" : "11000",
      "1" : "10000",
      "2" : "00000",
      "3" : "00000",
      "4" : "01010"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(arraysAreEqual([3,0], result));
  });

  it("knows move for end game for 11100", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "11100"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(arraysAreEqual([1,0], result));
  });

  it("knows move for end game for 00111", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "00111"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(arraysAreEqual([3,0], result));
  });

  it("knows move for end game for 10110", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "10110"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(arraysAreEqual([4,0], result));
  });

  it("knows move for end game for 01101", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "01101"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(arraysAreEqual([0,0], result));
  });

  it("knows move for end game for 11011", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "11011"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(arraysAreEqual([2,0], result));
  });

});<commit_msg>Repoint arraysAreEqual requests to utils<commit_after>const chai = require('chai');
const assert = chai.assert;

const NextMove = require('../lib/next-move.js');
const Board = require('../lib/board.js');
const utils = require('../lib/utils.js');

describe('NextMove', function() {
  before(function() {
    this.nextMove = new NextMove();
  });

  it("knows upper left light on board", function() {
    var level = {
      "0" : "11000",
      "1" : "11000",
      "2" : "00000",
      "3" : "00000",
      "4" : "00000"
    };

    var board = new Board(level);

    var expected = [0, 0];
    var result = this.nextMove.upperLeftLit(board.lightGrid);

    assert(board.lightGrid[0][0].lit);
    assert(utils.arraysAreEqual(expected, result));
  });

  it("knows different upper left light", function() {
    var level = {
      "0" : "01000",
      "1" : "11000",
      "2" : "00000",
      "3" : "00000",
      "4" : "00000"
    };

    var board = new Board(level);

    var expected = [1, 0];
    var result = this.nextMove.upperLeftLit(board.lightGrid);

    assert(board.lightGrid[1][0].lit);
    assert(utils.arraysAreEqual(expected, result));
  });

  it("knows upper left light in second row", function() {
    var level = {
      "0" : "00000",
      "1" : "11000",
      "2" : "00000",
      "3" : "00000",
      "4" : "00000"
    };

    var board = new Board(level);

    var expected = [0, 1];
    var result = this.nextMove.upperLeftLit(board.lightGrid);
    assert(utils.arraysAreEqual(expected, result));
  });

  it("knows for single light on top row", function(){
    var level = {
      "0" : "01000",
      "1" : "11100",
      "2" : "01000",
      "3" : "00000",
      "4" : "00000"
    };
    var board = new Board(level);
    assert(utils.arraysAreEqual([1,1], this.nextMove.forBoard(board)));
  });

  it("knows for different single light on top row", function(){
    var level = {
      "0" : "00100",
      "1" : "01110",
      "2" : "00100",
      "3" : "00000",
      "4" : "00000"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(utils.arraysAreEqual([2,1], result));// this.nextMove.forBoard(board)));
  });

  it("knows for another board for above last row", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "10001",
      "3" : "11011",
      "4" : "11111"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(utils.arraysAreEqual([0,3], result));
  });

  it("knows first move for end game for 10001", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "10001"
    };

    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(utils.arraysAreEqual([0,0], result));
  });

  it("knows second move for end game for 10001", function() {
    var level = {
      "0" : "11000",
      "1" : "10000",
      "2" : "00000",
      "3" : "00000",
      "4" : "10001"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(utils.arraysAreEqual([1,0], result));
  });

  it("knows first move for end game for 01010", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "01010"
    };

    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(utils.arraysAreEqual([0,0], result));
  });

  it("knows second move for end game for 01010", function() {
    var level = {
      "0" : "11000",
      "1" : "10000",
      "2" : "00000",
      "3" : "00000",
      "4" : "01010"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(utils.arraysAreEqual([3,0], result));
  });

  it("knows move for end game for 11100", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "11100"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(utils.arraysAreEqual([1,0], result));
  });

  it("knows move for end game for 00111", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "00111"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(utils.arraysAreEqual([3,0], result));
  });

  it("knows move for end game for 10110", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "10110"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(utils.arraysAreEqual([4,0], result));
  });

  it("knows move for end game for 01101", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "01101"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(utils.arraysAreEqual([0,0], result));
  });

  it("knows move for end game for 11011", function() {
    var level = {
      "0" : "00000",
      "1" : "00000",
      "2" : "00000",
      "3" : "00000",
      "4" : "11011"
    };
    var board = new Board(level);
    var result = this.nextMove.forBoard(board);

    assert(utils.arraysAreEqual([2,0], result));
  });

});<|endoftext|>


<commit_before>import React from 'react';
import $ from 'jquery';
import { Link } from 'react-router-component';

module.exports = React.createClass({
  render: function render() {
    const link = 'shows/' + this.props.show.slug;
    const thumbnailStyle = { backgroundImage: `url('/static/files/${show.image_path}` };
    let episodes = '1 Episode';
    if (this.props.show.num_episodes !== 1) {
      episodes = this.props.show.num_episodes + ' Episodes';
    }
    return (
      <Link href={ link } class="post-module">

{ this.props.show.name }

{ this.props.show.frequency }

{ this.props.show.tagline }

            { episodes }

      </Link>
    );
  },
});
<commit_msg>Refactor showThumbnail<commit_after>import React from 'react';
import $ from 'jquery';
import { Link } from 'react-router-component';

module.exports = React.createClass({

  render: function render() {
    const show = this.props.show;
    const link = 'shows/' + show.slug;
    const thumbnailStyle = { backgroundImage: `url('/static/files/${show.image_path}` };
    let episodes = '1 Episode';
    if (show.num_episodes !== 1) {
      episodes = show.num_episodes + ' Episodes';
    }
    return (
      <Link href={ link } class="post-module">

{ show.name }

{ show.frequency }

{ show.tagline }

            { episodes }

      </Link>
    );
  },
});
<|endoftext|>