<commit_before>(function($) {

	var suite = Echo.Tests.Unit.compatibilityTest = function() {};

	suite.prototype.info = {
		"suiteName": "Compatibility tests"
	};

	suite.prototype.tests = {};

	suite.prototype.tests.JqueryNoConflict = {
		"config": {
			"async": true,
			"testTimeout": 20000 // 20 secs
		},
		"check": function() {
			var testVersion = "1.4.1";

			QUnit.ok(!(window.$ || window.jQuery),
				"Checking if jQuery is not defined in the window namespace");

			Echo.Loader.download([{"url": "https://ajax.googleapis.com/ajax/libs/jquery/" + testVersion + "/jquery.min.js"}], function () {
				QUnit.ok(window.$.fn.jquery === testVersion && $.fn.jquery !== window.$.fn.jquery, "Checking if Echo jQuery lib wasn't overridden after another jQuery version inclusion into the page");
				Echo.Loader.download([{"url": "third-party/jquery.pack.js"}], function () {
					QUnit.ok(window.$.fn.jquery === testVersion && $.fn.jquery !== window.$.fn.jquery, "Checking if the native jQuery lib on the page is not overridden after Echo jQuery inclusion");
					window.jQuery.noConflict(true);
					QUnit.start();
				});
			});
		}
	};

	suite.prototype.tests.V2V3CompatibilityCheck = {
		"config": {
			"async": true,
			"testTimeout": 20000 // 20 secs
		},
		"check": function() {
			var self = this;
			var ids = {
				"v2": "echo-apps-v2",
				"v3": "echo-apps-v3"
			};

			var Stack = function(callback) {
				var counter = 0;
				var state = {};
				this.init = function(id) {
					if( !state.hasOwnProperty(id) ) {
						state[id] = false;
						counter++;
					}
				};
				this.done = function(id) {
					if (state[id] === false) {
						state[id] = true;
						--counter == 0 && callback();
					}
				}
			};

			Echo.Variables.V2V3Test = new Stack(function() {
				var res = !$.map(window[ids.v3].Echo, function(val, key) {
					return window[ids.v2].Echo[key];
				}).length;

				QUnit.ok(res, "Check if v3 Echo keys  don't match with v2 Echo keys");
				QUnit.start();
			});

			$.each(ids, function(key, val) {
				self.config.target.append('');
			});
		}
	};

	suite.prototype.tests.YepnopeNoConflict = {
		"config": {
			"async": true,
			"testTimeout": 20000 // 20 secs
		},
		"check": function() {
			var origYepnope = window.yepnope;

			QUnit.ok(!!Echo.yepnope, "Check if Echo's yepnope is loaded");

			QUnit.ok(!window.yepnope, "Check if global yepnope is not loaded");

			Echo.Loader.download([{"url": "https://cdnjs.cloudflare.com/ajax/libs/yepnope/1.5.4/yepnope.min.js"}], function () {
				var prevYepnope = window.yepnope;

				QUnit.ok(!!window.yepnope, "Check if global yepnope has been loaded");

				QUnit.ok(Echo.yepnope !== window.yepnope,
					"Check if global yepnope and Echo's yepnope are different objects");

				Echo.Loader.download([{"url": "loader.js"}], function () {

					QUnit.ok(prevYepnope === window.yepnope,
						"Check if global yepnope is not overwritten after Echo's yepnope has been loaded");

					window.yepnope = origYepnope;
					QUnit.start();
				});
			});
		}
	};

})(Echo.jQuery);
<commit_msg>F:584 Let's do not suppose that global yepnope is not loaded in the test beginning<commit_after>(function($) {

	var suite = Echo.Tests.Unit.compatibilityTest = function() {};

	suite.prototype.info = {
		"suiteName": "Compatibility tests"
	};

	suite.prototype.tests = {};

	suite.prototype.tests.JqueryNoConflict = {
		"config": {
			"async": true,
			"testTimeout": 20000 // 20 secs
		},
		"check": function() {
			var testVersion = "1.4.1";

			QUnit.ok(!(window.$ || window.jQuery),
				"Checking if jQuery is not defined in the window namespace");

			Echo.Loader.download([{"url": "https://ajax.googleapis.com/ajax/libs/jquery/" + testVersion + "/jquery.min.js"}], function () {
				QUnit.ok(window.$.fn.jquery === testVersion && $.fn.jquery !== window.$.fn.jquery, "Checking if Echo jQuery lib wasn't overridden after another jQuery version inclusion into the page");
				Echo.Loader.download([{"url": "third-party/jquery.pack.js"}], function () {
					QUnit.ok(window.$.fn.jquery === testVersion && $.fn.jquery !== window.$.fn.jquery, "Checking if the native jQuery lib on the page is not overridden after Echo jQuery inclusion");
					window.jQuery.noConflict(true);
					QUnit.start();
				});
			});
		}
	};

	suite.prototype.tests.V2V3CompatibilityCheck = {
		"config": {
			"async": true,
			"testTimeout": 20000 // 20 secs
		},
		"check": function() {
			var self = this;
			var ids = {
				"v2": "echo-apps-v2",
				"v3": "echo-apps-v3"
			};

			var Stack = function(callback) {
				var counter = 0;
				var state = {};
				this.init = function(id) {
					if( !state.hasOwnProperty(id) ) {
						state[id] = false;
						counter++;
					}
				};
				this.done = function(id) {
					if (state[id] === false) {
						state[id] = true;
						--counter == 0 && callback();
					}
				}
			};

			Echo.Variables.V2V3Test = new Stack(function() {
				var res = !$.map(window[ids.v3].Echo, function(val, key) {
					return window[ids.v2].Echo[key];
				}).length;

				QUnit.ok(res, "Check if v3 Echo keys  don't match with v2 Echo keys");
				QUnit.start();
			});

			$.each(ids, function(key, val) {
				self.config.target.append('');
			});
		}
	};

	suite.prototype.tests.YepnopeNoConflict = {
		"config": {
			"async": true,
			"testTimeout": 20000 // 20 secs
		},
		"check": function() {
			var origYepnope = window.yepnope;

			QUnit.ok(!!Echo.yepnope, "Check if Echo's yepnope is loaded");

			Echo.Loader.download([{"url": "https://cdnjs.cloudflare.com/ajax/libs/yepnope/1.5.4/yepnope.min.js"}], function () {
				var globalYepnope = window.yepnope;

				QUnit.ok(!!window.yepnope, "Check if global yepnope has been loaded");

				QUnit.ok(Echo.yepnope !== window.yepnope,
					"Check if global yepnope and Echo's yepnope are different objects");

				Echo.Loader.download([{"url": "loader.js"}], function () {

					QUnit.ok(globalYepnope === window.yepnope,
						"Check if global yepnope is not overwritten after Echo's yepnope has been loaded");

					window.yepnope = origYepnope;
					QUnit.start();
				});
			});
		}
	};

})(Echo.jQuery);
<|endoftext|>


<commit_before>var handler = require('../../src/modules/handler');
var chai = require('chai');
var expect = chai.expect;

describe("Handler tests: need to be sure that handler works fine with various keys argument", function () {

    describe("Should be ended with error when ... ", function () {

        it("... keys value is empty", function () {
            return handler.execute('').then(noop, function (err) {
                expect(err).to.be.ok;
                expect(err.message).to.equal('Keys is not provided.');
            });
        });

        it("... keys value is undefined", function () {
            return handler.execute(undefined).then(noop, function (err) {
                expect(err).to.be.ok;
                expect(err.message).to.equal('Keys is not provided.');
            });
        });

        it("... keys value is null", function () {
            return handler.execute(null).then(noop, function (err) {
                expect(err).to.be.ok;
                expect(err.message).to.equal('Keys is not provided.');
            });
        });
    });

    describe("Should receive success result when ... ", function () {

        it("... keys are specified and not empty", function () {
            return handler.execute('123').then(function (res) {
                expect(res).to.be.ok;
                expect(res.encoded).to.be.equal('"123"');
            }, noop);
        });

    });

});

function noop() {

}<commit_msg>cleanup;<commit_after>// dependencies

var handler = require('../../src/modules/handler');
var chai = require('chai');
var expect = chai.expect;

// specs

describe("Handler tests: need to be sure that handler works fine with various keys argument", function () {

    describe("Should be ended with error when ... ", function () {

        it("... keys value is empty", function () {
            return handler.execute('').then(noop, function (err) {
                expect(err).to.be.ok;
                expect(err.message).to.equal('Keys is not provided.');
            });
        });

        it("... keys value is undefined", function () {
            return handler.execute(undefined).then(noop, function (err) {
                expect(err).to.be.ok;
                expect(err.message).to.equal('Keys is not provided.');
            });
        });

        it("... keys value is null", function () {
            return handler.execute(null).then(noop, function (err) {
                expect(err).to.be.ok;
                expect(err.message).to.equal('Keys is not provided.');
            });
        });
    });

    describe("Should receive success result when ... ", function () {

        it("... keys are specified and not empty", function () {
            return handler.execute('123').then(function (res) {
                expect(res).to.be.ok;
                expect(res.encoded).to.be.equal('"123"');
            }, noop);
        });

    });

});

function noop() {

}<|endoftext|>


<commit_before>goog.provide('ol.FeatureOverlay');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.object');
goog.require('ol.Collection');
goog.require('ol.CollectionEventType');
goog.require('ol.Feature');
goog.require('ol.render.EventType');
goog.require('ol.renderer.vector');
goog.require('ol.style.Style');

/**
 * @classdesc
 * A mechanism for changing the style of a small number of features on a
 * temporary basis, for example highlighting. This is necessary with the Canvas
 * renderer, where, unlike in SVG, features cannot be individually referenced.
 * See examples/vector-layers for an example: create a FeatureOverlay with a
 * different style, copy the feature(s) you want rendered in this different
 * style into it, and then remove them again when you're finished.
 *
 * @constructor
 * @param {olx.FeatureOverlayOptions=} opt_options Options.
 * @api
 */
