<commit_before>// jquery.pjax.js
// copyright chris wanstrath
// https://github.com/defunkt/pjax

// When called on a link, fetches the href with ajax into the
// container specified as the first parameter or with the data-pjax
// attribute on the link itself.
//
// Tries to make sure the back button and ctrl+click work the way
// you'd expect.
//
// Accepts a jQuery ajax options object that may include these
// pjax specific options:
//
// container - Stick the response body in this String selector
//             $(container).html(xhr.responseBody)
//      push - Whether to pushState the URL. Defaults to true (of course).
//   replace - Want to use replaceState instead? That's cool.
//
// For convenience the first parameter can be either the container or
// the options object.
//
// Returns the jQuery object
jQuery.fn.pjax = function( container, options ) {
  var $ = jQuery

  if ( options )
    options.container = container
  else
    options = $.isPlainObject(container) ? container : {container:container}

  return this.live('click', function(event){
    // Middle click, cmd click, and ctrl click should open
    // links in a new tab as normal.
    if ( event.which == 2 || event.metaKey )
      return true

    var defaults = {
      url: this.href,
      container: $(this).attr('data-pjax')
    }

    $.pjax($.extend({}, defaults, options))

    event.preventDefault()
  })
}

// Loads a URL with ajax, puts the response body inside a container,
// then pushState()'s the loaded URL. Also ensures the back button
// works the way you'd expect.
//
// Works just like $.ajax in that it accepts a jQuery ajax
// settings object (with keys like url, type, data, etc).
//
// Accepts these extra keys:
//
// container - Where to stick the response body.
//             $(container).html(xhr.responseBody)
//      push - Whether to pushState the URL. Defaults to true (of course).
//   replace - Want to use replaceState instead? That's cool.
//
// Use it just like $.ajax:
//
//   var xhr = $.pjax({ url: this.href, container: '#main' })
//   console.log( xhr.readyState )
//
// Returns whatever $.ajax returns.
jQuery.pjax = function( options ) {
  // Helper
  var $ = jQuery, $container = $(options.container)

  var defaults = {
    timeout: 650,
    push: true,
    replace: false,
    cache: false,
    type: 'GET',
    dataType: 'html',
    beforeSend: function(xhr){
      xhr.setRequestHeader('X-PJAX', 'true')
    },
    error: function(){ window.location = options.url },
    success: function( data ) {
      // If we got no data or an entire web page, go directly
      // to the page and let normal error handling happen.
      if ( !$.trim(data) || /<html/i.test(data) )
        return window.location = options.url

      // Make it happen.
      $container.html(data)

      if ( !$.pjax.active ) {
        $.pjax.active = true
        window.history.replaceState({ pjax: true },
                                    document.title,
                                    location.pathname)
      }

      // If there's a <title> tag in the response, use it as
      // the page's title.
      var title = $.trim( $container.find('title').remove().text() )
      if ( title ) document.title = title

      var state = { pjax: options.container }

      if ( options.data )
        state.url = options.url + '?' + $.param(options.data)

      if ( options.replace ) {
        window.history.replaceState(state, document.title, options.url)
      } else if ( options.push ) {
        window.history.pushState(state, document.title, options.url)
      }

      if ( (options.replace || options.push) && window._gaq )
        _gaq.push(['_trackPageview'])

      // Invoke their success handler if they gave us one.
      success.apply(this, arguments)
    }
  }

  // We don't want to let anyone override our success handler.
  var success = options.success || $.noop
  delete options.success

  options = $.extend(true, {}, defaults, options)
  var xhr = $.ajax(options)

  $(document).trigger('pjax', xhr, options)
  return xhr
}

// Has the pjaxing begun? We must know.
jQuery.pjax.active = false

// onpopstate fires at some point after the first page load, by design.
// pjax only cares about the back button, so we ignore the first onpopstate.
//
// Of course, older webkit doesn't fire the onopopstate event on load.
// So we have to special case. The joys.
jQuery.pjax.firstLoad = true

if ( jQuery.browser.webkit && parseInt(jQuery.browser.version) < 534 )
  jQuery.pjax.firstLoad = false

// Bind our popstate handler which takes care of the back and
// forward buttons, but only once we've called pjax().
//
// You probably shouldn't use pjax on pages with other pushState
// stuff yet.
jQuery(window).bind('popstate', function(event){
  // Do nothing if we're not pjaxing
  if ( jQuery.pjax == jQuery.noop )
    return

  if ( jQuery.pjax.firstLoad )
    return jQuery.pjax.firstLoad = false

  var state = event.state

  if ( jQuery.pjax.active || state && state.pjax ) {
    var container = jQuery(state.pjax+'')
    if ( container.length )
      jQuery.pjax({
        url: state.url || location.href,
        container: container,
        push: false
      })
    else
      window.location = location.href
  }
})

// Add the state property to jQuery's event object so we can use it in
// $(window).bind('popstate')
jQuery.event.props.push('state')

// Fall back to normalcy for older browsers.
if ( !window.history || !window.history.pushState ) {
  jQuery.pjax = jQuery.noop
  jQuery.fn.pjax = function() { return this }
};
<commit_msg>clearer description of argument<commit_after>// jquery.pjax.js
// copyright chris wanstrath
// https://github.com/defunkt/pjax

// When called on a link, fetches the href with ajax into the
// container specified as the first parameter or with the data-pjax
// attribute on the link itself.
//
// Tries to make sure the back button and ctrl+click work the way
// you'd expect.
//
// Accepts a jQuery ajax options object that may include these
// pjax specific options:
//
// container - Where to stick the response body. Usually a String selector.
//             $(container).html(xhr.responseBody)
//      push - Whether to pushState the URL. Defaults to true (of course).
//   replace - Want to use replaceState instead? That's cool.
//
// For convenience the first parameter can be either the container or
// the options object.
//
// Returns the jQuery object
jQuery.fn.pjax = function( container, options ) {
  var $ = jQuery

  if ( options )
    options.container = container
  else
    options = $.isPlainObject(container) ? container : {container:container}

  return this.live('click', function(event){
    // Middle click, cmd click, and ctrl click should open
    // links in a new tab as normal.
    if ( event.which == 2 || event.metaKey )
      return true

    var defaults = {
      url: this.href,
      container: $(this).attr('data-pjax')
    }

    $.pjax($.extend({}, defaults, options))

    event.preventDefault()
  })
}

// Loads a URL with ajax, puts the response body inside a container,
// then pushState()'s the loaded URL. Also ensures the back button
// works the way you'd expect.
//
// Works just like $.ajax in that it accepts a jQuery ajax
// settings object (with keys like url, type, data, etc).
//
// Accepts these extra keys:
//
// container - Where to stick the response body.
//             $(container).html(xhr.responseBody)
//      push - Whether to pushState the URL. Defaults to true (of course).
//   replace - Want to use replaceState instead? That's cool.
//
// Use it just like $.ajax:
//
//   var xhr = $.pjax({ url: this.href, container: '#main' })
//   console.log( xhr.readyState )
//
// Returns whatever $.ajax returns.
jQuery.pjax = function( options ) {
  // Helper
  var $ = jQuery, $container = $(options.container)

  var defaults = {
    timeout: 650,
    push: true,
    replace: false,
    cache: false,
    type: 'GET',
    dataType: 'html',
    beforeSend: function(xhr){
      xhr.setRequestHeader('X-PJAX', 'true')
    },
    error: function(){ window.location = options.url },
    success: function( data ) {
      // If we got no data or an entire web page, go directly
      // to the page and let normal error handling happen.
      if ( !$.trim(data) || /<html/i.test(data) )
        return window.location = options.url

      // Make it happen.
      $container.html(data)

      if ( !$.pjax.active ) {
        $.pjax.active = true
        window.history.replaceState({ pjax: true },
                                    document.title,
                                    location.pathname)
      }

      // If there's a <title> tag in the response, use it as
      // the page's title.
      var title = $.trim( $container.find('title').remove().text() )
      if ( title ) document.title = title

      var state = { pjax: options.container }

      if ( options.data )
        state.url = options.url + '?' + $.param(options.data)

      if ( options.replace ) {
        window.history.replaceState(state, document.title, options.url)
      } else if ( options.push ) {
        window.history.pushState(state, document.title, options.url)
      }

      if ( (options.replace || options.push) && window._gaq )
        _gaq.push(['_trackPageview'])

      // Invoke their success handler if they gave us one.
      success.apply(this, arguments)
    }
  }

  // We don't want to let anyone override our success handler.
  var success = options.success || $.noop
  delete options.success

  options = $.extend(true, {}, defaults, options)
  var xhr = $.ajax(options)

  $(document).trigger('pjax', xhr, options)
  return xhr
}

