<commit_before>'use strict';

var globals = require('./globals'),
  consts = require('./consts'),
  util = require('./util'),
  vlfield = require('./field'),
  vlenc = require('./enc'),
  schema = require('./schema/schema'),
  time = require('./compile/time');

var Encoding = module.exports = (function() {

  function Encoding(marktype, enc, config, filter, theme) {
    var defaults = schema.instantiate();

    var spec = {
      marktype: marktype,
      enc: enc,
      cfg: config,
      filter: filter || []
    };

    // type to bitcode
    for (var e in defaults.enc) {
      defaults.enc[e].type = consts.dataTypes[defaults.enc[e].type];
    }

    var specExtended = schema.util.merge(defaults, theme || {}, spec) ;

    this._marktype = specExtended.marktype;
    this._enc = specExtended.enc;
    this._cfg = specExtended.cfg;
    this._filter = specExtended.filter;
  }

  var proto = Encoding.prototype;

  proto.marktype = function() {
    return this._marktype;
  };

  proto.is = function(m) {
    return this._marktype === m;
  };

  proto.has = function(encType) {
    // equivalent to calling vlenc.has(this._enc, encType)
    return this._enc[encType].name !== undefined;
  };

  proto.enc = function(x) {
    return this._enc[x];
  };

  proto.filter = function() {
    var filterNull = [],
      fields = this.fields(),
      self = this;

    util.forEach(fields, function(fieldList, fieldName) {
      if (fieldName === '*') return; //count

      if ((self.config('filterNull').Q && fieldList.containsType[Q]) ||
          (self.config('filterNull').T && fieldList.containsType[T]) ||
          (self.config('filterNull').O && fieldList.containsType[O])) {
        filterNull.push({
          operands: [fieldName],
          operator: 'notNull'
        });
      }
    });

    return filterNull.concat(this._filter);
  };

  // get "field" property for vega
  proto.field = function(x, nodata, nofn) {
    if (!this.has(x)) return null;

    var f = (nodata ? '' : 'data.');

    if (this._enc[x].aggr === 'count') {
      return f + 'count';
    } else if (!nofn && this._enc[x].bin) {
      return f + 'bin_' + this._enc[x].name;
    } else if (!nofn && this._enc[x].aggr) {
      return f + this._enc[x].aggr + '_' + this._enc[x].name;
    } else if (!nofn && this._enc[x].fn) {
      return f + this._enc[x].fn + '_' + this._enc[x].name;
    } else {
      return f + this._enc[x].name;
    }
  };

  proto.fieldName = function(x) {
    return this._enc[x].name;
  };

  /*
   * return key-value pairs of field name and list of fields of that field name
   */
  proto.fields = function() {
    return vlenc.fields(this._enc);
  };

  proto.fieldTitle = function(x) {
    if (vlfield.isCount(this._enc[x])) {
      return vlfield.count.displayName;
    }
    var fn = this._enc[x].aggr || this._enc[x].fn || (this._enc[x].bin && "bin");
    if (fn) {
      return fn.toUpperCase() + '(' + this._enc[x].name + ')';
    } else {
      return this._enc[x].name;
    }
  };

  proto.scale = function(x) {
    return this._enc[x].scale || {};
  };

  proto.axis = function(x) {
    return this._enc[x].axis || {};
  };

  proto.band = function(x) {
    return this._enc[x].band || {};
  };

  proto.bandSize = function(encType, useSmallBand) {
    useSmallBand = useSmallBand ||
      //isBandInSmallMultiples
      (encType === Y && this.has(ROW) && this.has(Y)) ||
      (encType === X && this.has(COL) && this.has(X));

    // if band.size is explicitly specified, follow the specification, otherwise draw value from config.
    return this.band(encType).size ||
      this.config(useSmallBand ? 'smallBandSize' : 'largeBandSize');
  };

  proto.aggr = function(x) {
    return this._enc[x].aggr;
  };

  // returns false if binning is disabled, otherwise an object with binning properties
  proto.bin = function(x) {
    var bin = this._enc[x].bin;
    if (bin === {})
      return false;
    if (bin === true)
      return {
        maxbins: schema.MAXBINS_DEFAULT
      };
    return bin;
  };

  proto.legend = function(x) {
    return this._enc[x].legend;
  };

  proto.value = function(x) {
    return this._enc[x].value;
  };

  proto.fn = function(x) {
    return this._enc[x].fn;
  };

   proto.sort = function(x) {
    return this._enc[x].sort;
  };

  proto.any = function(f) {
    return util.any(this._enc, f);
  };

  proto.all = function(f) {
    return util.all(this._enc, f);
  };

  proto.length = function() {
    return util.keys(this._enc).length;
  };

  proto.map = function(f) {
    return vlenc.map(this._enc, f);
  };

  proto.reduce = function(f, init) {
    return vlenc.reduce(this._enc, f, init);
  };

  proto.forEach = function(f) {
    return vlenc.forEach(this._enc, f);
  };

  proto.type = function(et) {
    return this.has(et) ? this._enc[et].type : null;
  };

  proto.role = function(et) {
    return this.has(et) ? vlfield.role(this._enc[et]) : null;
  };

  proto.text = function(prop) {
    var text = this._enc[TEXT].text;
    return prop ? text[prop] : text;
  };

  proto.font = function(prop) {
    var font = this._enc[TEXT].font;
    return prop ? font[prop] : font;
  };

  proto.isType = function(x, type) {
    var field = this.enc(x);
    return field && Encoding.isType(field, type);
  };

  Encoding.isType = function (fieldDef, type) {
    return (fieldDef.type & type) > 0;
  };

  Encoding.isOrdinalScale = function(encoding, encType) {
    return vlfield.isOrdinalScale(encoding.enc(encType), true);
  };

  Encoding.isDimension = function(encoding, encType) {
    return vlfield.isDimension(encoding.enc(encType), true);
  };

  Encoding.isMeasure = function(encoding, encType) {
    return vlfield.isMeasure(encoding.enc(encType), true);
  };

  proto.isOrdinalScale = function(encType) {
    return this.has(encType) && Encoding.isOrdinalScale(this, encType);
  };

  proto.isDimension = function(encType) {
    return this.has(encType) && Encoding.isDimension(this, encType);
  };

  proto.isMeasure = function(encType) {
    return this.has(encType) && Encoding.isMeasure(this, encType);
  };

  proto.isAggregate = function() {
    return vlenc.isAggregate(this._enc);
  };

  Encoding.isAggregate = function(spec) {
    return vlenc.isAggregate(spec.enc);
  };

  Encoding.alwaysNoOcclusion = function(spec, stats) {
    // FIXME raw OxQ with # of rows = # of O
    return vlenc.isAggregate(spec.enc);
  };

  Encoding.isStack = function(spec) {
    // FIXME update this once we have control for stack ...
    return (spec.marktype === 'bar' || spec.marktype === 'area') &&
      spec.enc.color;
  };

  proto.isStack = function() {
    // FIXME update this once we have control for stack ...
    return (this.is('bar') || this.is('area')) && this.has('color');
  };

  proto.cardinality = function(encType, stats) {
    return vlfield.cardinality(this.enc(encType), stats, this.config('filterNull'), true);
  };

  proto.isRaw = function() {
    return !this.isAggregate();
  };

  proto.config = function(name) {
    return this._cfg[name];
  };

  proto.toSpec = function(excludeConfig) {
    var enc = util.duplicate(this._enc),
      spec;

    // convert type's bitcode to type name
    for (var e in enc) {
      enc[e].type = consts.dataTypeNames[enc[e].type];
    }

    spec = {
      marktype: this._marktype,
      enc: enc,
      filter: this._filter
    };

    if (!excludeConfig) {
      spec.cfg = util.duplicate(this._cfg);
    }

    // remove defaults
    var defaults = schema.instantiate();
    return schema.util.subtract(spec, defaults);
  };

  proto.toShorthand = function() {
    var c = consts.shorthand;
    return 'mark' + c.assign + this._marktype +
      c.delim + vlenc.shorthand(this._enc);
  };

  Encoding.shorthand = function (spec) {
    var c = consts.shorthand;
    return 'mark' + c.assign + spec.marktype +
      c.delim + vlenc.shorthand(spec.enc);
  };

  Encoding.parseShorthand = function(shorthand, cfg) {
    var c = consts.shorthand,
        split = shorthand.split(c.delim),
        marktype = split.shift().split(c.assign)[1].trim(),
        enc = vlenc.parseShorthand(split, true);

    return new Encoding(marktype, enc, cfg);
  };

  // FIXME remove this -- simply use Encoding.shorthand
  Encoding.shorthandFromSpec = function(/*spec, theme*/) {
    return Encoding.fromSpec.apply(null, arguments).toShorthand();
  };

  Encoding.specFromShorthand = function(shorthand, cfg, excludeConfig) {
    return Encoding.parseShorthand(shorthand, cfg).toSpec(excludeConfig);
  };

  Encoding.fromSpec = function(spec, theme) {
    var enc = util.duplicate(spec.enc || {});

    //convert type from string to bitcode (e.g, O=1)
    for (var e in enc) {
      enc[e].type = consts.dataTypes[enc[e].type];
    }

    return new Encoding(spec.marktype, enc, spec.cfg, spec.filter, theme);
  };

  Encoding.transpose = function(spec) {
    var oldenc = spec.enc,
      enc = util.duplicate(spec.enc);
    enc.x = oldenc.y;
    enc.y = oldenc.x;
    enc.row = oldenc.col;
    enc.col = oldenc.row;
    spec.enc = enc;
    return spec;
  };

  Encoding.toggleSort = function(spec) {
    if (!Encoding.toggleSort.support(spec)) { return; }
    var enc = spec.enc;

    var oField = enc.x.type === 'O' ? enc.x : enc.y,
      qField = enc.x.type === 'O' ? enc.y : enc.x;

    // FIXME add ascending / descending
    if (oField.sort && oField.sort.length > 0) {
      oField.sort = [];
    } else {
      oField.sort = [{
        name: qField.name,
        aggr: qField.aggr,
        type: qField.type,
        reverse: true
      }];
    }

    spec.enc = enc;
    return spec;
  };

  Encoding.toggleSort.direction = function(spec) {
    if (!Encoding.toggleSort.support(spec)) { return; }
    var enc = spec.enc;
    return enc.x.type === 'O' ? 'x' :  'y';
  };

  Encoding.toggleSort.mode = function(spec) {
    if (!Encoding.toggleSort.support(spec)) { return; }
    var enc = spec.enc;

    var oField = enc.x.type === 'O' ? enc.x : enc.y,
      qField = enc.x.type === 'O' ? enc.y : enc.x;

    // FIXME add ascending / descending
    if (oField.sort && oField.sort.length > 0) {
      return 'Q';
    } else {
      return 'O';
    }
  };

  Encoding.toggleSort.support = function(spec) {
    var enc = spec.enc;
    if (vlenc.has(enc, ROW) || vlenc.has(enc, COL) ||
      !vlenc.has(enc, X) || !vlenc.has(enc, Y) ||
      !Encoding.alwaysNoOcclusion(spec)) {
      return false;
    }
    return (enc.x.type === 'O' && vlfield.isMeasure(enc.y)) ? 'x' :
      (enc.y.type === 'O' && vlfield.isMeasure(enc.x)) ? 'y' : false;
  };

  Encoding.toggleFilterNullO = function(spec) {
    spec.cfg.filterNull.O = !spec.cfg.filterNull.O;
    return spec;
  };

  return Encoding;
})();
<commit_msg>add Encoding.toggleFilterNullO.support<commit_after>'use strict';