ol.FeatureOverlay = function(opt_options) {

  var options = goog.isDef(opt_options) ? opt_options : {};

  /**
   * @private
   * @type {ol.Collection.<ol.Feature>}
   */
  this.features_ = null;

  /**
   * @private
   * @type {Array.<goog.events.Key>}
   */
  this.featuresListenerKeys_ = null;

  /**
   * @private
   * @type {Object.<string, goog.events.Key>}
   */
  this.featureChangeListenerKeys_ = null;

  /**
   * @private
   * @type {ol.Map}
   */
  this.map_ = null;

  /**
   * @private
   * @type {goog.events.Key}
   */
  this.postComposeListenerKey_ = null;

  /**
   * @private
   * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
   */
  this.style_ = null;

  /**
   * @private
   * @type {ol.style.StyleFunction|undefined}
   */
  this.styleFunction_ = undefined;

  this.setStyle(goog.isDef(options.style) ?
      options.style : ol.style.defaultStyleFunction);

  if (goog.isDef(options.features)) {
    if (goog.isArray(options.features)) {
      this.setFeatures(new ol.Collection(options.features.slice()));
    } else {
      goog.asserts.assertInstanceof(options.features, ol.Collection,
          'options.features should be an ol.Collection');
      this.setFeatures(options.features);
    }
  } else {
    this.setFeatures(new ol.Collection());
  }

  if (goog.isDef(options.map)) {
    this.setMap(options.map);
  }

};

/**
 * @param {ol.Feature} feature Feature.
 * @api
 */
ol.FeatureOverlay.prototype.addFeature = function(feature) {
  this.features_.push(feature);
};

/**
 * @return {ol.Collection.<ol.Feature>} Features collection.
 * @api
 */
ol.FeatureOverlay.prototype.getFeatures = function() {
  return this.features_;
};

/**
 * @return {?ol.Map} The map with which this feature overlay is associated.
 * @api
 */
ol.FeatureOverlay.prototype.getMap = function() {
  return this.map_;
};

/**
 * @private
 */
ol.FeatureOverlay.prototype.handleFeatureChange_ = function() {
  this.render_();
};

/**
 * @private
 * @param {ol.CollectionEvent} collectionEvent Collection event.
 */
ol.FeatureOverlay.prototype.handleFeaturesAdd_ = function(collectionEvent) {
  goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_),
      'this.featureChangeListenerKeys_ should not be null');
  var feature = /** @type {ol.Feature} */ (collectionEvent.element);
  this.featureChangeListenerKeys_[goog.getUid(feature).toString()] =
      goog.events.listen(feature, goog.events.EventType.CHANGE,
      this.handleFeatureChange_, false, this);
  this.render_();
};

/**
 * @private
 * @param {ol.CollectionEvent} collectionEvent Collection event.
 */
ol.FeatureOverlay.prototype.handleFeaturesRemove_ = function(collectionEvent) {
  goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_),
      'this.featureChangeListenerKeys_ should not be null');
  var feature = /** @type {ol.Feature} */ (collectionEvent.element);
  var key = goog.getUid(feature).toString();
  goog.events.unlistenByKey(this.featureChangeListenerKeys_[key]);
  delete this.featureChangeListenerKeys_[key];
  this.render_();
};

/**
 * Handle changes in image style state.
 * @param {goog.events.Event} event Image style change event.
 * @private
 */
ol.FeatureOverlay.prototype.handleImageChange_ = function(event) {
  this.render_();
};

/**
 * @param {ol.render.Event} event Event.
 * @private
 */
ol.FeatureOverlay.prototype.handleMapPostCompose_ = function(event) {
  if (goog.isNull(this.features_)) {
    return;
  }
  var styleFunction = this.styleFunction_;
  if (!goog.isDef(styleFunction)) {
    styleFunction = ol.style.defaultStyleFunction;
  }
  var replayGroup = /** @type {ol.render.IReplayGroup} */
      (event.replayGroup);
  goog.asserts.assert(goog.isDef(replayGroup),
      'replayGroup should be defined');
  var frameState = event.frameState;
  var pixelRatio = frameState.pixelRatio;
  var resolution = frameState.viewState.resolution;
  var squaredTolerance = ol.renderer.vector.getSquaredTolerance(resolution,
      pixelRatio);
  var i, ii, styles, featureStyleFunction;
  this.features_.forEach(function(feature) {
    featureStyleFunction = feature.getStyleFunction();
    styles = goog.isDef(featureStyleFunction) ?
        featureStyleFunction.call(feature, resolution) :
        styleFunction(feature, resolution);

    if (!goog.isDefAndNotNull(styles)) {
      return;
    }
    ii = styles.length;
    for (i = 0; i < ii; ++i) {
      ol.renderer.vector.renderFeature(replayGroup, feature, styles[i],
          squaredTolerance, this.handleImageChange_, this);
    }
  }, this);
};

/**
 * @param {ol.Feature} feature Feature.
 * @api
 */
ol.FeatureOverlay.prototype.removeFeature = function(feature) {
  this.features_.remove(feature);
};

/**
 * @private
 */
ol.FeatureOverlay.prototype.render_ = function() {
  if (!goog.isNull(this.map_)) {
    this.map_.render();
  }
};

/**
 * @param {ol.Collection.<ol.Feature>} features Features collection.
 * @api
 */
ol.FeatureOverlay.prototype.setFeatures = function(features) {
  if (!goog.isNull(this.featuresListenerKeys_)) {
    goog.array.forEach(this.featuresListenerKeys_, goog.events.unlistenByKey);
    this.featuresListenerKeys_ = null;
  }
  if (!goog.isNull(this.featureChangeListenerKeys_)) {
    goog.array.forEach(
        goog.object.getValues(this.featureChangeListenerKeys_),
        goog.events.unlistenByKey);
    this.featureChangeListenerKeys_ = null;
  }
  this.features_ = features;
  if (!goog.isNull(features)) {
    this.featuresListenerKeys_ = [
      goog.events.listen(features, ol.CollectionEventType.ADD,
          this.handleFeaturesAdd_, false, this),
      goog.events.listen(features, ol.CollectionEventType.REMOVE,
          this.handleFeaturesRemove_, false, this)
    ];
    this.featureChangeListenerKeys_ = {};
    features.forEach(function(feature) {
      this.featureChangeListenerKeys_[goog.getUid(feature).toString()] =
          goog.events.listen(feature, goog.events.EventType.CHANGE,
          this.handleFeatureChange_, false, this);
    }, this);
  }
  this.render_();
};

/**
 * @param {ol.Map} map Map.
 * @api
 */
ol.FeatureOverlay.prototype.setMap = function(map) {
  if (!goog.isNull(this.postComposeListenerKey_)) {
    goog.events.unlistenByKey(this.postComposeListenerKey_);
    this.postComposeListenerKey_ = null;
  }
  this.render_();
  this.map_ = map;
  if (!goog.isNull(map)) {
    this.postComposeListenerKey_ = goog.events.listen(
        map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false,
        this);
    map.render();
  }
};

/**
 * Set the style for features.  This can be a single style object, an array
 * of styles, or a function that takes a feature and resolution and returns
 * an array of styles.
 * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} style
 *     Overlay style.
 * @api
 */
ol.FeatureOverlay.prototype.setStyle = function(style) {
  this.style_ = style;
  this.styleFunction_ = ol.style.createStyleFunction(style);
  this.render_();
};

/**
 * Get the style for features.  This returns whatever was passed to the `style`
 * option at construction or to the `setStyle` method.
 * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
 *     Overlay style.
 * @api
 */
ol.FeatureOverlay.prototype.getStyle = function() {
  return this.style_;
};

/**
 * Get the style function.
 * @return {ol.style.StyleFunction|undefined} Style function.
 * @api
 */
ol.FeatureOverlay.prototype.getStyleFunction = function() {
  return this.styleFunction_;
};
<commit_msg>Feature overlay docs<commit_after>goog.provide('ol.FeatureOverlay');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.object');
goog.require('ol.Collection');
goog.require('ol.CollectionEventType');
goog.require('ol.Feature');
goog.require('ol.render.EventType');
goog.require('ol.renderer.vector');
goog.require('ol.style.Style');

/**
 * @classdesc
 * A mechanism for changing the style of a small number of features on a
 * temporary basis, for example highlighting. This is necessary with the Canvas
 * renderer, where, unlike in SVG, features cannot be individually referenced.
 * See examples/vector-layers for an example: create a FeatureOverlay with a
 * different style, copy the feature(s) you want rendered in this different
 * style into it, and then remove them again when you're finished.
 *
 * @constructor
 * @param {olx.FeatureOverlayOptions=} opt_options Options.
 * @api
 */
ol.FeatureOverlay = function(opt_options) {

  var options = goog.isDef(opt_options) ? opt_options : {};

  /**
   * @private
   * @type {ol.Collection.<ol.Feature>}
   */
  this.features_ = null;

  /**
   * @private
   * @type {Array.<goog.events.Key>}
   */
  this.featuresListenerKeys_ = null;

  /**
   * @private
   * @type {Object.<string, goog.events.Key>}
   */
  this.featureChangeListenerKeys_ = null;

  /**
   * @private
   * @type {ol.Map}
   */
  this.map_ = null;

  /**
   * @private
   * @type {goog.events.Key}
   */
  this.postComposeListenerKey_ = null;

  /**
   * @private
   * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
   */
  this.style_ = null;

  /**
   * @private
   * @type {ol.style.StyleFunction|undefined}
   */
  this.styleFunction_ = undefined;

  this.setStyle(goog.isDef(options.style) ?
      options.style : ol.style.defaultStyleFunction);

  if (goog.isDef(options.features)) {
    if (goog.isArray(options.features)) {
      this.setFeatures(new ol.Collection(options.features.slice()));
    } else {
      goog.asserts.assertInstanceof(options.features, ol.Collection,
          'options.features should be an ol.Collection');
      this.setFeatures(options.features);
    }
  } else {
    this.setFeatures(new ol.Collection());
  }

  if (goog.isDef(options.map)) {
    this.setMap(options.map);
  }

};

/**
 * Add a feature to the overlay.
 * @param {ol.Feature} feature Feature.
 * @api
 */
ol.FeatureOverlay.prototype.addFeature = function(feature) {
  this.features_.push(feature);
};

/**
 * Get the features on the overlay.
 * @return {ol.Collection.<ol.Feature>} Features collection.
 * @api
 */
ol.FeatureOverlay.prototype.getFeatures = function() {
  return this.features_;
};

/**
 * Get the map associated with the overlay.
 * @return {?ol.Map} The map with which this feature overlay is associated.
 * @api
 */