// Has the pjaxing begun? We must know.
jQuery.pjax.active = false

// onpopstate fires at some point after the first page load, by design.
// pjax only cares about the back button, so we ignore the first onpopstate.
//
// Of course, older webkit doesn't fire the onopopstate event on load.
// So we have to special case. The joys.
jQuery.pjax.firstLoad = true

if ( jQuery.browser.webkit && parseInt(jQuery.browser.version) < 534 )
  jQuery.pjax.firstLoad = false

// Bind our popstate handler which takes care of the back and
// forward buttons, but only once we've called pjax().
//
// You probably shouldn't use pjax on pages with other pushState
// stuff yet.
jQuery(window).bind('popstate', function(event){
  // Do nothing if we're not pjaxing
  if ( jQuery.pjax == jQuery.noop )
    return

  if ( jQuery.pjax.firstLoad )
    return jQuery.pjax.firstLoad = false

  var state = event.state

  if ( jQuery.pjax.active || state && state.pjax ) {
    var container = jQuery(state.pjax+'')
    if ( container.length )
      jQuery.pjax({
        url: state.url || location.href,
        container: container,
        push: false
      })
    else
      window.location = location.href
  }
})

// Add the state property to jQuery's event object so we can use it in
// $(window).bind('popstate')
jQuery.event.props.push('state')

// Fall back to normalcy for older browsers.
if ( !window.history || !window.history.pushState ) {
  jQuery.pjax = jQuery.noop
  jQuery.fn.pjax = function() { return this }
};
<|endoftext|>


<commit_before>const Bulma = {
    /**
     * Current BulmaJS version.
     * @type {String}
     */
    VERSION: '0.7.0',

    /**
     * An index of the registered plugins
     * @type {Object}
     */
    plugins: {},

    /**
     * Helper method to create a new plugin.
     * @param  {String} key The plugin's key
     * @param  {Object} options The options to be passed to the plugin
     * @return {Object} The newly created plugin instance
     */
    create(key, options) {
        if(!key || !Bulma.plugins.hasOwnProperty(key)) {
            throw new Error('[BulmaJS] A plugin with the key \''+key+'\' has not been registered.');
        }

        return Bulma.plugins[key].create(options);
    },

    /**
     * Register a new plugin
     * @param  {String} key The key to register the plugin under
     * @param  {Object} plugin The plugin's main constructor
     * @return {undefined}
     */
    registerPlugin(key, plugin) {
        if(!key) {
            throw new Error('[BulmaJS] Key attribute is required.');
        }

        this.plugins[key] = plugin;
    },

    /**
     * Parse the HTML DOM searching for data-bulma attributes. We will then pass
     * each element to the appropriate plugin to handle the required processing.
     *
     * @return {undefined}
     */
    traverseDOM() {
        let elements = document.querySelectorAll(this.getPluginClasses());

        elements.forEach((element) => {
            let plugin = this.findCompatiblePlugin(element);

            if(plugin.hasOwnProperty('handleDomParsing')) {
                plugin.handleDomParsing(element);
            }
        });
    },

    /**
     * Return a string of classes to search the DOM for
     * @returns {string}
     */
    getPluginClasses() {
        var classes = [];

        for(var key in this.plugins) {
            classes.push('.' + this.plugins[key].getRootClass());
        }

        return classes.join(',');
    },

    /**
     * Search our plugins and find one that matches the element
     * @returns {Object}
     */
    findCompatiblePlugin(element) {
        for(var key in this.plugins) {
            if(element.classList.contains(this.plugins[key].getRootClass())) {
                return this.plugins[key];
            }
        }
    },

    /**
     * Create an element and assign classes
     * @param {string} name The name of the element to create
     * @param {array} classes An array of classes to add to the element
     * @return {HTMLElement} The newly created element
     */
    createElement(name, classes) {
        if(!classes) {
            classes = [];
        }

        if(typeof classes === 'string') {
            classes = [classes];
        }

        let elem = document.createElement(name);

        classes.forEach((className) => {
            elem.classList.add(className);
        });

        return elem;
    }
};

document.addEventListener('DOMContentLoaded', () => {
    Bulma.traverseDOM();
});

export default Bulma;
<commit_msg>Add a check to prevent empty strings beeing passed to our class search string.<commit_after>const Bulma = {
    /**
     * Current BulmaJS version.
     * @type {String}
     */
    VERSION: '0.7.0',

    /**
     * An index of the registered plugins
     * @type {Object}
     */
    plugins: {},

    /**
     * Helper method to create a new plugin.
     * @param  {String} key The plugin's key
     * @param  {Object} options The options to be passed to the plugin
     * @return {Object} The newly created plugin instance
     */
    create(key, options) {
        if(!key || !Bulma.plugins.hasOwnProperty(key)) {
            throw new Error('[BulmaJS] A plugin with the key \''+key+'\' has not been registered.');
        }

        return Bulma.plugins[key].create(options);
    },

    /**
     * Register a new plugin
     * @param  {String} key The key to register the plugin under
     * @param  {Object} plugin The plugin's main constructor
     * @return {undefined}
     */
    registerPlugin(key, plugin) {
        if(!key) {
            throw new Error('[BulmaJS] Key attribute is required.');
        }

        this.plugins[key] = plugin;
    },

    /**
     * Parse the HTML DOM searching for data-bulma attributes. We will then pass
     * each element to the appropriate plugin to handle the required processing.
     *
     * @return {undefined}
     */
    traverseDOM() {
        let elements = document.querySelectorAll(this.getPluginClasses());

        elements.forEach((element) => {
            let plugin = this.findCompatiblePlugin(element);

            if(plugin.hasOwnProperty('handleDomParsing')) {
                plugin.handleDomParsing(element);
            }
        });
    },

    /**
     * Return a string of classes to search the DOM for
     * @returns {string}
     */
    getPluginClasses() {
        var classes = [];

        for(var key in this.plugins) {
            if(!this.plugins[key].getRootClass()) {
                continue;
            }

            classes.push('.' + this.plugins[key].getRootClass());
        }

        return classes.join(',');
    },

    /**
     * Search our plugins and find one that matches the element
     * @returns {Object}
     */
    findCompatiblePlugin(element) {
        for(var key in this.plugins) {
            if(element.classList.contains(this.plugins[key].getRootClass())) {
                return this.plugins[key];
            }
        }
    },

    /**
     * Create an element and assign classes
     * @param {string} name The name of the element to create
     * @param {array} classes An array of classes to add to the element
     * @return {HTMLElement} The newly created element
     */
    createElement(name, classes) {
        if(!classes) {
            classes = [];
        }

        if(typeof classes === 'string') {
            classes = [classes];
        }

        let elem = document.createElement(name);

        classes.forEach((className) => {
            elem.classList.add(className);
        });

        return elem;
    }
};

document.addEventListener('DOMContentLoaded', () => {
    Bulma.traverseDOM();
});

export default Bulma;
<|endoftext|>


<commit_before>import Pikaday from "pikaday";
import moment from "moment";
import * as angular from "angular";

export default class DatePickerInputDirective {
    static factory($timeout) {
        return {
            restrict: 'E',
            require: '^ngModel',
            template: `

                          <input class="form-control"
                                 type="text"
                                 pikaday="myPicker"
                                 ng-model="stringDateValue"
                                 ng-required="isRequired"
                                 on-select="onPikadaySelect(pikaday)"
                                 pattern="([0-9]{2}\\.){2}[0-9]{4}">

                                <button class="btn btn-default"
                                        type="button"
                                        ng-if="!isDisabled"
                                        ng-click="clear()">

                                 </button>

            `,
            controller: DataPickerInputController,
            scope: {
                ngModel: '='
            },
            link: function (scope, element, attrs, modelCtrl) {
                let format = "DD.MM.YYYY";
                scope.el = angular.element(element.find("input"));

                scope.onPikadaySelect = (pikaday) => {
                    let filteredDate = pikaday.getMoment().format('YYYY-MM-DD');
                    modelCtrl.$setViewValue(new Date(filteredDate + 'T00:00:00+0000'));
                };
                scope.clear = () => {
                    modelCtrl.$setViewValue(undefined);
                };

                function setPikadayValue(modelValue) {
                    if (modelValue && moment(modelValue).isValid()) {
                        scope.stringDateValue = moment(modelValue).format(format);
                        modelCtrl.$setValidity("pikaday", true);
                    } else if (modelValue) {
                        modelCtrl.$setValidity("pikaday", false);
                    } else {
                        scope.stringDateValue = undefined;
                        modelCtrl.$setValidity("pikaday", !scope.ngRequired);
                    }
                }

                scope.$watch('ngModel', setPikadayValue);

                scope.$watch('ngDisabled', () => {
                    scope.isDisabled = !!attrs.disabled;
                });

                scope.$watch('ngRequired', () => {
                    scope.isRequired = !!attrs.required;
                });
            }
        };
    };
}