var globals = require('./globals'),
  consts = require('./consts'),
  util = require('./util'),
  vlfield = require('./field'),
  vlenc = require('./enc'),
  schema = require('./schema/schema'),
  time = require('./compile/time');

var Encoding = module.exports = (function() {

  function Encoding(marktype, enc, config, filter, theme) {
    var defaults = schema.instantiate();

    var spec = {
      marktype: marktype,
      enc: enc,
      cfg: config,
      filter: filter || []
    };

    // type to bitcode
    for (var e in defaults.enc) {
      defaults.enc[e].type = consts.dataTypes[defaults.enc[e].type];
    }

    var specExtended = schema.util.merge(defaults, theme || {}, spec) ;

    this._marktype = specExtended.marktype;
    this._enc = specExtended.enc;
    this._cfg = specExtended.cfg;
    this._filter = specExtended.filter;
  }

  var proto = Encoding.prototype;

  proto.marktype = function() {
    return this._marktype;
  };

  proto.is = function(m) {
    return this._marktype === m;
  };

  proto.has = function(encType) {
    // equivalent to calling vlenc.has(this._enc, encType)
    return this._enc[encType].name !== undefined;
  };

  proto.enc = function(x) {
    return this._enc[x];
  };

  proto.filter = function() {
    var filterNull = [],
      fields = this.fields(),
      self = this;

    util.forEach(fields, function(fieldList, fieldName) {
      if (fieldName === '*') return; //count

      if ((self.config('filterNull').Q && fieldList.containsType[Q]) ||
          (self.config('filterNull').T && fieldList.containsType[T]) ||
          (self.config('filterNull').O && fieldList.containsType[O])) {
        filterNull.push({
          operands: [fieldName],
          operator: 'notNull'
        });
      }
    });

    return filterNull.concat(this._filter);
  };

  // get "field" property for vega
  proto.field = function(x, nodata, nofn) {
    if (!this.has(x)) return null;

    var f = (nodata ? '' : 'data.');

    if (this._enc[x].aggr === 'count') {
      return f + 'count';
    } else if (!nofn && this._enc[x].bin) {
      return f + 'bin_' + this._enc[x].name;
    } else if (!nofn && this._enc[x].aggr) {
      return f + this._enc[x].aggr + '_' + this._enc[x].name;
    } else if (!nofn && this._enc[x].fn) {
      return f + this._enc[x].fn + '_' + this._enc[x].name;
    } else {
      return f + this._enc[x].name;
    }
  };

  proto.fieldName = function(x) {
    return this._enc[x].name;
  };

  /*
   * return key-value pairs of field name and list of fields of that field name
   */
  proto.fields = function() {
    return vlenc.fields(this._enc);
  };

  proto.fieldTitle = function(x) {
    if (vlfield.isCount(this._enc[x])) {
      return vlfield.count.displayName;
    }
    var fn = this._enc[x].aggr || this._enc[x].fn || (this._enc[x].bin && "bin");
    if (fn) {
      return fn.toUpperCase() + '(' + this._enc[x].name + ')';
    } else {
      return this._enc[x].name;
    }
  };

  proto.scale = function(x) {
    return this._enc[x].scale || {};
  };

  proto.axis = function(x) {
    return this._enc[x].axis || {};
  };

  proto.band = function(x) {
    return this._enc[x].band || {};
  };

  proto.bandSize = function(encType, useSmallBand) {
    useSmallBand = useSmallBand ||
      //isBandInSmallMultiples
      (encType === Y && this.has(ROW) && this.has(Y)) ||
      (encType === X && this.has(COL) && this.has(X));

    // if band.size is explicitly specified, follow the specification, otherwise draw value from config.
    return this.band(encType).size ||
      this.config(useSmallBand ? 'smallBandSize' : 'largeBandSize');
  };

  proto.aggr = function(x) {
    return this._enc[x].aggr;
  };

  // returns false if binning is disabled, otherwise an object with binning properties
  proto.bin = function(x) {
    var bin = this._enc[x].bin;
    if (bin === {})
      return false;
    if (bin === true)
      return {
        maxbins: schema.MAXBINS_DEFAULT
      };
    return bin;
  };

  proto.legend = function(x) {
    return this._enc[x].legend;
  };

  proto.value = function(x) {
    return this._enc[x].value;
  };

  proto.fn = function(x) {
    return this._enc[x].fn;
  };

   proto.sort = function(x) {
    return this._enc[x].sort;
  };

  proto.any = function(f) {
    return util.any(this._enc, f);
  };

  proto.all = function(f) {
    return util.all(this._enc, f);
  };

  proto.length = function() {
    return util.keys(this._enc).length;
  };

  proto.map = function(f) {
    return vlenc.map(this._enc, f);
  };

  proto.reduce = function(f, init) {
    return vlenc.reduce(this._enc, f, init);
  };

  proto.forEach = function(f) {
    return vlenc.forEach(this._enc, f);
  };

  proto.type = function(et) {
    return this.has(et) ? this._enc[et].type : null;
  };

  proto.role = function(et) {
    return this.has(et) ? vlfield.role(this._enc[et]) : null;
  };

  proto.text = function(prop) {
    var text = this._enc[TEXT].text;
    return prop ? text[prop] : text;
  };

  proto.font = function(prop) {
    var font = this._enc[TEXT].font;
    return prop ? font[prop] : font;
  };

  proto.isType = function(x, type) {
    var field = this.enc(x);
    return field && Encoding.isType(field, type);
  };

  Encoding.isType = function (fieldDef, type) {
    return (fieldDef.type & type) > 0;
  };

  Encoding.isOrdinalScale = function(encoding, encType) {
    return vlfield.isOrdinalScale(encoding.enc(encType), true);
  };

  Encoding.isDimension = function(encoding, encType) {
    return vlfield.isDimension(encoding.enc(encType), true);
  };

  Encoding.isMeasure = function(encoding, encType) {
    return vlfield.isMeasure(encoding.enc(encType), true);
  };

  proto.isOrdinalScale = function(encType) {
    return this.has(encType) && Encoding.isOrdinalScale(this, encType);
  };

  proto.isDimension = function(encType) {
    return this.has(encType) && Encoding.isDimension(this, encType);
  };

  proto.isMeasure = function(encType) {
    return this.has(encType) && Encoding.isMeasure(this, encType);
  };

  proto.isAggregate = function() {
    return vlenc.isAggregate(this._enc);
  };

  Encoding.isAggregate = function(spec) {
    return vlenc.isAggregate(spec.enc);
  };

  Encoding.alwaysNoOcclusion = function(spec, stats) {
    // FIXME raw OxQ with # of rows = # of O
    return vlenc.isAggregate(spec.enc);
  };

  Encoding.isStack = function(spec) {
    // FIXME update this once we have control for stack ...
    return (spec.marktype === 'bar' || spec.marktype === 'area') &&
      spec.enc.color;
  };

  proto.isStack = function() {
    // FIXME update this once we have control for stack ...
    return (this.is('bar') || this.is('area')) && this.has('color');
  };

  proto.cardinality = function(encType, stats) {
    return vlfield.cardinality(this.enc(encType), stats, this.config('filterNull'), true);
  };

  proto.isRaw = function() {
    return !this.isAggregate();
  };

  proto.config = function(name) {
    return this._cfg[name];
  };

  proto.toSpec = function(excludeConfig) {
    var enc = util.duplicate(this._enc),
      spec;

    // convert type's bitcode to type name
    for (var e in enc) {
      enc[e].type = consts.dataTypeNames[enc[e].type];
    }

    spec = {
      marktype: this._marktype,
      enc: enc,
      filter: this._filter
    };

    if (!excludeConfig) {
      spec.cfg = util.duplicate(this._cfg);
    }

    // remove defaults
    var defaults = schema.instantiate();
    return schema.util.subtract(spec, defaults);
  };

  proto.toShorthand = function() {
    var c = consts.shorthand;
    return 'mark' + c.assign + this._marktype +
      c.delim + vlenc.shorthand(this._enc);
  };

  Encoding.shorthand = function (spec) {
    var c = consts.shorthand;
    return 'mark' + c.assign + spec.marktype +
      c.delim + vlenc.shorthand(spec.enc);
  };

  Encoding.parseShorthand = function(shorthand, cfg) {
    var c = consts.shorthand,
        split = shorthand.split(c.delim),
        marktype = split.shift().split(c.assign)[1].trim(),
        enc = vlenc.parseShorthand(split, true);

    return new Encoding(marktype, enc, cfg);
  };

  // FIXME remove this -- simply use Encoding.shorthand
  Encoding.shorthandFromSpec = function(/*spec, theme*/) {
    return Encoding.fromSpec.apply(null, arguments).toShorthand();
  };

  Encoding.specFromShorthand = function(shorthand, cfg, excludeConfig) {
    return Encoding.parseShorthand(shorthand, cfg).toSpec(excludeConfig);
  };

  Encoding.fromSpec = function(spec, theme) {
    var enc = util.duplicate(spec.enc || {});

    //convert type from string to bitcode (e.g, O=1)
    for (var e in enc) {
      enc[e].type = consts.dataTypes[enc[e].type];
    }

    return new Encoding(spec.marktype, enc, spec.cfg, spec.filter, theme);
  };

  Encoding.transpose = function(spec) {
    var oldenc = spec.enc,
      enc = util.duplicate(spec.enc);
    enc.x = oldenc.y;
    enc.y = oldenc.x;
    enc.row = oldenc.col;
    enc.col = oldenc.row;
    spec.enc = enc;
    return spec;
  };

  Encoding.toggleSort = function(spec) {
    if (!Encoding.toggleSort.support(spec)) { return; }
    var enc = spec.enc;

    var oField = enc.x.type === 'O' ? enc.x : enc.y,
      qField = enc.x.type === 'O' ? enc.y : enc.x;

    // FIXME add ascending / descending
    if (oField.sort && oField.sort.length > 0) {
      oField.sort = [];
    } else {
      oField.sort = [{
        name: qField.name,
        aggr: qField.aggr,
        type: qField.type,
        reverse: true
      }];
    }

    spec.enc = enc;
    return spec;
  };

  Encoding.toggleSort.direction = function(spec) {
    if (!Encoding.toggleSort.support(spec)) { return; }
    var enc = spec.enc;
    return enc.x.type === 'O' ? 'x' :  'y';
  };

  Encoding.toggleSort.mode = function(spec) {
    if (!Encoding.toggleSort.support(spec)) { return; }
    var enc = spec.enc;

    var oField = enc.x.type === 'O' ? enc.x : enc.y,
      qField = enc.x.type === 'O' ? enc.y : enc.x;

    // FIXME add ascending / descending
    if (oField.sort && oField.sort.length > 0) {
      return 'Q';
    } else {
      return 'O';
    }
  };

  Encoding.toggleSort.support = function(spec, stats) {
    var enc = spec.enc;
    if (vlenc.has(enc, ROW) || vlenc.has(enc, COL) ||
      !vlenc.has(enc, X) || !vlenc.has(enc, Y) ||
      !Encoding.alwaysNoOcclusion(spec, stats)) {
      return false;
    }
    return (enc.x.type === 'O' && vlfield.isMeasure(enc.y)) ? 'x' :
      (enc.y.type === 'O' && vlfield.isMeasure(enc.x)) ? 'y' : false;
  };

  Encoding.toggleFilterNullO = function(spec) {
    spec.cfg.filterNull.O = !spec.cfg.filterNull.O;
    return spec;
  };

  Encoding.toggleFilterNullO.support = function(spec, stats) {
    var fields = vlenc.fields(spec.enc);
    console.log('fields', fields);
    for (var fieldName in fields) {
      var fieldList = fields[fieldName];
      if (fieldList.containsType.O && stats[fieldName].numNulls > 0) {
        return true;
      }
    }
    return false;
  };

  return Encoding;
})();
<|endoftext|>