ol.FeatureOverlay.prototype.getMap = function() {
  return this.map_;
};

/**
 * @private
 */
ol.FeatureOverlay.prototype.handleFeatureChange_ = function() {
  this.render_();
};

/**
 * @private
 * @param {ol.CollectionEvent} collectionEvent Collection event.
 */
ol.FeatureOverlay.prototype.handleFeaturesAdd_ = function(collectionEvent) {
  goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_),
      'this.featureChangeListenerKeys_ should not be null');
  var feature = /** @type {ol.Feature} */ (collectionEvent.element);
  this.featureChangeListenerKeys_[goog.getUid(feature).toString()] =
      goog.events.listen(feature, goog.events.EventType.CHANGE,
      this.handleFeatureChange_, false, this);
  this.render_();
};

/**
 * @private
 * @param {ol.CollectionEvent} collectionEvent Collection event.
 */
ol.FeatureOverlay.prototype.handleFeaturesRemove_ = function(collectionEvent) {
  goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_),
      'this.featureChangeListenerKeys_ should not be null');
  var feature = /** @type {ol.Feature} */ (collectionEvent.element);
  var key = goog.getUid(feature).toString();
  goog.events.unlistenByKey(this.featureChangeListenerKeys_[key]);
  delete this.featureChangeListenerKeys_[key];
  this.render_();
};

/**
 * Handle changes in image style state.
 * @param {goog.events.Event} event Image style change event.
 * @private
 */
ol.FeatureOverlay.prototype.handleImageChange_ = function(event) {
  this.render_();
};

/**
 * @param {ol.render.Event} event Event.
 * @private
 */
ol.FeatureOverlay.prototype.handleMapPostCompose_ = function(event) {
  if (goog.isNull(this.features_)) {
    return;
  }
  var styleFunction = this.styleFunction_;
  if (!goog.isDef(styleFunction)) {
    styleFunction = ol.style.defaultStyleFunction;
  }
  var replayGroup = /** @type {ol.render.IReplayGroup} */
      (event.replayGroup);
  goog.asserts.assert(goog.isDef(replayGroup),
      'replayGroup should be defined');
  var frameState = event.frameState;
  var pixelRatio = frameState.pixelRatio;
  var resolution = frameState.viewState.resolution;
  var squaredTolerance = ol.renderer.vector.getSquaredTolerance(resolution,
      pixelRatio);
  var i, ii, styles, featureStyleFunction;
  this.features_.forEach(function(feature) {
    featureStyleFunction = feature.getStyleFunction();
    styles = goog.isDef(featureStyleFunction) ?
        featureStyleFunction.call(feature, resolution) :
        styleFunction(feature, resolution);

    if (!goog.isDefAndNotNull(styles)) {
      return;
    }
    ii = styles.length;
    for (i = 0; i < ii; ++i) {
      ol.renderer.vector.renderFeature(replayGroup, feature, styles[i],
          squaredTolerance, this.handleImageChange_, this);
    }
  }, this);
};

/**
 * Remove a feature from the overlay.
 * @param {ol.Feature} feature The feature to be removed.
 * @api
 */
ol.FeatureOverlay.prototype.removeFeature = function(feature) {
  this.features_.remove(feature);
};

/**
 * @private
 */
ol.FeatureOverlay.prototype.render_ = function() {
  if (!goog.isNull(this.map_)) {
    this.map_.render();
  }
};

/**
 * Set the features for the overlay.
 * @param {ol.Collection.<ol.Feature>} features Features collection.
 * @api
 */
ol.FeatureOverlay.prototype.setFeatures = function(features) {
  if (!goog.isNull(this.featuresListenerKeys_)) {
    goog.array.forEach(this.featuresListenerKeys_, goog.events.unlistenByKey);
    this.featuresListenerKeys_ = null;
  }
  if (!goog.isNull(this.featureChangeListenerKeys_)) {
    goog.array.forEach(
        goog.object.getValues(this.featureChangeListenerKeys_),
        goog.events.unlistenByKey);
    this.featureChangeListenerKeys_ = null;
  }
  this.features_ = features;
  if (!goog.isNull(features)) {
    this.featuresListenerKeys_ = [
      goog.events.listen(features, ol.CollectionEventType.ADD,
          this.handleFeaturesAdd_, false, this),
      goog.events.listen(features, ol.CollectionEventType.REMOVE,
          this.handleFeaturesRemove_, false, this)
    ];
    this.featureChangeListenerKeys_ = {};
    features.forEach(function(feature) {
      this.featureChangeListenerKeys_[goog.getUid(feature).toString()] =
          goog.events.listen(feature, goog.events.EventType.CHANGE,
          this.handleFeatureChange_, false, this);
    }, this);
  }
  this.render_();
};

/**
 * Set the map for the overlay.
 * @param {ol.Map} map Map.
 * @api
 */
ol.FeatureOverlay.prototype.setMap = function(map) {
  if (!goog.isNull(this.postComposeListenerKey_)) {
    goog.events.unlistenByKey(this.postComposeListenerKey_);
    this.postComposeListenerKey_ = null;
  }
  this.render_();
  this.map_ = map;
  if (!goog.isNull(map)) {
    this.postComposeListenerKey_ = goog.events.listen(
        map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false,
        this);
    map.render();
  }
};

/**
 * Set the style for features.  This can be a single style object, an array
 * of styles, or a function that takes a feature and resolution and returns
 * an array of styles.
 * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} style
 *     Overlay style.
 * @api
 */
ol.FeatureOverlay.prototype.setStyle = function(style) {
  this.style_ = style;
  this.styleFunction_ = ol.style.createStyleFunction(style);
  this.render_();
};

/**
 * Get the style for features.  This returns whatever was passed to the `style`
 * option at construction or to the `setStyle` method.
 * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
 *     Overlay style.
 * @api
 */
ol.FeatureOverlay.prototype.getStyle = function() {
  return this.style_;
};

/**
 * Get the style function.
 * @return {ol.style.StyleFunction|undefined} Style function.
 * @api
 */
ol.FeatureOverlay.prototype.getStyleFunction = function() {
  return this.styleFunction_;
};
<|endoftext|>


<commit_before>import React, { Component, Fragment } from 'react'
import Helmet from 'react-helmet'
import api from 'api'
import storage from 'storage'
import {
  ThemeProvider,
  Box,
  Button,
  Card,
  Container,
  Section,
  Flex,
  Heading,
  LargeButton,
  Link as DSLink,
  Text as T,
  cx
} from '@hackclub/design-system'
import { clubApplicationSchema } from 'components/apply/ClubApplicationForm'
import ApplyNav from 'components/apply/ApplyNav'
import LeaderInviteForm from 'components/apply/LeaderInviteForm'
import Login from 'components/apply/Login'
import SubmitButton from 'components/apply/SubmitButton'
import LoadingAnimation from 'components/LoadingAnimation'
import fetch from 'unfetch'
import Link from 'gatsby-link'

LargeButton.link = LargeButton.withComponent(Link)

const A = DSLink.extend`
  cursor: pointer;
  :hover {
    text-decoration: underline;
  }
`

const Text = props => <T mt={3} mb={3} {...props} />

const Span = T.span

const timeSince = time => {
  const seconds = Math.floor((new Date() - new Date(time)) / 1000)
  const intervals = [
    [Math.floor(seconds / (60 * 60 * 24 * 7)), 'weeks'],
    [Math.floor(seconds / (60 * 60 * 24)), 'days'],
    [Math.floor(seconds / (60 * 60)), 'hours'],
    [Math.floor(seconds / 60), 'minutes']
  ]
  for (var i = 0; i < intervals.length; i++) {
    let interval = intervals[i]
    if (interval[0] > 1) {
      return interval.join(' ')
    }
  }
  return 'less than a minute'
}

const Neg = () =>

const CustomCard = Card.extend.attrs({
  p: [3, 4],
  color: 'black',
  bg: 'snow',
  w: 1,
  boxShadowSize: 'md'
})``