class DataPickerInputController {

    constructor($scope, $timeout) {
        $scope.myPicker = new Pikaday({
            field: $scope.el
        });

        $timeout(() => {
            $scope.myPicker.setDate(moment($scope.ngModel).toDate());
        }, 0);
    }

}<commit_msg>#36 improved datepicker behavior for invalid values<commit_after>import Pikaday from "pikaday";
import moment from "moment";
import * as angular from "angular";
import * as _ from "lodash";

export default class DatePickerInputDirective {
    static factory($timeout) {
        return {
            restrict: 'E',
            require: '^ngModel',
            template: `

                          <input class="form-control"
                                 type="text"
                                 pikaday="myPicker"
                                 ng-model="stringDateValue"
                                 ng-required="isRequired"
                                 ng-change="checkIfEmpty()"
                                 on-select="onPikadaySelect(pikaday)"
                                 pattern="([0-9]{2}\\.){2}[0-9]{4}">

                                <button class="btn btn-default"
                                        type="button"
                                        ng-if="!isDisabled"
                                        ng-click="clear()">

                                 </button>

            `,
            controller: DataPickerInputController,
            scope: {
                ngModel: '='
            },
            link: function (scope, element, attrs, modelCtrl) {
                let format = "DD.MM.YYYY";
                scope.el = angular.element(element.find("input"));

                scope.onPikadaySelect = (pikaday) => {
                    let filteredDate = pikaday.getMoment().format('YYYY-MM-DD');
                    modelCtrl.$setViewValue(new Date(filteredDate + 'T00:00:00+0000'));
                };
                scope.checkIfEmpty = () => {
                    if(_.isEmpty(scope.stringDateValue)) {
                        modelCtrl.$setViewValue(undefined);
                    }
                };
                scope.clear = () => {
                    scope.myPicker.setDate(undefined);
                    modelCtrl.$setViewValue(undefined);
                };

                function setPikadayValue(modelValue) {
                    if (modelValue && moment(modelValue).isValid()) {
                        scope.stringDateValue = moment(modelValue).format(format);
                        modelCtrl.$setValidity("pikaday", true);
                    } else if (modelValue) {
                        modelCtrl.$setValidity("pikaday", false);
                    } else {
                        scope.stringDateValue = undefined;
                        modelCtrl.$setValidity("pikaday", !scope.ngRequired);
                    }
                }

                scope.$watch('ngModel', setPikadayValue);

                scope.$watch('ngDisabled', () => {
                    scope.isDisabled = !!attrs.disabled;
                });

                scope.$watch('ngRequired', () => {
                    scope.isRequired = !!attrs.required;
                });
            }
        };
    };
}

class DataPickerInputController {

    constructor($scope, $timeout) {
        $scope.myPicker = new Pikaday({
            field: $scope.el
        });

        $timeout(() => {
            if(!_.isEmpty($scope.ngModel)) {
                $scope.myPicker.setDate(moment($scope.ngModel).toDate());
            }
        }, 0);
    }

}<|endoftext|>


<commit_before>const { Book } = require("../");

describe("accounts", function() {
  it("should deal with JavaScript rounding weirdness", async function() {
    const book = new Book("MyBook");
    await book
      .entry("depth test")
      .credit("X:Y:AUD", 1)
      .credit("X:Y:EUR", 1)
      .credit("X:Y:USD", 1)
      .credit("X:Y:INR", 1)
      .credit("X:Y:CHF", 1)
      .debit("Assets", 5)
      .commit();
    let result = await book.balance({ account: "X:Y" });
    result.balance.should.equal(5);
  });
});
<commit_msg>Fix a typo<commit_after>const { Book } = require("../");

describe("accounts", function() {
  it("should allow more than 4 subaccounts of third level", async function() {
    const book = new Book("MyBook");
    await book
      .entry("depth test")
      .credit("X:Y:AUD", 1)
      .credit("X:Y:EUR", 1)
      .credit("X:Y:USD", 1)
      .credit("X:Y:INR", 1)
      .credit("X:Y:CHF", 1)
      .debit("CashAssets", 5)
      .commit();
    let result = await book.balance({ account: "X:Y" });
    result.balance.should.equal(5);
  });
});
<|endoftext|>


<commit_before>/**
 * @module digdug/SeleniumTunnel
 */

var format = require('util').format;
var fs = require('fs');
var pathUtil = require('path');
var Promise = require('dojo/Promise');
var request = require('dojo/request');
var Tunnel = require('./Tunnel');
var util = require('./util');

/*
 * For the three included driver configs, the `platform` and `arch` properties are assumed to use values from the same
 * sets as Node's `os.platform` and `os.arch` properties.
 */

function ChromeConfig(options) {
	util.mixin(this, options);
}

ChromeConfig.prototype = {
	constructor: ChromeConfig,
	version: '2.22',
	baseUrl: 'https://chromedriver.storage.googleapis.com',
	platform: process.platform,
	arch: process.arch,
	get artifact() {
		var platform = this.platform;
		if (platform === 'linux') {
			platform = 'linux' + (this.arch === 'x64' ? '64' : '32');
		}
		else if (platform === 'darwin') {
			var parts = String(this.version).split('.').map(Number);
			var isGreater = [ 2, 22 ].some(function (part, i) {
				return parts[i] > part;
			});
			platform = isGreater ? 'mac64' : 'mac32';
		}
		return format('chromedriver_%s.zip', platform);
	},
	get url() {
		return format('%s/%s/%s', this.baseUrl, this.version, this.artifact);
	},
	get executable() {
		return this.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver';
	},
	get seleniumProperty() {
		return 'webdriver.chrome.driver';
	}
};

function IeConfig(options) {
	util.mixin(this, options);
}

IeConfig.prototype = {
	constructor: IeConfig,
	version: '2.53.0',
	baseUrl: 'https://selenium-release.storage.googleapis.com',
	arch: process.arch,
	get artifact() {
		var architecture = this.arch === 'x64' ? 'x64' : 'Win32';
		return format('IEDriverServer_%s_%s.zip', architecture, this.version);
	},
	get url() {
		var majorMinorVersion = this.version.slice(0, this.version.lastIndexOf('.'));
		return format('%s/%s/%s', this.baseUrl, majorMinorVersion, this.artifact);
	},
	get executable() {
		return 'IEDriverServer.exe';
	},
	get seleniumProperty() {
		return 'webdriver.ie.driver';
	}
};

function FirefoxConfig(options) {
	util.mixin(this, options);
}

FirefoxConfig.prototype = {
	constructor: FirefoxConfig,
	version: '0.11.1',
	baseUrl: 'https://github.com/mozilla/geckodriver/releases/download',
	arch: process.arch,
	platform: process.platform,
	get artifact() {
		var platform = this.platform;
		if (platform === 'linux') {
			platform = 'linux' + (this.arch === 'x64' ? '64' : '32');
		}
		else if (platform === 'win32') {
			platform = 'win' + (this.arch === 'x64' ? '64' : '32');
		}
		else if (platform === 'darwin') {
			platform = 'macos';
		}
		var extension = /^win/.test(platform) ? '.zip' : '.tar.gz';
		return format('geckodriver-v%s-%s%s', this.version, platform, extension);
	},
	get url() {
		return format('%s/v%s/%s', this.baseUrl, this.version, this.artifact);
	},
	get executable() {
		return this.platform === 'win32' ? 'geckodriver.exe' : 'geckodriver';
	},
	get seleniumProperty() {
		return 'webdriver.gecko.driver';
	}
};

var driverNameMap = {
	chrome: ChromeConfig,
	ie: IeConfig,
	firefox: FirefoxConfig
};

/**
 * A Selenium tunnel. This tunnel downloads the {@link http://www.seleniumhq.org/download/ Selenium-standalone server}
 * and any necessary WebDriver executables, and handles starting and stopping Selenium.
 *
 * The primary configuration option is {@linkcode module:digdug/SeleniumTunnel#drivers drivers}, which determines which
 * browsers the Selenium tunnel will support.
 *
 * Note that Java must be installed and in the system path to use this tunnel.
 *
 * @constructor module:digdug/SeleniumTunnel
 * @extends module:digdug/Tunnel
 */
function SeleniumTunnel() {
	Tunnel.apply(this, arguments);

	if (this.drivers === null) {
		this.drivers = [ 'chrome' ];
	}
}

