<commit_before>/**
 * Blueprint API Configuration
 * (sails.config.blueprints)
 *
 * These settings are for the global configuration of blueprint routes and
 * request options (which impact the behavior of blueprint actions).
 *
 * You may also override any of these settings on a per-controller basis
 * by defining a '_config' key in your controller defintion, and assigning it
 * a configuration object with overrides for the settings in this file.
 * A lot of the configuration options below affect so-called "CRUD methods",
 * or your controllers' `find`, `create`, `update`, and `destroy` actions.
 *
 * It's important to realize that, even if you haven't defined these yourself, as long as
 * a model exists with the same name as the controller, Sails will respond with built-in CRUD
 * logic in the form of a JSON API, including support for sort, pagination, and filtering.
 *
 * For more information on the blueprint API, check out:
 * http://sailsjs.org/#/documentation/reference/blueprint-api
 *
 * For more information on the settings in this file, see:
 * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.blueprints.html
 *
 */

module.exports.blueprints = {

  /***************************************************************************
  *                                                                          *
  * Action routes speed up the backend development workflow by               *
  * eliminating the need to manually bind routes. When enabled, GET, POST,   *
  * PUT, and DELETE routes will be generated for every one of a controller's *
  * actions.                                                                 *
  *                                                                          *
  * If an `index` action exists, additional naked routes will be created for *
  * it. Finally, all `actions` blueprints support an optional path           *
  * parameter, `id`, for convenience.                                        *
  *                                                                          *
  * `actions` are enabled by default, and can be OK for production--         *
  * however, if you'd like to continue to use controller/action autorouting  *
  * in a production deployment, you must take great care not to              *
  * inadvertently expose unsafe/unintentional controller logic to GET        *
  * requests.                                                                *
  *                                                                          *
  ***************************************************************************/

  // actions: true,

  /***************************************************************************
  *                                                                          *
  * RESTful routes (`sails.config.blueprints.rest`)                          *
  *                                                                          *
  * REST blueprints are the automatically generated routes Sails uses to     *
  * expose a conventional REST API on top of a controller's `find`,          *
  * `create`, `update`, and `destroy` actions.                               *
  *                                                                          *
  * For example, a BoatController with `rest` enabled generates the          *
  * following routes:                                                        *
  * :::::::::::::::::::::::::::::::::::::::::::::::::::::::                  *
  *  GET /boat/:id? -> BoatController.find                                   *
  *  POST /boat -> BoatController.create                                     *
  *  PUT /boat/:id -> BoatController.update                                  *
  *  DELETE /boat/:id -> BoatController.destroy                              *
  *                                                                          *
  * `rest` blueprint routes are enabled by default, and are suitable for use *
  * in a production scenario, as long you take standard security precautions *
  * (combine w/ policies, etc.)                                              *
  *                                                                          *
  ***************************************************************************/

  // rest: true,

  /***************************************************************************
  *                                                                          *
  * Shortcut routes are simple helpers to provide access to a                *
  * controller's CRUD methods from your browser's URL bar. When enabled,     *
  * GET, POST, PUT, and DELETE routes will be generated for the              *
  * controller's`find`, `create`, `update`, and `destroy` actions.           *
  *                                                                          *
  * `shortcuts` are enabled by default, but should be disabled in            *
  * production.                                                              *
  *                                                                          *
  ***************************************************************************/

  // shortcuts: true,

  /***************************************************************************
  *                                                                          *
  * An optional mount path for all blueprint routes on a controller,         *
  * including `rest`, `actions`, and `shortcuts`. This allows you to take    *
  * advantage of blueprint routing, even if you need to namespace your API   *
  * methods.                                                                 *
  *                                                                          *
  * (NOTE: This only applies to blueprint autoroutes, not manual routes from *
  * `sails.config.routes`)                                                   *
  *                                                                          *
  ***************************************************************************/

  // prefix: '',

  /***************************************************************************
  *                                                                          *
  * Whether to pluralize controller names in blueprint routes.               *
  *                                                                          *
  * (NOTE: This only applies to blueprint autoroutes, not manual routes from *
  * `sails.config.routes`)                                                   *
  *                                                                          *
  * For example, REST blueprints for `FooController` with `pluralize`        *
  * enabled:                                                                 *
  * GET /foos/:id?                                                           *
  * POST /foos                                                               *
  * PUT /foos/:id?                                                           *
  * DELETE /foos/:id?                                                        *
  *                                                                          *
  ***************************************************************************/

  // pluralize: false,

  /***************************************************************************
  *                                                                          *
  * Whether the blueprint controllers should populate model fetches with     *
  * data from other models which are linked by associations                  *
  *                                                                          *
  * If you have a lot of data in one-to-many associations, leaving this on   *
  * may result in very heavy api calls                                       *
  *                                                                          *
  ***************************************************************************/

  // populate: true,

  /****************************************************************************
  *                                                                           *
  * Whether to run Model.watch() in the find and findOne blueprint actions.   *
  * Can be overridden on a per-model basis.                                   *
  *                                                                           *
  ****************************************************************************/

  // autoWatch: true,

  /****************************************************************************
  *                                                                           *
  * The default number of records to show in the response from a "find"       *
  * action. Doubles as the default size of populated arrays if populate is    *
  * true.                                                                     *
  *                                                                           *
  ****************************************************************************/

  // defaultLimit: 30

};
<commit_msg>Made sure all blueprints are automatic<commit_after>/**
 * Blueprint API Configuration
 * (sails.config.blueprints)
 *
 * These settings are for the global configuration of blueprint routes and
 * request options (which impact the behavior of blueprint actions).
 *
 * You may also override any of these settings on a per-controller basis
 * by defining a '_config' key in your controller defintion, and assigning it
 * a configuration object with overrides for the settings in this file.
 * A lot of the configuration options below affect so-called "CRUD methods",
 * or your controllers' `find`, `create`, `update`, and `destroy` actions.
 *
 * It's important to realize that, even if you haven't defined these yourself, as long as
 * a model exists with the same name as the controller, Sails will respond with built-in CRUD
 * logic in the form of a JSON API, including support for sort, pagination, and filtering.
 *
 * For more information on the blueprint API, check out:
 * http://sailsjs.org/#/documentation/reference/blueprint-api
 *
 * For more information on the settings in this file, see:
 * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.blueprints.html
 *
 */

module.exports.blueprints = {

  /***************************************************************************
  *                                                                          *
  * Action routes speed up the backend development workflow by               *
  * eliminating the need to manually bind routes. When enabled, GET, POST,   *
  * PUT, and DELETE routes will be generated for every one of a controller's *
  * actions.                                                                 *
  *                                                                          *
  * If an `index` action exists, additional naked routes will be created for *
  * it. Finally, all `actions` blueprints support an optional path           *
  * parameter, `id`, for convenience.                                        *
  *                                                                          *
  * `actions` are enabled by default, and can be OK for production--         *
  * however, if you'd like to continue to use controller/action autorouting  *
  * in a production deployment, you must take great care not to              *
  * inadvertently expose unsafe/unintentional controller logic to GET        *
  * requests.                                                                *
  *                                                                          *
  ***************************************************************************/

  actions: true,

  /***************************************************************************
  *                                                                          *
  * RESTful routes (`sails.config.blueprints.rest`)                          *
  *                                                                          *
  * REST blueprints are the automatically generated routes Sails uses to     *
  * expose a conventional REST API on top of a controller's `find`,          *
  * `create`, `update`, and `destroy` actions.                               *
  *                                                                          *
  * For example, a BoatController with `rest` enabled generates the          *
  * following routes:                                                        *
  * :::::::::::::::::::::::::::::::::::::::::::::::::::::::                  *
  *  GET /boat/:id? -> BoatController.find                                   *
  *  POST /boat -> BoatController.create                                     *
  *  PUT /boat/:id -> BoatController.update                                  *
  *  DELETE /boat/:id -> BoatController.destroy                              *
  *                                                                          *
  * `rest` blueprint routes are enabled by default, and are suitable for use *
  * in a production scenario, as long you take standard security precautions *
  * (combine w/ policies, etc.)                                              *
  *                                                                          *
  ***************************************************************************/

  rest: true,

  /***************************************************************************
  *                                                                          *
  * Shortcut routes are simple helpers to provide access to a                *
  * controller's CRUD methods from your browser's URL bar. When enabled,     *
  * GET, POST, PUT, and DELETE routes will be generated for the              *
  * controller's`find`, `create`, `update`, and `destroy` actions.           *
  *                                                                          *
  * `shortcuts` are enabled by default, but should be disabled in            *
  * production.                                                              *
  *                                                                          *
  ***************************************************************************/

  shortcuts: true,

  /***************************************************************************
  *                                                                          *
  * An optional mount path for all blueprint routes on a controller,         *
  * including `rest`, `actions`, and `shortcuts`. This allows you to take    *
  * advantage of blueprint routing, even if you need to namespace your API   *
  * methods.                                                                 *
  *                                                                          *
  * (NOTE: This only applies to blueprint autoroutes, not manual routes from *
  * `sails.config.routes`)                                                   *
  *                                                                          *
  ***************************************************************************/

  prefix: '',

  /***************************************************************************
  *                                                                          *
  * Whether to pluralize controller names in blueprint routes.               *
  *                                                                          *
  * (NOTE: This only applies to blueprint autoroutes, not manual routes from *
  * `sails.config.routes`)                                                   *
  *                                                                          *
  * For example, REST blueprints for `FooController` with `pluralize`        *
  * enabled:                                                                 *
  * GET /foos/:id?                                                           *
  * POST /foos                                                               *
  * PUT /foos/:id?                                                           *
  * DELETE /foos/:id?                                                        *
  *                                                                          *
  ***************************************************************************/

  // pluralize: false,

  /***************************************************************************
  *                                                                          *
  * Whether the blueprint controllers should populate model fetches with     *
  * data from other models which are linked by associations                  *
  *                                                                          *
  * If you have a lot of data in one-to-many associations, leaving this on   *
  * may result in very heavy api calls                                       *
  *                                                                          *
  ***************************************************************************/

  // populate: true,

  /****************************************************************************
  *                                                                           *
  * Whether to run Model.watch() in the find and findOne blueprint actions.   *
  * Can be overridden on a per-model basis.                                   *
  *                                                                           *
  ****************************************************************************/

  // autoWatch: true,

  /****************************************************************************
  *                                                                           *
  * The default number of records to show in the response from a "find"       *
  * action. Doubles as the default size of populated arrays if populate is    *
  * true.                                                                     *
  *                                                                           *
  ****************************************************************************/

  // defaultLimit: 30

};
<|endoftext|>