const ApplicationCard = props => {
  const {
    id,
    leader_profiles,
    updated_at,
    created_at,
    submitted_at,
    point_of_contact_id
  } = props.app
  const { authToken, callback, app, resetCallback } = props

  const leaderProfile = leader_profiles.find(
    profile => profile.user.id == props.userId
  )
  const coLeaderProfiles = leader_profiles.filter(
    profile => profile.user.id != props.userId
  )
  const isPoc = leaderProfile.user.id === point_of_contact_id

  const completeProfiles = leader_profiles.every(
    profile => profile.completed_at
  )
  const completeApplication = clubApplicationSchema.isValidSync(app)
  let submitButtonStatus
  if (app.submitted_at) {
    submitButtonStatus = 'submitted'
  } else if (completeApplication && completeProfiles) {
    submitButtonStatus = 'complete'
  } else {
    submitButtonStatus = 'incomplete'
  }

  return (
    <Container maxWidth={36} my={3} p={3}>
      {app.rejected_at ? (
        <Flex mb={4}>
          <CustomCard>
            <Heading.h3>Unfortunately, you’ve been rejected</Heading.h3>

            <Text>
              You can start a new application by clicking{' '}
              here.
            </Text>
          </CustomCard>
        </Flex>
      ) : null}
      <CustomCard>
        <Heading.h3 mb={2}>How to get into Hack Club</Heading.h3>

        <Text>
          Our admissions process is very competitive, accepting less than 5% of
          applicants. Here’s what we look for in applicants:
        </Text>

            Strong founding teams with 2-3 members. You probably can’t do it
            alone and we rarely accept solo founders.

            Diverse skillsets on leadership team – the best Hack Clubs are led
            by both CEO and CTO types.

            Traction. Indicators of progress to date, especially formal shows of
            support from your school (like securing a teacher sponsor), are very
            meaningful.

        <Text>
          At the end of the day, to start a successful Hack Club all you have to
          do are two things: make a club that people want to attend and get it
          in front of the right people in the right way.
        </Text>
        <Text>
          Though we wish we could work with more clubs, our low acceptance rate
          is simply a result of too much interest and not enough time.
        </Text>
        <Heading.h3>After you apply</Heading.h3>

We’ll get back to you with our decision in 7 days

            If you’re accepted, we’ll schedule a call to train you to lead your
            club

            We’ll give you access to our curriculum, a template for your
            meetings, and our online community of club leaders

            Once you start holding meetings, we’ll check in with you each week
            to make sure everything is going well

        <Heading.h3>
          {submitted_at ? 'Your' : 'Edit your'} application
        </Heading.h3>

          Contact us at team@hackclub.com{' '}
          if you have any questions while applying.

            This application was{' '}
            {submitted_at !== null
              ? 'submitted'
              : updated_at === created_at ? 'created' : 'updated'}{' '}
            {timeSince(updated_at)} ago

            You have {leaderProfile.completed_at ? null : <Neg />} finished your
            leader profile

          {coLeaderProfiles.map((profile, index) => (

              {profile.user.email}
              {profile.completed_at ? null : (

                  has <Neg />{' '}

              )}
              finished their leader profile{' '}
              {isPoc ? (
                <Fragment>
                  (
                   {
                      if (
                        confirm(
                          `Are you sure you want to remove ${
                            profile.user.email
                          } as a team member?`
                        )
                      ) {
                        api
                          .delete(
                            `v1/new_club_applications/${id}/remove_user`,
                            {
                              authToken,
                              data: { user_id: profile.user.id }
                            }
                          )
                          .then(json => {
                            callback()
                            alert(
                              `${
                                profile.user.email
                              } has been removed from the application`
                            )
                          })
                      }
                    }}
                    style={{ cursor: 'pointer' }}
                  >
                    remove

                  )
                </Fragment>
              ) : null}

          ))}

        <Text mb={0}>Invite your co-leads:</Text>
        <LeaderInviteForm id={id} authToken={authToken} callback={callback} />
        <Text color="slate" f={1}>

            * We also accept applications from clubs that have already held
            meetings.

        </Text>
      </CustomCard>
      <Flex
        align="center"
        justify="center"
        flexDirection={['column', 'row']}
        mt={4}
        mx={[null, -2]}
      >
        <LargeButton.link
          w={1}
          m={2}
          inverted
          to={`/apply/club?id=${id}`}
          children={`${submitted_at ? 'View' : 'Edit'} Application`}
        />
        <LargeButton.link
          w={1}
          m={2}
          inverted
          to={`/apply/leader?id=${leaderProfile.id}`}
          children={`${submitted_at ? 'View' : 'Edit'} Leader Profile`}
        />
      </Flex>
      <Flex mt={2} mb={4}>
        <SubmitButton
          authToken={authToken}
          applicationId={app.id}
          status={submitButtonStatus}
          callback={callback}
        />
      </Flex>
    </Container>
  )
}

export default class extends Component {
  constructor(props) {
    super(props)

    this.state = {
      status: 'loading',
      app: undefined,
      authToken: undefined,
      userId: undefined
    }

    this.populateApplications = this.populateApplications.bind(this)
    this.createNewApplication = this.createNewApplication.bind(this)
  }

  createNewApplication(firstTime = false) {
    const msg =
      'If you start a new application you won’t be able to access this one. Continue?'
    if (!firstTime && !confirm(msg)) {
      return null
    }
    return api
      .post(`v1/users/${storage.get('userId')}/new_club_applications`, {
        authToken: storage.get('authToken')
      })
      .then(app => {
        return app
      })
  }

  populateApplications(application = null) {
    if (application) {
      this.setState({
        status: 'finished',
        app: application
      })
    } else {
      api
        .get(`v1/users/${storage.get('userId')}/new_club_applications`, {
          authToken: storage.get('authToken')
        })
        .then(json => {
          if (json.length === 0) {
            return this.createNewApplication(true)
          }
          return json.sort((a, b) => {
            return new Date(b.created_at) - new Date(a.created_at)
          })[0]
        })
        .then(app => {
          this.setState({
            status: 'finished',
            app: app
          })
        })
        .catch(e => {
          if (e.status === 401) {
            this.setState({ status: 'needsToAuth' })
          }
        })
    }
  }

  componentDidMount() {
    const authToken = storage.get('authToken')
    const userId = storage.get('userId')
    this.setState({ authToken, userId })
    const needsToAuth = authToken === null || userId === null

    if (needsToAuth) {
      this.setState({ status: 'needsToAuth' })
    } else {
      this.populateApplications()
    }
  }

  content() {
    const { app, status, authToken, userId } = this.state
    switch (status) {
      case 'needsToAuth':
        return <Login />
      case 'loading':
        return <LoadingAnimation />
      case 'finished':
        return (
          <Fragment>
            <ApplyNav breadcrumb={0} />
            <Heading.h1
              bg="primary"
              color="white"
              f={[5, 6]}
              mt={-1}
              py={4}
              px={3}
              align="center"
            >
              Apply to Hack Club
            </Heading.h1>
            <ApplicationCard
              app={app}
              userId={userId}
              authToken={authToken}
              callback={this.populateApplications}
              resetCallback={() => {
                this.createNewApplication.then(app => {
                  this.setState({
                    status: 'finished',
                    app: app
                  })
                })
              }}
            />
          </Fragment>
        )
      default:
        return <Text>Something terrible has happened.</Text>
    }
  }

  render() {
    return (
      <ThemeProvider>
        <Helmet title="Apply – Hack Club" />
        {this.content()}
      </ThemeProvider>
    )
  }
}
<commit_msg>Minor verbage improvement<commit_after>import React, { Component, Fragment } from 'react'
import Helmet from 'react-helmet'
import api from 'api'
import storage from 'storage'
import {
  ThemeProvider,
  Box,
  Button,
  Card,
  Container,
  Section,
  Flex,
  Heading,
  LargeButton,
  Link as DSLink,
  Text as T,
  cx
} from '@hackclub/design-system'
import { clubApplicationSchema } from 'components/apply/ClubApplicationForm'
import ApplyNav from 'components/apply/ApplyNav'
import LeaderInviteForm from 'components/apply/LeaderInviteForm'
import Login from 'components/apply/Login'
import SubmitButton from 'components/apply/SubmitButton'
import LoadingAnimation from 'components/LoadingAnimation'
import fetch from 'unfetch'
import Link from 'gatsby-link'

LargeButton.link = LargeButton.withComponent(Link)

const A = DSLink.extend`
  cursor: pointer;
  :hover {
    text-decoration: underline;
  }
`

const Text = props => <T mt={3} mb={3} {...props} />

const Span = T.span

const timeSince = time => {
  const seconds = Math.floor((new Date() - new Date(time)) / 1000)
  const intervals = [
    [Math.floor(seconds / (60 * 60 * 24 * 7)), 'weeks'],
    [Math.floor(seconds / (60 * 60 * 24)), 'days'],
    [Math.floor(seconds / (60 * 60)), 'hours'],
    [Math.floor(seconds / 60), 'minutes']
  ]
  for (var i = 0; i < intervals.length; i++) {
    let interval = intervals[i]
    if (interval[0] > 1) {
      return interval.join(' ')
    }
  }
  return 'less than a minute'
}

const Neg = () =>

const CustomCard = Card.extend.attrs({
  p: [3, 4],
  color: 'black',
  bg: 'snow',
  w: 1,
  boxShadowSize: 'md'
})``

const ApplicationCard = props => {
  const {
    id,
    leader_profiles,
    updated_at,
    created_at,
    submitted_at,
    point_of_contact_id
  } = props.app
  const { authToken, callback, app, resetCallback } = props

  const leaderProfile = leader_profiles.find(
    profile => profile.user.id == props.userId
  )
  const coLeaderProfiles = leader_profiles.filter(
    profile => profile.user.id != props.userId
  )
  const isPoc = leaderProfile.user.id === point_of_contact_id

  const completeProfiles = leader_profiles.every(
    profile => profile.completed_at
  )
  const completeApplication = clubApplicationSchema.isValidSync(app)
  let submitButtonStatus
  if (app.submitted_at) {
    submitButtonStatus = 'submitted'
  } else if (completeApplication && completeProfiles) {
    submitButtonStatus = 'complete'
  } else {
    submitButtonStatus = 'incomplete'
  }

  return (
    <Container maxWidth={36} my={3} p={3}>
      {app.rejected_at ? (
        <Flex mb={4}>
          <CustomCard>
            <Heading.h3>Unfortunately, you’ve been rejected</Heading.h3>

            <Text>
              You can start a new application by clicking{' '}
              here.
            </Text>
          </CustomCard>
        </Flex>
      ) : null}
      <CustomCard>
        <Heading.h3 mb={2}>How to get into Hack Club</Heading.h3>

        <Text>
          Our admissions process is very competitive, accepting less than 5% of
          applicants. Here’s what we look for:
        </Text>

            Strong founding teams with 2-3 members. You probably can’t do it
            alone and we rarely accept solo founders.

            Diverse skillsets on leadership team – the best Hack Clubs are led
            by both CEO and CTO types.

            Traction. Indicators of progress to date, especially formal shows of
            support from your school (like securing a teacher sponsor), are very
            meaningful.

        <Text>
          At the end of the day, to start a successful Hack Club all you have to
          do are two things: make a club that people want to attend and get it
          in front of the right people in the right way.
        </Text>
        <Text>
          Though we wish we could work with more clubs, our low acceptance rate
          is simply a result of too much interest and not enough time.
        </Text>
        <Heading.h3>After you apply</Heading.h3>

We’ll get back to you with our decision in 7 days

            If you’re accepted, we’ll schedule a call to train you to lead your
            club

            We’ll give you access to our curriculum, a template for your
            meetings, and our online community of club leaders

            Once you start holding meetings, we’ll check in with you each week
            to make sure everything is going well

        <Heading.h3>
          {submitted_at ? 'Your' : 'Edit your'} application
        </Heading.h3>

          Contact us at team@hackclub.com{' '}
          if you have any questions while applying.

            This application was{' '}
            {submitted_at !== null
              ? 'submitted'
              : updated_at === created_at ? 'created' : 'updated'}{' '}
            {timeSince(updated_at)} ago

            You have {leaderProfile.completed_at ? null : <Neg />} finished your
            leader profile

          {coLeaderProfiles.map((profile, index) => (

              {profile.user.email}
              {profile.completed_at ? null : (

                  has <Neg />{' '}

              )}
              finished their leader profile{' '}
              {isPoc ? (
                <Fragment>
                  (
                   {
                      if (
                        confirm(
                          `Are you sure you want to remove ${
                            profile.user.email
                          } as a team member?`
                        )
                      ) {
                        api
                          .delete(
                            `v1/new_club_applications/${id}/remove_user`,
                            {
                              authToken,
                              data: { user_id: profile.user.id }
                            }
                          )
                          .then(json => {
                            callback()
                            alert(
                              `${
                                profile.user.email
                              } has been removed from the application`
                            )
                          })
                      }
                    }}
                    style={{ cursor: 'pointer' }}
                  >
                    remove

                  )
                </Fragment>
              ) : null}

          ))}

        <Text mb={0}>Invite your co-leads:</Text>
        <LeaderInviteForm id={id} authToken={authToken} callback={callback} />
        <Text color="slate" f={1}>

            * We also accept applications from clubs that have already held
            meetings.

        </Text>
      </CustomCard>
      <Flex
        align="center"
        justify="center"
        flexDirection={['column', 'row']}
        mt={4}
        mx={[null, -2]}
      >
        <LargeButton.link
          w={1}
          m={2}
          inverted
          to={`/apply/club?id=${id}`}
          children={`${submitted_at ? 'View' : 'Edit'} Application`}
        />
        <LargeButton.link
          w={1}
          m={2}
          inverted
          to={`/apply/leader?id=${leaderProfile.id}`}
          children={`${submitted_at ? 'View' : 'Edit'} Leader Profile`}
        />
      </Flex>
      <Flex mt={2} mb={4}>
        <SubmitButton
          authToken={authToken}
          applicationId={app.id}
          status={submitButtonStatus}
          callback={callback}
        />
      </Flex>
    </Container>
  )
}

