<commit_before>/**
 * @provide pskl.tools.drawing.selection.BaseSelect
 *
 * @require pskl.utils
 */
(function() {
  var ns = $.namespace('pskl.tools.drawing.selection');

  ns.BaseSelect = function() {
    this.secondaryToolId = pskl.tools.drawing.Move.TOOL_ID;
    this.bodyRoot = $('body');

    // Select's first point coordinates (set in applyToolAt)
    this.startCol = null;
    this.startRow = null;

    this.lastMoveCol = null;
    this.lastMoveRow = null;

    this.selection = null;

    this.tooltipDescriptors = [
      {description : 'Drag the selection to move it. You may switch to other layers and frames.'},
      {key : 'ctrl+c', description : 'Copy the selected area'},
      {key : 'ctrl+v', description : 'Paste the copied area'}
    ];
  };

  pskl.utils.inherit(ns.BaseSelect, pskl.tools.drawing.BaseTool);

  /**
   * @override
   */
  ns.BaseSelect.prototype.applyToolAt = function(col, row, frame, overlay, event) {
    this.startCol = col;
    this.startRow = row;

    this.lastMoveCol = col;
    this.lastMoveRow = row;

    // The select tool can be in two different state.
    // If the inital click of the tool is not on a selection, we go in 'select'
    // mode to create a selection.
    // If the initial click is on a previous selection, we go in 'moveSelection'
    // mode to allow to move the selection by drag'n dropping it.
    if (!this.isInSelection(col, row)) {
      this.mode = 'select';
      this.onSelectStart_(col, row, frame, overlay);
    } else {
      this.mode = 'moveSelection';
      this.onSelectionMoveStart_(col, row, frame, overlay);
    }
  };

  /**
   * @override
   */
  ns.BaseSelect.prototype.moveToolAt = function(col, row, frame, overlay, event) {
    if (this.mode == 'select') {
      this.onSelect_(col, row, frame, overlay);
    } else if (this.mode == 'moveSelection') {
      this.onSelectionMove_(col, row, frame, overlay);
    }
  };

  /**
   * @override
   */
  ns.BaseSelect.prototype.releaseToolAt = function(col, row, frame, overlay, event) {
    if (this.mode == 'select') {
      this.onSelectEnd_(col, row, frame, overlay);
    } else if (this.mode == 'moveSelection') {
      this.onSelectionMoveEnd_(col, row, frame, overlay);
    }
  };

  /**
   * If we mouseover the selection draw inside the overlay frame, show the 'move' cursor
   * instead of the 'select' one. It indicates that we can move the selection by dragndroping it.
   * @override
   */
  ns.BaseSelect.prototype.moveUnactiveToolAt = function(col, row, frame, overlay, event) {
    if (overlay.containsPixel(col, row)) {
      if (this.isInSelection(col, row)) {
        // We're hovering the selection, show the move tool:
        this.bodyRoot.addClass(this.secondaryToolId);
        this.bodyRoot.removeClass(this.toolId);
      } else {
        // We're not hovering the selection, show create selection tool:
        this.bodyRoot.addClass(this.toolId);
        this.bodyRoot.removeClass(this.secondaryToolId);
      }
    }
  };

  ns.BaseSelect.prototype.isInSelection = function (col, row) {
    return this.selection && this.selection.pixels.some(function (pixel) {
      return pixel.col === col && pixel.row === row;
    });
  };

  ns.BaseSelect.prototype.hideHighlightedPixel = function() {
    // there is no highlighted pixel for selection tools, do nothing
  };

  /**
   * For each pixel in the selection draw it in white transparent on the tool overlay
   * @protected
   */
  ns.BaseSelect.prototype.drawSelectionOnOverlay_ = function (overlay) {
    var pixels = this.selection.pixels;
    for (var i = 0, l = pixels.length; i < l ; i++) {
      var pixel = pixels[i];
      var hasColor = pixel.color && pixel.color !== Constants.TRANSPARENT_COLOR ;
      var color = hasColor ? this.getTransparentVariant_(pixel.color) : Constants.SELECTION_TRANSPARENT_COLOR;

      overlay.setPixel(pixels[i].col, pixels[i].row, color);
    }
  };

  ns.BaseSelect.prototype.getTransparentVariant_ = pskl.utils.FunctionUtils.memo(function (colorStr) {
    var color = window.tinycolor(colorStr);
    color = window.tinycolor.lighten(color, 10);
    color.setAlpha(0.5);
    return color.toRgbString();
  }, {});

  // The list of callbacks to implement by specialized tools to implement the selection creation behavior.
  /** @protected */
  ns.BaseSelect.prototype.onSelectStart_ = function (col, row, frame, overlay) {};
  /** @protected */
  ns.BaseSelect.prototype.onSelect_ = function (col, row, frame, overlay) {};
  /** @protected */
  ns.BaseSelect.prototype.onSelectEnd_ = function (col, row, frame, overlay) {};

  // The list of callbacks that define the drag'n drop behavior of the selection.
  /** @private */

  ns.BaseSelect.prototype.onSelectionMoveStart_ = function (col, row, color, frame, overlay) {};

  /** @private */
  ns.BaseSelect.prototype.onSelectionMove_ = function (col, row, frame, overlay) {
    var deltaCol = col - this.lastMoveCol;
    var deltaRow = row - this.lastMoveRow;

    var colDiff = col - this.startCol;
    var rowDiff = row - this.startRow;

    this.selection.move(deltaCol, deltaRow);

    overlay.clear();
    this.drawSelectionOnOverlay_(overlay);

    this.lastMoveCol = col;
    this.lastMoveRow = row;
  };

  /** @private */
  ns.BaseSelect.prototype.onSelectionMoveEnd_ = function (col, row, color, frame, overlay) {
    this.onSelectionMove_(col, row, color, frame, overlay);
  };
})();
<commit_msg>Issue #311 : Fix post-rebase issues<commit_after>/**
 * @provide pskl.tools.drawing.selection.BaseSelect
 *
 * @require pskl.utils
 */
(function() {
  var ns = $.namespace('pskl.tools.drawing.selection');

  ns.BaseSelect = function() {
    this.secondaryToolId = pskl.tools.drawing.Move.TOOL_ID;
    this.bodyRoot = $('body');

    // Select's first point coordinates (set in applyToolAt)
    this.startCol = null;
    this.startRow = null;

    this.lastMoveCol = null;
    this.lastMoveRow = null;

    this.selection = null;

    this.tooltipDescriptors = [
      {description : 'Drag the selection to move it. You may switch to other layers and frames.'},
      {key : 'ctrl+c', description : 'Copy the selected area'},
      {key : 'ctrl+v', description : 'Paste the copied area'}
    ];
  };

  pskl.utils.inherit(ns.BaseSelect, pskl.tools.drawing.BaseTool);

  /**
   * @override
   */
  ns.BaseSelect.prototype.applyToolAt = function(col, row, frame, overlay, event) {
    this.startCol = col;
    this.startRow = row;

    this.lastMoveCol = col;
    this.lastMoveRow = row;

    // The select tool can be in two different state.
    // If the inital click of the tool is not on a selection, we go in 'select'
    // mode to create a selection.
    // If the initial click is on a previous selection, we go in 'moveSelection'
    // mode to allow to move the selection by drag'n dropping it.
    if (!this.isInSelection(col, row)) {
      this.mode = 'select';
      this.onSelectStart_(col, row, frame, overlay);
    } else {
      this.mode = 'moveSelection';
      this.onSelectionMoveStart_(col, row, frame, overlay);
    }
  };

  /**
   * @override
   */
  ns.BaseSelect.prototype.moveToolAt = function(col, row, frame, overlay, event) {
    if (this.mode == 'select') {
      this.onSelect_(col, row, frame, overlay);
    } else if (this.mode == 'moveSelection') {
      this.onSelectionMove_(col, row, frame, overlay);
    }
  };

  /**
   * @override
   */
  ns.BaseSelect.prototype.releaseToolAt = function(col, row, frame, overlay, event) {
    if (this.mode == 'select') {
      this.onSelectEnd_(col, row, frame, overlay);
    } else if (this.mode == 'moveSelection') {
      this.onSelectionMoveEnd_(col, row, frame, overlay);
    }
  };

  /**
   * If we mouseover the selection draw inside the overlay frame, show the 'move' cursor
   * instead of the 'select' one. It indicates that we can move the selection by dragndroping it.
   * @override
   */
  ns.BaseSelect.prototype.moveUnactiveToolAt = function(col, row, frame, overlay, event) {
    if (overlay.containsPixel(col, row)) {
      if (this.isInSelection(col, row)) {
        // We're hovering the selection, show the move tool:
        this.bodyRoot.addClass(this.secondaryToolId);
        this.bodyRoot.removeClass(this.toolId);
      } else {
        // We're not hovering the selection, show create selection tool:
        this.bodyRoot.addClass(this.toolId);
        this.bodyRoot.removeClass(this.secondaryToolId);
      }
    }
  };

  ns.BaseSelect.prototype.isInSelection = function (col, row) {
    return this.selection && this.selection.pixels.some(function (pixel) {
      return pixel.col === col && pixel.row === row;
    });
  };

  ns.BaseSelect.prototype.hideHighlightedPixel = function() {
    // there is no highlighted pixel for selection tools, do nothing
  };

  /**
   * For each pixel in the selection draw it in white transparent on the tool overlay
   * @protected
   */
  ns.BaseSelect.prototype.drawSelectionOnOverlay_ = function (overlay) {
    var pixels = this.selection.pixels;
    for (var i = 0, l = pixels.length; i < l ; i++) {
      var pixel = pixels[i];
      var hasColor = pixel.color && pixel.color !== Constants.TRANSPARENT_COLOR ;
      var color = hasColor ? this.getTransparentVariant_(pixel.color) : Constants.SELECTION_TRANSPARENT_COLOR;

      overlay.setPixel(pixels[i].col, pixels[i].row, color);
    }
  };

  ns.BaseSelect.prototype.getTransparentVariant_ = pskl.utils.FunctionUtils.memo(function (colorStr) {
    var color = window.tinycolor(colorStr);
    color = window.tinycolor.lighten(color, 10);
    color.setAlpha(0.5);
    return color.toRgbString();
  }, {});

  // The list of callbacks to implement by specialized tools to implement the selection creation behavior.
  /** @protected */
  ns.BaseSelect.prototype.onSelectStart_ = function (col, row, frame, overlay) {};
  /** @protected */
  ns.BaseSelect.prototype.onSelect_ = function (col, row, frame, overlay) {};
  /** @protected */
  ns.BaseSelect.prototype.onSelectEnd_ = function (col, row, frame, overlay) {};

  // The list of callbacks that define the drag'n drop behavior of the selection.
  /** @private */

  ns.BaseSelect.prototype.onSelectionMoveStart_ = function (col, row, frame, overlay) {};

  /** @private */
  ns.BaseSelect.prototype.onSelectionMove_ = function (col, row, frame, overlay) {
    var deltaCol = col - this.lastMoveCol;
    var deltaRow = row - this.lastMoveRow;

    var colDiff = col - this.startCol;
    var rowDiff = row - this.startRow;

    this.selection.move(deltaCol, deltaRow);

    overlay.clear();
    this.drawSelectionOnOverlay_(overlay);

    this.lastMoveCol = col;
    this.lastMoveRow = row;
  };

  /** @private */
  ns.BaseSelect.prototype.onSelectionMoveEnd_ = function (col, row, frame, overlay) {
    this.onSelectionMove_(col, row, frame, overlay);
  };
})();
<|endoftext|>