<commit_before>var Backbone = require('backbone');
var CartoDBLayer = require('../../../../src/geo/map/cartodb-layer');
var _ = require('underscore');
var sharedTestsForInteractiveLayers = require('./shared-for-interactive-layers');
var AnalysisFactory = require('../../../../src/analysis/analysis-factory.js');

describe('geo/map/cartodb-layer', function () {
  sharedTestsForInteractiveLayers(CartoDBLayer);

  it('should be type CartoDB', function () {
    var layer = new CartoDBLayer();
    expect(layer.get('type')).toEqual('CartoDB');
  });

  describe('.getInteractiveColumnNames', function () {
    beforeEach(function () {
      this.layer = new CartoDBLayer();
      spyOn(this.layer, 'getInfowindowFieldNames');
      spyOn(this.layer, 'getTooltipFieldNames');
    });

    it('should include cartodb_id if there is any other field required from the infowindow or tooltip', function () {
      this.layer.getInfowindowFieldNames.and.returnValue(['column_a']);
      this.layer.getTooltipFieldNames.and.returnValue([]);
      expect(_.contains(this.layer.getInteractiveColumnNames(), 'cartodb_id')).toBeTruthy();

      this.layer.getInfowindowFieldNames.and.returnValue([]);
      this.layer.getTooltipFieldNames.and.returnValue(['column_b']);
      expect(_.contains(this.layer.getInteractiveColumnNames(), 'cartodb_id')).toBeTruthy();
    });

    it("should not include cartodb_id if there isn't any field required", function () {
      this.layer.getInfowindowFieldNames.and.returnValue([]);
      this.layer.getTooltipFieldNames.and.returnValue([]);
      expect(_.contains(this.layer.getInteractiveColumnNames(), 'cartodb_id')).toBeFalsy();
    });
  });
});
<commit_msg>Removed unused deps<commit_after>var CartoDBLayer = require('../../../../src/geo/map/cartodb-layer');
var _ = require('underscore');
var sharedTestsForInteractiveLayers = require('./shared-for-interactive-layers');

describe('geo/map/cartodb-layer', function () {
  sharedTestsForInteractiveLayers(CartoDBLayer);

  it('should be type CartoDB', function () {
    var layer = new CartoDBLayer();
    expect(layer.get('type')).toEqual('CartoDB');
  });

  describe('.getInteractiveColumnNames', function () {
    beforeEach(function () {
      this.layer = new CartoDBLayer();
      spyOn(this.layer, 'getInfowindowFieldNames');
      spyOn(this.layer, 'getTooltipFieldNames');
    });

    it('should include cartodb_id if there is any other field required from the infowindow or tooltip', function () {
      this.layer.getInfowindowFieldNames.and.returnValue(['column_a']);
      this.layer.getTooltipFieldNames.and.returnValue([]);
      expect(_.contains(this.layer.getInteractiveColumnNames(), 'cartodb_id')).toBeTruthy();

      this.layer.getInfowindowFieldNames.and.returnValue([]);
      this.layer.getTooltipFieldNames.and.returnValue(['column_b']);
      expect(_.contains(this.layer.getInteractiveColumnNames(), 'cartodb_id')).toBeTruthy();
    });

    it("should not include cartodb_id if there isn't any field required", function () {
      this.layer.getInfowindowFieldNames.and.returnValue([]);
      this.layer.getTooltipFieldNames.and.returnValue([]);
      expect(_.contains(this.layer.getInteractiveColumnNames(), 'cartodb_id')).toBeFalsy();
    });
  });
});
<|endoftext|>


<commit_before>import { Server as MockSocketServer } from 'mock-socket';
import { Palindrom } from '../../src/palindrom';
import chai, { expect, assert } from 'chai';
import sinonChai  from "sinon-chai";
import fetchMock from 'fetch-mock';
import sinon from 'sinon';
import { PalindromConnectionError } from '../../src/palindrom-errors';
import { sleep, getTestURL } from '../utils';

chai.use(sinonChai);

describe('Sockets - if `useWebSocket` flag is provided', () => {
    let mockSocketServer;
    const remoteUrl = getTestURL('testURL');
    const wsUrl = getTestURL('testURL', false, true);
    let palindrom;
    let webSocketConnection;
    beforeEach(() => {
        webSocketConnection = sinon.spy().named('Web Socket connection');
    });
    afterEach(() => {
        fetchMock.restore();
        mockSocketServer.stop();
        // stop all networking and DOM activity of abandoned instance
        palindrom.stop();
        palindrom = undefined;
    });

    describe('Before HTTP connection is established', () => {
        const remoteUrl = getTestURL('testURL/koko');
        beforeEach(() => {
            mockSocketServer = new MockSocketServer(getTestURL('testURL/koko', false, true));
            fetchMock.mock(remoteUrl, {
                status: 200,
                body: '{"hello": "world"}'
            });
        });
        it("shouldn't start a Web Socket connection", async () => {
            mockSocketServer.on('connection', webSocketConnection);

            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true,
                onStateReset: () =>
                    expect(webSocketConnection).not.to.be.called
            });
            expect(webSocketConnection).not.to.be.called;
            // wait for onStateReset expectation
            await sleep();
        });

        it("shouldn't call `onSocketOpened` callback", async () => {
            const onSocketOpened = sinon.spy().named('onSocketOpened');

            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true,
                onStateReset: () =>
                    expect(onSocketOpened).not.to.be.called,
                onSocketOpened
            });
            expect(onSocketOpened).not.to.be.called;
            // wait for onStateReset expectation
            await sleep();
        });

        it("shouldn't send any change a patch using WebSocket", async () => {
            const messages = [];

            mockSocketServer.on('message', patch => {
                let patchParsed = JSON.parse(patch);
                messages.push(...patchParsed);
            });

            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true,
                onStateReset: obj => (obj.firstName = 'Omar')
            });

            // Wait for HTTP to finish
            await sleep(15);

            assert(messages.length === 0);
        });
    });

    describe('After HTTP connection is established', () => {
        const remoteUrl = getTestURL('testURL/koko');
        beforeEach(() => {
            mockSocketServer = new MockSocketServer(getTestURL('testURL/koko', false, true));
            mockSocketServer.on('connection', webSocketConnection);
            fetchMock.mock(remoteUrl, {
                status: 200,
                body: '{"hello": "world"}'
            });
        });
        it("should start a Web Socket connection", async () => {
            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true
            });
            await sleep(50);
            expect(webSocketConnection).to.be.calledOnce;

        });

        it("should call `onSocketOpened` callback", async () => {
            const onSocketOpened = sinon.spy().named('onSocketOpened');

            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true,
                onSocketOpened
            });
            await sleep(50);
            expect(onSocketOpened).to.be.calledOnce;
        });
        it('should call `onConnectionError` when a non-JSON message is received from the server', async () => {
            const onConnectionError = sinon.spy().named('onConnectionError');
            const socketMessageSpy = sinon.spy().named('socket message');

            let mockSocket;
            mockSocketServer.on('connection', socket => {
                mockSocket = socket;
                socket.on('message', socketMessageSpy);
            });

            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true,
                onConnectionError
            });

            await sleep();

            mockSocket.send(`[{"op": "replace", "path": "/hello", "value": "bye"}]`);
            // make sure the change is applied
            assert.equal(palindrom.obj.hello, 'bye');

            /* no issues so far */
            expect(onConnectionError).not.to.be.called;

            // send a non-JSON message from the server
            mockSocket.send(`Some error message from the server`);

            /* atual test */
            expect(onConnectionError).to.be.calledOnce;

            const error = onConnectionError.lastCall.args[0];

            expect(error).to.be.instanceof(PalindromConnectionError);
            expect(error).to.have.property('message',
                'Server error\n\tSome error message from the server'
            );
        });

        it('should send new changes over WebSocket', async () => {
            const socketMessageSpy = sinon.spy().named('socket message');
            mockSocketServer.on('connection', socket => {
                socket.on('message', socketMessageSpy);
            });
            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true
            });

            /* wait for HTTP */
            await sleep();

            palindrom.obj.firstName = 'Omar';

            await sleep();

            expect(socketMessageSpy).to.have.been.calledOnceWithExactly(JSON.stringify([{
                op: 'add',
                path: '/firstName',
                value: 'Omar'
            }]));
        });

        it('should call `onConnectionError` callback if there is no response after `pingIntervalS`', async () => {
            const onConnectionError = sinon.spy().named('onConnectionError');
            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true,
                onConnectionError,
                pingIntervalS: 0.5
            });

            /* wait for HTTP & heartbeat */

            await sleep(1200);

            /* onConnectionError should be called once now */
            expect(onConnectionError).to.have.been.calledOnce;
            const argument = onConnectionError.getCall(0).args[0];
            expect(argument).to.be.an.instanceof(PalindromConnectionError);
            expect(argument).to.have.property('message').that.match(/timeout/i);
            expect(argument).to.have.property('side', "Client");
        });
    });

    it('should calculate WebSocket URL correctly', async () => {
        mockSocketServer = new MockSocketServer(wsUrl);
        mockSocketServer.on('connection', webSocketConnection);

        fetchMock.mock(remoteUrl, {
            status: 200,
            headers: { location: remoteUrl },
            body: '{"hello": "world"}'
        });

        palindrom = new Palindrom({
            remoteUrl,
            useWebSocket: true
        });

        await sleep();

        const websocket = webSocketConnection.lastCall.args[0];
        expect(websocket, "Web Socket").to.have.property('url',
            getTestURL('testURL', false, true)
        );
    });

    it('should resolve to correct WebSocket URL from location header, with root slash /', async () => {
        mockSocketServer = new MockSocketServer(
            getTestURL('default/this_is_a_nice_url', false, true)
        );
        mockSocketServer.on('connection', webSocketConnection);

        fetchMock.mock(remoteUrl, {
            status: 200,
            headers: { location: '/default/this_is_a_nice_url' },
            body: '{"hello": "world"}'
        });

        palindrom = new Palindrom({
            remoteUrl,
            useWebSocket: true
        });

        await sleep();

        const websocket = webSocketConnection.lastCall.args[0];
        expect(websocket, "Web Socket").to.have.property('url',
            getTestURL('default/this_is_a_nice_url', false, true)
        );
    });

    it('should resolve to correct WebSocket URL from location header, relatively', async () => {
        mockSocketServer = new MockSocketServer(
            getTestURL('default/this_is_a_nice_url', false, true)
        );
        mockSocketServer.on('connection', webSocketConnection);

        fetchMock.mock(remoteUrl, {
            status: 200,
            headers: { location: 'default/this_is_a_nice_url' },
            body: '{"hello": "world"}'
        });

        palindrom = new Palindrom({
            remoteUrl,
            useWebSocket: true
        });

        await sleep();
        const websocket = webSocketConnection.lastCall.args[0];
        expect(websocket, "Web Socket").to.have.property('url',
            getTestURL('default/this_is_a_nice_url', false, true)
        );
    });

    it('should resolve to correct WebSocket URL from location header, with root slash and extra pathname', async () => {
        mockSocketServer = new MockSocketServer(
            getTestURL('default/this_is_a_nice_url', false, true)
        );
        mockSocketServer.on('connection', webSocketConnection);

        fetchMock.mock(getTestURL('testURL/koko'), {
            status: 200,
            headers: { location: '/default/this_is_a_nice_url' },
            body: '{"hello": "world"}'
        });

        palindrom = new Palindrom({
            remoteUrl: getTestURL('testURL/koko'),
            useWebSocket: true
        });

        await sleep();
        const websocket = webSocketConnection.lastCall.args[0];
        expect(websocket, "Web Socket").to.have.property('url',
            getTestURL('default/this_is_a_nice_url', false, true)
        );
    });

    it('should resolve to correct WebSocket URL from location header, without root slash and extra pathname', async () => {
        mockSocketServer = new MockSocketServer(
            getTestURL('testURL/default/this_is_a_nice_url', false, true)
        );
        mockSocketServer.on('connection', webSocketConnection);

        fetchMock.mock(getTestURL('testURL/koko'), {
            status: 200,
            headers: { location: 'default/this_is_a_nice_url' },
            body: '{"hello": "world"}'
        });

        palindrom = new Palindrom({
            remoteUrl: getTestURL('testURL/koko'),
            useWebSocket: true
        });

        await sleep();
        const websocket = webSocketConnection.lastCall.args[0];
        expect(websocket, "Web Socket").to.have.property('url',
            getTestURL('testURL/default/this_is_a_nice_url', false, true)
        );
    });
    it('should use wss for https remote URL', async () => {
        mockSocketServer = new MockSocketServer(
            'wss://localhost/testURL/default/this_is_a_nice_url'
        );
        mockSocketServer.on('connection', webSocketConnection);

        fetchMock.mock('https://localhost/testURL/koko', {
            status: 200,
            headers: { location: 'default/this_is_a_nice_url' },
            body: '{"hello": "world"}'
        });

        palindrom = new Palindrom({
            remoteUrl: 'https://localhost/testURL/koko',
            useWebSocket: true
        });

        await sleep();
        const websocket = webSocketConnection.lastCall.args[0];
        expect(websocket, "Web Socket").to.have.property('url',
            'wss://localhost/testURL/default/this_is_a_nice_url'
        );
    });

    it('should use same host, port, username, and password as provided in remoteUrl', async () => {
        mockSocketServer = new MockSocketServer(
            getTestURL('test/this_is_a_nice_url', false, true)
        );
        mockSocketServer.on('connection', webSocketConnection);

        const remoteUrl = getTestURL('testURL/koko');

        fetchMock.mock(remoteUrl, {
            status: 200,
            headers: { location: getTestURL('test/this_is_a_nice_url') },
            body: '{"hello": "world"}'
        });

        palindrom = new Palindrom({
            remoteUrl,
            useWebSocket: true
        });

        await sleep();
        const websocket = webSocketConnection.lastCall.args[0];
        expect(websocket, "Web Socket").to.have.property('url',
            getTestURL('test/this_is_a_nice_url', false, true)
        );
    });

    it('should send a patch over HTTP before ws.readyState is OPENED, and over WebSocket after ws.readyState is OPENED', async () => {
        const socketMessageSpy = sinon.spy().named('socket message');
        mockSocketServer = new MockSocketServer(getTestURL('testURL', false, true));
        mockSocketServer.on('connection', socket => {
            socket.on('message', socketMessageSpy);
        });

        fetchMock.mock(remoteUrl, {
            status: 200,
            body: '{"hello": "world"}'
        });

        let tempObj;
        palindrom = new Palindrom({
            remoteUrl,
            useWebSocket: true,
            onStateReset: obj => {
                fetchMock.restore();

                // prepare a response for the patch
                fetchMock.mock(remoteUrl, {
                    status: 200,
                    headers: { contentType: 'application/json-patch+json' },
                    body: `[]`
                });

                /* here, socket connection isn't established yet, let's issue a change */
                obj.name = 'Mark';

                assert.equal(
                    '[{"op":"add","path":"/name","value":"Mark"}]',
                    fetchMock.lastOptions().body
                );

                tempObj = obj;
            }
        });

        /* make sure there is no socket messages */
        expect(socketMessageSpy).not.to.be.called;

        /* now socket is connected, let's issue a change */
        await sleep();

        tempObj.firstName = 'Omar';

        await sleep();

        expect(socketMessageSpy).to.have.been.calledOnceWithExactly('[{"op":"add","path":"/firstName","value":"Omar"}]');

        /* now socket is connected, let's issue another change */
        await sleep();
        tempObj.firstName = 'Hanan';
        // mock-socket is asynchronous, so let's wait for it to propagate event
        await sleep(10);

        expect(socketMessageSpy).to.have.been.calledTwice;
        expect(socketMessageSpy).to.have.been.calledWithExactly('[{"op":"replace","path":"/firstName","value":"Hanan"}]');
    });
});
<commit_msg>Move URL checking tests to "After..." section<commit_after>import { Server as MockSocketServer } from 'mock-socket';
import { Palindrom } from '../../src/palindrom';
import chai, { expect, assert } from 'chai';
import sinonChai  from "sinon-chai";
import fetchMock from 'fetch-mock';
import sinon from 'sinon';
import { PalindromConnectionError } from '../../src/palindrom-errors';
import { sleep, getTestURL } from '../utils';