var _super = Tunnel.prototype;

SeleniumTunnel.prototype = util.mixin(Object.create(_super), /** @lends module:digdug/SeleniumTunnel# */ {
	constructor: SeleniumTunnel,

	/**
	 * Additional arguments to send to the Selenium server at startup
	 *
	 * @type {Array.<string>}
	 */
	seleniumArgs: null,

	/**
	 * The desired Selenium drivers to install. Each entry may be a string or an object. Strings must be the names of
	 * existing drivers in SeleniumTunnel. An object with a 'name' property is a configuration object -- the name must
	 * be the name of an existing driver in SeleniumTunnel, and the remaining properties will be used to configure that
	 * driver. An object without a 'name' property is a driver definition. It must contain three properties:
	 *
	 *   - executable - the name of the driver executable
	 *   - url - the URL where the driver can be downloaded from
	 *   - seleniumProperty - the name of the Java property used to tell Selenium where the driver is
	 *
	 * @example
	 * 	[
	 *      'chrome',
	 *      {
	 *          name: 'firefox',
	 *          version: '0.8.0'
	 *      },
	 *      {
	 *          url: 'https://github.com/operasoftware/operachromiumdriver/releases/.../operadriver_mac64.zip',
	 *          executable: 'operadriver',
	 *          seleniumProperty: 'webdriver.opera.driver'
	 *      }
	 * 	]
	 *
	 * @type {Array.<string|Object>}
	 * @default [ 'chrome' ]
	 */
	drivers: null,

	/**
	 * The base address where Selenium artifacts may be found.
	 *
	 * @type {string}
	 * @default https://selenium-release.storage.googleapis.com
	 */
	baseUrl: 'https://selenium-release.storage.googleapis.com',

	/**
	 * The desired version of selenium to install.
	 *
	 * @type {string}
	 * @default 2.53.1
	 */
	version: '2.53.1',

	/**
	 * Timeout in milliseconds for communicating with the Selenium server
	 *
	 * @type {number}
	 * @default 5000
	 */
	seleniumTimeout: 5000,

	get artifact() {
		return 'selenium-server-standalone-' + this.version + '.jar';
	},

	get directory() {
		return pathUtil.join(__dirname, 'selenium-standalone');
	},

	get executable() {
		return 'java';
	},

	get isDownloaded() {
		var directory = this.directory;
		return this._getDriverConfigs().every(function (config) {
			return util.fileExists(pathUtil.join(directory, config.executable));
		}, this) && util.fileExists(pathUtil.join(directory, this.artifact));
	},

	get url() {
		var majorMinorVersion = this.version.slice(0, this.version.lastIndexOf('.'));
		return format('%s/%s/%s', this.baseUrl, majorMinorVersion, this.artifact);
	},

	download: function (forceDownload) {
		if (!forceDownload && this.isDownloaded) {
			return Promise.resolve();
		}

		var self = this;
		return new Promise(function (resolve, reject, progress, setCanceler) {
			setCanceler(function (reason) {
				tasks && tasks.forEach(function (task) {
					task.cancel(reason);
				});
			});

			var configs = [ { url: self.url, executable: self.artifact } ];
			configs = configs.concat(self._getDriverConfigs());

			var tasks = configs.map(function (config) {
				var executable = config.executable;
				if (fs.existsSync(pathUtil.join(self.directory, executable))) {
					return Promise.resolve();
				}

				return self._downloadFile(config.url, self.proxy, {
					executable: executable
				}).then(null, null, progress);
			});

			resolve(Promise.all(tasks));
		});
	},

	sendJobState: function () {
		// This is a noop for Selenium
		return Promise.resolve();
	},

	_getDriverConfigs: function () {
		function getDriverConfig(name, options) {
			var Constructor = driverNameMap[name];
			if (!Constructor) {
				throw new Error('Invalid driver name "' + name + '"');
			}
			return new Constructor(options);
		}

		return this.drivers.map(function (data) {
			if (typeof data === 'string') {
				return getDriverConfig(data);
			}

			if (typeof data === 'object' && data.name) {
				return getDriverConfig(data.name, data);
			}

			// data is a driver definition
			return data;
		});
	},

	_makeArgs: function () {
		var directory = this.directory;
		var driverConfigs = this._getDriverConfigs();
		var args = [];

		driverConfigs.reduce(function (args, config) {
			var file = pathUtil.join(directory, config.executable);
			args.push('-D' + config.seleniumProperty + '=' + file);
			return args;
		}, args);

		if (this.seleniumArgs) {
			args = args.concat(this.seleniumArgs);
		}

		args = args.concat([
			'-jar',
			pathUtil.join(this.directory, this.artifact),
			'-port',
			this.port
		]);

		if (this.verbose) {
			args.push('-debug');
			console.log('starting with arguments: ', args.join(' '));
		}

		return args;
	},

	_postDownloadFile: function (response, options) {
		if (pathUtil.extname(options.executable) === '.jar') {
			return util.writeFile(response.data, pathUtil.join(this.directory, options.executable));
		}
		return util.decompress(response.data, this.directory);
	},

	_start: function () {
		var self = this;
		var childHandle = this._makeChild();
		var child = childHandle.process;
		var dfd = childHandle.deferred;
		var handle = util.on(child.stderr, 'data', function (data) {
			// Selenium recommends that we poll the hub looking for a status response
			// https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/7957
			// We're going against the recommendation here for a few reasons
			// 1. There's no default pid or log to look for errors to provide a specific failure
			// 2. Polling on a failed server start could leave us with an unpleasant wait
			// 3. Just polling a selenium server doesn't guarantee it's the server we started
			// 4. This works pretty well
			if (data.indexOf('Selenium Server is up and running') > -1) {
				dfd.resolve();
			}
			if (self.verbose) {
				process.stderr.write(data);
			}
		});
		var removeHandle = handle.remove.bind(handle);

		dfd.promise.then(removeHandle, removeHandle);

		return childHandle;
	},

	_stop: function () {
		var dfd = new Promise.Deferred();
		var childProcess = this._process;
		var timeout;

		childProcess.once('exit', function (code) {
			dfd.resolve(code);
			clearTimeout(timeout);
		});

		// Nicely ask the Selenium server to shutdown
		request('http://' + this.hostname + ':' + this.port +
			'/selenium-server/driver/?cmd=shutDownSeleniumServer', {
			timeout: this.seleniumTimeout,
			handleAs: 'text'
		});

		// Give Selenium a few seconds, then forcefully tell it to shutdown
		timeout = setTimeout(function () {
			childProcess.kill('SIGTERM');
		}, 5000);

		return dfd.promise;
	}
});

module.exports = SeleniumTunnel;
<commit_msg>Update chromedriver and IEDriver default versions<commit_after>/**
 * @module digdug/SeleniumTunnel
 */

var format = require('util').format;
var fs = require('fs');
var pathUtil = require('path');
var Promise = require('dojo/Promise');
var request = require('dojo/request');
var Tunnel = require('./Tunnel');
var util = require('./util');

/*
 * For the three included driver configs, the `platform` and `arch` properties are assumed to use values from the same
 * sets as Node's `os.platform` and `os.arch` properties.
 */

function ChromeConfig(options) {
	util.mixin(this, options);
}

ChromeConfig.prototype = {
	constructor: ChromeConfig,
	version: '2.25',
	baseUrl: 'https://chromedriver.storage.googleapis.com',
	platform: process.platform,
	arch: process.arch,
	get artifact() {
		var platform = this.platform;
		if (platform === 'linux') {
			platform = 'linux' + (this.arch === 'x64' ? '64' : '32');
		}
		else if (platform === 'darwin') {
			var parts = String(this.version).split('.').map(Number);
			var isGreater = [ 2, 22 ].some(function (part, i) {
				return parts[i] > part;
			});
			platform = isGreater ? 'mac64' : 'mac32';
		}
		return format('chromedriver_%s.zip', platform);
	},
	get url() {
		return format('%s/%s/%s', this.baseUrl, this.version, this.artifact);
	},
	get executable() {
		return this.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver';
	},
	get seleniumProperty() {
		return 'webdriver.chrome.driver';
	}
};

function IeConfig(options) {
	util.mixin(this, options);
}

IeConfig.prototype = {
	constructor: IeConfig,
	version: '2.53.1',
	baseUrl: 'https://selenium-release.storage.googleapis.com',
	arch: process.arch,
	get artifact() {
		var architecture = this.arch === 'x64' ? 'x64' : 'Win32';
		return format('IEDriverServer_%s_%s.zip', architecture, this.version);
	},
	get url() {
		var majorMinorVersion = this.version.slice(0, this.version.lastIndexOf('.'));
		return format('%s/%s/%s', this.baseUrl, majorMinorVersion, this.artifact);
	},
	get executable() {
		return 'IEDriverServer.exe';
	},
	get seleniumProperty() {
		return 'webdriver.ie.driver';
	}
};

