<commit_before>import testRule from 'stylelint-test-rule-tape'

import declarationStrictValue, { ruleName } from '../src'

const { rule } = declarationStrictValue

// default config
testRule(rule, {
  ruleName,
  skipBasicChecks: true,

  config: 'color',

  accept: [
    { code: '.foo { color: $bar; }' },
    { code: '.foo { color: @bar; }' },
    { code: '.foo { color: var(--bar); }' },
    { code: '.foo { color: map-get($bar, baz); }' },
    { code: '.foo { color: darken(#fff, 10%); }' },
    { code: '.foo { color: color(#fff, lighten(10%)); }' },
  ],

  reject: [
    {
      code: '.foo { color: #fff; }',
      message: `Expected variable or function for #fff of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { color: red; }',
      message: `Expected variable or function for red of color (${ruleName})`,
      line: 1,
      column: 8,
    },
  ],
})

// disabling ignoreFunction
testRule(rule, {
  ruleName,
  skipBasicChecks: true,

  config: ['color', {
    ignoreFunctions: false,
  }],

  accept: [
    { code: '.foo { color: $bar; }' },
    { code: '.foo { color: @bar; }' },
    { code: '.foo { color: var(--bar); }' },
  ],

  reject: [
    {
      code: '.foo { color: #fff; }',
      message: `Expected variable for #fff of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { color: red; }',
      message: `Expected variable for red of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { color: map-get($bar, baz); }',
      message: `Expected variable for map-get($bar, baz) of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { color: darken(#fff, 10%); }',
      message: `Expected variable for darken(#fff, 10%) of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { color: color(#fff, lighten(10%)); }',
      message: `Expected variable for color(#fff, lighten(10%)) of color (${ruleName})`,
      line: 1,
      column: 8,
    },
  ],
})

// array of properties
testRule(rule, {
  ruleName,
  skipBasicChecks: true,

  config: [['color', 'font-size', 'fill']],

  accept: [
    { code: '.foo { color: $bar; }' },
    { code: '.foo { color: @bar; }' },
    { code: '.foo { color: var(--bar); }' },
    { code: '.foo { font-size: $bar; }' },
    { code: '.foo { font-size: @bar; }' },
    { code: '.foo { font-size: var(--bar); }' },
    { code: '.foo { fill: $bar; }' },
    { code: '.foo { fill: @bar; }' },
    { code: '.foo { fill: var(--bar); }' },
  ],

  reject: [
    {
      code: '.foo { color: #fff; }',
      message: `Expected variable or function for #fff of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { font-size: 16px; }',
      message: `Expected variable or function for 16px of font-size (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { fill: #fff; }',
      message: `Expected variable or function for #fff of fill (${ruleName})`,
      line: 1,
      column: 8,
    },
  ],
})

// RegExp properties
testRule(rule, {
  ruleName,
  skipBasicChecks: true,

  config: '/color$/',

  accept: [
    { code: '.foo { color: $bar; }' },
    { code: '.foo { color: @bar; }' },
    { code: '.foo { color: var(--bar); }' },
    { code: '.foo { background-color: $bar; }' },
    { code: '.foo { background-color: @bar; }' },
    { code: '.foo { background-color: var(--bar); }' },
    { code: '.foo { border-color: $bar; }' },
    { code: '.foo { border-color: @bar; }' },
    { code: '.foo { border-color: var(--bar); }' },
  ],

  reject: [
    {
      code: '.foo { color: #fff; }',
      message: `Expected variable or function for #fff of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { color: red; }',
      message: `Expected variable or function for red of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { background-color: #fff; }',
      message: `Expected variable or function for #fff of background-color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { background-color: red; }',
      message: `Expected variable or function for red of background-color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { border-color: #fff; }',
      message: `Expected variable or function for #fff of border-color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { border-color: red; }',
      message: `Expected variable or function for red of border-color (${ruleName})`,
      line: 1,
      column: 8,
    },
  ],
})

// ignore unlisted properties
testRule(rule, {
  ruleName,
  skipBasicChecks: true,

  config: 'color',

  accept: [
    { code: '.foo { display: block; }' },
    { code: '.foo { position: absolute; }' },
  ],
})

// ignore keywords
testRule(rule, {
  ruleName,
  skipBasicChecks: true,

  config: ['color', {
    ignoreKeywords: 'transparent',
  }],

  accept: [
    { code: '.foo { color: transparent; }' },
  ],

  reject: [
    {
      code: '.foo { color: inherit; }',
      message: `Expected variable, function or keyword for inherit of color (${ruleName})`,
      line: 1,
      column: 8,
    },
  ],
})
<commit_msg>added test for multiple keywords<commit_after>import testRule from 'stylelint-test-rule-tape'

import declarationStrictValue, { ruleName } from '../src'

const { rule } = declarationStrictValue

// default config
testRule(rule, {
  ruleName,
  skipBasicChecks: true,

  config: 'color',

  accept: [
    { code: '.foo { color: $bar; }' },
    { code: '.foo { color: @bar; }' },
    { code: '.foo { color: var(--bar); }' },
    { code: '.foo { color: map-get($bar, baz); }' },
    { code: '.foo { color: darken(#fff, 10%); }' },
    { code: '.foo { color: color(#fff, lighten(10%)); }' },
  ],

  reject: [
    {
      code: '.foo { color: #fff; }',
      message: `Expected variable or function for #fff of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { color: red; }',
      message: `Expected variable or function for red of color (${ruleName})`,
      line: 1,
      column: 8,
    },
  ],
})

// disabling ignoreFunction
testRule(rule, {
  ruleName,
  skipBasicChecks: true,

  config: ['color', {
    ignoreFunctions: false,
  }],

  accept: [
    { code: '.foo { color: $bar; }' },
    { code: '.foo { color: @bar; }' },
    { code: '.foo { color: var(--bar); }' },
  ],

  reject: [
    {
      code: '.foo { color: #fff; }',
      message: `Expected variable for #fff of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { color: red; }',
      message: `Expected variable for red of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { color: map-get($bar, baz); }',
      message: `Expected variable for map-get($bar, baz) of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { color: darken(#fff, 10%); }',
      message: `Expected variable for darken(#fff, 10%) of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { color: color(#fff, lighten(10%)); }',
      message: `Expected variable for color(#fff, lighten(10%)) of color (${ruleName})`,
      line: 1,
      column: 8,
    },
  ],
})

// array of properties
testRule(rule, {
  ruleName,
  skipBasicChecks: true,

  config: [['color', 'font-size', 'fill']],

  accept: [
    { code: '.foo { color: $bar; }' },
    { code: '.foo { color: @bar; }' },
    { code: '.foo { color: var(--bar); }' },
    { code: '.foo { font-size: $bar; }' },
    { code: '.foo { font-size: @bar; }' },
    { code: '.foo { font-size: var(--bar); }' },
    { code: '.foo { fill: $bar; }' },
    { code: '.foo { fill: @bar; }' },
    { code: '.foo { fill: var(--bar); }' },
  ],

  reject: [
    {
      code: '.foo { color: #fff; }',
      message: `Expected variable or function for #fff of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { font-size: 16px; }',
      message: `Expected variable or function for 16px of font-size (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { fill: #fff; }',
      message: `Expected variable or function for #fff of fill (${ruleName})`,
      line: 1,
      column: 8,
    },
  ],
})

// RegExp properties
testRule(rule, {
  ruleName,
  skipBasicChecks: true,

  config: '/color$/',

  accept: [
    { code: '.foo { color: $bar; }' },
    { code: '.foo { color: @bar; }' },
    { code: '.foo { color: var(--bar); }' },
    { code: '.foo { background-color: $bar; }' },
    { code: '.foo { background-color: @bar; }' },
    { code: '.foo { background-color: var(--bar); }' },
    { code: '.foo { border-color: $bar; }' },
    { code: '.foo { border-color: @bar; }' },
    { code: '.foo { border-color: var(--bar); }' },
  ],

  reject: [
    {
      code: '.foo { color: #fff; }',
      message: `Expected variable or function for #fff of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { color: red; }',
      message: `Expected variable or function for red of color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { background-color: #fff; }',
      message: `Expected variable or function for #fff of background-color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { background-color: red; }',
      message: `Expected variable or function for red of background-color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { border-color: #fff; }',
      message: `Expected variable or function for #fff of border-color (${ruleName})`,
      line: 1,
      column: 8,
    },
    {
      code: '.foo { border-color: red; }',
      message: `Expected variable or function for red of border-color (${ruleName})`,
      line: 1,
      column: 8,
    },
  ],
})

// ignore unlisted properties
testRule(rule, {
  ruleName,
  skipBasicChecks: true,

  config: 'color',

  accept: [
    { code: '.foo { display: block; }' },
    { code: '.foo { position: absolute; }' },
  ],
})

// ignore keywords
testRule(rule, {
  ruleName,
  skipBasicChecks: true,

  config: ['color', {
    ignoreKeywords: 'transparent',
  }],

  accept: [
    { code: '.foo { color: transparent; }' },
  ],

  reject: [
    {
      code: '.foo { color: inherit; }',
      message: `Expected variable, function or keyword for inherit of color (${ruleName})`,
      line: 1,
      column: 8,
    },
  ],
})

// ignore multiple keywords
testRule(rule, {
  ruleName,
  skipBasicChecks: true,

  config: ['color', {
    ignoreKeywords: ['transparent', 'currentColor', 'blue'],
  }],

  accept: [
    { code: '.foo { color: transparent; }' },
    { code: '.foo { color: currentColor; }' },
    { code: '.foo { color: blue; }' },
  ],

  reject: [
    {
      code: '.foo { color: inherit; }',
      message: `Expected variable, function or keyword for inherit of color (${ruleName})`,
      line: 1,
      column: 8,
    },
  ],
})
<|endoftext|>


<commit_before>import 'babel-polyfill';
import * as should from '../node_modules/should';
import './base';
import './monad';
import './tuple';
import './list';
<commit_msg>Create Maybe test file<commit_after>import 'babel-polyfill';
import * as should from '../node_modules/should';
import './base';
import './monad';
import './maybe';
import './tuple';
import './list';
<|endoftext|>


<commit_before>/*
 * L.FeatureGroup extends L.LayerGroup by introducing mouse events and bindPopup method shared between a group of layers.
 */

L.FeatureGroup = L.LayerGroup.extend({
	includes: L.Mixin.Events,

	addLayer: function (layer) {
		if (this._layers[L.Util.stamp(layer)]) {
			return this;
		}

		layer.on('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);

		L.LayerGroup.prototype.addLayer.call(this, layer);

		if (this._popupContent && layer.bindPopup) {
			layer.bindPopup(this._popupContent);
		}

		return this;
	},

	removeLayer: function (layer) {
		layer.off('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);

		L.LayerGroup.prototype.removeLayer.call(this, layer);

		return this.invoke('unbindPopup');
	},

	bindPopup: function (content) {
		this._popupContent = content;
		return this.invoke('bindPopup', content);
	},

	setStyle: function (style) {
		return this.invoke('setStyle', style);
	},

	getBounds: function () {
		var bounds = new L.LatLngBounds();
		this.eachLayer(function (layer) {
			bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
		}, this);
		return bounds;
	},

	_propagateEvent: function (e) {
		e.layer  = e.target;
		e.target = this;

		this.fire(e.type, e);
	}
});

L.featureGroup = function (layers) {
	return new L.FeatureGroup(layers);
};
<commit_msg>When removing a marker from a FeatureGroup, only remove the popup if it was put there by the FeatureGroup.<commit_after>/*
 * L.FeatureGroup extends L.LayerGroup by introducing mouse events and bindPopup method shared between a group of layers.
 */

L.FeatureGroup = L.LayerGroup.extend({
	includes: L.Mixin.Events,

	addLayer: function (layer) {
		if (this._layers[L.Util.stamp(layer)]) {
			return this;
		}

		layer.on('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);

		L.LayerGroup.prototype.addLayer.call(this, layer);

		if (this._popupContent && layer.bindPopup) {
			layer.bindPopup(this._popupContent);
		}

		return this;
	},

	removeLayer: function (layer) {
		layer.off('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);

		L.LayerGroup.prototype.removeLayer.call(this, layer);

		if (this._popupContent) {
			return this.invoke('unbindPopup');
		} else {
			return this;
		}
	},

	bindPopup: function (content) {
		this._popupContent = content;
		return this.invoke('bindPopup', content);
	},

	setStyle: function (style) {
		return this.invoke('setStyle', style);
	},

	getBounds: function () {
		var bounds = new L.LatLngBounds();
		this.eachLayer(function (layer) {
			bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
		}, this);
		return bounds;
	},

	_propagateEvent: function (e) {
		e.layer  = e.target;
		e.target = this;

		this.fire(e.type, e);
	}
});

L.featureGroup = function (layers) {
	return new L.FeatureGroup(layers);
};
<|endoftext|>


<commit_before>( function() {
  'use strict';

  var
    $window = $( window ),
    $body = $( 'body' );

  var
    // TODO: These should be settings
    CHARACTERS = 'asdfewjklio'.toUpperCase().split( '' ),
    SCROLL_DISTANCE = 450,  // px
    SCROLL_DURATION = 100;  // ms

  var GREEDY_INPUT_TYPES = [ 'text', 'password', 'phone', 'email' ];

  var
    TARGETABLE_ELEMENTS = [ 'a', 'button', 'input', 'select' ],
    TARGETABLE_ELEMENT_SELECTOR = TARGETABLE_ELEMENTS.join( ', ' );

  var KEYCODE_LOOKUP = {
    9: 'tab', 17: 'ctrl', 16: 'shift',
    18: 'alt', 27: 'esc', 8: 'delete',
    91: 'cmd', 93: 'cmd', 13: 'return',

    65: 'a', 66: 'b', 67: 'c', 68: 'd', 69: 'e',
    70: 'f', 71: 'g', 72: 'h', 73: 'i', 74: 'j',
    75: 'k', 76: 'l', 77: 'm', 78: 'n', 79: 'o',
    80: 'p', 81: 'q', 82: 'r', 83: 's', 84: 't',
    85: 'u', 86: 'v', 87: 'w', 88: 'x', 89: 'y',
    90: 'z',

    219: '[', 191: '/'
  };

  // TODO: Benchmark and improve hint creation time

  var MODIFIER_KEYCODES = [ 16, 17, 18, 91, 93 ];

  var
    MODE = 'command',
    FORCE_NEW_TAB = false,

    SCROLL_IS_LOCKED = false,

    HINTS = [],

    PREVIOUS_KEY = null,
    TYPED_HINT_CHARACTERS = [];

  // // // // //

  function boot() {

    if ( amAnIframe() ) return;

    window.addEventListener( 'keydown', vimmyKeyDownHandler, true )
    $body.append( '

' );

  }

  function amAnIframe() {
    return top !== self;
  }

  function vimmyKeyDownHandler( event ) {

    if ( elementCapturesKeys( document.activeElement ) ) return;

    var
      key = getKeyNameFromEvent( event ),
      $elements;

    if ( MODE === 'command' ) {

      if ( key.isIn([ 'f', 'shift-f' ]) ) {
        swallowEvent( event );

        FORCE_NEW_TAB = ( key === 'shift-f' );

        $elements = getVisibleElements();
        if ( $elements.length === 0 ) return;

        MODE = 'elements';

        HINTS = createHintsForElements( $elements );
        showHints();

        return;
      }

      if ( [ 'h', 'j', 'k', 'l' ].contains( key ) ) {
        swallowEvent( event );

        if ( key === 'h' ) scrollBy( -SCROLL_DISTANCE, 0 );
        if ( key === 'j' ) scrollBy( 0, SCROLL_DISTANCE );
        if ( key === 'k' ) scrollBy( 0, -SCROLL_DISTANCE );
        if ( key === 'l' ) scrollBy( SCROLL_DISTANCE, 0 );

        return;
      }

      if ( key === 'g' && PREVIOUS_KEY === 'g' ) scrollTo( null, 0 );
      if ( key === 'shift-g' ) scrollTo( null, document.height );

    } else if ( MODE === 'elements' ) {
      swallowEvent( event );

      if ( [ 'esc', 'ctrl+[' ].contains( key ) ) {
        MODE = 'command';
        hideHints();

        return;
      }

      if ( key === 'delete' ) {
        TYPED_HINT_CHARACTERS.pop();
        filterHints( TYPED_HINT_CHARACTERS );

        return;
      }

      if ( key.toUpperCase().isIn( CHARACTERS ) ) {
        TYPED_HINT_CHARACTERS.push( key );
        filterHints( TYPED_HINT_CHARACTERS );
      }

    }

    PREVIOUS_KEY = key;
  }

  function swallowEvent( event ) {

    event.preventDefault();
    event.stopPropagation();
    event.stopImmediatePropagation();
  }

  function elementCapturesKeys( element ) {

    var
      tag = element.tagName,
      type = element.getAttribute( 'type' );

    if ( tag === 'TEXTAREA' ) return true;
    if ( tag === 'INPUT' && type.isIn( GREEDY_INPUT_TYPES ) ) return true;
    if ( element.contentEditable === 'true' ) return true;

    return false;
  }

  function getKeyNameFromEvent( event ) {

    var
      keyName,
      modifier = '';

    if ( event.metaKey ) modifier += 'cmd-';
    if ( event.ctrlKey ) modifier += 'ctrl-';
    if ( event.altKey ) modifier +=  'alt-';
    if ( event.shiftKey ) modifier += 'shift-';

    if ( event.keyCode >= 48 && event.keyCode <= 57 ) {
      keyName = ( event.keyCode - 48 ).toString();
    } else {
      keyName = KEYCODE_LOOKUP[ event.keyCode ];
    }

    if ( MODIFIER_KEYCODES.contains( event.keyCode ) ) return keyName;
    return modifier + keyName;
  }

  function getVisibleElements() {

    return Array.prototype.slice
      .call( document.querySelectorAll( TARGETABLE_ELEMENT_SELECTOR ) )
      .filter( inViewPort )
      .filter( isVisible );

  }

  function inViewPort( element ) {

    var bounds = element.getBoundingClientRect()

    return ( bounds.left >= 0 && bounds.top >= 0 && bounds.bottom <= window.innerHeight && bounds.right <= window.innerWidth );

  }

  function isVisible( element ) {
    // TODO: Check performance issues
    if ( element.offsetWidth === 0 && element.offsetHeight === 0 ) return false;
    if ( element.getClientRects().length === 0 ) return false;

    var style = window.getComputedStyle( element );

    if ( style.visibility === 'hidden' ) return false;
    if ( style.opacity === '0' ) return false;

    return true;
  }

  function createHintsForElements( $elements ) {

    var
      requiredHintLength = Math.floor( Math.log( $elements.length ) / Math.log( CHARACTERS.length ) ) + 1, // TODO: Abstract out
      j = 0, i = 0,
      text;

    return $elements.map( function( $element ) {

      text = '';
      for ( j = 1; j <= requiredHintLength; j++ ) {
        text += CHARACTERS[ Math.floor( i /  Math.pow( CHARACTERS.length, requiredHintLength - j ) ) % CHARACTERS.length ]; // TODO: Abstract out
      }

      i += 1;

      text = text.reverse(); // A usability feature, ensure that adjacent hints start with different letters

      return {
        text: text,
        $hint: makeHintHTML( $element, text ),
        $element: $element,
      };

    });

  }

  function makeHintHTML( $element, text ) {

    var
      // TODO: We already calculated bounds, we need to re-use them if possible!
      bounds = $element.getBoundingClientRect(),
      attachment = ( bounds.left > 40 ) ? 'left' : 'right';

    var
      left,
      top;

    if ( attachment === 'left' ) {
      left = ( bounds.left - ( ( text.length * 8 ) + 15 ) ),
      top = ( bounds.top + bounds.bottom ) / 2 - 8;
    } else {
      left = ( bounds.right ) + 8,
      top = ( bounds.top + bounds.bottom ) / 2 - 8;
    };

    return $( '' +
      '' +  text.split('').join('') +
      '' );

  }

  function hideHints() {

    TYPED_HINT_CHARACTERS = [];

    $( '#vimmy-hints' )
      .html( '' );

  }

  function showHints() {

    var $hints = HINTS.map( function( hint ) {
      return hint.$hint;
    });

    $( '#vimmy-hints' )
      .append( $hints )

  }

  function filterHints( characters ) {

    var
      prefix = characters.join( '' ).toUpperCase(),
      activeHints = [],
      activeHint;

    HINTS.forEach( function checkHint( hint ) {

      if ( hint.text.startsWith( prefix ) ) {
        // This hint is valid!
        activeHints.push( hint );
        hint.$hint.children()
          .removeClass( 'typed' )
          .slice( 0, prefix.length )
          .addClass( 'typed' );

        hint.$hint.removeClass( 'ineligible' );
      } else {
        hint.$hint.addClass( 'ineligible')
      }

    });

    if ( activeHints.length > 1 ) return;
    activeHint = activeHints[ 0 ];

    if ( activeHint.text === prefix ) {
      activateElement( activeHint.$element );
      highlightHint( activeHint.$hint );
    }

  }

  function activateElement( $element ) {

    var activator;

    if ( isLinkLike( $element ) ) {
      activator = activateAnchor;
    } else if ( isButtonLike( $element ) ) {
      activator = clickElement;
    } else {
      activator = focusElement;
    }

    window.setTimeout( function() {
      activator( $element );
    }, 0 );

    window.setTimeout( function() {

      hideHints();
      MODE = 'command';
    }, 150 );

  }

  function highlightHint( $hint ) {

    $hint
      .addClass( 'accepted' )
      .find( 'b' )
      .removeClass( 'typed' );

  }

  function isLinkLike( $element ) {
    return $element.tagName === 'A';
  }

  function isButtonLike( $element ) {
    if ( $element.tagName === 'BUTTON' ) return true;
    if ( $element.getAttribute( 'type' ) === 'button' ) return true;

    return false
  }

  function focusElement( $element ) {

    $element.focus();
  }

  function activateAnchor( $anchor ) {

    var
      href = $anchor.getAttribute( 'href' ),
      target = $anchor.getAttribute( 'target' ),
      url = makeAbsoluteUrl( href );

    if ( FORCE_NEW_TAB ) {

      safari.self.tab.dispatchMessage( 'newtab', {
        url: url,
        destination: 'background',
      });

    } else if ( target === '_blank' ) {

      safari.self.tab.dispatchMessage( 'newtab', {
        url: url,
        destination: null,
      });

    } else {

      clickElement( $anchor );
    }

  }

  function clickElement( $element ) {

    $element.click();
  }

  function makeAbsoluteUrl( url ) {

    if ( url.startsWith( '#' ) ) {
      return window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search + url;
    }

    if ( !url.startsWith( 'http' ) ) {

      if ( url.startsWith( '//' ) ) {
        url = window.location.protocol + url;
      } else if ( url.startsWith( '/' ) ) {
        url = window.location.origin + url;
      } else {
        url = window.location.href.match(/.+\//) + url;
      }

    }

    return url;
  }

  function scrollBy( x, y ) {
    scroll( '+=' + ( x || 0 ) , '+=' + ( y || 0 ) );
  }

  function scrollTo( x, y ) {
    scroll( x, y );
  }

  function scroll( left, top ) {

    if ( SCROLL_IS_LOCKED ) return;
    SCROLL_IS_LOCKED = true;

    var animationProperties = {};

    if ( top !== null ) animationProperties.scrollTop = top + 'px';
    if ( left !== null ) animationProperties.scrollLeft = left + 'px';

    $body.animate( animationProperties, SCROLL_DURATION, function() {
      SCROLL_IS_LOCKED = false;
    });

  }

  // // // // //

  boot();

})();
<commit_msg>Prevent swallowing browser events while typing hints.<commit_after>( function() {
  'use strict';

  var
    $window = $( window ),
    $body = $( 'body' );

  var
    // TODO: These should be settings
    CHARACTERS = 'asdfewjklio'.toUpperCase().split( '' ),
    SCROLL_DISTANCE = 450,  // px
    SCROLL_DURATION = 100;  // ms

  var GREEDY_INPUT_TYPES = [ 'text', 'password', 'phone', 'email' ];

  var
    TARGETABLE_ELEMENTS = [ 'a', 'button', 'input', 'select' ],
    TARGETABLE_ELEMENT_SELECTOR = TARGETABLE_ELEMENTS.join( ', ' );

  var KEYCODE_LOOKUP = {
    9: 'tab', 17: 'ctrl', 16: 'shift',
    18: 'alt', 27: 'esc', 8: 'delete',
    91: 'cmd', 93: 'cmd', 13: 'return',

    65: 'a', 66: 'b', 67: 'c', 68: 'd', 69: 'e',
    70: 'f', 71: 'g', 72: 'h', 73: 'i', 74: 'j',
    75: 'k', 76: 'l', 77: 'm', 78: 'n', 79: 'o',
    80: 'p', 81: 'q', 82: 'r', 83: 's', 84: 't',
    85: 'u', 86: 'v', 87: 'w', 88: 'x', 89: 'y',
    90: 'z',

    219: '[', 191: '/'
  };

  // TODO: Benchmark and improve hint creation time

  var MODIFIER_KEYCODES = [ 16, 17, 18, 91, 93 ];

  var
    MODE = 'command',
    FORCE_NEW_TAB = false,

    SCROLL_IS_LOCKED = false,

    HINTS = [],

    PREVIOUS_KEY = null,
    TYPED_HINT_CHARACTERS = [];

  // // // // //

  function boot() {

    if ( amAnIframe() ) return;

    window.addEventListener( 'keydown', vimmyKeyDownHandler, true )
    $body.append( '

' );

  }

  function amAnIframe() {
    return top !== self;
  }

  function vimmyKeyDownHandler( event ) {

    if ( elementCapturesKeys( document.activeElement ) ) return;

    var
      key = getKeyNameFromEvent( event ),
      $elements;

    if ( MODE === 'command' ) {

      if ( key.isIn([ 'f', 'shift-f' ]) ) {
        swallowEvent( event );

        FORCE_NEW_TAB = ( key === 'shift-f' );

        $elements = getVisibleElements();
        if ( $elements.length === 0 ) return;

        MODE = 'elements';

        HINTS = createHintsForElements( $elements );
        showHints();

        return;
      }

      if ( [ 'h', 'j', 'k', 'l' ].contains( key ) ) {
        swallowEvent( event );

        if ( key === 'h' ) scrollBy( -SCROLL_DISTANCE, 0 );
        if ( key === 'j' ) scrollBy( 0, SCROLL_DISTANCE );
        if ( key === 'k' ) scrollBy( 0, -SCROLL_DISTANCE );
        if ( key === 'l' ) scrollBy( SCROLL_DISTANCE, 0 );

        return;
      }

      if ( key === 'g' && PREVIOUS_KEY === 'g' ) scrollTo( null, 0 );
      if ( key === 'shift-g' ) scrollTo( null, document.height );

    } else if ( MODE === 'elements' ) {

      if ( [ 'esc', 'ctrl+[' ].contains( key ) ) {
        swallowEvent( event );

        MODE = 'command';
        hideHints();

        return;
      }

      if ( key === 'delete' ) {
        swallowEvent( event );

        TYPED_HINT_CHARACTERS.pop();
        filterHints( TYPED_HINT_CHARACTERS );

        return;
      }

      if ( key.toUpperCase().isIn( CHARACTERS ) ) {
        swallowEvent( event );

        TYPED_HINT_CHARACTERS.push( key );
        filterHints( TYPED_HINT_CHARACTERS );
      }

    }

    PREVIOUS_KEY = key;
  }

  function swallowEvent( event ) {

    event.preventDefault();
    event.stopPropagation();
    event.stopImmediatePropagation();
  }

  function elementCapturesKeys( element ) {

    var
      tag = element.tagName,
      type = element.getAttribute( 'type' );

    if ( tag === 'TEXTAREA' ) return true;
    if ( tag === 'INPUT' && type.isIn( GREEDY_INPUT_TYPES ) ) return true;
    if ( element.contentEditable === 'true' ) return true;

    return false;
  }

  function getKeyNameFromEvent( event ) {

    var
      keyName,
      modifier = '';

    if ( event.metaKey ) modifier += 'cmd-';
    if ( event.ctrlKey ) modifier += 'ctrl-';
    if ( event.altKey ) modifier +=  'alt-';
    if ( event.shiftKey ) modifier += 'shift-';

    if ( event.keyCode >= 48 && event.keyCode <= 57 ) {
      keyName = ( event.keyCode - 48 ).toString();
    } else {
      keyName = KEYCODE_LOOKUP[ event.keyCode ];
    }

    if ( MODIFIER_KEYCODES.contains( event.keyCode ) ) return keyName;
    return modifier + keyName;
  }

  function getVisibleElements() {

    return Array.prototype.slice
      .call( document.querySelectorAll( TARGETABLE_ELEMENT_SELECTOR ) )
      .filter( inViewPort )
      .filter( isVisible );

  }

  function inViewPort( element ) {

    var bounds = element.getBoundingClientRect()

    return ( bounds.left >= 0 && bounds.top >= 0 && bounds.bottom <= window.innerHeight && bounds.right <= window.innerWidth );

  }

  function isVisible( element ) {
    // TODO: Check performance issues
    if ( element.offsetWidth === 0 && element.offsetHeight === 0 ) return false;
    if ( element.getClientRects().length === 0 ) return false;

    var style = window.getComputedStyle( element );

    if ( style.visibility === 'hidden' ) return false;
    if ( style.opacity === '0' ) return false;

    return true;
  }

  function createHintsForElements( $elements ) {

    var
      requiredHintLength = Math.floor( Math.log( $elements.length ) / Math.log( CHARACTERS.length ) ) + 1, // TODO: Abstract out
      j = 0, i = 0,
      text;

    return $elements.map( function( $element ) {

      text = '';
      for ( j = 1; j <= requiredHintLength; j++ ) {
        text += CHARACTERS[ Math.floor( i /  Math.pow( CHARACTERS.length, requiredHintLength - j ) ) % CHARACTERS.length ]; // TODO: Abstract out
      }

      i += 1;

      text = text.reverse(); // A usability feature, ensure that adjacent hints start with different letters

      return {
        text: text,
        $hint: makeHintHTML( $element, text ),
        $element: $element,
      };

    });

  }

  function makeHintHTML( $element, text ) {

    var
      // TODO: We already calculated bounds, we need to re-use them if possible!
      bounds = $element.getBoundingClientRect(),
      attachment = ( bounds.left > 40 ) ? 'left' : 'right';

    var
      left,
      top;

    if ( attachment === 'left' ) {
      left = ( bounds.left - ( ( text.length * 8 ) + 15 ) ),
      top = ( bounds.top + bounds.bottom ) / 2 - 8;
    } else {
      left = ( bounds.right ) + 8,
      top = ( bounds.top + bounds.bottom ) / 2 - 8;
    };

    return $( '' +
      '' +  text.split('').join('') +
      '' );

  }

  function hideHints() {

    TYPED_HINT_CHARACTERS = [];

    $( '#vimmy-hints' )
      .html( '' );

  }

  function showHints() {

    var $hints = HINTS.map( function( hint ) {
      return hint.$hint;
    });

    $( '#vimmy-hints' )
      .append( $hints )

  }

  function filterHints( characters ) {

    var
      prefix = characters.join( '' ).toUpperCase(),
      activeHints = [],
      activeHint;

    HINTS.forEach( function checkHint( hint ) {

      if ( hint.text.startsWith( prefix ) ) {
        // This hint is valid!
        activeHints.push( hint );
        hint.$hint.children()
          .removeClass( 'typed' )
          .slice( 0, prefix.length )
          .addClass( 'typed' );

        hint.$hint.removeClass( 'ineligible' );
      } else {
        hint.$hint.addClass( 'ineligible')
      }

    });

    if ( activeHints.length > 1 ) return;
    activeHint = activeHints[ 0 ];

    if ( activeHint.text === prefix ) {
      activateElement( activeHint.$element );
      highlightHint( activeHint.$hint );
    }

  }

  function activateElement( $element ) {

    var activator;

    if ( isLinkLike( $element ) ) {
      activator = activateAnchor;
    } else if ( isButtonLike( $element ) ) {
      activator = clickElement;
    } else {
      activator = focusElement;
    }

    window.setTimeout( function() {
      activator( $element );
    }, 0 );

    window.setTimeout( function() {

      hideHints();
      MODE = 'command';
    }, 150 );

  }

  function highlightHint( $hint ) {

    $hint
      .addClass( 'accepted' )
      .find( 'b' )
      .removeClass( 'typed' );

  }

  function isLinkLike( $element ) {
    return $element.tagName === 'A';
  }

  function isButtonLike( $element ) {
    if ( $element.tagName === 'BUTTON' ) return true;
    if ( $element.getAttribute( 'type' ) === 'button' ) return true;

    return false
  }

  function focusElement( $element ) {

    $element.focus();
  }

  function activateAnchor( $anchor ) {

    var
      href = $anchor.getAttribute( 'href' ),
      target = $anchor.getAttribute( 'target' ),
      url = makeAbsoluteUrl( href );

    if ( FORCE_NEW_TAB ) {

      safari.self.tab.dispatchMessage( 'newtab', {
        url: url,
        destination: 'background',
      });

    } else if ( target === '_blank' ) {

      safari.self.tab.dispatchMessage( 'newtab', {
        url: url,
        destination: null,
      });

    } else {

      clickElement( $anchor );
    }

  }

  function clickElement( $element ) {

    $element.click();
  }

  function makeAbsoluteUrl( url ) {

    if ( url.startsWith( '#' ) ) {
      return window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search + url;
    }

    if ( !url.startsWith( 'http' ) ) {

      if ( url.startsWith( '//' ) ) {
        url = window.location.protocol + url;
      } else if ( url.startsWith( '/' ) ) {
        url = window.location.origin + url;
      } else {
        url = window.location.href.match(/.+\//) + url;
      }

    }

    return url;
  }

  function scrollBy( x, y ) {
    scroll( '+=' + ( x || 0 ) , '+=' + ( y || 0 ) );
  }

  function scrollTo( x, y ) {
    scroll( x, y );
  }

  function scroll( left, top ) {

    if ( SCROLL_IS_LOCKED ) return;
    SCROLL_IS_LOCKED = true;

    var animationProperties = {};

    if ( top !== null ) animationProperties.scrollTop = top + 'px';
    if ( left !== null ) animationProperties.scrollLeft = left + 'px';

    $body.animate( animationProperties, SCROLL_DURATION, function() {
      SCROLL_IS_LOCKED = false;
    });

  }

  // // // // //

  boot();

})();
<|endoftext|>


<commit_before>// Define module using Universal Module Definition pattern
// https://github.com/umdjs/umd/blob/master/returnExports.js

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(['vegalite', 'clusterfck'], factory);
  } else if (typeof exports === 'object') {
    // Node. Does not work with strict CommonJS, but
    // only CommonJS-like environments that support module.exports,
    // like Node.
    module.exports = factory(
      require('vegalite'),
      require('clusterfck')
    );
  } else {
    // Browser globals (root is window)
    root.vgn = factory(root.vl, root.clusterfck);
  }
}(this, function (vl, clusterfck) {
  var vgn = {}; //VisGeN

  vgn.DEFAULT_OPT = {
    /**
     * Eliminate all transpose
     * - keeping horizontal dot plot only.
     * - for OxQ charts, always put O on Y
     */
    omitTranpose: true,
    /** remove all dot plot with >1 encoding */
    omitDotPlotWithExtraEncoding: false

  };

  var ENCODING_TYPES = ["x", "y", "row", "col", "size", "shape", "color", "alpha"]; //geo

  var CHART_TYPES = {
    TABLE: 'TABLE',
    BAR: 'BAR',
    PLOT: 'PLOT',
    LINE: 'LINE',
    AREA: 'AREA',
    MAP: 'MAP',
    HISTOGRAM: 'HISTOGRAM'
  };

  var MARK_TYPES = ["point", "bar", "line", "area",  "text"]; //filled_map

  var ANY_DATA_TYPES= (1<<4)-1;

  var AGGREGATION_FN = { //all possible aggregate function listed by each data type
    Q: ["avg", "sum", "min", "max", "count"]
  };

  var TRANSFORM_FN = { //all possible transform function listed by each data type
    Q: ["log", "sqrt", "abs"], // "logit?"
    T: ["year","month","day"] //,"hr", "min", "bmon", "bday", "bdow", "bhr"]
  };

  var json = function(s,sp){ return JSON.stringify(s, null, sp);};

  // Begin of Distance

  var DIST_BY_ENCTYPE = [
    ["x", "y", 0.2],
    ["color", "shape", 0.2],
    ["row", "col", 0.2],
    ["color", "alpha", 0.2]
  ].reduce(function(r, x){
    var a=x[0], b=x[1], d=x[2];
    r[a] = r[a] || {};
    r[b] = r[b] || {};
    r[a][b] = r[b][a] = d;
    return r;
  }, {}),
    DIST_MISSING = 100, CLUSTER_THRESHOLD=1;

  function colenc(encoding){
    var colenc = {},
      enc = encoding.enc;

    vl.keys(enc).forEach(function(encType){
      var e = vl.duplicate(enc[encType]);
      e.type = encType;
      colenc[e.name || ""] = e;
      delete e.name;
    });

    return {
      marktype: encoding.marktype,
      col: colenc
    };
  }

  vgn._getDistance = function(colenc1, colenc2){
    var cols = union(vl.keys(colenc1.col), vl.keys(colenc2.col)),
      dist = 0;

    cols.forEach(function(col){
      var e1 = colenc1.col[col], e2 = colenc2.col[col];

      if(e1 && e2){
        if(e1.type != e2.type){
          dist += (DIST_BY_ENCTYPE[e1.type]||{})[e2.type] || 1;
        }
        //FIXME add aggregation
      }else{
        dist += DIST_MISSING;
      }
    })
    return dist;
  }

  vgn.getDistanceTable = function(encodings){
    var len = encodings.length,
      colencs = encodings.map(function(e){ return colenc(e);}),
      diff = new Array(len), i;

    for(i=0; i<len; i++) diff[i] = new Array(len);

    for(i=0 ; i<len; i++){
      for(j=i+1; j<len; j++){
        diff[j][i] = diff[i][j] = vgn._getDistance(colencs[i], colencs[j]);
      }
    }
    return diff;
  }

  vgn.cluster = function(encodings, maxDistance){
    var dist = vgn.getDistanceTable(encodings),
      n = encodings.length;

    var clusterTrees = clusterfck.hcluster(range(n), function(i, j){
      return dist[i][j];
    }, "average", CLUSTER_THRESHOLD);

    var clusters = clusterTrees.map(function(tree){
      return traverse(tree, []);
    })

    console.log("clusters", clusters.map(function(c){ return c.join("+"); }));
    return clusters;
  }

  function traverse(node, arr){
    if(node.value !== undefined){
      arr.push(node.value);
    }else{
      if(node.left) traverse(node.left, arr)
      if(node.right) traverse(node.right, arr);
    }
    return arr;
  }

  // End of Clustering

  // BEGINING OF RULES

  //TODO markTypesAggregateSupport

  var marksRule = {
    point: pointRule,
    bar: barRule,
    line: lineRule,
    area: lineRule
  };

  function xOyQ(enc){
    return enc.x && enc.y && enc.x.type == "O" && enc.y.type == "Q";
  }

  function pointRule(enc, opt){
    if(enc.x && enc.y){
      if(opt.omitTranpose && xOyQ(enc)){
        return false;
      }

      if(enc.x.type == "O" && enc.y.type == "O"){ OxO
        // shape doesn't work with both x, y as ordinal
        if(enc.shape){
          return false;
        }
        if(enc.color && enc.color.type == "O"){
          return false;
        }
      }

    }else{ // plot with one axis = dot plot
      // Dot plot should always be horizontal
      if(opt.omitTranpose && enc.y) return false;

      // dot plot shouldn't have other encoding
      if(opt.omitDotPlotWithExtraEncoding && vl.keys(enc).length > 1) return false;

      // dot plot with shape is non-sense
      if (enc.shape) return false;
    }
    return true;
  }

  function barRule(enc, opt){

    if(opt.omitTranpose && xOyQ(enc)) return false;

    // Bar Chart requires at least one aggregate
    var hasAgg = false;
    for(var e in enc){
      if(enc[e].aggr){
        hasAgg = true;
        break;
      }
    }
    return hasAgg;
  }

  function lineRule(enc, opt){
    // Line chart should be only horizontal
    // and use only temporal data
    return enc.x == "T" && enc.y == "Q";
  }

  function generalRule(enc){
      // need at least one basic encoding
    if(enc.x || enc.y || enc.geo || enc.text || enc.arc){
      // don't use small multiple before filling up x,y
      if((!enc.x||!enc.y) && (enc.row || enc.col)) return false;

      // one dimension "count" is useless
      if(enc.x && enc.x.aggr=="count" && !enc.y) return false;
      if(enc.y && enc.y.aggr=="count" && !enc.x) return false;

      return true;
    }
    return false;;
  }

  var ENCODING_RULES = {
    x: {
      dataTypes: vl.dataTypes.O + vl.dataTypes.Q + vl.dataTypes.T,
      multiple: true //FIXME should allow multiple only for Q
    },
    y: {
      dataTypes: vl.dataTypes.O + vl.dataTypes.Q + vl.dataTypes.T,
      multiple: true //FIXME should allow multiple only for Q
    },
    row: {
      dataTypes: vl.dataTypes.O,
      multiple: true
    },
    col: {
      dataTypes: vl.dataTypes.O,
      multiple: true
    },
    shape: {
      dataTypes: vl.dataTypes.O
    },
    size: {
      dataTypes: vl.dataTypes.Q
    },
    color: {
      dataTypes: vl.dataTypes.O + vl.dataTypes.Q
    },
    alpha: {
      dataTypes: vl.dataTypes.Q
    },
    text: {
      dataTypes: ANY_DATA_TYPES
    }
    //geo: {
    //  dataTypes: [vl.dataTypes.G]
    //},
    //arc: { // pie
    //
    //}
  };

  // END OF RULES

  // Beginning of Chart Generation

  vgn.generateCharts = function (fields, marktypes, cfg, flat, opt){
    marktypes = marktypes || MARK_TYPES; // assign * if there is no constraints

    opt = opt ? vl.keys(opt).reduce(function(c ,k){ //merge with default
      c[k] = opt[k];
      return c;
    }, Object.create(vgn.DEFAULT_OPT)) : Object.create(vgn.DEFAULT_OPT)

    // generate permutation of encoding mappings
    var encodings = vgn._generateEncodings(fields);

    var chartGroups = encodings.map(function(enc){
      return vgn._getSupportedMarkTypes(enc, opt)
        .map(function(markType){
          return { marktype: markType, enc: enc, cfg: cfg };
        });
    });

    return flat ?
      [].concat.apply([], chartGroups) //flatten
      : chartGroups.filter(function(grp){ return grp.length > 0;}); //return non-empty groups
  };

  //TODO(kanitw): write test case
  vgn._getSupportedMarkTypes = function(enc, opt){
    var markTypes = MARK_TYPES.filter(function(markType){
      var mark = vl.marks[markType],
        reqs = mark.requiredEncoding,
        support = mark.supportedEncoding;

      for(var i in reqs){ // all required encodings in enc
        if(!(reqs[i] in enc)) return false;
      }

      for(var encType in enc){ // all encodings in enc are supported
        if(!support[encType]) return false;
      }

      return !marksRule[markType] || marksRule[markType](enc, opt);
    });

    //console.log('enc:', json(enc), " ~ marks:", markTypes);

    return markTypes;
  };

  //TODO(kanitw): write test case
  vgn._generateEncodings = function (fields){ // generate encodings (_enc property in vega)
    var encodings=[], tmpEnc = {};

    function assignField(i){
      // If all fields are assigned, save
      if(i===fields.length){
        // at the minimal all chart should have x, y, geo, text or arc
        if(generalRule(tmpEnc)){
          encodings.push(vl.duplicate(tmpEnc));
        }
        return;
      }

      // Otherwise, assign i-th field
      var field = fields[i];

      // TODO(kanitw): Aggregation
      // TODO(kanitw): Bin

      for(var j in ENCODING_TYPES){
        var et = ENCODING_TYPES[j];

        //TODO: support "multiple" assignment
        if(!(et in tmpEnc) &&
          (ENCODING_RULES[et].dataTypes & vl.dataTypes[field.type]) > 0){
          tmpEnc[et] = field;
          assignField(i+1);
          delete tmpEnc[et];
        }
      }
    }

    assignField(0);

    return encodings;
  };

  // UTILITY

  function union(a,b){
    var o = {};
    a.forEach(function(x){ o[x] = true;});
    b.forEach(function(x){ o[x] = true;});
    return vl.keys(o);
  }

  var abs = Math.abs;

  function range(start, stop, step) {
    if (arguments.length < 3) {
      step = 1;
      if (arguments.length < 2) {
        stop = start;
        start = 0;
      }
    }
    if ((stop - start) / step === Infinity) throw new Error("infinite range");
    var range = [], k = d3_range_integerScale(abs(step)), i = -1, j;
    start *= k, stop *= k, step *= k;
    if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k);
    return range;
  };

  function d3_range_integerScale(x) {
    var k = 1;
    while (x * k % 1) k *= 10;
    return k;
  }

  return vgn;
}));<commit_msg><typo bug><commit_after>// Define module using Universal Module Definition pattern
// https://github.com/umdjs/umd/blob/master/returnExports.js

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(['vegalite', 'clusterfck'], factory);
  } else if (typeof exports === 'object') {
    // Node. Does not work with strict CommonJS, but
    // only CommonJS-like environments that support module.exports,
    // like Node.
    module.exports = factory(
      require('vegalite'),
      require('clusterfck')
    );
  } else {
    // Browser globals (root is window)
    root.vgn = factory(root.vl, root.clusterfck);
  }
}(this, function (vl, clusterfck) {
  var vgn = {}; //VisGeN

  vgn.DEFAULT_OPT = {
    /**
     * Eliminate all transpose
     * - keeping horizontal dot plot only.
     * - for OxQ charts, always put O on Y
     */
    omitTranpose: true,
    /** remove all dot plot with >1 encoding */
    omitDotPlotWithExtraEncoding: false

  };

  var ENCODING_TYPES = ["x", "y", "row", "col", "size", "shape", "color", "alpha"]; //geo

  var CHART_TYPES = {
    TABLE: 'TABLE',
    BAR: 'BAR',
    PLOT: 'PLOT',
    LINE: 'LINE',
    AREA: 'AREA',
    MAP: 'MAP',
    HISTOGRAM: 'HISTOGRAM'
  };

  var MARK_TYPES = ["point", "bar", "line", "area",  "text"]; //filled_map

  var ANY_DATA_TYPES= (1<<4)-1;

  var AGGREGATION_FN = { //all possible aggregate function listed by each data type
    Q: ["avg", "sum", "min", "max", "count"]
  };

  var TRANSFORM_FN = { //all possible transform function listed by each data type
    Q: ["log", "sqrt", "abs"], // "logit?"
    T: ["year","month","day"] //,"hr", "min", "bmon", "bday", "bdow", "bhr"]
  };

  var json = function(s,sp){ return JSON.stringify(s, null, sp);};

  // Begin of Distance

  var DIST_BY_ENCTYPE = [
    ["x", "y", 0.2],
    ["color", "shape", 0.2],
    ["row", "col", 0.2],
    ["color", "alpha", 0.2]
  ].reduce(function(r, x){
    var a=x[0], b=x[1], d=x[2];
    r[a] = r[a] || {};
    r[b] = r[b] || {};
    r[a][b] = r[b][a] = d;
    return r;
  }, {}),
    DIST_MISSING = 100, CLUSTER_THRESHOLD=1;

  function colenc(encoding){
    var colenc = {},
      enc = encoding.enc;

    vl.keys(enc).forEach(function(encType){
      var e = vl.duplicate(enc[encType]);
      e.type = encType;
      colenc[e.name || ""] = e;
      delete e.name;
    });

    return {
      marktype: encoding.marktype,
      col: colenc
    };
  }

  vgn._getDistance = function(colenc1, colenc2){
    var cols = union(vl.keys(colenc1.col), vl.keys(colenc2.col)),
      dist = 0;

    cols.forEach(function(col){
      var e1 = colenc1.col[col], e2 = colenc2.col[col];

      if(e1 && e2){
        if(e1.type != e2.type){
          dist += (DIST_BY_ENCTYPE[e1.type]||{})[e2.type] || 1;
        }
        //FIXME add aggregation
      }else{
        dist += DIST_MISSING;
      }
    })
    return dist;
  }

  vgn.getDistanceTable = function(encodings){
    var len = encodings.length,
      colencs = encodings.map(function(e){ return colenc(e);}),
      diff = new Array(len), i;

    for(i=0; i<len; i++) diff[i] = new Array(len);

    for(i=0 ; i<len; i++){
      for(j=i+1; j<len; j++){
        diff[j][i] = diff[i][j] = vgn._getDistance(colencs[i], colencs[j]);
      }
    }
    return diff;
  }

  vgn.cluster = function(encodings, maxDistance){
    var dist = vgn.getDistanceTable(encodings),
      n = encodings.length;

    var clusterTrees = clusterfck.hcluster(range(n), function(i, j){
      return dist[i][j];
    }, "average", CLUSTER_THRESHOLD);

    var clusters = clusterTrees.map(function(tree){
      return traverse(tree, []);
    })

    console.log("clusters", clusters.map(function(c){ return c.join("+"); }));
    return clusters;
  }

  function traverse(node, arr){
    if(node.value !== undefined){
      arr.push(node.value);
    }else{
      if(node.left) traverse(node.left, arr)
      if(node.right) traverse(node.right, arr);
    }
    return arr;
  }

  // End of Clustering

  // BEGINING OF RULES

  //TODO markTypesAggregateSupport

  var marksRule = {
    point: pointRule,
    bar: barRule,
    line: lineRule,
    area: lineRule
  };

  function xOyQ(enc){
    return enc.x && enc.y && enc.x.type == "O" && enc.y.type == "Q";
  }

  function pointRule(enc, opt){
    if(enc.x && enc.y){
      if(opt.omitTranpose && xOyQ(enc)){
        return false;
      }

      if(enc.x.type == "O" && enc.y.type == "O"){ //OxO
        // shape doesn't work with both x, y as ordinal
        if(enc.shape){
          return false;
        }
        if(enc.color && enc.color.type == "O"){
          return false;
        }
      }

    }else{ // plot with one axis = dot plot
      // Dot plot should always be horizontal
      if(opt.omitTranpose && enc.y) return false;

      // dot plot shouldn't have other encoding
      if(opt.omitDotPlotWithExtraEncoding && vl.keys(enc).length > 1) return false;

      // dot plot with shape is non-sense
      if (enc.shape) return false;
    }
    return true;
  }

  function barRule(enc, opt){

    if(opt.omitTranpose && xOyQ(enc)) return false;

    // Bar Chart requires at least one aggregate
    var hasAgg = false;
    for(var e in enc){
      if(enc[e].aggr){
        hasAgg = true;
        break;
      }
    }
    return hasAgg;
  }

  function lineRule(enc, opt){
    // Line chart should be only horizontal
    // and use only temporal data
    return enc.x == "T" && enc.y == "Q";
  }

  function generalRule(enc){
      // need at least one basic encoding
    if(enc.x || enc.y || enc.geo || enc.text || enc.arc){
      // don't use small multiple before filling up x,y
      if((!enc.x||!enc.y) && (enc.row || enc.col)) return false;

      // one dimension "count" is useless
      if(enc.x && enc.x.aggr=="count" && !enc.y) return false;
      if(enc.y && enc.y.aggr=="count" && !enc.x) return false;

      return true;
    }
    return false;;
  }

  var ENCODING_RULES = {
    x: {
      dataTypes: vl.dataTypes.O + vl.dataTypes.Q + vl.dataTypes.T,
      multiple: true //FIXME should allow multiple only for Q
    },
    y: {
      dataTypes: vl.dataTypes.O + vl.dataTypes.Q + vl.dataTypes.T,
      multiple: true //FIXME should allow multiple only for Q
    },
    row: {
      dataTypes: vl.dataTypes.O,
      multiple: true
    },
    col: {
      dataTypes: vl.dataTypes.O,
      multiple: true
    },
    shape: {
      dataTypes: vl.dataTypes.O
    },
    size: {
      dataTypes: vl.dataTypes.Q
    },
    color: {
      dataTypes: vl.dataTypes.O + vl.dataTypes.Q
    },
    alpha: {
      dataTypes: vl.dataTypes.Q
    },
    text: {
      dataTypes: ANY_DATA_TYPES
    }
    //geo: {
    //  dataTypes: [vl.dataTypes.G]
    //},
    //arc: { // pie
    //
    //}
  };

  // END OF RULES

  // Beginning of Chart Generation

  vgn.generateCharts = function (fields, marktypes, cfg, flat, opt){
    marktypes = marktypes || MARK_TYPES; // assign * if there is no constraints

    opt = opt ? vl.keys(opt).reduce(function(c ,k){ //merge with default
      c[k] = opt[k];
      return c;
    }, Object.create(vgn.DEFAULT_OPT)) : Object.create(vgn.DEFAULT_OPT)

    // generate permutation of encoding mappings
    var encodings = vgn._generateEncodings(fields);

    var chartGroups = encodings.map(function(enc){
      return vgn._getSupportedMarkTypes(enc, opt)
        .map(function(markType){
          return { marktype: markType, enc: enc, cfg: cfg };
        });
    });

    return flat ?
      [].concat.apply([], chartGroups) //flatten
      : chartGroups.filter(function(grp){ return grp.length > 0;}); //return non-empty groups
  };

  //TODO(kanitw): write test case
  vgn._getSupportedMarkTypes = function(enc, opt){
    var markTypes = MARK_TYPES.filter(function(markType){
      var mark = vl.marks[markType],
        reqs = mark.requiredEncoding,
        support = mark.supportedEncoding;

      for(var i in reqs){ // all required encodings in enc
        if(!(reqs[i] in enc)) return false;
      }

      for(var encType in enc){ // all encodings in enc are supported
        if(!support[encType]) return false;
      }

      return !marksRule[markType] || marksRule[markType](enc, opt);
    });

    //console.log('enc:', json(enc), " ~ marks:", markTypes);

    return markTypes;
  };

  //TODO(kanitw): write test case
  vgn._generateEncodings = function (fields){ // generate encodings (_enc property in vega)
    var encodings=[], tmpEnc = {};

    function assignField(i){
      // If all fields are assigned, save
      if(i===fields.length){
        // at the minimal all chart should have x, y, geo, text or arc
        if(generalRule(tmpEnc)){
          encodings.push(vl.duplicate(tmpEnc));
        }
        return;
      }

      // Otherwise, assign i-th field
      var field = fields[i];

      // TODO(kanitw): Aggregation
      // TODO(kanitw): Bin

      for(var j in ENCODING_TYPES){
        var et = ENCODING_TYPES[j];

        //TODO: support "multiple" assignment
        if(!(et in tmpEnc) &&
          (ENCODING_RULES[et].dataTypes & vl.dataTypes[field.type]) > 0){
          tmpEnc[et] = field;
          assignField(i+1);
          delete tmpEnc[et];
        }
      }
    }

    assignField(0);

    return encodings;
  };

  // UTILITY

  function union(a,b){
    var o = {};
    a.forEach(function(x){ o[x] = true;});
    b.forEach(function(x){ o[x] = true;});
    return vl.keys(o);
  }

  var abs = Math.abs;

  function range(start, stop, step) {
    if (arguments.length < 3) {
      step = 1;
      if (arguments.length < 2) {
        stop = start;
        start = 0;
      }
    }
    if ((stop - start) / step === Infinity) throw new Error("infinite range");
    var range = [], k = d3_range_integerScale(abs(step)), i = -1, j;
    start *= k, stop *= k, step *= k;
    if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k);
    return range;
  };

  function d3_range_integerScale(x) {
    var k = 1;
    while (x * k % 1) k *= 10;
    return k;
  }

  return vgn;
}));<|endoftext|>


<commit_before>/*global describe, it*/
var expect = require('unexpected');

describe('inter', function () {
    var inter = require('../build/en_us');

    describe('#renderDate()', function () {
        var d = new Date(2013, 11, 6, 10, 54, 10);

        it('should format a date according to the mediumDateTime format', function () {
            expect(inter.renderDate(d, 'mediumDateTime'), 'to equal', 'Dec 6, 2013, 10:54:10 am');
        });

        it('should format a date according to the MMMM format (should adapt MMM=LLL to LLLL)', function () {
            expect(inter.renderDate(d, 'MMMM'), 'to equal', 'December');
        });

        it('should use the fullDateTime format if the formatId parameter is omitted', function () {
            var timeZoneOffsetStr = d.toTimeString().match(/GMT([-+]\d\d\d\d)/)[1].replace(/(\d\d)$/, ':$1');
            expect(inter.renderDate(d), 'to equal', 'Friday, December 6, 2013 at 10:54:10 am ' + timeZoneOffsetStr);
        });

        it('should accept a number (epoch milliseconds) instead of a Date instance', function () {
            expect(inter.renderDate(5000, 'shortDate'), 'to equal', '1/1/1970');
        });

        it('should accept a parsable date string instead of a Date instance', function () {
            expect(inter.renderDate('Nov 18 2014 18:00', 'shortDate'), 'to equal', '11/18/2014');
        });

        it('should use the regular month format when using the L format character', function () {
            expect(require('../build/fi').renderDateFormat(new Date('Nov 18 2014 18:00'), 'MMMM'), 'to equal', 'marraskuuta');
        });

        it('should use the standalone month format when using the L format character', function () {
            expect(require('../build/fi').renderDateFormat(new Date('Nov 18 2014 18:00'), 'LLLL'), 'to equal', 'marraskuu');
        });
    });

    describe('#renderList()', function () {
        it('should should render a list according to the unitNarrow pattern', function () {
            expect(inter.renderList(['foo', 'bar', 'quux'], 'unitNarrow'), 'to equal', 'foo bar quux')
        });

        it('should use the default format when the type parameter is omitted', function () {
            expect(inter.renderList(['foo', 'bar', 'quux']), 'to equal', 'foo, bar, and quux')
        });
    });

    describe('#renderNumber', function () {
        it('should render a number in the Indian English locale (#11)', function () {
            expect(require('../build/en_in').renderNumber(10000000.23), 'to equal', '10,000,000.23');
        });

        it('should render a number in the Danish locale (#11)', function () {
            expect(require('../build/da').renderNumber(10000000.23), 'to equal', '10,000,000.23');
        });
    });

    describe('#renderPercentage', function () {
        it('should accept the number of decimals as the second parameter', function () {
            expect(require('../build/da').renderPercentage(12.345678, 2), 'to equal', '1.234,57\xa0%');
        });

        it('should accept the number system as the third parameter', function () {
            expect(require('../build/da').renderPercentage(12.345678, 2, 'mlym'), 'to equal', '൧.൨൩൪,൫൭\xa0%');
        });
    });

    describe('#renderUnit', function () {
        it('should render a singular number of units', function () {
            expect(inter.renderUnit(1, 'durationWeek'), 'to equal', '1 week');
        });

        it('should render a plural number of units', function () {
            expect(inter.renderUnit(2, 'durationWeek'), 'to equal', '2 weeks');
        });

        it('should render in the narrow format', function () {
            expect(inter.renderUnit(1, 'durationWeek', 'narrow'), 'to equal', '1w');
        });
    });

    describe('#renderCurrencyShort', function () {
        it('should render a float with a currency', function () {
            expect(inter.renderCurrencyShort(134.40, '$'), 'to equal', '$134.40');
        });
    });

    describe('#makeUnitRenderer', function () {
        it('should render in the narrow format', function () {
            expect(inter.makeUnitRenderer('durationWeek', 'narrow')(1), 'to equal', '1w');
        });
    });
});
<commit_msg>Add one more test<commit_after>/*global describe, it*/
var expect = require('unexpected');

describe('inter', function () {
    var inter = require('../build/en_us');

    describe('#renderDate()', function () {
        var d = new Date(2013, 11, 6, 10, 54, 10);

        it('should format a date according to the mediumDateTime format', function () {
            expect(inter.renderDate(d, 'mediumDateTime'), 'to equal', 'Dec 6, 2013, 10:54:10 am');
        });

        it('should format a date according to the MMMM format (should adapt MMM=LLL to LLLL)', function () {
            expect(inter.renderDate(d, 'MMMM'), 'to equal', 'December');
        });

        it('should use the fullDateTime format if the formatId parameter is omitted', function () {
            var timeZoneOffsetStr = d.toTimeString().match(/GMT([-+]\d\d\d\d)/)[1].replace(/(\d\d)$/, ':$1');
            expect(inter.renderDate(d), 'to equal', 'Friday, December 6, 2013 at 10:54:10 am ' + timeZoneOffsetStr);
        });

        it('should accept a number (epoch milliseconds) instead of a Date instance', function () {
            expect(inter.renderDate(5000, 'shortDate'), 'to equal', '1/1/1970');
        });

        it('should accept a parsable date string instead of a Date instance', function () {
            expect(inter.renderDate('Nov 18 2014 18:00', 'shortDate'), 'to equal', '11/18/2014');
        });

        it('should use the regular month format when using the L format character', function () {
            expect(require('../build/fi').renderDateFormat(new Date('Nov 18 2014 18:00'), 'MMMM'), 'to equal', 'marraskuuta');
        });

        it('should use the standalone month format when using the L format character', function () {
            expect(require('../build/fi').renderDateFormat(new Date('Nov 18 2014 18:00'), 'LLLL'), 'to equal', 'marraskuu');
        });
    });

    describe('#renderList()', function () {
        it('should should render a list according to the unitNarrow pattern', function () {
            expect(inter.renderList(['foo', 'bar', 'quux'], 'unitNarrow'), 'to equal', 'foo bar quux')
        });

        it('should use the default format when the type parameter is omitted', function () {
            expect(inter.renderList(['foo', 'bar', 'quux']), 'to equal', 'foo, bar, and quux')
        });
    });

    describe('#renderNumber', function () {
        it('should render a number in the Indian English locale (#11)', function () {
            expect(require('../build/en_in').renderNumber(10000000.23), 'to equal', '10,000,000.23');
        });

        it('should render a number in the Danish locale (#11)', function () {
            expect(require('../build/da').renderNumber(10000000.23), 'to equal', '10,000,000.23');
        });
    });

    describe('#renderPercentage', function () {
        it('should accept the number of decimals as the second parameter', function () {
            expect(require('../build/da').renderPercentage(12.345678, 2), 'to equal', '1.234,57\xa0%');
        });

        it('should accept the number system as the third parameter', function () {
            expect(require('../build/da').renderPercentage(12.345678, 2, 'mlym'), 'to equal', '൧.൨൩൪,൫൭\xa0%');
        });
    });

    describe('#renderUnit', function () {
        it('should render a singular number of units', function () {
            expect(inter.renderUnit(1, 'durationWeek'), 'to equal', '1 week');
        });

        it('should render a plural number of units', function () {
            expect(inter.renderUnit(2, 'durationWeek'), 'to equal', '2 weeks');
        });

        it('should render in the narrow format', function () {
            expect(inter.renderUnit(1, 'durationWeek', 'narrow'), 'to equal', '1w');
        });
    });

    describe('#renderCurrencyShort', function () {
        it('should render a float with a currency', function () {
            expect(inter.renderCurrencyShort(134.40, '$'), 'to equal', '$134.40');
        });

        it('should render a number in the Dutch locale', function () {
            expect(require('../build/nl').renderCurrencyShort(134.40, '€'), 'to equal', '€ 134,40');
        });
    });

    describe('#makeUnitRenderer', function () {
        it('should render in the narrow format', function () {
            expect(inter.makeUnitRenderer('durationWeek', 'narrow')(1), 'to equal', '1w');
        });
    });
});
<|endoftext|>


<commit_before>var bitcoinjs = require('bitcoinjs-lib')
var braveHapi = require('./brave-hapi')
var crypto = require('crypto')
var debug = new (require('sdebug'))('wallet')
var Joi = require('joi')
var underscore = require('underscore')

var onceonlyP

var Wallet = function (config, runtime) {
  if (!(this instanceof Wallet)) return new Wallet(config)

  if (!config.wallet) throw new Error('config.wallet undefined')

  if (!config.wallet.bitgo) config.wallet = { bitgo: config.wallet }
  this.config = config.wallet
  this.config.environment = config.wallet.bitgo.environment
  this.runtime = runtime
  this.bitgo = new (require('bitgo')).BitGo({
    accessToken: config.wallet.bitgo.accessToken,
    env: config.wallet.bitgo.environment || 'prod'
  })
  debug('environment: ' + this.config.environment)

  if (!onceonlyP) {
    onceonlyP = true

    maintenance(this.config, this.runtime)
    setInterval(function () { maintenance(this.config, this.runtime) }.bind(this), 15 * 60 * 1000)
  }
}

Wallet.prototype.create = async function (prefix, label, keychains) {
  var result
  var xpubs = []

  xpubs[0] = underscore.pick(await this.bitgo.keychains().add(underscore.extend({ label: 'user' }, keychains.user)), [ 'xpub' ])
  xpubs[1] = underscore.pick(await this.bitgo.keychains().add({
    label: 'unspendable',
    xpub: this.config.bitgo.unspendableXpub
  }), [ 'xpub' ])
  xpubs[2] = underscore.pick(await this.bitgo.keychains().createBitGo({}), [ 'xpub' ])

  result = await this.bitgo.wallets().add({
    label: label,
    m: 2,
    n: 3,
    keychains: xpubs,
    enterprise: this.config.bitgo.enterpriseId,
    disableTransactionNotifications: true
  })
  result.wallet.provider = 'bitgo'

  result.addWebhook({ url: prefix + '/callbacks/bitgo/sink', type: 'transaction', numConfirmations: 1 }, function (err) {
    if (err) debug('wallet addWebhook', { label: label, message: err.toString() })

    result.setPolicyRule({
      id: 'com.brave.limit.velocity.30d',
      type: 'velocityLimit',
      condition: {
        type: 'velocity',
        amount: 7000000,
        timeWindow: 30 * 86400,
        groupTags: [],
        excludeTags: []
      },
      action: { type: 'deny' } }, function (err) {
      if (err) debug('wallet setPolicyRule', { label: label, message: err.toString() })
    })
  })

  return result
}

Wallet.prototype.balances = async function (info) {
  var f = Wallet.providers[info.provider].balances

  if (!f) throw new Error('provider ' + info.provider + ' balances not supported')
  return f.bind(this)(info)
}

Wallet.prototype.purchaseBTC = function (info, amount, currency) {
  var f = Wallet.providers[info.provider].purchaseBTC

  if (!f) f = Wallet.providers.coinbase.purchaseBTC
  if (!f) return {}
  return f.bind(this)(info, amount, currency)
}

Wallet.prototype.recurringBTC = function (info, amount, currency) {
  var f = Wallet.providers[info.provider].recurringBTC

  if (!f) f = Wallet.providers.coinbase.recurringBTC
  if (!f) return {}
  return f.bind(this)(info, amount, currency)
}

Wallet.prototype.transferP = function (info) {
  var f = Wallet.providers[info.provider].transferP

  return ((!!f) && (f.bind(this)(info)))
}

Wallet.prototype.transfer = async function (info, satoshis) {
  var f = Wallet.providers[info.provider].transfer

  if (!f) throw new Error('provider ' + info.provider + ' transfer not supported')
  return f.bind(this)(info, satoshis)
}

Wallet.prototype.compareTx = function (unsignedHex, signedHex) {
  var i
  var signedTx = bitcoinjs.Transaction.fromHex(signedHex)
  var unsignedTx = bitcoinjs.Transaction.fromHex(unsignedHex)

  if ((unsignedTx.version !== signedTx.version) || (unsignedTx.locktime !== signedTx.locktime)) return false

  if (unsignedTx.ins.length !== signedTx.ins.length) return false
  for (i = 0; i < unsignedTx.ins.length; i++) {
    if (!underscore.isEqual(underscore.omit(unsignedTx.ins[i], 'script'), underscore.omit(signedTx.ins[i], 'script'))) {
      return false
    }
  }

  return underscore.isEqual(unsignedTx.outs, signedTx.outs)
}

Wallet.prototype.submitTx = async function (info, signedTx) {
  var f = Wallet.providers[info.provider].submitTx

  if (!f) throw new Error('provider ' + info.provider + ' submitTx not supported')
  return f.bind(this)(info, signedTx)
}

Wallet.prototype.unsignedTx = async function (info, amount, currency, balance) {
  var f = Wallet.providers[info.provider].unsignedTx

  if (!f) throw new Error('provider ' + info.provider + ' unsignedTx not supported')
  return f.bind(this)(info, amount, currency, balance)
}

Wallet.prototype.rates = {}

var schema1 = Joi.object({}).pattern(/timestamp|[A-Z][A-Z][A-Z]/,
                                     Joi.alternatives().try(Joi.date(),
                                                            Joi.object().keys({ last: Joi.number().positive() }).unknown(true)))
                 .required()
var schema2 = Joi.object()
              .keys({
                bpi: Joi.object().pattern(/[A-Z][A-Z][A-Z]/,
                                          Joi.object().keys({
                                            code: Joi.string().regex(/[A-Z][A-Z][A-Z]/).required(),
                                            rate_float: Joi.number().positive().required()
                                          }).unknown(true)).required()

              }).unknown(true)

var maintenance = async function (config, runtime) {
  var rates, result1, result2
  var timestamp = Math.round(underscore.now() / 1000)
  var prefix = timestamp + '.' + config.bitcoin_average.publicKey
  var suffix = crypto.createHmac('sha256', config.bitcoin_average.secretKey).update(prefix).digest('hex')
  var signature = prefix + '.' + suffix

  var fetch = async function (url, props, schema) {
    var result, validity

    result = await braveHapi.wreck.get(url, props)
    if (Buffer.isBuffer(result)) result = result.toString()
// courtesy of https://stackoverflow.com/questions/822452/strip-html-from-text-javascript#822464
    if (result.indexOf('<html>') !== -1) throw new Error(result.replace(/<(?:.|\n)*?>/gm, ''))

    result = JSON.parse(result)
    validity = Joi.validate(result, schema)
    if (validity.error) throw new Error(validity.error)

    return result
  }

  try {
    result1 = await fetch('https://apiv2.bitcoinaverage.com/indices/global/ticker/all?crypto=BTC',
                          { headers: { 'x-signature': signature } }, schema1)
  } catch (ex) {
    return runtime.notify(debug, { text: 'maintenance error(1): ' + ex.toString() })
  }

  try {
    result2 = await fetch('http://api.coindesk.com/v1/bpi/currentprice.json', {}, schema2)
  } catch (ex) {
    return runtime.notify(debug, { text: 'maintenance error(2): ' + ex.toString() })
  }

  rates = {}
  underscore.keys(result1).forEach(currency => {
    var rate = result1[currency]

    if ((currency.indexOf('BTC') !== 0) || (typeof rate !== 'object') || (!rate.last)) return

    rates[currency.substr(3)] = rate.last
  })
  if ((!rates.USD) || (!rates.EUR) || (!rates.GBP)) {
    return runtime.notify(debug, { text: 'maintenance error(3): currencies available ' + underscore.keys(rates) })
  }
  if ((!result2.bpi.USD) || (!result2.bpi.EUR) || (!result2.bpi.GBP)) {
    return runtime.notify(debug, { text: 'maintenance error(4): currencies available ' + underscore.keys(result2.bpi) })
  }

  var compar = (currency) => {
    var ratio = rates[currency] / result2.bpi[currency].rate_float

    if ((ratio < 0.985) || (ratio > 1.015)) {
      throw new Error('maintenance error(5): ' + currency + ' ' + rates[currency] + ' vs. ' + result2.bpi[currency].rate_float)
    }
  }

  try {
    compar('USD')
    compar('EUR')
    compar('GBP')
  } catch (ex) {
    return runtime.notify(debug, { text: ex.toString() })
  }

  Wallet.prototype.rates = rates
  debug('BTC key rates', underscore.pick(rates, [ 'USD', 'EUR', 'GBP' ]))
}

module.exports = Wallet

Wallet.providers = {}

Wallet.providers.bitgo = {
  balances: async function (info) {
    var wallet = await this.bitgo.wallets().get({ type: 'bitcoin', id: info.address })

    return {
      balance: wallet.balance(),
      spendable: wallet.spendableBalance(),
      confirmed: wallet.confirmedBalance(),
      unconfirmed: wallet.unconfirmedReceives()
    }
  },

  submitTx: async function (info, signedTx) {
    var details, i, result
    var wallet = await this.bitgo.wallets().get({ type: 'bitcoin', id: info.address })

    result = await wallet.sendTransaction({ tx: signedTx })

// courtesy of https://stackoverflow.com/questions/33289726/combination-of-async-function-await-settimeout#33292942
    var timeout = function (msec) { return new Promise((resolve) => { setTimeout(resolve, msec) }) }

    for (i = 0; i < 5; i++) {
      try {
        details = await this.bitgo.blockchain().getTransaction({ id: result.hash })
        break
      } catch (ex) {
        debug('getTransaction', ex)
        await timeout(1 * 1000)
        debug('getTransaction', { retry: i + 1, max: 5 })
      }
    }
    underscore.extend(result, { fee: details.fee })

    for (i = details.outputs.length - 1; i >= 0; i--) {
      if (details.outputs[i].account !== this.config.bitgo.settlementAddress) continue

      underscore.extend(result, { address: details.outputs[i].account, satoshis: details.outputs[i].value })
      break
    }

    return result
  },

  transferP: async function (info) {
    console.log('transferP=' + ((!!this.config.bitgo.fundingAddress) && (!!this.config.bitgo.fundingPassphrase)))
    return ((!!this.config.bitgo.fundingAddress) && (!!this.config.bitgo.fundingPassphrase))
  },

  transfer: async function (info, satoshis) {
    var result, wallet

    if (!this.config.bitgo.fundingAddress) throw new Error('no funding address configured')
    if (!this.config.bitgo.fundingPassphrase) throw new Error('no funding passphrase configured')

    wallet = await this.bitgo.wallets().get({ type: 'bitcoin', id: this.config.bitgo.fundingAddress })
    try {
      result = await wallet.sendCoins({ address: info.address,
        amount: satoshis,
        walletPassphrase: this.config.bitgo.fundingPassphrase
      })

      return underscore.pick(result, [ 'hash', 'fee' ])
    } catch (ex) {
      throw new Error(ex.error)
    }
  },

  unsignedTx: async function (info, amount, currency, balance) {
    var desired, i, minimum, transaction, wallet
    var estimate = await this.bitgo.estimateFee({ numBlocks: 6 })
    var fee = estimate.feePerKb
    var rate = Wallet.prototype.rates[currency.toUpperCase()]
    var recipients = {}

    if (!rate) throw new Error('no such currency: ' + currency)

    desired = (amount / rate) * 1e8
    minimum = Math.floor(desired * 0.90)
    desired = Math.round(desired)
    debug('unsignedTx', { balance: balance, desired: desired, minimum: minimum })
    if (minimum > balance) return

    if (desired > balance) desired = balance

    wallet = await this.bitgo.wallets().get({ type: 'bitcoin', id: info.address })
    for (i = 0; i < 2; i++) {
      recipients[this.config.bitgo.settlementAddress] = desired - fee

      try {
        transaction = await wallet.createTransaction({ recipients: recipients, feeRate: estimate.feePerKb })
        debug('unsignedTx', { satoshis: desired, estimate: fee, actual: transaction.fee })
      } catch (ex) {
        debug('createTransaction', ex)
        this.runtime.newrelic.noticeError(ex, { recipients: recipients, feeRate: estimate.feePerKb })
        return
      }
      if (fee <= transaction.fee) break

      fee = transaction.fee
    }

    return underscore.extend(underscore.pick(transaction, [ 'transactionHex', 'unspents', 'fee' ]),
                             { xpub: transaction.walletKeychains[0].xpub })
  }
}

Wallet.providers.coinbase = {
  purchaseBTC: function (info, amount, currency) {
    // TBD: for the moment...
    if (currency !== 'USD') throw new Error('currency ' + currency + ' payment not supported')

    return ({
      buyURL: `https://buy.coinbase.com?crypto_currency=BTC` +
                `&code=${this.config.coinbase.widgetCode}` +
                `&amount=${amount}` +
                `&address=${info.address}`
    })
  },

  recurringBTC: function (info, amount, currency) {
    // TBD: for the moment...
    if (currency !== 'USD') throw new Error('currency ' + currency + ' payment not supported')

    return ({recurringURL: `https://www.coinbase.com/recurring_payments/new?type=send&repeat=monthly` +
                `&amount=${amount}` +
                `&currency=${currency}` +
                `&to=${info.address}`
    })
  }
}
<commit_msg>Final fix!<commit_after>var bitcoinjs = require('bitcoinjs-lib')
var braveHapi = require('./brave-hapi')
var crypto = require('crypto')
var debug = new (require('sdebug'))('wallet')
var Joi = require('joi')
var underscore = require('underscore')

var onceonlyP

var Wallet = function (config, runtime) {
  if (!(this instanceof Wallet)) return new Wallet(config)

  if (!config.wallet) throw new Error('config.wallet undefined')

  if (!config.wallet.bitgo) config.wallet = { bitgo: config.wallet }
  this.config = config.wallet
  this.config.environment = config.wallet.bitgo.environment
  this.runtime = runtime
  this.bitgo = new (require('bitgo')).BitGo({
    accessToken: config.wallet.bitgo.accessToken,
    env: config.wallet.bitgo.environment || 'prod'
  })
  debug('environment: ' + this.config.environment)

  if (!onceonlyP) {
    onceonlyP = true

    maintenance(this.config, this.runtime)
    setInterval(function () { maintenance(this.config, this.runtime) }.bind(this), 15 * 60 * 1000)
  }
}

Wallet.prototype.create = async function (prefix, label, keychains) {
  var result
  var xpubs = []

  xpubs[0] = underscore.pick(await this.bitgo.keychains().add(underscore.extend({ label: 'user' }, keychains.user)), [ 'xpub' ])
  xpubs[1] = underscore.pick(await this.bitgo.keychains().add({
    label: 'unspendable',
    xpub: this.config.bitgo.unspendableXpub
  }), [ 'xpub' ])
  xpubs[2] = underscore.pick(await this.bitgo.keychains().createBitGo({}), [ 'xpub' ])

  result = await this.bitgo.wallets().add({
    label: label,
    m: 2,
    n: 3,
    keychains: xpubs,
    enterprise: this.config.bitgo.enterpriseId,
    disableTransactionNotifications: true
  })
  result.wallet.provider = 'bitgo'

  result.addWebhook({ url: prefix + '/callbacks/bitgo/sink', type: 'transaction', numConfirmations: 1 }, function (err) {
    if (err) debug('wallet addWebhook', { label: label, message: err.toString() })

    result.setPolicyRule({
      id: 'com.brave.limit.velocity.30d',
      type: 'velocityLimit',
      condition: {
        type: 'velocity',
        amount: 7000000,
        timeWindow: 30 * 86400,
        groupTags: [],
        excludeTags: []
      },
      action: { type: 'deny' } }, function (err) {
      if (err) debug('wallet setPolicyRule', { label: label, message: err.toString() })
    })
  })

  return result
}

Wallet.prototype.balances = async function (info) {
  var f = Wallet.providers[info.provider].balances

  if (!f) throw new Error('provider ' + info.provider + ' balances not supported')
  return f.bind(this)(info)
}

Wallet.prototype.purchaseBTC = function (info, amount, currency) {
  var f = Wallet.providers[info.provider].purchaseBTC

  if (!f) f = Wallet.providers.coinbase.purchaseBTC
  if (!f) return {}
  return f.bind(this)(info, amount, currency)
}

Wallet.prototype.recurringBTC = function (info, amount, currency) {
  var f = Wallet.providers[info.provider].recurringBTC

  if (!f) f = Wallet.providers.coinbase.recurringBTC
  if (!f) return {}
  return f.bind(this)(info, amount, currency)
}

Wallet.prototype.transferP = function (info) {
  var f = Wallet.providers[info.provider].transferP

  return ((!!f) && (f.bind(this)(info)))
}

Wallet.prototype.transfer = async function (info, satoshis) {
  var f = Wallet.providers[info.provider].transfer

  if (!f) throw new Error('provider ' + info.provider + ' transfer not supported')
  return f.bind(this)(info, satoshis)
}

Wallet.prototype.compareTx = function (unsignedHex, signedHex) {
  var i
  var signedTx = bitcoinjs.Transaction.fromHex(signedHex)
  var unsignedTx = bitcoinjs.Transaction.fromHex(unsignedHex)

  if ((unsignedTx.version !== signedTx.version) || (unsignedTx.locktime !== signedTx.locktime)) return false

  if (unsignedTx.ins.length !== signedTx.ins.length) return false
  for (i = 0; i < unsignedTx.ins.length; i++) {
    if (!underscore.isEqual(underscore.omit(unsignedTx.ins[i], 'script'), underscore.omit(signedTx.ins[i], 'script'))) {
      return false
    }
  }

  return underscore.isEqual(unsignedTx.outs, signedTx.outs)
}

Wallet.prototype.submitTx = async function (info, signedTx) {
  var f = Wallet.providers[info.provider].submitTx

  if (!f) throw new Error('provider ' + info.provider + ' submitTx not supported')
  return f.bind(this)(info, signedTx)
}

Wallet.prototype.unsignedTx = async function (info, amount, currency, balance) {
  var f = Wallet.providers[info.provider].unsignedTx

  if (!f) throw new Error('provider ' + info.provider + ' unsignedTx not supported')
  return f.bind(this)(info, amount, currency, balance)
}

Wallet.prototype.rates = {}

var schema1 = Joi.object({}).pattern(/timestamp|[A-Z][A-Z][A-Z]/,
                                     Joi.alternatives().try(Joi.date(),
                                                            Joi.object().keys({ last: Joi.number().positive() }).unknown(true)))
                 .required()
var schema2 = Joi.object()
              .keys({
                bpi: Joi.object().pattern(/[A-Z][A-Z][A-Z]/,
                                          Joi.object().keys({
                                            code: Joi.string().regex(/[A-Z][A-Z][A-Z]/).required(),
                                            rate_float: Joi.number().positive().required()
                                          }).unknown(true)).required()

              }).unknown(true)

var maintenance = async function (config, runtime) {
  var rates, result1, result2
  var timestamp = Math.round(underscore.now() / 1000)
  var prefix = timestamp + '.' + config.bitcoin_average.publicKey
  var suffix = crypto.createHmac('sha256', config.bitcoin_average.secretKey).update(prefix).digest('hex')
  var signature = prefix + '.' + suffix

  var fetch = async function (url, props, schema) {
    var result, validity

    result = await braveHapi.wreck.get(url, props)
    if (Buffer.isBuffer(result)) result = result.toString()
// courtesy of https://stackoverflow.com/questions/822452/strip-html-from-text-javascript#822464
    if (result.indexOf('<html>') !== -1) throw new Error(result.replace(/<(?:.|\n)*?>/gm, ''))

    result = JSON.parse(result)
    validity = Joi.validate(result, schema)
    if (validity.error) throw new Error(validity.error)

    return result
  }

  try {
    result1 = await fetch('https://apiv2.bitcoinaverage.com/indices/global/ticker/all?crypto=BTC',
                          { headers: { 'x-signature': signature } }, schema1)
  } catch (ex) {
    return runtime.notify(debug, { text: 'maintenance error(1): ' + ex.toString() })
  }

  try {
    result2 = await fetch('http://api.coindesk.com/v1/bpi/currentprice.json', {}, schema2)
  } catch (ex) {
    return runtime.notify(debug, { text: 'maintenance error(2): ' + ex.toString() })
  }

  rates = {}
  underscore.keys(result1).forEach(currency => {
    var rate = result1[currency]

    if ((currency.indexOf('BTC') !== 0) || (typeof rate !== 'object') || (!rate.last)) return

    rates[currency.substr(3)] = rate.last
  })
  if ((!rates.USD) || (!rates.EUR) || (!rates.GBP)) {
    return runtime.notify(debug, { text: 'maintenance error(3): currencies available ' + underscore.keys(rates) })
  }
  if ((!result2.bpi.USD) || (!result2.bpi.EUR) || (!result2.bpi.GBP)) {
    return runtime.notify(debug, { text: 'maintenance error(4): currencies available ' + underscore.keys(result2.bpi) })
  }

  var compar = (currency) => {
    var ratio = rates[currency] / result2.bpi[currency].rate_float

    if ((ratio < 0.985) || (ratio > 1.015)) {
      throw new Error('maintenance error(5): ' + currency + ' ' + rates[currency] + ' vs. ' + result2.bpi[currency].rate_float)
    }
  }

  try {
    compar('USD')
    compar('EUR')
    compar('GBP')
  } catch (ex) {
    return runtime.notify(debug, { text: ex.toString() })
  }

  Wallet.prototype.rates = rates
  debug('BTC key rates', underscore.pick(rates, [ 'USD', 'EUR', 'GBP' ]))
}

module.exports = Wallet

Wallet.providers = {}

Wallet.providers.bitgo = {
  balances: async function (info) {
    var wallet = await this.bitgo.wallets().get({ type: 'bitcoin', id: info.address })

    return {
      balance: wallet.balance(),
      spendable: wallet.spendableBalance(),
      confirmed: wallet.confirmedBalance(),
      unconfirmed: wallet.unconfirmedReceives()
    }
  },

  submitTx: async function (info, signedTx) {
    var details, i, result
    var wallet = await this.bitgo.wallets().get({ type: 'bitcoin', id: info.address })

    result = await wallet.sendTransaction({ tx: signedTx })

// courtesy of https://stackoverflow.com/questions/33289726/combination-of-async-function-await-settimeout#33292942
    var timeout = function (msec) { return new Promise((resolve) => { setTimeout(resolve, msec) }) }

    for (i = 0; i < 5; i++) {
      try {
        details = await this.bitgo.blockchain().getTransaction({ id: result.hash })
        break
      } catch (ex) {
        debug('getTransaction', ex)
        await timeout(1 * 1000)
        debug('getTransaction', { retry: i + 1, max: 5 })
      }
    }
    underscore.extend(result, { fee: details.fee })

    for (i = details.outputs.length - 1; i >= 0; i--) {
      if (details.outputs[i].account !== this.config.bitgo.settlementAddress) continue

      underscore.extend(result, { address: details.outputs[i].account, satoshis: details.outputs[i].value })
      break
    }

    return result
  },

  transferP: function (info) {
    return ((!!this.config.bitgo.fundingAddress) && (!!this.config.bitgo.fundingPassphrase))
  },

  transfer: async function (info, satoshis) {
    var result, wallet

    if (!this.config.bitgo.fundingAddress) throw new Error('no funding address configured')
    if (!this.config.bitgo.fundingPassphrase) throw new Error('no funding passphrase configured')

    wallet = await this.bitgo.wallets().get({ type: 'bitcoin', id: this.config.bitgo.fundingAddress })
    try {
      result = await wallet.sendCoins({ address: info.address,
        amount: satoshis,
        walletPassphrase: this.config.bitgo.fundingPassphrase
      })

      return underscore.pick(result, [ 'hash', 'fee' ])
    } catch (ex) {
      throw new Error(ex.error)
    }
  },

  unsignedTx: async function (info, amount, currency, balance) {
    var desired, i, minimum, transaction, wallet
    var estimate = await this.bitgo.estimateFee({ numBlocks: 6 })
    var fee = estimate.feePerKb
    var rate = Wallet.prototype.rates[currency.toUpperCase()]
    var recipients = {}

    if (!rate) throw new Error('no such currency: ' + currency)

    desired = (amount / rate) * 1e8
    minimum = Math.floor(desired * 0.90)
    desired = Math.round(desired)
    debug('unsignedTx', { balance: balance, desired: desired, minimum: minimum })
    if (minimum > balance) return

    if (desired > balance) desired = balance

    wallet = await this.bitgo.wallets().get({ type: 'bitcoin', id: info.address })
    for (i = 0; i < 2; i++) {
      recipients[this.config.bitgo.settlementAddress] = desired - fee

      try {
        transaction = await wallet.createTransaction({ recipients: recipients, feeRate: estimate.feePerKb })
        debug('unsignedTx', { satoshis: desired, estimate: fee, actual: transaction.fee })
      } catch (ex) {
        debug('createTransaction', ex)
        this.runtime.newrelic.noticeError(ex, { recipients: recipients, feeRate: estimate.feePerKb })
        return
      }
      if (fee <= transaction.fee) break

      fee = transaction.fee
    }

    return underscore.extend(underscore.pick(transaction, [ 'transactionHex', 'unspents', 'fee' ]),
                             { xpub: transaction.walletKeychains[0].xpub })
  }
}

Wallet.providers.coinbase = {
  purchaseBTC: function (info, amount, currency) {
    // TBD: for the moment...
    if (currency !== 'USD') throw new Error('currency ' + currency + ' payment not supported')

    return ({
      buyURL: `https://buy.coinbase.com?crypto_currency=BTC` +
                `&code=${this.config.coinbase.widgetCode}` +
                `&amount=${amount}` +
                `&address=${info.address}`
    })
  },

  recurringBTC: function (info, amount, currency) {
    // TBD: for the moment...
    if (currency !== 'USD') throw new Error('currency ' + currency + ' payment not supported')

    return ({recurringURL: `https://www.coinbase.com/recurring_payments/new?type=send&repeat=monthly` +
                `&amount=${amount}` +
                `&currency=${currency}` +
                `&to=${info.address}`
    })
  }
}
<|endoftext|>


<commit_before>/*global describe, it*/
var expect = require('unexpected');

describe('inter', function () {
    var inter = require('../build/en_us');

    describe('#renderDate()', function () {
        var d = new Date(2013, 11, 6, 10, 54, 10);

        it('should format a date according to the mediumDateTime format', function () {
            expect(inter.renderDate(d, 'mediumDateTime'), 'to equal', 'Dec 6, 2013, 10:54:10 am');
        });

        it('should format a date according to the MMMM format (should adapt MMM=LLL to LLLL)', function () {
            expect(inter.renderDate(d, 'MMMM'), 'to equal', 'December');
        });

        it('should use the fullDateTime format if the formatId parameter is omitted', function () {
            var timeZoneOffsetStr = d.toTimeString().match(/GMT([-+]\d\d\d\d)/)[1].replace(/(\d\d)$/, ':$1');
            expect(inter.renderDate(d), 'to equal', 'Friday, December 6, 2013 at 10:54:10 am ' + timeZoneOffsetStr);
        });

        it('should accept a number (epoch milliseconds) instead of a Date instance', function () {
            expect(inter.renderDate(5000, 'shortDate'), 'to equal', '1/1/1970');
        });

        it('should accept a parsable date string instead of a Date instance', function () {
            expect(inter.renderDate('Nov 18 2014 18:00', 'shortDate'), 'to equal', '11/18/2014');
        });

        it('should use the regular month format when using the L format character', function () {
            expect(require('../build/fi').renderDateFormat(new Date('Nov 18 2014 18:00'), 'MMMM'), 'to equal', 'marraskuuta');
        });

        it('should use the standalone month format when using the L format character', function () {
            expect(require('../build/fi').renderDateFormat(new Date('Nov 18 2014 18:00'), 'LLLL'), 'to equal', 'marraskuu');
        });
    });

    describe('#renderList()', function () {
        it('should should render a list according to the unitNarrow pattern', function () {
            expect(inter.renderList(['foo', 'bar', 'quux'], 'unitNarrow'), 'to equal', 'foo bar quux')
        });

        it('should use the default format when the type parameter is omitted', function () {
            expect(inter.renderList(['foo', 'bar', 'quux']), 'to equal', 'foo, bar, and quux')
        });
    });

    describe('#renderNumber', function () {
        it('should render a number in the Indian English locale (#11)', function () {
            expect(require('../build/en_in').renderNumber(10000000.23), 'to equal', '10,000,000.23');
        });

        it('should render a number in the Danish locale (#11)', function () {
            expect(require('../build/da').renderNumber(10000000.23), 'to equal', '10.000.000,23');
        });
    });

    describe('#renderPercentage', function () {
        it('should accept the number of decimals as the second parameter', function () {
            expect(require('../build/da').renderPercentage(12.345678, 2), 'to equal', '1.234,57\xa0%');
        });

        it('should accept the number system as the third parameter', function () {
            expect(require('../build/da').renderPercentage(12.345678, 2, 'mlym'), 'to equal', '൧.൨൩൪,൫൭\xa0%');
        });
    });

    describe('#renderUnit', function () {
        it('should render a singular number of units', function () {
            expect(inter.renderUnit(1, 'durationWeek'), 'to equal', '1 week');
        });

        it('should render a plural number of units', function () {
            expect(inter.renderUnit(2, 'durationWeek'), 'to equal', '2 weeks');
        });

        it('should render in the narrow format', function () {
            expect(inter.renderUnit(1, 'durationWeek', 'narrow'), 'to equal', '1w');
        });
    });

    describe('#renderCurrencyShort', function () {
        it('should render a float with a currency', function () {
            expect(inter.renderCurrencyShort(134.40, 'USD'), 'to equal', '$134.40');
        });

        it('should render a number in the Dutch locale', function () {
            expect(require('../build/nl').renderCurrencyShort(134.40, 'EUR'), 'to equal', '€ 134,40');
        });
    });

    describe('#renderCurrencyLong', function () {
        it('should render a float with a currency', function () {
            expect(inter.renderCurrencyLong(134.40, 'USD'), 'to equal', 'USD 134.40');
        });

        it('should render a number in the Dutch locale', function () {
            expect(require('../build/nl').renderCurrencyLong(134.40, 'EUR'), 'to equal', 'EUR 134,40');
        });
    });

    describe('#makeUnitRenderer', function () {
        it('should render in the narrow format', function () {
            expect(inter.makeUnitRenderer('durationWeek', 'narrow')(1), 'to equal', '1w');
        });
    });
});
<commit_msg>Add tests for numDecimals support in renderCurrencyLong/Short<commit_after>/*global describe, it*/
var expect = require('unexpected');

describe('inter', function () {
    var inter = require('../build/en_us');

    describe('#renderDate()', function () {
        var d = new Date(2013, 11, 6, 10, 54, 10);

        it('should format a date according to the mediumDateTime format', function () {
            expect(inter.renderDate(d, 'mediumDateTime'), 'to equal', 'Dec 6, 2013, 10:54:10 am');
        });

        it('should format a date according to the MMMM format (should adapt MMM=LLL to LLLL)', function () {
            expect(inter.renderDate(d, 'MMMM'), 'to equal', 'December');
        });

        it('should use the fullDateTime format if the formatId parameter is omitted', function () {
            var timeZoneOffsetStr = d.toTimeString().match(/GMT([-+]\d\d\d\d)/)[1].replace(/(\d\d)$/, ':$1');
            expect(inter.renderDate(d), 'to equal', 'Friday, December 6, 2013 at 10:54:10 am ' + timeZoneOffsetStr);
        });

        it('should accept a number (epoch milliseconds) instead of a Date instance', function () {
            expect(inter.renderDate(5000, 'shortDate'), 'to equal', '1/1/1970');
        });

        it('should accept a parsable date string instead of a Date instance', function () {
            expect(inter.renderDate('Nov 18 2014 18:00', 'shortDate'), 'to equal', '11/18/2014');
        });

        it('should use the regular month format when using the L format character', function () {
            expect(require('../build/fi').renderDateFormat(new Date('Nov 18 2014 18:00'), 'MMMM'), 'to equal', 'marraskuuta');
        });

        it('should use the standalone month format when using the L format character', function () {
            expect(require('../build/fi').renderDateFormat(new Date('Nov 18 2014 18:00'), 'LLLL'), 'to equal', 'marraskuu');
        });
    });

    describe('#renderList()', function () {
        it('should should render a list according to the unitNarrow pattern', function () {
            expect(inter.renderList(['foo', 'bar', 'quux'], 'unitNarrow'), 'to equal', 'foo bar quux')
        });

        it('should use the default format when the type parameter is omitted', function () {
            expect(inter.renderList(['foo', 'bar', 'quux']), 'to equal', 'foo, bar, and quux')
        });
    });

    describe('#renderNumber', function () {
        it('should render a number in the Indian English locale (#11)', function () {
            expect(require('../build/en_in').renderNumber(10000000.23), 'to equal', '10,000,000.23');
        });

        it('should render a number in the Danish locale (#11)', function () {
            expect(require('../build/da').renderNumber(10000000.23), 'to equal', '10.000.000,23');
        });
    });

    describe('#renderPercentage', function () {
        it('should accept the number of decimals as the second parameter', function () {
            expect(require('../build/da').renderPercentage(12.345678, 2), 'to equal', '1.234,57\xa0%');
        });

        it('should accept the number system as the third parameter', function () {
            expect(require('../build/da').renderPercentage(12.345678, 2, 'mlym'), 'to equal', '൧.൨൩൪,൫൭\xa0%');
        });
    });

    describe('#renderUnit', function () {
        it('should render a singular number of units', function () {
            expect(inter.renderUnit(1, 'durationWeek'), 'to equal', '1 week');
        });

        it('should render a plural number of units', function () {
            expect(inter.renderUnit(2, 'durationWeek'), 'to equal', '2 weeks');
        });

        it('should render in the narrow format', function () {
            expect(inter.renderUnit(1, 'durationWeek', 'narrow'), 'to equal', '1w');
        });
    });

    describe('#renderCurrencyShort', function () {
        it('should render a float with a currency', function () {
            expect(inter.renderCurrencyShort(134.40, 'USD'), 'to equal', '$134.40');
        });

        it('should render a float with a currency, with a different amount of decimals', function () {
            expect(inter.renderCurrencyShort(134.40, 'USD', 'latn', 1), 'to equal', '$134.4');
        });

        it('should render a number in the Dutch locale', function () {
            expect(require('../build/nl').renderCurrencyShort(134.40, 'EUR'), 'to equal', '€ 134,40');
        });
    });

    describe('#renderCurrencyLong', function () {
        it('should render a float with a currency', function () {
            expect(inter.renderCurrencyLong(134.40, 'USD'), 'to equal', 'USD 134.40');
        });

        it('should render a float with a currency, with a different amount of decimals', function () {
            expect(inter.renderCurrencyLong(134.40, 'USD', 'latn', 1), 'to equal', 'USD 134.4');
        });

        it('should render a number in the Dutch locale', function () {
            expect(require('../build/nl').renderCurrencyLong(134.40, 'EUR'), 'to equal', 'EUR 134,40');
        });
    });

    describe('#makeUnitRenderer', function () {
        it('should render in the narrow format', function () {
            expect(inter.makeUnitRenderer('durationWeek', 'narrow')(1), 'to equal', '1w');
        });
    });
});
<|endoftext|>


<commit_before>'use strict';

var monkey = require('stdout-monkey');

describe('gulp-runtime', function(){

  var fs = require('fs');
  var stdout = hookStdout();
  var runtime = require('../.');

  var testFiles = fs.readdirSync('./test/suite');

  testFiles.forEach(function(testFile){
    describe(testFile.replace('.','/'), function(){
      require('./suite/'+testFile)(runtime, stdout);
    });

  });

});
<commit_msg>have to fix those test<commit_after>'use strict';

var monkey = require('stdout-monkey')();

describe('gulp-runtime', function(){

  var fs = require('fs');
  var runtime = require('../.');

  var testFiles = fs.readdirSync('./test/suite');

  testFiles.forEach(function(testFile){
    describe(testFile.replace('.','/'), function(){
      require('./suite/'+testFile)(runtime, monkey);
    });

  });

});
<|endoftext|>