chai.use(sinonChai);

describe('Sockets - if `useWebSocket` flag is provided', () => {
    let mockSocketServer;
    const remoteUrl = getTestURL('testURL/koko');
    const wsUrl = getTestURL('testURL/koko', false, true);
    let palindrom;
    let webSocketConnection;
    beforeEach(() => {
        webSocketConnection = sinon.spy().named('Web Socket connection');
    });
    afterEach(() => {
        fetchMock.restore();
        mockSocketServer.stop();
        // stop all networking and DOM activity of abandoned instance
        palindrom.stop();
        palindrom = undefined;
    });

    describe('Before HTTP connection is established', () => {
        const remoteUrl = getTestURL('testURL/koko');
        beforeEach(() => {
            mockSocketServer = new MockSocketServer(getTestURL('testURL/koko', false, true));
            fetchMock.mock(remoteUrl, {
                status: 200,
                body: '{"hello": "world"}'
            });
        });
        it("shouldn't start a Web Socket connection", async () => {
            mockSocketServer.on('connection', webSocketConnection);

            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true,
                onStateReset: () =>
                    expect(webSocketConnection).not.to.be.called
            });
            expect(webSocketConnection).not.to.be.called;
            // wait for onStateReset expectation
            await sleep();
        });

        it("shouldn't call `onSocketOpened` callback", async () => {
            const onSocketOpened = sinon.spy().named('onSocketOpened');

            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true,
                onStateReset: () =>
                    expect(onSocketOpened).not.to.be.called,
                onSocketOpened
            });
            expect(onSocketOpened).not.to.be.called;
            // wait for onStateReset expectation
            await sleep();
        });

        it("shouldn't send any change a patch using WebSocket", async () => {
            const messages = [];

            mockSocketServer.on('message', patch => {
                let patchParsed = JSON.parse(patch);
                messages.push(...patchParsed);
            });

            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true,
                onStateReset: obj => (obj.firstName = 'Omar')
            });

            // Wait for HTTP to finish
            await sleep(15);

            assert(messages.length === 0);
        });
    });

    describe('After HTTP connection is established', () => {
        it("should start a Web Socket connection", async () => {
            mockSocketServer = new MockSocketServer(getTestURL('testURL/koko', false, true));
            mockSocketServer.on('connection', webSocketConnection);
            fetchMock.mock(remoteUrl, {
                status: 200,
                body: '{"hello": "world"}'
            });
            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true
            });
            await sleep(50);

            expect(webSocketConnection).to.be.calledOnce;
        });

        it('should calculate WebSocket URL correctly', async () => {
            mockSocketServer = new MockSocketServer(wsUrl);
            mockSocketServer.on('connection', webSocketConnection);

            fetchMock.mock(remoteUrl, {
                status: 200,
                headers: { location: remoteUrl },
                body: '{"hello": "world"}'
            });

            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true
            });

            await sleep();

            const websocket = webSocketConnection.lastCall.args[0];
            expect(websocket, "Web Socket").to.have.property('url',
                getTestURL('testURL/koko', false, true)
            );
        });

        it('should resolve to correct WebSocket URL from location header, with root slash /', async () => {
            mockSocketServer = new MockSocketServer(
                getTestURL('default/this_is_a_nice_url', false, true)
            );
            mockSocketServer.on('connection', webSocketConnection);

            fetchMock.mock(remoteUrl, {
                status: 200,
                headers: { location: '/default/this_is_a_nice_url' },
                body: '{"hello": "world"}'
            });

            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true
            });

            await sleep();

            const websocket = webSocketConnection.lastCall.args[0];
            expect(websocket, "Web Socket").to.have.property('url',
                getTestURL('default/this_is_a_nice_url', false, true)
            );
        });

        it('should resolve to correct WebSocket URL from location header, relatively', async () => {
            mockSocketServer = new MockSocketServer(
                getTestURL('testURL/default/this_is_a_nice_url', false, true)
            );
            mockSocketServer.on('connection', webSocketConnection);

            fetchMock.mock(remoteUrl, {
                status: 200,
                headers: { location: 'default/this_is_a_nice_url' },
                body: '{"hello": "world"}'
            });

            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true
            });

            await sleep();
            const websocket = webSocketConnection.lastCall.args[0];
            expect(websocket, "Web Socket").to.have.property('url',
                getTestURL('testURL/default/this_is_a_nice_url', false, true)
            );
        });

        it('should resolve to correct WebSocket URL from location header, with root slash and extra pathname', async () => {
            mockSocketServer = new MockSocketServer(
                getTestURL('default/this_is_a_nice_url', false, true)
            );
            mockSocketServer.on('connection', webSocketConnection);

            fetchMock.mock(getTestURL('testURL/koko'), {
                status: 200,
                headers: { location: '/default/this_is_a_nice_url' },
                body: '{"hello": "world"}'
            });

            palindrom = new Palindrom({
                remoteUrl: getTestURL('testURL/koko'),
                useWebSocket: true
            });

            await sleep();
            const websocket = webSocketConnection.lastCall.args[0];
            expect(websocket, "Web Socket").to.have.property('url',
                getTestURL('default/this_is_a_nice_url', false, true)
            );
        });

        it('should resolve to correct WebSocket URL from location header, without root slash and extra pathname', async () => {
            mockSocketServer = new MockSocketServer(
                getTestURL('testURL/default/this_is_a_nice_url', false, true)
            );
            mockSocketServer.on('connection', webSocketConnection);

            fetchMock.mock(getTestURL('testURL/koko'), {
                status: 200,
                headers: { location: 'default/this_is_a_nice_url' },
                body: '{"hello": "world"}'
            });

            palindrom = new Palindrom({
                remoteUrl: getTestURL('testURL/koko'),
                useWebSocket: true
            });

            await sleep();
            const websocket = webSocketConnection.lastCall.args[0];
            expect(websocket, "Web Socket").to.have.property('url',
                getTestURL('testURL/default/this_is_a_nice_url', false, true)
            );
        });
        it('should use wss for https remote URL', async () => {
            mockSocketServer = new MockSocketServer(
                'wss://localhost/testURL/default/this_is_a_nice_url'
            );
            mockSocketServer.on('connection', webSocketConnection);

            fetchMock.mock('https://localhost/testURL/koko', {
                status: 200,
                headers: { location: 'default/this_is_a_nice_url' },
                body: '{"hello": "world"}'
            });

            palindrom = new Palindrom({
                remoteUrl: 'https://localhost/testURL/koko',
                useWebSocket: true
            });

            await sleep();
            const websocket = webSocketConnection.lastCall.args[0];
            expect(websocket, "Web Socket").to.have.property('url',
                'wss://localhost/testURL/default/this_is_a_nice_url'
            );
        });

        it('should use same host, port, username, and password as provided in remoteUrl', async () => {
            mockSocketServer = new MockSocketServer(
                getTestURL('test/this_is_a_nice_url', false, true)
            );
            mockSocketServer.on('connection', webSocketConnection);

            const remoteUrl = getTestURL('testURL/koko');

            fetchMock.mock(remoteUrl, {
                status: 200,
                headers: { location: getTestURL('test/this_is_a_nice_url') },
                body: '{"hello": "world"}'
            });

            palindrom = new Palindrom({
                remoteUrl,
                useWebSocket: true
            });

            await sleep();
            const websocket = webSocketConnection.lastCall.args[0];
            expect(websocket, "Web Socket").to.have.property('url',
                getTestURL('test/this_is_a_nice_url', false, true)
            );
        });
        describe('', () => {

            const remoteUrl = getTestURL('testURL/koko');
            beforeEach(() => {
                mockSocketServer = new MockSocketServer(getTestURL('testURL/koko', false, true));
                mockSocketServer.on('connection', webSocketConnection);
                fetchMock.mock(remoteUrl, {
                    status: 200,
                    body: '{"hello": "world"}'
                });
            });

            it("should call `onSocketOpened` callback", async () => {
                const onSocketOpened = sinon.spy().named('onSocketOpened');

                palindrom = new Palindrom({
                    remoteUrl,
                    useWebSocket: true,
                    onSocketOpened
                });
                await sleep(50);
                expect(onSocketOpened).to.be.calledOnce;
            });
            it('should call `onConnectionError` when a non-JSON message is received from the server', async () => {
                const onConnectionError = sinon.spy().named('onConnectionError');
                const socketMessageSpy = sinon.spy().named('socket message');

                let mockSocket;
                mockSocketServer.on('connection', socket => {
                    mockSocket = socket;
                    socket.on('message', socketMessageSpy);
                });

                palindrom = new Palindrom({
                    remoteUrl,
                    useWebSocket: true,
                    onConnectionError
                });

                await sleep();

                mockSocket.send(`[{"op": "replace", "path": "/hello", "value": "bye"}]`);
                // make sure the change is applied
                assert.equal(palindrom.obj.hello, 'bye');

                /* no issues so far */
                expect(onConnectionError).not.to.be.called;

                // send a non-JSON message from the server
                mockSocket.send(`Some error message from the server`);

                /* atual test */
                expect(onConnectionError).to.be.calledOnce;

                const error = onConnectionError.lastCall.args[0];

                expect(error).to.be.instanceof(PalindromConnectionError);
                expect(error).to.have.property('message',
                    'Server error\n\tSome error message from the server'
                );
            });

            it('should send new changes over WebSocket', async () => {
                const socketMessageSpy = sinon.spy().named('socket message');
                mockSocketServer.on('connection', socket => {
                    socket.on('message', socketMessageSpy);
                });
                palindrom = new Palindrom({
                    remoteUrl,
                    useWebSocket: true
                });

                /* wait for HTTP */
                await sleep();

                palindrom.obj.firstName = 'Omar';

                await sleep();

                expect(socketMessageSpy).to.have.been.calledOnceWithExactly(JSON.stringify([{
                    op: 'add',
                    path: '/firstName',
                    value: 'Omar'
                }]));
            });

            it('should call `onConnectionError` callback if there is no response after `pingIntervalS`', async () => {
                const onConnectionError = sinon.spy().named('onConnectionError');
                palindrom = new Palindrom({
                    remoteUrl,
                    useWebSocket: true,
                    onConnectionError,
                    pingIntervalS: 0.5
                });

                /* wait for HTTP & heartbeat */

                await sleep(1200);

                /* onConnectionError should be called once now */
                expect(onConnectionError).to.have.been.calledOnce;
                const argument = onConnectionError.getCall(0).args[0];
                expect(argument).to.be.an.instanceof(PalindromConnectionError);
                expect(argument).to.have.property('message').that.match(/timeout/i);
                expect(argument).to.have.property('side', "Client");
            });
        });
    });

    it('should send a patch over HTTP before ws.readyState is OPENED, and over WebSocket after ws.readyState is OPENED', async () => {
        const socketMessageSpy = sinon.spy().named('socket message');
        mockSocketServer = new MockSocketServer(getTestURL('testURL/koko', false, true));
        mockSocketServer.on('connection', socket => {
            socket.on('message', socketMessageSpy);
        });

        fetchMock.mock(remoteUrl, {
            status: 200,
            body: '{"hello": "world"}'
        });

        let tempObj;
        palindrom = new Palindrom({
            remoteUrl,
            useWebSocket: true,
            onStateReset: obj => {
                fetchMock.restore();

                // prepare a response for the patch
                fetchMock.mock(remoteUrl, {
                    status: 200,
                    headers: { contentType: 'application/json-patch+json' },
                    body: `[]`
                });

                /* here, socket connection isn't established yet, let's issue a change */
                obj.name = 'Mark';

                assert.equal(
                    '[{"op":"add","path":"/name","value":"Mark"}]',
                    fetchMock.lastOptions().body
                );

                tempObj = obj;
            }
        });

        /* make sure there is no socket messages */
        expect(socketMessageSpy).not.to.be.called;

        /* now socket is connected, let's issue a change */
        await sleep();

        tempObj.firstName = 'Omar';

        await sleep();

        expect(socketMessageSpy).to.have.been.calledOnceWithExactly('[{"op":"add","path":"/firstName","value":"Omar"}]');

        /* now socket is connected, let's issue another change */
        await sleep();
        tempObj.firstName = 'Hanan';
        // mock-socket is asynchronous, so let's wait for it to propagate event
        await sleep(10);

        expect(socketMessageSpy).to.have.been.calledTwice;
        expect(socketMessageSpy).to.have.been.calledWithExactly('[{"op":"replace","path":"/firstName","value":"Hanan"}]');
    });
});
<|endoftext|>