function FirefoxConfig(options) {
	util.mixin(this, options);
}

FirefoxConfig.prototype = {
	constructor: FirefoxConfig,
	version: '0.11.1',
	baseUrl: 'https://github.com/mozilla/geckodriver/releases/download',
	arch: process.arch,
	platform: process.platform,
	get artifact() {
		var platform = this.platform;
		if (platform === 'linux') {
			platform = 'linux' + (this.arch === 'x64' ? '64' : '32');
		}
		else if (platform === 'win32') {
			platform = 'win' + (this.arch === 'x64' ? '64' : '32');
		}
		else if (platform === 'darwin') {
			platform = 'macos';
		}
		var extension = /^win/.test(platform) ? '.zip' : '.tar.gz';
		return format('geckodriver-v%s-%s%s', this.version, platform, extension);
	},
	get url() {
		return format('%s/v%s/%s', this.baseUrl, this.version, this.artifact);
	},
	get executable() {
		return this.platform === 'win32' ? 'geckodriver.exe' : 'geckodriver';
	},
	get seleniumProperty() {
		return 'webdriver.gecko.driver';
	}
};

var driverNameMap = {
	chrome: ChromeConfig,
	ie: IeConfig,
	firefox: FirefoxConfig
};

/**
 * A Selenium tunnel. This tunnel downloads the {@link http://www.seleniumhq.org/download/ Selenium-standalone server}
 * and any necessary WebDriver executables, and handles starting and stopping Selenium.
 *
 * The primary configuration option is {@linkcode module:digdug/SeleniumTunnel#drivers drivers}, which determines which
 * browsers the Selenium tunnel will support.
 *
 * Note that Java must be installed and in the system path to use this tunnel.
 *
 * @constructor module:digdug/SeleniumTunnel
 * @extends module:digdug/Tunnel
 */
function SeleniumTunnel() {
	Tunnel.apply(this, arguments);

	if (this.drivers === null) {
		this.drivers = [ 'chrome' ];
	}
}

var _super = Tunnel.prototype;

SeleniumTunnel.prototype = util.mixin(Object.create(_super), /** @lends module:digdug/SeleniumTunnel# */ {
	constructor: SeleniumTunnel,

	/**
	 * Additional arguments to send to the Selenium server at startup
	 *
	 * @type {Array.<string>}
	 */
	seleniumArgs: null,

	/**
	 * The desired Selenium drivers to install. Each entry may be a string or an object. Strings must be the names of
	 * existing drivers in SeleniumTunnel. An object with a 'name' property is a configuration object -- the name must
	 * be the name of an existing driver in SeleniumTunnel, and the remaining properties will be used to configure that
	 * driver. An object without a 'name' property is a driver definition. It must contain three properties:
	 *
	 *   - executable - the name of the driver executable
	 *   - url - the URL where the driver can be downloaded from
	 *   - seleniumProperty - the name of the Java property used to tell Selenium where the driver is
	 *
	 * @example
	 * 	[
	 *      'chrome',
	 *      {
	 *          name: 'firefox',
	 *          version: '0.8.0'
	 *      },
	 *      {
	 *          url: 'https://github.com/operasoftware/operachromiumdriver/releases/.../operadriver_mac64.zip',
	 *          executable: 'operadriver',
	 *          seleniumProperty: 'webdriver.opera.driver'
	 *      }
	 * 	]
	 *
	 * @type {Array.<string|Object>}
	 * @default [ 'chrome' ]
	 */
	drivers: null,

	/**
	 * The base address where Selenium artifacts may be found.
	 *
	 * @type {string}
	 * @default https://selenium-release.storage.googleapis.com
	 */
	baseUrl: 'https://selenium-release.storage.googleapis.com',

	/**
	 * The desired version of selenium to install.
	 *
	 * @type {string}
	 * @default 2.53.1
	 */
	version: '2.53.1',

	/**
	 * Timeout in milliseconds for communicating with the Selenium server
	 *
	 * @type {number}
	 * @default 5000
	 */
	seleniumTimeout: 5000,

	get artifact() {
		return 'selenium-server-standalone-' + this.version + '.jar';
	},

	get directory() {
		return pathUtil.join(__dirname, 'selenium-standalone');
	},

	get executable() {
		return 'java';
	},

	get isDownloaded() {
		var directory = this.directory;
		return this._getDriverConfigs().every(function (config) {
			return util.fileExists(pathUtil.join(directory, config.executable));
		}, this) && util.fileExists(pathUtil.join(directory, this.artifact));
	},

	get url() {
		var majorMinorVersion = this.version.slice(0, this.version.lastIndexOf('.'));
		return format('%s/%s/%s', this.baseUrl, majorMinorVersion, this.artifact);
	},

	download: function (forceDownload) {
		if (!forceDownload && this.isDownloaded) {
			return Promise.resolve();
		}

		var self = this;
		return new Promise(function (resolve, reject, progress, setCanceler) {
			setCanceler(function (reason) {
				tasks && tasks.forEach(function (task) {
					task.cancel(reason);
				});
			});

			var configs = [ { url: self.url, executable: self.artifact } ];
			configs = configs.concat(self._getDriverConfigs());

			var tasks = configs.map(function (config) {
				var executable = config.executable;
				if (fs.existsSync(pathUtil.join(self.directory, executable))) {
					return Promise.resolve();
				}

				return self._downloadFile(config.url, self.proxy, {
					executable: executable
				}).then(null, null, progress);
			});

			resolve(Promise.all(tasks));
		});
	},

	sendJobState: function () {
		// This is a noop for Selenium
		return Promise.resolve();
	},

	_getDriverConfigs: function () {
		function getDriverConfig(name, options) {
			var Constructor = driverNameMap[name];
			if (!Constructor) {
				throw new Error('Invalid driver name "' + name + '"');
			}
			return new Constructor(options);
		}

		return this.drivers.map(function (data) {
			if (typeof data === 'string') {
				return getDriverConfig(data);
			}

			if (typeof data === 'object' && data.name) {
				return getDriverConfig(data.name, data);
			}

			// data is a driver definition
			return data;
		});
	},

	_makeArgs: function () {
		var directory = this.directory;
		var driverConfigs = this._getDriverConfigs();
		var args = [];

		driverConfigs.reduce(function (args, config) {
			var file = pathUtil.join(directory, config.executable);
			args.push('-D' + config.seleniumProperty + '=' + file);
			return args;
		}, args);

		if (this.seleniumArgs) {
			args = args.concat(this.seleniumArgs);
		}

		args = args.concat([
			'-jar',
			pathUtil.join(this.directory, this.artifact),
			'-port',
			this.port
		]);

		if (this.verbose) {
			args.push('-debug');
			console.log('starting with arguments: ', args.join(' '));
		}

		return args;
	},

	_postDownloadFile: function (response, options) {
		if (pathUtil.extname(options.executable) === '.jar') {
			return util.writeFile(response.data, pathUtil.join(this.directory, options.executable));
		}
		return util.decompress(response.data, this.directory);
	},

	_start: function () {
		var self = this;
		var childHandle = this._makeChild();
		var child = childHandle.process;
		var dfd = childHandle.deferred;
		var handle = util.on(child.stderr, 'data', function (data) {
			// Selenium recommends that we poll the hub looking for a status response
			// https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/7957
			// We're going against the recommendation here for a few reasons
			// 1. There's no default pid or log to look for errors to provide a specific failure
			// 2. Polling on a failed server start could leave us with an unpleasant wait
			// 3. Just polling a selenium server doesn't guarantee it's the server we started
			// 4. This works pretty well
			if (data.indexOf('Selenium Server is up and running') > -1) {
				dfd.resolve();
			}
			if (self.verbose) {
				process.stderr.write(data);
			}
		});
		var removeHandle = handle.remove.bind(handle);

		dfd.promise.then(removeHandle, removeHandle);

		return childHandle;
	},

	_stop: function () {
		var dfd = new Promise.Deferred();
		var childProcess = this._process;
		var timeout;

		childProcess.once('exit', function (code) {
			dfd.resolve(code);
			clearTimeout(timeout);
		});

		// Nicely ask the Selenium server to shutdown
		request('http://' + this.hostname + ':' + this.port +
			'/selenium-server/driver/?cmd=shutDownSeleniumServer', {
			timeout: this.seleniumTimeout,
			handleAs: 'text'
		});

		// Give Selenium a few seconds, then forcefully tell it to shutdown
		timeout = setTimeout(function () {
			childProcess.kill('SIGTERM');
		}, 5000);

		return dfd.promise;
	}
});