<commit_before>var NativeKeyboard = require('./native.js');

var ARROW_UP = 38;
var ARROW_DOWN = 40;
var ARROW_LEFT = 37;
var ARROW_RIGHT = 39;
var W_KEY = 87;
var S_KEY = 83;
var A_KEY = 65;
var D_KEY = 68;
var CTRL_KEY = 17;
var SHIFT_KEY = 16;

module.exports = Keyboard;

function Keyboard(delegator) {
    var native = NativeKeyboard(delegator);

    return {
        isDown: native.isDown,
        keysDown: native.keysDown,
        lastPressed: native.lastPressed,
        directions: native.directions,
        arrows: native.directions(
            ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT),
        wasd: native.directions(W_KEY, S_KEY, A_KEY, D_KEY),
        ctrl: native.isDown(CTRL_KEY),
        shift: native.isDown(SHIFT_KEY)
    };
}
<commit_msg>expose keydown<commit_after>var NativeKeyboard = require('./native.js');

var ARROW_UP = 38;
var ARROW_DOWN = 40;
var ARROW_LEFT = 37;
var ARROW_RIGHT = 39;
var W_KEY = 87;
var S_KEY = 83;
var A_KEY = 65;
var D_KEY = 68;
var CTRL_KEY = 17;
var SHIFT_KEY = 16;

module.exports = Keyboard;

function Keyboard(delegator) {
    var native = NativeKeyboard(delegator);

    return {
        isDown: native.isDown,
        keysDown: native.keysDown,
        lastPressed: native.lastPressed,
        keyDown: native.keyDown,
        directions: native.directions,
        arrows: native.directions(
            ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT),
        wasd: native.directions(W_KEY, S_KEY, A_KEY, D_KEY),
        ctrl: native.isDown(CTRL_KEY),
        shift: native.isDown(SHIFT_KEY)
    };
}
<|endoftext|>


<commit_before>/**
 *
 * @license
 * Copyright (C) 2016 Joseph Roque
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @author Joseph Roque
 * @created 2016-11-1
 * @file TextUtils-test.js
 * @description Tests text formatting and adjustments.
 *
 */
'use strict';

// An example valid URL to open.
const exampleURL = 'http://google.com';
// An example valid telephone number to open.
const exampleTelephone = 'tel:1235556789';
// An example valid formatted telephone number to display.
const exampleFormattedTelephone = '(123) 555-6789';
// An example valid shortened telephone number to open.
const exampleShortTelephone = 'tel:123';
// An example valid formatted shortened telephone number to display.
const exampleShortFormattedTelephone = '123';
// An example valid email to open.
const exampleEmail = 'mailto:google@google.com';
// An example valid formatted email to display.
const exampleFormattedEmail = 'google@google.com';
// An example of an invalid URL.
const invalidURL = 'thisisnotavalidURL';

// A dictionary of time values, with their expected formatting in 24 hour adjusted time.
const expectedTimeFormats = {
  '10:20': '10:20',
  '14:08': '14:08',
  '36:11': '12:11',
  '18:45': '18:45',
  '02:50': '02:50',
  '25:19': '01:19',
  '28:30': '04:30',
};

// Require modules for testing
import * as TextUtils from '../TextUtils';

// Add the String.format function
if (!String.format) {
  String.format = function format(str: string) {
    const args = Array.prototype.slice.call(arguments, 1);
    return str.replace(/{(\d+)}/g, (match, number) => {
      return typeof args[number] == 'undefined'
        ? match
        : args[number];
    });
  };
}

describe('TextUtils-test', () => {

  it('tests converting time formats with invalid params', () => {
    const invalidTime = 'not a time';
    const invalidFormat = '00';
    expect(() => TextUtils.convertTimeFormat('12h', invalidTime)).toThrow();
    expect(() => TextUtils.convertTimeFormat(invalidFormat, '12:00')).toThrow();
  });

  it('tests converting to a 12h time format', () => {
    expect(TextUtils.convertTimeFormat('12h', '12:00 am')).toBe('12:00 am');
    expect(TextUtils.convertTimeFormat('12h', '12:00 a.m.')).toBe('12:00 am');
    expect(TextUtils.convertTimeFormat('12h', '12:00 AM')).toBe('12:00 am');
    expect(TextUtils.convertTimeFormat('12h', '12:00 A.M.')).toBe('12:00 am');
    expect(TextUtils.convertTimeFormat('12h', '00:00')).toBe('12:00 am');
    expect(TextUtils.convertTimeFormat('12h', '23:59')).toBe('11:59 pm');
    expect(TextUtils.convertTimeFormat('12h', '06:00')).toBe('6:00 am');
    expect(TextUtils.convertTimeFormat('12h', '12:00')).toBe('12:00 pm');
    expect(TextUtils.convertTimeFormat('12h', '18:00')).toBe('6:00 pm');
  });

  it('tests converting to a 24h time format', () => {
    expect(TextUtils.convertTimeFormat('24h', '23:59')).toBe('23:59');
    expect(TextUtils.convertTimeFormat('24h', '6:00 am')).toBe('06:00');
    expect(TextUtils.convertTimeFormat('24h', '11:59 pm')).toBe('23:59');
    expect(TextUtils.convertTimeFormat('24h', '11:59 p.m.')).toBe('23:59');
    expect(TextUtils.convertTimeFormat('24h', '11:59 PM')).toBe('23:59');
    expect(TextUtils.convertTimeFormat('24h', '11:59 P.M.')).toBe('23:59');
    expect(TextUtils.convertTimeFormat('24h', '12:00 am')).toBe('00:00');
    expect(TextUtils.convertTimeFormat('24h', '12:00 a.m.')).toBe('00:00');
    expect(TextUtils.convertTimeFormat('24h', '12:00 AM')).toBe('00:00');
    expect(TextUtils.convertTimeFormat('24h', '12:00 A.M.')).toBe('00:00');
  });

  it('tests that formatting a link returns the proper value.', () => {
    expect(TextUtils.formatLink(exampleURL)).toBe(exampleURL);
    expect(TextUtils.formatLink(exampleTelephone)).toBe(exampleFormattedTelephone);
    expect(TextUtils.formatLink(exampleShortTelephone)).toBe(exampleShortFormattedTelephone);
    expect(TextUtils.formatLink(exampleEmail)).toBe(exampleFormattedEmail);
    expect(TextUtils.formatLink(invalidURL)).toBe(invalidURL);
    expect(TextUtils.formatLink('')).toBe('');
    expect(TextUtils.formatLink(null)).toBe('');
  });

  it('tests that left padding a string returns the proper value.', () => {

    /* Magic numbers are used here for simplicity's sake */
    /* eslint-disable no-magic-numbers */
    expect(TextUtils.leftPad('Hello', 20)).toBe('               Hello');
    expect(TextUtils.leftPad('Goodbye', 5)).toBe('Goodbye');
    expect(TextUtils.leftPad('Hello, world!', 20, 'e')).toBe('eeeeeeeHello, world!');
    expect(TextUtils.leftPad('12345', 10, '0')).toBe('0000012345');
    expect(TextUtils.leftPad('CampusGuide', 10, 'too_long')).toBe('CampusGuide');

    /* eslint-enable no-magic-numbers */

  });

  it('tests that concatenating a string with ellipses returns the proper value.', () => {

    /* Magic numbers are used here for simplicity's sake */
    /* eslint-disable no-magic-numbers */
    expect(TextUtils.getTextWithEllipses('Hello', 20)).toBe('Hello');
    expect(TextUtils.getTextWithEllipses('World', 5)).toBe('World');
    expect(TextUtils.getTextWithEllipses('Canada', 4)).toBe('Ca..');
    expect(TextUtils.getTextWithEllipses('Goodbye', 100)).toBe('Goodbye');
    expect(TextUtils.getTextWithEllipses('CampusGuide', 2)).toBe('..');
    expect(TextUtils.getTextWithEllipses('ReactNative', 1)).toBe('..');

    /* eslint-enable no-magic-numbers */

  });

  it('tests getting a 24 hour adjusted time works', () => {
    for (const time in expectedTimeFormats) {
      if (expectedTimeFormats.hasOwnProperty(time)) {
        expect(TextUtils.get24HourAdjustedTime(time)).toBe(expectedTimeFormats[time]);
      }
    }
  });
});
<commit_msg>100% textutils coverage<commit_after>/**
 *
 * @license
 * Copyright (C) 2016 Joseph Roque
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @author Joseph Roque
 * @created 2016-11-1
 * @file TextUtils-test.js
 * @description Tests text formatting and adjustments.
 *
 */