<commit_before>'use babel';
import fs from 'fs';
import path from 'path';
import SubAtom from 'sub-atom';
import $ from 'jquery';

// css classes on the status items that we don't want
// to be serialized
const genericCSSClasses = /inline-block/;

var disposables;

export function activate() {
  disposables = new SubAtom();
  atom.packages.onDidActivateInitialPackages(function() {
    setInitialOrder();
    handleEvents();
  });
}

export function deactivate() {
  disposables.dispose();
  [disposables, serializedState] = [];
}

function handleEvents() {
  disposables.add('status-bar [class*="status-bar-"]', 'mousedown', '> *', dragStart);
}

var dragDisposable;

function dragStart(event) {
  var currentItem = event.currentTarget;
  dragDisposable = new SubAtom();
  dragDisposable.add(document.body, 'mousemove', (event) => drag(currentItem, event));
  dragDisposable.add(document.body, 'mouseup mouseleave', () => dragEnd(currentItem));
  currentItem.classList.add('dragging');
  currentItem.parentNode.classList.add('dragging');
}

function drag(currentItem, event) {
  var {pageX} = event;
  var nextItem = getNextItem(currentItem);
  var previousItem = getNextItem(currentItem, true);
  if(previousItem && pageX < $(previousItem).offset().left + previousItem.clientWidth / 2) {
    $(currentItem).insertBefore(previousItem);
  } else if(nextItem && pageX > $(nextItem).offset().left + nextItem.clientWidth / 2) {
    $(currentItem).insertAfter(nextItem);
  }
}

function dragEnd(currentItem) {
  dragDisposable.dispose();
  dragDisposable = null;
  saveOrder();
  currentItem.classList.remove('dragging');
  currentItem.parentNode.classList.remove('dragging');
}

function getNextItem(currentItem, reverse = false) {
  var parent = currentItem.parentNode;
  var currentIndex = Array.prototype.indexOf.call(parent.children, currentItem);
  while(currentIndex > 0 && currentIndex < parent.children.length - 1) {
    currentIndex += Math.pow(-1, reverse); // 1, or -1 if reverse == true
    let item = parent.children[currentIndex];
    if(isVisible(item)) return item;
  }
}

function isVisible(element) {
  var {display, visibility, width} = window.getComputedStyle(element);
  width = width ? parseInt(width) : element.clientWidth;
  return display != 'none' && visibility != 'hidden' && width > 0;
}

function saveOrder() {
  for(let side of ['left', 'right']) {
    let statusItems = document.querySelectorAll(`status-bar .status-bar-${side} > *`);
    atom.config.set(`move-status-items.${side}`, serializeStatusItems(statusItems));
  }
}

function serializeStatusItems(statusItems) {
  return Array.prototype.map.call(statusItems, function(statusItem) {
    if(!isHTMLElement(statusItem)) return '';
    return serializeStatusItem(statusItem);
  });
}

function serializeStatusItem(statusItem) {
  var serializedStatusItem = statusItem.tagName.toLowerCase();
  if(statusItem.className) serializedStatusItem += serializeClassName(statusItem.className);
  var isAttr = statusItem.getAttribute('is');
  if(isAttr) serializedStatusItem += `[is="${isAttr}"]`;
  return serializedStatusItem;
}

function serializeClassName(className) {
  serializedClassName = '';
  for(let classNamePart of className.split(' ')) {
    if(className.match(genericCSSClasses)) continue;
    serializedClassName += `.${classNamePart}`;
  }
  return serializedClassName;
}

function setInitialOrder() {
  for(let side of ['left', 'right']) {
    let serializedState = atom.config.get(`move-status-items.${side}`);
    if(!serializedState) continue;
    let statusItemContainer = document.querySelector(`status-bar .status-bar-${side}`);
    for(let serializedStatusItem of serializedState) {
      let statusItem = statusItemContainer.querySelector(serializedStatusItem);
      if(!isHTMLElement(statusItem)) continue;
      statusItemContainer.appendChild(statusItem);
    }
  }
}

function isHTMLElement(item) {
  return item instanceof HTMLElement;
}
<commit_msg>fix some issues<commit_after>'use babel';
import fs from 'fs';
import path from 'path';
import SubAtom from 'sub-atom';
import $ from 'jquery';

// css classes on the status items that we don't want
// to be serialized
const genericCSSClasses = /inline-block/;

var disposables;

export function activate() {
  disposables = new SubAtom();
  atom.packages.onDidActivateInitialPackages(function init() {
    process.nextTick(function delayedInit() {
      handleEvents();
      setInitialOrder();
    });
  });
}