module.exports = SeleniumTunnel;
<|endoftext|>


<commit_before>var request = require( 'request' );
var async = require( 'async' );
var _ = require( 'underscore' );
var moment = require( 'moment' );
var parseString = require('xml2js').parseString;
var apiRequest = require( "./utils" ).apiRequest;
var convertXML = require( "./utils" ).convertXML;

module.exports = function ( apiKey ) {

    if ( !apiKey ) {
        throw new Error( "CTA Bustracker requires an API key" );
    }

    var apiUrl = "http://www.ctabustracker.com/bustime/api/v1";

    var baseOptions = {
        qs: {
            key: apiKey
        },
        method: "GET"
    };

    var requestFactory = function ( options ) {
        return function ( done ) {
            return apiRequest( options, done );
        };
    };

    var responseFactory = function ( options, cb ) {
        async.waterfall( [ options.makeRequest, convertXML ],
            function ( err, data ) {

                if ( err ) {
                    return cb( err );
                }
                return cb( null, data[ options.dataProp ] );

            } );
    };

    return {
        time: function ( cb ) {

            var options = _.clone( baseOptions );
            options.url = apiUrl + "/gettime";

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "tm"
            }, cb );

        },
        vehiclesById: function ( vehicleIds, cb ) {

            var self = this;

            var options = _.clone( baseOptions );
            options.url = apiUrl + "/getvehicles";
            options.qs.vid = vehicleIds.join( "," );

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "vehicle"
            }, cb );

        },
        vehiclesByRoute: function ( routeIds, cb ) {

            var options = _.clone( baseOptions );
            options.url = apiUrl + "/getvehicles";
            options.qs.rt = routeIds.join( "," );

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "vehicle"
            }, cb );

        },
        routes: function ( cb ) {

            var options = _.clone( baseOptions );
            options.url = apiUrl + "/getroutes";

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "route"
            }, cb );

        },
        routeDirections: function ( routeId, cb ) {

            var options = _.clone( baseOptions );
            options.url = apiUrl + "/getdirections";
            options.qs.rt = routeId;

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "dir"
            }, cb );

        },
        stops: function ( routeId, direction, cb ) {

            var options = _.clone( baseOptions );
            options.url = apiUrl + "/getstops";
            options.qs.rt = routeId;
            options.qs.dir = direction;

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "stop"
            }, cb );

        },
        patternsById: function ( patternIds, cb ) {

            var options = _.clone( baseOptions );
            options.url = apiUrl + "/getpatterns";
            options.qs.pid = patternIds.join( "," );

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "ptr"
            }, cb );

        },
        patternsByRoute: function ( routeId, cb ) {

            var options = _.clone( baseOptions );
            options.url = apiUrl + "/getpatterns";
            options.qs.rt = routeId;

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "ptr"
            }, cb );

        },
        predictionsByStop: function ( options, cb ) {

            var stopIds = options.stopIds;
            var routeIds = options.routeIds;
            var topCount = options.topCount;

            var reqOptions = _.clone( baseOptions );
            reqOptions.url = apiUrl + "/getpredictions";
            reqOptions.qs.stpid = stopIds.join( "," );
            if ( typeof routeIds !== "undefined" ) {
                reqOptions.qs.rt = routeIds.join( "," );
            }

            return responseFactory( {
                makeRequest: requestFactory( reqOptions ),
                dataProp: "prd"
            }, cb );

        },
        predictionsByVehicle: function ( options, cb ) {

            var vehicleIds = options.vehicleIds;
            var topCount = options.topCount;

            var reqOptions = _.clone( baseOptions );
            reqOptions.url = apiUrl + "/getpredictions";
            reqOptions.qs.vid = vehicleIds.join( "," );

            return responseFactory( {
                makeRequest: requestFactory( reqOptions ),
                dataProp: "prd"
            }, cb );

        },
        serviceBulletinsByRoute: function ( options, cb ) {

            var routeIds = options.routeIds;
            var routeDirection = options.routeDirection;

            var reqOptions = _.clone( baseOptions );
            reqOptions.url = apiUrl + "/getservicebulletins";
            reqOptions.qs.rt = routeIds.join( "," );
            if ( typeof routeDirection !== "undefined" ) {
                reqOptions.qs.rtdir = routeDirection;
            }

            return responseFactory( {
                makeRequest: requestFactory( reqOptions ),
                dataProp: "sb"
            }, cb );

        },
        serviceBulletinsByStop: function ( stopIds, cb ) {

            var reqOptions = _.clone( baseOptions );
            reqOptions.url = apiUrl + "/getservicebulletins";
            reqOptions.qs.stpid = stopIds.join( "," );

            return responseFactory( {
                makeRequest: requestFactory( reqOptions ),
                dataProp: "sb"
            }, cb );

        }
    };

};
<commit_msg>Refactor baseOptions<commit_after>var request = require( 'request' );
var async = require( 'async' );
var moment = require( 'moment' );
var parseString = require('xml2js').parseString;
var apiRequest = require( "./utils" ).apiRequest;
var convertXML = require( "./utils" ).convertXML;

module.exports = function ( apiKey ) {

    if ( !apiKey ) {
        throw new Error( "CTA Bustracker requires an API key" );
    }

    var apiUrl = "http://www.ctabustracker.com/bustime/api/v1";

    var baseOptions = function () {
        return {
            qs: {
                key: apiKey
            },
            method: "GET"
        };
    };

    var requestFactory = function ( options ) {
        return function ( done ) {
            return apiRequest( options, done );
        };
    };

    var responseFactory = function ( options, cb ) {
        async.waterfall( [ options.makeRequest, convertXML ],
            function ( err, data ) {

                if ( err ) {
                    return cb( err );
                }
                return cb( null, data[ options.dataProp ] );

            } );
    };

    return {
        time: function ( cb ) {

            var options = baseOptions();
            options.url = apiUrl + "/gettime";

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "tm"
            }, cb );

        },
        vehiclesById: function ( vehicleIds, cb ) {

            var options = baseOptions();
            options.url = apiUrl + "/getvehicles";
            options.qs.vid = vehicleIds.join( "," );

            console.log( options );

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "vehicle"
            }, cb );

        },
        vehiclesByRoute: function ( routeIds, cb ) {

            var options = baseOptions();
            options.url = apiUrl + "/getvehicles";
            options.qs.rt = routeIds.join( "," );

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "vehicle"
            }, cb );

        },
        routes: function ( cb ) {

            var options = baseOptions();
            options.url = apiUrl + "/getroutes";

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "route"
            }, cb );

        },
        routeDirections: function ( routeId, cb ) {

            var options = baseOptions();
            options.url = apiUrl + "/getdirections";
            options.qs.rt = routeId;

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "dir"
            }, cb );

        },
        stops: function ( routeId, direction, cb ) {

            var options = baseOptions();
            options.url = apiUrl + "/getstops";
            options.qs.rt = routeId;
            options.qs.dir = direction;

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "stop"
            }, cb );

        },
        patternsById: function ( patternIds, cb ) {

            var options = baseOptions();
            options.url = apiUrl + "/getpatterns";
            options.qs.pid = patternIds.join( "," );

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "ptr"
            }, cb );

        },
        patternsByRoute: function ( routeId, cb ) {

            var options = baseOptions();
            options.url = apiUrl + "/getpatterns";
            options.qs.rt = routeId;

            return responseFactory( {
                makeRequest: requestFactory( options ),
                dataProp: "ptr"
            }, cb );

        },
        predictionsByStop: function ( options, cb ) {

            var stopIds = options.stopIds;
            var routeIds = options.routeIds;
            var topCount = options.topCount;

            var reqOptions = baseOptions();
            reqOptions.url = apiUrl + "/getpredictions";
            reqOptions.qs.stpid = stopIds.join( "," );
            if ( typeof routeIds !== "undefined" ) {
                reqOptions.qs.rt = routeIds.join( "," );
            }

            return responseFactory( {
                makeRequest: requestFactory( reqOptions ),
                dataProp: "prd"
            }, cb );

        },
        predictionsByVehicle: function ( options, cb ) {

            var vehicleIds = options.vehicleIds;
            var topCount = options.topCount;

            var reqOptions = baseOptions();
            reqOptions.url = apiUrl + "/getpredictions";
            reqOptions.qs.vid = vehicleIds.join( "," );

            return responseFactory( {
                makeRequest: requestFactory( reqOptions ),
                dataProp: "prd"
            }, cb );

        },
        serviceBulletinsByRoute: function ( options, cb ) {

            var routeIds = options.routeIds;
            var routeDirection = options.routeDirection;

            var reqOptions = baseOptions();
            reqOptions.url = apiUrl + "/getservicebulletins";
            reqOptions.qs.rt = routeIds.join( "," );
            if ( typeof routeDirection !== "undefined" ) {
                reqOptions.qs.rtdir = routeDirection;
            }

            return responseFactory( {
                makeRequest: requestFactory( reqOptions ),
                dataProp: "sb"
            }, cb );

        },
        serviceBulletinsByStop: function ( stopIds, cb ) {

            var reqOptions = baseOptions();
            reqOptions.url = apiUrl + "/getservicebulletins";
            reqOptions.qs.stpid = stopIds.join( "," );

            return responseFactory( {
                makeRequest: requestFactory( reqOptions ),
                dataProp: "sb"
            }, cb );

        }
    };

};
<|endoftext|>