'use strict';

// An example valid URL to open.
const exampleURL = 'http://google.com';
// An example valid telephone number to open.
const exampleTelephone = 'tel:1235556789';
// An example valid formatted telephone number to display.
const exampleFormattedTelephone = '(123) 555-6789';
// An example valid shortened telephone number to open.
const exampleShortTelephone = 'tel:123';
// An example valid formatted shortened telephone number to display.
const exampleShortFormattedTelephone = '123';
// An example valid email to open.
const exampleEmail = 'mailto:google@google.com';
// An example valid long email to open.
const exampleLongEmail = 'mailto:google@google.com?subject=Test';
// An example valid formatted email to display.
const exampleFormattedEmail = 'google@google.com';
// An example of an invalid URL.
const invalidURL = 'thisisnotavalidURL';

// A dictionary of time values, with their expected formatting in 24 hour adjusted time.
const expectedTimeFormats = {
  '10:20': '10:20',
  '14:08': '14:08',
  '36:11': '12:11',
  '18:45': '18:45',
  '02:50': '02:50',
  '25:19': '01:19',
  '28:30': '04:30',
};

// Require modules for testing
import * as TextUtils from '../TextUtils';

// Add the String.format function
if (!String.format) {
  String.format = function format(str: string) {
    const args = Array.prototype.slice.call(arguments, 1);
    return str.replace(/{(\d+)}/g, (match, number) => {
      return typeof args[number] == 'undefined'
        ? match
        : args[number];
    });
  };
}

describe('TextUtils-test', () => {

  it('tests converting time formats with invalid params', () => {
    const invalidTime = 'not a time';
    const invalidFormat = '00';
    expect(() => TextUtils.convertTimeFormat('12h', invalidTime)).toThrow();
    expect(() => TextUtils.convertTimeFormat(invalidFormat, '12:00')).toThrow();
  });

  it('tests converting to a 12h time format', () => {
    expect(TextUtils.convertTimeFormat('12h', '12:00 am')).toBe('12:00 am');
    expect(TextUtils.convertTimeFormat('12h', '12:00 a.m.')).toBe('12:00 am');
    expect(TextUtils.convertTimeFormat('12h', '12:00 AM')).toBe('12:00 am');
    expect(TextUtils.convertTimeFormat('12h', '12:00 A.M.')).toBe('12:00 am');
    expect(TextUtils.convertTimeFormat('12h', '00:00')).toBe('12:00 am');
    expect(TextUtils.convertTimeFormat('12h', '23:59')).toBe('11:59 pm');
    expect(TextUtils.convertTimeFormat('12h', '06:00')).toBe('6:00 am');
    expect(TextUtils.convertTimeFormat('12h', '12:00')).toBe('12:00 pm');
    expect(TextUtils.convertTimeFormat('12h', '18:00')).toBe('6:00 pm');
  });

  it('tests converting to a 24h time format', () => {
    expect(TextUtils.convertTimeFormat('24h', '23:59')).toBe('23:59');
    expect(TextUtils.convertTimeFormat('24h', '6:00 am')).toBe('06:00');
    expect(TextUtils.convertTimeFormat('24h', '11:59 pm')).toBe('23:59');
    expect(TextUtils.convertTimeFormat('24h', '11:59 p.m.')).toBe('23:59');
    expect(TextUtils.convertTimeFormat('24h', '11:59 PM')).toBe('23:59');
    expect(TextUtils.convertTimeFormat('24h', '11:59 P.M.')).toBe('23:59');
    expect(TextUtils.convertTimeFormat('24h', '12:00 am')).toBe('00:00');
    expect(TextUtils.convertTimeFormat('24h', '12:00 a.m.')).toBe('00:00');
    expect(TextUtils.convertTimeFormat('24h', '12:00 AM')).toBe('00:00');
    expect(TextUtils.convertTimeFormat('24h', '12:00 A.M.')).toBe('00:00');
  });

  it('tests that formatting a link returns the proper value.', () => {
    expect(TextUtils.formatLink(exampleURL)).toBe(exampleURL);
    expect(TextUtils.formatLink(exampleTelephone)).toBe(exampleFormattedTelephone);
    expect(TextUtils.formatLink(exampleShortTelephone)).toBe(exampleShortFormattedTelephone);
    expect(TextUtils.formatLink(exampleEmail)).toBe(exampleFormattedEmail);
    expect(TextUtils.formatLink(exampleLongEmail)).toBe(exampleFormattedEmail);
    expect(TextUtils.formatLink(invalidURL)).toBe(invalidURL);
    expect(TextUtils.formatLink('')).toBe('');
    expect(TextUtils.formatLink(null)).toBe('');
  });

  it('tests that left padding a string returns the proper value.', () => {

    /* Magic numbers are used here for simplicity's sake */
    /* eslint-disable no-magic-numbers */
    expect(TextUtils.leftPad('Hello', 20)).toBe('               Hello');
    expect(TextUtils.leftPad('Goodbye', 5)).toBe('Goodbye');
    expect(TextUtils.leftPad('Hello, world!', 20, 'e')).toBe('eeeeeeeHello, world!');
    expect(TextUtils.leftPad('12345', 10, '0')).toBe('0000012345');
    expect(TextUtils.leftPad('CampusGuide', 10, 'too_long')).toBe('CampusGuide');

    /* eslint-enable no-magic-numbers */

  });

  it('tests that concatenating a string with ellipses returns the proper value.', () => {

    /* Magic numbers are used here for simplicity's sake */
    /* eslint-disable no-magic-numbers */
    expect(TextUtils.getTextWithEllipses('Hello', 20)).toBe('Hello');
    expect(TextUtils.getTextWithEllipses('World', 5)).toBe('World');
    expect(TextUtils.getTextWithEllipses('Canada', 4)).toBe('Ca..');
    expect(TextUtils.getTextWithEllipses('Goodbye', 100)).toBe('Goodbye');
    expect(TextUtils.getTextWithEllipses('CampusGuide', 2)).toBe('..');
    expect(TextUtils.getTextWithEllipses('ReactNative', 1)).toBe('..');

    /* eslint-enable no-magic-numbers */

  });

  it('tests getting a 24 hour adjusted time works', () => {
    for (const time in expectedTimeFormats) {
      if (expectedTimeFormats.hasOwnProperty(time)) {
        expect(TextUtils.get24HourAdjustedTime(time)).toBe(expectedTimeFormats[time]);
      }
    }
  });
});
<|endoftext|>


<commit_before>// TODO I suppose my attitude is to use an event emitter very sparingly, because
// I want to use one to emit Kibitzer events, the paxos log, but I don't want to
// create an interface that looks like one of the EventEmitter heavy interfaces.
//
// One of the challenges here is that events are going to flow immediately,
// unless I do something compliacated like only emit events when there is a
// listener. Having looked at that, it is not actually that complicated.
//
// Now we can fuss about some terminate logic.
//
// Put it out again, I prefer to program with error-first callbacks, but events
// happen. They are generally a way in which information enters the system. They
// are synchronous. Here is more information. Generally, they shouldn't block.
//
// That's how I use EventEmitters, but I'm not in for a penny, in for a pound.
// If there is a source of events, I treat that as a stream of events. Not in
// the Node.js streams sense, but I create an event emitter that emits a
// homogenous series of events terminated by a specific termination event.
//
// Then I stop. I don't go on to implement multi-interfaces that could take an
// error-first callback, or maybe register an event handler, or maybe use some
// other form of asynchronous notification.
//
// TODO Which is why I've made the event emitter in this class a separate
// object. Which is such a good idea, I belive I'll go and do it Happenstance.
var assert = require('assert')
var events = require('events')

var slice = [].slice

var Vestibule = require('vestibule')
var cadence = require('cadence')

var Reactor = require('reactor')
var Legislator = require('paxos/legislator')
var Islander = require('islander')
var Monotonic = require('monotonic').asString

var Scheduler = require('happenstance')

var interrupt = require('interrupt').createInterrupter('bigeasy.kibitz')
var abend = require('abend')

var logger = require('prolific.logger').createLogger('bigasy.kibitz.kibitzer')