<commit_before>const chai = require('chai')
const expect = chai.expect

describe('omdb API movies search', function () {
  it('should make a search', function (done) {
    let searchMovie = 'fast'

    MoviesOMDB.search(searchMovie).then(result => {
      expect(result.Search).to.be.a('array')
      expect(result.totalResults).to.be.above(0)
      expect(result.Response).to.be.equal('True')
      done()
    })
  })

  it('should have coherent search results', function (done) {
    let searchMovie = 'Warcraft'

    MoviesOMDB.search(searchMovie).then(result => {
      expect(result).to.be.a('array')
      done()
    })
  })
})
<commit_msg>Update movies-omdb test<commit_after>const chai = require('chai')
const expect = chai.expect
const MovieOMDB = require('../app/MovieOMDB')

describe('omdb API movies search', function () {
  it('should make a search', function (done) {
    // omdb api is quite long to response
    this.timeout(10000)

    const searchMovie = 'Warcraft'

    MovieOMDB.search(searchMovie).then(result => {
      expect(result.Search).to.be.a('array')
      expect(result.totalResults).to.be.above(0)
      expect(result.Response).to.be.equal('True')

      done()
    })
  })

  it('should have coherent search results', function (done) {
    // omdb api is quite long to response
    this.timeout(10000)

    const searchMovie = 'fast'

    MovieOMDB.search(searchMovie).then(result => {
      expect(result.Search).to.be.a('array')

      result.Search.forEach(movie => {
        expect(movie.Title.toLowerCase().search('fast')).to.be.above(-1)
      })

      done()
    })
  })

  it('should search for a movie that cannot be found', function (done) {
    // omdb api is quite long to response
    this.timeout(10000)

    const searchMovie = 'movie that cannot be found'

    MovieOMDB.search(searchMovie).then(result => {
      result.should.have.property('Response')
      result.should.have.property('Error')
      result.Response.should.be.equal('False')
      result.Error.should.be.equal('Movie not found!')

      done()
    })
  })
})
<|endoftext|>


<commit_before>var PositionSensorVRDevice = require('./base.js').PositionSensorVRDevice;
var TouchPanner = require('./touch-panner.js');
var THREE = require('./three-math.js');
var Util = require('./util.js');

/**
 * Leap Motion position / orientation sensor (uses Leap Motion tool tracking functionality)
 */
function LeapMotionPositionSensorVRDevice(host, port) {
  this.deviceId = 'webvr-polyfill:leapmotion';
  this.deviceName = 'VR Position Device (webvr-polyfill:leapmotion)';

  // Keep track of a reset transform for resetSensor.
  this.resetQ = new THREE.Quaternion();

  this.orientation = new THREE.Quaternion();
  this.position = new THREE.Vector3();

  // Leap Motion input
  var leapConfig = {
    background: true /* ,
    frameEventName: 'animationFrame' */
  };
  if (host) {
    leapConfig.host = host;
  }
  if (port) {
    leapConfig.port = port;
  }
  this.leapController = new Leap.Controller(leapConfig);

  this.leapController.on('connect', function () {
    console.log('LeapMotionPositionSensorVRDevice: connected to Leap Motion controller');
  });
  this.leapController.on('streamingStarted', function () {
    console.log('LeapMotionPositionSensorVRDevice: streaming started');
  });
  this.leapController.on('streamingStopped', function () {
    console.log('LeapMotionPositionSensorVRDevice: streaming stopped');
  });

  this.leapController.connect();
}
LeapMotionPositionSensorVRDevice.prototype = new PositionSensorVRDevice();

/**
 * Returns {orientation: {x,y,z,w}, position: {x,y,z}}.
 */
LeapMotionPositionSensorVRDevice.prototype.getState = ( function () {
  var lastFrameID;
  var direction = new THREE.Vector3();
  var UP = new THREE.Vector3(0, 1, 0);
  return function () {

    // Update state if new Leap Motion frame is available.
    var frame = this.leapController.frame();
    if (frame.valid && frame.id != lastFrameID) {
      lastFrameID = frame.id;
      if (frame.tools.length === 1) {
        var tool = frame.tools[0];
        //this.position.fromArray(tool.tipPosition).multiplyScalar(0.001);
        this.position.fromArray(tool.stabilizedTipPosition).multiplyScalar(0.001);
        // will need at least 2 tools to set orientation
        //this.orientation.setFromUnitVectors(UP, direction.fromArray(tool.direction));
      }
    }

    return {
      hasOrientation: true,
      orientation: this.orientation,
      hasPosition: true,
      position: this.position
    };

  };
} )();

LeapMotionPositionSensorVRDevice.prototype.getOrientation = function() {
  return this.orientation;
};

LeapMotionPositionSensorVRDevice.prototype.getPosition = function() {
  return this.position;
};

LeapMotionPositionSensorVRDevice.prototype.resetSensor = function() {
  // TODO
};

module.exports = LeapMotionPositionSensorVRDevice;
<commit_msg>orientation via two tools<commit_after>var PositionSensorVRDevice = require('./base.js').PositionSensorVRDevice;
var TouchPanner = require('./touch-panner.js');
var THREE = require('./three-math.js');
var Util = require('./util.js');

/**
 * Leap Motion position / orientation sensor (uses Leap Motion tool tracking functionality)
 */
function LeapMotionPositionSensorVRDevice(host, port) {
  this.deviceId = 'webvr-polyfill:leapmotion';
  this.deviceName = 'VR Position Device (webvr-polyfill:leapmotion)';

  // Keep track of a reset transform for resetSensor.
  this.resetQ = new THREE.Quaternion();

  this.orientation = new THREE.Quaternion();
  this.position = new THREE.Vector3();

  // configure leap motion host/port via url params:
  location.search.substr(1).split("&").forEach( function(item) {
    var kv = item.split("=");
    if (kv[0] === 'host') {
      host = host || decodeURIComponent(kv[1]);
    }
    else if (kv[0] === 'port') {
      port = port || decodeURIComponent(kv[1]);
    }
  } );

  var leapConfig = {
    background: true /* ,
    frameEventName: 'animationFrame' */
  };
  if (host) {
    leapConfig.host = host;
  }
  if (port) {
    leapConfig.port = port;
  }

  this.leapController = new Leap.Controller(leapConfig);

  this.leapController.on('connect', function () {
    console.log('LeapMotionPositionSensorVRDevice: connected to Leap Motion controller');
  });
  this.leapController.on('streamingStarted', function () {
    console.log('LeapMotionPositionSensorVRDevice: streaming started');
  });
  this.leapController.on('streamingStopped', function () {
    console.log('LeapMotionPositionSensorVRDevice: streaming stopped');
  });

  this.leapController.connect();
}
LeapMotionPositionSensorVRDevice.prototype = new PositionSensorVRDevice();

/**
 * Returns {orientation: {x,y,z,w}, position: {x,y,z}}.
 */
LeapMotionPositionSensorVRDevice.prototype.getState = ( function () {
  var lastFrameID;
  // tool ids (TODO):
  //var idA, idB;
  // normalized pointing directions of the tools:
  var directionA = new THREE.Vector3();
  var directionB = new THREE.Vector3();
  // used for computing orientation quaternion:
  var cross = new THREE.Vector3();
  var avg = new THREE.Vector3();
  var Y = new THREE.Vector3(0, 1, 0);
  var NZ = new THREE.Vector3(0, 0, -1);
  var quat = new THREE.Quaternion();

  return function () {

    // Update state if new Leap Motion frame is available.
    var frame = this.leapController.frame();
    if (frame.valid && frame.id != lastFrameID) {
      lastFrameID = frame.id;

      if (frame.tools.length === 2) {

        var toolA = frame.tools[0];
        var toolB = frame.tools[1];

        // set position to the average of the tips:
        this.position.set(0.0005 * (toolA.tipPosition[0] + toolB.tipPosition[0]),
                          0.0005 * (toolA.tipPosition[1] + toolB.tipPosition[1]),
                          0.0005 * (toolA.tipPosition[2] + toolB.tipPosition[2]));

        // determine orientation:
        cross.crossVectors(directionA.fromArray(toolA.direction), directionB.fromArray(toolB.direction));
        if (cross.y < 0) {
          cross.negate();
        }
        avg.addVectors(directionA, directionB).normalize();
        this.orientation.setFromUnitVectors(NZ, avg);
        Y.set(0, 1, 0).applyQuaternion(this.orientation);
        quat.setFromUnitVectors(Y, cross);
        this.orientation.multiplyQuaternions(this.orientation, quat);
      }

    }

    return {
      hasOrientation: true,
      orientation: this.orientation,
      hasPosition: true,
      position: this.position
    };

  };
} )();

// TODO:
// LeapMotionPositionSensorVRDevice.prototype.getOrientation = function() {
//   return this.orientation;
// };
// LeapMotionPositionSensorVRDevice.prototype.getPosition = function() {
//   return this.position;
// };
// LeapMotionPositionSensorVRDevice.prototype.resetSensor = function() {
// };

module.exports = LeapMotionPositionSensorVRDevice;
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * 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.
 */