export function deactivate() {
  disposables.dispose();
  [disposables, serializedState] = [];
}

function handleEvents() {
  disposables.add('status-bar [class*="status-bar-"]', 'mousedown', '> *', dragStart);
}

var dragDisposable;

function dragStart(event) {
  var currentItem = event.currentTarget;
  dragDisposable = new SubAtom();
  dragDisposable.add(document.body, 'mousemove', (event) => drag(currentItem, event));
  dragDisposable.add(document.body, 'mouseup mouseleave', () => dragEnd(currentItem));
  currentItem.classList.add('dragging');
  currentItem.parentNode.classList.add('dragging');
}

function drag(currentItem, event) {
  var {pageX} = event;
  var nextItem = getNextItem(currentItem);
  var previousItem = getNextItem(currentItem, true);
  if(previousItem && pageX < $(previousItem).offset().left + previousItem.clientWidth / 2) {
    $(currentItem).insertBefore(previousItem);
  } else if(nextItem && pageX > $(nextItem).offset().left + nextItem.clientWidth / 2) {
    $(currentItem).insertAfter(nextItem);
  }
}

function dragEnd(currentItem) {
  dragDisposable.dispose();
  dragDisposable = null;
  currentItem.classList.remove('dragging');
  currentItem.parentNode.classList.remove('dragging');
  saveOrder();
}

function getNextItem(currentItem, reverse = false) {
  var parent = currentItem.parentNode;
  var currentIndex = Array.prototype.indexOf.call(parent.children, currentItem);
  while(currentIndex > 0 && currentIndex < parent.children.length - 1) {
    currentIndex += Math.pow(-1, reverse); // 1, or -1 if reverse == true
    let item = parent.children[currentIndex];
    if(isVisible(item)) return item;
  }
}

function isVisible(element) {
  var {display, visibility, width} = window.getComputedStyle(element);
  width = width ? parseInt(width) : element.clientWidth;
  return display != 'none' && visibility != 'hidden' && width > 0;
}

function saveOrder() {
  for(let side of ['left', 'right']) {
    let statusItems = document.querySelectorAll(`status-bar .status-bar-${side} > *`);
    atom.config.set(`move-status-items.${side}`, serializeStatusItems(statusItems));
  }
}

function serializeStatusItems(statusItems) {
  return Array.prototype.map.call(statusItems, function(statusItem) {
    if(!isHTMLElement(statusItem)) return '';
    return serializeStatusItem(statusItem);
  });
}

function serializeStatusItem(statusItem) {
  var serializedStatusItem = statusItem.tagName.toLowerCase();
  if(statusItem.className) serializedStatusItem += serializeClassName(statusItem.className);
  var isAttr = statusItem.getAttribute('is');
  if(isAttr) serializedStatusItem += `[is="${isAttr}"]`;
  return serializedStatusItem;
}

function serializeClassName(className) {
  serializedClassName = '';
  for(let classNamePart of className.split(' ')) {
    if(classNamePart.match(genericCSSClasses)) continue;
    serializedClassName += `.${classNamePart}`;
  }
  return serializedClassName;
}

function setInitialOrder() {
  for(let side of ['left', 'right']) {
    let serializedState = atom.config.get(`move-status-items.${side}`);
    if(!serializedState) continue;
    let statusItemContainer = document.querySelector(`status-bar .status-bar-${side}`);
    for(let serializedStatusItem of serializedState) {
      let statusItem = statusItemContainer.querySelector(serializedStatusItem);
      if(!isHTMLElement(statusItem)) continue;
      statusItemContainer.appendChild(statusItem);
    }
  }
}

function isHTMLElement(item) {
  return item instanceof HTMLElement;
}
<|endoftext|>


<commit_before>import { interpreter } from '../src';

const DECISION_TREE_1_V1 = require('./data/interpreter-test-suite/decide/trees/v1/enum.json');
const DECISION_TREE_2_V1 = require('./data/interpreter-test-suite/decide/trees/v1/continuous.json');
const DECISION_TREE_3_V1 = require('./data/interpreter-test-suite/decide/trees/v1/oneColor.json');

const DECISION_TREE_1_V2 = require('./data/interpreter-test-suite/decide/trees/v2/enum.json');
const DECISION_TREE_2_V2 = require('./data/interpreter-test-suite/decide/trees/v2/continuous.json');
const DECISION_TREE_3_V2 = require('./data/interpreter-test-suite/decide/trees/v2/oneColor.json');
const DECISION_TREE_4_V2 = require('./data/interpreter-test-suite/decide/trees/v2/optional_branch_enum.json');

const INVALID_DECISION_TREE_1 = require('./data/interpreter-test-suite/decide/trees/v1/emptyArray.json');

describe('interpreter.getDecisionRulesProperties(<tree>)', function() {
  it('can list the properties used in a tree\'s decision rules (enum output)', function() {
    const expected_output = [
      {
        'is_generated': true,
        'property': 'timeOfDay',
        'type': 'time_of_day'
      }
    ];
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_1_V1)).to.be.deep.equal(expected_output);
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_1_V2)).to.be.deep.equal(expected_output);
  });

  it('can list the properties used in a tree\'s decision rules (continuous output)', function() {
    const expected_output = [
      {
        'is_generated': true,
        'property': 'time',
        'type': 'time_of_day'
      },
      {
        'is_generated': true,
        'property': 'day',
        'type': 'day_of_week'
      },
      {
        'property': 'tz',
        'type': 'timezone'
      }
    ];
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_2_V1)).to.be.deep.equal(expected_output);
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_2_V2)).to.be.deep.equal(expected_output);
  });

  it('can list the properties used in a tree\'s decision rules (optional property)', function() {
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_4_V2)).to.be.deep.equal([
      {
        'property': 'enum4',
        'type': 'enum'
      },
      {
        'property': 'continuous3',
        'type': 'continuous'
      },
      {
        'property': 'continuous5',
        'type': 'continuous'
      },
      {
        'property': 'continuous4',
        'type': 'continuous'
      },
      {
        'property': 'enum1',
        'type': 'enum'
      },
      {
        'property': 'enum5',
        'type': 'enum'
      },
      {
        'is_optional': true,
        'property': 'enum2',
        'type': 'enum'
      },
      {
        'property': 'enum3',
        'type': 'enum'
      },
      {
        'property': 'enum6',
        'type': 'enum'
      },
      {
        'property': 'continuous2',
        'type': 'continuous'
      },
      {
        'property': 'continuous6',
        'type': 'continuous'
      },
      {
        'property': 'enum7',
        'type': 'enum'
      },
      {
        'property': 'continuous1',
        'type': 'continuous'
      }
    ]);
  });

  it('can list the properties used in a tree\'s decision rules (single leaf)', function() {
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_3_V1)).to.be.deep.equal([]);
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_3_V2)).to.be.deep.equal([]);
  });

  it('works properly on invalid trees', function() {
    expect(() => interpreter.getDecisionRulesProperties(INVALID_DECISION_TREE_1)).to.throw;
  });
});
<commit_msg>add tree with boolean property in getDecisionRulesProperties tests<commit_after>import { interpreter } from '../src';

const DECISION_TREE_1_V1 = require('./data/interpreter-test-suite/decide/trees/v1/enum.json');
const DECISION_TREE_2_V1 = require('./data/interpreter-test-suite/decide/trees/v1/continuous.json');
const DECISION_TREE_3_V1 = require('./data/interpreter-test-suite/decide/trees/v1/oneColor.json');

const DECISION_TREE_1_V2 = require('./data/interpreter-test-suite/decide/trees/v2/enum.json');
const DECISION_TREE_2_V2 = require('./data/interpreter-test-suite/decide/trees/v2/continuous.json');
const DECISION_TREE_3_V2 = require('./data/interpreter-test-suite/decide/trees/v2/oneColor.json');
const DECISION_TREE_4_V2 = require('./data/interpreter-test-suite/decide/trees/v2/optional_branch_enum.json');
const DECISION_TREE_5_V2 = require('./data/interpreter-test-suite/decide/trees/v2/boolean_operator.json');

const INVALID_DECISION_TREE_1 = require('./data/interpreter-test-suite/decide/trees/v1/emptyArray.json');

describe('interpreter.getDecisionRulesProperties(<tree>)', function() {
  it('can list the properties used in a tree\'s decision rules (enum output)', function() {
    const expected_output = [
      {
        'is_generated': true,
        'property': 'timeOfDay',
        'type': 'time_of_day'
      }
    ];
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_1_V1)).to.be.deep.equal(expected_output);
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_1_V2)).to.be.deep.equal(expected_output);
  });

  it('can list the properties used in a tree\'s decision rules (continuous output)', function() {
    const expected_output = [
      {
        'is_generated': true,
        'property': 'time',
        'type': 'time_of_day'
      },
      {
        'is_generated': true,
        'property': 'day',
        'type': 'day_of_week'
      },
      {
        'property': 'tz',
        'type': 'timezone'
      }
    ];
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_2_V1)).to.be.deep.equal(expected_output);
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_2_V2)).to.be.deep.equal(expected_output);
  });

  it('can list the properties used in a tree\'s decision rules (optional property)', function() {
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_4_V2)).to.be.deep.equal([
      {
        'property': 'enum4',
        'type': 'enum'
      },
      {
        'property': 'continuous3',
        'type': 'continuous'
      },
      {
        'property': 'continuous5',
        'type': 'continuous'
      },
      {
        'property': 'continuous4',
        'type': 'continuous'
      },
      {
        'property': 'enum1',
        'type': 'enum'
      },
      {
        'property': 'enum5',
        'type': 'enum'
      },
      {
        'is_optional': true,
        'property': 'enum2',
        'type': 'enum'
      },
      {
        'property': 'enum3',
        'type': 'enum'
      },
      {
        'property': 'enum6',
        'type': 'enum'
      },
      {
        'property': 'continuous2',
        'type': 'continuous'
      },
      {
        'property': 'continuous6',
        'type': 'continuous'
      },
      {
        'property': 'enum7',
        'type': 'enum'
      },
      {
        'property': 'continuous1',
        'type': 'continuous'
      }
    ]);
  });

  it('can list the properties used in a tree\'s decision rules (boolean operator)', function() {
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_5_V2)).to.be.deep.equal([
      {
        'property': 'presence',
        'type': 'boolean'
      },
      {
        'property': 'timeOfDay',
        'type': 'time_of_day',
        'is_generated': true
      }
    ]);
  });

  it('can list the properties used in a tree\'s decision rules (single leaf)', function() {
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_3_V1)).to.be.deep.equal([]);
    expect(interpreter.getDecisionRulesProperties(DECISION_TREE_3_V2)).to.be.deep.equal([]);
  });

  it('works properly on invalid trees', function() {
    expect(() => interpreter.getDecisionRulesProperties(INVALID_DECISION_TREE_1)).to.throw;
  });
});
<|endoftext|>


<commit_before>'use strict';

const { app } = require('electron');
const fs      = require('fs');
const path    = require('path');

const {
	NEGATIVE_IGNORE_SETTINGS,
	NEGATIVE_SETTINGS_PATH
} = process.env;