function Kibitzer (islandId, id, options) {
    assert(id != null, 'id is required')

    options || (options = {})

    options.ping || (options.ping = 250)
    options.timeout || (options.timeout = 1000)

    this._pulser = new Reactor({ object: this, method: '_pulse' })
    this._publisher = new Reactor({ object: this, method: '_publish' })

    this._Date = options.Date || Date
    this.scheduler = new Scheduler({ Date: options.Date || Date })

    this.properties = options.properties
    this._ua = options.ua
    this.id = id

    this.legislator = new Legislator(islandId, id, this._Date.now(), {
        ping: options.ping,
        timeout: options.timeout,
        scheduler: {
            Date: options.Date || Date,
            timerless: options.timerless || false
        }
    })
// TODO Scheduler is not shutting down, so maybe `unref` for now.
    this.legislator.scheduler.on('timeout', this._send.bind(this))

    this.islander = new Islander(this.legislator.id)

    this.iterators = {
        legislator: this.legislator.log.min(),
        islander: { dummy: true }
    }

    // TODO Very much crufty.
    this.log = new events.EventEmitter
    this.log.on('newListener', this._newLogListener.bind(this))

    this._advanced = new Vestibule
    this._terminated = false
}

function Replay (kibitzer, parent) {
    this.kibitzer = kibitzer
    this.parent = parent
    this.recorded = []
    this.kibitzer.legislator._trace = function (method, vargs) {
        this.recorded.push({ method: method, vargs: JSON.parse(JSON.stringify(vargs)) })
    }.bind(this)
}

Replay.prototype.replay = function (entry) {
    if (entry.context == 'bigeasy.paxos') {
        if (this.recorded.length) {
            assert.deepEqual(this.recorded.shift(), {
                method: entry.name,
                vargs: entry.specific.vargs
            })
        } else {
            var legislator = this.kibitzer.legislator
            legislator[entry.name].apply(legislator, entry.specific.vargs)
            this.kibitzer._advanced.notify()
            this.replay(entry)
        }
    }
// TODO Return parent at some point.
    return this
}

Kibitzer.prototype.createReplay = function (parent) {
    var replay = new Replay(this, parent)
    this._prime(abend)
    return replay
}

Kibitzer.prototype.terminate = function () {
    if (!this._terminated) {
        this._terminated = true
        this.scheduler.shutdown()
        this.legislator.scheduler.shutdown()
        this._advanced.notify()
    }
}

Kibitzer.prototype._newLogListener = function () {
    setImmediate(this._advanced.notify.bind(this._advanced))
}

Kibitzer.prototype._logger = function (level, message, context) {
    logger[level](message, context)
}

Kibitzer.prototype._publish = cadence(function (async) {
    var outgoing = this.islander.outbox()
    if (outgoing.length == 0) {
        return
    }
    var post
    async(function () {
        var properties = this.legislator.properties[this.legislator.government.majority[0]]
        this._ua.send(properties, post = {
            islandId: this.legislator.islandId,
            type: 'enqueue',
            entries: outgoing
        }, async())
    }, function (body) {
        this._logger('info', 'enqueued', {
            kibitzerId: this.legislator.id,
            sent: post,
            received: body
        })
        if (body == null) {
            body = { entries: [] }
        }
        if (body) {
            this.islander.published(body.entries)
        }
        var delay = this._Date.now() + (body.entries == 0 ? 1000 : 0)
        this.scheduler.schedule(delay, 'publish', { object: this, method: '_checkPublisher' })
    })
})

// TODO Wierd but scheduler calls us with a timing, so that is interpreted as
// the callback to `check`. Could spend all day trying to decide if that's a
// correct use of `Operation`.
Kibitzer.prototype._checkPublisher = function () {
    this._publisher.check()
}

// TODO Annoying how difficult it is to stop this crazy thing. There are going
// to be race conditions where we have a termination, come in, we shut things
// down, but then we continue with processing a pulse which triggers a timer.
// Sending messages to legislator can restart it's scheduler.
//
// TODO We could kill the timer in the scheduler, set the boolean we added to
// tell it to no longer schedule.
Kibitzer.prototype._pulse = cadence(function (async, timeout, pulse) {
    var responses = {}
    async(function () {
        async.forEach(function (id) {
            async(function () {
                if (id == this.legislator.id) {
                    this._receive(pulse, async())
                } else {
                    var properties = this.legislator.properties[id]
                    this._ua.send(properties, { type: 'receive', pulse: pulse }, async())
                }
            }, function (response) {
                this._logger('info', 'published', {
                    kibitzerId: this.legislator.id,
                    sent: pulse,
                    received: response
                })
                responses[id] = response
            })
        })(pulse.route)
    }, function () {
        this.legislator.sent(this._Date.now(), pulse, responses)
    }, function () {
        if (this.iterators.legislator.next != null) {
            this._advanced.notify()
        }
    }, function () {
        this._send()
    })
})

Kibitzer.prototype._send = function () {
    var now = this._Date.now()
    for (;;) {
        var outbox = this.legislator.synchronize(now)
        if (outbox.length == 0) {
            var consensus = this.legislator.consensus(now)
            if (consensus != null) {
                outbox.push(consensus)
            }
        }
        if (outbox.length == 0) {
            break
        }
        outbox.forEach(function (pulse) {
            this._pulser.push(pulse)
        }, this)
    }
}

Kibitzer.prototype.getProperties = function () {
    var properties = []
    for (var key in this.legislator.properties) {
        properties.push(this.legislator.properties[key])
    }
    return properties
}

Kibitzer.prototype._prime = cadence(function (async) {
    async(function () {
        var loop = async(function () {
            async(function () {
                this._advanced.enter(async())
            }, function () {
                if (this.iterators.legislator.next != null) {
                    this.iterators.legislator = this.iterators.legislator.next
                    this.iterators.islander = {
                        next: this.islander.prime(this.iterators.legislator)
                    }
                    return [ loop.break ]
                }
            })
        })()
    }, function () {
        this._advance(abend)
    })
})

Kibitzer.prototype.bootstrap = cadence(function (async) {
    this._logger('info', 'bootstrap', { kibitzerId: this.legislator.id })
    this._prime(async())
    this.legislator.bootstrap(this._Date.now(), this.properties)
    this._send()
})

// TODO Use Isochronous to repeatedly send join message.
Kibitzer.prototype.join = cadence(function (async, properties) {
    assert(typeof properties == 'object')
    this._prime(async())
    this.scheduler.schedule(this._Date.now() + 0, 'join', { object: this, method: '_checkJoin' }, properties)
})

Kibitzer.prototype._checkJoin = function (when, properties) {
    this._join(properties, abend)
}

Kibitzer.prototype._join = cadence(function (async, properties) {
    async(function () {
        this._logger('info', 'join', {
            kibitzerId: this.legislator.id,
            received: JSON.stringify(properties)
        })
        this._ua.send(properties, {
            type: 'immigrate',
            islandId: this.legislator.islandId,
            id: this.legislator.id,
            cookie: this.legislator.cookie,
            properties: this.properties,
            hops: 0
        }, async())
    }, function (response) {
        if (response == null || !response.enqueued) {
            var delay = this._Date.now() + 1000
            this.scheduler.schedule(delay, 'join', { object: this, method: '_checkJoin' }, properties)
        }
    })
})

Kibitzer.prototype.dispatch = cadence(function (async, body) {
    switch (body.type) {
    case 'immigrate':
        this._naturalize(body, async())
        break
    case 'receive':
        this._receive(body.pulse, async())
        break
    case 'enqueue':
        this._enqueue(body, async())
        break
    }
})

Kibitzer.prototype.publish = function (entry) {
    var cookie = this.islander.publish(entry, false)
    if (this.scheduler.scheduled('publish') == null) {
        this.scheduler.schedule(0, 'publish', { object: this, method: '_checkPublisher' })
    }
    return cookie
}

Kibitzer.prototype._naturalize = cadence(function (async, post) {
    assert(post.hops != null)
    var outcome = this.legislator.immigrate(this._Date.now(), post.islandId, post.id, post.cookie, post.properties)
    this._logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        received: JSON.stringify(post),
        outcome: JSON.stringify(outcome)
    })
    if (!outcome.enqueued && outcome.leader != null && post.hops == 0) {
        var properties = this.legislator.citizens[this.legislator.government.majority[0]]
        post.hops++
        this._ua.send(properties, post, async())
    } else {
        this._send()
        return [ outcome ]
    }
})

Kibitzer.prototype._enqueue = cadence(function (async, post) {
    var response = { posted: false, entries: [] }
// TODO Redirect enqueue or wait for stability and retry.
    for (var i = 0, I = post.entries.length; i < I; i++) {
        var entry = post.entries[i]
        var outcome = this.legislator.enqueue(this._Date.now(), post.islandId, entry)
        if (!outcome.enqueued) {
            response.entries.length = 0
            break
        }
        response.entries.push({ cookie: entry.cookie, promise: outcome.promise })
    }
    this._send()
    this._logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        received: JSON.stringify(post),
        sent: JSON.stringify(response)
    })
    return response
})

Kibitzer.prototype._receive = cadence(function (async, pulse) {
    this._logger('info', 'receive', {
        kibitzerId: this.legislator.id,
        received: pulse
    })
    var ret = [ this.legislator.receive(this._Date.now(), pulse, pulse.messages) ]
    if (this.iterators.legislator.next != null) {
        this._advanced.notify()
    }
    this._send()
    return ret
})