// Accumulator which formats objects in a column.  Used within GridView
var COL = {
  create: function() { return { __proto__: this, values: [] }; },
  put: function(v) { this.values.push(v); },
  toHTML: function() { return this.values.join('
'); },
  initHTML: function() {
    for ( var i in this.values ) {
      var o = this.values[i];
      o.initHTML && o.initHTML();
    }
  },
  clone: function() { return this.create(); }
};

var DragAndDropGrid = FOAM({
  model_: 'Model',
  extendsModel: 'GridByExpr',

  properties: [
    {
      name: 'dao'
    }
  ],

  methods: {
    renderCell: function(x, y, value) {
      var cell = IssueDropCell.create({
        value: value,
        dao: this.dao,
        props: [this.xFunc, this.yFunc],
        values: [x, y]
      });
      this.children.push(cell);
      return cell.toHTML();
    },
    clone: function() {
      var clone = this.SUPER();
      clone.dao = this.dao;
      return clone;
    }
  }
});

var IssueDropCell = FOAM({
  model_: 'Model',

  extendsModel: 'AbstractView',

  properties: [
    {
      name: 'value'
    },
    {
      name: 'dao',
      hidden: true
    },
    {
      model_: 'ArrayProperty',
      name: 'props'
    },
    {
      model_: 'ArrayProperty',
      name: 'values'
    }
  ],

  methods: {
    toHTML: function() {
      this.on('dragenter', this.onDragEnter, this.getID());
      this.on('dragover', this.onDragEnter, this.getID());
      this.on('drop', this.onDrop, this.getID());
      return '' +
        (this.value ? (this.value.toHTML ? this.value.toHTML() : this.value) : '') + '	';
    },
    initHTML: function() {
      this.SUPER();
      this.value && this.value.initHTML && this.value.initHTML();
    },
    put: function(obj) {
      this.value.put(obj);
    }
  },

  listeners: [
    {
      name: 'onDragEnter',
      code: function(e) {
        for ( var i = 0; i < e.dataTransfer.types.length; i++ ) {
          if ( e.dataTransfer.types[i] === 'application/x-foam-id' ) {
            e.dataTransfer.dropEffect = "move";
            e.preventDefault();
            return;
          }
        }
      }
    },
    {
      name: 'onDrop',
      code: function(e) {
        var data = e.dataTransfer.getData('application/x-foam-id');
        if ( ! data ) return;

        e.preventDefault();

        var props = this.props;
        var values = this.values;
        var dao = this.dao;
        dao.find(data, {
          put: function(obj) {
            obj = obj.clone();
            for ( var i = 0; i < props.length; i++ ) {
              var p = props[i];
              var v = values[i];
              obj[p.name] = v;
            }
            dao.put(obj);
          }
        });
      }
    }
  ]
});

/*
 * An extension to COUNT() which turns count into a link which performs
 * a query for only the selected data when clicked.
 */
var ItemCount = Model.create({
  extendsModel: 'CountExpr',

  properties: [
    {
      name: 'browser'
    }
  ],

  methods: {
    put: function(obj) {
      if ( ! this.obj ) {
        this.obj = obj;
        this.eid = AbstractView.getPrototype().nextID();
      }
      this.SUPER(obj);
    },
    toHTML: function() {
      return '' + this.count + (this.count == 1 ? ' item' : ' items') + '';
    },
    initHTML: function() {
      var f = function() {
        var searchField = this.browser.searchField;
        var altView = this.browser.view;
        var col = altView.views[1].view().col.value.get();
        var row = altView.views[1].view().row.value.get();
        var q = AND(
          QueryParser.parseString(searchField.value.get()),
          EQ(col, col.f(this.obj)),
          EQ(row, row.f(this.obj))).partialEval();
        this.browser.location.copyFrom({ mode: 'list', q: q.toMQL() });
      }.bind(this);
      $(this.eid).addEventListener('click', f, false);
    }
  }
});

// Formats Issue ID's as links to the main crbug.com site.
var idFormatter = {
  f: function(i) {
    return '' + i.id + '';
  }
};

var priColorMap = {
  colorMap: {
    'Critical': 'hsl(  0, 100%, 70%)',
    'High':     'hsl(-340, 100%, 70%)',
    'Medium':   'hsl(-320, 100%, 70%)',
    'Low':      'hsl(-300, 100%, 70%)',
    '':         'lightgray'
  }
};

var QIssueTableView = FOAM({
  model_: 'Model',

  name: 'QIssueTableView',

  extendsModel: 'TableView2',

  properties: [
    { name: 'browser' }
  ],

  methods: {
    /*
    initHTML: function() {
      this.SUPER();

      this.selection.addListener(function(_,_,_,obj) {
        if ( obj.id && obj.id !== this.browser.previewID ) this.browser.preview(null);
      });
    },
    */
    toHTML: function() {
      return '

' + this.SUPER();
    }
  }
});

function createView(rowSelection, browser) {
  var location = browser.location;

  return AlternateView.create({
    dao: browser.IssueDAO,
    headerView: browser.countField,
    views: [
      ViewChoice.create({
        label: 'List',
        view: function() {
          var tableView = QIssueTableView.create({
            model: QIssue,
            dao: browser.IssueDAO,
            browser: browser,
            hardSelection: rowSelection,
            scrollEnabled: true,
            editColumnsEnabled: true
          });

          tableView.sortOrder$  = location.sort$;
          tableView.properties$ = location.colspec$;

          tableView.window = browser.window;

          return tableView;
        }
      }),
      ViewChoice.create({
        label: 'Grid',
        view: function() {
           var g = Model.create({
              name: 'QIssueGridView',
              extendsModel: 'GridView',
              properties: [
                {
                  name: 'dao',
                  // crbug limits grid view to 6000 rows, so do the same
                  preSet: function(dao) { return dao.limit(6000); },
                  postSet: function(old, dao) {
                     if ( this.listener ) {
                        old && old.unlisten(this.listener);
                        dao.listen(this.listener);
                     }
                     this.grid.dao = dao;
                     this.updateHTML();
                  },
                },
              ],
              methods: {
                 init: function(args) {
                    this.SUPER(args);
                    this.listener = {
                       put: this.daoUpdate,
                       remove: this.daoUpdate
                    };
                 }
              },
              listeners: [
                 {
                    name: 'daoUpdate',
                    isMerged: 1000,
                    code: function() {
                       this.updateHTML();
                    }
                 }
              ]}).create({
                model: QIssue,
                accChoices: [
                  [ MAP(QIssueTileView.create({browser: browser}), COL.create()), "Tiles" ],
                  [ MAP(idFormatter, COL.create()),             "IDs" ],
                  [ ItemCount.create({browser: browser}),       "Counts" ],
                  [ PIE(QIssue.STATUS),                         "Pie(Status)"  ],
                  [ PIE(QIssue.PRIORITY, priColorMap),          "Pie(Priority)" ]
                  //                 [ PIE(QIssue.STATE, {colorMap: {open:'red',closed:'green'}}), "PIE(State)" ]
                ],
              grid: /*GridByExpr*/DragAndDropGrid.create({})
           });

          g.row.value = location.y$;
          g.col.value = location.x$;

          // TODO: cleanup this block
          function setAcc(title) {
            var acc = g.accChoices[0];
            for ( var i = 1 ; i < g.accChoices.length ; i++ ) {
              if ( location.tile === g.accChoices[i][1].toLowerCase() ) acc = g.accChoices[i];
            }
            g.acc.choice = acc;
          }
          setAcc(location.title);

          g.acc.value.addListener(function(choice) { location.tile = g.acc.choice[1].toLowerCase(); });
          location.tile$.addListener(setAcc);

          return g;
        }
      })
    ]
  });
}
<commit_msg>Crbug: Modified Grid limit.<commit_after>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * 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.
 */

// Accumulator which formats objects in a column.  Used within GridView
var COL = {
  create: function() { return { __proto__: this, values: [] }; },
  put: function(v) { this.values.push(v); },
  toHTML: function() { return this.values.join('
'); },
  initHTML: function() {
    for ( var i in this.values ) {
      var o = this.values[i];
      o.initHTML && o.initHTML();
    }
  },
  clone: function() { return this.create(); }
};

var DragAndDropGrid = FOAM({
  model_: 'Model',
  extendsModel: 'GridByExpr',

  properties: [
    {
      name: 'dao'
    }
  ],

  methods: {
    renderCell: function(x, y, value) {
      var cell = IssueDropCell.create({
        value: value,
        dao: this.dao,
        props: [this.xFunc, this.yFunc],
        values: [x, y]
      });
      this.children.push(cell);
      return cell.toHTML();
    },
    clone: function() {
      var clone = this.SUPER();
      clone.dao = this.dao;
      return clone;
    }
  }
});

var IssueDropCell = FOAM({
  model_: 'Model',

  extendsModel: 'AbstractView',

  properties: [
    {
      name: 'value'
    },
    {
      name: 'dao',
      hidden: true
    },
    {
      model_: 'ArrayProperty',
      name: 'props'
    },
    {
      model_: 'ArrayProperty',
      name: 'values'
    }
  ],

  methods: {
    toHTML: function() {
      this.on('dragenter', this.onDragEnter, this.getID());
      this.on('dragover', this.onDragEnter, this.getID());
      this.on('drop', this.onDrop, this.getID());
      return '' +
        (this.value ? (this.value.toHTML ? this.value.toHTML() : this.value) : '') + '	';
    },
    initHTML: function() {
      this.SUPER();
      this.value && this.value.initHTML && this.value.initHTML();
    },
    put: function(obj) {
      this.value.put(obj);
    }
  },

  listeners: [
    {
      name: 'onDragEnter',
      code: function(e) {
        for ( var i = 0; i < e.dataTransfer.types.length; i++ ) {
          if ( e.dataTransfer.types[i] === 'application/x-foam-id' ) {
            e.dataTransfer.dropEffect = "move";
            e.preventDefault();
            return;
          }
        }
      }
    },
    {
      name: 'onDrop',
      code: function(e) {
        var data = e.dataTransfer.getData('application/x-foam-id');
        if ( ! data ) return;

        e.preventDefault();

        var props = this.props;
        var values = this.values;
        var dao = this.dao;
        dao.find(data, {
          put: function(obj) {
            obj = obj.clone();
            for ( var i = 0; i < props.length; i++ ) {
              var p = props[i];
              var v = values[i];
              obj[p.name] = v;
            }
            dao.put(obj);
          }
        });
      }
    }
  ]
});

/*
 * An extension to COUNT() which turns count into a link which performs
 * a query for only the selected data when clicked.
 */
var ItemCount = Model.create({
  extendsModel: 'CountExpr',

  properties: [
    {
      name: 'browser'
    }
  ],

  methods: {
    put: function(obj) {
      if ( ! this.obj ) {
        this.obj = obj;
        this.eid = AbstractView.getPrototype().nextID();
      }
      this.SUPER(obj);
    },
    toHTML: function() {
      return '' + this.count + (this.count == 1 ? ' item' : ' items') + '';
    },
    initHTML: function() {
      var f = function() {
        var searchField = this.browser.searchField;
        var altView = this.browser.view;
        var col = altView.views[1].view().col.value.get();
        var row = altView.views[1].view().row.value.get();
        var q = AND(
          QueryParser.parseString(searchField.value.get()),
          EQ(col, col.f(this.obj)),
          EQ(row, row.f(this.obj))).partialEval();
        this.browser.location.copyFrom({ mode: 'list', q: q.toMQL() });
      }.bind(this);
      $(this.eid).addEventListener('click', f, false);
    }
  }
});

// Formats Issue ID's as links to the main crbug.com site.
var idFormatter = {
  f: function(i) {
    return '' + i.id + '';
  }
};

var priColorMap = {
  colorMap: {
    'Critical': 'hsl(  0, 100%, 70%)',
    'High':     'hsl(-340, 100%, 70%)',
    'Medium':   'hsl(-320, 100%, 70%)',
    'Low':      'hsl(-300, 100%, 70%)',
    '':         'lightgray'
  }
};

var QIssueTableView = FOAM({
  model_: 'Model',

  name: 'QIssueTableView',

  extendsModel: 'TableView2',

  properties: [
    { name: 'browser' }
  ],

  methods: {
    /*
    initHTML: function() {
      this.SUPER();

      this.selection.addListener(function(_,_,_,obj) {
        if ( obj.id && obj.id !== this.browser.previewID ) this.browser.preview(null);
      });
    },
    */
    toHTML: function() {
      return '

' + this.SUPER();
    }
  }
});

function createView(rowSelection, browser) {
  var location = browser.location;

  return AlternateView.create({
    dao: browser.IssueDAO,
    headerView: browser.countField,
    views: [
      ViewChoice.create({
        label: 'List',
        view: function() {
          var tableView = QIssueTableView.create({
            model: QIssue,
            dao: browser.IssueDAO,
            browser: browser,
            hardSelection: rowSelection,
            scrollEnabled: true,
            editColumnsEnabled: true
          });

          tableView.sortOrder$  = location.sort$;
          tableView.properties$ = location.colspec$;

          tableView.window = browser.window;

          return tableView;
        }
      }),
      ViewChoice.create({
        label: 'Grid',
        view: function() {
           var g = Model.create({
              name: 'QIssueGridView',
              extendsModel: 'GridView',
              properties: [
                {
                  name: 'dao',
                  // crbug limits grid view to 6000 rows, so do the same
                  preSet: function(dao) { return dao.limit(2000); },
                  postSet: function(old, dao) {
                     if ( this.listener ) {
                        old && old.unlisten(this.listener);
                        dao.listen(this.listener);
                     }
                     this.grid.dao = dao;
                     this.updateHTML();
                  },
                },
              ],
              methods: {
                 init: function(args) {
                    this.SUPER(args);
                    this.listener = {
                       put: this.daoUpdate,
                       remove: this.daoUpdate
                    };
                 }
              },
              listeners: [
                 {
                    name: 'daoUpdate',
                    isMerged: 1000,
                    code: function() {
                       this.updateHTML();
                    }
                 }
              ]}).create({
                model: QIssue,
                accChoices: [
                  [ MAP(QIssueTileView.create({browser: browser}), COL.create()), "Tiles" ],
                  [ MAP(idFormatter, COL.create()),             "IDs" ],
                  [ ItemCount.create({browser: browser}),       "Counts" ],
                  [ PIE(QIssue.STATUS),                         "Pie(Status)"  ],
                  [ PIE(QIssue.PRIORITY, priColorMap),          "Pie(Priority)" ]
                  //                 [ PIE(QIssue.STATE, {colorMap: {open:'red',closed:'green'}}), "PIE(State)" ]
                ],
              grid: /*GridByExpr*/DragAndDropGrid.create({})
           });

          g.row.value = location.y$;
          g.col.value = location.x$;

          // TODO: cleanup this block
          function setAcc(title) {
            var acc = g.accChoices[0];
            for ( var i = 1 ; i < g.accChoices.length ; i++ ) {
              if ( location.tile === g.accChoices[i][1].toLowerCase() ) acc = g.accChoices[i];
            }
            g.acc.choice = acc;
          }
          setAcc(location.title);

          g.acc.value.addListener(function(choice) { location.tile = g.acc.choice[1].toLowerCase(); });
          location.tile$.addListener(setAcc);

          return g;
        }
      })
    ]
  });
}
<|endoftext|>


<commit_before>"use strict";

describe("loglevel included via node", function () {
    it("is included successfully", function () {
        expect(require('../lib/loglevel')).not.toBeUndefined();
    });

    it("allows setting the logging level", function () {
        var log = require('../lib/loglevel');

        log.setLevel(log.levels.TRACE);
        log.setLevel(log.levels.DEBUG);
        log.setLevel(log.levels.INFO);
        log.setLevel(log.levels.WARN);
        log.setLevel(log.levels.ERROR);
    });

    it("successfully logs", function () {
        var log = require('../lib/loglevel');
        console.info = jasmine.createSpy("info");

        log.setLevel(log.levels.INFO);
        log.info("test message");

        expect(console.info).toHaveBeenCalledWith("test message");
    });

    it("supports using symbols as names", function() {
        var log = require('../lib/loglevel');

        var s1 = Symbol("a-symbol");
        var s2 = Symbol("a-symbol");

        var logger1 = log.getLogger(s1);
        var logger2 = log.getLogger(s2);

        // Should be unequal: same name, but different symbol instances
        expect(logger1).not.toEqual(logger2);
    });
});
<commit_msg>Add a little extra to the symbol-name logging test<commit_after>"use strict";

describe("loglevel included via node", function () {
    it("is included successfully", function () {
        expect(require('../lib/loglevel')).not.toBeUndefined();
    });

    it("allows setting the logging level", function () {
        var log = require('../lib/loglevel');

        log.setLevel(log.levels.TRACE);
        log.setLevel(log.levels.DEBUG);
        log.setLevel(log.levels.INFO);
        log.setLevel(log.levels.WARN);
        log.setLevel(log.levels.ERROR);
    });

    it("successfully logs", function () {
        var log = require('../lib/loglevel');
        console.info = jasmine.createSpy("info");

        log.setLevel(log.levels.INFO);
        log.info("test message");

        expect(console.info).toHaveBeenCalledWith("test message");
    });

    it("supports using symbols as names", function() {
        var log = require('../lib/loglevel');

        var s1 = Symbol("a-symbol");
        var s2 = Symbol("a-symbol");

        var logger1 = log.getLogger(s1);
        var defaultLevel = logger1.getLevel();
        logger1.setLevel(log.levels.TRACE);

        var logger2 = log.getLogger(s2);

        // Should be unequal: same name, but different symbol instances
        expect(logger1).not.toEqual(logger2);
        expect(logger2.getLevel()).toEqual(defaultLevel);
    });
});
<|endoftext|>


<commit_before>'use strict';
/* global describe, it, beforeEach */
let assert = require('assert');
let cheerio = require('cheerio');
let Cycle = require('../../src/cycle');
let {Rx, h} = Cycle;

describe('renderAsHTML()', function () {
  it('should output HTML when given a simple vtree stream', function (done) {
    let vtree$ = Rx.Observable.just(h('div.test-element', ['Foobar']));
    let html$ = Cycle.renderAsHTML(vtree$);
    html$.subscribe(function (html) {
      let $ = cheerio.load(html);
      assert.ok($('*').first().is('div.test-element'));
      assert.strictEqual($('div.test-element').length, 1);
      assert.strictEqual($('div.test-element').text(), 'Foobar');
      done();
    });
  });

  it.skip('should render a simple nested custom element as HTML', function (done) {
    let MyElement = Cycle.createReactClass('MyElement', function () {
      return Rx.Observable.just(h('h3.myelementclass'));
    });
    var vtree$ = Rx.Observable.just(
      h('div.test-element', h(MyElement))
    );
    let html$ = Cycle.renderAsHTML(vtree$);
    html$.subscribe(function (html) {
      assert.strictEqual(html,
        '
' +
          '

' +
        '
'
      );
      done();
    });
  });

  it.skip('should render double nested custom elements as HTML', function (done) {
    Cycle.registerCustomElement('myelement', function () {
      return {
        vtree$: Rx.Observable.just(h('h3.myelementclass'))
      };
    });
    Cycle.registerCustomElement('nice-element', function () {
      return {
        vtree$: Rx.Observable.just(h('div.a-nice-element', [
          String('foobar'), h('myelement')
        ]))
      };
    });
    var vtree$ = Rx.Observable.just(h('div.test-element', [h('nice-element')]));
    let html$ = Cycle.renderAsHTML(vtree$);
    html$.subscribe(function (html) {
      assert.strictEqual(html,
        '
' +
          '
foobar

' +
        '
'
      );
      done();
    });
  });

  it.skip('should render a nested custom element with props as HTML', function (done) {
    Cycle.registerCustomElement('myelement', function (interactions, props) {
      return {
        vtree$: props.get('foobar')
          .map(foobar => h('h3.myelementclass', String(foobar).toUpperCase()))
      };
    });
    var vtree$ = Rx.Observable.just(
      h('div.test-element', [
        h('myelement', {foobar: 'yes'})
      ])
    );
    let html$ = Cycle.renderAsHTML(vtree$);
    html$.subscribe(function (html) {
      assert.strictEqual(html,
        '
' +
          '
YES
' +
        '
'
      );
      done();
    });
  });

  it.skip('should render a complex custom element tree as HTML', function (done) {
    Cycle.registerCustomElement('x-foo', function () {
      return {
        vtree$: Rx.Observable.just(h('h1.fooclass'))
      };
    });
    Cycle.registerCustomElement('x-bar', function () {
      return {
        vtree$: Rx.Observable.just(h('h2.barclass'))
      };
    });
    var vtree$ = Rx.Observable.just(
      h('.test-element', [
        h('div', [
          h('h2.a', 'a'),
          h('h4.b', 'b'),
          h('x-foo')
        ]),
        h('div', [
          h('h3.c', 'c'),
          h('div', [
            h('p.d', 'd'),
            h('x-bar')
          ])
        ])
      ])
    );
    let html$ = Cycle.renderAsHTML(vtree$);
    html$.subscribe(function (html) {
      assert.strictEqual(html,
        '
' +
          '
' +
            '
a
' +
            '
b
' +
            '

' +
          '
' +
          '
' +
            '
c
' +
            '
' +
              '
d
' +
              '

' +
            '
' +
          '
' +
        '
'
      );
      done();
    });
  });
});
<commit_msg>Complete tests for renderAsHTML #1<commit_after>'use strict';
/* global describe, it, beforeEach */
let assert = require('assert');
let cheerio = require('cheerio');
let Cycle = require('../../src/cycle');
let {Rx, h} = Cycle;

describe('renderAsHTML()', function () {
  it('should output HTML when given a simple vtree stream', function (done) {
    let vtree$ = Rx.Observable.just(h('div.test-element', ['Foobar']));
    let html$ = Cycle.renderAsHTML(vtree$);
    html$.subscribe(function (html) {
      let $ = cheerio.load(html);
      assert.ok($('*').first().is('div.test-element'));
      assert.strictEqual($('div.test-element').length, 1);
      assert.strictEqual($('div.test-element').text(), 'Foobar');
      done();
    });
  });

  it('should render a simple nested custom element as HTML', function (done) {
    let MyElement = Cycle.createReactClass('MyElement', function () {
      return Rx.Observable.just(h('h3.myelementclass'));
    });
    let vtree$ = Rx.Observable.just(
      h('div.test-element', null, h(MyElement))
    );
    let html$ = Cycle.renderAsHTML(vtree$);
    html$.subscribe(function (html) {
      let $ = cheerio.load(html);
      assert.ok($('*').first().is('div.test-element'));
      assert.strictEqual($('div.test-element').length, 1);
      assert.ok($('div.test-element').children().is('h3.myelementclass'));
      done();
    });
  });

  it('should render double nested custom elements as HTML', function (done) {
    let MyElement = Cycle.createReactClass('MyElement', function () {
      return Rx.Observable.just(h('h3.myelementclass'));
    });
    let NiceElement = Cycle.createReactClass('NiceElement', function () {
      return Rx.Observable.just(h('div.a-nice-element', null, [
        'foobar', h(MyElement)
      ]));
    });
    let vtree$ = Rx.Observable.just(h(
      'div.test-element', null, h(NiceElement)
    ));
    let html$ = Cycle.renderAsHTML(vtree$);
    html$.subscribe(function (html) {
      let $ = cheerio.load(html);
      assert.ok($('*').first().is('div.test-element'));
      assert.strictEqual($('div.test-element').length, 1);
      assert.ok($('div.test-element').children().is('div.a-nice-element'));
      assert.strictEqual($('div.a-nice-element').children().first().text(), 'foobar');
      assert.ok($('div.a-nice-element').children().eq(1).is('h3.myelementclass'));
      done();
    });
  });

  it('should render a nested custom element with props as HTML', function (done) {
    let MyElement = Cycle.createReactClass('MyElement', function (_, props) {
      return props.get('foobar')
        .map(foobar => h('h3.myelementclass', null, String(foobar).toUpperCase()))
    });
    let vtree$ = Rx.Observable.just(
      h('div.test-element', [
        h(MyElement, {foobar: 'yes'})
      ])
    );
    let html$ = Cycle.renderAsHTML(vtree$);
    html$.subscribe(function (html) {
      let $ = cheerio.load(html);
      assert.ok($('*').first().is('div.test-element'));
      assert.strictEqual($('div.test-element').length, 1);
      assert.ok($('div.test-element').children().is('h3.myelementclass'));
      assert.strictEqual($('h3.myelementclass').text(), 'YES');
      done();
    });
  });
});
<|endoftext|>


<commit_before>'use strict';

var async       = require('asyncawait/async');
var OrbitClient = require('../OrbitClient');
var Timer       = require('../Timer');

var host     = 'localhost:3006';
var username = 'testrunner';
var password = '';

let run = (async(() => {
  try {
    var channel = 'hello-world-test1'

    // Connect
    var orbit = OrbitClient.connect(host, username, password);

    // Delete channel and its data
    var result = orbit.channel(channel, '').delete();

    var messages = 100;
    var i = 0;
    while(i < messages) {
      var timer = new Timer(true);
      // Send a message
      var head = orbit.channel(channel, '').send('hello');
      console.log(i, head, timer.stop() + "ms");
      i ++;
    }

  } catch(e) {
    console.error("error:", e);
    console.error(e.stack);
    process.exit(1);
  }
}))();

module.exports = run;
<commit_msg>Change writeMessage example to show arbituary data write<commit_after>'use strict';

var async       = require('asyncawait/async');
var OrbitClient = require('../OrbitClient');
var Timer       = require('../Timer');

var host     = 'localhost:3006';
var username = 'testrunner';
var password = '';

let run = (async(() => {
  try {
    var channel = 'hello-world-test1'

    // Connect
    var orbit = OrbitClient.connect(host, username, password);

    // Delete channel and its data
    var result = orbit.channel(channel, '').delete();

    var messages = 100;
    var i = 0;
    while(i < messages) {
      var timer = new Timer(true);
      // Send a message
      var head = orbit.channel(channel, '').send(JSON.stringify({ omg: "hello" }));
      console.log(i, head, timer.stop() + "ms");
      i ++;
    }

  } catch(e) {
    console.error("error:", e);
    console.error(e.stack);
    process.exit(1);
  }
}))();

module.exports = run;
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var EventEmitter = require('./lib/event_emitter');
var inherits = require('util').inherits;

var errors = require('./errors');

function TChannelRequest(channel, options) {
    options = options || {};
    assert(!options.streamed, "streaming request federation not implemented");
    var self = this;
    EventEmitter.call(self);
    self.errorEvent = self.defineEvent('error');
    self.responseEvent = self.defineEvent('response');

    self.channel = channel;
    self.services = self.channel.services;
    self.logger = self.channel.logger;
    self.random = self.channel.random;
    self.timers = self.channel.timers;

    self.options = options;

    if (!self.options.retryFlags) {
        self.options.retryFlags = {
            never: false,
            onConnectionError: true,
            onTimeout: false
        };
    }

    self.triedRemoteAddrs = {};
    self.outReqs = [];
    self.timeout = self.options.timeout || TChannelRequest.defaultTimeout;
    self.limit = self.options.retryLimit || TChannelRequest.defaultRetryLimit;
    self.start = 0;
    self.end = 0;
    self.elapsed = 0;
    self.resendSanity = 0;
    self.trackPending = self.options.trackPending || false;

    self.serviceName = options.serviceName || '';
    self.headers = self.options.headers || {}; // so that as-foo can punch req.headers.X
    self.options.headers = self.headers; // for passing to peer.request(opts) later

    self.arg1 = null;
    self.arg2 = null;
    self.arg3 = null;

    self.err = null;
    self.res = null;

    self.errorEvent.on(self.onError);
    self.responseEvent.on(self.onResponse);
}

inherits(TChannelRequest, EventEmitter);

TChannelRequest.defaultRetryLimit = 5;
TChannelRequest.defaultTimeout = 5000;

TChannelRequest.prototype.type = 'tchannel.request';

TChannelRequest.prototype.onError = function onError(err, self) {
    if (!self.end) self.end = self.timers.now();
    self.err = err;
};

TChannelRequest.prototype.onResponse = function onResponse(res, self) {
    if (!self.end) self.end = self.timers.now();
    self.res = res;
};

TChannelRequest.prototype.hookupStreamCallback = function hookupCallback(callback) {
    throw new Error('not implemented');
};

TChannelRequest.prototype.hookupCallback = function hookupCallback(callback) {
    var self = this;
    if (callback.canStream) {
        return self.hookupStreamCallback(callback);
    }
    var called = false;

    self.errorEvent.on(onError);
    self.responseEvent.on(onResponse);

    function onError(err) {
        if (called) return;
        called = true;

        if (err.isErrorFrame) {
            self.channel.outboundCallsSystemErrorsStat.increment(1, {
                'target-service': self.serviceName,
                'service': self.headers.cn,
                // TODO should always be buffer
                'target-endpoint': String(self.arg1),
                'type': err.codeName
            });
        } else {
            self.channel.outboundCallsOperationalErrorsStat.increment(1, {
                'target-service': self.serviceName,
                'service': self.headers.cn,
                // TODO should always be buffer
                'target-endpoint': String(self.arg1),
                'type': err.type || 'unknown'
            });
        }

        callback(err, null, null, null);
    }

    function onResponse(res) {
        if (called) return;
        called = true;
        res.withArg23(function gotArg23(err, arg2, arg3) {
            if (res.ok) {
                self.channel.outboundCallsSuccessStat.increment(1, {
                    'target-service': self.serviceName,
                    'service': self.headers.cn,
                    // TODO should always be buffer
                    'target-endpoint': String(self.arg1)
                });
            } else {
                self.channel.outboundCallsAppErrorsStat.increment(1, {
                    'target-service': self.serviceName,
                    'service': self.headers.cn,
                    // TODO should always be buffer
                    'target-endpoint': String(self.arg1),
                    // TODO define transport header
                    // for application error type
                    'type': 'unknown'
                });
            }

            callback(err, res, arg2, arg3);
        });
    }

    return self;
};

TChannelRequest.prototype.choosePeer = function choosePeer() {
    var self = this;
    return self.channel.peers.choosePeer(self, self.options);
};

TChannelRequest.prototype.send = function send(arg1, arg2, arg3, callback) {
    var self = this;
    self.arg1 = arg1;
    self.arg2 = arg2;
    self.arg3 = arg3;
    if (callback) {
        self.hookupCallback(callback);
    }
    self.start = self.timers.now();
    self.resendSanity = self.limit + 1;

    self.resend();
};

TChannelRequest.prototype.resend = function resend() {
    var self = this;

    if (self.trackPending && self.checkPending()) return;

    if (self.checkTimeout()) return;

    var peer = self.choosePeer();
    if (!peer) {
        if (self.outReqs.length) {
            var lastReq = self.outReqs[self.outReqs.length - 1];
            if (lastReq.err) {
                self.errorEvent.emit(self, lastReq.err);
            } else {
                self.responseEvent.emit(self, lastReq.res);
            }
        } else {
            self.errorEvent.emit(self, errors.NoPeerAvailable());
        }
        return;
    }

    var opts = {};
    var keys = Object.keys(self.options);
    for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        opts[key] = self.options[key];
    }
    opts.timeout = self.timeout - self.elapsed;
    var outReq = peer.request(opts);
    self.outReqs.push(outReq);

    if (self.outReqs.length === 1) {
        self.channel.outboundCallsSentStat.increment(1, {
            'target-service': outReq.serviceName,
            'service': outReq.headers.cn,
            // TODO should always be buffer
            'target-endpoint': String(self.arg1)
        });
    } else {
        self.channel.outboundCallsRetriesStat.increment(1, {
            'target-service': outReq.serviceName,
            'service': outReq.headers.cn,
            // TODO should always be buffer
            'target-endpoint': String(self.arg1),
            'retry-count': self.outReqs.length - 1
        });
    }

    self.triedRemoteAddrs[outReq.remoteAddr] = (self.triedRemoteAddrs[outReq.remoteAddr] || 0) + 1;
    outReq.responseEvent.on(onResponse);
    outReq.errorEvent.on(onError);
    outReq.send(self.arg1, self.arg2, self.arg3);

    self.services.onRequest(self);

    function onError(err) {
        self.onSubreqError(err);
        self.services.onRequestError(self);
    }

    function onResponse(res) {
        self.onSubreqResponse(res);
    }
};

TChannelRequest.prototype.onSubreqError = function onSubreqError(err) {
    var self = this;
    if (self.checkTimeout(err)) return;
    if (self.shouldRetryError(err)) {
        self.deferResend();
    } else {
        self.errorEvent.emit(self, err);
    }
};

TChannelRequest.prototype.onSubreqResponse = function onSubreqResponse(res) {
    var self = this;
    if (self.checkTimeout(null, res)) return;
    if (res.ok) {
        self.responseEvent.emit(self, res);
    } else if (self.options.shouldApplicationRetry) {
        self.maybeAppRetry(res);
    } else {
        self.responseEvent.emit(self, res);
    }
};

TChannelRequest.prototype.deferResend = function deferResend() {
    var self = this;
    if (--self.resendSanity <= 0) {
        self.errorEvent.emit(self, new Error('TChannelRequest out of resend sanity'));
    } else {
        process.nextTick(doResend);
    }
    function doResend() {
        self.resend();
    }
};

TChannelRequest.prototype.checkPending = function checkPending() {
    var self = this;
    var err = self.services.errorIfExceedsMaxPending(self);
    if (err) {
        self.errorEvent.emit(self, err);
        return true;
    }
    return false;
};

TChannelRequest.prototype.checkTimeout = function checkTimeout(err, res) {
    var self = this;
    var now = self.timers.now();
    self.elapsed = now - self.start;
    if (self.elapsed < self.timeout) return false;
    if (err) {
        if (!self.err) {
            self.errorEvent.emit(self, err);
        }
    } else if (res) {
        if (!self.err && !self.res) {
            self.responseEvent.emit(self, res);
        }
    } else if (!self.err) {
        self.errorEvent.emit(self, errors.TimeoutError({
            start: self.start,
            elapsed: self.elapsed,
            timeout: self.timeout
        }));
    }
    return true;
};

TChannelRequest.prototype.shouldRetryError = function shouldRetryError(err) {
    var self = this;

    if (self.outReqs.length >= self.limit) {
        return false;
    }

    if (self.options.retryFlags.never) {
        return false;
    }

    if (err) {
        switch (err.type) {
            case 'tchannel.bad-request':
            case 'tchannel.canceled':
                return false;

            case 'tchannel.busy':
            case 'tchannel.declined':
                return true;

            case 'tchannel.timeout':
                return !!self.options.retryFlags.onTimeout;

            case 'tchannel.socket':
            case 'tchannel.socket-closed':
            case 'tchannel.network':
            case 'tchannel.protocol':
            case 'tchannel.unexpected':
                return !!self.options.retryFlags.onConnectionError;

            default:
                self.logger.error('unknown error type in request retry', {
                    error: err
                });
                return true;
        }
    }

    return false;
};

TChannelRequest.prototype.maybeAppRetry = function maybeAppRetry(res) {
    var self = this;
    self.options.shouldApplicationRetry(self, res, retry, done);

    function retry() {
        if (self.checkTimeout(null, res)) return;
        self.deferResend();
    }

    function done(err) {
        if (err) {
            self.errorEvent.emit(self, err);
        } else {
            self.responseEvent.emit(self, res);
        }
    }
};

module.exports = TChannelRequest;
<commit_msg>request: emit latency stat<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var EventEmitter = require('./lib/event_emitter');
var inherits = require('util').inherits;

var errors = require('./errors');

function TChannelRequest(channel, options) {
    options = options || {};
    assert(!options.streamed, "streaming request federation not implemented");
    var self = this;
    EventEmitter.call(self);
    self.errorEvent = self.defineEvent('error');
    self.responseEvent = self.defineEvent('response');

    self.channel = channel;
    self.services = self.channel.services;
    self.logger = self.channel.logger;
    self.random = self.channel.random;
    self.timers = self.channel.timers;

    self.options = options;

    if (!self.options.retryFlags) {
        self.options.retryFlags = {
            never: false,
            onConnectionError: true,
            onTimeout: false
        };
    }

    self.triedRemoteAddrs = {};
    self.outReqs = [];
    self.timeout = self.options.timeout || TChannelRequest.defaultTimeout;
    self.limit = self.options.retryLimit || TChannelRequest.defaultRetryLimit;
    self.start = 0;
    self.end = 0;
    self.elapsed = 0;
    self.resendSanity = 0;
    self.trackPending = self.options.trackPending || false;

    self.serviceName = options.serviceName || '';
    self.headers = self.options.headers || {}; // so that as-foo can punch req.headers.X
    self.options.headers = self.headers; // for passing to peer.request(opts) later

    self.arg1 = null;
    self.arg2 = null;
    self.arg3 = null;

    self.err = null;
    self.res = null;

    self.errorEvent.on(self.onError);
    self.responseEvent.on(self.onResponse);
}

inherits(TChannelRequest, EventEmitter);

TChannelRequest.defaultRetryLimit = 5;
TChannelRequest.defaultTimeout = 5000;

TChannelRequest.prototype.type = 'tchannel.request';

TChannelRequest.prototype.onError = function onError(err, self) {
    if (!self.end) self.end = self.timers.now();
    self.err = err;
};

TChannelRequest.prototype.onResponse = function onResponse(res, self) {
    if (!self.end) self.end = self.timers.now();
    self.res = res;
};

TChannelRequest.prototype.hookupStreamCallback = function hookupCallback(callback) {
    throw new Error('not implemented');
};

TChannelRequest.prototype.hookupCallback = function hookupCallback(callback) {
    var self = this;
    if (callback.canStream) {
        return self.hookupStreamCallback(callback);
    }
    var called = false;

    self.errorEvent.on(onError);
    self.responseEvent.on(onResponse);

    function onError(err) {
        if (called) return;
        called = true;

        if (err.isErrorFrame) {
            self.channel.outboundCallsSystemErrorsStat.increment(1, {
                'target-service': self.serviceName,
                'service': self.headers.cn,
                // TODO should always be buffer
                'target-endpoint': String(self.arg1),
                'type': err.codeName
            });
        } else {
            self.channel.outboundCallsOperationalErrorsStat.increment(1, {
                'target-service': self.serviceName,
                'service': self.headers.cn,
                // TODO should always be buffer
                'target-endpoint': String(self.arg1),
                'type': err.type || 'unknown'
            });
        }

        emitLatency();

        callback(err, null, null, null);
    }

    function onResponse(res) {
        if (called) return;
        called = true;
        res.withArg23(function gotArg23(err, arg2, arg3) {
            if (res.ok) {
                self.channel.outboundCallsSuccessStat.increment(1, {
                    'target-service': self.serviceName,
                    'service': self.headers.cn,
                    // TODO should always be buffer
                    'target-endpoint': String(self.arg1)
                });
            } else {
                self.channel.outboundCallsAppErrorsStat.increment(1, {
                    'target-service': self.serviceName,
                    'service': self.headers.cn,
                    // TODO should always be buffer
                    'target-endpoint': String(self.arg1),
                    // TODO define transport header
                    // for application error type
                    'type': 'unknown'
                });
            }

            emitLatency();

            callback(err, res, arg2, arg3);
        });
    }

    function emitLatency() {
        var latency = self.end - self.start;
        self.channel.outboundCallsLatencyStat.add(latency, {
            'target-service': self.serviceName,
            'service': self.headers.cn,
            // TODO should always be buffer
            'target-endpoint': String(self.arg1)
        });
    }

    return self;
};

TChannelRequest.prototype.choosePeer = function choosePeer() {
    var self = this;
    return self.channel.peers.choosePeer(self, self.options);
};

TChannelRequest.prototype.send = function send(arg1, arg2, arg3, callback) {
    var self = this;
    self.arg1 = arg1;
    self.arg2 = arg2;
    self.arg3 = arg3;
    if (callback) {
        self.hookupCallback(callback);
    }
    self.start = self.timers.now();
    self.resendSanity = self.limit + 1;

    self.resend();
};

TChannelRequest.prototype.resend = function resend() {
    var self = this;

    if (self.trackPending && self.checkPending()) return;

    if (self.checkTimeout()) return;

    var peer = self.choosePeer();
    if (!peer) {
        if (self.outReqs.length) {
            var lastReq = self.outReqs[self.outReqs.length - 1];
            if (lastReq.err) {
                self.errorEvent.emit(self, lastReq.err);
            } else {
                self.responseEvent.emit(self, lastReq.res);
            }
        } else {
            self.errorEvent.emit(self, errors.NoPeerAvailable());
        }
        return;
    }

    var opts = {};
    var keys = Object.keys(self.options);
    for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        opts[key] = self.options[key];
    }
    opts.timeout = self.timeout - self.elapsed;
    var outReq = peer.request(opts);
    self.outReqs.push(outReq);

    if (self.outReqs.length === 1) {
        self.channel.outboundCallsSentStat.increment(1, {
            'target-service': outReq.serviceName,
            'service': outReq.headers.cn,
            // TODO should always be buffer
            'target-endpoint': String(self.arg1)
        });
    } else {
        self.channel.outboundCallsRetriesStat.increment(1, {
            'target-service': outReq.serviceName,
            'service': outReq.headers.cn,
            // TODO should always be buffer
            'target-endpoint': String(self.arg1),
            'retry-count': self.outReqs.length - 1
        });
    }

    self.triedRemoteAddrs[outReq.remoteAddr] = (self.triedRemoteAddrs[outReq.remoteAddr] || 0) + 1;
    outReq.responseEvent.on(onResponse);
    outReq.errorEvent.on(onError);
    outReq.send(self.arg1, self.arg2, self.arg3);

    self.services.onRequest(self);

    function onError(err) {
        self.onSubreqError(err);
        self.services.onRequestError(self);
    }

    function onResponse(res) {
        self.onSubreqResponse(res);
    }
};

TChannelRequest.prototype.onSubreqError = function onSubreqError(err) {
    var self = this;
    if (self.checkTimeout(err)) return;
    if (self.shouldRetryError(err)) {
        self.deferResend();
    } else {
        self.errorEvent.emit(self, err);
    }
};

TChannelRequest.prototype.onSubreqResponse = function onSubreqResponse(res) {
    var self = this;
    if (self.checkTimeout(null, res)) return;
    if (res.ok) {
        self.responseEvent.emit(self, res);
    } else if (self.options.shouldApplicationRetry) {
        self.maybeAppRetry(res);
    } else {
        self.responseEvent.emit(self, res);
    }
};

TChannelRequest.prototype.deferResend = function deferResend() {
    var self = this;
    if (--self.resendSanity <= 0) {
        self.errorEvent.emit(self, new Error('TChannelRequest out of resend sanity'));
    } else {
        process.nextTick(doResend);
    }
    function doResend() {
        self.resend();
    }
};

TChannelRequest.prototype.checkPending = function checkPending() {
    var self = this;
    var err = self.services.errorIfExceedsMaxPending(self);
    if (err) {
        self.errorEvent.emit(self, err);
        return true;
    }
    return false;
};

TChannelRequest.prototype.checkTimeout = function checkTimeout(err, res) {
    var self = this;
    var now = self.timers.now();
    self.elapsed = now - self.start;
    if (self.elapsed < self.timeout) return false;
    if (err) {
        if (!self.err) {
            self.errorEvent.emit(self, err);
        }
    } else if (res) {
        if (!self.err && !self.res) {
            self.responseEvent.emit(self, res);
        }
    } else if (!self.err) {
        self.errorEvent.emit(self, errors.TimeoutError({
            start: self.start,
            elapsed: self.elapsed,
            timeout: self.timeout
        }));
    }
    return true;
};

TChannelRequest.prototype.shouldRetryError = function shouldRetryError(err) {
    var self = this;

    if (self.outReqs.length >= self.limit) {
        return false;
    }

    if (self.options.retryFlags.never) {
        return false;
    }

    if (err) {
        switch (err.type) {
            case 'tchannel.bad-request':
            case 'tchannel.canceled':
                return false;

            case 'tchannel.busy':
            case 'tchannel.declined':
                return true;

            case 'tchannel.timeout':
                return !!self.options.retryFlags.onTimeout;

            case 'tchannel.socket':
            case 'tchannel.socket-closed':
            case 'tchannel.network':
            case 'tchannel.protocol':
            case 'tchannel.unexpected':
                return !!self.options.retryFlags.onConnectionError;

            default:
                self.logger.error('unknown error type in request retry', {
                    error: err
                });
                return true;
        }
    }

    return false;
};

TChannelRequest.prototype.maybeAppRetry = function maybeAppRetry(res) {
    var self = this;
    self.options.shouldApplicationRetry(self, res, retry, done);

    function retry() {
        if (self.checkTimeout(null, res)) return;
        self.deferResend();
    }

    function done(err) {
        if (err) {
            self.errorEvent.emit(self, err);
        } else {
            self.responseEvent.emit(self, res);
        }
    }
};

module.exports = TChannelRequest;
<|endoftext|>


<commit_before>"use strict";

const chai = require("chai");

const expect = chai.expect;
const parseDomain = require("../lib/parseDomain.js");

chai.config.includeStack = true;

describe("parseDomain(url)", () => {
    it("should remove the protocol", () => {
        expect(parseDomain("http://example.com")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
        expect(parseDomain("//example.com")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
        expect(parseDomain("https://example.com")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    it("should remove sub-domains", () => {
        expect(parseDomain("www.example.com")).to.eql({
            subdomain: "www",
            domain: "example",
            tld: "com",
        });
        expect(parseDomain("www.some.other.subdomain.example.com")).to.eql({
            subdomain: "www.some.other.subdomain",
            domain: "example",
            tld: "com",
        });
    });

    it("should remove the path", () => {
        expect(parseDomain("example.com/some/path?and&query")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
        expect(parseDomain("example.com/")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    it("should remove the query string", () => {
        expect(parseDomain("example.com?and&query")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    it("should remove special characters", () => {
        expect(parseDomain("http://m.example.com\r")).to.eql({
            subdomain: "m",
            domain: "example",
            tld: "com",
        });
    });

    it("should remove the port", () => {
        expect(parseDomain("example.com:8080")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    it("should remove the authentication", () => {
        expect(parseDomain("user:password@example.com")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    it("should allow @ characters in the path", () => {
        expect(parseDomain("https://medium.com/@username/")).to.eql({
            subdomain: "",
            domain: "medium",
            tld: "com",
        });
    });

    it("should also work with three-level domains like .co.uk", () => {
        expect(parseDomain("www.example.co.uk")).to.eql({
            subdomain: "www",
            domain: "example",
            tld: "co.uk",
        });
    });

    it("should not include private domains like blogspot.com by default", () => {
        expect(parseDomain("foo.blogspot.com")).to.eql({
            subdomain: "foo",
            domain: "blogspot",
            tld: "com",
        });
    });

    it("should include private tlds", () => {
        expect(parseDomain("foo.blogspot.com", {privateTlds: true})).to.eql({
            subdomain: "",
            domain: "foo",
            tld: "blogspot.com",
        });
    });

    it("should work when all url parts are present", () => {
        expect(parseDomain("https://user@www.some.other.subdomain.example.co.uk:8080/some/path?and&query#hash")).to.eql(
            {
                subdomain: "www.some.other.subdomain",
                domain: "example",
                tld: "co.uk",
            }
        );
    });

    it("should also work with the minimum", () => {
        expect(parseDomain("example.com")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    it("should return null if the given url contains an unsupported top-level domain", () => {
        expect(parseDomain("example.kk")).to.equal(null);
    });

    it("should return null if the given value is not a string", () => {
        expect(parseDomain(undefined)).to.equal(null);
        expect(parseDomain({})).to.equal(null);
    });

    it("should return null if the given string is not a valid URL", () => {
        expect(parseDomain("\xa0")).to.equal(null);
        expect(parseDomain("")).to.equal(null);
        expect(parseDomain(" ")).to.equal(null);
        expect(parseDomain("http://hell.d\ne.ibm.com")).to.equal(null);
    });

    it("should return null if the given is an empty string with a space character", () => {
        expect(parseDomain(" ")).to.equal(null);
    });

    it("should work with domains that could match multiple tlds", () => {
        expect(parseDomain("http://hello.de.ibm.com")).to.eql({
            subdomain: "hello.de",
            domain: "ibm",
            tld: "com",
        });
    });

    it("should work with custom top-level domains (eg .local)", () => {
        const options = {customTlds: ["local"]};

        expect(parseDomain("mymachine.local", options)).to.eql({
            subdomain: "",
            domain: "mymachine",
            tld: "local",
        });

        // Sanity checks if the option does not misbehave
        expect(parseDomain("mymachine.local")).to.eql(null);
        expect(parseDomain("http://example.com", options)).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    // See https://github.com/peerigon/parse-domain/pull/65
    it("should parse police.uk as tld", () => {
        expect(parseDomain("example.police.uk")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "police.uk",
        });
    });

    it("should also work with custom top-level domains passed as regexps", () => {
        const options = {customTlds: /(\.local|localhost)$/};

        expect(parseDomain("mymachine.local", options)).to.eql({
            subdomain: "",
            domain: "mymachine",
            tld: "local",
        });
        expect(parseDomain("localhost", options)).to.eql({
            subdomain: "",
            domain: "",
            tld: "localhost",
        });
        expect(parseDomain("localhost:8080", options)).to.eql({
            subdomain: "",
            domain: "",
            tld: "localhost",
        });

        // Sanity checks if the option does not misbehave
        expect(parseDomain("mymachine.local")).to.eql(null);
        expect(parseDomain("http://example.com", options)).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });
});
<commit_msg>chore: Add tests for real-world use cases.<commit_after>"use strict";

const chai = require("chai");

const expect = chai.expect;
const parseDomain = require("../lib/parseDomain.js");

chai.config.includeStack = true;

describe("parseDomain(url)", () => {
    it("should remove the protocol", () => {
        expect(parseDomain("http://example.com")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
        expect(parseDomain("//example.com")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
        expect(parseDomain("https://example.com")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    it("should remove sub-domains", () => {
        expect(parseDomain("www.example.com")).to.eql({
            subdomain: "www",
            domain: "example",
            tld: "com",
        });
        expect(parseDomain("www.some.other.subdomain.example.com")).to.eql({
            subdomain: "www.some.other.subdomain",
            domain: "example",
            tld: "com",
        });
    });

    it("should remove the path", () => {
        expect(parseDomain("example.com/some/path?and&query")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
        expect(parseDomain("example.com/")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    it("should remove the query string", () => {
        expect(parseDomain("example.com?and&query")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    it("should remove special characters", () => {
        expect(parseDomain("http://m.example.com\r")).to.eql({
            subdomain: "m",
            domain: "example",
            tld: "com",
        });
    });

    it("should remove the port", () => {
        expect(parseDomain("example.com:8080")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    it("should remove the authentication", () => {
        expect(parseDomain("user:password@example.com")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    it("should allow @ characters in the path", () => {
        expect(parseDomain("https://medium.com/@username/")).to.eql({
            subdomain: "",
            domain: "medium",
            tld: "com",
        });
    });

    it("should also work with three-level domains like .co.uk", () => {
        expect(parseDomain("www.example.co.uk")).to.eql({
            subdomain: "www",
            domain: "example",
            tld: "co.uk",
        });
    });

    it("should not include private domains like blogspot.com by default", () => {
        expect(parseDomain("foo.blogspot.com")).to.eql({
            subdomain: "foo",
            domain: "blogspot",
            tld: "com",
        });
    });

    it("should include private tlds", () => {
        expect(parseDomain("foo.blogspot.com", {privateTlds: true})).to.eql({
            subdomain: "",
            domain: "foo",
            tld: "blogspot.com",
        });
    });

    it("should work when all url parts are present", () => {
        expect(parseDomain("https://user@www.some.other.subdomain.example.co.uk:8080/some/path?and&query#hash")).to.eql(
            {
                subdomain: "www.some.other.subdomain",
                domain: "example",
                tld: "co.uk",
            }
        );
    });

    it("should also work with the minimum", () => {
        expect(parseDomain("example.com")).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    it("should return null if the given url contains an unsupported top-level domain", () => {
        expect(parseDomain("example.kk")).to.equal(null);
    });

    it("should return null if the given value is not a string", () => {
        expect(parseDomain(undefined)).to.equal(null);
        expect(parseDomain({})).to.equal(null);
    });

    it("should return null if the given string is not a valid URL", () => {
        expect(parseDomain("\xa0")).to.equal(null);
        expect(parseDomain("")).to.equal(null);
        expect(parseDomain(" ")).to.equal(null);
        expect(parseDomain("http://hell.d\ne.ibm.com")).to.equal(null);
    });

    it("should return null if the given is an empty string with a space character", () => {
        expect(parseDomain(" ")).to.equal(null);
    });

    it("should work with domains that could match multiple tlds", () => {
        expect(parseDomain("http://hello.de.ibm.com")).to.eql({
            subdomain: "hello.de",
            domain: "ibm",
            tld: "com",
        });
    });

    it("should work with custom top-level domains (eg .local)", () => {
        const options = {customTlds: ["local"]};

        expect(parseDomain("mymachine.local", options)).to.eql({
            subdomain: "",
            domain: "mymachine",
            tld: "local",
        });

        // Sanity checks if the option does not misbehave
        expect(parseDomain("mymachine.local")).to.eql(null);
        expect(parseDomain("http://example.com", options)).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    it("should also work with custom top-level domains passed as regexps", () => {
        const options = {customTlds: /(\.local|localhost)$/};

        expect(parseDomain("mymachine.local", options)).to.eql({
            subdomain: "",
            domain: "mymachine",
            tld: "local",
        });
        expect(parseDomain("localhost", options)).to.eql({
            subdomain: "",
            domain: "",
            tld: "localhost",
        });
        expect(parseDomain("localhost:8080", options)).to.eql({
            subdomain: "",
            domain: "",
            tld: "localhost",
        });

        // Sanity checks if the option does not misbehave
        expect(parseDomain("mymachine.local")).to.eql(null);
        expect(parseDomain("http://example.com", options)).to.eql({
            subdomain: "",
            domain: "example",
            tld: "com",
        });
    });

    describe("real-world use cases", () => {
        // See https://github.com/peerigon/parse-domain/pull/65
        it("should parse police.uk as tld", () => {
            expect(parseDomain("example.police.uk")).to.eql({
                subdomain: "",
                domain: "example",
                tld: "police.uk",
            });
        });

        // See https://github.com/peerigon/parse-domain/issues/67
        it.skip("should parse gouv.fr as tld", () => {
            expect(parseDomain("dev.classea12.beta.gouv.fr", {privateTlds: true})).to.eql({
                tld: "gouv.fr",
                domain: "beta",
                subdomain: "dev.classea12",
            });
        });
    });
});
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var DEFAULT_RETRY_LIMIT = 5;

function TChannelRequest(channel, options) {
    options = options || {};
    var self = this;
    if (options.streamed) {
        throw new Error('streaming request federation not supported');
    }
    self.channel = channel;
    self.options = options;
    self.outReqs = [];
    self.timeout = self.options.timeout;
    self.limit = self.options.retryLimit || DEFAULT_RETRY_LIMIT;
    self.start = 0;
    self.end = 0;
    self.elapsed = 0;

    self.headers = self.options.headers || {}; // so that as-foo can punch req.headers.X
    self.options.headers = self.headers; // for passing to peer.request(opts) later

    self.arg1 = null;
    self.arg2 = null;
    self.arg3 = null;
    self._callback = null;
}

TChannelRequest.prototype.type = 'tchannel.request';

TChannelRequest.prototype.makeOutRequest = function makeOutRequest() {
    var self = this;
    var outReq = self.channel.peers.request(self, self.options);
    self.outReqs.push(outReq);
    return outReq;
};

TChannelRequest.prototype.send = function send(arg1, arg2, arg3, callback) {
    var self = this;
    self.arg1 = arg1;
    self.arg2 = arg2;
    self.arg3 = arg3;
    self._callback = callback;
    var outReq = self.makeOutRequest();
    self.start = self.channel.timers.now();
    outReq.send(arg1, arg2, arg3, outReqDone);
    function outReqDone(err, res, arg2, arg3) {
        self.onReqDone(err, res, arg2, arg3);
    }
};

TChannelRequest.prototype.resend = function resend() {
    var self = this;
    var outReq = self.makeOutRequest();
    outReq.send(self.arg1, self.arg2, self.arg3, outReqRedone);
    function outReqRedone(err, res, arg2, arg3) {
        self.onReqDone(err, res, arg2, arg3);
    }
};

TChannelRequest.prototype.onReqDone = function onReqDone(err, res, arg2, arg3) {
    var self = this;
    var now = self.channel.timers.now();
    self.elapsed = now - self.start;
    if (self.elapsed < self.timeout &&
        self.shouldRetry(err, res, arg2, arg3)) {
        process.nextTick(deferResend);
    } else {
        self.end = now;
        self._callback(err, res, arg2, arg3);
    }
    function deferResend() {
        self.resend();
    }
};

TChannelRequest.prototype.shouldRetry = function shouldRetry(err, res, arg2, arg3) {
    var self = this;

    if (self.outReqs.length >= self.retryLimit) {
        return false;
    }

    if (err) {
        switch (err.type) {
            case 'tchannel.bad-request':
            case 'tchannel.canceled':
                return false;

            case 'tchannel.socket':
            case 'tchannel.timeout':
            case 'tchannel.busy':
            case 'tchannel.declined':
            case 'tchannel.unexpected':
            case 'tchannel.protocol':
                return true;

            default:
                self.channel.logger.error('unknown error type in request retry', {
                    error: err
                });
                return true;
        }
    }

    if (!res.ok && self.options.shouldApplicationRetry) {
        return self.options.shouldApplicationRetry(self, res, arg2, arg3);
    }

    return false;
};

module.exports = TChannelRequest;
<commit_msg>TChannelRequest: reuse resend in send<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var DEFAULT_RETRY_LIMIT = 5;

function TChannelRequest(channel, options) {
    options = options || {};
    var self = this;
    if (options.streamed) {
        throw new Error('streaming request federation not supported');
    }
    self.channel = channel;
    self.options = options;
    self.outReqs = [];
    self.timeout = self.options.timeout;
    self.limit = self.options.retryLimit || DEFAULT_RETRY_LIMIT;
    self.start = 0;
    self.end = 0;
    self.elapsed = 0;

    self.headers = self.options.headers || {}; // so that as-foo can punch req.headers.X
    self.options.headers = self.headers; // for passing to peer.request(opts) later

    self.arg1 = null;
    self.arg2 = null;
    self.arg3 = null;
    self._callback = null;
}

TChannelRequest.prototype.type = 'tchannel.request';

TChannelRequest.prototype.makeOutRequest = function makeOutRequest() {
    var self = this;
    var outReq = self.channel.peers.request(self, self.options);
    self.outReqs.push(outReq);
    return outReq;
};

TChannelRequest.prototype.send = function send(arg1, arg2, arg3, callback) {
    var self = this;
    self.arg1 = arg1;
    self.arg2 = arg2;
    self.arg3 = arg3;
    self._callback = callback;
    self.start = self.channel.timers.now();
    self.resend();
};

TChannelRequest.prototype.resend = function resend() {
    var self = this;
    var outReq = self.makeOutRequest();
    outReq.send(self.arg1, self.arg2, self.arg3, outReqRedone);
    function outReqRedone(err, res, arg2, arg3) {
        self.onReqDone(err, res, arg2, arg3);
    }
};

TChannelRequest.prototype.onReqDone = function onReqDone(err, res, arg2, arg3) {
    var self = this;
    var now = self.channel.timers.now();
    self.elapsed = now - self.start;
    if (self.elapsed < self.timeout &&
        self.shouldRetry(err, res, arg2, arg3)) {
        process.nextTick(deferResend);
    } else {
        self.end = now;
        self._callback(err, res, arg2, arg3);
    }
    function deferResend() {
        self.resend();
    }
};

TChannelRequest.prototype.shouldRetry = function shouldRetry(err, res, arg2, arg3) {
    var self = this;

    if (self.outReqs.length >= self.retryLimit) {
        return false;
    }

    if (err) {
        switch (err.type) {
            case 'tchannel.bad-request':
            case 'tchannel.canceled':
                return false;

            case 'tchannel.socket':
            case 'tchannel.timeout':
            case 'tchannel.busy':
            case 'tchannel.declined':
            case 'tchannel.unexpected':
            case 'tchannel.protocol':
                return true;

            default:
                self.channel.logger.error('unknown error type in request retry', {
                    error: err
                });
                return true;
        }
    }

    if (!res.ok && self.options.shouldApplicationRetry) {
        return self.options.shouldApplicationRetry(self, res, arg2, arg3);
    }

    return false;
};

module.exports = TChannelRequest;
<|endoftext|>