module.exports = {
	init(callback) {
		const testSettingsFilePath = NEGATIVE_SETTINGS_PATH && path.resolve(__dirname, NEGATIVE_SETTINGS_PATH);

		this.settingsFilePath = path.join(app.getPath('userData'), 'settings.json');

		fs.readFile(testSettingsFilePath || this.settingsFilePath, (err, result) => {
			if (err || NEGATIVE_IGNORE_SETTINGS === 'true') {
				this.settings = {};
			} else {
				try {
					this.settings = JSON.parse(result);
				} catch (e) {
					process.stderr.write(e);
					this.settings = {};
				}
			}

			callback();
		});
	},

	get(key) {
		return this.settings[key];
	},

	set(key, value) {
		this.settings[key] = value;
	},

	save(callback) {
		fs.writeFile(this.settingsFilePath, JSON.stringify(this.settings), callback);
	}
};
<commit_msg>Remove unnecessary log<commit_after>'use strict';

const { app } = require('electron');
const fs      = require('fs');
const path    = require('path');

const {
	NEGATIVE_IGNORE_SETTINGS,
	NEGATIVE_SETTINGS_PATH
} = process.env;

module.exports = {
	init(callback) {
		const testSettingsFilePath = NEGATIVE_SETTINGS_PATH && path.resolve(__dirname, NEGATIVE_SETTINGS_PATH);

		this.settingsFilePath = path.join(app.getPath('userData'), 'settings.json');

		fs.readFile(testSettingsFilePath || this.settingsFilePath, (err, result) => {
			if (err || NEGATIVE_IGNORE_SETTINGS === 'true') {
				this.settings = {};
			} else {
				try {
					this.settings = JSON.parse(result);
				} catch (e) {
					this.settings = {};
				}
			}

			callback();
		});
	},

	get(key) {
		return this.settings[key];
	},

	set(key, value) {
		this.settings[key] = value;
	},

	save(callback) {
		fs.writeFile(this.settingsFilePath, JSON.stringify(this.settings), callback);
	}
};
<|endoftext|>


<commit_before>import Vue from 'vue';
import InfiniteLoading from '../../../src/components/InfiniteLoading';

function isShow(elm) {
  const styles = getComputedStyle(elm);

  return styles.getPropertyValue('display') !== 'none';
}

describe('InfiniteLoading.vue', () => {
  let vm;
  const initConf = {
    data() {
      return {
        list: [],
        distance: 50,
        isLoadedAll: false,
        isDivScroll: true,
        isCustomSpinner: false,
      };
    },
    render(createElement) {
      return createElement(
        'div',
        {
          style: {
            height: '100px',
            overflow: this.isDivScroll ? 'auto' : 'visible',
          },
        },
        [
          createElement('ul', this.list.map((item) => createElement('li', item))),
          this.isLoadedAll ? undefined : createElement(InfiniteLoading,
            {
              props: {
                distance: this.distance,
                onInfinite: this.onInfinite,
              },
              ref: 'infiniteLoading',
            },
            [
              this.isCustomSpinner ? createElement('span',
                {
                  slot: 'spinner',
                },
                [
                  createElement('i', {
                    attrs: {
                      class: 'custom-spinner',
                    },
                  }),
                ]
              ) : undefined,
            ]
          ),
        ]
      );
    },
  };

  // create new Vue instance for every test case
  beforeEach(() => {
    const container = document.createElement('div');
    container.setAttribute('id', 'app');
    document.body.appendChild(container);

    vm = new Vue(initConf);
  });

  afterEach(() => {
    /**
     * because of call the $destroy method of parent cannot trigger
     * destroy event for child component in Vue.js 2.0.0-rc6
     */
    vm.$refs.infiniteLoading && vm.$refs.infiniteLoading.$destroy();
    vm.$destroy(true);
    /**
     * because of pass true as the argument for destroy method cannot
     * remove element from DOM in Vue.js 2.0.0-rc6
     */
    vm.$el && vm.$el.remove();
  });

  it('should render a basic template', (done) => {
    vm.isDivScroll = false;
    setTimeout(() => {
      vm.$mount('#app');
      expect(vm.$el.querySelector('.loading-default')).to.be.ok;
      done();
    }, 1);
  });

  it('should execute callback and display a spinner immediately after initialize', (done) => {
    vm.onInfinite = function test() {
      Vue.nextTick(() => {
        expect(isShow(vm.$el.querySelector('.loading-default'))).to.be.true;
        done();
      });
    };

    vm.$mount('#app');
  });

  it('should not to execute callback if the previous loading has not be completed', (done) => {
    vm.onInfinite = function test() {
      const len = this.list.length + 1;
      for (let i = len; i < len + 20; i++) {
        this.list.push(i);
      }

      Vue.nextTick(() => {
        if (this.list.length === 20) {
          vm.$el.addEventListener('scroll', () => {
            expect(this.list).to.have.lengthOf(20);
            done();
          });

          // trigger scroll event manually
          vm.$el.scrollTop = vm.$el.scrollHeight;
        }
      });
    }.bind(vm);

    vm.$mount('#app');
  });

  it('should be destroyed completely by v-if', (done) => {
    vm.onInfinite = function test() {
      this.isLoadedAll = true;
      Vue.nextTick(() => {
        expect(vm.$el.querySelector('.loading-default')).to.not.be.ok;
        done();
      });
    }.bind(vm);

    vm.$mount('#app');
  });

  it('should display no results prompt', (done) => {
    vm.onInfinite = function test() {
      this.$refs.infiniteLoading.$emit('$InfiniteLoading:complete');
      Vue.nextTick(() => {
        expect(isShow(vm.$el.querySelectorAll('.infinite-status-prompt')[0])).to.be.true;
        done();
      });
    }.bind(vm);

    vm.$mount('#app');
  });

  it('should display no more data prompt', (done) => {
    vm.onInfinite = function test() {
      this.$refs.infiniteLoading.$emit('$InfiniteLoading:loaded');
      this.$refs.infiniteLoading.$emit('$InfiniteLoading:complete');
      Vue.nextTick(() => {
        expect(isShow(vm.$el.querySelectorAll('.infinite-status-prompt')[1])).to.be.true;
        done();
      });
    }.bind(vm);

    vm.$mount('#app');
  });

  it('should reset component and call onInfinite again', (done) => {
    let callCount = 0;
    vm.onInfinite = function test() {
      if (!callCount++) {
        this.$refs.infiniteLoading.$emit('$InfiniteLoading:reset');
      } else {
        done();
      }
    }.bind(vm);

    vm.$mount('#app');
  });

  it('should display the custom spinner if customize it with slot', () => {
    vm.isCustomSpinner = true;
    delete vm.distance;
    vm.$mount('#app');

    expect(vm.$el.querySelector('.custom-spinner')).to.be.ok;
  });
});
<commit_msg>Add unit test for the continue fill up feature<commit_after>import Vue from 'vue';
import InfiniteLoading from '../../../src/components/InfiniteLoading';

function isShow(elm) {
  const styles = getComputedStyle(elm);

  return styles.getPropertyValue('display') !== 'none';
}

describe('InfiniteLoading.vue', () => {
  let vm;
  const initConf = {
    data() {
      return {
        list: [],
        distance: 50,
        isLoadedAll: false,
        isDivScroll: true,
        isCustomSpinner: false,
        listContainerHeight: 200,
        listItemHeight: 20,
      };
    },
    render(createElement) {
      return createElement(
        'div',
        {
          style: {
            height: `${this.listContainerHeight}px`,
            overflow: this.isDivScroll ? 'auto' : 'visible',
          },
        },
        [
          createElement('ul', {
            style: {
              margin: 0,
              padding: 0,
            },
          }, this.list.map((item) => createElement('li', {
            style: {
              height: `${this.listItemHeight}px`,
            },
          }, item))),
          this.isLoadedAll ? undefined : createElement(InfiniteLoading,
            {
              props: {
                distance: this.distance,
                onInfinite: this.onInfinite,
              },
              ref: 'infiniteLoading',
            },
            [
              this.isCustomSpinner ? createElement('span',
                {
                  slot: 'spinner',
                },
                [
                  createElement('i', {
                    attrs: {
                      class: 'custom-spinner',
                    },
                  }),
                ]
              ) : undefined,
            ]
          ),
        ]
      );
    },
  };

  // create new Vue instance for every test case
  beforeEach(() => {
    const container = document.createElement('div');
    container.setAttribute('id', 'app');
    document.body.appendChild(container);

    vm = new Vue(initConf);
  });

  afterEach(() => {
    /**
     * because of call the $destroy method of parent cannot trigger
     * destroy event for child component in Vue.js 2.0.0-rc6
     */
    vm.$refs.infiniteLoading && vm.$refs.infiniteLoading.$destroy();
    vm.$destroy(true);
    /**
     * because of pass true as the argument for destroy method cannot
     * remove element from DOM in Vue.js 2.0.0-rc6
     */
    vm.$el && vm.$el.remove();
  });

  it('should render a basic template', (done) => {
    vm.isDivScroll = false;
    setTimeout(() => {
      vm.$mount('#app');
      expect(vm.$el.querySelector('.loading-default')).to.be.ok;
      done();
    }, 1);
  });

  it('should execute callback and display a spinner immediately after initialize', (done) => {
    vm.onInfinite = function test() {
      Vue.nextTick(() => {
        expect(isShow(vm.$el.querySelector('.loading-default'))).to.be.true;
        done();
      });
    };

    vm.$mount('#app');
  });

  it('should not to execute callback if the previous loading has not be completed', (done) => {
    vm.onInfinite = function test() {
      const len = this.list.length + 1;
      for (let i = len; i < len + 20; i++) {
        this.list.push(i);
      }

      Vue.nextTick(() => {
        if (this.list.length === 20) {
          vm.$el.addEventListener('scroll', () => {
            expect(this.list).to.have.lengthOf(20);
            done();
          });

          // trigger scroll event manually
          vm.$el.scrollTop = vm.$el.scrollHeight;
        }
      });
    }.bind(vm);

    vm.$mount('#app');
  });

  it('should be destroyed completely by v-if', (done) => {
    vm.onInfinite = function test() {
      this.isLoadedAll = true;
      Vue.nextTick(() => {
        expect(vm.$el.querySelector('.loading-default')).to.not.be.ok;
        done();
      });
    }.bind(vm);

    vm.$mount('#app');
  });

  it('should display no results prompt', (done) => {
    vm.onInfinite = function test() {
      this.$refs.infiniteLoading.$emit('$InfiniteLoading:complete');
      Vue.nextTick(() => {
        expect(isShow(vm.$el.querySelectorAll('.infinite-status-prompt')[0])).to.be.true;
        done();
      });
    }.bind(vm);

    vm.$mount('#app');
  });

  it('should display no more data prompt', (done) => {
    vm.onInfinite = function test() {
      this.$refs.infiniteLoading.$emit('$InfiniteLoading:loaded');
      this.$refs.infiniteLoading.$emit('$InfiniteLoading:complete');
      // test for whether trigger again after complete
      this.$refs.infiniteLoading.$emit('$InfiniteLoading:loaded');
      Vue.nextTick(() => {
        expect(isShow(vm.$el.querySelectorAll('.infinite-status-prompt')[1])).to.be.true;
        done();
      });
    }.bind(vm);

    vm.$mount('#app');
  });

  it('should load results to fill up the container', (done) => {
    const expectedCount = Math.floor(vm.listContainerHeight / vm.listItemHeight);
    let i = 0;
    let timer;

    vm.onInfinite = function test() {
      setTimeout(() => {
        this.list.push(++i);
        this.$refs.infiniteLoading.$emit('$InfiniteLoading:loaded');
        clearTimeout(timer);
        timer = setTimeout(() => {
          if (i >= expectedCount) {
            done();
          } else {
            done(new Error('List not be fill up!'));
          }
        }, 100);
      }, 1);
    }.bind(vm);

    vm.$mount('#app');
  });

  it('should reset component and call onInfinite again', (done) => {
    let callCount = 0;
    vm.onInfinite = function test() {
      if (!callCount++) {
        this.$refs.infiniteLoading.$emit('$InfiniteLoading:reset');
      } else {
        done();
      }
    }.bind(vm);

    vm.$mount('#app');
  });

  it('should display the custom spinner if customize it with slot', () => {
    vm.isCustomSpinner = true;
    delete vm.distance;
    vm.$mount('#app');

    expect(vm.$el.querySelector('.custom-spinner')).to.be.ok;
  });
});
<|endoftext|>