Kibitzer.prototype._advance = cadence(function (async) {
    var loop = async(function () {
        if (this._terminated) {
            this.log.emit('terminated')
            return [ loop.break ]
        }
        while (this.iterators.legislator.next != null) {
            this.iterators.legislator = this.iterators.legislator.next
            this._logger('info', 'consuming', {
                kibitzerId: this.legislator.id,
                route: this.iterators.legislator.route,
                promise: this.iterators.legislator.promise,
                value: this.iterators.legislator.value
            })
        }
        if (this.log.listenerCount('entry') == 0 || this.iterators.islander.next == null) {
            this._advanced.enter(async())
        } else {
// TODO Think hard about how messy this has become.
// TODO Maybe header and body and the header is used internallyish? Islander would use it.
            var entry = this.iterators.islander = this.iterators.islander.next
            var government = Monotonic.isBoundary(entry.promise, 0)
            var value = government ? entry.value : entry.value.value
            this.log.emit('entry', {
                government: government,
                promise: entry.promise,
                value: value
            })
        }
    })()
})

module.exports = Kibitzer
<commit_msg>Replay within Kibitzer.<commit_after>// TODO I suppose my attitude is to use an event emitter very sparingly, because
// I want to use one to emit Kibitzer events, the paxos log, but I don't want to
// create an interface that looks like one of the EventEmitter heavy interfaces.
//
// One of the challenges here is that events are going to flow immediately,
// unless I do something compliacated like only emit events when there is a
// listener. Having looked at that, it is not actually that complicated.
//
// Now we can fuss about some terminate logic.
//
// Put it out again, I prefer to program with error-first callbacks, but events
// happen. They are generally a way in which information enters the system. They
// are synchronous. Here is more information. Generally, they shouldn't block.
//
// That's how I use EventEmitters, but I'm not in for a penny, in for a pound.
// If there is a source of events, I treat that as a stream of events. Not in
// the Node.js streams sense, but I create an event emitter that emits a
// homogenous series of events terminated by a specific termination event.
//
// Then I stop. I don't go on to implement multi-interfaces that could take an
// error-first callback, or maybe register an event handler, or maybe use some
// other form of asynchronous notification.
//
// TODO Which is why I've made the event emitter in this class a separate
// object. Which is such a good idea, I belive I'll go and do it Happenstance.
var assert = require('assert')
var events = require('events')

var slice = [].slice

var Vestibule = require('vestibule')
var cadence = require('cadence')

var Reactor = require('reactor')
var Legislator = require('paxos/legislator')
var Islander = require('islander')
var Monotonic = require('monotonic').asString

var Scheduler = require('happenstance')

var interrupt = require('interrupt').createInterrupter('bigeasy.kibitz')
var abend = require('abend')

var logger = require('prolific.logger').createLogger('bigasy.kibitz.kibitzer')

function Kibitzer (islandId, id, options) {
    assert(id != null, 'id is required')

    options || (options = {})

    options.ping || (options.ping = 250)
    options.timeout || (options.timeout = 1000)

    this._pulser = new Reactor({ object: this, method: '_pulse' })
    this._publisher = new Reactor({ object: this, method: '_publish' })

    this._Date = options.Date || Date
    this.scheduler = new Scheduler({ Date: options.Date || Date })

    this.properties = options.properties
    this._ua = options.ua
    this.id = id

    this.legislator = new Legislator(islandId, id, this._Date.now(), {
        ping: options.ping,
        timeout: options.timeout,
        scheduler: {
            Date: options.Date || Date,
            timerless: options.timerless || false
        }
    })
// TODO Scheduler is not shutting down, so maybe `unref` for now.
    this.legislator.scheduler.on('timeout', this._send.bind(this))

    this.islander = new Islander(this.legislator.id)

    this.iterators = {
        legislator: this.legislator.log.min(),
        islander: { dummy: true }
    }

    // TODO Very much crufty.
    this.log = new events.EventEmitter
    this.log.on('newListener', this._newLogListener.bind(this))

    this._advanced = new Vestibule
    this._terminated = false
    this._recorded = null
}

Kibitzer.prototype.play = function (entry) {
    if (entry.context == 'bigeasy.paxos') {
        if (this._recorded.length) {
            assert.deepEqual(this._recorded.shift(), {
                method: entry.name,
                vargs: entry.specific.vargs
            })
        } else {
            this.legislator[entry.name].apply(this.legislator, entry.specific.vargs)
            this._advanced.notify()
            this.play(entry)
        }
    }
}

Kibitzer.prototype.replay = function () {
    this._recorded = []
    this.legislator._trace = function (method, vargs) {
        this._recorded.push({ method: method, vargs: JSON.parse(JSON.stringify(vargs)) })
    }.bind(this)
    this._prime(abend)
}

Kibitzer.prototype.terminate = function () {
    if (!this._terminated) {
        this._terminated = true
        this.scheduler.shutdown()
        this.legislator.scheduler.shutdown()
        this._advanced.notify()
    }
}

Kibitzer.prototype._newLogListener = function () {
    setImmediate(this._advanced.notify.bind(this._advanced))
}

Kibitzer.prototype._logger = function (level, message, context) {
    logger[level](message, context)
}

Kibitzer.prototype._publish = cadence(function (async) {
    var outgoing = this.islander.outbox()
    if (outgoing.length == 0) {
        return
    }
    var post
    async(function () {
        var properties = this.legislator.properties[this.legislator.government.majority[0]]
        this._ua.send(properties, post = {
            islandId: this.legislator.islandId,
            type: 'enqueue',
            entries: outgoing
        }, async())
    }, function (body) {
        this._logger('info', 'enqueued', {
            kibitzerId: this.legislator.id,
            sent: post,
            received: body
        })
        if (body == null) {
            body = { entries: [] }
        }
        if (body) {
            this.islander.published(body.entries)
        }
        var delay = this._Date.now() + (body.entries == 0 ? 1000 : 0)
        this.scheduler.schedule(delay, 'publish', { object: this, method: '_checkPublisher' })
    })
})

// TODO Wierd but scheduler calls us with a timing, so that is interpreted as
// the callback to `check`. Could spend all day trying to decide if that's a
// correct use of `Operation`.
Kibitzer.prototype._checkPublisher = function () {
    this._publisher.check()
}

// TODO Annoying how difficult it is to stop this crazy thing. There are going
// to be race conditions where we have a termination, come in, we shut things
// down, but then we continue with processing a pulse which triggers a timer.
// Sending messages to legislator can restart it's scheduler.
//
// TODO We could kill the timer in the scheduler, set the boolean we added to
// tell it to no longer schedule.
Kibitzer.prototype._pulse = cadence(function (async, timeout, pulse) {
    var responses = {}
    async(function () {
        async.forEach(function (id) {
            async(function () {
                if (id == this.legislator.id) {
                    this._receive(pulse, async())
                } else {
                    var properties = this.legislator.properties[id]
                    this._ua.send(properties, { type: 'receive', pulse: pulse }, async())
                }
            }, function (response) {
                this._logger('info', 'published', {
                    kibitzerId: this.legislator.id,
                    sent: pulse,
                    received: response
                })
                responses[id] = response
            })
        })(pulse.route)
    }, function () {
        this.legislator.sent(this._Date.now(), pulse, responses)
    }, function () {
        if (this.iterators.legislator.next != null) {
            this._advanced.notify()
        }
    }, function () {
        this._send()
    })
})

Kibitzer.prototype._send = function () {
    var now = this._Date.now()
    for (;;) {
        var outbox = this.legislator.synchronize(now)
        if (outbox.length == 0) {
            var consensus = this.legislator.consensus(now)
            if (consensus != null) {
                outbox.push(consensus)
            }
        }
        if (outbox.length == 0) {
            break
        }
        outbox.forEach(function (pulse) {
            this._pulser.push(pulse)
        }, this)
    }
}

Kibitzer.prototype.getProperties = function () {
    var properties = []
    for (var key in this.legislator.properties) {
        properties.push(this.legislator.properties[key])
    }
    return properties
}

Kibitzer.prototype._prime = cadence(function (async) {
    async(function () {
        var loop = async(function () {
            async(function () {
                this._advanced.enter(async())
            }, function () {
                if (this.iterators.legislator.next != null) {
                    this.iterators.legislator = this.iterators.legislator.next
                    this.iterators.islander = {
                        next: this.islander.prime(this.iterators.legislator)
                    }
                    return [ loop.break ]
                }
            })
        })()
    }, function () {
        this._advance(abend)
    })
})

Kibitzer.prototype.bootstrap = cadence(function (async) {
    this._logger('info', 'bootstrap', { kibitzerId: this.legislator.id })
    this._prime(async())
    this.legislator.bootstrap(this._Date.now(), this.properties)
    this._send()
})

// TODO Use Isochronous to repeatedly send join message.
Kibitzer.prototype.join = cadence(function (async, properties) {
    assert(typeof properties == 'object')
    this._prime(async())
    this.scheduler.schedule(this._Date.now() + 0, 'join', { object: this, method: '_checkJoin' }, properties)
})

Kibitzer.prototype._checkJoin = function (when, properties) {
    this._join(properties, abend)
}