<commit_before>const {add} = require('../src/lib/add');
const {expect} = require('chai');

describe("add", () => {
  it('add numbers together', () => {
    expect(add(2, 3)).to.equal(5)
  })
  it('should return undefined if no arguments are given'() => {
    expect(add()).to.equal(undefined)
  })
})
<commit_msg>edit add<commit_after>const {add} = require('../src/lib/add');
const {expect} = require('chai');

describe('add', function()) {
  it('add numbers together', function() {
    expect(add(2, 3)).to.equal(5)
  })
  it('should return undefined if no arguments are given', function () {
    expect(add()).to.equal(undefined)
  })
})
<|endoftext|>


<commit_before>const core = {
  array: value => core.is.array(value) ? value : core.defined(value) ? [value] : [],
  buffer: filename => new Buffer(core.stream(filename)),
  config: filename => {
    const json = core.json(filename)
    return core.resolve(json)
  },
  defined: value => value !== null && value !== undefined && value !== '' && (value && value.length),
  debug: function () {
    if (process.env.debug) {
      console.log.apply(console, [].slice.call(arguments))
    }
  },
  dir: path => core.path.join(process.cwd(), path),
  exists: (path, cwd) => {
    cwd = cwd || process.cwd()
    if (!path) return false
    try {
      return core.fs.statSync(core.path.join(cwd, path))
    } catch (err) {
      return false
    }
  },
  filters: {
    arrays: (key, hash) => core.is.array(hash[key]),
    functions: (key, hash) => core.is.func(hash[key]),
    objects: (key, hash) => core.is.object(hash[key]),
    strings: (key, hash) => core.is.string(hash[key])
  },
  fs: require('fs'),
  is: {
    array: value => value && value instanceof Array,
    func: value => value && typeof value === 'function',
    object: value => value && typeof value === 'object',
    string: value => value && typeof value === 'string'
  },
  json: filename => JSON.parse(core.buffer(filename)),
  mapdeep: (hash, action) => {
    Object.keys(hash).map(key => {
      const value = action(key, hash[key])
      if (core.is.array(value) || core.is.object(value)) {
        core.mapdeep(value, action)
      }
    })
  },
  merge: require('merge').recursive,
  os: require('os'),
  path: require('path'),
  render: require('mustache').render,
  require: value => {
    if (!value) throw new Error('Required value was not provided.')
  },
  quote: (value, quote, separator) => {
    quote = quote || "'"
    separator = separator || ', '
    if (core.is.array(value)) {
      return value.map(item => core.quote(item)).join(separator)
    }
    if (core.is.string(value)) {
      return quote + value + quote
    }
    return value
  },
  resolve: (hash, root, all) => {
    root = root || hash
    Object.keys(hash).map(key => {
      var value = hash[key]
      if (core.is.string(value) && (all || value.indexOf(':') === 0)) {
        hash[key] = core.render(value, root).substring(1)
      } else if (core.is.array(value) || core.is.object(value)) {
        core.resolve(value, root, all)
      }
    })
    return root
  },
  stream: filename => core.fs.readFileSync(filename),
  taskname: (prefix, name) => [prefix, name].join(':'),
  text: filename => core.stream(filename).toString(),
  unqiue: value => {
    const hash = {}
    const results = []
    Object.keys(value).map(key => {
      if (!hash[key]) {
        hash[key] = key
        results.push(value[key])
      }
    })
    return results
  }
}

module.exports = core
<commit_msg>fix for not truncating first char on resolve<commit_after>const core = {
  array: value => core.is.array(value) ? value : core.defined(value) ? [value] : [],
  buffer: filename => new Buffer(core.stream(filename)),
  config: filename => {
    const json = core.json(filename)
    return core.resolve(json)
  },
  defined: value => value !== null && value !== undefined && value !== '' && (value && value.length),
  debug: function () {
    if (process.env.debug) {
      console.log.apply(console, [].slice.call(arguments))
    }
  },
  dir: path => core.path.join(process.cwd(), path),
  exists: (path, cwd) => {
    cwd = cwd || process.cwd()
    if (!path) return false
    try {
      return core.fs.statSync(core.path.join(cwd, path))
    } catch (err) {
      return false
    }
  },
  filters: {
    arrays: (key, hash) => core.is.array(hash[key]),
    functions: (key, hash) => core.is.func(hash[key]),
    objects: (key, hash) => core.is.object(hash[key]),
    strings: (key, hash) => core.is.string(hash[key])
  },
  fs: require('fs'),
  is: {
    array: value => value && value instanceof Array,
    func: value => value && typeof value === 'function',
    object: value => value && typeof value === 'object',
    string: value => value && typeof value === 'string'
  },
  json: filename => JSON.parse(core.buffer(filename)),
  mapdeep: (hash, action) => {
    Object.keys(hash).map(key => {
      const value = action(key, hash[key])
      if (core.is.array(value) || core.is.object(value)) {
        core.mapdeep(value, action)
      }
    })
  },
  merge: require('merge').recursive,
  os: require('os'),
  path: require('path'),
  render: require('mustache').render,
  require: value => {
    if (!value) throw new Error('Required value was not provided.')
  },
  quote: (value, quote, separator) => {
    quote = quote || "'"
    separator = separator || ', '
    if (core.is.array(value)) {
      return value.map(item => core.quote(item)).join(separator)
    }
    if (core.is.string(value)) {
      return quote + value + quote
    }
    return value
  },
  resolve: (hash, root, all) => {
    root = root || hash
    Object.keys(hash).map(key => {
      var value = hash[key]
      if (core.is.string(value) && (all || value.indexOf(':') === 0)) {
        const rendered = core.render(value, root)
        hash[key] = all ? rendered : rendered.substring(1)
      } else if (core.is.array(value) || core.is.object(value)) {
        core.resolve(value, root, all)
      }
    })
    return root
  },
  stream: filename => core.fs.readFileSync(filename),
  taskname: (prefix, name) => [prefix, name].join(':'),
  text: filename => core.stream(filename).toString(),
  unqiue: value => {
    const hash = {}
    const results = []
    Object.keys(value).map(key => {
      if (!hash[key]) {
        hash[key] = key
        results.push(value[key])
      }
    })
    return results
  }
}

module.exports = core
<|endoftext|>


<commit_before>/* eslint-env node, mocha */

/* Purpose:
 *
 * Tests that are SPECIFIC TO THIS ADDON's FUNCTIONALITY
 */

// for unhandled promise rejection debugging
process.on("unhandledRejection", r => console.log(r)); // eslint-disable-line no-console

const assert = require("assert");
const utils = require("./utils");
const webdriver = require("selenium-webdriver");
const firefox = require("selenium-webdriver/firefox");

const By = webdriver.By;
const Context = firefox.Context;
const until = webdriver.until;

/* Part 1:  Test helpers */

async function postTestReset(driver) {
  // reset the counter pref to 0 so that the treatment is always shown
  // reset the addedBool pref
  await driver.executeAsyncScript((...args) => {
    const callback = args[args.length - 1];
    /*
    TODO: add tests that confirm that the study is only executed a predefined set number of times
    Components.utils.import("resource://gre/modules/Preferences.jsm");
    const COUNTER_PREF = "extensions.template-shield-study.counter";
    const ADDED_BOOL_PREF = "extensions.template-shield-study.addedBool";
    if (Preferences.has(COUNTER_PREF)) {
      Preferences.set(COUNTER_PREF, 0);
    }
    if (Preferences.has(ADDED_BOOL_PREF)) {
      Preferences.set(ADDED_BOOL_PREF, false);
    }
    */
    callback();
  });
}

/* Part 2:  The Tests */