<commit_before>jest.mock('../../../../lib/platform/github/gh-got-wrapper');
jest.mock('../../../../lib/datasource/npm');
jest.mock('got');

const hostRules = require('../../../../lib/util/host-rules');
const ghGot = require('../../../../lib/platform/github/gh-got-wrapper');

const { getChangeLogJSON } = require('../../../../lib/workers/pr/changelog');
const releaseNotes = require('../../../../lib/workers/pr/changelog/release-notes');

releaseNotes.addReleaseNotes = jest.fn(input => input);

const upgrade = {
  depName: 'renovate',
  versionScheme: 'semver',
  fromVersion: '1.0.0',
  toVersion: '3.0.0',
  repositoryUrl: 'https://github.com/chalk/chalk',
  releases: [
    { version: '0.9.0' },
    { version: '1.0.0', gitRef: 'npm_1.0.0' },
    {
      version: '2.3.0',
      gitRef: 'npm_2.3.0',
      releaseTimestamp: '2017-10-24T03:20:46.238Z',
    },
    { version: '2.2.2', gitRef: 'npm_2.2.2' },
    { version: '2.4.2', releaseTimestamp: '2017-12-24T03:20:46.238Z' },
    { version: '2.5.2' },
  ],
};

describe('workers/pr/changelog', () => {
  describe('getChangeLogJSON', () => {
    beforeEach(async () => {
      ghGot.mockClear();
      hostRules.clear();
      hostRules.update({
        platform: 'github',
        endpoint: 'https://api.github.com/',
      });
      await global.renovateCache.rmAll();
    });
    it('returns null if @types', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          fromVersion: null,
        })
      ).toBe(null);
      expect(ghGot.mock.calls).toHaveLength(0);
    });
    it('returns null if no fromVersion', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: 'https://github.com/DefinitelyTyped/DefinitelyTyped',
        })
      ).toBe(null);
      expect(ghGot.mock.calls).toHaveLength(0);
    });
    it('returns null if fromVersion equals toVersion', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          fromVersion: '1.0.0',
          toVersion: '1.0.0',
        })
      ).toBe(null);
      expect(ghGot.mock.calls).toHaveLength(0);
    });
    it('skips invalid repos', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: 'https://github.com/about',
        })
      ).toBe(null);
    });
    it('works without Github', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
        })
      ).toMatchSnapshot();
    });
    it('uses GitHub tags', async () => {
      ghGot.mockReturnValueOnce(
        Promise.resolve({
          body: [
            { name: '0.9.0' },
            { name: '1.0.0' },
            { name: '1.4.0' },
            { name: 'v2.3.0' },
            { name: '2.2.2' },
            { name: 'v2.4.2' },
          ],
        })
      );
      expect(
        await getChangeLogJSON({
          ...upgrade,
        })
      ).toMatchSnapshot();
    });
    it('returns cached JSON', async () => {
      const first = await getChangeLogJSON({ ...upgrade });
      const firstCalls = [...ghGot.mock.calls];
      ghGot.mockClear();
      const second = await getChangeLogJSON({ ...upgrade });
      const secondCalls = [...ghGot.mock.calls];
      expect(first).toEqual(second);
      expect(firstCalls.length).toBeGreaterThan(secondCalls.length);
    });
    it('filters unnecessary warns', async () => {
      ghGot.mockImplementation(() => {
        throw new Error('Unknown Github Repo');
      });
      expect(
        await getChangeLogJSON({
          ...upgrade,
          depName: '@renovate/no',
        })
      ).toMatchSnapshot();
    });
    it('supports node engines', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          depType: 'engines',
        })
      ).toMatchSnapshot();
    });
    it('handles no repositoryUrl', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: undefined,
        })
      ).toBe(null);
    });
    it('handles invalid repositoryUrl', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: 'http://example.com',
        })
      ).toBe(null);
    });
    it('handles no releases', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          releases: [],
        })
      ).toBe(null);
    });
    it('handles not enough releases', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          releases: [{ version: '0.9.0' }],
        })
      ).toBe(null);
    });
    it('supports github enterprise and github.com changelog', async () => {
      hostRules.update({
        platform: 'github',
        token: 'super_secret',
        endpoint: 'https://github-enterprise.example.com/',
      });
      expect(
        await getChangeLogJSON({
          ...upgrade,
        })
      ).toMatchSnapshot();
    });
    it('supports github enterprise and github enterprise changelog', async () => {
      hostRules.update({
        platform: 'github',
        endpoint: 'https://github-enterprise.example.com/',
      });
      process.env.GITHUB_ENDPOINT = '';
      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: 'https://github-enterprise.example.com/chalk/chalk',
        })
      ).toMatchSnapshot();
    });

    it('supports github enterprise alwo when retrieving data from cache', async () => {
      hostRules.update({
        platform: 'github',
        endpoint: 'https://github-enterprise.example.com/',
      });
      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: 'https://github-enterprise.example.com/chalk/chalk',
        })
      ).toMatchSnapshot();

      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: 'https://github-enterprise.example.com/chalk/chalk',
        })
      ).toMatchSnapshot();
    });
  });
});
<commit_msg>chore: refactor cached changelog test (#2607)<commit_after>jest.mock('../../../../lib/platform/github/gh-got-wrapper');
jest.mock('../../../../lib/datasource/npm');
jest.mock('got');

const hostRules = require('../../../../lib/util/host-rules');
const ghGot = require('../../../../lib/platform/github/gh-got-wrapper');

const { getChangeLogJSON } = require('../../../../lib/workers/pr/changelog');
const releaseNotes = require('../../../../lib/workers/pr/changelog/release-notes');

releaseNotes.addReleaseNotes = jest.fn(input => input);

const upgrade = {
  depName: 'renovate',
  versionScheme: 'semver',
  fromVersion: '1.0.0',
  toVersion: '3.0.0',
  repositoryUrl: 'https://github.com/chalk/chalk',
  releases: [
    { version: '0.9.0' },
    { version: '1.0.0', gitRef: 'npm_1.0.0' },
    {
      version: '2.3.0',
      gitRef: 'npm_2.3.0',
      releaseTimestamp: '2017-10-24T03:20:46.238Z',
    },
    { version: '2.2.2', gitRef: 'npm_2.2.2' },
    { version: '2.4.2', releaseTimestamp: '2017-12-24T03:20:46.238Z' },
    { version: '2.5.2' },
  ],
};

describe('workers/pr/changelog', () => {
  describe('getChangeLogJSON', () => {
    beforeEach(async () => {
      ghGot.mockClear();
      hostRules.clear();
      hostRules.update({
        platform: 'github',
        endpoint: 'https://api.github.com/',
      });
      await global.renovateCache.rmAll();
    });
    it('returns null if @types', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          fromVersion: null,
        })
      ).toBe(null);
      expect(ghGot.mock.calls).toHaveLength(0);
    });
    it('returns null if no fromVersion', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: 'https://github.com/DefinitelyTyped/DefinitelyTyped',
        })
      ).toBe(null);
      expect(ghGot.mock.calls).toHaveLength(0);
    });
    it('returns null if fromVersion equals toVersion', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          fromVersion: '1.0.0',
          toVersion: '1.0.0',
        })
      ).toBe(null);
      expect(ghGot.mock.calls).toHaveLength(0);
    });
    it('skips invalid repos', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: 'https://github.com/about',
        })
      ).toBe(null);
    });
    it('works without Github', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
        })
      ).toMatchSnapshot();
    });
    it('uses GitHub tags', async () => {
      ghGot.mockReturnValueOnce(
        Promise.resolve({
          body: [
            { name: '0.9.0' },
            { name: '1.0.0' },
            { name: '1.4.0' },
            { name: 'v2.3.0' },
            { name: '2.2.2' },
            { name: 'v2.4.2' },
          ],
        })
      );
      expect(
        await getChangeLogJSON({
          ...upgrade,
        })
      ).toMatchSnapshot();
    });
    it('returns cached JSON', async () => {
      ghGot.mockClear();
      const first = await getChangeLogJSON({ ...upgrade });
      const firstCallsCount = ghGot.mock.calls.length;
      const second = await getChangeLogJSON({ ...upgrade });
      const secondCallsCount = ghGot.mock.calls.length - firstCallsCount;
      expect(first).toEqual(second);
      expect(firstCallsCount).toBeGreaterThan(secondCallsCount);
    });
    it('filters unnecessary warns', async () => {
      ghGot.mockImplementation(() => {
        throw new Error('Unknown Github Repo');
      });
      expect(
        await getChangeLogJSON({
          ...upgrade,
          depName: '@renovate/no',
        })
      ).toMatchSnapshot();
    });
    it('supports node engines', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          depType: 'engines',
        })
      ).toMatchSnapshot();
    });
    it('handles no repositoryUrl', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: undefined,
        })
      ).toBe(null);
    });
    it('handles invalid repositoryUrl', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: 'http://example.com',
        })
      ).toBe(null);
    });
    it('handles no releases', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          releases: [],
        })
      ).toBe(null);
    });
    it('handles not enough releases', async () => {
      expect(
        await getChangeLogJSON({
          ...upgrade,
          releases: [{ version: '0.9.0' }],
        })
      ).toBe(null);
    });
    it('supports github enterprise and github.com changelog', async () => {
      hostRules.update({
        platform: 'github',
        token: 'super_secret',
        endpoint: 'https://github-enterprise.example.com/',
      });
      expect(
        await getChangeLogJSON({
          ...upgrade,
        })
      ).toMatchSnapshot();
    });
    it('supports github enterprise and github enterprise changelog', async () => {
      hostRules.update({
        platform: 'github',
        endpoint: 'https://github-enterprise.example.com/',
      });
      process.env.GITHUB_ENDPOINT = '';
      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: 'https://github-enterprise.example.com/chalk/chalk',
        })
      ).toMatchSnapshot();
    });

    it('supports github enterprise alwo when retrieving data from cache', async () => {
      hostRules.update({
        platform: 'github',
        endpoint: 'https://github-enterprise.example.com/',
      });
      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: 'https://github-enterprise.example.com/chalk/chalk',
        })
      ).toMatchSnapshot();

      expect(
        await getChangeLogJSON({
          ...upgrade,
          repositoryUrl: 'https://github-enterprise.example.com/chalk/chalk',
        })
      ).toMatchSnapshot();
    });
  });
});
<|endoftext|>