export default class extends Component {
  constructor(props) {
    super(props)

    this.state = {
      status: 'loading',
      app: undefined,
      authToken: undefined,
      userId: undefined
    }

    this.populateApplications = this.populateApplications.bind(this)
    this.createNewApplication = this.createNewApplication.bind(this)
  }

  createNewApplication(firstTime = false) {
    const msg =
      'If you start a new application you won’t be able to access this one. Continue?'
    if (!firstTime && !confirm(msg)) {
      return null
    }
    return api
      .post(`v1/users/${storage.get('userId')}/new_club_applications`, {
        authToken: storage.get('authToken')
      })
      .then(app => {
        return app
      })
  }

  populateApplications(application = null) {
    if (application) {
      this.setState({
        status: 'finished',
        app: application
      })
    } else {
      api
        .get(`v1/users/${storage.get('userId')}/new_club_applications`, {
          authToken: storage.get('authToken')
        })
        .then(json => {
          if (json.length === 0) {
            return this.createNewApplication(true)
          }
          return json.sort((a, b) => {
            return new Date(b.created_at) - new Date(a.created_at)
          })[0]
        })
        .then(app => {
          this.setState({
            status: 'finished',
            app: app
          })
        })
        .catch(e => {
          if (e.status === 401) {
            this.setState({ status: 'needsToAuth' })
          }
        })
    }
  }

  componentDidMount() {
    const authToken = storage.get('authToken')
    const userId = storage.get('userId')
    this.setState({ authToken, userId })
    const needsToAuth = authToken === null || userId === null

    if (needsToAuth) {
      this.setState({ status: 'needsToAuth' })
    } else {
      this.populateApplications()
    }
  }

  content() {
    const { app, status, authToken, userId } = this.state
    switch (status) {
      case 'needsToAuth':
        return <Login />
      case 'loading':
        return <LoadingAnimation />
      case 'finished':
        return (
          <Fragment>
            <ApplyNav breadcrumb={0} />
            <Heading.h1
              bg="primary"
              color="white"
              f={[5, 6]}
              mt={-1}
              py={4}
              px={3}
              align="center"
            >
              Apply to Hack Club
            </Heading.h1>
            <ApplicationCard
              app={app}
              userId={userId}
              authToken={authToken}
              callback={this.populateApplications}
              resetCallback={() => {
                this.createNewApplication.then(app => {
                  this.setState({
                    status: 'finished',
                    app: app
                  })
                })
              }}
            />
          </Fragment>
        )
      default:
        return <Text>Something terrible has happened.</Text>
    }
  }

  render() {
    return (
      <ThemeProvider>
        <Helmet title="Apply – Hack Club" />
        {this.content()}
      </ThemeProvider>
    )
  }
}
<|endoftext|>


<commit_before>import { tidyUpSequenceData } from "ve-sequence-utils";

function jsonToBed(jsonSequence, options = {}) {
  let sequenceInfo = options.featuresOnly
    ? jsonSequence
    : tidyUpSequenceData(jsonSequence);
  const { name, features, size, description, circular } = sequenceInfo;

  let sequenceNameToMatchFasta = "";
  sequenceNameToMatchFasta += `${name || "Untitled Sequence"}|`;
  sequenceNameToMatchFasta += "|" + size;
  sequenceNameToMatchFasta += description ? "|" + description : "";
  sequenceNameToMatchFasta += "|" + (circular ? "circular" : "linear");
  const sequenceNameToUse = options.sequenceName || sequenceNameToMatchFasta;
  let outString = "";
  outString += `track name="${sequenceNameToUse}" description="${name} Annotations" itemRgb="On"\n`;

  features.forEach(function(feat) {
    const { start, end, name, type, forward, strand } = feat;
    let label;
    if (name && type) {
      label = `${name}/${type}`;
    } else if (name) {
      label = name;
    } else {
      label = type;
    }
    let orientation;
    if (forward || strand === 1) {
      orientation = "+";
    } else if (!forward || strand === -1) {
      orientation = "-";
    } else {
      // "." = no strand
      orientation = ".";
    }
    // chromStart is 0-based, chromEnd of the BED file format is not included in the feature
    outString += `${sequenceNameToUse}\t${start}\t${end +
      1}\t${label}\t1000\t${orientation}\t${start}\t${start}\t65,105,225\n`;
  });
  return outString;
}

export default jsonToBed;
<commit_msg>removing spaces from annotation names in jsonToBed<commit_after>import { tidyUpSequenceData } from "ve-sequence-utils";

function jsonToBed(jsonSequence, options = {}) {
  let sequenceInfo = options.featuresOnly
    ? jsonSequence
    : tidyUpSequenceData(jsonSequence);
  const { name, features, size, description, circular } = sequenceInfo;

  let sequenceNameToMatchFasta = "";
  sequenceNameToMatchFasta += `${name || "Untitled Sequence"}|`;
  sequenceNameToMatchFasta += "|" + size;
  sequenceNameToMatchFasta += description ? "|" + description : "";
  sequenceNameToMatchFasta += "|" + (circular ? "circular" : "linear");
  const sequenceNameToUse = options.sequenceName || sequenceNameToMatchFasta;
  let outString = "";
  outString += `track name="${sequenceNameToUse}" description="${name} Annotations" itemRgb="On"\n`;

  features.forEach(function(feat) {
    const { start, end, name, type, forward, strand } = feat;
    let label;
    if (name && type) {
      label = `${name.replace(/\s/g, "")}/${type}`;
    } else if (name) {
      label = name;
    } else {
      label = type;
    }
    let orientation;
    if (forward || strand === 1) {
      orientation = "+";
    } else if (!forward || strand === -1) {
      orientation = "-";
    } else {
      // "." = no strand
      orientation = ".";
    }
    // chromStart is 0-based, chromEnd of the BED file format is not included in the feature
    outString += `${sequenceNameToUse}\t${start}\t${end +
      1}\t${label}\t1000\t${orientation}\t${start}\t${start}\t65,105,225\n`;
  });
  return outString;
}

export default jsonToBed;
<|endoftext|>