describe("basic functional tests", function() {
  // This gives Firefox time to start, and us a bit longer during some of the tests.
  this.timeout(15000);

  let driver;
  let addonId;
  let pings;

  before(async() => {
    driver = await utils.promiseSetupDriver();
    // install the addon
    addonId = await utils.installAddon(driver);
    // allow our shield study addon some time to start
    await driver.sleep(1000);
    // collect sent pings
    pings = await utils.getTelemetryPings(driver, ["shield-study", "shield-study-addon"]);
  });

  after(async() => driver.quit());

  afterEach(async() => postTestReset(driver));

  it("should send shield telemetry pings", async() => {

    assert(pings.length > 0, "at least one shield telemetry ping");

  });

  it("at least one shield-study telemetry ping with study_state=installed", async() => {

    const foundPings = utils.searchTelemetry([
      ping => ping.type === "shield-study" && ping.payload.data.study_state === "installed",
    ], pings);
    assert(foundPings.length > 0, "at least one shield-study telemetry ping with study_state=installed");

  });

  it("at least one shield-study telemetry ping with study_state=enter", async() => {

    const foundPings = utils.searchTelemetry([
      ping => ping.type === "shield-study" && ping.payload.data.study_state === "enter",
    ], pings);
    assert(foundPings.length > 0, "at least one shield-study telemetry ping with study_state=enter");

  });

  it("one shield-study-addon telemetry ping for the esper-init event", async() => {

    const foundPings = utils.searchTelemetry([
      ping => ping.type === "shield-study-addon" && ping.payload.data.attributes.event === "esper-init",
    ], pings);
    assert(foundPings.length === 1);

  });

  it("one proper shield-study-addon telemetry ping for the telemetry-payload event", async() => {

    const foundPings = utils.searchTelemetry([
      ping => ping.type === "shield-study-addon" && ping.payload.data.attributes.event === "telemetry-payload",
    ], pings);
    assert(foundPings.length === 1);

    const ping = foundPings[0];

    assert(ping.payload.shield_version === "4.1.0", "expected shield-study-utils version");

    const assertion = value => {
      return value !== "undefined"
    };

    const expected = {
      "completeTelemetrySessionPayload": true,
      "uptime": true,
      "total_time": true,
      "profile_subsession_counter": true,
      "subsession_start_date": true,
      "timezone_offset": true,
      "places_bookmarks_count": true,
      "places_pages_count": true,
      "search_counts": true,
      "scalar_parent_browser_engagement_window_open_event_count": true,
      "scalar_parent_browser_engagement_total_uri_count": true,
      "scalar_parent_browser_engagement_navigation_urlbar": true,
      "scalar_parent_browser_engagement_navigation_contextmenu": true,
      "scalar_parent_browser_engagement_tab_open_event_count": true,
      "scalar_parent_browser_engagement_navigation_searchbar": true,
      "scalar_parent_browser_engagement_navigation_about_newtab": true,
      "scalar_parent_browser_engagement_unique_domains_count": true,
      "scalar_parent_browser_engagement_max_concurrent_window_count": true,
      "scalar_parent_browser_engagement_max_concurrent_tab_count": true,
      "scalar_parent_browser_engagement_unfiltered_uri_count": true,
    };

    const actual = {};

    for (const attribute in expected) {
      actual[attribute] = assertion(ping.payload.data.attributes[attribute]);
    }

    assert.deepEqual(expected, actual, "no attributes should be 'undefined'");

  });

});
<commit_msg>Clarified shield ping attribute assertions<commit_after>/* eslint-env node, mocha */

/* Purpose:
 *
 * Tests that are SPECIFIC TO THIS ADDON's FUNCTIONALITY
 */

// for unhandled promise rejection debugging
process.on("unhandledRejection", r => console.log(r)); // eslint-disable-line no-console

const assert = require("assert");
const utils = require("./utils");
const webdriver = require("selenium-webdriver");
const firefox = require("selenium-webdriver/firefox");

const By = webdriver.By;
const Context = firefox.Context;
const until = webdriver.until;

/* Part 1:  Test helpers */

async function postTestReset(driver) {
  // reset the counter pref to 0 so that the treatment is always shown
  // reset the addedBool pref
  await driver.executeAsyncScript((...args) => {
    const callback = args[args.length - 1];
    /*
    TODO: add tests that confirm that the study is only executed a predefined set number of times
    Components.utils.import("resource://gre/modules/Preferences.jsm");
    const COUNTER_PREF = "extensions.template-shield-study.counter";
    const ADDED_BOOL_PREF = "extensions.template-shield-study.addedBool";
    if (Preferences.has(COUNTER_PREF)) {
      Preferences.set(COUNTER_PREF, 0);
    }
    if (Preferences.has(ADDED_BOOL_PREF)) {
      Preferences.set(ADDED_BOOL_PREF, false);
    }
    */
    callback();
  });
}

/* Part 2:  The Tests */

describe("basic functional tests", function() {
  // This gives Firefox time to start, and us a bit longer during some of the tests.
  this.timeout(15000);

  let driver;
  let addonId;
  let pings;

  before(async() => {
    driver = await utils.promiseSetupDriver();
    // install the addon
    addonId = await utils.installAddon(driver);
    // allow our shield study addon some time to start
    await driver.sleep(1000);
    // collect sent pings
    pings = await utils.getTelemetryPings(driver, ["shield-study", "shield-study-addon"]);
  });

  after(async() => driver.quit());

  afterEach(async() => postTestReset(driver));

  it("should send shield telemetry pings", async() => {

    assert(pings.length > 0, "at least one shield telemetry ping");

  });

  it("at least one shield-study telemetry ping with study_state=installed", async() => {

    const foundPings = utils.searchTelemetry([
      ping => ping.type === "shield-study" && ping.payload.data.study_state === "installed",
    ], pings);
    assert(foundPings.length > 0, "at least one shield-study telemetry ping with study_state=installed");

  });

  it("at least one shield-study telemetry ping with study_state=enter", async() => {

    const foundPings = utils.searchTelemetry([
      ping => ping.type === "shield-study" && ping.payload.data.study_state === "enter",
    ], pings);
    assert(foundPings.length > 0, "at least one shield-study telemetry ping with study_state=enter");

  });

  it("one shield-study-addon telemetry ping for the esper-init event", async() => {

    const foundPings = utils.searchTelemetry([
      ping => ping.type === "shield-study-addon" && ping.payload.data.attributes.event === "esper-init",
    ], pings);
    assert(foundPings.length === 1);

  });

  it("one proper shield-study-addon telemetry ping for the telemetry-payload event", async() => {

    const foundPings = utils.searchTelemetry([
      ping => ping.type === "shield-study-addon" && ping.payload.data.attributes.event === "telemetry-payload",
    ], pings);
    assert(foundPings.length === 1);

    const ping = foundPings[0];

    assert(ping.payload.shield_version === "4.1.0", "expected shield-study-utils version");

    // no undefined expected attributes

    const notUndefined = value => {
      return value !== "undefined"
    };

    const assertionsByAttribute = {
      "completeTelemetrySessionPayload": notUndefined,
      "uptime": notUndefined,
      "total_time": notUndefined,
      "profile_subsession_counter": notUndefined,
      "subsession_start_date": notUndefined,
      "timezone_offset": notUndefined,
      "places_bookmarks_count": notUndefined,
      "places_pages_count": notUndefined,
      "search_counts": notUndefined,
      "scalar_parent_browser_engagement_window_open_event_count": notUndefined,
      "scalar_parent_browser_engagement_total_uri_count": notUndefined,
      "scalar_parent_browser_engagement_navigation_urlbar": notUndefined,
      "scalar_parent_browser_engagement_navigation_contextmenu": notUndefined,
      "scalar_parent_browser_engagement_tab_open_event_count": notUndefined,
      "scalar_parent_browser_engagement_navigation_searchbar": notUndefined,
      "scalar_parent_browser_engagement_navigation_about_newtab": notUndefined,
      "scalar_parent_browser_engagement_unique_domains_count": notUndefined,
      "scalar_parent_browser_engagement_max_concurrent_window_count": notUndefined,
      "scalar_parent_browser_engagement_max_concurrent_tab_count": notUndefined,
      "scalar_parent_browser_engagement_unfiltered_uri_count": notUndefined,
    };

    const expected = {};
    const actual = {};

    for (const attribute in assertionsByAttribute) {
      expected[attribute] = true;
      const assertion = assertionsByAttribute[attribute];
      const actualValue = ping.payload.data.attributes[attribute];
      actual[attribute] = assertion(actualValue);
    }

    assert.deepEqual(expected, actual, "no expected attributes should be 'undefined'");

  });

});
<|endoftext|>