Kibitzer.prototype._join = cadence(function (async, properties) {
    async(function () {
        this._logger('info', 'join', {
            kibitzerId: this.legislator.id,
            received: JSON.stringify(properties)
        })
        this._ua.send(properties, {
            type: 'immigrate',
            islandId: this.legislator.islandId,
            id: this.legislator.id,
            cookie: this.legislator.cookie,
            properties: this.properties,
            hops: 0
        }, async())
    }, function (response) {
        if (response == null || !response.enqueued) {
            var delay = this._Date.now() + 1000
            this.scheduler.schedule(delay, 'join', { object: this, method: '_checkJoin' }, properties)
        }
    })
})

Kibitzer.prototype.dispatch = cadence(function (async, body) {
    switch (body.type) {
    case 'immigrate':
        this._naturalize(body, async())
        break
    case 'receive':
        this._receive(body.pulse, async())
        break
    case 'enqueue':
        this._enqueue(body, async())
        break
    }
})

Kibitzer.prototype.publish = function (entry) {
    var cookie = this.islander.publish(entry, false)
    if (this.scheduler.scheduled('publish') == null) {
        this.scheduler.schedule(0, 'publish', { object: this, method: '_checkPublisher' })
    }
    return cookie
}

Kibitzer.prototype._naturalize = cadence(function (async, post) {
    assert(post.hops != null)
    var outcome = this.legislator.immigrate(this._Date.now(), post.islandId, post.id, post.cookie, post.properties)
    this._logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        received: JSON.stringify(post),
        outcome: JSON.stringify(outcome)
    })
    if (!outcome.enqueued && outcome.leader != null && post.hops == 0) {
        var properties = this.legislator.citizens[this.legislator.government.majority[0]]
        post.hops++
        this._ua.send(properties, post, async())
    } else {
        this._send()
        return [ outcome ]
    }
})

Kibitzer.prototype._enqueue = cadence(function (async, post) {
    var response = { posted: false, entries: [] }
// TODO Redirect enqueue or wait for stability and retry.
    for (var i = 0, I = post.entries.length; i < I; i++) {
        var entry = post.entries[i]
        var outcome = this.legislator.enqueue(this._Date.now(), post.islandId, entry)
        if (!outcome.enqueued) {
            response.entries.length = 0
            break
        }
        response.entries.push({ cookie: entry.cookie, promise: outcome.promise })
    }
    this._send()
    this._logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        received: JSON.stringify(post),
        sent: JSON.stringify(response)
    })
    return response
})

Kibitzer.prototype._receive = cadence(function (async, pulse) {
    this._logger('info', 'receive', {
        kibitzerId: this.legislator.id,
        received: pulse
    })
    var ret = [ this.legislator.receive(this._Date.now(), pulse, pulse.messages) ]
    if (this.iterators.legislator.next != null) {
        this._advanced.notify()
    }
    this._send()
    return ret
})

Kibitzer.prototype._advance = cadence(function (async) {
    var loop = async(function () {
        if (this._terminated) {
            this.log.emit('terminated')
            return [ loop.break ]
        }
        while (this.iterators.legislator.next != null) {
            this.iterators.legislator = this.iterators.legislator.next
            this._logger('info', 'consuming', {
                kibitzerId: this.legislator.id,
                route: this.iterators.legislator.route,
                promise: this.iterators.legislator.promise,
                value: this.iterators.legislator.value
            })
        }
        if (this.log.listenerCount('entry') == 0 || this.iterators.islander.next == null) {
            this._advanced.enter(async())
        } else {
// TODO Think hard about how messy this has become.
// TODO Maybe header and body and the header is used internallyish? Islander would use it.
            var entry = this.iterators.islander = this.iterators.islander.next
            var government = Monotonic.isBoundary(entry.promise, 0)
            var value = government ? entry.value : entry.value.value
            this.log.emit('entry', {
                government: government,
                promise: entry.promise,
                value: value
            })
        }
    })()
})

module.exports = Kibitzer
<|endoftext|>


<commit_before>/* @flow */
/**
 *  Copyright (c) 2016, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 */

import {
  getNamedType,
  GraphQLScalarType,
  GraphQLEnumType,
  GraphQLInputObjectType,
  GraphQLInterfaceType,
  GraphQLObjectType,
  GraphQLUnionType,
  getNullableType,
} from '../type/definition';

import type { GraphQLNamedType, GraphQLFieldMap } from '../type/definition';

import { GraphQLSchema } from '../type/schema';

export const BreakingChangeType = {
  FIELD_CHANGED_KIND: 'FIELD_CHANGED_KIND',
  FIELD_REMOVED: 'FIELD_REMOVED',
  TYPE_CHANGED_KIND: 'TYPE_CHANGED_KIND',
  TYPE_REMOVED: 'TYPE_REMOVED',
  TYPE_REMOVED_FROM_UNION: 'TYPE_REMOVED_FROM_UNION',
  VALUE_REMOVED_FROM_ENUM: 'VALUE_REMOVED_FROM_ENUM',
  ARG_REMOVED: 'ARG_REMOVED',
  ARG_CHANGED_KIND: 'ARG_CHANGED_KIND',
};

export const DangerousChangeType = {
  ARG_DEFAULT_VALUE_CHANGE: 'ARG_DEFAULT_VALUE_CHANGE',
};

export type BreakingChange = {
  type: $Keys<typeof BreakingChangeType>;
  description: string;
};

export type DangerousChange = {
  type: $Keys<typeof DangerousChangeType>;
  description: string;
};

/**
 * Given two schemas, returns an Array containing descriptions of all the types
 * of breaking changes covered by the other functions down below.
 */
export function findBreakingChanges(
  oldSchema: GraphQLSchema,
  newSchema: GraphQLSchema
): Array<BreakingChange> {
  return [
    ...findRemovedTypes(oldSchema, newSchema),
    ...findTypesThatChangedKind(oldSchema, newSchema),
    ...findFieldsThatChangedType(oldSchema, newSchema),
    ...findTypesRemovedFromUnions(oldSchema, newSchema),
    ...findValuesRemovedFromEnums(oldSchema, newSchema),
    ...findArgChanges(oldSchema, newSchema).breakingChanges,
  ];
}

/**
 * Given two schemas, returns an Array containing descriptions of all the types
 * of potentially dangerous changes covered by the other functions down below.
 */
export function findDangerousChanges(
  oldSchema: GraphQLSchema,
  newSchema: GraphQLSchema
): Array<DangerousChange> {
  return [
    ...findArgChanges(oldSchema, newSchema).dangerousChanges,
  ];
}

/**
 * Given two schemas, returns an Array containing descriptions of any breaking
 * changes in the newSchema related to removing an entire type.
 */
export function findRemovedTypes(
  oldSchema: GraphQLSchema,
  newSchema: GraphQLSchema
): Array<BreakingChange> {
  const oldTypeMap = oldSchema.getTypeMap();
  const newTypeMap = newSchema.getTypeMap();

  const breakingChanges = [];
  Object.keys(oldTypeMap).forEach(typeName => {
    if (!newTypeMap[typeName]) {
      breakingChanges.push({
        type: BreakingChangeType.TYPE_REMOVED,
        description: `${typeName} was removed.`,
      });
    }
  });
  return breakingChanges;
}

/**
 * Given two schemas, returns an Array containing descriptions of any breaking
 * changes in the newSchema related to changing the type of a type.
 */
export function findTypesThatChangedKind(
 oldSchema: GraphQLSchema,
 newSchema: GraphQLSchema
): Array<BreakingChange> {
  const oldTypeMap = oldSchema.getTypeMap();
  const newTypeMap = newSchema.getTypeMap();

  const breakingChanges = [];
  Object.keys(oldTypeMap).forEach(typeName => {
    if (!newTypeMap[typeName]) {
      return;
    }
    const oldType = oldTypeMap[typeName];
    const newType = newTypeMap[typeName];
    if (!(oldType instanceof newType.constructor)) {
      breakingChanges.push({
        type: BreakingChangeType.TYPE_CHANGED_KIND,
        description: `${typeName} changed from ` +
          `${typeKindName(oldType)} to ${typeKindName(newType)}.`
      });
    }
  });
  return breakingChanges;
}

/**
 * Given two schemas, returns an Array containing descriptions of any breaking
 * changes in the newSchema related to arguments (such as removal or change
 * of type of an argument, or a change in an argument's default value).
 */