<commit_before>/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name CompoundPath
 *
 * @class A compound path contains two or more paths, holes are drawn
 * where the paths overlap. All the paths in a compound path take on the
 * style of the backmost path and can be accessed through its
 * {@link Item#children} list.
 *
 * @extends PathItem
 */
var CompoundPath = this.CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
	_type: 'compoundpath',
	/**
	 * Creates a new compound path item and places it in the active layer.
	 *
	 * @param {Path[]} [paths] the paths to place within the compound path.
	 *
	 * @example {@paperscript}
	 * // Create a circle shaped path with a hole in it:
	 * var circle = new Path.Circle(new Point(50, 50), 30);
	 * var innerCircle = new Path.Circle(new Point(50, 50), 10);
	 * var compoundPath = new CompoundPath([circle, innerCircle]);
	 * compoundPath.fillColor = 'red';
	 *
	 * // Move the inner circle 5pt to the right:
	 * compoundPath.children[1].position.x += 5;
	 */
	initialize: function(arg) {
		this.base();
		// CompoundPath has children and supports named children.
		this._children = [];
		this._namedChildren = {};
		if (!this._set(arg))
			this.addChildren(Array.isArray(arg) ? arg : arguments);
	},

	insertChild: function(index, item, _cloning) {
		// Only allow the insertion of paths
		if (item._type !== 'path')
			return null;
		item = this.base(index, item);
		// All children except for the bottom one (first one in list) are set
		// to anti-clockwise orientation, so that they appear as holes, but
		// only if their orientation was not already specified before
		// (= _clockwise is defined).
		if (!_cloning && item && item._clockwise === undefined)
			item.setClockwise(item._index == 0);
		return item;
	},

	/**
	 * If this is a compound path with only one path inside,
	 * the path is moved outside and the compound path is erased.
	 * Otherwise, the compound path is returned unmodified.
	 *
	 * @return {CompoundPath|Path} the flattened compound path
	 */
	reduce: function() {
		if (this._children.length == 1) {
			var child = this._children[0];
			child.insertAbove(this);
			this.remove();
			return child;
		}
		return this;
	},

	smooth: function() {
		for (var i = 0, l = this._children.length; i < l; i++)
			this._children[i].smooth();
	},

	/**
	 * The first Segment contained within the path.
	 *
	 * @type Segment
	 * @bean
	 */
	getFirstSegment: function() {
		var first = this.getFirstChild();
		return first && first.getFirstSegment();
	},

	/**
	 * The last Segment contained within the path.
	 *
	 * @type Segment
	 * @bean
	 */
	getLastSegment: function() {
		var last = this.getLastChild();
		return last && last.getLastSegment();
	},

	/**
	 * All the curves contained within the compound-path, from all its child
	 * {@link Path} items.
	 *
	 * @type Curve[]
	 * @bean
	 */
	getCurves: function() {
		var curves = [];
		for (var i = 0, l = this._children.length; i < l; i++)
			curves = curves.concat(this._children[i].getCurves());
		return curves;
	},

	/**
	 * The first Curve contained within the path.
	 *
	 * @type Curve
	 * @bean
	 */
	getFirstCurve: function() {
		var first = this.getFirstChild();
		return first && first.getFirstCurve();
	},

	/**
	 * The last Curve contained within the path.
	 *
	 * @type Curve
	 * @bean
	 */
	getLastCurve: function() {
		var last = this.getLastChild();
		return last && last.getFirstCurve();
	},

	/**
	 * A private method to help with both #contains() and #_hitTest().
	 */
	_contains: function(point) {
		// Compound-paths are a little complex: Due to the even-odd rule, in
		// order to determine wether a point is inside a path or not, we need to
		// check all the children and count how many intersect. If it's an odd
		// number, the point is inside the path. Once we know we're inside the
		// path, _hitTest also needs access to the first intersecting element,
		// so we return a list here.
		point = Point.read(arguments);
		var children = [];
		for (var i = 0, l = this._children.length; i < l; i++) {
			var child = this._children[i];
			if (child.contains(point))
				children.push(child);
		}
		return (children.length & 1) == 1 && children;
	},

	contains: function(point) {
		return !!this._contains(point);
	},

	_hitTest: function(point, options) {
		var res = this.base(point, Base.merge(options, { fill: false }));
		if (!res && options.fill && this._style._fillColor) {
			res = this._contains(point);
			res = res ? new HitResult('fill', res[0]) : null;
		}
		return res;
	},

	draw: function(ctx, param) {
		var children = this._children,
			style = this._style;
		// Return early if the compound path doesn't have any children:
		if (children.length == 0)
			return;
		ctx.beginPath();
		param.compound = true;
		for (var i = 0, l = children.length; i < l; i++)
			Item.draw(children[i], ctx, param);
		param.compound = false;
		if (this._clipMask) {
			ctx.clip();
		} else {
			this._setStyles(ctx);
			if (style._fillColor)
				ctx.fill();
			if (style._strokeColor)
				ctx.stroke();
		}
	}
}, new function() { // Injection scope for PostScript-like drawing functions
	/**
	 * Helper method that returns the current path and checks if a moveTo()
	 * command is required first.
	 */
	function getCurrentPath(that) {
		if (!that._children.length)
			throw new Error('Use a moveTo() command first');
		return that._children[that._children.length - 1];
	}

	var fields = {
		// Note: Documentation for these methods is found in PathItem, as they
		// are considered abstract methods of PathItem and need to be defined in
		// all implementing classes.
		moveTo: function(point) {
			var path = new Path();
			this.addChild(path);
			path.moveTo.apply(path, arguments);
		},

		moveBy: function(point) {
			this.moveTo(getCurrentPath(this).getLastSegment()._point.add(
					Point.read(arguments)));
		},

		closePath: function() {
			getCurrentPath(this).setClosed(true);
		}
	};

	// Redirect all other drawing commands to the current path
	Base.each(['lineTo', 'cubicCurveTo', 'quadraticCurveTo', 'curveTo',
			'arcTo', 'lineBy', 'curveBy', 'arcBy'], function(key) {
		fields[key] = function() {
			var path = getCurrentPath(this);
			path[key].apply(path, arguments);
		};
	});

	return fields;
});
<commit_msg>Clean up hitTest code a little.<commit_after>/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name CompoundPath
 *
 * @class A compound path contains two or more paths, holes are drawn
 * where the paths overlap. All the paths in a compound path take on the
 * style of the backmost path and can be accessed through its
 * {@link Item#children} list.
 *
 * @extends PathItem
 */
var CompoundPath = this.CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
	_type: 'compoundpath',
	/**
	 * Creates a new compound path item and places it in the active layer.
	 *
	 * @param {Path[]} [paths] the paths to place within the compound path.
	 *
	 * @example {@paperscript}
	 * // Create a circle shaped path with a hole in it:
	 * var circle = new Path.Circle(new Point(50, 50), 30);
	 * var innerCircle = new Path.Circle(new Point(50, 50), 10);
	 * var compoundPath = new CompoundPath([circle, innerCircle]);
	 * compoundPath.fillColor = 'red';
	 *
	 * // Move the inner circle 5pt to the right:
	 * compoundPath.children[1].position.x += 5;
	 */
	initialize: function(arg) {
		this.base();
		// CompoundPath has children and supports named children.
		this._children = [];
		this._namedChildren = {};
		if (!this._set(arg))
			this.addChildren(Array.isArray(arg) ? arg : arguments);
	},

	insertChild: function(index, item, _cloning) {
		// Only allow the insertion of paths
		if (item._type !== 'path')
			return null;
		item = this.base(index, item);
		// All children except for the bottom one (first one in list) are set
		// to anti-clockwise orientation, so that they appear as holes, but
		// only if their orientation was not already specified before
		// (= _clockwise is defined).
		if (!_cloning && item && item._clockwise === undefined)
			item.setClockwise(item._index == 0);
		return item;
	},

	/**
	 * If this is a compound path with only one path inside,
	 * the path is moved outside and the compound path is erased.
	 * Otherwise, the compound path is returned unmodified.
	 *
	 * @return {CompoundPath|Path} the flattened compound path
	 */
	reduce: function() {
		if (this._children.length == 1) {
			var child = this._children[0];
			child.insertAbove(this);
			this.remove();
			return child;
		}
		return this;
	},

	smooth: function() {
		for (var i = 0, l = this._children.length; i < l; i++)
			this._children[i].smooth();
	},

	/**
	 * The first Segment contained within the path.
	 *
	 * @type Segment
	 * @bean
	 */
	getFirstSegment: function() {
		var first = this.getFirstChild();
		return first && first.getFirstSegment();
	},

	/**
	 * The last Segment contained within the path.
	 *
	 * @type Segment
	 * @bean
	 */
	getLastSegment: function() {
		var last = this.getLastChild();
		return last && last.getLastSegment();
	},

	/**
	 * All the curves contained within the compound-path, from all its child
	 * {@link Path} items.
	 *
	 * @type Curve[]
	 * @bean
	 */
	getCurves: function() {
		var curves = [];
		for (var i = 0, l = this._children.length; i < l; i++)
			curves = curves.concat(this._children[i].getCurves());
		return curves;
	},

	/**
	 * The first Curve contained within the path.
	 *
	 * @type Curve
	 * @bean
	 */
	getFirstCurve: function() {
		var first = this.getFirstChild();
		return first && first.getFirstCurve();
	},

	/**
	 * The last Curve contained within the path.
	 *
	 * @type Curve
	 * @bean
	 */
	getLastCurve: function() {
		var last = this.getLastChild();
		return last && last.getFirstCurve();
	},

	/**
	 * A private method to help with both #contains() and #_hitTest().
	 */
	_contains: function(point) {
		// Compound paths are a little complex: In order to determine wether a
		// point is inside a path or not due to the even-odd rule, we need to
		// check all the children and count how many intersect. If it's an odd
		// number, the point is inside the path. Once we know it's inside the
		// path, _hitTest also needs access to the first intersecting element,
		// for the HitResult, so we collect and return a list here.
		var children = [];
		for (var i = 0, l = this._children.length; i < l; i++) {
			var child = this._children[i];
			if (child.contains(point))
				children.push(child);
		}
		return (children.length & 1) == 1 && children;
	},

	contains: function(point) {
		return !!this._contains(Point.read(arguments));
	},

	_hitTest: function(point, options) {
		var res = this.base(point, Base.merge(options, { fill: false }));
		if (!res && options.fill && this._style._fillColor) {
			res = this._contains(point);
			res = res ? new HitResult('fill', res[0]) : null;
		}
		return res;
	},

	draw: function(ctx, param) {
		var children = this._children,
			style = this._style;
		// Return early if the compound path doesn't have any children:
		if (children.length == 0)
			return;
		ctx.beginPath();
		param.compound = true;
		for (var i = 0, l = children.length; i < l; i++)
			Item.draw(children[i], ctx, param);
		param.compound = false;
		if (this._clipMask) {
			ctx.clip();
		} else {
			this._setStyles(ctx);
			if (style._fillColor)
				ctx.fill();
			if (style._strokeColor)
				ctx.stroke();
		}
	}
}, new function() { // Injection scope for PostScript-like drawing functions
	/**
	 * Helper method that returns the current path and checks if a moveTo()
	 * command is required first.
	 */
	function getCurrentPath(that) {
		if (!that._children.length)
			throw new Error('Use a moveTo() command first');
		return that._children[that._children.length - 1];
	}

	var fields = {
		// Note: Documentation for these methods is found in PathItem, as they
		// are considered abstract methods of PathItem and need to be defined in
		// all implementing classes.
		moveTo: function(point) {
			var path = new Path();
			this.addChild(path);
			path.moveTo.apply(path, arguments);
		},

		moveBy: function(point) {
			this.moveTo(getCurrentPath(this).getLastSegment()._point.add(
					Point.read(arguments)));
		},

		closePath: function() {
			getCurrentPath(this).setClosed(true);
		}
	};

	// Redirect all other drawing commands to the current path
	Base.each(['lineTo', 'cubicCurveTo', 'quadraticCurveTo', 'curveTo',
			'arcTo', 'lineBy', 'curveBy', 'arcBy'], function(key) {
		fields[key] = function() {
			var path = getCurrentPath(this);
			path[key].apply(path, arguments);
		};
	});

	return fields;
});
<|endoftext|>


<commit_before>define([
    "jquery",
    "../registry",
    '../core/logger',
    "jquery_validate"
], function($, patterns, logger) {
    var log = logger.getLogger('pat.validate');

    var pattern_spec = {
        name: "validate",
        trigger: "form.validate",

        init: function($el) {
            var validators = $el.find('[data-validator]').toArray().reduce(function(acc, el) {
                var i, path_to_func, $el = $(el),
                    varray = $el.data('validator').split(' ', 2),
                    message = $el.data('validator-message');

                path_to_func = varray[1].split('.');
                func = window[path_to_func[0]];
                for (i=1; i<path_to_func.length; i++) {
                    try {
                        func = func[path_to_func[i]];
                    } catch(e) {
                        log.error('Could not find the validator function: ' + varray[1]);
                    }
                }
                $.validator.addMethod(varray[0], func, message);
            }, {});

            var rules = $el.find('[data-required-if]').toArray().reduce(function(acc, el) {
                var $el = $(el),
                    id = $el.attr('id');
                if (!id) {
                    log.error('Element needs id, skipping:', $el);
                    return acc;
                }
                acc[id] = {required: $el.data('required-if')};
                return acc;
            }, {});
            log.debug('rules:', rules);

            // ATTENTION: adding the debug option to validate, disables
            // form submission
            $el.validate({rules: rules});
            return $el;
        }
    };

    patterns.register(pattern_spec);
    return pattern_spec;
});
// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<commit_msg>declare variable, whitespace<commit_after>define([
    "jquery",
    "../registry",
    '../core/logger',
    "jquery_validate"
], function($, patterns, logger) {
    var log = logger.getLogger('pat.validate');

    var pattern_spec = {
        name: "validate",
        trigger: "form.validate",

        init: function($el) {
            var validators = $el.find('[data-validator]').toArray().reduce(function(acc, el) {
                var i, path_to_func, $el = $(el),
                    varray = $el.data('validator').split(' ', 2),
                    message = $el.data('validator-message');

                path_to_func = varray[1].split('.');
                var func = window[path_to_func[0]];
                for (i=1; i<path_to_func.length; i++) {
                    try {
                        func = func[path_to_func[i]];
                    } catch(e) {
                        log.error('Could not find the validator function: ' + varray[1]);
                    }
                }
                $.validator.addMethod(varray[0], func, message);
            }, {});

            var rules = $el.find('[data-required-if]').toArray().reduce(function(acc, el) {
                var $el = $(el),
                    id = $el.attr('id');
                if (!id) {
                    log.error('Element needs id, skipping:', $el);
                    return acc;
                }
                acc[id] = {required: $el.data('required-if')};
                return acc;
            }, {});
            log.debug('rules:', rules);

            // ATTENTION: adding the debug option to validate, disables
            // form submission
            $el.validate({rules: rules});
            return $el;
        }
    };

    patterns.register(pattern_spec);
    return pattern_spec;
});
// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<|endoftext|>


<commit_before>const {assert} = require('chai'),
    Transformer = require('../dist/Transformer'),
    {inspect} = require('util')

class Foo {
    constructor() {}
    toString() { return '<foo>' }
}
function isFoo(x) { return x instanceof Foo }
function enc(x) { return null }
function dec() { return new Foo() }

class Tree {
    constructor(val, children=[]) {
        this.val = val
        this.children = children.map((child) =>
            child instanceof Tree ? child : new Tree(child))
    }
    toString() {
        let val = this.val.toString(),
            children = this.children.map((c) => c.toString()).join(', ')
        return `Tree{ ${val}, [ ${children} ] }`
    }
}

describe('Transformer', () => {

    let spec1 = {token: 'foo', class: Foo,   encode: enc, decode: dec},
        spec2 = {token: 'foo', class: Foo,   encode: enc},
        spec3 = {token: 'foo', pred:  isFoo, encode: enc, decode: dec},
        t     = new Transformer([spec1]),
        foo   = new Foo

    describe('#validateSpec()', () => {

        let val = Transformer.prototype.validateSpec

        it('passes valid spec', () => {
            assert.strictEqual(undefined, val(spec1))
            assert.strictEqual(undefined, val(spec2))
        })

        it('tells if spec is not a plain object', () => {
            assert.strictEqual('spec is not a plain object', val(null))
            assert.strictEqual('spec is not a plain object', val(2))
        })

        it('tells if there are no correct token', () => {
            assert.strictEqual('missing token', val({class: Foo, encode: enc}))
            assert.strictEqual('missing token', val({class: Foo, encode: enc, token: ''}))
            assert.strictEqual('missing token', val({class: Foo, encode: enc, token: 3}))
        })

        it('tells if there are no correct class or predicate', () => {
            assert.strictEqual('missing class or predicate', val({token: 'foo', encode: enc}))
            assert.strictEqual('missing class or predicate', val({token: 'foo', encode: enc, class: 3}))
            assert.strictEqual('missing class or predicate', val({token: 'foo', encode: enc, pred: 'foo'}))
        })

        it('tells if there are no correct encode method', () => {
            assert.strictEqual('missing encode method', val({token: 'foo', class: Foo}))
            assert.strictEqual('missing encode method', val({token: 'foo', class: Foo, encode: 3}))
        })

        it('tells if there are incorrect decode method', () => {
            assert.strictEqual('invalid decode method', val({token: 'foo', class: Foo, encode: enc, decode: 3}))
        })

    })

    describe('#validateSpecs()', () => {

        let val = Transformer.prototype.validateSpecs.bind(Transformer.prototype)

        it('passes array of valid specs', () => {
            assert.strictEqual(undefined, val([]))
            assert.strictEqual(undefined, val([spec1, spec2]))
        })

        it('tells if non-array given', () => {
            assert.strictEqual('expected array of specs', val(null))
            assert.strictEqual('expected array of specs', val({}))
        })

        it('returns message for the first error found', () => {
            assert.strictEqual('missing encode method', val([{token: 'foo', class: Foo}]))
            assert.strictEqual('missing encode method', val([spec1, {token: 'foo', class: Foo}]))
        })

    })

    describe('#validateConsistency()', () => {

        let val = Transformer.prototype.validateConsistency

        it('passes if there are only one incoder and decoder for each token', () => {
            let barSpec = {token: 'bar', class: Foo, encode: enc, decode: dec}
            assert.strictEqual(undefined, val([spec1, barSpec]))
        })

        it('tells if there are no decoder for some token', () => {
            assert.strictEqual('0 decoders for token foo', val([spec2]))
        })

        it('tells if there are more than one decoder for some token', () => {
            assert.strictEqual('2 decoders for token foo', val([spec1, spec1]))
        })

        it('tells if there are no encoder for some token', () => {
            assert.strictEqual('no encoders for token foo', val([{token: 'foo', class: Foo, decode: dec}]))
        })

        it('passes if there are more than one encoder for some token', () => {
            assert.strictEqual(undefined, val([spec1, spec2]))
        })

    })

    describe('#constructor()', () => {

        it('checks specs with #validateSpecs() throwing it\'s messages as errors', () => {
            let test = () => new Transformer({})
            assert.throws(test, 'Failed to create transformer: expected array of specs')
        })

        it('check specs with #validateConsistency() throwint it\'s messages as errors', () => {
            let test = () => new Transformer([spec2])
            assert.throws(test, 'Failed to create transformer: 0 decoders for token foo')
        })

        it('preserves specs if valid', () => {
            let specs = [spec1, spec2]
            assert.strictEqual(specs, (new Transformer(specs)).specs)
        })

        it('assigns prefix and serializer from params', () => {
            let t = new Transformer([], {prefix: 'foo', serializer: 'bar'})
            assert.strictEqual('foo', t.prefix)
            assert.strictEqual('bar', t.serializer)
        })

        it('uses default prefix and serializer if not provided', () => {
            assert.strictEqual('$', t.prefix)
            assert.strictEqual(JSON, t.serializer)
        })

    })

    let treeSpec = {
            token: 'tree',
            class: Tree,
            encode: (tree) => ({val: tree.val, children: tree.children}),
            decode: (obj) => new Tree(obj.val, obj.children)
        },
        treeT = new Transformer([spec1, treeSpec]),
        tree = new Tree(foo, [new Tree(foo)]),
        treeRepr = {$tree: {
            val: {$foo: null},
            children: [
                {$tree: {
                    val: {$foo: null},
                    children: []
                }}
            ]
        }}

    describe('#encode()', () => {

        it('does not change scalar values by default', () => {
            assert.strictEqual(t.encode(3), 3)
            assert.strictEqual(t.encode('x'), 'x')
            assert.strictEqual(t.encode(null), null)
            assert.strictEqual(t.encode(undefined), undefined)
        })

        it('clones arrays and plain objects by default', () => {
            let arr = [3],
                obj = {x: 3}
            assert.notEqual(t.encode(arr), arr)
            assert.notEqual(t.encode(obj), obj)
            assert.deepEqual(t.encode(arr), arr)
            assert.deepEqual(t.encode(obj), obj)
        })

        it('converts instance if spec class matches', () => {
            assert.deepEqual(t.encode(foo), {$foo: null})
        })

        it('converts value if spec predicate matches', () => {
            let t = new Transformer([spec3])
            assert.deepEqual(t.encode(foo), {$foo: null})
        })

        it('converts values inside nested arrays and plain objects', () => {
            let arr = [[foo]],
                obj = {baz: {bar: foo}}
            assert.deepEqual(t.encode(arr), [[{$foo: null}]])
            assert.deepEqual(t.encode(obj), {baz: {bar: {$foo: null}}})
        })

        it('converts values inside encoded objects', () => {
            assert.deepEqual(treeT.encode(tree), treeRepr)
        })

    })

    describe('#_encode()', () => {

        it('copies object and array by default', () => {
            let obj = {foo: 3, bar: 14},
                arr = [3, 14]
            assert.notEqual(t._encode(obj), obj)
            assert.notEqual(t._encode(arr), arr)
            assert.deepEqual(t._encode(obj), obj)
            assert.deepEqual(t._encode(arr), arr)
        })

        it('modifies object when `mutate` argument is true', () => {
            let obj = {foo: 3, bar: 14},
                arr = [3, 14]
            assert.strictEqual(t._encode(obj, true), obj)
            assert.strictEqual(t._encode(arr, true), arr)
        })

    })

    describe('#decode()', () => {

        it('doesn\'t change scalar values', () => {
            assert.strictEqual(t.decode(3), 3)
            assert.strictEqual(t.decode('x'), 'x')
            assert.strictEqual(t.decode(null), null)
            assert.strictEqual(t.decode(undefined), undefined)
        })

        it('clones arrays and plain objects without tokens', () => {
            let arr = [3],
                obj = {x: 3}
            assert.notEqual(t.decode(arr), arr)
            assert.notEqual(t.decode(obj), obj)
            assert.deepEqual(t.decode(arr), arr)
            assert.deepEqual(t.decode(obj), obj)
        })

        it('recognizes tokens in object keys and decodes values', () => {
            assert.instanceOf(t.decode({$foo: null}), Foo)
        })

        it('recognizes tokens in nested objects and arrays', () => {
            let encoded1 = [[{$foo: null}]],
                decoded1 = [[foo]],
                encoded2 = {baz: {bar: {$foo: null}}},
                decoded2 = {baz: {bar: foo}}
            assert.deepEqual(t.decode(encoded1), decoded1)
            assert.deepEqual(t.decode(encoded2), decoded2)
            assert.instanceOf(t.decode(encoded1)[0][0], Foo)
            assert.instanceOf(t.decode(encoded2).baz.bar, Foo)
        })

        it('recreates encoded objects recursively', () => {
            let decoded = treeT.decode(treeRepr)
            assert.instanceOf(decoded.val, Foo)
            assert.instanceOf(decoded.children[0], Tree)
            assert.instanceOf(decoded.children[0].val, Foo)
        })

    })

})
<commit_msg>Update tests<commit_after>const {assert} = require('chai'),
    Transformer = require('../dist/Transformer'),
    {inspect} = require('util')