export function findArgChanges(
 oldSchema: GraphQLSchema,
 newSchema: GraphQLSchema
): {
  breakingChanges: Array<BreakingChange>,
  dangerousChanges: Array<DangerousChange>
} {
  const oldTypeMap = oldSchema.getTypeMap();
  const newTypeMap = newSchema.getTypeMap();

  const breakingChanges = [];
  const dangerousChanges = [];

  Object.keys(oldTypeMap).forEach(typeName => {
    const oldType = oldTypeMap[typeName];
    const newType = newTypeMap[typeName];
    if (
      !(oldType instanceof GraphQLObjectType) ||
      !(newType instanceof oldType.constructor)
    ) {
      return;
    }

    const oldTypeFields: GraphQLFieldMap<*, *> = oldType.getFields();
    const newTypeFields: GraphQLFieldMap<*, *> = newType.getFields();

    Object.keys(oldTypeFields).forEach(fieldName => {
      if (!newTypeFields[fieldName]) {
        return;
      }

      oldTypeFields[fieldName].args.forEach(oldArgDef => {
        const newArgs = newTypeFields[fieldName].args;
        const newTypeArgIndex = newArgs.findIndex(
          arg => arg.name === oldArgDef.name
        );
        const newArgDef = newArgs[newTypeArgIndex];

        // Arg not present
        if (newTypeArgIndex < 0) {
          breakingChanges.push({
            type: BreakingChangeType.ARG_REMOVED,
            description: `${oldType.name}.${fieldName} arg ` +
              `${oldArgDef.name} was removed`,
          });

        // Arg changed type in a breaking way
        } else if (
          oldArgDef.type !== newArgDef.type &&
          getNullableType(oldArgDef.type) !== newArgDef.type
        ) {
          breakingChanges.push({
            type: BreakingChangeType.ARG_CHANGED_KIND,
            description: `${oldType.name}.${fieldName} arg ` +
              `${oldArgDef.name} has changed type from ` +
              `${oldArgDef.type.toString()} to ${newArgDef.type.toString()}`,
          });

        // Arg default value has changed
        } else if (oldArgDef.defaultValue !== undefined &&
          oldArgDef.defaultValue !== newArgDef.defaultValue) {
          dangerousChanges.push({
            type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
            description: `${oldType.name}.${fieldName} arg ${oldArgDef.name} ` +
              'has changed defaultValue',
          });
        }
      });
    });
  });

  return {
    breakingChanges,
    dangerousChanges,
  };
}

function typeKindName(type: GraphQLNamedType): string {
  if (type instanceof GraphQLScalarType) {
    return 'a Scalar type';
  }
  if (type instanceof GraphQLObjectType) {
    return 'an Object type';
  }
  if (type instanceof GraphQLInterfaceType) {
    return 'an Interface type';
  }
  if (type instanceof GraphQLUnionType) {
    return 'a Union type';
  }
  if (type instanceof GraphQLEnumType) {
    return 'an Enum type';
  }
  if (type instanceof GraphQLInputObjectType) {
    return 'an Input type';
  }
  throw new TypeError('Unknown type ' + type.constructor.name);
}

/**
 * Given two schemas, returns an Array containing descriptions of any breaking
 * changes in the newSchema related to the fields on a type. This includes if
 * a field has been removed from a type or if a field has changed type.
 */
export function findFieldsThatChangedType(
  oldSchema: GraphQLSchema,
  newSchema: GraphQLSchema
): Array<BreakingChange> {
  const oldTypeMap = oldSchema.getTypeMap();
  const newTypeMap = newSchema.getTypeMap();

  const breakingFieldChanges = [];
  Object.keys(oldTypeMap).forEach(typeName => {
    const oldType = oldTypeMap[typeName];
    const newType = newTypeMap[typeName];
    if (
      !(oldType instanceof GraphQLObjectType ||
        oldType instanceof GraphQLInterfaceType ||
        oldType instanceof GraphQLInputObjectType) ||
      !(newType instanceof oldType.constructor)
    ) {
      return;
    }

    const oldTypeFieldsDef = oldType.getFields();
    const newTypeFieldsDef = newType.getFields();
    Object.keys(oldTypeFieldsDef).forEach(fieldName => {
      // Check if the field is missing on the type in the new schema.
      if (!(fieldName in newTypeFieldsDef)) {
        breakingFieldChanges.push({
          type: BreakingChangeType.FIELD_REMOVED,
          description: `${typeName}.${fieldName} was removed.`,
        });
      } else {
        // Check if the field's type has changed in the new schema.
        const oldFieldType = getNamedType(oldTypeFieldsDef[fieldName].type);
        const newFieldType = getNamedType(newTypeFieldsDef[fieldName].type);
        if (oldFieldType &&
            newFieldType &&
            oldFieldType.name !== newFieldType.name) {
          breakingFieldChanges.push({
            type: BreakingChangeType.FIELD_CHANGED_KIND,
            description: `${typeName}.${fieldName} changed type from ` +
              `${oldFieldType.name} to ${newFieldType.name}.`,
          });
        }
      }
    });
  });
  return breakingFieldChanges;
}

/**
 * Given two schemas, returns an Array containing descriptions of any breaking
 * changes in the newSchema related to removing types from a union type.
 */
export function findTypesRemovedFromUnions(
  oldSchema: GraphQLSchema,
  newSchema: GraphQLSchema
): Array<BreakingChange> {
  const oldTypeMap = oldSchema.getTypeMap();
  const newTypeMap = newSchema.getTypeMap();

  const typesRemovedFromUnion = [];
  Object.keys(oldTypeMap).forEach(typeName => {
    const oldType = oldTypeMap[typeName];
    const newType = newTypeMap[typeName];
    if (!(oldType instanceof GraphQLUnionType) ||
        !(newType instanceof GraphQLUnionType)) {
      return;
    }
    const typeNamesInNewUnion = Object.create(null);
    newType.getTypes().forEach(type => {
      typeNamesInNewUnion[type.name] = true;
    });
    oldType.getTypes().forEach(type => {
      if (!typeNamesInNewUnion[type.name]) {
        typesRemovedFromUnion.push({
          type: BreakingChangeType.TYPE_REMOVED_FROM_UNION,
          description: `${type.name} was removed from union type ${typeName}.`
        });
      }
    });
  });
  return typesRemovedFromUnion;
}

/**
 * Given two schemas, returns an Array containing descriptions of any breaking
 * changes in the newSchema related to removing values from an enum type.
 */
export function findValuesRemovedFromEnums(
  oldSchema: GraphQLSchema,
  newSchema: GraphQLSchema
): Array<BreakingChange> {
  const oldTypeMap = oldSchema.getTypeMap();
  const newTypeMap = newSchema.getTypeMap();

  const valuesRemovedFromEnums = [];
  Object.keys(oldTypeMap).forEach(typeName => {
    const oldType = oldTypeMap[typeName];
    const newType = newTypeMap[typeName];
    if (!(oldType instanceof GraphQLEnumType) ||
        !(newType instanceof GraphQLEnumType)) {
      return;
    }
    const valuesInNewEnum = Object.create(null);
    newType.getValues().forEach(value => {
      valuesInNewEnum[value.name] = true;
    });
    oldType.getValues().forEach(value => {
      if (!valuesInNewEnum[value.name]) {
        valuesRemovedFromEnums.push({
          type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM,
          description: `${value.name} was removed from enum type ${typeName}.`
        });
      }
    });
  });
  return valuesRemovedFromEnums;
}
<commit_msg>Correct a code comment<commit_after>/* @flow */
/**
 *  Copyright (c) 2016, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 */

import {
  getNamedType,
  GraphQLScalarType,
  GraphQLEnumType,
  GraphQLInputObjectType,
  GraphQLInterfaceType,
  GraphQLObjectType,
  GraphQLUnionType,
  getNullableType,
} from '../type/definition';

import type { GraphQLNamedType, GraphQLFieldMap } from '../type/definition';

import { GraphQLSchema } from '../type/schema';

export const BreakingChangeType = {
  FIELD_CHANGED_KIND: 'FIELD_CHANGED_KIND',
  FIELD_REMOVED: 'FIELD_REMOVED',
  TYPE_CHANGED_KIND: 'TYPE_CHANGED_KIND',
  TYPE_REMOVED: 'TYPE_REMOVED',
  TYPE_REMOVED_FROM_UNION: 'TYPE_REMOVED_FROM_UNION',
  VALUE_REMOVED_FROM_ENUM: 'VALUE_REMOVED_FROM_ENUM',
  ARG_REMOVED: 'ARG_REMOVED',
  ARG_CHANGED_KIND: 'ARG_CHANGED_KIND',
};

export const DangerousChangeType = {
  ARG_DEFAULT_VALUE_CHANGE: 'ARG_DEFAULT_VALUE_CHANGE',
};

export type BreakingChange = {
  type: $Keys<typeof BreakingChangeType>;
  description: string;
};

export type DangerousChange = {
  type: $Keys<typeof DangerousChangeType>;
  description: string;
};

/**
 * Given two schemas, returns an Array containing descriptions of all the types
 * of breaking changes covered by the other functions down below.
 */
export function findBreakingChanges(
  oldSchema: GraphQLSchema,
  newSchema: GraphQLSchema
): Array<BreakingChange> {
  return [
    ...findRemovedTypes(oldSchema, newSchema),
    ...findTypesThatChangedKind(oldSchema, newSchema),
    ...findFieldsThatChangedType(oldSchema, newSchema),
    ...findTypesRemovedFromUnions(oldSchema, newSchema),
    ...findValuesRemovedFromEnums(oldSchema, newSchema),
    ...findArgChanges(oldSchema, newSchema).breakingChanges,
  ];
}

/**
 * Given two schemas, returns an Array containing descriptions of all the types
 * of potentially dangerous changes covered by the other functions down below.
 */
export function findDangerousChanges(
  oldSchema: GraphQLSchema,
  newSchema: GraphQLSchema
): Array<DangerousChange> {
  return [
    ...findArgChanges(oldSchema, newSchema).dangerousChanges,
  ];
}

/**
 * Given two schemas, returns an Array containing descriptions of any breaking
 * changes in the newSchema related to removing an entire type.
 */
export function findRemovedTypes(
  oldSchema: GraphQLSchema,
  newSchema: GraphQLSchema
): Array<BreakingChange> {
  const oldTypeMap = oldSchema.getTypeMap();
  const newTypeMap = newSchema.getTypeMap();

  const breakingChanges = [];
  Object.keys(oldTypeMap).forEach(typeName => {
    if (!newTypeMap[typeName]) {
      breakingChanges.push({
        type: BreakingChangeType.TYPE_REMOVED,
        description: `${typeName} was removed.`,
      });
    }
  });
  return breakingChanges;
}

/**
 * Given two schemas, returns an Array containing descriptions of any breaking
 * changes in the newSchema related to changing the type of a type.
 */
export function findTypesThatChangedKind(
 oldSchema: GraphQLSchema,
 newSchema: GraphQLSchema
): Array<BreakingChange> {
  const oldTypeMap = oldSchema.getTypeMap();
  const newTypeMap = newSchema.getTypeMap();

  const breakingChanges = [];
  Object.keys(oldTypeMap).forEach(typeName => {
    if (!newTypeMap[typeName]) {
      return;
    }
    const oldType = oldTypeMap[typeName];
    const newType = newTypeMap[typeName];
    if (!(oldType instanceof newType.constructor)) {
      breakingChanges.push({
        type: BreakingChangeType.TYPE_CHANGED_KIND,
        description: `${typeName} changed from ` +
          `${typeKindName(oldType)} to ${typeKindName(newType)}.`
      });
    }
  });
  return breakingChanges;
}

/**
 * Given two schemas, returns an Array containing descriptions of any
 * breaking or dangerous changes in the newSchema related to arguments
 * (such as removal or change of type of an argument, or a change in an
 * argument's default value).
 */
export function findArgChanges(
 oldSchema: GraphQLSchema,
 newSchema: GraphQLSchema
): {
  breakingChanges: Array<BreakingChange>,
  dangerousChanges: Array<DangerousChange>
} {
  const oldTypeMap = oldSchema.getTypeMap();
  const newTypeMap = newSchema.getTypeMap();

  const breakingChanges = [];
  const dangerousChanges = [];

  Object.keys(oldTypeMap).forEach(typeName => {
    const oldType = oldTypeMap[typeName];
    const newType = newTypeMap[typeName];
    if (
      !(oldType instanceof GraphQLObjectType) ||
      !(newType instanceof oldType.constructor)
    ) {
      return;
    }

    const oldTypeFields: GraphQLFieldMap<*, *> = oldType.getFields();
    const newTypeFields: GraphQLFieldMap<*, *> = newType.getFields();

    Object.keys(oldTypeFields).forEach(fieldName => {
      if (!newTypeFields[fieldName]) {
        return;
      }

      oldTypeFields[fieldName].args.forEach(oldArgDef => {
        const newArgs = newTypeFields[fieldName].args;
        const newTypeArgIndex = newArgs.findIndex(
          arg => arg.name === oldArgDef.name
        );
        const newArgDef = newArgs[newTypeArgIndex];

        // Arg not present
        if (newTypeArgIndex < 0) {
          breakingChanges.push({
            type: BreakingChangeType.ARG_REMOVED,
            description: `${oldType.name}.${fieldName} arg ` +
              `${oldArgDef.name} was removed`,
          });

        // Arg changed type in a breaking way
        } else if (
          oldArgDef.type !== newArgDef.type &&
          getNullableType(oldArgDef.type) !== newArgDef.type
        ) {
          breakingChanges.push({
            type: BreakingChangeType.ARG_CHANGED_KIND,
            description: `${oldType.name}.${fieldName} arg ` +
              `${oldArgDef.name} has changed type from ` +
              `${oldArgDef.type.toString()} to ${newArgDef.type.toString()}`,
          });

        // Arg default value has changed
        } else if (oldArgDef.defaultValue !== undefined &&
          oldArgDef.defaultValue !== newArgDef.defaultValue) {
          dangerousChanges.push({
            type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
            description: `${oldType.name}.${fieldName} arg ${oldArgDef.name} ` +
              'has changed defaultValue',
          });
        }
      });
    });
  });

  return {
    breakingChanges,
    dangerousChanges,
  };
}

function typeKindName(type: GraphQLNamedType): string {
  if (type instanceof GraphQLScalarType) {
    return 'a Scalar type';
  }
  if (type instanceof GraphQLObjectType) {
    return 'an Object type';
  }
  if (type instanceof GraphQLInterfaceType) {
    return 'an Interface type';
  }
  if (type instanceof GraphQLUnionType) {
    return 'a Union type';
  }
  if (type instanceof GraphQLEnumType) {
    return 'an Enum type';
  }
  if (type instanceof GraphQLInputObjectType) {
    return 'an Input type';
  }
  throw new TypeError('Unknown type ' + type.constructor.name);
}

/**
 * Given two schemas, returns an Array containing descriptions of any breaking
 * changes in the newSchema related to the fields on a type. This includes if
 * a field has been removed from a type or if a field has changed type.
 */
export function findFieldsThatChangedType(
  oldSchema: GraphQLSchema,
  newSchema: GraphQLSchema
): Array<BreakingChange> {
  const oldTypeMap = oldSchema.getTypeMap();
  const newTypeMap = newSchema.getTypeMap();

  const breakingFieldChanges = [];
  Object.keys(oldTypeMap).forEach(typeName => {
    const oldType = oldTypeMap[typeName];
    const newType = newTypeMap[typeName];
    if (
      !(oldType instanceof GraphQLObjectType ||
        oldType instanceof GraphQLInterfaceType ||
        oldType instanceof GraphQLInputObjectType) ||
      !(newType instanceof oldType.constructor)
    ) {
      return;
    }

    const oldTypeFieldsDef = oldType.getFields();
    const newTypeFieldsDef = newType.getFields();
    Object.keys(oldTypeFieldsDef).forEach(fieldName => {
      // Check if the field is missing on the type in the new schema.
      if (!(fieldName in newTypeFieldsDef)) {
        breakingFieldChanges.push({
          type: BreakingChangeType.FIELD_REMOVED,
          description: `${typeName}.${fieldName} was removed.`,
        });
      } else {
        // Check if the field's type has changed in the new schema.
        const oldFieldType = getNamedType(oldTypeFieldsDef[fieldName].type);
        const newFieldType = getNamedType(newTypeFieldsDef[fieldName].type);
        if (oldFieldType &&
            newFieldType &&
            oldFieldType.name !== newFieldType.name) {
          breakingFieldChanges.push({
            type: BreakingChangeType.FIELD_CHANGED_KIND,
            description: `${typeName}.${fieldName} changed type from ` +
              `${oldFieldType.name} to ${newFieldType.name}.`,
          });
        }
      }
    });
  });
  return breakingFieldChanges;
}

/**
 * Given two schemas, returns an Array containing descriptions of any breaking
 * changes in the newSchema related to removing types from a union type.
 */
export function findTypesRemovedFromUnions(
  oldSchema: GraphQLSchema,
  newSchema: GraphQLSchema
): Array<BreakingChange> {
  const oldTypeMap = oldSchema.getTypeMap();
  const newTypeMap = newSchema.getTypeMap();

  const typesRemovedFromUnion = [];
  Object.keys(oldTypeMap).forEach(typeName => {
    const oldType = oldTypeMap[typeName];
    const newType = newTypeMap[typeName];
    if (!(oldType instanceof GraphQLUnionType) ||
        !(newType instanceof GraphQLUnionType)) {
      return;
    }
    const typeNamesInNewUnion = Object.create(null);
    newType.getTypes().forEach(type => {
      typeNamesInNewUnion[type.name] = true;
    });
    oldType.getTypes().forEach(type => {
      if (!typeNamesInNewUnion[type.name]) {
        typesRemovedFromUnion.push({
          type: BreakingChangeType.TYPE_REMOVED_FROM_UNION,
          description: `${type.name} was removed from union type ${typeName}.`
        });
      }
    });
  });
  return typesRemovedFromUnion;
}

/**
 * Given two schemas, returns an Array containing descriptions of any breaking
 * changes in the newSchema related to removing values from an enum type.
 */
export function findValuesRemovedFromEnums(
  oldSchema: GraphQLSchema,
  newSchema: GraphQLSchema
): Array<BreakingChange> {
  const oldTypeMap = oldSchema.getTypeMap();
  const newTypeMap = newSchema.getTypeMap();

  const valuesRemovedFromEnums = [];
  Object.keys(oldTypeMap).forEach(typeName => {
    const oldType = oldTypeMap[typeName];
    const newType = newTypeMap[typeName];
    if (!(oldType instanceof GraphQLEnumType) ||
        !(newType instanceof GraphQLEnumType)) {
      return;
    }
    const valuesInNewEnum = Object.create(null);
    newType.getValues().forEach(value => {
      valuesInNewEnum[value.name] = true;
    });
    oldType.getValues().forEach(value => {
      if (!valuesInNewEnum[value.name]) {
        valuesRemovedFromEnums.push({
          type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM,
          description: `${value.name} was removed from enum type ${typeName}.`
        });
      }
    });
  });
  return valuesRemovedFromEnums;
}
<|endoftext|>