class Foo {
    constructor() {}
    toString() { return '<foo>' }
}
class Bar {
    constructor() {}
    toString() { return '<bar>' }
}
function isFoo(x) { return x instanceof Foo }
function enc(x) { return null }
function dec() { return new Foo() }

class Tree {
    constructor(val, children=[]) {
        this.val = val
        this.children = children.map((child) =>
            child instanceof Tree ? child : new Tree(child))
    }
    toString() {
        let val = this.val.toString(),
            children = this.children.map((c) => c.toString()).join(', ')
        return `Tree{ ${val}, [ ${children} ] }`
    }
}

describe('Transformer', () => {

    let spec1 = {token: 'foo', class: Foo,   encode: enc, decode: dec},
        spec2 = {token: 'foo', class: Foo,   encode: enc},
        spec3 = {token: 'foo', pred:  isFoo, encode: enc, decode: dec},
        treeSpec = {
            token: 'tree',
            class: Tree,
            encode: (tree) => ({val: tree.val, children: tree.children}),
            decode: (obj) => new Tree(obj.val, obj.children)
        },
        t     = new Transformer([spec1]),
        treeT = new Transformer([spec1, treeSpec]),
        foo   = new Foo,
        bar   = new Bar,
        tree = new Tree(foo, [new Tree(foo)]),
        treeRepr = {$tree: {
            val: {$foo: null},
            children: [
                {$tree: {
                    val: {$foo: null},
                    children: []
                }}
            ]
        }}

    describe('#validateSpec()', () => {

        let val = Transformer.prototype.validateSpec

        it('passes valid spec', () => {
            assert.strictEqual(undefined, val(spec1))
            assert.strictEqual(undefined, val(spec2))
        })

        it('tells if spec is not a plain object', () => {
            assert.strictEqual('spec is not a plain object', val(null))
            assert.strictEqual('spec is not a plain object', val(2))
        })

        it('tells if there are no correct token', () => {
            assert.strictEqual('missing token', val({class: Foo, encode: enc}))
            assert.strictEqual('missing token', val({class: Foo, encode: enc, token: ''}))
            assert.strictEqual('missing token', val({class: Foo, encode: enc, token: 3}))
        })

        it('tells if there are no correct class or predicate', () => {
            assert.strictEqual('missing class or predicate', val({token: 'foo', encode: enc}))
            assert.strictEqual('missing class or predicate', val({token: 'foo', encode: enc, class: 3}))
            assert.strictEqual('missing class or predicate', val({token: 'foo', encode: enc, pred: 'foo'}))
        })

        it('tells if there are no correct encode method', () => {
            assert.strictEqual('missing encode method', val({token: 'foo', class: Foo}))
            assert.strictEqual('missing encode method', val({token: 'foo', class: Foo, encode: 3}))
        })

        it('tells if there are incorrect decode method', () => {
            assert.strictEqual('invalid decode method', val({token: 'foo', class: Foo, encode: enc, decode: 3}))
        })

    })

    describe('#validateSpecs()', () => {

        let val = Transformer.prototype.validateSpecs.bind(Transformer.prototype)

        it('passes array of valid specs', () => {
            assert.strictEqual(undefined, val([]))
            assert.strictEqual(undefined, val([spec1, spec2]))
        })

        it('tells if non-array given', () => {
            assert.strictEqual('expected array of specs', val(null))
            assert.strictEqual('expected array of specs', val({}))
        })

        it('returns message for the first error found', () => {
            assert.strictEqual('missing encode method', val([{token: 'foo', class: Foo}]))
            assert.strictEqual('missing encode method', val([spec1, {token: 'foo', class: Foo}]))
        })

    })

    describe('#validateConsistency()', () => {

        let val = Transformer.prototype.validateConsistency

        it('passes if there are only one incoder and decoder for each token', () => {
            let barSpec = {token: 'bar', class: Bar, encode: enc, decode: dec}
            assert.strictEqual(undefined, val([spec1, barSpec]))
        })

        it('tells if there are no decoder for some token', () => {
            assert.strictEqual('0 decoders for token foo', val([spec2]))
        })

        it('tells if there are more than one decoder for some token', () => {
            assert.strictEqual('2 decoders for token foo', val([spec1, spec1]))
        })

        it('tells if there are no encoder for some token', () => {
            assert.strictEqual('no encoders for token foo', val([{token: 'foo', class: Foo, decode: dec}]))
        })

        it('tells if there are more than one spec contain some class', () => {
            assert.strictEqual('some specs contain the same class', val([spec1, spec2]))
        })

    })

    describe('#constructor()', () => {

        it('checks specs with #validateSpecs() throwing it\'s messages as errors', () => {
            let test = () => new Transformer({})
            assert.throws(test, 'Failed to create transformer: expected array of specs')
        })

        it('check specs with #validateConsistency() throwint it\'s messages as errors', () => {
            let test = () => new Transformer([spec2])
            assert.throws(test, 'Failed to create transformer: 0 decoders for token foo')
        })

        it('preserves specs if valid', () => {
            let specs = [spec1, treeSpec]
            assert.strictEqual(specs, (new Transformer(specs)).specs)
        })

        it('assigns prefix and serializer from params', () => {
            let t = new Transformer([], {prefix: 'foo', serializer: 'bar'})
            assert.strictEqual('foo', t.prefix)
            assert.strictEqual('bar', t.serializer)
        })

        it('uses default prefix and serializer if not provided', () => {
            assert.strictEqual('$', t.prefix)
            assert.strictEqual(JSON, t.serializer)
        })

    })

    describe('#encode()', () => {

        it('does not change scalar values by default', () => {
            assert.strictEqual(t.encode(3), 3)
            assert.strictEqual(t.encode('x'), 'x')
            assert.strictEqual(t.encode(null), null)
            assert.strictEqual(t.encode(undefined), undefined)
        })

        it('clones arrays and plain objects by default', () => {
            let arr = [3],
                obj = {x: 3}
            assert.notEqual(t.encode(arr), arr)
            assert.notEqual(t.encode(obj), obj)
            assert.deepEqual(t.encode(arr), arr)
            assert.deepEqual(t.encode(obj), obj)
        })

        it('converts instance if spec class matches', () => {
            assert.deepEqual(t.encode(foo), {$foo: null})
        })

        it('converts value if spec predicate matches', () => {
            let t = new Transformer([spec3])
            assert.deepEqual(t.encode(foo), {$foo: null})
        })

        it('converts values inside nested arrays and plain objects', () => {
            let arr = [[foo]],
                obj = {baz: {bar: foo}}
            assert.deepEqual(t.encode(arr), [[{$foo: null}]])
            assert.deepEqual(t.encode(obj), {baz: {bar: {$foo: null}}})
        })

        it('converts values inside encoded objects', () => {
            assert.deepEqual(treeT.encode(tree), treeRepr)
        })

    })

    describe('#_encode()', () => {

        it('copies object and array by default', () => {
            let obj = {foo: 3, bar: 14},
                arr = [3, 14]
            assert.notEqual(t._encode(obj), obj)
            assert.notEqual(t._encode(arr), arr)
            assert.deepEqual(t._encode(obj), obj)
            assert.deepEqual(t._encode(arr), arr)
        })

        it('modifies object when `mutate` argument is true', () => {
            let obj = {foo: 3, bar: 14},
                arr = [3, 14]
            assert.strictEqual(t._encode(obj, true), obj)
            assert.strictEqual(t._encode(arr, true), arr)
        })

    })

    describe('#decode()', () => {

        it('doesn\'t change scalar values', () => {
            assert.strictEqual(t.decode(3), 3)
            assert.strictEqual(t.decode('x'), 'x')
            assert.strictEqual(t.decode(null), null)
            assert.strictEqual(t.decode(undefined), undefined)
        })

        it('clones arrays and plain objects without tokens', () => {
            let arr = [3],
                obj = {x: 3}
            assert.notEqual(t.decode(arr), arr)
            assert.notEqual(t.decode(obj), obj)
            assert.deepEqual(t.decode(arr), arr)
            assert.deepEqual(t.decode(obj), obj)
        })

        it('recognizes tokens in object keys and decodes values', () => {
            assert.instanceOf(t.decode({$foo: null}), Foo)
        })

        it('recognizes tokens in nested objects and arrays', () => {
            let encoded1 = [[{$foo: null}]],
                decoded1 = [[foo]],
                encoded2 = {baz: {bar: {$foo: null}}},
                decoded2 = {baz: {bar: foo}}
            assert.deepEqual(t.decode(encoded1), decoded1)
            assert.deepEqual(t.decode(encoded2), decoded2)
            assert.instanceOf(t.decode(encoded1)[0][0], Foo)
            assert.instanceOf(t.decode(encoded2).baz.bar, Foo)
        })

        it('recreates encoded objects recursively', () => {
            let decoded = treeT.decode(treeRepr)
            assert.instanceOf(decoded.val, Foo)
            assert.instanceOf(decoded.children[0], Tree)
            assert.instanceOf(decoded.children[0].val, Foo)
        })

    })

})
<|endoftext|>